summaryrefslogtreecommitdiffstats
path: root/layout/printing/nsPrintJob.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /layout/printing/nsPrintJob.cpp
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.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/nsPrintJob.cpp')
-rw-r--r--layout/printing/nsPrintJob.cpp3059
1 files changed, 3059 insertions, 0 deletions
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
+//---------------------------------------------------------------