diff options
Diffstat (limited to 'layout/printing')
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 Binary files differnew file mode 100644 index 0000000000..6bcf82782e --- /dev/null +++ b/layout/printing/crashtests/1748277.png 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___ */ |