summaryrefslogtreecommitdiffstats
path: root/layout/printing
diff options
context:
space:
mode:
Diffstat (limited to 'layout/printing')
-rw-r--r--layout/printing/DrawEventRecorder.cpp51
-rw-r--r--layout/printing/DrawEventRecorder.h171
-rw-r--r--layout/printing/PrintTranslator.cpp90
-rw-r--r--layout/printing/PrintTranslator.h179
-rw-r--r--layout/printing/crashtests/1662259.html7
-rw-r--r--layout/printing/crashtests/1663722.html14
-rw-r--r--layout/printing/crashtests/1671503.html12
-rw-r--r--layout/printing/crashtests/1748277.html26
-rw-r--r--layout/printing/crashtests/1748277.pngbin0 -> 228 bytes
-rw-r--r--layout/printing/crashtests/1758199-1.html54
-rw-r--r--layout/printing/crashtests/1804571.html11
-rw-r--r--layout/printing/crashtests/1804794.html10
-rw-r--r--layout/printing/crashtests/1804798.html7
-rw-r--r--layout/printing/crashtests/1819468-1.html21
-rw-r--r--layout/printing/crashtests/1819468-2.html18
-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.list14
-rw-r--r--layout/printing/ipc/PRemotePrintJob.ipdl57
-rw-r--r--layout/printing/ipc/RemotePrintJobChild.cpp171
-rw-r--r--layout/printing/ipc/RemotePrintJobChild.h69
-rw-r--r--layout/printing/ipc/RemotePrintJobParent.cpp344
-rw-r--r--layout/printing/ipc/RemotePrintJobParent.h102
-rw-r--r--layout/printing/moz.build33
-rw-r--r--layout/printing/nsPagePrintTimer.cpp224
-rw-r--r--layout/printing/nsPagePrintTimer.h84
-rw-r--r--layout/printing/nsPrintData.cpp126
-rw-r--r--layout/printing/nsPrintData.h53
-rw-r--r--layout/printing/nsPrintJob.cpp2427
-rw-r--r--layout/printing/nsPrintJob.h311
-rw-r--r--layout/printing/nsPrintObject.cpp93
-rw-r--r--layout/printing/nsPrintObject.h88
34 files changed, 4904 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..26adf8862a
--- /dev/null
+++ b/layout/printing/DrawEventRecorder.h
@@ -0,0 +1,171 @@
+/* -*- 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();
+
+ gfx::RecorderType GetRecorderType() const final {
+ return gfx::RecorderType::PRFILEDESC;
+ }
+
+ 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..d84c26e959
--- /dev/null
+++ b/layout/printing/PrintTranslator.cpp
@@ -0,0 +1,90 @@
+/* -*- 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/ProfilerMarkers.h"
+#include "mozilla/UniquePtr.h"
+#include "InlineTranslator.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace layout {
+
+PrintTranslator::PrintTranslator(nsDeviceContext* aDeviceContext)
+ : mDeviceContext(aDeviceContext) {
+ UniquePtr<gfxContext> context =
+ mDeviceContext->CreateReferenceRenderingContext();
+ mBaseDT = context->GetDrawTarget();
+}
+
+bool PrintTranslator::TranslateRecording(PRFileDescStream& aRecording) {
+ AUTO_PROFILER_MARKER_TEXT("PrintTranslator", LAYOUT_Printing, {},
+ "PrintTranslator::TranslateRecording"_ns);
+
+ 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;
+ }
+
+ uint8_t eventType = RecordedEvent::EventType::INVALID;
+ 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) {
+ UniquePtr<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();
+}
+
+} // namespace layout
+} // namespace mozilla
diff --git a/layout/printing/PrintTranslator.h b/layout/printing/PrintTranslator.h
new file mode 100644
index 0000000000..629b54854d
--- /dev/null
+++ b/layout/printing/PrintTranslator.h
@@ -0,0 +1,179 @@
+/* -*- 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"
+
+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);
+
+ 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;
+ }
+
+ already_AddRefed<GradientStops> LookupGradientStops(
+ ReferencePtr aRefPtr) final {
+ return mGradientStops.Get(aRefPtr);
+ }
+
+ 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;
+ }
+
+ void AddDrawTarget(ReferencePtr aRefPtr, DrawTarget* aDT) final {
+ mDrawTargets.InsertOrUpdate(aRefPtr, RefPtr{aDT});
+ }
+
+ void AddPath(ReferencePtr aRefPtr, Path* aPath) final {
+ mPaths.InsertOrUpdate(aRefPtr, RefPtr{aPath});
+ }
+
+ void AddSourceSurface(ReferencePtr aRefPtr, SourceSurface* aSurface) final {
+ mSourceSurfaces.InsertOrUpdate(aRefPtr, RefPtr{aSurface});
+ }
+
+ void AddFilterNode(ReferencePtr aRefPtr, FilterNode* aFilter) final {
+ mFilterNodes.InsertOrUpdate(aRefPtr, RefPtr{aFilter});
+ }
+
+ void AddGradientStops(ReferencePtr aRefPtr, GradientStops* aStops) final {
+ mGradientStops.InsertOrUpdate(aRefPtr, RefPtr{aStops});
+ }
+
+ void AddScaledFont(ReferencePtr aRefPtr, ScaledFont* aScaledFont) final {
+ mScaledFonts.InsertOrUpdate(aRefPtr, RefPtr{aScaledFont});
+ }
+
+ void AddUnscaledFont(ReferencePtr aRefPtr,
+ UnscaledFont* aUnscaledFont) final {
+ mUnscaledFonts.InsertOrUpdate(aRefPtr, RefPtr{aUnscaledFont});
+ }
+
+ void AddNativeFontResource(uint64_t aKey,
+ NativeFontResource* aScaledFontResouce) final {
+ mNativeFontResources.InsertOrUpdate(aKey, RefPtr{aScaledFontResouce});
+ }
+
+ void RemoveDrawTarget(ReferencePtr aRefPtr) final {
+ ReferencePtr currentDT = mCurrentDT;
+ if (currentDT == aRefPtr) {
+ mCurrentDT = nullptr;
+ }
+ mDrawTargets.Remove(aRefPtr);
+ }
+
+ bool SetCurrentDrawTarget(ReferencePtr aRefPtr) final {
+ mCurrentDT = mDrawTargets.GetWeak(aRefPtr);
+ return !!mCurrentDT;
+ }
+
+ 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;
+};
+
+} // 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/1663722.html b/layout/printing/crashtests/1663722.html
new file mode 100644
index 0000000000..cd7a32a317
--- /dev/null
+++ b/layout/printing/crashtests/1663722.html
@@ -0,0 +1,14 @@
+<html class="reftest-wait">
+<head>
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const proxy = SpecialPowers.wrap(self).printPreview();
+ proxy.sizeToContent();
+ setTimeout(function () {
+ proxy.close();
+ document.documentElement.className = "";
+ }, 0);
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/printing/crashtests/1671503.html b/layout/printing/crashtests/1671503.html
new file mode 100644
index 0000000000..5399c7d71e
--- /dev/null
+++ b/layout/printing/crashtests/1671503.html
@@ -0,0 +1,12 @@
+<html class="reftest-wait">
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ window.setTimeout(() => {
+ SpecialPowers.wrap(window).printPreview()?.close()
+ SpecialPowers.wrap(window).printPreview()?.close()
+ document.documentElement.className = "";
+ }, 0)
+})
+</script>
+<iframe src='data:text/html,foo'></iframe>
+</html>
diff --git a/layout/printing/crashtests/1748277.html b/layout/printing/crashtests/1748277.html
new file mode 100644
index 0000000000..9cfbef2c46
--- /dev/null
+++ b/layout/printing/crashtests/1748277.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<style>
+ @namespace url(http://www.w3.org/1998/Math/MathML);
+
+ * {
+ padding-inline-start: 27em;
+ break-after: page;
+ content: url(1748277.png);
+ writing-mode: sideways-lr;
+ }
+</style>
+<script>
+ document.addEventListener("DOMContentLoaded", () => {
+ SpecialPowers.wrap(window).printPreview()?.close()
+ })
+</script>
+<math>
+ <mfrac>
+ <munder>
+ <ms>
+ <mglyph>
+ </mglyph>
+ </ms>
+ </munder>
+ </mfrac>
+</math>
diff --git a/layout/printing/crashtests/1748277.png b/layout/printing/crashtests/1748277.png
new file mode 100644
index 0000000000..6bcf82782e
--- /dev/null
+++ b/layout/printing/crashtests/1748277.png
Binary files differ
diff --git a/layout/printing/crashtests/1758199-1.html b/layout/printing/crashtests/1758199-1.html
new file mode 100644
index 0000000000..3f7c9227a4
--- /dev/null
+++ b/layout/printing/crashtests/1758199-1.html
@@ -0,0 +1,54 @@
+<html class="reftest-wait">
+<script>
+let pp;
+let documentElements = [];
+documentElements.push(document.documentElement);
+
+window.onload = () => {
+ documentElements.push(document.documentElement);
+
+ let o = document.getElementById('a')
+ o.parentNode.appendChild(o)
+ pp = SpecialPowers.wrap(self).printPreview();
+ pp?.print()
+ window.requestIdleCallback(() => {
+ documentElements.push(document.documentElement);
+
+ document.write('');
+
+ setTimeout(finish, 100);
+ });
+}
+
+function finish() {
+
+ // The printPreview call above actually opens two print preview windows
+ // because the <embed src='#'> below causes a second one to open. At least
+ // we close the one window we can access, not sure if there is a way to get
+ // ahold of the other window to close it. So this test leaves a window open
+ // after it finishes.
+ try { pp.close(); } catch (e) {}
+
+ if (document.documentElement) {
+ try { document.documentElement.className = ""; } catch (e) {}
+ }
+
+ // The documentElement that the reftest harness looks at to determine if the
+ // test is done is not what document.documentElement points to when this code
+ // is run. So we save all the document.documentElement's we encounter while
+ // running this test and clear all of their class names.
+ for (let de of documentElements) {
+ if (de) {
+ try {
+ de.className = "";
+ } catch (e) {}
+ }
+ }
+}
+</script>
+<style>
+:first-of-type { padding-block-start: 99% }
+</style>
+<mark id='a'>
+<embed src='#'>
+</html>
diff --git a/layout/printing/crashtests/1804571.html b/layout/printing/crashtests/1804571.html
new file mode 100644
index 0000000000..b4f1c7dad3
--- /dev/null
+++ b/layout/printing/crashtests/1804571.html
@@ -0,0 +1,11 @@
+<script>
+window.onload = () => {
+ SpecialPowers.wrap(window).printPreview()?.close()
+}
+</script>
+<style>
+:only-child { float: left }
+</style>
+<ruby>
+<label>
+a
diff --git a/layout/printing/crashtests/1804794.html b/layout/printing/crashtests/1804794.html
new file mode 100644
index 0000000000..e071b392a4
--- /dev/null
+++ b/layout/printing/crashtests/1804794.html
@@ -0,0 +1,10 @@
+<script>
+window.onload = () => {
+ SpecialPowers.wrap(window).printPreview()?.close()
+}
+</script>
+<style>
+html { column-count: 5 }
+</style>
+<hr style="page: a">
+<a>A</a>
diff --git a/layout/printing/crashtests/1804798.html b/layout/printing/crashtests/1804798.html
new file mode 100644
index 0000000000..dedf5d8ea9
--- /dev/null
+++ b/layout/printing/crashtests/1804798.html
@@ -0,0 +1,7 @@
+<script>
+window.onload = () => {
+ SpecialPowers.wrap(window).printPreview()?.close()
+}
+</script>
+<textarea></textarea>
+<textarea>
diff --git a/layout/printing/crashtests/1819468-1.html b/layout/printing/crashtests/1819468-1.html
new file mode 100644
index 0000000000..1ce7ded9e2
--- /dev/null
+++ b/layout/printing/crashtests/1819468-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<style>
+.c {
+ break-after: page;
+ page: Rotated;
+}
+</style>
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ b.insertAdjacentHTML("afterBegin", a.innerHTML)
+ SpecialPowers.wrap(window).printPreview()?.close()
+})
+</script>
+<audio>
+<optgroup id="a">
+<option class="c">a</option>
+</audio>
+<menu style="max-height: 0em">
+<menu id="b"></menu>
+</menu>
+<select class="c">
diff --git a/layout/printing/crashtests/1819468-2.html b/layout/printing/crashtests/1819468-2.html
new file mode 100644
index 0000000000..65b957d95f
--- /dev/null
+++ b/layout/printing/crashtests/1819468-2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<style>
+.c {
+ break-after: page;
+ page: Rotated;
+}
+</style>
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ SpecialPowers.wrap(window).printPreview()?.close()
+})
+</script>
+<menu style="max-height: 0em">
+<menu id="b">
+<option class="c">a</option>
+</menu>
+</menu>
+<select class="c">
diff --git a/layout/printing/crashtests/509839-1.html b/layout/printing/crashtests/509839-1.html
new file mode 100644
index 0000000000..e531f1f581
--- /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 transform, position: fixed table displays</title>
+</head><body>
+<div style="position: fixed;">
+<div style="display: table-header-group; 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..94b29816cf
--- /dev/null
+++ b/layout/printing/crashtests/509839-2.html
@@ -0,0 +1,8 @@
+<html style="position: fixed; 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..e2c57b282c
--- /dev/null
+++ b/layout/printing/crashtests/crashtests.list
@@ -0,0 +1,14 @@
+load 509839-1.html
+load 509839-2.html
+load 576878.xhtml
+asserts-if(Android,0-1) load 793844.html
+skip-if(Android) load 1662259.html
+skip-if(Android) pref(dom.window.sizeToContent.enabled,true) load 1663722.html
+skip-if(Android) load 1671503.html
+load 1748277.html # Bug 1751260
+skip-if(Android) load 1758199-1.html # printPreview doesn't work on android
+load 1804571.html
+load 1804798.html
+load 1804794.html
+load 1819468-1.html
+load 1819468-2.html
diff --git a/layout/printing/ipc/PRemotePrintJob.ipdl b/layout/printing/ipc/PRemotePrintJob.ipdl
new file mode 100644
index 0000000000..50b978cfc7
--- /dev/null
+++ b/layout/printing/ipc/PRemotePrintJob.ipdl
@@ -0,0 +1,57 @@
+/* -*- 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 PContent;
+
+namespace mozilla {
+namespace layout {
+
+[ChildImpl=virtual, ParentImpl=virtual]
+async protocol PRemotePrintJob
+{
+ manager PContent;
+
+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,
+ 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(int32_t aWidthInPoints, int32_t aHeightInPoints, uint64_t[] aDeps);
+
+ // This informs the real print device that we've finished, so it can trigger
+ // the actual print.
+ async FinalizePrint();
+
+ // 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..bc92272451
--- /dev/null
+++ b/layout/printing/ipc/RemotePrintJobChild.cpp
@@ -0,0 +1,171 @@
+/* -*- 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 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, aStartPage, aEndPage);
+ mozilla::SpinEventLoopUntil("RemotePrintJobChild::InitializePrint"_ns,
+ [&]() { 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(!mDestroyed);
+ MOZ_ASSERT(mNextPageFD);
+ PRFileDesc* fd = mNextPageFD;
+ mNextPageFD = nullptr;
+ return fd;
+}
+
+void RemotePrintJobChild::SetNextPageFD(
+ const mozilla::ipc::FileDescriptor& aFd) {
+ MOZ_ASSERT(!mDestroyed);
+ auto handle = aFd.ClonePlatformHandle();
+ mNextPageFD = PR_ImportFile(PROsfd(handle.release()));
+}
+
+void RemotePrintJobChild::ProcessPage(const IntSize& aSizeInPoints,
+ nsTArray<uint64_t>&& aDeps) {
+ MOZ_ASSERT(mPagePrintTimer);
+
+ mPagePrintTimer->WaitForRemotePrint();
+ if (!mDestroyed) {
+ Unused << SendProcessPage(aSizeInPoints.width, aSizeInPoints.height,
+ 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(!mDestroyed);
+ MOZ_ASSERT(aPagePrintTimer);
+
+ mPagePrintTimer = aPagePrintTimer;
+}
+
+void RemotePrintJobChild::SetPrintJob(nsPrintJob* aPrintJob) {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(aPrintJob);
+
+ mPrintJob = aPrintJob;
+}
+
+// nsIWebProgressListener
+
+NS_IMETHODIMP
+RemotePrintJobChild::OnStateChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags,
+ nsresult aStatus) {
+ // `RemotePrintJobParent` emits its own state change events based on its
+ // own progress & the actor lifecycle, so any forwarded event here would get
+ // ignored.
+ 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..73f2b5ef9c
--- /dev/null
+++ b/layout/printing/ipc/RemotePrintJobChild.h
@@ -0,0 +1,69 @@
+/* -*- 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 "mozilla/gfx/Point.h"
+#include "nsIWebProgressListener.h"
+
+class nsPagePrintTimer;
+class nsPrintJob;
+
+namespace mozilla {
+namespace layout {
+
+class RemotePrintJobChild final : public PRemotePrintJobChild,
+ public nsIWebProgressListener {
+ public:
+ using IntSize = mozilla::gfx::IntSize;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ RemotePrintJobChild();
+
+ void ActorDestroy(ActorDestroyReason aWhy) final;
+
+ nsresult InitializePrint(const nsString& aDocumentTitle,
+ const int32_t& aStartPage, const int32_t& aEndPage);
+
+ mozilla::ipc::IPCResult RecvPrintInitializationResult(
+ const nsresult& aRv, const FileDescriptor& aFd) final;
+
+ void ProcessPage(const IntSize& aSizeInPoints, 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();
+
+ [[nodiscard]] bool IsDestroyed() const { return mDestroyed; }
+
+ 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..4e4e363a8a
--- /dev/null
+++ b/layout/printing/ipc/RemotePrintJobParent.cpp
@@ -0,0 +1,344 @@
+/* -*- 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/ProfilerMarkers.h"
+#include "mozilla/Unused.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDeviceContext.h"
+#include "nsIDeviceContextSpec.h"
+#include "nsIPrintSettings.h"
+#include "nsIWebProgressListener.h"
+#include "PrintTranslator.h"
+#include "private/pprio.h"
+#include "nsAnonymousTemporaryFile.h"
+
+namespace mozilla::layout {
+
+RemotePrintJobParent::RemotePrintJobParent(nsIPrintSettings* aPrintSettings)
+ : mPrintSettings(aPrintSettings),
+ mIsDoingPrinting(false),
+ mStatus(NS_ERROR_UNEXPECTED) {
+ MOZ_COUNT_CTOR(RemotePrintJobParent);
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvInitializePrint(
+ const nsAString& aDocumentTitle, const int32_t& aStartPage,
+ const int32_t& aEndPage) {
+ PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {},
+ "RemotePrintJobParent::RecvInitializePrint"_ns);
+
+ nsresult rv = InitializePrintDevice(aDocumentTitle, aStartPage, aEndPage);
+ if (NS_FAILED(rv)) {
+ Unused << SendPrintInitializationResult(rv, FileDescriptor());
+ mStatus = rv;
+ Unused << Send__delete__(this);
+ return IPC_OK();
+ }
+
+ mPrintTranslator.reset(new PrintTranslator(mPrintDeviceContext));
+ FileDescriptor fd;
+ rv = PrepareNextPageFD(&fd);
+ if (NS_FAILED(rv)) {
+ Unused << SendPrintInitializationResult(rv, FileDescriptor());
+ mStatus = rv;
+ Unused << Send__delete__(this);
+ return IPC_OK();
+ }
+
+ Unused << SendPrintInitializationResult(NS_OK, fd);
+ return IPC_OK();
+}
+
+nsresult RemotePrintJobParent::InitializePrintDevice(
+ const nsAString& aDocumentTitle, const int32_t& aStartPage,
+ const int32_t& aEndPage) {
+ AUTO_PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {},
+ "RemotePrintJobParent::InitializePrintDevice"_ns);
+
+ nsresult rv;
+ nsCOMPtr<nsIDeviceContextSpec> deviceContextSpec =
+ do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = deviceContextSpec->Init(mPrintSettings, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mPrintDeviceContext = new nsDeviceContext();
+ rv = mPrintDeviceContext->InitForPrinting(deviceContextSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString fileName;
+ mPrintSettings->GetToFileName(fileName);
+
+ rv = mPrintDeviceContext->BeginDocument(aDocumentTitle, fileName, aStartPage,
+ aEndPage);
+ if (NS_FAILED(rv)) {
+ NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
+ "Failed to initialize print device");
+ return rv;
+ }
+
+ mIsDoingPrinting = true;
+
+ return NS_OK;
+}
+
+nsresult RemotePrintJobParent::PrepareNextPageFD(FileDescriptor* aFd) {
+ AUTO_PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {},
+ "RemotePrintJobParent::PrepareNextPageFD"_ns);
+
+ 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(
+ const int32_t& aWidthInPoints, const int32_t& aHeightInPoints,
+ nsTArray<uint64_t>&& aDeps) {
+ PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {},
+ "RemotePrintJobParent::RecvProcessPage"_ns);
+ if (!mCurrentPageStream.IsOpen()) {
+ Unused << SendAbortPrint(NS_ERROR_FAILURE);
+ return IPC_OK();
+ }
+ mCurrentPageStream.Seek(0, PR_SEEK_SET);
+
+ gfx::IntSize pageSizeInPoints(aWidthInPoints, aHeightInPoints);
+
+ if (aDeps.IsEmpty()) {
+ FinishProcessingPage(pageSizeInPoints);
+ return IPC_OK();
+ }
+
+ nsTHashSet<uint64_t> deps;
+ for (auto i : aDeps) {
+ deps.Insert(i);
+ }
+
+ gfx::CrossProcessPaint::Start(std::move(deps))
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, pageSizeInPoints](
+ gfx::CrossProcessPaint::ResolvedFragmentMap&& aFragments) {
+ self->FinishProcessingPage(pageSizeInPoints, &aFragments);
+ },
+ [self = RefPtr{this}, pageSizeInPoints](const nsresult& aRv) {
+ self->FinishProcessingPage(pageSizeInPoints);
+ });
+
+ return IPC_OK();
+}
+
+void RemotePrintJobParent::FinishProcessingPage(
+ const gfx::IntSize& aSizeInPoints,
+ gfx::CrossProcessPaint::ResolvedFragmentMap* aFragments) {
+ nsresult rv = PrintPage(aSizeInPoints, mCurrentPageStream, aFragments);
+
+ mCurrentPageStream.Close();
+
+ PageDone(rv);
+}
+
+nsresult RemotePrintJobParent::PrintPage(
+ const gfx::IntSize& aSizeInPoints, PRFileDescStream& aRecording,
+ gfx::CrossProcessPaint::ResolvedFragmentMap* aFragments) {
+ MOZ_ASSERT(mPrintDeviceContext);
+ AUTO_PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {},
+ "RemotePrintJobParent::PrintPage"_ns);
+
+ nsresult rv = mPrintDeviceContext->BeginPage(aSizeInPoints);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (aFragments) {
+ mPrintTranslator->SetDependentSurfaces(aFragments);
+ }
+ if (!mPrintTranslator->TranslateRecording(aRecording)) {
+ mPrintTranslator->SetDependentSurfaces(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+ mPrintTranslator->SetDependentSurfaces(nullptr);
+
+ rv = mPrintDeviceContext->EndPage();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void RemotePrintJobParent::PageDone(nsresult aResult) {
+ MOZ_ASSERT(mIsDoingPrinting);
+
+ if (NS_FAILED(aResult)) {
+ Unused << SendAbortPrint(aResult);
+ } else {
+ FileDescriptor fd;
+ aResult = PrepareNextPageFD(&fd);
+ if (NS_FAILED(aResult)) {
+ Unused << SendAbortPrint(aResult);
+ }
+
+ Unused << SendPageProcessed(fd);
+ }
+}
+
+static void NotifyStatusChange(
+ const nsCOMArray<nsIWebProgressListener>& aListeners, nsresult aStatus) {
+ uint32_t numberOfListeners = aListeners.Length();
+ for (uint32_t i = 0; i < numberOfListeners; ++i) {
+ nsIWebProgressListener* listener = aListeners[static_cast<int32_t>(i)];
+ listener->OnStatusChange(nullptr, nullptr, aStatus, nullptr);
+ }
+}
+
+static void NotifyStateChange(
+ const nsCOMArray<nsIWebProgressListener>& aListeners, long aStateFlags,
+ nsresult aStatus) {
+ uint32_t numberOfListeners = aListeners.Length();
+ for (uint32_t i = 0; i < numberOfListeners; ++i) {
+ nsIWebProgressListener* listener = aListeners[static_cast<int32_t>(i)];
+ listener->OnStateChange(nullptr, nullptr, aStateFlags, aStatus);
+ }
+}
+
+static void Cleanup(const nsCOMArray<nsIWebProgressListener>& aListeners,
+ RefPtr<nsDeviceContext>& aAbortContext,
+ const bool aPrintingInterrupted, const nsresult aResult) {
+ auto result = aResult;
+ if (MOZ_UNLIKELY(aPrintingInterrupted && NS_SUCCEEDED(result))) {
+ result = NS_ERROR_UNEXPECTED;
+ }
+ if (NS_FAILED(result)) {
+ NotifyStatusChange(aListeners, result);
+ }
+ if (aPrintingInterrupted && aAbortContext) {
+ // Abort any started print.
+ Unused << aAbortContext->AbortDocument();
+ }
+ // However the print went, let the listeners know that we're done.
+ NotifyStateChange(aListeners,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_DOCUMENT,
+ result);
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvFinalizePrint() {
+ PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {},
+ "RemotePrintJobParent::RecvFinalizePrint"_ns);
+
+ // EndDocument is sometimes called in the child even when BeginDocument has
+ // not been called. See bug 1223332.
+ if (mPrintDeviceContext) {
+ mPrintDeviceContext->EndDocument()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [listeners = std::move(mPrintProgressListeners)](
+ const mozilla::gfx::PrintEndDocumentPromise::ResolveOrRejectValue&
+ aResult) {
+ // Printing isn't interrupted, so we don't need the device context
+ // here.
+ RefPtr<nsDeviceContext> empty;
+ if (aResult.IsResolve()) {
+ Cleanup(listeners, empty, /* aPrintingInterrupted = */ false,
+ NS_OK);
+ } else {
+ Cleanup(listeners, empty, /* aPrintingInterrupted = */ false,
+ aResult.RejectValue());
+ }
+ });
+ mStatus = NS_OK;
+ }
+
+ mIsDoingPrinting = false;
+
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvAbortPrint(
+ const nsresult& aRv) {
+ PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {},
+ "RemotePrintJobParent::RecvAbortPrint"_ns);
+
+ // Leave the cleanup to `ActorDestroy()`.
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvProgressChange(
+ const long& aCurSelfProgress, const long& aMaxSelfProgress,
+ const long& aCurTotalProgress, const long& aMaxTotalProgress) {
+ PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {},
+ "RemotePrintJobParent::RecvProgressChange"_ns);
+ // Our progress follows that of `RemotePrintJobChild` closely enough - forward
+ // it instead of keeping more state variables here.
+ for (auto* listener : mPrintProgressListeners) {
+ listener->OnProgressChange(nullptr, nullptr, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvStatusChange(
+ const nsresult& aStatus) {
+ PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {},
+ "RemotePrintJobParent::RecvProgressChange"_ns);
+ if (NS_FAILED(aStatus)) {
+ // Remember the failure status for cleanup to forward to listeners.
+ mStatus = aStatus;
+ }
+
+ return IPC_OK();
+}
+
+void RemotePrintJobParent::RegisterListener(nsIWebProgressListener* aListener) {
+ MOZ_ASSERT(aListener);
+
+ // Our listener is a Promise created by CanonicalBrowsingContext::Print
+ mPrintProgressListeners.AppendElement(aListener);
+}
+
+already_AddRefed<nsIPrintSettings> RemotePrintJobParent::GetPrintSettings() {
+ nsCOMPtr<nsIPrintSettings> printSettings = mPrintSettings;
+ return printSettings.forget();
+}
+
+RemotePrintJobParent::~RemotePrintJobParent() {
+ MOZ_COUNT_DTOR(RemotePrintJobParent);
+}
+
+void RemotePrintJobParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (MOZ_UNLIKELY(mIsDoingPrinting && NS_SUCCEEDED(mStatus))) {
+ mStatus = NS_ERROR_UNEXPECTED;
+ }
+ Cleanup(mPrintProgressListeners, mPrintDeviceContext, mIsDoingPrinting,
+ mStatus);
+ // At any rate, this actor is done and cleaned up.
+ mIsDoingPrinting = false;
+}
+
+} // namespace mozilla::layout
diff --git a/layout/printing/ipc/RemotePrintJobParent.h b/layout/printing/ipc/RemotePrintJobParent.h
new file mode 100644
index 0000000000..2f80553183
--- /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/Point.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 nsAString& aDocumentTitle,
+ const int32_t& aStartPage,
+ const int32_t& aEndPage) final;
+
+ mozilla::ipc::IPCResult RecvProcessPage(const int32_t& aWidthInPoints,
+ const int32_t& aHeightInPoints,
+ nsTArray<uint64_t>&& aDeps) final;
+
+ mozilla::ipc::IPCResult RecvFinalizePrint() final;
+
+ mozilla::ipc::IPCResult RecvAbortPrint(const nsresult& aRv) 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 nsAString& aDocumentTitle,
+ const int32_t& aStartPage,
+ const int32_t& aEndPage);
+
+ nsresult PrepareNextPageFD(FileDescriptor* aFd);
+
+ nsresult PrintPage(
+ const gfx::IntSize& aSizeInPoints, PRFileDescStream& aRecording,
+ gfx::CrossProcessPaint::ResolvedFragmentMap* aFragments = nullptr);
+ void FinishProcessingPage(
+ const gfx::IntSize& aSizeInPoints,
+ 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;
+ nsresult mStatus;
+};
+
+} // 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..f4bfab42f5
--- /dev/null
+++ b/layout/printing/moz.build
@@ -0,0 +1,33 @@
+# -*- 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/.
+
+EXPORTS.mozilla.layout += [
+ "ipc/RemotePrintJobChild.h",
+ "ipc/RemotePrintJobParent.h",
+]
+
+EXPORTS.mozilla.layout.printing += ["DrawEventRecorder.h"]
+
+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/nsPagePrintTimer.cpp b/layout/printing/nsPagePrintTimer.cpp
new file mode 100644
index 0000000000..88f4deb3e5
--- /dev/null
+++ b/layout/printing/nsPagePrintTimer.cpp
@@ -0,0 +1,224 @@
+/* -*- 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/ProfilerMarkers.h"
+#include "mozilla/Unused.h"
+#include "nsPrintJob.h"
+#include "nsPrintObject.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPagePrintTimer, mozilla::Runnable,
+ nsITimerCallback)
+
+nsPagePrintTimer::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();
+}
+
+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,
+ GetMainThreadSerialEventTarget());
+}
+
+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,
+ GetMainThreadSerialEventTarget());
+}
+
+void nsPagePrintTimer::StopWatchDogTimer() {
+ if (mWatchDogTimer) {
+ mWatchDogTimer->Cancel();
+ mWatchDogTimer = nullptr;
+ }
+}
+
+// nsRunnable
+NS_IMETHODIMP
+nsPagePrintTimer::Run() {
+ bool initNewTimer = true;
+ bool donePrinting;
+
+ // donePrinting will be true if it completed successfully or
+ // if the printing was cancelled
+ donePrinting = !mPrintJob || mPrintJob->PrintSheet(mPrintObj);
+ 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(/*aUseDelay*/ true);
+ 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++;
+ PROFILER_MARKER_TEXT(
+ "nsPagePrintTimer::Notify", LAYOUT_Printing, {},
+ nsPrintfCString("Watchdog Timer Count %d", 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(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(GetMainThreadSerialEventTarget());
+ 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");
+ PROFILER_MARKER_TEXT("nsPagePrintTimer", LAYOUT_Printing, {},
+ "nsPagePrintTimer::Fail aborting print operation"_ns);
+
+ 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..068d9fafb7
--- /dev/null
+++ b/layout/printing/nsPagePrintTimer.h
@@ -0,0 +1,84 @@
+/* -*- 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 "mozilla/Attributes.h"
+#include "mozilla/OwningNonNull.h"
+#include "nsThreadUtils.h"
+
+class nsPrintJob;
+class nsPrintObject;
+
+namespace mozilla::dom {
+class Document;
+}
+
+//---------------------------------------------------
+//-- 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);
+
+ 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..db09acae39
--- /dev/null
+++ b/layout/printing/nsPrintData.cpp
@@ -0,0 +1,126 @@
+/* -*- 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 "mozilla/gfx/PrintPromise.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);
+
+static void InformListenersOfProgressChange(
+ const nsCOMArray<nsIWebProgressListener>& aListeners, int32_t aProgress,
+ int32_t aMaxProgress, bool aDoStartStop, int32_t aFlag) {
+ size_t numberOfListeners = aListeners.Length();
+ for (size_t i = 0; i < numberOfListeners; ++i) {
+ nsCOMPtr<nsIWebProgressListener> listener = aListeners.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);
+ }
+ }
+}
+
+static void InformListenersOfEndPrinting(
+ const nsCOMArray<nsIWebProgressListener>& aListeners) {
+ InformListenersOfProgressChange(
+ aListeners, 100, 100, true,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_DOCUMENT);
+ InformListenersOfProgressChange(aListeners, 100, 100, true,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_NETWORK);
+}
+
+//---------------------------------------------------
+//-- nsPrintData Class Impl
+//---------------------------------------------------
+nsPrintData::nsPrintData(ePrintDataType aType)
+ : mType(aType), mOnStartSent(false), mIsAborted(false) {}
+
+nsPrintData::~nsPrintData() {
+ // Two things need to be done:
+ // - Inform the listeners
+ // - End/Abort document
+ // Preview requires neither, so return early.
+ if (mType == eIsPrintPreview) {
+ return;
+ }
+
+ if (mPrintDC) {
+ PR_PL(("****************** End Document ************************\n"));
+ PR_PL(("\n"));
+ if (mPrintDC->IsCurrentlyPrintingDocument()) {
+ if (!mIsAborted) {
+ auto promise = mPrintDC->EndDocument();
+ if (mOnStartSent) {
+ promise->Then(mozilla::GetMainThreadSerialEventTarget(), __func__,
+ [listeners = std::move(mPrintProgressListeners)](
+ // We're in dtor, so capture listeners by move.
+ const mozilla::gfx::PrintEndDocumentPromise::
+ ResolveOrRejectValue&) {
+ InformListenersOfEndPrinting(listeners);
+ });
+ }
+ // Informing listeners asynchronously, or don't need to inform them, so
+ // return early.
+ return;
+ }
+ mPrintDC->AbortDocument();
+ }
+ }
+ if (mOnStartSent) {
+ // Synchronously notify the listeners.
+ OnEndPrinting();
+ }
+}
+
+void nsPrintData::OnStartPrinting() {
+ if (!mOnStartSent) {
+ InformListenersOfProgressChange(
+ mPrintProgressListeners, 0, 0, true,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_NETWORK);
+ mOnStartSent = true;
+ }
+}
+
+void nsPrintData::OnEndPrinting() {
+ InformListenersOfEndPrinting(mPrintProgressListeners);
+}
+
+void nsPrintData::DoOnProgressChange(int32_t aProgress, int32_t aMaxProgress,
+ bool aDoStartStop, int32_t aFlag) {
+ InformListenersOfProgressChange(mPrintProgressListeners, aProgress,
+ aMaxProgress, aDoStartStop, aFlag);
+}
+
+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..5d308fc7af
--- /dev/null
+++ b/layout/printing/nsPrintData.h
@@ -0,0 +1,53 @@
+/* -*- 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 "nsCOMArray.h"
+
+class nsPrintObject;
+class nsIWebProgressListener;
+
+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;
+
+ nsCOMArray<nsIWebProgressListener> mPrintProgressListeners;
+
+ bool mOnStartSent;
+ bool mIsAborted; // tells us the document is being aborted
+
+ 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..08bd54bc01
--- /dev/null
+++ b/layout/printing/nsPrintJob.cpp
@@ -0,0 +1,2427 @@
+/* -*- 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 "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/ShadowRoot.h"
+#include "mozilla/dom/CustomEvent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Try.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 "nsGkAtoms.h"
+#include "nsXPCOM.h"
+
+static const char sPrintSettingsServiceContractID[] =
+ "@mozilla.org/gfx/printsettings-service;1";
+
+// Printing Timer
+#include "nsPagePrintTimer.h"
+
+// FrameSet
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+
+// Misc
+#include "gfxContext.h"
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/layout/RemotePrintJobChild.h"
+#include "nsISupportsUtils.h"
+#include "nsIScriptContext.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "Text.h"
+
+#include "nsIDeviceContextSpec.h"
+#include "nsDeviceContextSpecProxy.h"
+#include "nsViewManager.h"
+
+#include "nsPageSequenceFrame.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebBrowserChrome.h"
+#include "mozilla/ReflowInput.h"
+#include "nsIDocumentViewer.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 "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")
+
+inline const char* LoggableTypeOfPO(const nsPrintObject* aPO) {
+ // TODO(dholbert): These strings used to represent actual enum values, but
+ // the enum type has been removed. We could replace them with more
+ // descriptive names, if anyone uses this logging and cares to do so.
+ return aPO->mParent ? "eIFrame" : "eDoc";
+}
+
+inline const char* ShortLoggableTypeOfPO(const nsPrintObject* aPO) {
+ return aPO->mParent ? "IF" : "DC";
+}
+
+// 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), mPrintDocList);
+# define DUMP_DOC_TREE DumpPrintObjectsTree(mPrintObject.get());
+# define DUMP_DOC_TREELAYOUT \
+ DumpPrintObjectsTreeLayout(mPrintObject, mPrt->mPrintDC);
+#else
+# define DUMP_DOC_LIST(_title)
+# define DUMP_DOC_TREE
+# define DUMP_DOC_TREELAYOUT
+#endif
+
+// -------------------------------------------------------
+// Helpers
+// -------------------------------------------------------
+
+/**
+ * Build a tree of nsPrintObjects under aPO. It also appends a (depth first)
+ * flat list of all the nsPrintObjects created to mPrintDocList. If
+ * one of the nsPrintObject's document is the focused document, then the print
+ * object is set as mSelectionRoot.
+ * @param aParentPO The parent nsPrintObject to populate, must not be null.
+ */
+void nsPrintJob::BuildNestedPrintObjects(
+ const UniquePtr<nsPrintObject>& aParentPO) {
+ MOZ_ASSERT(aParentPO);
+
+ // If aParentPO is for an iframe and its original document was the document
+ // that had focus then always set as the selection root.
+ if (aParentPO->mParent &&
+ aParentPO->mDocument->GetProperty(nsGkAtoms::printisfocuseddoc)) {
+ mSelectionRoot = aParentPO.get();
+ } else if (!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.
+ mSelectionRoot = mPrintObject.get();
+ }
+
+ for (auto& bc : aParentPO->mDocShell->GetBrowsingContext()->Children()) {
+ nsCOMPtr<nsIDocShell> docShell = bc->GetDocShell();
+ if (!docShell) {
+ if (auto* cc = dom::ContentChild::GetSingleton()) {
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService(sPrintSettingsServiceContractID);
+ embedding::PrintData printData;
+ printSettingsService->SerializeToPrintData(mPrintSettings, &printData);
+ Unused << cc->SendUpdateRemotePrintSettings(bc, printData);
+ }
+ 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;
+ }
+
+ // Note: docShell and doc are known-non-null at this point; they've been
+ // null-checked above (with null leading to 'continue' statements).
+ auto childPO = MakeUnique<nsPrintObject>(*docShell, *doc, aParentPO.get());
+
+ mPrintDocList.AppendElement(childPO.get());
+ BuildNestedPrintObjects(childPO);
+ aParentPO->mKids.AppendElement(std::move(childPO));
+ }
+}
+
+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)
+
+//-------------------------------------------------------
+nsPrintJob::~nsPrintJob() {
+ Destroy(); // for insurance
+ DisconnectPagePrintTimer();
+}
+
+bool nsPrintJob::CheckBeforeDestroy() const { return mPreparingForPrint; }
+
+//-------------------------------------------------------
+void nsPrintJob::Destroy() {
+ if (mIsDestroying) {
+ return;
+ }
+ mIsDestroying = true;
+
+ DestroyPrintingData();
+
+ mDocViewerPrint = nullptr;
+}
+
+//-------------------------------------------------------
+void nsPrintJob::DestroyPrintingData() {
+ mPrintObject = nullptr;
+ mPrt = nullptr;
+}
+
+nsPrintJob::nsPrintJob(nsIDocumentViewerPrint& aDocViewerPrint,
+ nsIDocShell& aDocShell, Document& aOriginalDoc,
+ float aScreenDPI)
+ : mDocViewerPrint(&aDocViewerPrint),
+ mDocShell(do_GetWeakReference(&aDocShell)),
+ mScreenDPI(aScreenDPI) {
+ // Any 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(nsGkAtoms::mozdisallowselectionprint);
+}
+
+//-----------------------------------------------------------------
+std::tuple<nsPageSequenceFrame*, int32_t>
+nsPrintJob::GetSeqFrameAndCountSheets() const {
+ if (NS_WARN_IF(!mPrt)) {
+ return {nullptr, 0};
+ }
+
+ const nsPrintObject* po = 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()};
+}
+
+// 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 (rv != NS_ERROR_ABORT && rv != NS_ERROR_OUT_OF_MEMORY) {
+ FirePrintingErrorEvent(rv);
+ }
+ DestroyPrintingData();
+ }
+
+ 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) {
+ mIsCreatingPrintPreview = true;
+ SetIsPrintPreview(true);
+ } else {
+ SetIsPrinting(true);
+ }
+
+ if (aWebProgressListener) {
+ printData->mPrintProgressListeners.AppendObject(aWebProgressListener);
+ }
+ if (mRemotePrintJob) {
+ // If we have a RemotePrintJob add it to the print progress listeners,
+ // so it can forward to the parent.
+ printData->mPrintProgressListeners.AppendElement(mRemotePrintJob);
+ }
+
+ // Get the docshell for this documentviewer
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if they don't pass in a PrintSettings, then get the Global PS
+ mPrintSettings = aPrintSettings;
+ if (!mPrintSettings) {
+ MOZ_TRY(GetDefaultPrintSettings(getter_AddRefs(mPrintSettings)));
+ }
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ // Note: docShell is implicitly non-null via do_QueryReferent necessarily
+ // having succeeded (if we got here).
+ mPrintObject = MakeUnique<nsPrintObject>(*docShell, aDoc);
+ mPrintDocList.AppendElement(mPrintObject.get());
+
+ BuildNestedPrintObjects(mPrintObject);
+ }
+
+ // 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 (!mPrintObject->mDocument || !mPrintObject->mDocument->GetRootElement())
+ return NS_ERROR_GFX_PRINTER_STARTDOC;
+
+ mPrintSettings->GetShrinkToFit(&mShrinkToFit);
+
+ nsCOMPtr<nsIDeviceContextSpec> devspec;
+ if (XRE_IsContentProcess()) {
+ devspec = new nsDeviceContextSpecProxy(mRemotePrintJob);
+ } else {
+ devspec = do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool printSilently = false;
+ mPrintSettings->GetPrintSilent(&printSilently);
+ if (StaticPrefs::print_always_print_silent()) {
+ printSilently = true;
+ }
+
+ if (mIsDoingPrinting && printSilently) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_SILENT_PRINT, 1);
+ }
+
+ MOZ_TRY(devspec->Init(mPrintSettings, mIsCreatingPrintPreview));
+
+ printData->mPrintDC = new nsDeviceContext();
+ MOZ_TRY(printData->mPrintDC->InitForPrinting(devspec));
+
+ MOZ_TRY(EnablePOsForPrinting());
+
+ if (!mIsCreatingPrintPreview) {
+ printData->OnStartPrinting();
+ }
+ InitPrintDocConstruction(false);
+
+ return NS_OK;
+}
+
+//---------------------------------------------------------------------------------
+nsresult nsPrintJob::Print(Document& aDoc, nsIPrintSettings* aPrintSettings,
+ RemotePrintJobChild* aRemotePrintJob,
+ nsIWebProgressListener* aWebProgressListener) {
+ mRemotePrintJob = aRemotePrintJob;
+ return CommonPrint(false, aPrintSettings, aWebProgressListener, aDoc);
+}
+
+nsresult nsPrintJob::PrintPreview(Document& aDoc,
+ 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, aDoc);
+ if (NS_FAILED(rv)) {
+ if (mPrintPreviewCallback) {
+ // signal error
+ mPrintPreviewCallback(
+ PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
+ 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;
+}
+
+//-----------------------------------------------------------------
+//-- Section: Pre-Reflow Methods
+//-----------------------------------------------------------------
+
+// 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::components::StringBundle::Service();
+ 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)));
+ PROFILER_MARKER_TEXT("PrintJob", LAYOUT_Printing, MarkerStack::Capture(),
+ "nsPrintJob::CleanupOnFailure"_ns);
+
+ /* 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) {
+ // signal error
+ mPrintPreviewCallback(
+ PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
+ mPrintPreviewCallback = nullptr;
+ }
+
+ nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
+ if (NS_WARN_IF(!viewer)) {
+ return;
+ }
+
+ const RefPtr<Document> doc = viewer->GetDocument();
+ const 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);
+
+ // Event listeners in chrome shouldn't delete this.
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*doc, *event,
+ ChromeOnlyDispatch::eYes);
+
+ // 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() {
+ 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 (nsPrintObject* po : mPrintDocList) {
+ 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);
+
+ 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 (po->mParent) {
+ 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 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(!mPrintObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this is creating print preview, mPrintObject->mPresContext and
+ // 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(!mPrintObject->mPresContext) ||
+ NS_WARN_IF(!mPrintObject->mPresShell))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this is printing some documents (not print-previewing the documents),
+ // mPrintObject->mPresContext and mPrintObject->mPresShell can be
+ // nullptr only when 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 && !mPrintObject->PrintingIsEnabled()) ||
+ (mPrintObject->mPresContext && mPrintObject->mPresShell),
+ "mPresContext and mPresShell shouldn't be nullptr when printing the "
+ "document or creating print-preview");
+
+ bool didReconstruction = false;
+
+ // This method works with 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();
+ 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.
+ if (mShrinkToFit) {
+ mShrinkToFitFactor = mPrintObject->mShrinkRatio;
+
+ if (mShrinkToFitFactor < 0.998f) {
+ nsresult rv = ReconstructAndReflow();
+ 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 = mPrintObject->mShrinkRatio;
+ PR_PL(
+ ("*******************************************************************"
+ "*******\n"));
+ PR_PL(("STF Ratio is: %8.5f Effective Ratio: %8.5f Diff: %8.5f\n",
+ mShrinkToFitFactor, calcRatio, mShrinkToFitFactor - 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(mNumPrintablePages);
+
+ PR_PL(("--- Printing %d pages\n", 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
+ if (mPrintSettings->GetOutputDestination() ==
+ nsIPrintSettings::kOutputDestinationFile) {
+ // On some platforms the BeginDocument needs to know the name of the file.
+ mPrintSettings->GetToFileName(fileNameStr);
+ }
+
+ nsAutoString docTitleStr;
+ nsAutoString docURLStr;
+ GetDisplayTitleAndURL(*mPrintObject->mDocument, mPrintSettings,
+ DocTitleDefault::eDocURLElseFallback, docTitleStr,
+ docURLStr);
+
+ int32_t startPage = 1;
+ int32_t endPage = mNumPrintablePages;
+
+ nsTArray<int32_t> ranges;
+ 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(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 =
+ mPrintObject->mPresShell->GetPageSequenceFrame();
+ if (seqFrame) {
+ seqFrame->StartPrint(mPrintObject->mPresContext, 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) {
+ // Double-check that mPrintObject is non-null, because it could have
+ // gotten cleared while running script since we last checked it.
+ if (NS_WARN_IF(!mPrintObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PrintDocContent(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) {
+ 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());
+
+ // Reflow the PO
+ MOZ_TRY(ReflowPrintObject(aPO));
+
+ for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
+ MOZ_TRY(ReflowDocList(kid));
+ }
+ 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<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
+ if (Document* document = viewer->GetDocument()) {
+ AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *document, u"printPreviewUpdate"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+ }
+ }
+}
+
+nsresult nsPrintJob::InitPrintDocConstruction(bool aHandleError) {
+ // Guarantee that mPrintObject won't be deleted.
+ 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(mPrintObject->mDocShell);
+ webProgress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
+ nsIWebProgress::NOTIFY_STATE_REQUEST);
+
+ MOZ_TRY(ReflowDocList(mPrintObject));
+
+ FirePrintPreviewUpdateEvent();
+ }
+
+ MaybeResumePrintAfterResourcesLoaded(aHandleError);
+ return NS_OK;
+}
+
+bool nsPrintJob::ShouldResumePrint() const {
+ if (mDoingInitialReflow) {
+ return false;
+ }
+ Document* doc = 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 mPrintObject to be null; we
+ // just check it for good measure, as we check its owner.
+ // Note: it shouldn't be possible for mPrintObject->mDocShell to be
+ // null; we just check it for good measure, as we check its owner.
+ if (!mPrt || NS_WARN_IF(!mPrintObject) ||
+ NS_WARN_IF(!mPrintObject->mDocShell)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(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) {
+ if (!aPO->mParent) {
+ if (mShrinkToFit) {
+ aPO->mZoomRatio = mShrinkToFitFactor;
+ // If we're actually going to scale (the factor is less than 1), we
+ // reduce the scale factor slightly to avoid the possibility of floating
+ // point rounding error causing slight clipping of the longest lines.
+ if (aPO->mZoomRatio != 1.0f) {
+ aPO->mZoomRatio -= 0.005f;
+ }
+ } else {
+ double scaling;
+ 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) {
+ const uint32_t rangeCount = selection->RangeCount();
+ for (const uint32_t inx : IntegerRange(rangeCount)) {
+ MOZ_ASSERT(selection->RangeCount() == rangeCount);
+ 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 (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;
+}
+
+nsView* nsPrintJob::GetParentViewForRoot() {
+ if (mIsCreatingPrintPreview) {
+ if (nsCOMPtr<nsIDocumentViewer> viewer =
+ do_QueryInterface(mDocViewerPrint)) {
+ return viewer->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(mPrintSettings);
+
+ // init it with the DC
+ MOZ_TRY(aPO->mPresContext->Init(printData->mPrintDC));
+
+ aPO->mViewManager = new nsViewManager();
+
+ MOZ_TRY(aPO->mViewManager->Init(printData->mPrintDC));
+
+ bool doReturn = false;
+ bool documentIsTopLevel = false;
+ nsSize adjSize;
+
+ nsresult rv = SetRootView(aPO.get(), doReturn, documentIsTopLevel, adjSize);
+
+ if (NS_FAILED(rv) || doReturn) {
+ return rv;
+ }
+
+ // 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 (mPrintSettings->HasOrthogonalPagesPerSheet()) {
+ std::swap(pageSize.width, pageSize.height);
+ }
+ // XXXalaskanemily: Is this actually necessary? We set it again before the
+ // first reflow.
+ aPO->mPresContext->SetPageSize(pageSize);
+
+ int32_t p2a = aPO->mPresContext->DeviceContext()->AppUnitsPerDevPixel();
+ if (documentIsTopLevel && mIsCreatingPrintPreview) {
+ if (nsCOMPtr<nsIDocumentViewer> viewer =
+ 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;
+ viewer->GetBounds(bounds);
+ adjSize = nsSize(bounds.width * p2a, bounds.height * p2a);
+ }
+ }
+ aPO->mPresContext->SetIsRootPaginatedDocument(documentIsTopLevel);
+ aPO->mPresContext->SetVisibleArea(nsRect(nsPoint(), adjSize));
+ aPO->mPresContext->SetPageScale(aPO->mZoomRatio);
+ // Calculate scale factor from printer to screen
+ float printDPI = float(AppUnitsPerCSSInch()) / float(p2a);
+ aPO->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);
+
+ // Do CreatePresShell() after we setup the page size, the visible area, and
+ // the flag |mIsRootPaginatedDocument|, to make sure we can resolve the
+ // correct viewport size for the print preview page when notifying the media
+ // feature values changed. See au_viewport_size_for_viewport_unit_resolution()
+ // in media_queries.rs for more details.
+ RefPtr<Document> doc = aPO->mDocument;
+ RefPtr<nsPresContext> presContext = aPO->mPresContext;
+ RefPtr<nsViewManager> viewManager = aPO->mViewManager;
+
+ aPO->mPresShell = doc->CreatePresShell(presContext, viewManager);
+ if (!aPO->mPresShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we're printing selection then remove the nonselected nodes from our
+ // cloned document.
+ if (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));
+ }
+
+ aPO->mPresShell->BeginObservingDocument();
+
+ PR_PL(
+ ("In DV::ReflowPrintObject PO: %p pS: %p (%9s) Setting page size w,h to "
+ "%d,%d\n",
+ aPO.get(), aPO->mPresShell.get(), LoggableTypeOfPO(aPO.get()),
+ pageSize.width, pageSize.height));
+
+ 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");
+
+ RefPtr<PresShell> presShell = aPO->mPresShell;
+ {
+ const ServoStyleSet::PageSizeAndOrientation sizeAndOrientation =
+ presShell->StyleSet()->GetDefaultPageSizeAndOrientation();
+ // XXX Should we enable this for known save-to-PDF pseudo-printers once
+ // bug 1826301 is fixed?
+ if (mPrintSettings->GetOutputFormat() ==
+ nsIPrintSettings::kOutputFormatPDF &&
+ StaticPrefs::
+ print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) {
+ mMaybeCSSPageSize = sizeAndOrientation.size;
+ if (sizeAndOrientation.size) {
+ pageSize = sizeAndOrientation.size.value();
+ aPO->mPresContext->SetPageSize(pageSize);
+ }
+ }
+
+ // If the document has a specified CSS page-size, we rotate the page to
+ // reflect this. Changing the orientation is reflected by the result of
+ // FinishPrintPreview, so that the frontend can reflect this.
+ // The new document has not yet been reflowed, so we have to query the
+ // original document for any CSS page-size.
+ if (sizeAndOrientation.orientation) {
+ switch (sizeAndOrientation.orientation.value()) {
+ case StylePageSizeOrientation::Landscape:
+ if (pageSize.width < pageSize.height) {
+ // Paper is in portrait, CSS page size is landscape.
+ std::swap(pageSize.width, pageSize.height);
+ }
+ break;
+ case StylePageSizeOrientation::Portrait:
+ if (pageSize.width > pageSize.height) {
+ // Paper is in landscape, CSS page size is portrait.
+ std::swap(pageSize.width, pageSize.height);
+ }
+ break;
+ }
+ mMaybeCSSPageLandscape = Some(sizeAndOrientation.orientation.value() ==
+ StylePageSizeOrientation::Landscape);
+ aPO->mPresContext->SetPageSize(pageSize);
+ }
+ }
+ // Process the reflow event Initialize posted
+ 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 (nsPrintObject* po : mPrintDocList) {
+ // 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).
+ nsTHashMap<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.WithEntryHandle(root, [&](auto&& entry) -> Position& {
+ return entry.OrInsertWith([&] { 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",
+ LoggableTypeOfPO(aPO.get())));
+ 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 (NS_WARN_IF(!printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ // 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
+ 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 (!mPrintSettings) {
+ // not sure what to do here!
+ SetIsPrinting(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString docTitleStr;
+ nsAutoString docURLStr;
+ GetDisplayTitleAndURL(*aPO->mDocument, 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 = 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(),
+ LoggableTypeOfPO(aPO.get())));
+ StartPagePrintTimer(aPO);
+ }
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+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;
+
+ // 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) {
+ NS_ASSERTION(aPO, "aPO is null!");
+ 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 || !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, LoggableTypeOfPO(aPO)));
+
+ if (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;
+ }
+
+ // 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) {
+ mPreparingForPrint = true;
+ }
+}
+
+//---------------------------------------------------------------------
+void nsPrintJob::SetIsPrintPreview(bool aIsPrintPreview) {
+ mCreatedForPrintPreview = aIsPrintPreview;
+
+ if (mDocViewerPrint) {
+ mDocViewerPrint->SetIsPrintPreview(aIsPrintPreview);
+ }
+}
+
+//-------------------------------------------------------
+bool nsPrintJob::DonePrintingSheets(nsPrintObject* aPO, nsresult aResult) {
+ // NS_ASSERTION(aPO, "Pointer is null!");
+ PR_PL(("****** In DV::DonePrintingSheets PO: %p (%s)\n", aPO,
+ aPO ? LoggableTypeOfPO(aPO) : ""));
+
+ // 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 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(mPrintObject, rv);
+ if (NS_SUCCEEDED(rv) && didPrint) {
+ PR_PL(
+ ("****** In DV::DonePrintingSheets PO: %p (%s) didPrint:%s (Not Done "
+ "Printing)\n",
+ aPO, LoggableTypeOfPO(aPO), PRT_YESNO(didPrint)));
+ return false;
+ }
+ }
+
+ 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 || !mPrintSettings) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PR_PL(("\n"));
+ PR_PL(("********* nsPrintJob::EnablePOsForPrinting *********\n"));
+
+ if (!mPrintSettings->GetPrintSelectionOnly()) {
+ 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 && mSelectionRoot);
+
+ // If mSelectionRoot is a selected iframe without a selection, then just
+ // enable normally from that point.
+ if (mSelectionRoot->mParent && !mSelectionRoot->HasSelection()) {
+ mSelectionRoot->EnablePrinting(true);
+ } else {
+ // Otherwise, only enable nsPrintObjects that have a selection.
+ mSelectionRoot->EnablePrintingSelectionOnly();
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------
+//-- Done: Misc Support Methods
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+//-- Section: Finishing up or Cleaning up
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+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.
+ // TODO(dshin): Does any listener attach to print preview? Doesn't seem like
+ // we call matching `OnStartPrinting()` for previews.
+ RefPtr<nsPrintData> printData = mPrt;
+ if (NS_FAILED(rv)) {
+ printData->OnEndPrinting();
+
+ return rv;
+ }
+
+ if (mPrintPreviewCallback) {
+ const bool hasSelection = !mDisallowSelectionPrint && mSelectionRoot;
+
+ Maybe<float> pageWidth;
+ Maybe<float> pageHeight;
+ if (mMaybeCSSPageSize) {
+ nsSize cssPageSize = *mMaybeCSSPageSize;
+ pageWidth = Some(float(cssPageSize.width) / float(AppUnitsPerCSSInch()));
+ pageHeight =
+ Some(float(cssPageSize.height) / float(AppUnitsPerCSSInch()));
+ }
+
+ mPrintPreviewCallback(PrintPreviewResultInfo(
+ GetPrintPreviewNumSheets(), GetRawNumPages(), GetIsEmpty(),
+ hasSelection, hasSelection && mPrintObject->HasSelection(),
+ mMaybeCSSPageLandscape, pageWidth, pageHeight));
+ mPrintPreviewCallback = nullptr;
+ }
+
+ // At this point we are done preparing everything
+ // before it is to be created
+
+ printData->OnEndPrinting();
+
+#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 = mPrintSettings->GetPrintPageDelay();
+
+ nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
+ NS_ENSURE_TRUE(viewer, NS_ERROR_FAILURE);
+ nsCOMPtr<Document> doc = viewer->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ mPagePrintTimer =
+ new nsPagePrintTimer(this, mDocViewerPrint, doc, printPageDelay);
+
+ if (mRemotePrintJob) {
+ mRemotePrintJob->SetPagePrintTimer(mPagePrintTimer);
+ mRemotePrintJob->SetPrintJob(this);
+ }
+ }
+
+ return mPagePrintTimer->Start(aPO.get());
+}
+
+//---------------------------------------------------------------
+//-- 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<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
+ NS_ENSURE_TRUE_VOID(viewer);
+ nsCOMPtr<Document> doc = viewer->GetDocument();
+ NS_ENSURE_TRUE_VOID(doc);
+ NS_ENSURE_SUCCESS_VOID(doc->Dispatch(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;
+ }
+
+ 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", ShortLoggableTypeOfPO(po),
+ 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;
+ 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", ShortLoggableTypeOfPO(po.get()), 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!");
+
+ 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", ShortLoggableTypeOfPO(aPO.get()), 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..e211e93582
--- /dev/null
+++ b/layout/printing/nsPrintJob.h
@@ -0,0 +1,311 @@
+/* -*- 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/layout/RemotePrintJobChild.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCOMPtr.h"
+
+#include "nsHashKeys.h"
+#include "nsIFrame.h" // For WeakFrame
+#include "nsSize.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsWeakReference.h"
+
+// Interfaces
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+
+// Classes
+class nsIFrame;
+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 nsIWebProgressListener,
+ public nsSupportsWeakReference {
+ using Document = mozilla::dom::Document;
+ using PrintPreviewResolver =
+ std::function<void(const mozilla::dom::PrintPreviewResultInfo&)>;
+ using RemotePrintJobChild = mozilla::layout::RemotePrintJobChild;
+
+ public:
+ // nsISupports interface...
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ /**
+ * Construct & 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)!
+ * TODO(dholbert): This^ note is probably somewhat out of date, in part
+ * because frontend code doesn't create "print preview tabs" anymore,
+ * now that we have a tab-modal print/print-preview dialog...
+ */
+ nsPrintJob(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,
+ RemotePrintJobChild* aRemotePrintJob,
+ 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; }
+ /// 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;
+
+ // 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 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();
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void FirePrintingErrorEvent(nsresult aPrintError);
+
+ bool CheckBeforeDestroy() const;
+ 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();
+
+ void BuildNestedPrintObjects(
+ const mozilla::UniquePtr<nsPrintObject>& aParentPO);
+
+ bool PrintDocContent(const mozilla::UniquePtr<nsPrintObject>& aPO,
+ nsresult& aStatus);
+ nsresult DoPrint(const mozilla::UniquePtr<nsPrintObject>& aPO);
+
+ nsresult ReflowDocList(const mozilla::UniquePtr<nsPrintObject>& aPO);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult ReflowPrintObject(const mozilla::UniquePtr<nsPrintObject>& aPO);
+
+ void CalcNumPrintablePages(int32_t& aNumPages);
+
+ nsresult StartPagePrintTimer(const mozilla::UniquePtr<nsPrintObject>& aPO);
+
+ /// 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();
+ void UpdateZoomRatio(nsPrintObject* aPO);
+ MOZ_CAN_RUN_SCRIPT nsresult ReconstructAndReflow();
+ 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);
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+
+ // 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;
+
+ RefPtr<nsPagePrintTimer> mPagePrintTimer;
+
+ // Only set if this nsPrintJob was created for a real print.
+ RefPtr<RemotePrintJobChild> mRemotePrintJob;
+
+ // The root print object.
+ mozilla::UniquePtr<nsPrintObject> mPrintObject;
+
+ // 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
+ // nsPrintJob. This includes mPrintObject, as well as all of its mKids (and
+ // their mKids, etc.)
+ nsTArray<nsPrintObject*> mPrintDocList;
+
+ // 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;
+
+ // The scale factor that would need to be applied to all pages to make the
+ // widest page fit without overflowing/clipping.
+ float mShrinkToFitFactor = 1.0f;
+
+ float mScreenDPI = 115.0f;
+
+ int32_t mNumPrintablePages = 0;
+
+ // Indicates if the page has a specific orientation from @page { size }.
+ // Stores true if this is landscape, false if this is portrait, or nothing
+ // if there is no page-size-orientation.
+ mozilla::Maybe<bool> mMaybeCSSPageLandscape;
+
+ // Indicates if the page has a specific size from @page { size }.
+ // Stores the page size if one was found.
+ mozilla::Maybe<nsSize> mMaybeCSSPageSize;
+
+ // If true, 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.
+ bool mPreparingForPrint = false;
+
+ bool mCreatedForPrintPreview = false;
+ bool mIsCreatingPrintPreview = false;
+ bool mIsDoingPrinting = false;
+ bool mDidLoadDataForPrinting = false;
+ bool mShrinkToFit = false;
+ bool mDoingInitialReflow = false;
+ bool mIsDestroying = false;
+ bool mDisallowSelectionPrint = false;
+};
+
+#endif // nsPrintJob_h
diff --git a/layout/printing/nsPrintObject.cpp b/layout/printing/nsPrintObject.cpp
new file mode 100644
index 0000000000..79c134a9de
--- /dev/null
+++ b/layout/printing/nsPrintObject.cpp
@@ -0,0 +1,93 @@
+/* -*- 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 "nsIDocumentViewer.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(nsIDocShell& aDocShell, Document& aDoc,
+ nsPrintObject* aParent)
+ : mDocShell(&aDocShell), mDocument(&aDoc), mParent(aParent) {
+ MOZ_COUNT_CTOR(nsPrintObject);
+ MOZ_ASSERT(aDoc.IsStaticDocument());
+
+ if (!aParent) {
+ // We are a root nsPrintObject.
+ // Ensure the document has no presentation.
+ DestroyPresentation();
+ } else {
+ // We are a nested nsPrintObject.
+ nsCOMPtr<nsPIDOMWindowOuter> window = aDoc.GetWindow();
+ mContent = window->GetFrameElementInternal();
+ }
+}
+
+nsPrintObject::~nsPrintObject() {
+ MOZ_COUNT_DTOR(nsPrintObject);
+
+ DestroyPresentation();
+ mDocShell = nullptr;
+ mTreeOwner = nullptr; // mTreeOwner must be released after mDocShell;
+}
+
+//------------------------------------------------------------------
+// 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..4c79bbbf0e
--- /dev/null
+++ b/layout/printing/nsPrintObject.h
@@ -0,0 +1,88 @@
+/* -*- 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 Class
+//---------------------------------------------------
+class nsPrintObject final {
+ public:
+ /**
+ * If aParent is nullptr (default), then this instance will be initialized as
+ * a "root" nsPrintObject. Otherwise, this will be a "nested" nsPrintObject.
+ */
+ nsPrintObject(nsIDocShell& aDocShell, mozilla::dom::Document& aDoc,
+ nsPrintObject* aParent = nullptr);
+ ~nsPrintObject();
+
+ 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;
+
+ nsTArray<mozilla::UniquePtr<nsPrintObject>> mKids;
+ const nsPrintObject* mParent; // This is a non-owning pointer.
+ bool mHasBeenPrinted = false;
+ bool mInvisible = false; // Indicates PO is set to not visible by CSS
+
+ // The scale factor that sheets should be scaled by. This is either the
+ // explicit scale chosen by the user or else the shrink-to-fit scale factor
+ // if the user selects shrink-to-fit. Only set on the top-level nsPrintObject
+ // since this is only used by nsPageFrame (via nsPresContext::GetPageScale()).
+ float mZoomRatio = 1.0;
+
+ // If the user selects the shrink-to-fit option, the shrink-to-fit scale
+ // factor is calculated and stored here. Only set on the top-level
+ // nsPrintObject.
+ float mShrinkRatio = 1.0;
+
+ private:
+ nsPrintObject& operator=(const nsPrintObject& aOther) = delete;
+
+ bool mPrintingIsEnabled = false;
+};
+
+#endif /* nsPrintObject_h___ */