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