summaryrefslogtreecommitdiffstats
path: root/layout/printing/nsPrintJob.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/printing/nsPrintJob.cpp')
-rw-r--r--layout/printing/nsPrintJob.cpp2427
1 files changed, 2427 insertions, 0 deletions
diff --git a/layout/printing/nsPrintJob.cpp b/layout/printing/nsPrintJob.cpp
new file mode 100644
index 0000000000..08bd54bc01
--- /dev/null
+++ b/layout/printing/nsPrintJob.cpp
@@ -0,0 +1,2427 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrintJob.h"
+
+#include "nsDebug.h"
+#include "nsDocShell.h"
+#include "nsReadableUtils.h"
+#include "nsQueryObject.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/PBrowser.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/CustomEvent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Try.h"
+#include "nsIBrowserChild.h"
+#include "nsIOService.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIStringBundle.h"
+#include "nsPIDOMWindow.h"
+#include "nsPrintData.h"
+#include "nsPrintObject.h"
+#include "nsIDocShell.h"
+#include "nsIURI.h"
+#include "nsITextToSubURI.h"
+#include "nsError.h"
+
+#include "nsView.h"
+#include <algorithm>
+
+// Print Options
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsGkAtoms.h"
+#include "nsXPCOM.h"
+
+static const char sPrintSettingsServiceContractID[] =
+ "@mozilla.org/gfx/printsettings-service;1";
+
+// Printing Timer
+#include "nsPagePrintTimer.h"
+
+// FrameSet
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+
+// Misc
+#include "gfxContext.h"
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/layout/RemotePrintJobChild.h"
+#include "nsISupportsUtils.h"
+#include "nsIScriptContext.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "Text.h"
+
+#include "nsIDeviceContextSpec.h"
+#include "nsDeviceContextSpecProxy.h"
+#include "nsViewManager.h"
+
+#include "nsPageSequenceFrame.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebBrowserChrome.h"
+#include "mozilla/ReflowInput.h"
+#include "nsIDocumentViewer.h"
+#include "nsIDocumentViewerPrint.h"
+
+#include "nsFocusManager.h"
+#include "nsRange.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLFrameElement.h"
+#include "mozilla/ServoStyleSet.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//-----------------------------------------------------
+// PR LOGGING
+#include "mozilla/Logging.h"
+
+#ifdef DEBUG
+// PR_LOGGING is force to always be on (even in release builds)
+// but we only want some of it on,
+// #define EXTENDED_DEBUG_PRINTING
+#endif
+
+// this log level turns on the dumping of each document's layout info
+#define DUMP_LAYOUT_LEVEL (static_cast<mozilla::LogLevel>(9))
+
+#ifndef PR_PL
+static mozilla::LazyLogModule gPrintingLog("printing");
+
+# define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
+#endif
+
+#ifdef EXTENDED_DEBUG_PRINTING
+static uint32_t gDumpFileNameCnt = 0;
+static uint32_t gDumpLOFileNameCnt = 0;
+#endif
+
+#define PRT_YESNO(_p) ((_p) ? "YES" : "NO")
+
+inline const char* LoggableTypeOfPO(const nsPrintObject* aPO) {
+ // TODO(dholbert): These strings used to represent actual enum values, but
+ // the enum type has been removed. We could replace them with more
+ // descriptive names, if anyone uses this logging and cares to do so.
+ return aPO->mParent ? "eIFrame" : "eDoc";
+}
+
+inline const char* ShortLoggableTypeOfPO(const nsPrintObject* aPO) {
+ return aPO->mParent ? "IF" : "DC";
+}
+
+// This processes the selection on aOrigDoc and creates an inverted selection on
+// aDoc, which it then deletes. If the start or end of the inverted selection
+// ranges occur in text nodes then an ellipsis is added.
+static nsresult DeleteNonSelectedNodes(Document& aDoc);
+
+#ifdef EXTENDED_DEBUG_PRINTING
+// Forward Declarations
+static void DumpPrintObjectsListStart(const char* aStr,
+ const nsTArray<nsPrintObject*>& aDocList);
+static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel = 0,
+ FILE* aFD = nullptr);
+static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
+ nsDeviceContext* aDC, int aLevel = 0,
+ FILE* aFD = nullptr);
+
+# define DUMP_DOC_LIST(_title) \
+ DumpPrintObjectsListStart((_title), mPrintDocList);
+# define DUMP_DOC_TREE DumpPrintObjectsTree(mPrintObject.get());
+# define DUMP_DOC_TREELAYOUT \
+ DumpPrintObjectsTreeLayout(mPrintObject, mPrt->mPrintDC);
+#else
+# define DUMP_DOC_LIST(_title)
+# define DUMP_DOC_TREE
+# define DUMP_DOC_TREELAYOUT
+#endif
+
+// -------------------------------------------------------
+// Helpers
+// -------------------------------------------------------
+
+/**
+ * Build a tree of nsPrintObjects under aPO. It also appends a (depth first)
+ * flat list of all the nsPrintObjects created to mPrintDocList. If
+ * one of the nsPrintObject's document is the focused document, then the print
+ * object is set as mSelectionRoot.
+ * @param aParentPO The parent nsPrintObject to populate, must not be null.
+ */
+void nsPrintJob::BuildNestedPrintObjects(
+ const UniquePtr<nsPrintObject>& aParentPO) {
+ MOZ_ASSERT(aParentPO);
+
+ // If aParentPO is for an iframe and its original document was the document
+ // that had focus then always set as the selection root.
+ if (aParentPO->mParent &&
+ aParentPO->mDocument->GetProperty(nsGkAtoms::printisfocuseddoc)) {
+ mSelectionRoot = aParentPO.get();
+ } else if (!mSelectionRoot && aParentPO->HasSelection()) {
+ // If there is no focused iframe but there is a selection in one or more
+ // frames then we want to set the root nsPrintObject as the focus root so
+ // that later EnablePrintingSelectionOnly can search for and enable all
+ // nsPrintObjects containing selections.
+ mSelectionRoot = mPrintObject.get();
+ }
+
+ for (auto& bc : aParentPO->mDocShell->GetBrowsingContext()->Children()) {
+ nsCOMPtr<nsIDocShell> docShell = bc->GetDocShell();
+ if (!docShell) {
+ if (auto* cc = dom::ContentChild::GetSingleton()) {
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService(sPrintSettingsServiceContractID);
+ embedding::PrintData printData;
+ printSettingsService->SerializeToPrintData(mPrintSettings, &printData);
+ Unused << cc->SendUpdateRemotePrintSettings(bc, printData);
+ }
+ continue;
+ }
+
+ RefPtr<Document> doc = docShell->GetDocument();
+ MOZ_DIAGNOSTIC_ASSERT(doc);
+ // We might find non-static documents here if the fission remoteness change
+ // hasn't happened / finished yet. In that case, just skip them, the same
+ // way we do for remote frames above.
+ MOZ_DIAGNOSTIC_ASSERT(doc->IsStaticDocument() || doc->IsInitialDocument());
+ if (!doc || !doc->IsStaticDocument()) {
+ continue;
+ }
+
+ // Note: docShell and doc are known-non-null at this point; they've been
+ // null-checked above (with null leading to 'continue' statements).
+ auto childPO = MakeUnique<nsPrintObject>(*docShell, *doc, aParentPO.get());
+
+ mPrintDocList.AppendElement(childPO.get());
+ BuildNestedPrintObjects(childPO);
+ aParentPO->mKids.AppendElement(std::move(childPO));
+ }
+}
+
+static nsresult GetDefaultPrintSettings(nsIPrintSettings** aSettings) {
+ *aSettings = nullptr;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService(sPrintSettingsServiceContractID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return printSettingsService->GetDefaultPrintSettingsForPrinting(aSettings);
+}
+
+//-------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsPrintJob, nsIWebProgressListener, nsISupportsWeakReference)
+
+//-------------------------------------------------------
+nsPrintJob::~nsPrintJob() {
+ Destroy(); // for insurance
+ DisconnectPagePrintTimer();
+}
+
+bool nsPrintJob::CheckBeforeDestroy() const { return mPreparingForPrint; }
+
+//-------------------------------------------------------
+void nsPrintJob::Destroy() {
+ if (mIsDestroying) {
+ return;
+ }
+ mIsDestroying = true;
+
+ DestroyPrintingData();
+
+ mDocViewerPrint = nullptr;
+}
+
+//-------------------------------------------------------
+void nsPrintJob::DestroyPrintingData() {
+ mPrintObject = nullptr;
+ mPrt = nullptr;
+}
+
+nsPrintJob::nsPrintJob(nsIDocumentViewerPrint& aDocViewerPrint,
+ nsIDocShell& aDocShell, Document& aOriginalDoc,
+ float aScreenDPI)
+ : mDocViewerPrint(&aDocViewerPrint),
+ mDocShell(do_GetWeakReference(&aDocShell)),
+ mScreenDPI(aScreenDPI) {
+ // Any state that we need from aOriginalDoc must be fetched and stored
+ // here, since the document that the user selected to print may mutate
+ // across consecutive PrintPreview() calls.
+
+ Element* root = aOriginalDoc.GetRootElement();
+ mDisallowSelectionPrint =
+ root && root->HasAttr(nsGkAtoms::mozdisallowselectionprint);
+}
+
+//-----------------------------------------------------------------
+std::tuple<nsPageSequenceFrame*, int32_t>
+nsPrintJob::GetSeqFrameAndCountSheets() const {
+ if (NS_WARN_IF(!mPrt)) {
+ return {nullptr, 0};
+ }
+
+ const nsPrintObject* po = mPrintObject.get();
+ if (NS_WARN_IF(!po)) {
+ return {nullptr, 0};
+ }
+
+ // This is sometimes incorrectly called before the pres shell has been created
+ // (bug 1141756). MOZ_DIAGNOSTIC_ASSERT so we'll still see the crash in
+ // Nightly/Aurora in case the other patch fixes this.
+ if (!po->mPresShell) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "GetSeqFrameAndCountSheets needs a non-null pres shell");
+ return {nullptr, 0};
+ }
+
+ nsPageSequenceFrame* seqFrame = po->mPresShell->GetPageSequenceFrame();
+ if (!seqFrame) {
+ return {nullptr, 0};
+ }
+
+ // count the total number of sheets
+ return {seqFrame, seqFrame->PrincipalChildList().GetLength()};
+}
+
+// Foward decl for Debug Helper Functions
+#ifdef EXTENDED_DEBUG_PRINTING
+# ifdef XP_WIN
+static int RemoveFilesInDir(const char* aDir);
+# endif
+static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
+ nsACString& aDocStr, nsACString& aURLStr);
+static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel, FILE* aFD);
+static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList);
+static void RootFrameList(nsPresContext* aPresContext, FILE* out,
+ const char* aPrefix);
+static void DumpViews(nsIDocShell* aDocShell, FILE* out);
+static void DumpLayoutData(const char* aTitleStr, const char* aURLStr,
+ nsPresContext* aPresContext, nsDeviceContext* aDC,
+ nsIFrame* aRootFrame, nsIDocShell* aDocShell,
+ FILE* aFD);
+#endif
+
+//--------------------------------------------------------------------------------
+
+nsresult nsPrintJob::CommonPrint(bool aIsPrintPreview,
+ nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener,
+ Document& aSourceDoc) {
+ // Callers must hold a strong reference to |this| to ensure that we stay
+ // alive for the duration of this method, because our main owning reference
+ // (on nsDocumentViewer) might be cleared during this function (if we cause
+ // script to run and it cancels the print operation).
+
+ nsresult rv = DoCommonPrint(aIsPrintPreview, aPrintSettings,
+ aWebProgressListener, aSourceDoc);
+ if (NS_FAILED(rv)) {
+ if (aIsPrintPreview) {
+ mIsCreatingPrintPreview = false;
+ SetIsPrintPreview(false);
+ } else {
+ SetIsPrinting(false);
+ }
+ if (rv != NS_ERROR_ABORT && rv != NS_ERROR_OUT_OF_MEMORY) {
+ FirePrintingErrorEvent(rv);
+ }
+ DestroyPrintingData();
+ }
+
+ return rv;
+}
+
+nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview,
+ nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener,
+ Document& aDoc) {
+ MOZ_ASSERT(aDoc.IsStaticDocument());
+
+ nsresult rv;
+
+ // Grab the new instance with local variable to guarantee that it won't be
+ // deleted during this method.
+ // Note: Methods we call early below rely on mPrt being set.
+ mPrt = new nsPrintData(aIsPrintPreview ? nsPrintData::eIsPrintPreview
+ : nsPrintData::eIsPrinting);
+ RefPtr<nsPrintData> printData = mPrt;
+
+ if (aIsPrintPreview) {
+ mIsCreatingPrintPreview = true;
+ SetIsPrintPreview(true);
+ } else {
+ SetIsPrinting(true);
+ }
+
+ if (aWebProgressListener) {
+ printData->mPrintProgressListeners.AppendObject(aWebProgressListener);
+ }
+ if (mRemotePrintJob) {
+ // If we have a RemotePrintJob add it to the print progress listeners,
+ // so it can forward to the parent.
+ printData->mPrintProgressListeners.AppendElement(mRemotePrintJob);
+ }
+
+ // Get the docshell for this documentviewer
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if they don't pass in a PrintSettings, then get the Global PS
+ mPrintSettings = aPrintSettings;
+ if (!mPrintSettings) {
+ MOZ_TRY(GetDefaultPrintSettings(getter_AddRefs(mPrintSettings)));
+ }
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ // Note: docShell is implicitly non-null via do_QueryReferent necessarily
+ // having succeeded (if we got here).
+ mPrintObject = MakeUnique<nsPrintObject>(*docShell, aDoc);
+ mPrintDocList.AppendElement(mPrintObject.get());
+
+ BuildNestedPrintObjects(mPrintObject);
+ }
+
+ // The nsAutoScriptBlocker above will now have been destroyed, which may
+ // cause our print/print-preview operation to finish. In this case, we
+ // should immediately return an error code so that the root caller knows
+ // it shouldn't continue to do anything with this instance.
+ if (mIsDestroying) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX This isn't really correct...
+ if (!mPrintObject->mDocument || !mPrintObject->mDocument->GetRootElement())
+ return NS_ERROR_GFX_PRINTER_STARTDOC;
+
+ mPrintSettings->GetShrinkToFit(&mShrinkToFit);
+
+ nsCOMPtr<nsIDeviceContextSpec> devspec;
+ if (XRE_IsContentProcess()) {
+ devspec = new nsDeviceContextSpecProxy(mRemotePrintJob);
+ } else {
+ devspec = do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool printSilently = false;
+ mPrintSettings->GetPrintSilent(&printSilently);
+ if (StaticPrefs::print_always_print_silent()) {
+ printSilently = true;
+ }
+
+ if (mIsDoingPrinting && printSilently) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_SILENT_PRINT, 1);
+ }
+
+ MOZ_TRY(devspec->Init(mPrintSettings, mIsCreatingPrintPreview));
+
+ printData->mPrintDC = new nsDeviceContext();
+ MOZ_TRY(printData->mPrintDC->InitForPrinting(devspec));
+
+ MOZ_TRY(EnablePOsForPrinting());
+
+ if (!mIsCreatingPrintPreview) {
+ printData->OnStartPrinting();
+ }
+ InitPrintDocConstruction(false);
+
+ return NS_OK;
+}
+
+//---------------------------------------------------------------------------------
+nsresult nsPrintJob::Print(Document& aDoc, nsIPrintSettings* aPrintSettings,
+ RemotePrintJobChild* aRemotePrintJob,
+ nsIWebProgressListener* aWebProgressListener) {
+ mRemotePrintJob = aRemotePrintJob;
+ return CommonPrint(false, aPrintSettings, aWebProgressListener, aDoc);
+}
+
+nsresult nsPrintJob::PrintPreview(Document& aDoc,
+ nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener,
+ PrintPreviewResolver&& aCallback) {
+ // Take ownership of aCallback, otherwise a function further up the call
+ // stack will call it to signal failure (by passing zero).
+ mPrintPreviewCallback = std::move(aCallback);
+
+ nsresult rv = CommonPrint(true, aPrintSettings, aWebProgressListener, aDoc);
+ if (NS_FAILED(rv)) {
+ if (mPrintPreviewCallback) {
+ // signal error
+ mPrintPreviewCallback(
+ PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
+ mPrintPreviewCallback = nullptr;
+ }
+ }
+ return rv;
+}
+
+int32_t nsPrintJob::GetRawNumPages() const {
+ auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
+ Unused << numSheets;
+ return seqFrame ? seqFrame->GetRawNumPages() : 0;
+}
+
+bool nsPrintJob::GetIsEmpty() const {
+ auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
+ if (!seqFrame) {
+ return true;
+ }
+ if (numSheets > 1) {
+ return false;
+ }
+ return !seqFrame->GetPagesInFirstSheet();
+}
+
+int32_t nsPrintJob::GetPrintPreviewNumSheets() const {
+ auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
+ Unused << seqFrame;
+ return numSheets;
+}
+
+//-----------------------------------------------------------------
+//-- Section: Pre-Reflow Methods
+//-----------------------------------------------------------------
+
+// static
+void nsPrintJob::GetDisplayTitleAndURL(Document& aDoc,
+ nsIPrintSettings* aSettings,
+ DocTitleDefault aTitleDefault,
+ nsAString& aTitle, nsAString& aURLStr) {
+ aTitle.Truncate();
+ aURLStr.Truncate();
+
+ if (aSettings) {
+ aSettings->GetTitle(aTitle);
+ aSettings->GetDocURL(aURLStr);
+ }
+
+ if (aTitle.IsEmpty()) {
+ aDoc.GetTitle(aTitle);
+ if (aTitle.IsEmpty()) {
+ if (!aURLStr.IsEmpty() &&
+ aTitleDefault == DocTitleDefault::eDocURLElseFallback) {
+ aTitle = aURLStr;
+ } else {
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ nsCOMPtr<nsIStringBundleService> svc =
+ mozilla::components::StringBundle::Service();
+ if (svc) {
+ svc->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(brandBundle));
+ if (brandBundle) {
+ brandBundle->GetStringFromName("brandShortName", aTitle);
+ }
+ }
+ if (aTitle.IsEmpty()) {
+ aTitle.AssignLiteral(u"Mozilla Document");
+ }
+ }
+ }
+ }
+
+ if (aURLStr.IsEmpty()) {
+ nsIURI* url = aDoc.GetDocumentURI();
+ if (!url) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(url);
+ nsAutoCString urlCStr;
+ nsresult rv = exposableURI->GetSpec(urlCStr);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ textToSubURI->UnEscapeURIForUI(urlCStr, aURLStr);
+ }
+}
+
+//---------------------------------------------------------------------
+nsresult nsPrintJob::DocumentReadyForPrinting() {
+ // Send the document to the printer...
+ nsresult rv = SetupToPrintContent();
+ if (NS_FAILED(rv)) {
+ // The print job was canceled or there was a problem
+ // So remove all other documents from the print list
+ DonePrintingSheets(nullptr, rv);
+ }
+ return rv;
+}
+
+/** ---------------------------------------------------
+ * Cleans up when an error occurred
+ */
+nsresult nsPrintJob::CleanupOnFailure(nsresult aResult, bool aIsPrinting) {
+ PR_PL(("**** Failed %s - rv 0x%" PRIX32,
+ aIsPrinting ? "Printing" : "Print Preview",
+ static_cast<uint32_t>(aResult)));
+ PROFILER_MARKER_TEXT("PrintJob", LAYOUT_Printing, MarkerStack::Capture(),
+ "nsPrintJob::CleanupOnFailure"_ns);
+
+ /* cleanup... */
+ if (mPagePrintTimer) {
+ mPagePrintTimer->Stop();
+ DisconnectPagePrintTimer();
+ }
+
+ if (aIsPrinting) {
+ SetIsPrinting(false);
+ } else {
+ SetIsPrintPreview(false);
+ mIsCreatingPrintPreview = false;
+ }
+
+ /* cleanup done, let's fire-up an error dialog to notify the user
+ * what went wrong...
+ *
+ * When rv == NS_ERROR_ABORT, it means we want out of the
+ * print job without displaying any error messages
+ */
+ if (aResult != NS_ERROR_ABORT) {
+ FirePrintingErrorEvent(aResult);
+ }
+
+ FirePrintCompletionEvent();
+
+ return aResult;
+}
+
+//---------------------------------------------------------------------
+void nsPrintJob::FirePrintingErrorEvent(nsresult aPrintError) {
+ if (mPrintPreviewCallback) {
+ // signal error
+ mPrintPreviewCallback(
+ PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
+ mPrintPreviewCallback = nullptr;
+ }
+
+ nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
+ if (NS_WARN_IF(!viewer)) {
+ return;
+ }
+
+ const RefPtr<Document> doc = viewer->GetDocument();
+ const RefPtr<CustomEvent> event = NS_NewDOMCustomEvent(doc, nullptr, nullptr);
+
+ MOZ_ASSERT(event);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(event->GetParentObject())) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JS::Value> detail(
+ cx, JS::NumberValue(static_cast<double>(aPrintError)));
+ event->InitCustomEvent(cx, u"PrintingError"_ns, false, false, detail);
+ event->SetTrusted(true);
+
+ // Event listeners in chrome shouldn't delete this.
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*doc, *event,
+ ChromeOnlyDispatch::eYes);
+
+ // Inform any progress listeners of the Error.
+ if (mPrt) {
+ // Note that nsPrintData::DoOnStatusChange() will call some listeners.
+ // So, mPrt can be cleared or recreated.
+ RefPtr<nsPrintData> printData = mPrt;
+ printData->DoOnStatusChange(aPrintError);
+ }
+}
+
+//-----------------------------------------------------------------
+//-- Section: Reflow Methods
+//-----------------------------------------------------------------
+
+nsresult nsPrintJob::ReconstructAndReflow() {
+ if (NS_WARN_IF(!mPrt)) {
+ return NS_ERROR_FAILURE;
+ }
+
+#if defined(XP_WIN) && defined(EXTENDED_DEBUG_PRINTING)
+ // We need to clear all the output files here
+ // because they will be re-created with second reflow of the docs
+ if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ RemoveFilesInDir(".\\");
+ gDumpFileNameCnt = 0;
+ gDumpLOFileNameCnt = 0;
+ }
+#endif
+
+ // In this loop, it's conceivable that one of our helpers might clear mPrt,
+ // while we're using it & its members! So we capture it in an owning local
+ // reference & use that instead of using mPrt directly.
+ RefPtr<nsPrintData> printData = mPrt;
+ for (nsPrintObject* po : mPrintDocList) {
+ if (!po->PrintingIsEnabled() || po->mInvisible) {
+ continue;
+ }
+
+ // When the print object has been marked as "print the document" (i.e,
+ // po->PrintingIsEnabled() is true), mPresContext and mPresShell should be
+ // non-nullptr (i.e., should've been created for the print) since they
+ // are necessary to print the document.
+ MOZ_ASSERT(po->mPresContext && po->mPresShell,
+ "mPresContext and mPresShell shouldn't be nullptr when the "
+ "print object "
+ "has been marked as \"print the document\"");
+
+ UpdateZoomRatio(po);
+
+ po->mPresContext->SetPageScale(po->mZoomRatio);
+
+ // Calculate scale factor from printer to screen
+ float printDPI = float(AppUnitsPerCSSInch()) /
+ float(printData->mPrintDC->AppUnitsPerDevPixel());
+ po->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);
+
+ RefPtr<PresShell> presShell(po->mPresShell);
+ if (NS_WARN_IF(presShell->IsDestroying())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ presShell->ReconstructFrames();
+
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // For all views except the first one, setup the root view.
+ // ??? Can there be multiple po for the top-level-document?
+ bool documentIsTopLevel = true;
+ if (po->mParent) {
+ nsSize adjSize;
+ bool doReturn;
+ nsresult rv = SetRootView(po, doReturn, documentIsTopLevel, adjSize);
+
+ MOZ_ASSERT(!documentIsTopLevel, "How could this happen?");
+
+ if (NS_FAILED(rv) || doReturn) {
+ return rv;
+ }
+ }
+
+ presShell->FlushPendingNotifications(FlushType::Layout);
+
+ if (NS_WARN_IF(presShell->IsDestroying())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = UpdateSelectionAndShrinkPrintObject(po, documentIsTopLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+nsresult nsPrintJob::SetupToPrintContent() {
+ // This method may be called while DoCommonPrint() initializes the instance
+ // when its script blocker goes out of scope. In such case, this cannot do
+ // its job as expected because some objects in mPrt have not been initialized
+ // yet but they are necessary.
+ // Note: it shouldn't be possible for mPrintObject to be null; we check
+ // it for good measure (after we check its owner) before we start
+ // dereferencing it below.
+ if (NS_WARN_IF(!mPrt) || NS_WARN_IF(!mPrintObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this is creating print preview, mPrintObject->mPresContext and
+ // mPrintObject->mPresShell need to be non-nullptr because this cannot
+ // initialize page sequence frame without them at end of this method since
+ // page sequence frame has already been destroyed or not been created yet.
+ if (mIsCreatingPrintPreview && (NS_WARN_IF(!mPrintObject->mPresContext) ||
+ NS_WARN_IF(!mPrintObject->mPresShell))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this is printing some documents (not print-previewing the documents),
+ // mPrintObject->mPresContext and mPrintObject->mPresShell can be
+ // nullptr only when mPrintObject->PrintingIsEnabled() is false. E.g.,
+ // if the document has a <frameset> element and it's printing only content in
+ // a <frame> element or all <frame> elements separately.
+ MOZ_ASSERT(
+ (!mIsCreatingPrintPreview && !mPrintObject->PrintingIsEnabled()) ||
+ (mPrintObject->mPresContext && mPrintObject->mPresShell),
+ "mPresContext and mPresShell shouldn't be nullptr when printing the "
+ "document or creating print-preview");
+
+ bool didReconstruction = false;
+
+ // This method works with mPrintObject. So, we need to guarantee that
+ // it won't be deleted in this method. We achieve this by holding a strong
+ // local reference to mPrt, which in turn keeps mPrintObject alive.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ // If some new content got loaded since the initial reflow rebuild
+ // everything.
+ if (mDidLoadDataForPrinting) {
+ nsresult rv = ReconstructAndReflow();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+ didReconstruction = true;
+ }
+
+ // Here is where we figure out if extra reflow for shrinking the content
+ // is required.
+ if (mShrinkToFit) {
+ mShrinkToFitFactor = mPrintObject->mShrinkRatio;
+
+ if (mShrinkToFitFactor < 0.998f) {
+ nsresult rv = ReconstructAndReflow();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+ didReconstruction = true;
+ }
+
+ if (MOZ_LOG_TEST(gPrintingLog, LogLevel::Debug)) {
+ float calcRatio = mPrintObject->mShrinkRatio;
+ PR_PL(
+ ("*******************************************************************"
+ "*******\n"));
+ PR_PL(("STF Ratio is: %8.5f Effective Ratio: %8.5f Diff: %8.5f\n",
+ mShrinkToFitFactor, calcRatio, mShrinkToFitFactor - calcRatio));
+ PR_PL(
+ ("*******************************************************************"
+ "*******\n"));
+ }
+ }
+
+ // If the frames got reconstructed and reflowed the number of pages might
+ // has changed.
+ if (didReconstruction) {
+ FirePrintPreviewUpdateEvent();
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ DUMP_DOC_LIST(("\nAfter Reflow------------------------------------------"));
+ PR_PL(("\n"));
+ PR_PL(("-------------------------------------------------------\n"));
+ PR_PL(("\n"));
+
+ CalcNumPrintablePages(mNumPrintablePages);
+
+ PR_PL(("--- Printing %d pages\n", mNumPrintablePages));
+ DUMP_DOC_TREELAYOUT;
+
+ // Print listener setup...
+ printData->OnStartPrinting();
+
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString fileNameStr;
+ // check to see if we are printing to a file
+ if (mPrintSettings->GetOutputDestination() ==
+ nsIPrintSettings::kOutputDestinationFile) {
+ // On some platforms the BeginDocument needs to know the name of the file.
+ mPrintSettings->GetToFileName(fileNameStr);
+ }
+
+ nsAutoString docTitleStr;
+ nsAutoString docURLStr;
+ GetDisplayTitleAndURL(*mPrintObject->mDocument, mPrintSettings,
+ DocTitleDefault::eDocURLElseFallback, docTitleStr,
+ docURLStr);
+
+ int32_t startPage = 1;
+ int32_t endPage = mNumPrintablePages;
+
+ nsTArray<int32_t> ranges;
+ mPrintSettings->GetPageRanges(ranges);
+ for (size_t i = 0; i < ranges.Length(); i += 2) {
+ startPage = std::max(1, std::min(startPage, ranges[i]));
+ endPage = std::min(mNumPrintablePages, std::max(endPage, ranges[i + 1]));
+ }
+
+ nsresult rv = NS_OK;
+ // BeginDocument may pass back a FAILURE code
+ // i.e. On Windows, if you are printing to a file and hit "Cancel"
+ // to the "File Name" dialog, this comes back as an error
+ // Don't start printing when regression test are executed
+ if (mIsDoingPrinting) {
+ rv = printData->mPrintDC->BeginDocument(docTitleStr, fileNameStr, startPage,
+ endPage);
+ }
+
+ if (mIsCreatingPrintPreview) {
+ // Copy docTitleStr and docURLStr to the pageSequenceFrame, to be displayed
+ // in the header
+ nsPageSequenceFrame* seqFrame =
+ mPrintObject->mPresShell->GetPageSequenceFrame();
+ if (seqFrame) {
+ seqFrame->StartPrint(mPrintObject->mPresContext, mPrintSettings,
+ docTitleStr, docURLStr);
+ }
+ }
+
+ PR_PL(("****************** Begin Document ************************\n"));
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
+ "Failed to begin document for printing");
+ return rv;
+ }
+
+ // This will print the docshell document
+ // when it completes asynchronously in the DonePrintingSheets method
+ // it will check to see if there are more docshells to be printed and
+ // then PrintDocContent will be called again.
+
+ if (mIsDoingPrinting) {
+ // Double-check that mPrintObject is non-null, because it could have
+ // gotten cleared while running script since we last checked it.
+ if (NS_WARN_IF(!mPrintObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PrintDocContent(mPrintObject, rv); // ignore return value
+ }
+
+ return rv;
+}
+
+//-------------------------------------------------------
+// Recursively reflow each sub-doc and then calc
+// all the frame locations of the sub-docs
+nsresult nsPrintJob::ReflowDocList(const UniquePtr<nsPrintObject>& aPO) {
+ NS_ENSURE_ARG_POINTER(aPO);
+
+ // Check to see if the subdocument's element has been hidden by the parent
+ // document
+ if (aPO->mParent && aPO->mParent->mPresShell) {
+ nsIFrame* frame =
+ aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr;
+ if (!frame || !frame->StyleVisibility()->IsVisible()) {
+ aPO->EnablePrinting(false);
+ aPO->mInvisible = true;
+ return NS_OK;
+ }
+ }
+
+ UpdateZoomRatio(aPO.get());
+
+ // Reflow the PO
+ MOZ_TRY(ReflowPrintObject(aPO));
+
+ for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
+ MOZ_TRY(ReflowDocList(kid));
+ }
+ return NS_OK;
+}
+
+void nsPrintJob::FirePrintPreviewUpdateEvent() {
+ // Dispatch the event only while in PrintPreview. When printing, there is no
+ // listener bound to this event and therefore no need to dispatch it.
+ if (mCreatedForPrintPreview && !mIsDoingPrinting) {
+ nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
+ if (Document* document = viewer->GetDocument()) {
+ AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *document, u"printPreviewUpdate"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+ }
+ }
+}
+
+nsresult nsPrintJob::InitPrintDocConstruction(bool aHandleError) {
+ // Guarantee that mPrintObject won't be deleted.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ if (NS_WARN_IF(!printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Attach progressListener to catch network requests.
+ mDidLoadDataForPrinting = false;
+
+ {
+ AutoRestore<bool> restore{mDoingInitialReflow};
+ mDoingInitialReflow = true;
+
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(mPrintObject->mDocShell);
+ webProgress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
+ nsIWebProgress::NOTIFY_STATE_REQUEST);
+
+ MOZ_TRY(ReflowDocList(mPrintObject));
+
+ FirePrintPreviewUpdateEvent();
+ }
+
+ MaybeResumePrintAfterResourcesLoaded(aHandleError);
+ return NS_OK;
+}
+
+bool nsPrintJob::ShouldResumePrint() const {
+ if (mDoingInitialReflow) {
+ return false;
+ }
+ Document* doc = mPrintObject->mDocument;
+ MOZ_ASSERT(doc);
+ NS_ENSURE_TRUE(doc, true);
+ nsCOMPtr<nsILoadGroup> lg = doc->GetDocumentLoadGroup();
+ NS_ENSURE_TRUE(lg, true);
+ bool pending = false;
+ nsresult rv = lg->IsPending(&pending);
+ NS_ENSURE_SUCCESS(rv, true);
+ return !pending;
+}
+
+nsresult nsPrintJob::MaybeResumePrintAfterResourcesLoaded(
+ bool aCleanupOnError) {
+ if (!ShouldResumePrint()) {
+ mDidLoadDataForPrinting = true;
+ return NS_OK;
+ }
+ // If Destroy() has already been called, mPtr is nullptr. Then, the instance
+ // needs to do nothing anymore in this method.
+ // Note: it shouldn't be possible for mPrintObject to be null; we
+ // just check it for good measure, as we check its owner.
+ // Note: it shouldn't be possible for mPrintObject->mDocShell to be
+ // null; we just check it for good measure, as we check its owner.
+ if (!mPrt || NS_WARN_IF(!mPrintObject) ||
+ NS_WARN_IF(!mPrintObject->mDocShell)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(mPrintObject->mDocShell);
+
+ webProgress->RemoveProgressListener(
+ static_cast<nsIWebProgressListener*>(this));
+
+ nsresult rv;
+ if (mIsDoingPrinting) {
+ rv = DocumentReadyForPrinting();
+ } else {
+ rv = FinishPrintPreview();
+ }
+
+ /* cleaup on failure + notify user */
+ if (aCleanupOnError && NS_FAILED(rv)) {
+ NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
+ "nsPrintJob::ResumePrintAfterResourcesLoaded failed");
+ CleanupOnFailure(rv, !mIsDoingPrinting);
+ }
+
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsPrintJob::OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aStatus) {
+ if (aStateFlags & STATE_STOP) {
+ // If all resources are loaded, then finish and reflow.
+ MaybeResumePrintAfterResourcesLoaded(/* aCleanupOnError */ true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsIURI* aLocation, uint32_t aFlags) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus, const char16_t* aMessage) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aState) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aEvent) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+
+void nsPrintJob::UpdateZoomRatio(nsPrintObject* aPO) {
+ if (!aPO->mParent) {
+ if (mShrinkToFit) {
+ aPO->mZoomRatio = mShrinkToFitFactor;
+ // If we're actually going to scale (the factor is less than 1), we
+ // reduce the scale factor slightly to avoid the possibility of floating
+ // point rounding error causing slight clipping of the longest lines.
+ if (aPO->mZoomRatio != 1.0f) {
+ aPO->mZoomRatio -= 0.005f;
+ }
+ } else {
+ double scaling;
+ mPrintSettings->GetScaling(&scaling);
+ aPO->mZoomRatio = float(scaling);
+ }
+ }
+}
+
+nsresult nsPrintJob::UpdateSelectionAndShrinkPrintObject(
+ nsPrintObject* aPO, bool aDocumentIsTopLevel) {
+ PresShell* displayPresShell = aPO->mDocShell->GetPresShell();
+ // Transfer Selection Ranges to the new Print PresShell
+ RefPtr<Selection> selection, selectionPS;
+ // It's okay if there is no display shell, just skip copying the selection
+ if (displayPresShell) {
+ selection = displayPresShell->GetCurrentSelection(SelectionType::eNormal);
+ }
+ selectionPS = aPO->mPresShell->GetCurrentSelection(SelectionType::eNormal);
+
+ // Reset all existing selection ranges that might have been added by calling
+ // this function before.
+ if (selectionPS) {
+ selectionPS->RemoveAllRanges(IgnoreErrors());
+ }
+ if (selection && selectionPS) {
+ const uint32_t rangeCount = selection->RangeCount();
+ for (const uint32_t inx : IntegerRange(rangeCount)) {
+ MOZ_ASSERT(selection->RangeCount() == rangeCount);
+ const RefPtr<nsRange> range{selection->GetRangeAt(inx)};
+ selectionPS->AddRangeAndSelectFramesAndNotifyListeners(*range,
+ IgnoreErrors());
+ }
+ }
+
+ // If we are trying to shrink the contents to fit on the page
+ // we must first locate the "pageContent" frame
+ // Then we walk the frame tree and look for the "xmost" frame
+ // this is the frame where the right-hand side of the frame extends
+ // the furthest
+ if (mShrinkToFit && aDocumentIsTopLevel) {
+ nsPageSequenceFrame* pageSeqFrame = aPO->mPresShell->GetPageSequenceFrame();
+ NS_ENSURE_STATE(pageSeqFrame);
+ aPO->mShrinkRatio = pageSeqFrame->GetSTFPercent();
+ // Limit the shrink-to-fit scaling for some text-ish type of documents.
+ nsAutoString contentType;
+ aPO->mPresShell->GetDocument()->GetContentType(contentType);
+ if (contentType.EqualsLiteral("application/xhtml+xml") ||
+ StringBeginsWith(contentType, u"text/"_ns)) {
+ int32_t limitPercent =
+ Preferences::GetInt("print.shrink-to-fit.scale-limit-percent", 20);
+ limitPercent = std::max(0, limitPercent);
+ limitPercent = std::min(100, limitPercent);
+ float minShrinkRatio = float(limitPercent) / 100;
+ aPO->mShrinkRatio = std::max(aPO->mShrinkRatio, minShrinkRatio);
+ }
+ }
+ return NS_OK;
+}
+
+nsView* nsPrintJob::GetParentViewForRoot() {
+ if (mIsCreatingPrintPreview) {
+ if (nsCOMPtr<nsIDocumentViewer> viewer =
+ do_QueryInterface(mDocViewerPrint)) {
+ return viewer->FindContainerView();
+ }
+ }
+ return nullptr;
+}
+
+nsresult nsPrintJob::SetRootView(nsPrintObject* aPO, bool& doReturn,
+ bool& documentIsTopLevel, nsSize& adjSize) {
+ bool canCreateScrollbars = true;
+
+ nsView* rootView;
+ nsView* parentView = nullptr;
+
+ doReturn = false;
+
+ if (aPO->mParent && aPO->mParent->PrintingIsEnabled()) {
+ nsIFrame* frame =
+ aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr;
+ // Without a frame, this document can't be displayed; therefore, there is no
+ // point to reflowing it
+ if (!frame) {
+ aPO->EnablePrinting(false);
+ doReturn = true;
+ return NS_OK;
+ }
+
+ // XXX If printing supported printing document hierarchies with non-constant
+ // zoom this would be wrong as we use the same mPrt->mPrintDC for all
+ // subdocuments.
+ adjSize = frame->GetContentRect().Size();
+ documentIsTopLevel = false;
+ // presshell exists because parent is printable
+
+ // the top nsPrintObject's widget will always have scrollbars
+ if (frame && frame->IsSubDocumentFrame()) {
+ nsView* view = frame->GetView();
+ NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
+ view = view->GetFirstChild();
+ NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
+ parentView = view;
+ canCreateScrollbars = false;
+ }
+ } else {
+ nscoord pageWidth, pageHeight;
+ mPrt->mPrintDC->GetDeviceSurfaceDimensions(pageWidth, pageHeight);
+ adjSize = nsSize(pageWidth, pageHeight);
+ documentIsTopLevel = true;
+ parentView = GetParentViewForRoot();
+ }
+
+ if (aPO->mViewManager->GetRootView()) {
+ // Reuse the root view that is already on the root frame.
+ rootView = aPO->mViewManager->GetRootView();
+ // Remove it from its existing parent if necessary
+ aPO->mViewManager->RemoveChild(rootView);
+ rootView->SetParent(parentView);
+ } else {
+ // Create a child window of the parent that is our "root view/window"
+ nsRect tbounds = nsRect(nsPoint(0, 0), adjSize);
+ rootView = aPO->mViewManager->CreateView(tbounds, parentView);
+ NS_ENSURE_TRUE(rootView, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ if (mIsCreatingPrintPreview && documentIsTopLevel) {
+ aPO->mPresContext->SetPaginatedScrolling(canCreateScrollbars);
+ }
+
+ // Setup hierarchical relationship in view manager
+ aPO->mViewManager->SetRootView(rootView);
+
+ return NS_OK;
+}
+
+// Reflow a nsPrintObject
+nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) {
+ NS_ENSURE_STATE(aPO);
+
+ if (!aPO->PrintingIsEnabled()) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!aPO->mPresContext, "Recreating prescontext");
+
+ // Guarantee that mPrt and the objects it owns won't be deleted in this method
+ // because it might be cleared if other modules called from here may fire
+ // events, notifying observers and/or listeners.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ // create the PresContext
+ nsPresContext::nsPresContextType type =
+ mIsCreatingPrintPreview ? nsPresContext::eContext_PrintPreview
+ : nsPresContext::eContext_Print;
+ const bool shouldBeRoot =
+ (!aPO->mParent || !aPO->mParent->PrintingIsEnabled()) &&
+ !GetParentViewForRoot();
+ aPO->mPresContext = shouldBeRoot ? new nsRootPresContext(aPO->mDocument, type)
+ : new nsPresContext(aPO->mDocument, type);
+ aPO->mPresContext->SetPrintSettings(mPrintSettings);
+
+ // init it with the DC
+ MOZ_TRY(aPO->mPresContext->Init(printData->mPrintDC));
+
+ aPO->mViewManager = new nsViewManager();
+
+ MOZ_TRY(aPO->mViewManager->Init(printData->mPrintDC));
+
+ bool doReturn = false;
+ bool documentIsTopLevel = false;
+ nsSize adjSize;
+
+ nsresult rv = SetRootView(aPO.get(), doReturn, documentIsTopLevel, adjSize);
+
+ if (NS_FAILED(rv) || doReturn) {
+ return rv;
+ }
+
+ // Here, we inform nsPresContext of the page size. Note that 'adjSize' is
+ // *usually* the page size, but we need to check. Strictly speaking, adjSize
+ // is the *device output size*, which is really the dimensions of a "sheet"
+ // rather than a "page" (an important distinction in an N-pages-per-sheet
+ // scenario). For some pages-per-sheet values, the pages are orthogonal to
+ // the sheet; we adjust for that here by swapping the width with the height.
+ nsSize pageSize = adjSize;
+ if (mPrintSettings->HasOrthogonalPagesPerSheet()) {
+ std::swap(pageSize.width, pageSize.height);
+ }
+ // XXXalaskanemily: Is this actually necessary? We set it again before the
+ // first reflow.
+ aPO->mPresContext->SetPageSize(pageSize);
+
+ int32_t p2a = aPO->mPresContext->DeviceContext()->AppUnitsPerDevPixel();
+ if (documentIsTopLevel && mIsCreatingPrintPreview) {
+ if (nsCOMPtr<nsIDocumentViewer> viewer =
+ do_QueryInterface(mDocViewerPrint)) {
+ // If we're print-previewing and the top level document, use the bounds
+ // from our doc viewer. Page bounds is not what we want.
+ nsIntRect bounds;
+ viewer->GetBounds(bounds);
+ adjSize = nsSize(bounds.width * p2a, bounds.height * p2a);
+ }
+ }
+ aPO->mPresContext->SetIsRootPaginatedDocument(documentIsTopLevel);
+ aPO->mPresContext->SetVisibleArea(nsRect(nsPoint(), adjSize));
+ aPO->mPresContext->SetPageScale(aPO->mZoomRatio);
+ // Calculate scale factor from printer to screen
+ float printDPI = float(AppUnitsPerCSSInch()) / float(p2a);
+ aPO->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);
+
+ // Do CreatePresShell() after we setup the page size, the visible area, and
+ // the flag |mIsRootPaginatedDocument|, to make sure we can resolve the
+ // correct viewport size for the print preview page when notifying the media
+ // feature values changed. See au_viewport_size_for_viewport_unit_resolution()
+ // in media_queries.rs for more details.
+ RefPtr<Document> doc = aPO->mDocument;
+ RefPtr<nsPresContext> presContext = aPO->mPresContext;
+ RefPtr<nsViewManager> viewManager = aPO->mViewManager;
+
+ aPO->mPresShell = doc->CreatePresShell(presContext, viewManager);
+ if (!aPO->mPresShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we're printing selection then remove the nonselected nodes from our
+ // cloned document.
+ if (mPrintSettings->GetPrintSelectionOnly()) {
+ // If we fail to remove the nodes then we should fail to print, because if
+ // the user was trying to print a small selection from a large document,
+ // sending the whole document to a real printer would be very frustrating.
+ MOZ_TRY(DeleteNonSelectedNodes(*aPO->mDocument));
+ }
+
+ aPO->mPresShell->BeginObservingDocument();
+
+ PR_PL(
+ ("In DV::ReflowPrintObject PO: %p pS: %p (%9s) Setting page size w,h to "
+ "%d,%d\n",
+ aPO.get(), aPO->mPresShell.get(), LoggableTypeOfPO(aPO.get()),
+ pageSize.width, pageSize.height));
+
+ if (mIsCreatingPrintPreview && documentIsTopLevel) {
+ mDocViewerPrint->SetPrintPreviewPresentation(
+ aPO->mViewManager, aPO->mPresContext, aPO->mPresShell.get());
+ }
+
+ MOZ_TRY(aPO->mPresShell->Initialize());
+ NS_ASSERTION(aPO->mPresShell, "Presshell should still be here");
+
+ RefPtr<PresShell> presShell = aPO->mPresShell;
+ {
+ const ServoStyleSet::PageSizeAndOrientation sizeAndOrientation =
+ presShell->StyleSet()->GetDefaultPageSizeAndOrientation();
+ // XXX Should we enable this for known save-to-PDF pseudo-printers once
+ // bug 1826301 is fixed?
+ if (mPrintSettings->GetOutputFormat() ==
+ nsIPrintSettings::kOutputFormatPDF &&
+ StaticPrefs::
+ print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) {
+ mMaybeCSSPageSize = sizeAndOrientation.size;
+ if (sizeAndOrientation.size) {
+ pageSize = sizeAndOrientation.size.value();
+ aPO->mPresContext->SetPageSize(pageSize);
+ }
+ }
+
+ // If the document has a specified CSS page-size, we rotate the page to
+ // reflect this. Changing the orientation is reflected by the result of
+ // FinishPrintPreview, so that the frontend can reflect this.
+ // The new document has not yet been reflowed, so we have to query the
+ // original document for any CSS page-size.
+ if (sizeAndOrientation.orientation) {
+ switch (sizeAndOrientation.orientation.value()) {
+ case StylePageSizeOrientation::Landscape:
+ if (pageSize.width < pageSize.height) {
+ // Paper is in portrait, CSS page size is landscape.
+ std::swap(pageSize.width, pageSize.height);
+ }
+ break;
+ case StylePageSizeOrientation::Portrait:
+ if (pageSize.width > pageSize.height) {
+ // Paper is in landscape, CSS page size is portrait.
+ std::swap(pageSize.width, pageSize.height);
+ }
+ break;
+ }
+ mMaybeCSSPageLandscape = Some(sizeAndOrientation.orientation.value() ==
+ StylePageSizeOrientation::Landscape);
+ aPO->mPresContext->SetPageSize(pageSize);
+ }
+ }
+ // Process the reflow event Initialize posted
+ presShell->FlushPendingNotifications(FlushType::Layout);
+
+ MOZ_TRY(UpdateSelectionAndShrinkPrintObject(aPO.get(), documentIsTopLevel));
+
+#ifdef EXTENDED_DEBUG_PRINTING
+ if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ nsAutoCString docStr;
+ nsAutoCString urlStr;
+ GetDocTitleAndURL(aPO, docStr, urlStr);
+ char filename[256];
+ sprintf(filename, "print_dump_%d.txt", gDumpFileNameCnt++);
+ // Dump all the frames and view to a a file
+ FILE* fd = fopen(filename, "w");
+ if (fd) {
+ nsIFrame* theRootFrame = aPO->mPresShell->GetRootFrame();
+ fprintf(fd, "Title: %s\n", docStr.get());
+ fprintf(fd, "URL: %s\n", urlStr.get());
+ fprintf(fd, "--------------- Frames ----------------\n");
+ // RefPtr<gfxContext> renderingContext =
+ // printData->mPrintDocDC->CreateRenderingContext();
+ RootFrameList(aPO->mPresContext, fd, 0);
+ // DumpFrames(fd, aPO->mPresContext, renderingContext, theRootFrame, 0);
+ fprintf(fd, "---------------------------------------\n\n");
+ fprintf(fd, "--------------- Views From Root Frame----------------\n");
+ nsView* v = theRootFrame->GetView();
+ if (v) {
+ v->List(fd);
+ } else {
+ printf("View is null!\n");
+ }
+ if (aPO->mDocShell) {
+ fprintf(fd, "--------------- All Views ----------------\n");
+ DumpViews(aPO->mDocShell, fd);
+ fprintf(fd, "---------------------------------------\n\n");
+ }
+ fclose(fd);
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+// Figure out how many documents and how many total pages we are printing
+void nsPrintJob::CalcNumPrintablePages(int32_t& aNumPages) {
+ aNumPages = 0;
+ // Count the number of printable documents and printable pages
+ for (nsPrintObject* po : mPrintDocList) {
+ // Note: The po->mPresContext null-check below is necessary, because it's
+ // possible po->mPresContext might never have been set. (e.g., if
+ // PrintingIsEnabled() returns false, ReflowPrintObject bails before setting
+ // mPresContext)
+ if (po->mPresContext && po->mPresContext->IsRootPaginatedDocument()) {
+ nsPageSequenceFrame* seqFrame = po->mPresShell->GetPageSequenceFrame();
+ if (seqFrame) {
+ aNumPages += seqFrame->PrincipalChildList().GetLength();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------
+//-- Done: Reflow Methods
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+//-- Section: Printing Methods
+//-----------------------------------------------------------------
+
+//-------------------------------------------------------
+// Called for each DocShell that needs to be printed
+bool nsPrintJob::PrintDocContent(const UniquePtr<nsPrintObject>& aPO,
+ nsresult& aStatus) {
+ NS_ASSERTION(aPO, "Pointer is null!");
+ aStatus = NS_OK;
+
+ if (!aPO->mHasBeenPrinted && aPO->PrintingIsEnabled()) {
+ aStatus = DoPrint(aPO);
+ return true;
+ }
+
+ // If |aPO->mHasBeenPrinted| is true,
+ // the kids frames are already processed in |PrintPage|.
+ // XXX This should be removed. Since bug 1552785 it has no longer been
+ // possible for us to have to print multiple subdocuments consecutively.
+ if (!aPO->mHasBeenPrinted && !aPO->mInvisible) {
+ for (const UniquePtr<nsPrintObject>& po : aPO->mKids) {
+ bool printed = PrintDocContent(po, aStatus);
+ if (printed || NS_FAILED(aStatus)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// A helper struct to aid with DeleteNonSelectedNodes.
+struct MOZ_STACK_CLASS SelectionRangeState {
+ explicit SelectionRangeState(RefPtr<Selection> aSelection)
+ : mSelection(std::move(aSelection)) {
+ MOZ_ASSERT(mSelection);
+ MOZ_ASSERT(!mSelection->RangeCount());
+ }
+
+ // Selects all the nodes that are _not_ included in a given set of ranges.
+ MOZ_CAN_RUN_SCRIPT void SelectComplementOf(Span<const RefPtr<nsRange>>);
+ // Removes the selected ranges from the document.
+ MOZ_CAN_RUN_SCRIPT void RemoveSelectionFromDocument();
+
+ private:
+ struct Position {
+ nsINode* mNode;
+ uint32_t mOffset;
+ };
+
+ MOZ_CAN_RUN_SCRIPT void SelectRange(nsRange*);
+ MOZ_CAN_RUN_SCRIPT void SelectNodesExcept(const Position& aStart,
+ const Position& aEnd);
+ MOZ_CAN_RUN_SCRIPT void SelectNodesExceptInSubtree(const Position& aStart,
+ const Position& aEnd);
+
+ // A map from subtree root (document or shadow root) to the start position of
+ // the non-selected content (so far).
+ nsTHashMap<nsPtrHashKey<nsINode>, Position> mPositions;
+
+ // The selection we're adding the ranges to.
+ const RefPtr<Selection> mSelection;
+};
+
+void SelectionRangeState::SelectComplementOf(
+ Span<const RefPtr<nsRange>> aRanges) {
+ for (const auto& range : aRanges) {
+ auto start = Position{range->GetStartContainer(), range->StartOffset()};
+ auto end = Position{range->GetEndContainer(), range->EndOffset()};
+ SelectNodesExcept(start, end);
+ }
+}
+
+void SelectionRangeState::SelectRange(nsRange* aRange) {
+ if (aRange && !aRange->Collapsed()) {
+ mSelection->AddRangeAndSelectFramesAndNotifyListeners(*aRange,
+ IgnoreErrors());
+ }
+}
+
+void SelectionRangeState::SelectNodesExcept(const Position& aStart,
+ const Position& aEnd) {
+ SelectNodesExceptInSubtree(aStart, aEnd);
+ if (auto* shadow = ShadowRoot::FromNode(aStart.mNode->SubtreeRoot())) {
+ auto* host = shadow->Host();
+ SelectNodesExcept(Position{host, 0}, Position{host, host->GetChildCount()});
+ } else {
+ MOZ_ASSERT(aStart.mNode->IsInUncomposedDoc());
+ }
+}
+
+void SelectionRangeState::SelectNodesExceptInSubtree(const Position& aStart,
+ const Position& aEnd) {
+ static constexpr auto kEllipsis = u"\x2026"_ns;
+
+ nsINode* root = aStart.mNode->SubtreeRoot();
+ auto& start =
+ mPositions.WithEntryHandle(root, [&](auto&& entry) -> Position& {
+ return entry.OrInsertWith([&] { return Position{root, 0}; });
+ });
+
+ bool ellipsizedStart = false;
+ if (auto* text = Text::FromNode(aStart.mNode)) {
+ if (start.mNode != text && aStart.mOffset &&
+ aStart.mOffset < text->Length()) {
+ text->InsertData(aStart.mOffset, kEllipsis, IgnoreErrors());
+ ellipsizedStart = true;
+ }
+ }
+
+ RefPtr<nsRange> range = nsRange::Create(
+ start.mNode, start.mOffset, aStart.mNode, aStart.mOffset, IgnoreErrors());
+ SelectRange(range);
+
+ start = aEnd;
+
+ // If we added an ellipsis at the start and the end position was relative to
+ // the same node account for it here.
+ if (ellipsizedStart && aStart.mNode == aEnd.mNode) {
+ start.mOffset += kEllipsis.Length();
+ }
+
+ // If the end is mid text then add an ellipsis.
+ if (auto* text = Text::FromNode(start.mNode)) {
+ if (start.mOffset && start.mOffset < text->Length()) {
+ text->InsertData(start.mOffset, kEllipsis, IgnoreErrors());
+ start.mOffset += kEllipsis.Length();
+ }
+ }
+}
+
+void SelectionRangeState::RemoveSelectionFromDocument() {
+ for (auto& entry : mPositions) {
+ const Position& pos = entry.GetData();
+ nsINode* root = entry.GetKey();
+ RefPtr<nsRange> range = nsRange::Create(
+ pos.mNode, pos.mOffset, root, root->GetChildCount(), IgnoreErrors());
+ SelectRange(range);
+ }
+ mSelection->DeleteFromDocument(IgnoreErrors());
+}
+
+/**
+ * Builds the complement set of ranges and adds those to the selection.
+ * Deletes all of the nodes contained in the complement set of ranges
+ * leaving behind only nodes that were originally selected.
+ * Adds ellipses to a selected node's text if text is truncated by a range.
+ * This is used to implement the "Print Selection Only" user interface option.
+ */
+MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteNonSelectedNodes(
+ Document& aDoc) {
+ MOZ_ASSERT(aDoc.IsStaticDocument());
+ const auto* printRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
+ aDoc.GetProperty(nsGkAtoms::printselectionranges));
+ if (!printRanges) {
+ return NS_OK;
+ }
+
+ PresShell* presShell = aDoc.GetPresShell();
+ NS_ENSURE_STATE(presShell);
+ RefPtr<Selection> selection =
+ presShell->GetCurrentSelection(SelectionType::eNormal);
+ NS_ENSURE_STATE(selection);
+
+ SelectionRangeState state(std::move(selection));
+ state.SelectComplementOf(*printRanges);
+ state.RemoveSelectionFromDocument();
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+nsresult nsPrintJob::DoPrint(const UniquePtr<nsPrintObject>& aPO) {
+ PR_PL(("\n"));
+ PR_PL(("**************************** %s ****************************\n",
+ LoggableTypeOfPO(aPO.get())));
+ PR_PL(("****** In DV::DoPrint PO: %p \n", aPO.get()));
+
+ PresShell* poPresShell = aPO->mPresShell;
+ nsPresContext* poPresContext = aPO->mPresContext;
+
+ NS_ASSERTION(poPresContext, "PrintObject has not been reflowed");
+ NS_ASSERTION(poPresContext->Type() != nsPresContext::eContext_PrintPreview,
+ "How did this context end up here?");
+
+ // Guarantee that mPrt and the objects it owns won't be deleted in this method
+ // because it might be cleared if other modules called from here may fire
+ // events, notifying observers and/or listeners.
+ RefPtr<nsPrintData> printData = mPrt;
+ if (NS_WARN_IF(!printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ // Ask the page sequence frame to print all the pages
+ nsPageSequenceFrame* seqFrame = poPresShell->GetPageSequenceFrame();
+ MOZ_ASSERT(seqFrame, "no page sequence frame");
+
+ // We are done preparing for printing, so we can turn this off
+ mPreparingForPrint = false;
+
+#ifdef EXTENDED_DEBUG_PRINTING
+ nsIFrame* rootFrame = poPresShell->GetRootFrame();
+ if (aPO->PrintingIsEnabled()) {
+ nsAutoCString docStr;
+ nsAutoCString urlStr;
+ GetDocTitleAndURL(aPO, docStr, urlStr);
+ DumpLayoutData(docStr.get(), urlStr.get(), poPresContext,
+ printData->mPrintDC, rootFrame, aPO->mDocShell, nullptr);
+ }
+#endif
+
+ if (!mPrintSettings) {
+ // not sure what to do here!
+ SetIsPrinting(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString docTitleStr;
+ nsAutoString docURLStr;
+ GetDisplayTitleAndURL(*aPO->mDocument, mPrintSettings,
+ DocTitleDefault::eFallback, docTitleStr, docURLStr);
+
+ if (!seqFrame) {
+ SetIsPrinting(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ // For telemetry, get paper size being used; convert the dimensions to
+ // points and ensure they reflect portrait orientation.
+ nsIPrintSettings* settings = mPrintSettings;
+ double paperWidth, paperHeight;
+ settings->GetPaperWidth(&paperWidth);
+ settings->GetPaperHeight(&paperHeight);
+ int16_t sizeUnit;
+ settings->GetPaperSizeUnit(&sizeUnit);
+ switch (sizeUnit) {
+ case nsIPrintSettings::kPaperSizeInches:
+ paperWidth *= 72.0;
+ paperHeight *= 72.0;
+ break;
+ case nsIPrintSettings::kPaperSizeMillimeters:
+ paperWidth *= 72.0 / 25.4;
+ paperHeight *= 72.0 / 25.4;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown paper size unit");
+ break;
+ }
+ if (paperWidth > paperHeight) {
+ std::swap(paperWidth, paperHeight);
+ }
+ // Use the paper size to build a Telemetry Scalar key.
+ nsString key;
+ key.AppendInt(int32_t(NS_round(paperWidth)));
+ key.Append(u"x");
+ key.AppendInt(int32_t(NS_round(paperHeight)));
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_PAPER_SIZE, key, 1);
+
+ mPageSeqFrame = seqFrame;
+ seqFrame->StartPrint(poPresContext, settings, docTitleStr, docURLStr);
+
+ // Schedule Page to Print
+ PR_PL(("Scheduling Print of PO: %p (%s) \n", aPO.get(),
+ LoggableTypeOfPO(aPO.get())));
+ StartPagePrintTimer(aPO);
+ }
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+bool nsPrintJob::PrePrintSheet() {
+ NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
+ NS_ASSERTION(mPrt, "mPrt is null!");
+
+ // Although these should NEVER be nullptr
+ // This is added insurance, to make sure we don't crash in optimized builds
+ if (!mPrt || !mPageSeqFrame.IsAlive()) {
+ return true; // means we are done preparing the sheet.
+ }
+
+ // Guarantee that mPrt won't be deleted during a call of
+ // FirePrintingErrorEvent().
+ RefPtr<nsPrintData> printData = mPrt;
+
+ // Ask mPageSeqFrame if the sheet is ready to be printed.
+ // If the sheet doesn't get printed at all, the |done| will be |true|.
+ bool done = false;
+ nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
+ nsresult rv = pageSeqFrame->PrePrintNextSheet(mPagePrintTimer, &done);
+ if (NS_FAILED(rv)) {
+ // ??? ::PrintSheet doesn't set |printData->mIsAborted = true| if
+ // rv != NS_ERROR_ABORT, but I don't really understand why this should be
+ // the right thing to do? Shouldn't |printData->mIsAborted| set to true
+ // all the time if something went wrong?
+ if (rv != NS_ERROR_ABORT) {
+ FirePrintingErrorEvent(rv);
+ printData->mIsAborted = true;
+ }
+ done = true;
+ }
+ return done;
+}
+
+bool nsPrintJob::PrintSheet(nsPrintObject* aPO) {
+ NS_ASSERTION(aPO, "aPO is null!");
+ NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
+ NS_ASSERTION(mPrt, "mPrt is null!");
+
+ // Although these should NEVER be nullptr
+ // This is added insurance, to make sure we don't crash in optimized builds
+ if (!mPrt || !aPO || !mPageSeqFrame.IsAlive()) {
+ FirePrintingErrorEvent(NS_ERROR_FAILURE);
+ return true; // means we are done printing
+ }
+
+ // Guarantee that mPrt won't be deleted during a call of
+ // nsPrintData::DoOnProgressChange() which runs some listeners,
+ // which may clear (& might otherwise destroy).
+ RefPtr<nsPrintData> printData = mPrt;
+
+ PR_PL(("-----------------------------------\n"));
+ PR_PL(("------ In DV::PrintSheet PO: %p (%s)\n", aPO, LoggableTypeOfPO(aPO)));
+
+ if (printData->mIsAborted) {
+ return true;
+ }
+
+ nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
+ const uint32_t sheetIdx = pageSeqFrame->GetCurrentSheetIdx();
+ const uint32_t numSheets = pageSeqFrame->PrincipalChildList().GetLength();
+
+ PR_PL(("****** Printing sheet index %d of %d sheets(s)\n", sheetIdx,
+ numSheets));
+
+ MOZ_ASSERT(numSheets > 0, "print operations must have at least 1 sheet");
+ MOZ_ASSERT(sheetIdx < numSheets,
+ "sheetIdx shouldn't be allowed to go out of bounds");
+ printData->DoOnProgressChange(sheetIdx, numSheets, false, 0);
+ if (NS_WARN_IF(mPrt != printData)) {
+ // If current printing is canceled or new print is started, let's return
+ // true to notify the caller of current printing is done.
+ return true;
+ }
+
+ // Print the sheet
+ // if a print job was cancelled externally, an EndPage or BeginPage may
+ // fail and the failure is passed back here.
+ // Returning true means we are done printing.
+ //
+ // When rv == NS_ERROR_ABORT, it means we want out of the
+ // print job without displaying any error messages
+ nsresult rv = pageSeqFrame->PrintNextSheet();
+ if (NS_FAILED(rv)) {
+ if (rv != NS_ERROR_ABORT) {
+ FirePrintingErrorEvent(rv);
+ printData->mIsAborted = true;
+ }
+ return true;
+ }
+
+ pageSeqFrame->DoPageEnd();
+
+ // If we just printed the final sheet (the one with index "numSheets-1"),
+ // then we're done!
+ return (sheetIdx == numSheets - 1);
+}
+
+void nsPrintJob::PageDone(nsresult aResult) {
+ MOZ_ASSERT(mIsDoingPrinting);
+
+ // mPagePrintTimer might be released during RemotePrintFinished, keep a
+ // reference here to make sure it lives long enough.
+ RefPtr<nsPagePrintTimer> timer = mPagePrintTimer;
+ timer->RemotePrintFinished();
+}
+
+//-----------------------------------------------------------------
+//-- Done: Printing Methods
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+//-- Section: Misc Support Methods
+//-----------------------------------------------------------------
+
+//---------------------------------------------------------------------
+void nsPrintJob::SetIsPrinting(bool aIsPrinting) {
+ mIsDoingPrinting = aIsPrinting;
+ if (aIsPrinting) {
+ mPreparingForPrint = true;
+ }
+}
+
+//---------------------------------------------------------------------
+void nsPrintJob::SetIsPrintPreview(bool aIsPrintPreview) {
+ mCreatedForPrintPreview = aIsPrintPreview;
+
+ if (mDocViewerPrint) {
+ mDocViewerPrint->SetIsPrintPreview(aIsPrintPreview);
+ }
+}
+
+//-------------------------------------------------------
+bool nsPrintJob::DonePrintingSheets(nsPrintObject* aPO, nsresult aResult) {
+ // NS_ASSERTION(aPO, "Pointer is null!");
+ PR_PL(("****** In DV::DonePrintingSheets PO: %p (%s)\n", aPO,
+ aPO ? LoggableTypeOfPO(aPO) : ""));
+
+ // If there is a pageSeqFrame, make sure there are no more printCanvas active
+ // that might call |Notify| on the pagePrintTimer after things are cleaned up
+ // and printing was marked as being done.
+ if (mPageSeqFrame.IsAlive()) {
+ nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
+ pageSeqFrame->ResetPrintCanvasList();
+ }
+
+ // Guarantee that mPrt and mPrintObject won't be deleted during a
+ // call of PrintDocContent() and FirePrintCompletionEvent().
+ RefPtr<nsPrintData> printData = mPrt;
+
+ if (aPO && !printData->mIsAborted) {
+ aPO->mHasBeenPrinted = true;
+ nsresult rv;
+ bool didPrint = PrintDocContent(mPrintObject, rv);
+ if (NS_SUCCEEDED(rv) && didPrint) {
+ PR_PL(
+ ("****** In DV::DonePrintingSheets PO: %p (%s) didPrint:%s (Not Done "
+ "Printing)\n",
+ aPO, LoggableTypeOfPO(aPO), PRT_YESNO(didPrint)));
+ return false;
+ }
+ }
+
+ if (NS_SUCCEEDED(aResult)) {
+ FirePrintCompletionEvent();
+ // XXX mPrt may be cleared or replaced with new instance here.
+ // However, the following methods will clean up with new mPrt or will
+ // do nothing due to no proper nsPrintData instance.
+ }
+
+ SetIsPrinting(false);
+
+ // Release reference to mPagePrintTimer; the timer object destroys itself
+ // after this returns true
+ DisconnectPagePrintTimer();
+
+ return true;
+}
+
+//-------------------------------------------------------
+nsresult nsPrintJob::EnablePOsForPrinting() {
+ // Guarantee that mPrt and the objects it owns won't be deleted.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ // NOTE: All POs have been "turned off" for printing
+ // this is where we decided which POs get printed.
+
+ if (!printData || !mPrintSettings) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PR_PL(("\n"));
+ PR_PL(("********* nsPrintJob::EnablePOsForPrinting *********\n"));
+
+ if (!mPrintSettings->GetPrintSelectionOnly()) {
+ mPrintObject->EnablePrinting(true);
+ return NS_OK;
+ }
+
+ // This means we are either printing a selected iframe or
+ // we are printing the current selection.
+ NS_ENSURE_STATE(!mDisallowSelectionPrint && mSelectionRoot);
+
+ // If mSelectionRoot is a selected iframe without a selection, then just
+ // enable normally from that point.
+ if (mSelectionRoot->mParent && !mSelectionRoot->HasSelection()) {
+ mSelectionRoot->EnablePrinting(true);
+ } else {
+ // Otherwise, only enable nsPrintObjects that have a selection.
+ mSelectionRoot->EnablePrintingSelectionOnly();
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------
+//-- Done: Misc Support Methods
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+//-- Section: Finishing up or Cleaning up
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+nsresult nsPrintJob::FinishPrintPreview() {
+ nsresult rv = NS_OK;
+
+#ifdef NS_PRINT_PREVIEW
+
+ // If mPrt is null we've already finished with print preview. If mPrt is not
+ // null but mIsCreatingPrintPreview is false FinishPrintPreview must have
+ // already failed due to DocumentReadyForPrinting failing.
+ if (!mPrt || !mIsCreatingPrintPreview) {
+ return rv;
+ }
+
+ rv = DocumentReadyForPrinting();
+
+ // Note that this method may be called while the instance is being
+ // initialized. Some methods which initialize the instance (e.g.,
+ // DoCommonPrint) may need to stop initializing and return error if
+ // this is called. Therefore it's important to set mIsCreatingPrintPreview
+ // state to false here. If you need to stop setting that here, you need to
+ // keep them being able to check whether the owner stopped using this
+ // instance.
+ mIsCreatingPrintPreview = false;
+
+ // mPrt may be cleared during a call of nsPrintData::OnEndPrinting()
+ // because that method invokes some arbitrary listeners.
+ // TODO(dshin): Does any listener attach to print preview? Doesn't seem like
+ // we call matching `OnStartPrinting()` for previews.
+ RefPtr<nsPrintData> printData = mPrt;
+ if (NS_FAILED(rv)) {
+ printData->OnEndPrinting();
+
+ return rv;
+ }
+
+ if (mPrintPreviewCallback) {
+ const bool hasSelection = !mDisallowSelectionPrint && mSelectionRoot;
+
+ Maybe<float> pageWidth;
+ Maybe<float> pageHeight;
+ if (mMaybeCSSPageSize) {
+ nsSize cssPageSize = *mMaybeCSSPageSize;
+ pageWidth = Some(float(cssPageSize.width) / float(AppUnitsPerCSSInch()));
+ pageHeight =
+ Some(float(cssPageSize.height) / float(AppUnitsPerCSSInch()));
+ }
+
+ mPrintPreviewCallback(PrintPreviewResultInfo(
+ GetPrintPreviewNumSheets(), GetRawNumPages(), GetIsEmpty(),
+ hasSelection, hasSelection && mPrintObject->HasSelection(),
+ mMaybeCSSPageLandscape, pageWidth, pageHeight));
+ mPrintPreviewCallback = nullptr;
+ }
+
+ // At this point we are done preparing everything
+ // before it is to be created
+
+ printData->OnEndPrinting();
+
+#endif // NS_PRINT_PREVIEW
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------
+//-- Done: Finishing up or Cleaning up
+//-----------------------------------------------------------------
+
+/*=============== Timer Related Code ======================*/
+nsresult nsPrintJob::StartPagePrintTimer(const UniquePtr<nsPrintObject>& aPO) {
+ if (!mPagePrintTimer) {
+ // Get the delay time in between the printing of each page
+ // this gives the user more time to press cancel
+ int32_t printPageDelay = mPrintSettings->GetPrintPageDelay();
+
+ nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
+ NS_ENSURE_TRUE(viewer, NS_ERROR_FAILURE);
+ nsCOMPtr<Document> doc = viewer->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ mPagePrintTimer =
+ new nsPagePrintTimer(this, mDocViewerPrint, doc, printPageDelay);
+
+ if (mRemotePrintJob) {
+ mRemotePrintJob->SetPagePrintTimer(mPagePrintTimer);
+ mRemotePrintJob->SetPrintJob(this);
+ }
+ }
+
+ return mPagePrintTimer->Start(aPO.get());
+}
+
+//---------------------------------------------------------------
+//-- PLEvent Notification
+//---------------------------------------------------------------
+class nsPrintCompletionEvent : public Runnable {
+ public:
+ explicit nsPrintCompletionEvent(nsIDocumentViewerPrint* docViewerPrint)
+ : mozilla::Runnable("nsPrintCompletionEvent"),
+ mDocViewerPrint(docViewerPrint) {
+ NS_ASSERTION(mDocViewerPrint, "mDocViewerPrint is null.");
+ }
+
+ NS_IMETHOD Run() override {
+ if (mDocViewerPrint) mDocViewerPrint->OnDonePrinting();
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint;
+};
+
+//-----------------------------------------------------------
+void nsPrintJob::FirePrintCompletionEvent() {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> event = new nsPrintCompletionEvent(mDocViewerPrint);
+ nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
+ NS_ENSURE_TRUE_VOID(viewer);
+ nsCOMPtr<Document> doc = viewer->GetDocument();
+ NS_ENSURE_TRUE_VOID(doc);
+ NS_ENSURE_SUCCESS_VOID(doc->Dispatch(event.forget()));
+}
+
+void nsPrintJob::DisconnectPagePrintTimer() {
+ if (mPagePrintTimer) {
+ mPagePrintTimer->Disconnect();
+ mPagePrintTimer = nullptr;
+ }
+}
+
+//---------------------------------------------------------------
+//---------------------------------------------------------------
+//-- Debug helper routines
+//---------------------------------------------------------------
+//---------------------------------------------------------------
+#if defined(XP_WIN) && defined(EXTENDED_DEBUG_PRINTING)
+# include <windows.h>
+# include <process.h>
+# include <direct.h>
+
+# define MY_FINDFIRST(a, b) FindFirstFile(a, b)
+# define MY_FINDNEXT(a, b) FindNextFile(a, b)
+# define ISDIR(a) (a.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+# define MY_FINDCLOSE(a) FindClose(a)
+# define MY_FILENAME(a) a.cFileName
+# define MY_FILESIZE(a) (a.nFileSizeHigh * MAXDWORD) + a.nFileSizeLow
+
+int RemoveFilesInDir(const char* aDir) {
+ WIN32_FIND_DATA data_ptr;
+ HANDLE find_handle;
+
+ char path[MAX_PATH];
+
+ strcpy(path, aDir);
+
+ // Append slash to the end of the directory names if not there
+ if (path[strlen(path) - 1] != '\\') strcat(path, "\\");
+
+ char findPath[MAX_PATH];
+ strcpy(findPath, path);
+ strcat(findPath, "*.*");
+
+ find_handle = MY_FINDFIRST(findPath, &data_ptr);
+
+ if (find_handle != INVALID_HANDLE_VALUE) {
+ do {
+ if (ISDIR(data_ptr) && (stricmp(MY_FILENAME(data_ptr), ".")) &&
+ (stricmp(MY_FILENAME(data_ptr), ".."))) {
+ // skip
+ } else if (!ISDIR(data_ptr)) {
+ if (!strncmp(MY_FILENAME(data_ptr), "print_dump", 10)) {
+ char fileName[MAX_PATH];
+ strcpy(fileName, aDir);
+ strcat(fileName, "\\");
+ strcat(fileName, MY_FILENAME(data_ptr));
+ printf("Removing %s\n", fileName);
+ remove(fileName);
+ }
+ }
+ } while (MY_FINDNEXT(find_handle, &data_ptr));
+ MY_FINDCLOSE(find_handle);
+ }
+ return TRUE;
+}
+#endif
+
+#ifdef EXTENDED_DEBUG_PRINTING
+
+/** ---------------------------------------------------
+ * Dumps Frames for Printing
+ */
+static void RootFrameList(nsPresContext* aPresContext, FILE* out,
+ const char* aPrefix) {
+ if (!aPresContext || !out) return;
+
+ if (PresShell* presShell = aPresContext->GetPresShell()) {
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (frame) {
+ frame->List(out, aPrefix);
+ }
+ }
+}
+
+/** ---------------------------------------------------
+ * Dumps Frames for Printing
+ */
+static void DumpFrames(FILE* out, nsPresContext* aPresContext,
+ gfxContext* aRendContext, nsIFrame* aFrame,
+ int32_t aLevel) {
+ NS_ASSERTION(out, "Pointer is null!");
+ NS_ASSERTION(aPresContext, "Pointer is null!");
+ NS_ASSERTION(aRendContext, "Pointer is null!");
+ NS_ASSERTION(aFrame, "Pointer is null!");
+
+ nsIFrame* child = aFrame->PrincipalChildList().FirstChild();
+ while (child != nullptr) {
+ for (int32_t i = 0; i < aLevel; i++) {
+ fprintf(out, " ");
+ }
+ nsAutoString tmp;
+ child->GetFrameName(tmp);
+ fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
+ bool isSelected;
+ if (child->IsVisibleForPainting()) {
+ fprintf(out, " %p %s", child, isSelected ? "VIS" : "UVS");
+ nsRect rect = child->GetRect();
+ fprintf(out, "[%d,%d,%d,%d] ", rect.x, rect.y, rect.width, rect.height);
+ fprintf(out, "v: %p ", (void*)child->GetView());
+ fprintf(out, "\n");
+ DumpFrames(out, aPresContext, aRendContext, child, aLevel + 1);
+ child = child->GetNextSibling();
+ }
+ }
+}
+
+/** ---------------------------------------------------
+ * Dumps the Views from the DocShell
+ */
+static void DumpViews(nsIDocShell* aDocShell, FILE* out) {
+ NS_ASSERTION(aDocShell, "Pointer is null!");
+ NS_ASSERTION(out, "Pointer is null!");
+
+ if (nullptr != aDocShell) {
+ fprintf(out, "docshell=%p \n", aDocShell);
+ if (PresShell* presShell = aDocShell->GetPresShell()) {
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsView* root = vm->GetRootView();
+ if (root) {
+ root->List(out);
+ }
+ }
+ } else {
+ fputs("null pres shell\n", out);
+ }
+
+ // dump the views of the sub documents
+ int32_t i, n;
+ BrowsingContext* bc = nsDocShell::Cast(aDocShell)->GetBrowsingContext();
+ for (auto& child : bc->Children()) {
+ if (auto childDS = child->GetDocShell()) {
+ DumpViews(childAsShell, out);
+ }
+ }
+ }
+}
+
+/** ---------------------------------------------------
+ * Dumps the Views and Frames
+ */
+void DumpLayoutData(const char* aTitleStr, const char* aURLStr,
+ nsPresContext* aPresContext, nsDeviceContext* aDC,
+ nsIFrame* aRootFrame, nsIDocShell* aDocShell,
+ FILE* aFD = nullptr) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ if (aPresContext == nullptr || aDC == nullptr) {
+ return;
+ }
+
+# ifdef NS_PRINT_PREVIEW
+ if (aPresContext->Type() == nsPresContext::eContext_PrintPreview) {
+ return;
+ }
+# endif
+
+ NS_ASSERTION(aRootFrame, "Pointer is null!");
+ NS_ASSERTION(aDocShell, "Pointer is null!");
+
+ // Dump all the frames and view to a a file
+ char filename[256];
+ sprintf(filename, "print_dump_layout_%d.txt", gDumpLOFileNameCnt++);
+ FILE* fd = aFD ? aFD : fopen(filename, "w");
+ if (fd) {
+ fprintf(fd, "Title: %s\n", aTitleStr ? aTitleStr : "");
+ fprintf(fd, "URL: %s\n", aURLStr ? aURLStr : "");
+ fprintf(fd, "--------------- Frames ----------------\n");
+ fprintf(fd, "--------------- Frames ----------------\n");
+ // RefPtr<gfxContext> renderingContext =
+ // aDC->CreateRenderingContext();
+ RootFrameList(aPresContext, fd, "");
+ // DumpFrames(fd, aPresContext, renderingContext, aRootFrame, 0);
+ fprintf(fd, "---------------------------------------\n\n");
+ fprintf(fd, "--------------- Views From Root Frame----------------\n");
+ nsView* v = aRootFrame->GetView();
+ if (v) {
+ v->List(fd);
+ } else {
+ printf("View is null!\n");
+ }
+ if (aDocShell) {
+ fprintf(fd, "--------------- All Views ----------------\n");
+ DumpViews(aDocShell, fd);
+ fprintf(fd, "---------------------------------------\n\n");
+ }
+ if (aFD == nullptr) {
+ fclose(fd);
+ }
+ }
+}
+
+//-------------------------------------------------------------
+static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ PR_PL(("Doc List\n***************************************************\n"));
+ PR_PL(
+ ("T P A H PO DocShell Seq Page Root Page# "
+ "Rect\n"));
+ for (nsPrintObject* po : aDocList) {
+ NS_ASSERTION(po, "nsPrintObject can't be null!");
+ nsIFrame* rootFrame = nullptr;
+ if (po->mPresShell) {
+ rootFrame = po->mPresShell->GetRootFrame();
+ while (rootFrame != nullptr) {
+ nsPageSequenceFrame* sqf = do_QueryFrame(rootFrame);
+ if (sqf) {
+ break;
+ }
+ rootFrame = rootFrame->PrincipalChildList().FirstChild();
+ }
+ }
+
+ PR_PL(("%s %d %d %p %p %p\n", ShortLoggableTypeOfPO(po),
+ po->PrintingIsEnabled(), po->mHasBeenPrinted, po,
+ po->mDocShell.get(), rootFrame));
+ }
+}
+
+//-------------------------------------------------------------
+static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel, FILE* aFD) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ NS_ASSERTION(aPO, "Pointer is null!");
+
+ FILE* fd = aFD ? aFD : stdout;
+ if (aLevel == 0) {
+ fprintf(fd,
+ "DocTree\n***************************************************\n");
+ fprintf(fd, "T PO DocShell Seq Page Page# Rect\n");
+ }
+ for (const auto& po : aPO->mKids) {
+ NS_ASSERTION(po, "nsPrintObject can't be null!");
+ for (int32_t k = 0; k < aLevel; k++) fprintf(fd, " ");
+ fprintf(fd, "%s %p %p\n", ShortLoggableTypeOfPO(po.get()), po.get(),
+ po->mDocShell.get());
+ }
+}
+
+//-------------------------------------------------------------
+static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
+ nsACString& aDocStr, nsACString& aURLStr) {
+ nsAutoString docTitleStr;
+ nsAutoString docURLStr;
+ GetDocumentTitleAndURL(aPO->mDocument, docTitleStr, docURLStr);
+ CopyUTF16toUTF8(docTitleStr, aDocStr);
+ CopyUTF16toUTF8(docURLStr, aURLStr);
+}
+
+//-------------------------------------------------------------
+static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
+ nsDeviceContext* aDC, int aLevel,
+ FILE* aFD) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ NS_ASSERTION(aPO, "Pointer is null!");
+ NS_ASSERTION(aDC, "Pointer is null!");
+
+ FILE* fd = nullptr;
+ if (aLevel == 0) {
+ fd = fopen("tree_layout.txt", "w");
+ fprintf(fd,
+ "DocTree\n***************************************************\n");
+ fprintf(fd, "***************************************************\n");
+ fprintf(fd, "T PO DocShell Seq Page Page# Rect\n");
+ } else {
+ fd = aFD;
+ }
+ if (fd) {
+ nsIFrame* rootFrame = nullptr;
+ if (aPO->mPresShell) {
+ rootFrame = aPO->mPresShell->GetRootFrame();
+ }
+ for (int32_t k = 0; k < aLevel; k++) fprintf(fd, " ");
+ fprintf(fd, "%s %p %p\n", ShortLoggableTypeOfPO(aPO.get()), aPO.get(),
+ aPO->mDocShell.get());
+ if (aPO->PrintingIsEnabled()) {
+ nsAutoCString docStr;
+ nsAutoCString urlStr;
+ GetDocTitleAndURL(aPO, docStr, urlStr);
+ DumpLayoutData(docStr.get(), urlStr.get(), aPO->mPresContext, aDC,
+ rootFrame, aPO->mDocShell, fd);
+ }
+ fprintf(fd, "<***************************************************>\n");
+
+ for (const auto& po : aPO->mKids) {
+ NS_ASSERTION(po, "nsPrintObject can't be null!");
+ DumpPrintObjectsTreeLayout(po, aDC, aLevel + 1, fd);
+ }
+ }
+ if (aLevel == 0 && fd) {
+ fclose(fd);
+ }
+}
+
+//-------------------------------------------------------------
+static void DumpPrintObjectsListStart(
+ const char* aStr, const nsTArray<nsPrintObject*>& aDocList) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ NS_ASSERTION(aStr, "Pointer is null!");
+
+ PR_PL(("%s\n", aStr));
+ DumpPrintObjectsList(aDocList);
+}
+
+#endif
+
+//---------------------------------------------------------------
+//---------------------------------------------------------------
+//-- End of debug helper routines
+//---------------------------------------------------------------