layout/printing/nsPrintJob.cpp
author Mike Hommey <mh+mozilla@glandium.org>
Thu, 31 May 2018 10:16:10 +0900
changeset 461453 07db1154d5b9411004d33cfe5d6a0e842cf15163
parent 459368 ce7798f62689e38772e62ca85dc0b4e3d4e35781
child 461738 b54db66223586b4e04f5cb926fccdacf8a176b91
permissions -rw-r--r--
Bug 1253064 - Prefer Clang to GCC in local developer builds. r=gps For Android targets, we just ignore plain clang, it's unlikely to work.

/* -*- 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 "nsIStringBundle.h"
#include "nsReadableUtils.h"
#include "nsCRT.h"

#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/ComputedStyleInlines.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/CustomEvent.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsIScriptGlobalObject.h"
#include "nsPIDOMWindow.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 "nsIServiceManager.h"
#include "nsGkAtoms.h"
#include "nsXPCOM.h"
#include "nsISupportsPrimitives.h"

static const char sPrintSettingsServiceContractID[] = "@mozilla.org/gfx/printsettings-service;1";

// Printing Events
#include "nsPrintPreviewListener.h"
#include "nsThreadUtils.h"

// Printing
#include "nsIWebBrowserPrint.h"

// Print Preview
#include "imgIContainer.h" // image animation mode constants
#include "nsIWebBrowserPrint.h" // needed for PrintPreview Navigation constants

// Print Progress
#include "nsIPrintProgress.h"
#include "nsIPrintProgressParams.h"
#include "nsIObserver.h"

// Print error dialog
#include "nsIPrompt.h"
#include "nsIWindowWatcher.h"

// Printing Prompts
#include "nsIPrintingPromptService.h"
static const char kPrintingPromptService[] = "@mozilla.org/embedcomp/printingprompt-service;1";

// Printing Timer
#include "nsPagePrintTimer.h"

// FrameSet
#include "nsIDocument.h"
#include "nsIDocumentInlines.h"

// Focus
#include "nsISelectionController.h"

// Misc
#include "gfxContext.h"
#include "mozilla/gfx/DrawEventRecorder.h"
#include "mozilla/layout/RemotePrintJobChild.h"
#include "nsISupportsUtils.h"
#include "nsIScriptContext.h"
#include "nsIDocumentObserver.h"
#include "nsISelectionListener.h"
#include "nsContentCID.h"
#include "nsLayoutCID.h"
#include "nsContentUtils.h"
#include "nsIPresShell.h"
#include "nsLayoutStylesheetCache.h"
#include "nsLayoutUtils.h"
#include "mozilla/Preferences.h"
#include "Text.h"

#include "nsWidgetsCID.h"
#include "nsIDeviceContextSpec.h"
#include "nsDeviceContextSpecProxy.h"
#include "nsViewManager.h"
#include "nsView.h"

#include "nsIPageSequenceFrame.h"
#include "nsIURL.h"
#include "nsIContentViewerEdit.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIWebBrowserChrome.h"
#include "nsIBaseWindow.h"
#include "nsILayoutHistoryState.h"
#include "nsFrameManager.h"
#include "mozilla/ReflowInput.h"
#include "nsIContentViewer.h"
#include "nsIDocumentViewerPrint.h"

#include "nsFocusManager.h"
#include "nsRange.h"
#include "nsCDefaultURIFixup.h"
#include "nsIURIFixup.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLFrameElement.h"
#include "nsContentList.h"
#include "nsIChannel.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"};
static const char * gPrintFrameTypeStr[]   = {"kNoFrames", "kFramesAsIs", "kSelectedFrame", "kEachFrameSep"};
static const char * gFrameHowToEnableStr[] = {"kFrameEnableNone", "kFrameEnableAll", "kFrameEnableAsIsAndEach"};
static const char * gPrintRangeStr[]       = {"kRangeAllPages", "kRangeSpecifiedPageRange", "kRangeSelection", "kRangeFocusFrame"};

// 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 DeleteUnselectedNodes(nsIDocument* aOrigDoc, nsIDocument* 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

class nsScriptSuppressor
{
public:
  explicit nsScriptSuppressor(nsPrintJob* aPrintJob)
    : mPrintJob(aPrintJob)
    , mSuppressed(false)
  {}

  ~nsScriptSuppressor() { Unsuppress(); }

  void Suppress()
  {
    if (mPrintJob) {
      mSuppressed = true;
      mPrintJob->TurnScriptingOn(false);
    }
  }

  void Unsuppress()
  {
    if (mPrintJob && mSuppressed) {
      mPrintJob->TurnScriptingOn(true);
    }
    mSuppressed = false;
  }

  void Disconnect() { mPrintJob = nullptr; }
protected:
  RefPtr<nsPrintJob>      mPrintJob;
  bool                    mSuppressed;
};

// -------------------------------------------------------
// 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<nsIDocument> doc = aParent->GetDocument();
  if (doc) {
    nsIContent *rootElement = doc->GetRootElement();
    if (rootElement) {
      isFrameSet = HasFramesetChild(rootElement);
    }
  }
  return isFrameSet;
}

static nsPrintObject*
FindPrintObjectByDOMWin(nsPrintObject* aPO,
                        nsPIDOMWindowOuter* aDOMWin)
{
  NS_ASSERTION(aPO, "Pointer is null!");

  // Often the CurFocused DOMWindow is passed in
  // andit is valid for it to be null, so short circut
  if (!aDOMWin) {
    return nullptr;
  }

  nsCOMPtr<nsIDocument> doc = aDOMWin->GetDoc();
  if (aPO->mDocument && aPO->mDocument->GetOriginalDocument() == doc) {
    return aPO;
  }

  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
    nsPrintObject* po = FindPrintObjectByDOMWin(kid.get(), aDOMWin);
    if (po) {
      return po;
    }
  }

  return nullptr;
}

static void
GetDocumentTitleAndURL(nsIDocument* aDoc,
                       nsAString& aTitle,
                       nsAString& aURLStr)
{
  NS_ASSERTION(aDoc, "Pointer is null!");

  aTitle.Truncate();
  aURLStr.Truncate();

  aDoc->GetTitle(aTitle);

  nsIURI* url = aDoc->GetDocumentURI();
  if (!url) return;

  nsCOMPtr<nsIURIFixup> urifixup(do_GetService(NS_URIFIXUP_CONTRACTID));
  if (!urifixup) return;

  nsCOMPtr<nsIURI> exposableURI;
  urifixup->CreateExposableURI(url, getter_AddRefs(exposableURI));

  if (!exposableURI) return;

  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(NS_LITERAL_CSTRING("UTF-8"),
                                 urlCStr, aURLStr);
}

static nsresult
GetSeqFrameAndCountPagesInternal(const UniquePtr<nsPrintObject>& aPO,
                                 nsIFrame*& aSeqFrame,
                                 int32_t& aCount)
{
  NS_ENSURE_ARG_POINTER(aPO);

  // 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 (!aPO->mPresShell) {
    MOZ_DIAGNOSTIC_ASSERT(false,
                          "GetSeqFrameAndCountPages needs a non-null pres shell");
    return NS_ERROR_FAILURE;
  }

  // Finds the SimplePageSequencer frame
  nsIPageSequenceFrame* seqFrame = aPO->mPresShell->GetPageSequenceFrame();
  aSeqFrame = do_QueryFrame(seqFrame);
  if (!aSeqFrame) {
    return NS_ERROR_FAILURE;
  }

  // count the total number of pages
  aCount = aSeqFrame->PrincipalChildList().GetLength();

  return NS_OK;
}

/**
 * Recursively sets the PO items to be printed "As Is"
 * from the given item down into the treei
 */
static void
SetPrintAsIs(nsPrintObject* aPO, bool aAsIs = true)
{
  NS_ASSERTION(aPO, "Pointer is null!");

  aPO->mPrintAsIs = aAsIs;
  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
    SetPrintAsIs(kid.get(), aAsIs);
  }
}

/**
 * This method is key to the entire print mechanism.
 *
 * This "maps" or figures out which sub-doc represents a
 * given Frame or IFrame in its parent sub-doc.
 *
 * So the Mcontent pointer in the child sub-doc points to the
 * content in the its parent document, that caused it to be printed.
 * This is used later to (after reflow) to find the absolute location
 * of the sub-doc on its parent's page frame so it can be
 * printed in the correct location.
 *
 * This method recursvely "walks" the content for a document finding
 * all the Frames and IFrames, then sets the "mFrameType" data member
 * which tells us what type of PO we have
 */
static void
MapContentForPO(const UniquePtr<nsPrintObject>& aPO,
                nsIContent* aContent)
{
  MOZ_ASSERT(aPO && aContent, "Null argument");

  nsIDocument* doc = aContent->GetComposedDoc();

  NS_ASSERTION(doc, "Content without a document from a document tree?");

  nsIDocument* subDoc = doc->GetSubDocumentFor(aContent);

  if (subDoc) {
    nsCOMPtr<nsIDocShell> docShell(subDoc->GetDocShell());

    if (docShell) {
      nsPrintObject * po = nullptr;
      for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
        if (kid->mDocument == subDoc) {
          po = kid.get();
          break;
        }
      }

      // XXX If a subdocument has no onscreen presentation, there will be no PO
      //     This is even if there should be a print presentation
      if (po) {
        // "frame" elements not in a frameset context should be treated
        // as iframes
        if (aContent->IsHTMLElement(nsGkAtoms::frame) && po->mParent->mFrameType == eFrameSet) {
          po->mFrameType = eFrame;
        } else {
          // Assume something iframe-like, i.e. iframe, object, or embed
          po->mFrameType = eIFrame;
          SetPrintAsIs(po, true);
          NS_ASSERTION(po->mParent, "The root must be a parent");
          po->mParent->mPrintAsIs = true;
        }
      }
    }
  }

  // walk children content
  for (nsIContent* child = aContent->GetFirstChild();
       child;
       child = child->GetNextSibling()) {
    MapContentForPO(aPO, child);
  }
}

/**
 * The walks the PO tree and for each document it walks the content
 * tree looking for any content that are sub-shells
 *
 * It then sets the mContent pointer in the "found" PO object back to the
 * the document that contained it.
 */
static void
MapContentToWebShells(const UniquePtr<nsPrintObject>& aRootPO,
                      const UniquePtr<nsPrintObject>& aPO)
{
  NS_ASSERTION(aRootPO, "Pointer is null!");
  NS_ASSERTION(aPO, "Pointer is null!");

  // Recursively walk the content from the root item
  // XXX Would be faster to enumerate the subdocuments, although right now
  //     nsIDocument doesn't expose quite what would be needed.
  nsCOMPtr<nsIContentViewer> viewer;
  aPO->mDocShell->GetContentViewer(getter_AddRefs(viewer));
  if (!viewer) return;

  nsCOMPtr<nsIDocument> doc = viewer->GetDocument();
  if (!doc) return;

  Element* rootElement = doc->GetRootElement();
  if (rootElement) {
    MapContentForPO(aPO, rootElement);
  } else {
    NS_WARNING("Null root content on (sub)document.");
  }

  // Continue recursively walking the chilren of this PO
  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
    MapContentToWebShells(aRootPO, kid);
  }

}


//-------------------------------------------------------

NS_IMPL_ISUPPORTS(nsPrintJob, nsIWebProgressListener,
                  nsISupportsWeakReference, nsIObserver)

//-------------------------------------------------------
nsPrintJob::~nsPrintJob()
{
  Destroy(); // for insurance
  DisconnectPagePrintTimer();
}

//-------------------------------------------------------
void
nsPrintJob::Destroy()
{
  if (mIsDestroying) {
    return;
  }
  mIsDestroying = true;

  mPrt = nullptr;

#ifdef NS_PRINT_PREVIEW
  mPrtPreview = nullptr;
  mOldPrtPreview = nullptr;
#endif
  mDocViewerPrint = nullptr;
}

//-------------------------------------------------------
void
nsPrintJob::DestroyPrintingData()
{
  mPrt = nullptr;
}

//---------------------------------------------------------------------------------
//-- Section: Methods needed by the DocViewer
//---------------------------------------------------------------------------------

//--------------------------------------------------------
nsresult
nsPrintJob::Initialize(nsIDocumentViewerPrint* aDocViewerPrint,
                       nsIDocShell*            aContainer,
                       nsIDocument*            aDocument,
                       float                   aScreenDPI)
{
  NS_ENSURE_ARG_POINTER(aDocViewerPrint);
  NS_ENSURE_ARG_POINTER(aContainer);
  NS_ENSURE_ARG_POINTER(aDocument);

  mDocViewerPrint = aDocViewerPrint;
  mContainer      = do_GetWeakReference(aContainer);
  mDocument       = aDocument;
  mScreenDPI      = aScreenDPI;

  return NS_OK;
}

//-------------------------------------------------------
bool
nsPrintJob::CheckBeforeDestroy()
{
  if (mPrt && mPrt->mPreparingForPrint) {
    mPrt->mDocWasToBeDestroyed = true;
    return true;
  }
  return false;
}

//-------------------------------------------------------
nsresult
nsPrintJob::Cancelled()
{
  if (mPrt && mPrt->mPrintSettings) {
    return mPrt->mPrintSettings->SetIsCancelled(true);
  }
  return NS_ERROR_FAILURE;
}

//-------------------------------------------------------
// Install our event listeners on the document to prevent
// some events from being processed while in PrintPreview
//
// No return code - if this fails, there isn't much we can do
void
nsPrintJob::InstallPrintPreviewListener()
{
  if (!mPrt->mPPEventListeners) {
    nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mContainer);
    if (!docShell) {
      return;
    }

    if (nsPIDOMWindowOuter* win = docShell->GetWindow()) {
      nsCOMPtr<EventTarget> target = win->GetFrameElementInternal();
      mPrt->mPPEventListeners = new nsPrintPreviewListener(target);
      mPrt->mPPEventListeners->AddListeners();
    }
  }
}

//-----------------------------------------------------------------
nsresult
nsPrintJob::GetSeqFrameAndCountPages(nsIFrame*& aSeqFrame, int32_t& aCount)
{
  MOZ_ASSERT(mPrtPreview);
  // Guarantee that mPrintPreview->mPrintObject won't be deleted during a call
  // of GetSeqFrameAndCountPagesInternal().
  RefPtr<nsPrintData> printDataForPrintPreview = mPrtPreview;
  return GetSeqFrameAndCountPagesInternal(
           printDataForPrintPreview->mPrintObject, aSeqFrame, aCount);
}
//---------------------------------------------------------------------------------
//-- 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,
                        nsIDocument* aDoc)
{
  // 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, aDoc);
  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,
                          nsIDocument*            aDoc)
{
  nsresult rv;

  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;

    if (mIsDoingPrintPreview) {
      mOldPrtPreview = Move(mPrtPreview);
    }
  } else {
    mProgressDialogIsShown = false;
  }

  // Grab the new instance with local variable to guarantee that it won't be
  // deleted during this method.
  mPrt = new nsPrintData(aIsPrintPreview ? nsPrintData::eIsPrintPreview :
                                           nsPrintData::eIsPrinting);
  RefPtr<nsPrintData> printData = mPrt;

  // if they don't pass in a PrintSettings, then get the Global PS
  printData->mPrintSettings = aPrintSettings;
  if (!printData->mPrintSettings) {
    rv = GetGlobalPrintSettings(getter_AddRefs(printData->mPrintSettings));
    NS_ENSURE_SUCCESS(rv, rv);
  }

  rv = CheckForPrinters(printData->mPrintSettings);
  NS_ENSURE_SUCCESS(rv, rv);

  printData->mPrintSettings->SetIsCancelled(false);
  printData->mPrintSettings->GetShrinkToFit(&printData->mShrinkToFit);

  if (aIsPrintPreview) {
    mIsCreatingPrintPreview = true;
    SetIsPrintPreview(true);
    nsCOMPtr<nsIContentViewer> viewer =
      do_QueryInterface(mDocViewerPrint);
    if (viewer) {
      viewer->SetTextZoom(1.0f);
      viewer->SetFullZoom(1.0f);
      viewer->SetMinFontSize(0);
    }
  }

  // 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 (!aIsPrintPreview) {
    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<mozilla::layout::RemotePrintJobChild> remotePrintJob;
      printSession->GetRemotePrintJob(getter_AddRefs(remotePrintJob));
      if (NS_SUCCEEDED(rv) && 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;
      }
    }

  }

  if (aWebProgressListener != nullptr) {
    printData->mPrintProgressListeners.AppendObject(aWebProgressListener);
  }

  // Get the currently focused window and cache it
  // because the Print Dialog will "steal" focus and later when you try
  // to get the currently focused windows it will be nullptr
  printData->mCurrentFocusWin = FindFocusedDOMWindow();

  // Check to see if there is a "regular" selection
  bool isSelection = IsThereARangeSelection(printData->mCurrentFocusWin);

  // Get the docshell for this documentviewer
  nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  {
    if (aIsPrintPreview) {
      nsCOMPtr<nsIContentViewer> viewer;
      webContainer->GetContentViewer(getter_AddRefs(viewer));
      if (viewer && viewer->GetDocument() && viewer->GetDocument()->IsShowing()) {
        viewer->GetDocument()->OnPageHide(false, nullptr);
      }
    }

    nsAutoScriptBlocker scriptBlocker;
    printData->mPrintObject = MakeUnique<nsPrintObject>();
    rv = printData->mPrintObject->Init(webContainer, aDoc, aIsPrintPreview);
    NS_ENSURE_SUCCESS(rv, rv);

    NS_ENSURE_TRUE(printData->mPrintDocList.AppendElement(
                                              printData->mPrintObject.get()),
                   NS_ERROR_OUT_OF_MEMORY);

    printData->mIsParentAFrameSet = IsParentAFrameSet(webContainer);
    printData->mPrintObject->mFrameType =
      printData->mIsParentAFrameSet ? eFrameSet : eDoc;

    // Build the "tree" of PrintObjects
    BuildDocTree(printData->mPrintObject->mDocShell, &printData->mPrintDocList,
                 printData->mPrintObject);
  }

  // The nsAutoScriptBlocker above will now have been destroyed, which may
  // cause our print/print-preview operation to finish. In this case, we
  // should immediately return an error code so that the root caller knows
  // it shouldn't continue to do anything with this instance.
  if (mIsDestroying || (aIsPrintPreview && !mIsCreatingPrintPreview)) {
    return NS_ERROR_FAILURE;
  }

  if (!aIsPrintPreview) {
    SetIsPrinting(true);
  }

  // XXX This isn't really correct...
  if (!printData->mPrintObject->mDocument ||
      !printData->mPrintObject->mDocument->GetRootElement())
    return NS_ERROR_GFX_PRINTER_STARTDOC;

  // Create the linkage from the sub-docs back to the content element
  // in the parent document
  MapContentToWebShells(printData->mPrintObject, printData->mPrintObject);

  printData->mIsIFrameSelected =
    IsThereAnIFrameSelected(webContainer, printData->mCurrentFocusWin,
                            printData->mIsParentAFrameSet);

  // Setup print options for UI
  if (printData->mIsParentAFrameSet) {
    if (printData->mCurrentFocusWin) {
      printData->mPrintSettings->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAll);
    } else {
      printData->mPrintSettings->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAsIsAndEach);
    }
  } else {
    printData->mPrintSettings->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableNone);
  }
  // Now determine how to set up the Frame print UI
  printData->mPrintSettings->SetPrintOptions(
                               nsIPrintSettings::kEnableSelectionRB,
                               isSelection || printData->mIsIFrameSelected);

  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);
  }

  nsScriptSuppressor scriptSuppressor(this);
  // 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 (!aIsPrintPreview || printingViaParent) {
    scriptSuppressor.Suppress();
    bool printSilently;
    printData->mPrintSettings->GetPrintSilent(&printSilently);

    // Check prefs for a default setting as to whether we should print silently
    printSilently =
      Preferences::GetBool("print.always_print_silent", printSilently);

    // 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 (!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 (!aIsPrintPreview) {
          domWin = mDocument->GetWindow();
          NS_ENSURE_TRUE(domWin, NS_ERROR_FAILURE);
        }

        // 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
        //
        nsCOMPtr<nsIWebBrowserPrint> wbp(do_QueryInterface(mDocViewerPrint));
        rv = printPromptService->ShowPrintDialog(domWin, wbp,
                                                 printData->mPrintSettings);
        //
        // 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 && !aIsPrintPreview) {
            // 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<mozilla::layout::RemotePrintJobChild> remotePrintJob;
              printSession->GetRemotePrintJob(getter_AddRefs(remotePrintJob));
              if (NS_SUCCEEDED(rv) && 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 {
      // 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);
  }

  rv = devspec->Init(nullptr, printData->mPrintSettings, aIsPrintPreview);
  NS_ENSURE_SUCCESS(rv, rv);

  printData->mPrintDC = new nsDeviceContext();
  rv = printData->mPrintDC->InitForPrinting(devspec);
  NS_ENSURE_SUCCESS(rv, rv);

  if (XRE_IsParentProcess() && !printData->mPrintDC->IsSyncPagePrinting()) {
    RefPtr<nsPrintJob> self(this);
    printData->mPrintDC->RegisterPageDoneCallback([self](nsresult aResult) { self->PageDone(aResult); });
  }

  if (aIsPrintPreview) {
    printData->mPrintSettings->SetPrintFrameType(nsIPrintSettings::kFramesAsIs);

    // override any UI that wants to PrintPreview any selection or page range
    // we want to view every page in PrintPreview each time
    printData->mPrintSettings->SetPrintRange(nsIPrintSettings::kRangeAllPages);
  } else {
    // Always check and set the print settings first and then fall back
    // onto the PrintService if there isn't a PrintSettings
    //
    // Posiible Usage values:
    //   nsIPrintSettings::kUseInternalDefault
    //   nsIPrintSettings::kUseSettingWhenPossible
    //
    // NOTE: The consts are the same for PrintSettings and PrintSettings
    int16_t printFrameTypeUsage = nsIPrintSettings::kUseSettingWhenPossible;
    printData->mPrintSettings->GetPrintFrameTypeUsage(&printFrameTypeUsage);

    // Ok, see if we are going to use our value and override the default
    if (printFrameTypeUsage == nsIPrintSettings::kUseSettingWhenPossible) {
      // Get the Print Options/Settings PrintFrameType to see what is preferred
      int16_t printFrameType = nsIPrintSettings::kEachFrameSep;
      printData->mPrintSettings->GetPrintFrameType(&printFrameType);

      // Don't let anybody do something stupid like try to set it to
      // kNoFrames when we are printing a FrameSet
      if (printFrameType == nsIPrintSettings::kNoFrames) {
        printData->mPrintFrameType = nsIPrintSettings::kEachFrameSep;
        printData->mPrintSettings->SetPrintFrameType(
                                     printData->mPrintFrameType);
      } else {
        // First find out from the PrinService what options are available
        // to us for Printing FrameSets
        int16_t howToEnableFrameUI;
        printData->mPrintSettings->GetHowToEnableFrameUI(&howToEnableFrameUI);
        if (howToEnableFrameUI != nsIPrintSettings::kFrameEnableNone) {
          switch (howToEnableFrameUI) {
          case nsIPrintSettings::kFrameEnableAll:
            printData->mPrintFrameType = printFrameType;
            break;

          case nsIPrintSettings::kFrameEnableAsIsAndEach:
            if (printFrameType != nsIPrintSettings::kSelectedFrame) {
              printData->mPrintFrameType = printFrameType;
            } else { // revert back to a good value
              printData->mPrintFrameType = nsIPrintSettings::kEachFrameSep;
            }
            break;
          } // switch
          printData->mPrintSettings->SetPrintFrameType(
                                       printData->mPrintFrameType);
        }
      }
    } else {
      printData->mPrintSettings->GetPrintFrameType(&printData->mPrintFrameType);
    }
  }

  if (printData->mPrintFrameType == nsIPrintSettings::kEachFrameSep) {
    CheckForChildFrameSets(printData->mPrintObject);
  }

  if (NS_FAILED(EnablePOsForPrinting())) {
    return NS_ERROR_FAILURE;
  }

  // Attach progressListener to catch network requests.
  nsCOMPtr<nsIWebProgress> webProgress =
    do_QueryInterface(printData->mPrintObject->mDocShell);
  webProgress->AddProgressListener(
    static_cast<nsIWebProgressListener*>(this),
    nsIWebProgress::NOTIFY_STATE_REQUEST);

  mLoadCounter = 0;
  mDidLoadDataForPrinting = false;

  if (aIsPrintPreview) {
    bool notifyOnInit = false;
    ShowPrintProgress(false, notifyOnInit);

    // Very important! Turn Off scripting
    TurnScriptingOn(false);

    if (!notifyOnInit) {
      InstallPrintPreviewListener();
      rv = InitPrintDocConstruction(false);
    } else {
      rv = NS_OK;
    }
  } else {
    bool doNotify;
    ShowPrintProgress(true, doNotify);
    if (!doNotify) {
      // Print listener setup...
      printData->OnStartPrinting();

      rv = InitPrintDocConstruction(false);
    }
  }

  // We will enable scripting later after printing has finished.
  scriptSuppressor.Disconnect();

  return NS_OK;
}

//---------------------------------------------------------------------------------
NS_IMETHODIMP
nsPrintJob::Print(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.
  nsIDocument* doc =
    mPrtPreview && mPrtPreview->mPrintObject ?
      mPrtPreview->mPrintObject->mDocument : mDocument;

  return CommonPrint(false, aPrintSettings, aWebProgressListener, doc);
}

NS_IMETHODIMP
nsPrintJob::PrintPreview(nsIPrintSettings* aPrintSettings,
                         mozIDOMWindowProxy* aChildDOMWin,
                         nsIWebProgressListener* aWebProgressListener)
{
  // Get the DocShell and see if it is busy
  // (We can't Print Preview this document if it is still busy)
  nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mContainer));
  NS_ENSURE_STATE(docShell);

  uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE;
  if (NS_FAILED(docShell->GetBusyFlags(&busyFlags)) ||
      busyFlags != nsIDocShell::BUSY_FLAGS_NONE) {
    CloseProgressDialog(aWebProgressListener);
    FirePrintingErrorEvent(NS_ERROR_GFX_PRINTER_DOC_IS_BUSY);
    return NS_ERROR_FAILURE;
  }

  auto* window = nsPIDOMWindowOuter::From(aChildDOMWin);
  NS_ENSURE_STATE(window);
  nsCOMPtr<nsIDocument> doc = window->GetDoc();
  NS_ENSURE_STATE(doc);

  // Document is not busy -- go ahead with the Print Preview
  return CommonPrint(true, aPrintSettings, aWebProgressListener, doc);
}

//----------------------------------------------------------------------------------
NS_IMETHODIMP
nsPrintJob::GetIsFramesetDocument(bool* aIsFramesetDocument)
{
  nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer));
  *aIsFramesetDocument = IsParentAFrameSet(webContainer);
  return NS_OK;
}

//----------------------------------------------------------------------------------
NS_IMETHODIMP
nsPrintJob::GetIsIFrameSelected(bool* aIsIFrameSelected)
{
  *aIsIFrameSelected = false;

  // Get the docshell for this documentviewer
  nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer));
  // Get the currently focused window
  nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow();
  if (currentFocusWin && webContainer) {
    // Get whether the doc contains a frameset
    // Also, check to see if the currently focus docshell
    // is a child of this docshell
    bool isParentFrameSet;
    *aIsIFrameSelected = IsThereAnIFrameSelected(webContainer, currentFocusWin, isParentFrameSet);
  }
  return NS_OK;
}

//----------------------------------------------------------------------------------
NS_IMETHODIMP
nsPrintJob::GetIsRangeSelection(bool* aIsRangeSelection)
{
  // Get the currently focused window
  nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow();
  *aIsRangeSelection = IsThereARangeSelection(currentFocusWin);
  return NS_OK;
}

//----------------------------------------------------------------------------------
NS_IMETHODIMP
nsPrintJob::GetIsFramesetFrameSelected(bool* aIsFramesetFrameSelected)
{
  // Get the currently focused window
  nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow();
  *aIsFramesetFrameSelected = currentFocusWin != nullptr;
  return NS_OK;
}

//----------------------------------------------------------------------------------
NS_IMETHODIMP
nsPrintJob::GetPrintPreviewNumPages(int32_t* aPrintPreviewNumPages)
{
  NS_ENSURE_ARG_POINTER(aPrintPreviewNumPages);

  nsIFrame* seqFrame  = nullptr;
  *aPrintPreviewNumPages = 0;

  // When calling this function, the FinishPrintPreview() function might not
  // been called as there are still some
  RefPtr<nsPrintData> printData = mPrtPreview ? mPrtPreview : mPrt;
  if (NS_WARN_IF(!printData)) {
    return NS_ERROR_FAILURE;
  }
  nsresult rv =
    GetSeqFrameAndCountPagesInternal(printData->mPrintObject, seqFrame,
                                     *aPrintPreviewNumPages);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

//----------------------------------------------------------------------------------
// Enumerate all the documents for their titles
NS_IMETHODIMP
nsPrintJob::EnumerateDocumentNames(uint32_t* aCount,
                                   char16_t*** aResult)
{
  NS_ENSURE_ARG(aCount);
  NS_ENSURE_ARG_POINTER(aResult);

  *aCount = 0;
  *aResult = nullptr;

  int32_t     numDocs = mPrt->mPrintDocList.Length();
  char16_t** array   = (char16_t**) moz_xmalloc(numDocs * sizeof(char16_t*));
  if (!array)
    return NS_ERROR_OUT_OF_MEMORY;

  for (int32_t i=0;i<numDocs;i++) {
    nsPrintObject* po = mPrt->mPrintDocList.ElementAt(i);
    NS_ASSERTION(po, "nsPrintObject can't be null!");
    nsAutoString docTitleStr;
    nsAutoString docURLStr;
    GetDocumentTitleAndURL(po->mDocument, docTitleStr, docURLStr);

    // Use the URL if the doc is empty
    if (docTitleStr.IsEmpty() && !docURLStr.IsEmpty()) {
      docTitleStr = docURLStr;
    }
    array[i] = ToNewUnicode(docTitleStr);
  }
  *aCount  = numDocs;
  *aResult = array;

  return NS_OK;

}

//----------------------------------------------------------------------------------
nsresult
nsPrintJob::GetGlobalPrintSettings(nsIPrintSettings** aGlobalPrintSettings)
{
  NS_ENSURE_ARG_POINTER(aGlobalPrintSettings);

  nsresult rv = NS_ERROR_FAILURE;
  nsCOMPtr<nsIPrintSettingsService> printSettingsService =
    do_GetService(sPrintSettingsServiceContractID, &rv);
  if (NS_SUCCEEDED(rv)) {
    rv = printSettingsService->GetGlobalPrintSettings(aGlobalPrintSettings);
  }
  return rv;
}

//----------------------------------------------------------------------------------
NS_IMETHODIMP
nsPrintJob::GetDoingPrint(bool* aDoingPrint)
{
  NS_ENSURE_ARG_POINTER(aDoingPrint);
  *aDoingPrint = mIsDoingPrinting;
  return NS_OK;
}

//----------------------------------------------------------------------------------
NS_IMETHODIMP
nsPrintJob::GetDoingPrintPreview(bool* aDoingPrintPreview)
{
  NS_ENSURE_ARG_POINTER(aDoingPrintPreview);
  *aDoingPrintPreview = mIsDoingPrintPreview;
  return NS_OK;
}

//----------------------------------------------------------------------------------
NS_IMETHODIMP
nsPrintJob::GetCurrentPrintSettings(nsIPrintSettings** aCurrentPrintSettings)
{
  NS_ENSURE_ARG_POINTER(aCurrentPrintSettings);

  if (mPrt) {
    *aCurrentPrintSettings = mPrt->mPrintSettings;

  } else if (mPrtPreview) {
    *aCurrentPrintSettings = mPrtPreview->mPrintSettings;

  } else {
    *aCurrentPrintSettings = nullptr;
  }
  NS_IF_ADDREF(*aCurrentPrintSettings);
  return NS_OK;
}

//-----------------------------------------------------------------
//-- Section: Pre-Reflow Methods
//-----------------------------------------------------------------

//---------------------------------------------------------------------
// This method checks to see if there is at least one printer defined
// and if so, it sets the first printer in the list as the default name
// in the PrintSettings which is then used for Printer Preview
nsresult
nsPrintJob::CheckForPrinters(nsIPrintSettings* aPrintSettings)
{
#if defined(XP_MACOSX) || defined(ANDROID)
  // Mac doesn't support retrieving a printer list.
  return NS_OK;
#else
#if defined(MOZ_X11)
  // On Linux, default printer name should be requested on the parent side.
  // Unless we are in the parent, we ignore this function
  if (!XRE_IsParentProcess()) {
    return NS_OK;
  }
#endif
  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 default.
  nsCOMPtr<nsIPrintSettingsService> printSettingsService =
    do_GetService(sPrintSettingsServiceContractID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = printSettingsService->GetDefaultPrinterName(printerName);
  if (NS_SUCCEEDED(rv) && !printerName.IsEmpty()) {
    rv = aPrintSettings->SetPrinterName(printerName);
  }
  return rv;
#endif
}

//----------------------------------------------------------------------
// Set up to use the "pluggable" Print Progress Dialog
void
nsPrintJob::ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify)
{
  // 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;

  // Assume we can't do progress and then see if we can
  bool showProgresssDialog = false;

  // if it is already being shown then don't bother to find out if it should be
  // so skip this and leave mShowProgressDialog set to FALSE
  if (!mProgressDialogIsShown) {
    showProgresssDialog = Preferences::GetBool("print.show_print_progress");
  }

  // 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;

  // 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) {
      nsPIDOMWindowOuter* domWin = mDocument->GetWindow();
      if (!domWin) return;

      nsCOMPtr<nsIDocShell> docShell = domWin->GetDocShell();
      if (!docShell) return;
      nsCOMPtr<nsIDocShellTreeOwner> owner;
      docShell->GetTreeOwner(getter_AddRefs(owner));
      nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(owner);
      if (!browserChrome) return;
      bool isModal = true;
      browserChrome->IsWindowModal(&isModal);
      if (isModal) {
        // Showing a print progress dialog when printing a modal window
        // isn't supported. See bug 301560.
        return;
      }

      nsCOMPtr<nsIWebProgressListener> printProgressListener;

      nsCOMPtr<nsIWebBrowserPrint> wbp(do_QueryInterface(mDocViewerPrint));
      nsresult rv =
        printPromptService->ShowProgress(
                              domWin, wbp, 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) {
          SetDocAndURLIntoProgress(printData->mPrintObject,
                                   printData->mPrintProgressParams);
        }
      }
    }
  }
}

//---------------------------------------------------------------------
bool
nsPrintJob::IsThereARangeSelection(nsPIDOMWindowOuter* aDOMWin)
{
  if (mDisallowSelectionPrint)
    return false;

  nsCOMPtr<nsIPresShell> presShell;
  if (aDOMWin) {
    presShell = aDOMWin->GetDocShell()->GetPresShell();
  }

  if (!presShell)
    return false;

  // check here to see if there is a range selection
  // so we know whether to turn on the "Selection" radio button
  Selection* selection = presShell->GetCurrentSelection(SelectionType::eNormal);
  if (!selection) {
    return false;
  }

  int32_t rangeCount = selection->RangeCount();
  if (!rangeCount) {
    return false;
  }

  if (rangeCount > 1) {
    return true;
  }

  // check to make sure it isn't an insertion selection
  return selection->GetRangeAt(0) && !selection->IsCollapsed();
}

//---------------------------------------------------------------------
// Recursively build a list of sub documents to be printed
// that mirrors the document tree
void
nsPrintJob::BuildDocTree(nsIDocShell*      aParentNode,
                         nsTArray<nsPrintObject*>* aDocList,
                         const UniquePtr<nsPrintObject>& aPO)
{
  NS_ASSERTION(aParentNode, "Pointer is null!");
  NS_ASSERTION(aDocList, "Pointer is null!");
  NS_ASSERTION(aPO, "Pointer is null!");

  int32_t childWebshellCount;
  aParentNode->GetChildCount(&childWebshellCount);
  if (childWebshellCount > 0) {
    for (int32_t i=0;i<childWebshellCount;i++) {
      nsCOMPtr<nsIDocShellTreeItem> child;
      aParentNode->GetChildAt(i, getter_AddRefs(child));
      nsCOMPtr<nsIDocShell> childAsShell(do_QueryInterface(child));

      nsCOMPtr<nsIContentViewer>  viewer;
      childAsShell->GetContentViewer(getter_AddRefs(viewer));
      if (viewer) {
        nsCOMPtr<nsIDocument> doc = do_GetInterface(childAsShell);
        auto po = MakeUnique<nsPrintObject>();
        po->mParent = aPO.get();
        nsresult rv = po->Init(childAsShell, doc, aPO->mPrintPreview);
        if (NS_FAILED(rv))
          NS_NOTREACHED("Init failed?");
        aPO->mKids.AppendElement(Move(po));
        aDocList->AppendElement(aPO->mKids.LastElement().get());
        BuildDocTree(childAsShell, aDocList, aPO->mKids.LastElement());
      }
    }
  }
}

//-------------------------------------------------------
// A Frame's sub-doc may contain content or a FrameSet
// When it contains a FrameSet the mFrameType for the PrintObject
// is always set to an eFrame. Which is fine when printing "AsIs"
// but is incorrect when when printing "Each Frame Separately".
// When printing "Each Frame Separately" the Frame really acts like
// a frameset.
//
// This method walks the PO tree and checks to see if the PrintObject is
// an eFrame and has children that are eFrames (meaning it's a Frame containing a FrameSet)
// If so, then the mFrameType need to be changed to eFrameSet
//
// Also note: We only want to call this we are printing "Each Frame Separately"
//            when printing "As Is" leave it as an eFrame
void
nsPrintJob::CheckForChildFrameSets(const UniquePtr<nsPrintObject>& aPO)
{
  NS_ASSERTION(aPO, "Pointer is null!");

  // Continue recursively walking the chilren of this PO
  bool hasChildFrames = false;
  for (const UniquePtr<nsPrintObject>& po : aPO->mKids) {
    if (po->mFrameType == eFrame) {
      hasChildFrames = true;
      CheckForChildFrameSets(po);
    }
  }

  if (hasChildFrames && aPO->mFrameType == eFrame) {
    aPO->mFrameType = eFrameSet;
  }
}

//---------------------------------------------------------------------
bool
nsPrintJob::IsThereAnIFrameSelected(nsIDocShell* aDocShell,
                                    nsPIDOMWindowOuter* aDOMWin,
                                    bool& aIsParentFrameSet)
{
  aIsParentFrameSet = IsParentAFrameSet(aDocShell);
  bool iFrameIsSelected = false;
  if (mPrt && mPrt->mPrintObject) {
    nsPrintObject* po = FindPrintObjectByDOMWin(mPrt->mPrintObject.get(), aDOMWin);
    iFrameIsSelected = po && po->mFrameType == eIFrame;
  } else {
    // First, check to see if we are a frameset
    if (!aIsParentFrameSet) {
      // Check to see if there is a currenlt focused frame
      // if so, it means the selected frame is either the main docshell
      // or an IFRAME
      if (aDOMWin) {
        // Get the main docshell's DOMWin to see if it matches
        // the frame that is selected
        nsPIDOMWindowOuter* domWin = aDocShell ? aDocShell->GetWindow() : nullptr;
        if (domWin != aDOMWin) {
          iFrameIsSelected = true; // we have a selected IFRAME
        }
      }
    }
  }

  return iFrameIsSelected;
}

//---------------------------------------------------------------------
// Recursively sets all the PO items to be printed
// from the given item down into the tree
void
nsPrintJob::SetPrintPO(nsPrintObject* aPO, bool aPrint)
{
  NS_ASSERTION(aPO, "Pointer is null!");

  // Set whether to print flag
  aPO->mDontPrint = !aPrint;

  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
    SetPrintPO(kid.get(), aPrint);
  }
}

//---------------------------------------------------------------------
// This will first use a Title and/or URL from the PrintSettings
// if one isn't set then it uses the one from the document
// then if not title is there we will make sure we send something back
// depending on the situation.
void
nsPrintJob::GetDisplayTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
                                  nsAString& aTitle,
                                  nsAString& aURLStr,
                                  eDocTitleDefault aDefType)
{
  NS_ASSERTION(aPO, "Pointer is null!");

  if (!mPrt)
    return;

  aTitle.Truncate();
  aURLStr.Truncate();

  // First check to see if the PrintSettings has defined an alternate title
  // and use that if it did
  if (mPrt->mPrintSettings) {
    mPrt->mPrintSettings->GetTitle(aTitle);
    mPrt->mPrintSettings->GetDocURL(aURLStr);
  }

  nsAutoString docTitle;
  nsAutoString docUrl;
  GetDocumentTitleAndURL(aPO->mDocument, docTitle, docUrl);

  if (aURLStr.IsEmpty() && !docUrl.IsEmpty()) {
    aURLStr = docUrl;
  }

  if (aTitle.IsEmpty()) {
    if (!docTitle.IsEmpty()) {
      aTitle = docTitle;
    } else {
      if (aDefType == eDocTitleDefURLDoc) {
        if (!aURLStr.IsEmpty()) {
          aTitle = aURLStr;
        } else if (!mPrt->mBrandName.IsEmpty()) {
          aTitle = mPrt->mBrandName;
        }
      }
    }
  }
}

//---------------------------------------------------------------------
nsresult
nsPrintJob::DocumentReadyForPrinting()
{
  if (mPrt->mPrintFrameType == nsIPrintSettings::kEachFrameSep) {
    // Guarantee that mPrt->mPrintObject won't be deleted during a call of
    // CheckForChildFrameSets().
    RefPtr<nsPrintData> printData = mPrt;
    CheckForChildFrameSets(printData->mPrintObject);
  }

  //
  // 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
    DonePrintingPages(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)
{
  nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
  if (NS_WARN_IF(!cv)) {
    return;
  }

  nsCOMPtr<nsIDocument> 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, NS_LITERAL_STRING("PrintingError"), false, false,
                         detail);
  event->SetTrusted(true);

  RefPtr<AsyncEventDispatcher> asyncDispatcher =
    new AsyncEventDispatcher(doc, event);
  asyncDispatcher->mOnlyChromeDispatch = true;
  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->mDontPrint || po->mInvisible) {
      continue;
    }

    // When the print object has been marked as "print the document" (i.e,
    // po->mDontPrint is false), 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(printData->mPrintDC->AppUnitsPerCSSInch()) /
                       float(printData->mPrintDC->AppUnitsPerDevPixel());
    po->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);

    po->mPresShell->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;
      }
    }

    po->mPresShell->FlushPendingNotifications(FlushType::Layout);

    // 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->mDontPrint is set to true.  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->IsPrintable()) ||
    (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, docTitleStr, docURLStr,
                        eDocTitleDefURLDoc);

  int32_t startPage = 1;
  int32_t endPage = printData->mNumPrintablePages;

  int16_t printRangeType = nsIPrintSettings::kRangeAllPages;
  printData->mPrintSettings->GetPrintRange(&printRangeType);
  if (printRangeType == nsIPrintSettings::kRangeSpecifiedPageRange) {
    printData->mPrintSettings->GetStartPageRange(&startPage);
    printData->mPrintSettings->GetEndPageRange(&endPage);
    if (endPage > printData->mNumPrintablePages) {
      endPage = printData->mNumPrintablePages;
    }
  }

  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
    nsIPageSequenceFrame* 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 DonePrintingPages 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()) {
      SetPrintPO(aPO.get(), false);
      aPO->mInvisible = true;
      return NS_OK;
    }
  }

  UpdateZoomRatio(aPO.get(), aSetPixelScale);

  nsresult rv;
  // Reflow the PO
  rv = ReflowPrintObject(aPO);
  NS_ENSURE_SUCCESS(rv, rv);

  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
    rv = ReflowDocList(kid, aSetPixelScale);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  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 (mIsDoingPrintPreview && !mIsDoingPrinting) {
    nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
    (new AsyncEventDispatcher(
       cv->GetDocument(), NS_LITERAL_STRING("printPreviewUpdate"), true, true)
    )->RunDOMEventWhenSafe();
  }
}

nsresult
nsPrintJob::InitPrintDocConstruction(bool aHandleError)
{
  nsresult rv;
  // 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;
  rv = ReflowDocList(printData->mPrintObject, DoSetPixelScale());
  NS_ENSURE_SUCCESS(rv, rv);

  FirePrintPreviewUpdateEvent();

  if (mLoadCounter == 0) {
    AfterNetworkPrint(aHandleError);
  }
  return rv;
}

nsresult
nsPrintJob::AfterNetworkPrint(bool aHandleError)
{
  // 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 (aHandleError && NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
                         "nsPrintJob::AfterNetworkPrint failed");
    CleanupOnFailure(rv, !mIsDoingPrinting);
  }

  return rv;
}

////////////////////////////////////////////////////////////////////////////////
// nsIWebProgressListener

NS_IMETHODIMP
nsPrintJob::OnStateChange(nsIWebProgress* aWebProgress,
                          nsIRequest* aRequest,
                          uint32_t aStateFlags,
                          nsresult aStatus)
{
  nsAutoCString name;
  aRequest->GetName(name);
  if (name.EqualsLiteral("about:document-onload-blocker")) {
    return NS_OK;
  }
  if (aStateFlags & STATE_START) {
    ++mLoadCounter;
  } else if (aStateFlags & STATE_STOP) {
    mDidLoadDataForPrinting = true;
    --mLoadCounter;

    // If all resources are loaded, then do a small timeout and if there
    // are still no new requests, then another reflow.
    if (mLoadCounter == 0) {
      AfterNetworkPrint(true);
    }
  }
  return NS_OK;
}


NS_IMETHODIMP
nsPrintJob::OnProgressChange(nsIWebProgress* aWebProgress,
                             nsIRequest* aRequest,
                             int32_t aCurSelfProgress,
                             int32_t aMaxSelfProgress,
                             int32_t aCurTotalProgress,
                             int32_t aMaxTotalProgress)
{
  NS_NOTREACHED("notification excluded in AddProgressListener(...)");
  return NS_OK;
}

NS_IMETHODIMP
nsPrintJob::OnLocationChange(nsIWebProgress* aWebProgress,
                             nsIRequest* aRequest,
                             nsIURI* aLocation,
                             uint32_t aFlags)
{
  NS_NOTREACHED("notification excluded in AddProgressListener(...)");
  return NS_OK;
}

NS_IMETHODIMP
nsPrintJob::OnStatusChange(nsIWebProgress* aWebProgress,
                           nsIRequest* aRequest,
                           nsresult aStatus,
                           const char16_t* aMessage)
{
  NS_NOTREACHED("notification excluded in AddProgressListener(...)");
  return NS_OK;
}

NS_IMETHODIMP
nsPrintJob::OnSecurityChange(nsIWebProgress* aWebProgress,
                             nsIRequest* aRequest,
                             uint32_t aState)
{
  NS_NOTREACHED("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) {
    float ratio;
    if (mPrt->mPrintFrameType == nsIPrintSettings::kFramesAsIs || mPrt->mPrintFrameType == nsIPrintSettings::kNoFrames) {
      ratio = mPrt->mShrinkRatio - 0.005f; // round down
    } else {
      ratio = aPO->mShrinkRatio - 0.005f; // round down
    }
    aPO->mZoomRatio = ratio;
  } else if (!mPrt->mShrinkToFit) {
    double scaling;
    mPrt->mPrintSettings->GetScaling(&scaling);
    aPO->mZoomRatio = float(scaling);
  }
}

nsresult
nsPrintJob::UpdateSelectionAndShrinkPrintObject(nsPrintObject* aPO,
                                                bool aDocumentIsTopLevel)
{
  nsCOMPtr<nsIPresShell> displayShell = 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 (displayShell) {
    selection = displayShell->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) {
      selectionPS->AddRange(*selection->GetRangeAt(inx), 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) {
    nsIPageSequenceFrame* pageSequence = aPO->mPresShell->GetPageSequenceFrame();
    NS_ENSURE_STATE(pageSequence);
    pageSequence->GetSTFPercent(aPO->mShrinkRatio);
    // 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, NS_LITERAL_STRING("text/"))) {
      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) {
    nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
    if (cv) {
      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->IsPrintable()) {
    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) {
      SetPrintPO(aPO, 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->IsPrintable()) {
    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;
  nsView* parentView =
    aPO->mParent && aPO->mParent->IsPrintable() ? nullptr : GetParentViewForRoot();
  aPO->mPresContext = parentView ?
      new nsPresContext(aPO->mDocument, type) :
      new nsRootPresContext(aPO->mDocument, type);
  NS_ENSURE_TRUE(aPO->mPresContext, NS_ERROR_OUT_OF_MEMORY);
  aPO->mPresContext->SetPrintSettings(printData->mPrintSettings);

  // set the presentation context to the value in the print settings
  bool printBGColors;
  printData->mPrintSettings->GetPrintBGColors(&printBGColors);
  aPO->mPresContext->SetBackgroundColorDraw(printBGColors);
  printData->mPrintSettings->GetPrintBGImages(&printBGColors);
  aPO->mPresContext->SetBackgroundImageDraw(printBGColors);

  // init it with the DC
  nsresult rv = aPO->mPresContext->Init(printData->mPrintDC);
  NS_ENSURE_SUCCESS(rv, rv);

  aPO->mViewManager = new nsViewManager();

  rv = aPO->mViewManager->Init(printData->mPrintDC);
  NS_ENSURE_SUCCESS(rv,rv);

  UniquePtr<ServoStyleSet> styleSet =
    mDocViewerPrint->CreateStyleSet(aPO->mDocument);

  if (aPO->mDocument->IsSVGDocument()) {
    // The SVG document only loads minimal-xul.css, so it doesn't apply other
    // styles. We should add ua.css for applying style which related to print.
    auto cache = nsLayoutStylesheetCache::Singleton();
    styleSet->PrependStyleSheet(SheetType::Agent, cache->UASheet());
  }

  aPO->mPresShell = aPO->mDocument->CreateShell(aPO->mPresContext,
                                                aPO->mViewManager,
                                                Move(styleSet));
  if (!aPO->mPresShell) {
    return NS_ERROR_FAILURE;
  }

  // If we're printing selection then remove the unselected nodes from our
  // cloned document.
  int16_t printRangeType = nsIPrintSettings::kRangeAllPages;
  printData->mPrintSettings->GetPrintRange(&printRangeType);
  if (printRangeType == nsIPrintSettings::kRangeSelection) {
    DeleteUnselectedNodes(aPO->mDocument->GetOriginalDocument(), aPO->mDocument);
  }

  // The pres shell now owns the style set object.

  bool doReturn = false;;
  bool documentIsTopLevel = false;
  nsSize adjSize;

  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));


  // This docshell stuff is weird; will go away when we stop having multiple
  // presentations per document
  aPO->mPresContext->SetContainer(aPO->mDocShell);

  aPO->mPresShell->BeginObservingDocument();

  aPO->mPresContext->SetPageSize(adjSize);
  aPO->mPresContext->SetVisibleArea(nsRect(0, 0, adjSize.width, adjSize.height));
  aPO->mPresContext->SetIsRootPaginatedDocument(documentIsTopLevel);
  aPO->mPresContext->SetPageScale(aPO->mZoomRatio);
  // Calculate scale factor from printer to screen
  float printDPI = float(printData->mPrintDC->AppUnitsPerCSSInch()) /
                     float(printData->mPrintDC->AppUnitsPerDevPixel());
  aPO->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);

  if (mIsCreatingPrintPreview && documentIsTopLevel) {
    mDocViewerPrint->SetPrintPreviewPresentation(aPO->mViewManager,
                                                 aPO->mPresContext,
                                                 aPO->mPresShell);
  }

  rv = aPO->mPresShell->Initialize();

  NS_ENSURE_SUCCESS(rv, rv);
  NS_ASSERTION(aPO->mPresShell, "Presshell should still be here");

  // Process the reflow event Initialize posted
  aPO->mPresShell->FlushPendingNotifications(FlushType::Layout);

  rv = UpdateSelectionAndShrinkPrintObject(aPO.get(), documentIsTopLevel);
  NS_ENSURE_SUCCESS(rv, rv);

#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
    // IsPrintable() returns false, ReflowPrintObject bails before setting
    // mPresContext)
    if (po->mPresContext && po->mPresContext->IsRootPaginatedDocument()) {
      nsIPageSequenceFrame* pageSequence = po->mPresShell->GetPageSequenceFrame();
      nsIFrame * seqFrame = do_QueryFrame(pageSequence);
      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->IsPrintable()) {
    aStatus = DoPrint(aPO);
    return true;
  }

  // If |aPO->mPrintAsIs| and |aPO->mHasBeenPrinted| are true,
  // the kids frames are already processed in |PrintPage|.
  if (!aPO->mInvisible && !(aPO->mPrintAsIs && aPO->mHasBeenPrinted)) {
    for (const UniquePtr<nsPrintObject>& po : aPO->mKids) {
      bool printed = PrintDocContent(po, aStatus);
      if (printed || NS_FAILED(aStatus)) {
        return true;
      }
    }
  }
  return false;
}

static nsINode*
GetCorrespondingNodeInDocument(const nsINode* aNode, nsIDocument* aDoc)
{
  MOZ_ASSERT(aNode);
  MOZ_ASSERT(aDoc);

  // Selections in anonymous subtrees aren't supported.
  if (aNode->IsInAnonymousSubtree()) {
    return nullptr;
  }

  nsTArray<int32_t> indexArray;
  const nsINode* child = aNode;
  while (const nsINode* parent = child->GetParentNode()) {
    int32_t index = parent->ComputeIndexOf(child);
    MOZ_ASSERT(index >= 0);
    indexArray.AppendElement(index);
    child = parent;
  }
  MOZ_ASSERT(child->IsDocument());

  nsINode* correspondingNode = aDoc;
  for (int32_t i = indexArray.Length() - 1; i >= 0; --i) {
    correspondingNode = correspondingNode->GetChildAt_Deprecated(indexArray[i]);
    NS_ENSURE_TRUE(correspondingNode, nullptr);
  }

  return correspondingNode;
}

static NS_NAMED_LITERAL_STRING(kEllipsis, u"\x2026");

static nsresult
DeleteUnselectedNodes(nsIDocument* aOrigDoc, nsIDocument* aDoc)
{
  nsIPresShell* origShell = aOrigDoc->GetShell();
  nsIPresShell* shell = aDoc->GetShell();
  NS_ENSURE_STATE(origShell && shell);

  RefPtr<Selection> origSelection =
    origShell->GetCurrentSelection(SelectionType::eNormal);
  RefPtr<Selection> selection =
    shell->GetCurrentSelection(SelectionType::eNormal);
  NS_ENSURE_STATE(origSelection && selection);

  nsINode* bodyNode = aDoc->GetBodyElement();
  nsINode* startNode = bodyNode;
  uint32_t startOffset = 0;
  uint32_t ellipsisOffset = 0;

  int32_t rangeCount = origSelection->RangeCount();
  for (int32_t i = 0; i < rangeCount; ++i) {
    nsRange* origRange = origSelection->GetRangeAt(i);

    // New end is start of original range.
    nsINode* endNode =
      GetCorrespondingNodeInDocument(origRange->GetStartContainer(), aDoc);

    // If we're no longer in the same text node reset the ellipsis offset.
    if (endNode != startNode) {
      ellipsisOffset = 0;
    }
    uint32_t endOffset = origRange->StartOffset() + ellipsisOffset;

    // Create the range that we want to remove. Note that if startNode or
    // endNode are null CreateRange will fail and we won't remove that section.
    RefPtr<nsRange> range;
    nsresult rv = nsRange::CreateRange(startNode, startOffset, endNode,
                                       endOffset, getter_AddRefs(range));

    if (NS_SUCCEEDED(rv) && !range->Collapsed()) {
      selection->AddRange(*range, IgnoreErrors());

      // Unless we've already added an ellipsis at the start, if we ended mid
      // text node then add ellipsis.
      Text* text = endNode->GetAsText();
      if (!ellipsisOffset && text && endOffset && endOffset < text->Length()) {
        text->InsertData(endOffset, kEllipsis, IgnoreErrors());
        ellipsisOffset += kEllipsis.Length();
      }
    }

    // Next new start is end of original range.
    startNode =
      GetCorrespondingNodeInDocument(origRange->GetEndContainer(), aDoc);

    // If we're no longer in the same text node reset the ellipsis offset.
    if (startNode != endNode) {
      ellipsisOffset = 0;
    }
    startOffset = origRange->EndOffset() + ellipsisOffset;

    // If the next node will start mid text node then add ellipsis.
    Text* text = startNode ? startNode->GetAsText() : nullptr;
    if (text && startOffset && startOffset < text->Length()) {
      text->InsertData(startOffset, kEllipsis, IgnoreErrors());
      startOffset += kEllipsis.Length();
      ellipsisOffset += kEllipsis.Length();
    }
  }

  // Add in the last range to the end of the body.
  RefPtr<nsRange> lastRange;
  nsresult rv = nsRange::CreateRange(startNode, startOffset, bodyNode,
                                     bodyNode->GetChildCount(),
                                     getter_AddRefs(lastRange));
  if (NS_SUCCEEDED(rv) && !lastRange->Collapsed()) {
    selection->AddRange(*lastRange, IgnoreErrors());
  }

  selection->DeleteFromDocument(IgnoreErrors());
  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()));

  nsIPresShell*   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) {
    SetDocAndURLIntoProgress(aPO, printData->mPrintProgressParams);
  }

  {
    // Ask the page sequence frame to print all the pages
    nsIPageSequenceFrame* pageSequence = poPresShell->GetPageSequenceFrame();
    NS_ASSERTION(nullptr != pageSequence, "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->IsPrintable()) {
      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, docTitleStr, docURLStr, eDocTitleDefBlank);

    nsIFrame * seqFrame = do_QueryFrame(pageSequence);
    if (!seqFrame) {
      SetIsPrinting(false);
      return NS_ERROR_FAILURE;
    }

    mPageSeqFrame = seqFrame;
    pageSequence->StartPrint(poPresContext, printData->mPrintSettings,
                             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::SetDocAndURLIntoProgress(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, docTitleStr, docURLStr, eDocTitleDefURLDoc);

  // 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("...");
    }
  }
}

static bool
DocHasPrintCallbackCanvas(nsIDocument* aDoc, void* aData)
{
  if (!aDoc) {
    return true;
  }
  Element* root = aDoc->GetRootElement();
  if (!root) {
    return true;
  }
  RefPtr<nsContentList> canvases = NS_GetContentList(root,
                                                       kNameSpaceID_XHTML,
                                                       NS_LITERAL_STRING("canvas"));
  uint32_t canvasCount = canvases->Length(true);
  for (uint32_t i = 0; i < canvasCount; ++i) {
    HTMLCanvasElement* canvas = HTMLCanvasElement::FromNodeOrNull(canvases->Item(i, false));
    if (canvas && canvas->GetMozPrintCallback()) {
      // This subdocument has a print callback. Set result and return false to
      // stop iteration.
      *static_cast<bool*>(aData) = true;
      return false;
    }
  }
  return true;
}

static bool
DocHasPrintCallbackCanvas(nsIDocument* aDoc)
{
  bool result = false;
  aDoc->EnumerateSubDocuments(&DocHasPrintCallbackCanvas, static_cast<void*>(&result));
  return result;
}

/**
 * Checks to see if the document this print engine is associated with has any
 * canvases that have a mozPrintCallback.
 * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement#Properties
 */
bool
nsPrintJob::HasPrintCallbackCanvas()
{
  if (!mDocument) {
    return false;
  }
  // First check this mDocument.
  bool result = false;
  DocHasPrintCallbackCanvas(mDocument, static_cast<void*>(&result));
  // Also check the sub documents.
  return result || DocHasPrintCallbackCanvas(mDocument);
}

//-------------------------------------------------------
bool
nsPrintJob::PrePrintPage()
{
  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 page.
  }

  // 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 page is ready to be printed.
  // If the page doesn't get printed at all, the |done| will be |true|.
  bool done = false;
  nsIPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
  nsresult rv = pageSeqFrame->PrePrintNextPage(mPagePrintTimer, &done);
  if (NS_FAILED(rv)) {
    // ??? ::PrintPage 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::PrintPage(nsPrintObject* aPO,
                      bool& aInRange)
{
  NS_ASSERTION(aPO,            "aPO is null!");
  NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
  NS_ASSERTION(mPrt,           "mPrt is null!");

  // Although these should NEVER be nullptr
  // This is added insurance, to make sure we don't crash in optimized builds
  if (!mPrt || !aPO || !mPageSeqFrame.IsAlive()) {
    FirePrintingErrorEvent(NS_ERROR_FAILURE);
    return true; // means we are done printing
  }

  // Guarantee that mPrt won't be deleted during a call of
  // nsPrintData::DoOnProgressChange() which runs some listeners,
  // which may clear (& might otherwise destroy).
  RefPtr<nsPrintData> printData = mPrt;

  PR_PL(("-----------------------------------\n"));
  PR_PL(("------ In DV::PrintPage 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;
  }

  int32_t pageNum, numPages, endPage;
  nsIPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
  pageSeqFrame->GetCurrentPageNum(&pageNum);
  pageSeqFrame->GetNumPages(&numPages);

  bool donePrinting;
  bool isDoingPrintRange;
  pageSeqFrame->IsDoingPrintRange(&isDoingPrintRange);
  if (isDoingPrintRange) {
    int32_t fromPage;
    int32_t toPage;
    pageSeqFrame->GetPrintRange(&fromPage, &toPage);

    if (fromPage > numPages) {
      return true;
    }
    if (toPage > numPages) {
      toPage = numPages;
    }

    PR_PL(("****** Printing Page %d printing from %d to page %d\n", pageNum, fromPage, toPage));

    donePrinting = pageNum >= toPage;
    aInRange = pageNum >= fromPage && pageNum <= toPage;
    endPage = (toPage - fromPage)+1;
  } else {
    PR_PL(("****** Printing Page %d of %d page(s)\n", pageNum, numPages));

    donePrinting = pageNum >= numPages;
    endPage = numPages;
    aInRange = true;
  }

  // XXX This is wrong, but the actual behavior in the presence of a print
  // range sucks.
  if (printData->mPrintFrameType == nsIPrintSettings::kEachFrameSep) {
    endPage = printData->mNumPrintablePages;
  }

  printData->DoOnProgressChange(++printData->mNumPagesPrinted,
                                endPage, 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 Page
  // 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->PrintNextPage();
  if (NS_FAILED(rv)) {
    if (rv != NS_ERROR_ABORT) {
      FirePrintingErrorEvent(rv);
      printData->mIsAborted = true;
    }
    return true;
  }

  pageSeqFrame->DoPageEnd();

  return donePrinting;
}

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;
  // Calling SetIsPrinting while in print preview confuses the document viewer
  // This is safe because we prevent exiting print preview while printing
  if (!mIsDoingPrintPreview && mDocViewerPrint) {
    mDocViewerPrint->SetIsPrinting(aIsPrinting);
  }
  if (mPrt && aIsPrinting) {
    mPrt->mPreparingForPrint = true;
  }
}

//---------------------------------------------------------------------
void
nsPrintJob::SetIsPrintPreview(bool aIsPrintPreview)
{
  mIsDoingPrintPreview = aIsPrintPreview;

  if (mDocViewerPrint) {
    mDocViewerPrint->SetIsPrintPreview(aIsPrintPreview);
  }
}

//---------------------------------------------------------------------
void
nsPrintJob::CleanupDocTitleArray(char16_t**& aArray, int32_t& aCount)
{
  for (int32_t i = aCount - 1; i >= 0; i--) {
    free(aArray[i]);
  }
  free(aArray);
  aArray = nullptr;
  aCount = 0;
}

/** ---------------------------------------------------
 *  Get the Focused Frame for a documentviewer
 */
already_AddRefed<nsPIDOMWindowOuter>
nsPrintJob::FindFocusedDOMWindow()
{
  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
  NS_ENSURE_TRUE(fm, nullptr);

  nsPIDOMWindowOuter* window = mDocument->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.forget();
  }

  return nullptr;
}

//---------------------------------------------------------------------
bool
nsPrintJob::IsWindowsInOurSubTree(nsPIDOMWindowOuter* window)
{
  bool found = false;

  // now check to make sure it is in "our" tree of docshells
  if (window) {
    nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();

    if (docShell) {
      // get this DocViewer docshell
      nsCOMPtr<nsIDocShell> thisDVDocShell(do_QueryReferent(mContainer));
      while (!found) {
        if (docShell) {
          if (docShell == thisDVDocShell) {
            found = true;
            break;
          }
        } else {
          break; // at top of tree
        }
        nsCOMPtr<nsIDocShellTreeItem> docShellItemParent;
        docShell->GetSameTypeParent(getter_AddRefs(docShellItemParent));
        docShell = do_QueryInterface(docShellItemParent);
      } // while
    }
  } // scriptobj

  return found;
}

//-------------------------------------------------------
bool
nsPrintJob::DonePrintingPages(nsPrintObject* aPO, nsresult aResult)
{
  //NS_ASSERTION(aPO, "Pointer is null!");
  PR_PL(("****** In DV::DonePrintingPages 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()) {
    nsIPageSequenceFrame* 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::DonePrintingPages PO: %p (%s) didPrint:%s (Not Done Printing)\n", aPO, gFrameTypesStr[aPO->mFrameType], PRT_YESNO(didPrint)));
      return false;
    }
  }

  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.
  }

  TurnScriptingOn(true);
  SetIsPrinting(false);

  // Release reference to mPagePrintTimer; the timer object destroys itself
  // after this returns true
  DisconnectPagePrintTimer();

  return true;
}

//-------------------------------------------------------
nsresult
nsPrintJob::EnablePOsForPrinting()
{
  // Guarantee that mPrt and the objects it owns won't be deleted.
  RefPtr<nsPrintData> printData = mPrt;

  // NOTE: All POs have been "turned off" for printing
  // this is where we decided which POs get printed.

  if (!printData->mPrintSettings) {
    return NS_ERROR_FAILURE;
  }

  printData->mPrintFrameType = nsIPrintSettings::kNoFrames;
  printData->mPrintSettings->GetPrintFrameType(&printData->mPrintFrameType);

  int16_t printHowEnable = nsIPrintSettings::kFrameEnableNone;
  printData->mPrintSettings->GetHowToEnableFrameUI(&printHowEnable);

  int16_t printRangeType = nsIPrintSettings::kRangeAllPages;
  printData->mPrintSettings->GetPrintRange(&printRangeType);

  PR_PL(("\n"));
  PR_PL(("********* nsPrintJob::EnablePOsForPrinting *********\n"));
  PR_PL(("PrintFrameType:     %s \n",
         gPrintFrameTypeStr[printData->mPrintFrameType]));
  PR_PL(("HowToEnableFrameUI: %s \n", gFrameHowToEnableStr[printHowEnable]));
  PR_PL(("PrintRange:         %s \n", gPrintRangeStr[printRangeType]));
  PR_PL(("----\n"));

  // ***** This is the ultimate override *****
  // if we are printing the selection (either an IFrame or selection range)
  // then set the mPrintFrameType as if it were the selected frame
  if (printRangeType == nsIPrintSettings::kRangeSelection) {
    printData->mPrintFrameType = nsIPrintSettings::kSelectedFrame;
    printHowEnable = nsIPrintSettings::kFrameEnableNone;
  }

  // This tells us that the "Frame" UI has turned off,
  // so therefore there are no FrameSets/Frames/IFrames to be printed
  //
  // This means there are not FrameSets,
  // but the document could contain an IFrame
  if (printHowEnable == nsIPrintSettings::kFrameEnableNone) {
    // Print all the pages or a sub range of pages
    if (printRangeType == nsIPrintSettings::kRangeAllPages ||
        printRangeType == nsIPrintSettings::kRangeSpecifiedPageRange) {
      SetPrintPO(printData->mPrintObject.get(), true);

      // Set the children so they are PrinAsIs
      // In this case, the children are probably IFrames
      if (printData->mPrintObject->mKids.Length() > 0) {
        for (const UniquePtr<nsPrintObject>& po :
               printData->mPrintObject->mKids) {
          NS_ASSERTION(po, "nsPrintObject can't be null!");
          SetPrintAsIs(po.get());
        }

        // ***** Another override *****
        printData->mPrintFrameType = nsIPrintSettings::kFramesAsIs;
      }
      PR_PL(("PrintFrameType:     %s \n",
             gPrintFrameTypeStr[printData->mPrintFrameType]));
      PR_PL(("HowToEnableFrameUI: %s \n",
             gFrameHowToEnableStr[printHowEnable]));
      PR_PL(("PrintRange:         %s \n", gPrintRangeStr[printRangeType]));
      return NS_OK;
    }

    // This means we are either printed a selected IFrame or
    // we are printing the current selection
    if (printRangeType == nsIPrintSettings::kRangeSelection) {
      // If the currentFocusDOMWin can'r be null if something is selected
      if (printData->mCurrentFocusWin) {
        // Find the selected IFrame
        nsPrintObject* po =
          FindPrintObjectByDOMWin(printData->mPrintObject.get(),
                                  printData->mCurrentFocusWin);
        if (po) {
          // Makes sure all of its children are be printed "AsIs"
          SetPrintAsIs(po);

          // Now, only enable this POs (the selected PO) and all of its children
          SetPrintPO(po, true);

          // check to see if we have a range selection,
          // as oppose to a insert selection
          // this means if the user just clicked on the IFrame then
          // there will not be a selection so we want the entire page to print
          //
          // XXX this is sort of a hack right here to make the page
          // not try to reposition itself when printing selection
          nsPIDOMWindowOuter* domWin =
            po->mDocument->GetOriginalDocument()->GetWindow();
          if (!IsThereARangeSelection(domWin)) {
            printRangeType = nsIPrintSettings::kRangeAllPages;
            printData->mPrintSettings->SetPrintRange(printRangeType);
          }
          PR_PL(("PrintFrameType:     %s \n",
                 gPrintFrameTypeStr[printData->mPrintFrameType]));
          PR_PL(("HowToEnableFrameUI: %s \n",
                 gFrameHowToEnableStr[printHowEnable]));
          PR_PL(("PrintRange:         %s \n",
                 gPrintRangeStr[printRangeType]));
          return NS_OK;
        }
      } else {
        for (uint32_t i = 0; i < printData->mPrintDocList.Length(); i++) {
          nsPrintObject* po = printData->mPrintDocList.ElementAt(i);
          NS_ASSERTION(po, "nsPrintObject can't be null!");
          nsCOMPtr<nsPIDOMWindowOuter> domWin = po->mDocShell->GetWindow();
          if (IsThereARangeSelection(domWin)) {
            printData->mCurrentFocusWin = domWin.forget();
            SetPrintPO(po, true);
            break;
          }
        }
        return NS_OK;
      }
    }
  }

  // check to see if there is a selection when a FrameSet is present
  if (printRangeType == nsIPrintSettings::kRangeSelection) {
    // If the currentFocusDOMWin can'r be null if something is selected
    if (printData->mCurrentFocusWin) {
      // Find the selected IFrame
      nsPrintObject* po =
        FindPrintObjectByDOMWin(printData->mPrintObject.get(),
                                printData->mCurrentFocusWin);
      if (po) {
        // Makes sure all of its children are be printed "AsIs"
        SetPrintAsIs(po);

        // Now, only enable this POs (the selected PO) and all of its children
        SetPrintPO(po, true);

        // check to see if we have a range selection,
        // as oppose to a insert selection
        // this means if the user just clicked on the IFrame then
        // there will not be a selection so we want the entire page to print
        //
        // XXX this is sort of a hack right here to make the page
        // not try to reposition itself when printing selection
        nsCOMPtr<nsPIDOMWindowOuter> domWin = po->mDocument->GetOriginalDocument()->GetWindow();
        if (!IsThereARangeSelection(domWin)) {
          printRangeType = nsIPrintSettings::kRangeAllPages;
          printData->mPrintSettings->SetPrintRange(printRangeType);
        }
        PR_PL(("PrintFrameType:     %s \n",
               gPrintFrameTypeStr[printData->mPrintFrameType]));
        PR_PL(("HowToEnableFrameUI: %s \n",
               gFrameHowToEnableStr[printHowEnable]));
        PR_PL(("PrintRange:         %s \n", gPrintRangeStr[printRangeType]));
        return NS_OK;
      }
    }
  }

  // If we are printing "AsIs" then sets all the POs to be printed as is
  if (printData->mPrintFrameType == nsIPrintSettings::kFramesAsIs) {
    SetPrintAsIs(printData->mPrintObject.get());
    SetPrintPO(printData->mPrintObject.get(), true);
    return NS_OK;
  }

  // If we are printing the selected Frame then
  // find that PO for that selected DOMWin and set it all of its
  // children to be printed
  if (printData->mPrintFrameType == nsIPrintSettings::kSelectedFrame) {
    if ((printData->mIsParentAFrameSet && printData->mCurrentFocusWin) ||
        printData->mIsIFrameSelected) {
      nsPrintObject* po =
        FindPrintObjectByDOMWin(printData->mPrintObject.get(),
                                printData->mCurrentFocusWin);
      if (po) {
        // NOTE: Calling this sets the "po" and
        // we don't want to do this for documents that have no children,
        // because then the "DoEndPage" gets called and it shouldn't
        if (po->mKids.Length() > 0) {
          // Makes sure that itself, and all of its children are printed "AsIs"
          SetPrintAsIs(po);
        }

        // Now, only enable this POs (the selected PO) and all of its children
        SetPrintPO(po, true);
      }
    }
    return NS_OK;
  }

  // If we are print each subdoc separately,
  // then don't print any of the FraneSet Docs
  if (printData->mPrintFrameType == nsIPrintSettings::kEachFrameSep) {
    SetPrintPO(printData->mPrintObject.get(), true);
    int32_t cnt = printData->mPrintDocList.Length();
    for (int32_t i=0;i<cnt;i++) {
      nsPrintObject* po = printData->mPrintDocList.ElementAt(i);
      NS_ASSERTION(po, "nsPrintObject can't be null!");
      if (po->mFrameType == eFrameSet) {
        po->mDontPrint = true;
      }
    }
  }

  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;
}

//-------------------------------------------------------
void
nsPrintJob::TurnScriptingOn(bool aDoTurnOn)
{
  if (mIsDoingPrinting && aDoTurnOn && mDocViewerPrint &&
      mDocViewerPrint->GetIsPrintPreview()) {
    // We don't want to turn scripting on if print preview is shown still after
    // printing.
    return;
  }

  // The following for loop uses nsPrintObject instances that are owned by
  // mPrt or mPrtPreview.  Therefore, this method needs to guarantee that
  // they won't be deleted in this method.
  RefPtr<nsPrintData> printData = mPrt ? mPrt : mPrtPreview;
  if (!printData) {
    return;
  }

  NS_ASSERTION(mDocument, "We MUST have a document.");
  // First, get the script global object from the document...

  for (uint32_t i = 0; i < printData->mPrintDocList.Length(); i++) {
    nsPrintObject* po = printData->mPrintDocList.ElementAt(i);
    MOZ_ASSERT(po);

    nsIDocument* doc = po->mDocument;
    if (!doc) {
      continue;
    }

    if (nsCOMPtr<nsPIDOMWindowInner> window = doc->GetInnerWindow()) {
      nsCOMPtr<nsIGlobalObject> go = window->AsGlobal();
      NS_WARNING_ASSERTION(go->GetGlobalJSObject(), "Can't get global");
      nsresult propThere = NS_PROPTABLE_PROP_NOT_THERE;
      doc->GetProperty(nsGkAtoms::scriptEnabledBeforePrintOrPreview,
                       &propThere);
      if (aDoTurnOn) {
        if (propThere != NS_PROPTABLE_PROP_NOT_THERE) {
          doc->DeleteProperty(nsGkAtoms::scriptEnabledBeforePrintOrPreview);
          if (go->GetGlobalJSObject()) {
            xpc::Scriptability::Get(go->GetGlobalJSObject()).Unblock();
          }
          window->Resume();
        }
      } else {
        // Have to be careful, because people call us over and over again with
        // aDoTurnOn == false.  So don't set the property if it's already
        // set, since in that case we'd set it to the wrong value.
        if (propThere == NS_PROPTABLE_PROP_NOT_THERE) {
          // Stash the current value of IsScriptEnabled on the document, so
          // that layout code running in print preview doesn't get confused.
          doc->SetProperty(nsGkAtoms::scriptEnabledBeforePrintOrPreview,
                           NS_INT32_TO_PTR(doc->IsScriptEnabled()));
          if (go && go->GetGlobalJSObject()) {
            xpc::Scriptability::Get(go->GetGlobalJSObject()).Block();
          }
          window->Suspend();
        }
      }
    }
  }
}

//-----------------------------------------------------------------
//-- 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) {
    /* we're already finished with print preview */
    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();
    // XXX mPrt may be nullptr here.  So, Shouldn't TurnScriptingOn() take
    //     nsPrintData as an argument?
    TurnScriptingOn(true);

    return rv;
  }

  // At this point we are done preparing everything
  // before it is to be created

  if (mIsDoingPrintPreview && mOldPrtPreview) {
    mOldPrtPreview = nullptr;
  }

  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 = 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<nsIDocument> doc = cv->GetDocument();
    NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);

    RefPtr<nsPagePrintTimer> timer =
      new nsPagePrintTimer(this, mDocViewerPrint, doc, printPageDelay);
    timer.forget(&mPagePrintTimer);

    nsCOMPtr<nsIPrintSession> printSession;
    nsresult rv = mPrt->mPrintSettings->GetPrintSession(getter_AddRefs(printSession));
    if (NS_SUCCEEDED(rv) && printSession) {
      RefPtr<mozilla::layout::RemotePrintJobChild> remotePrintJob;
      printSession->GetRemotePrintJob(getter_AddRefs(remotePrintJob));
      if (NS_SUCCEEDED(rv) && remotePrintJob) {
        remotePrintJob->SetPagePrintTimer(mPagePrintTimer);
        remotePrintJob->SetPrintJob(this);
      }
    }
  }

  return mPagePrintTimer->Start(aPO.get());
}

/*=============== nsIObserver Interface ======================*/
NS_IMETHODIMP
nsPrintJob::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
{
  // Only process a null topic which means the progress dialog is open.
  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<nsIDocument> 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();
    NS_RELEASE(mPagePrintTimer);
  }
}

//---------------------------------------------------------------
//---------------------------------------------------------------
//-- 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;

  nsIPresShell *shell = aPresContext->GetPresShell();
  if (shell) {
    nsIFrame* frame = shell->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);
    nsIPresShell* shell = aDocShell->GetPresShell();
    if (shell) {
      nsViewManager* vm = shell->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;
    aDocShell->GetChildCount(&n);
    for (i = 0; i < n; i++) {
      nsCOMPtr<nsIDocShellTreeItem> child;
      aDocShell->GetChildAt(i, getter_AddRefs(child));
      nsCOMPtr<nsIDocShell> childAsShell(do_QueryInterface(child));
      if (childAsShell) {
        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) {
        nsIPageSequenceFrame * sqf = do_QueryFrame(rootFrame);
        if (sqf) {
          break;
        }
        rootFrame = rootFrame->PrincipalChildList().FirstChild();
      }
    }

    PR_PL(("%s %d %d %d %p %p %p\n", types[po->mFrameType],
            po->IsPrintable(), po->mPrintAsIs, 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);
  aDocStr = NS_ConvertUTF16toUTF8(docTitleStr);
  aURLStr = NS_ConvertUTF16toUTF8(docURLStr);
}

//-------------------------------------------------------------
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->IsPrintable()) {
      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
//---------------------------------------------------------------