/* -*- 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 "nsDocShell.h" #include "nsIStringBundle.h" #include "nsReadableUtils.h" #include "nsCRT.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/ComputedStyleInlines.h" #include "mozilla/dom/BrowsingContext.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 // 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"; #include "nsThreadUtils.h" // Printing #include "nsIWebBrowserPrint.h" // Print Preview #include "imgIContainer.h" // image animation mode 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 "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.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 "nsLayoutStylesheetCache.h" #include "nsLayoutUtils.h" #include "mozilla/Preferences.h" #include "mozilla/PresShell.h" #include "Text.h" #include "nsWidgetsCID.h" #include "nsIDeviceContextSpec.h" #include "nsDeviceContextSpecProxy.h" #include "nsViewManager.h" #include "nsPageSequenceFrame.h" #include "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 "nsIURIFixup.h" #include "mozilla/Components.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLFrameElement.h" #include "nsContentList.h" #include "nsIChannel.h" #include "PrintPreviewUserEventSuppressor.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(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* gPrintRangeStr[] = { "kRangeAllPages", "kRangeSpecifiedPageRange", "kRangeSelection"}; // 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(Document* aOrigDoc, Document* aDoc); #ifdef EXTENDED_DEBUG_PRINTING // Forward Declarations static void DumpPrintObjectsListStart(const char* aStr, const nsTArray& aDocList); static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel = 0, FILE* aFD = nullptr); static void DumpPrintObjectsTreeLayout(const UniquePtr& 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 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 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 doc = aDOMWin->GetDoc(); if (aPO->mDocument && aPO->mDocument->GetOriginalDocument() == doc) { return aPO; } for (const UniquePtr& kid : aPO->mKids) { nsPrintObject* po = FindPrintObjectByDOMWin(kid.get(), aDOMWin); if (po) { return po; } } return nullptr; } static void GetDocumentTitleAndURL(Document* 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 urifixup(components::URIFixup::Service()); if (!urifixup) return; nsCOMPtr exposableURI; urifixup->CreateExposableURI(url, getter_AddRefs(exposableURI)); if (!exposableURI) return; nsAutoCString urlCStr; nsresult rv = exposableURI->GetSpec(urlCStr); if (NS_FAILED(rv)) return; nsCOMPtr 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& 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; } aSeqFrame = aPO->mPresShell->GetPageSequenceFrame(); 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& 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& aPO, nsIContent* aContent) { MOZ_ASSERT(aPO && aContent, "Null argument"); Document* doc = aContent->GetComposedDoc(); NS_ASSERTION(doc, "Content without a document from a document tree?"); Document* subDoc = doc->GetSubDocumentFor(aContent); if (subDoc) { nsCOMPtr docShell(subDoc->GetDocShell()); if (docShell) { nsPrintObject* po = nullptr; for (const UniquePtr& 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& aRootPO, const UniquePtr& 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 // Document doesn't expose quite what would be needed. nsCOMPtr viewer; aPO->mDocShell->GetContentViewer(getter_AddRefs(viewer)); if (!viewer) return; nsCOMPtr 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& kid : aPO->mKids) { MapContentToWebShells(aRootPO, kid); } } /** * The outparam aDocList returns a (depth first) flat list of all the * nsPrintObjects created. */ static void BuildNestedPrintObjects(BrowsingContext* aBrowsingContext, const UniquePtr& aPO, nsTArray* aDocList) { MOZ_ASSERT(aBrowsingContext, "Pointer is null!"); MOZ_ASSERT(aDocList, "Pointer is null!"); MOZ_ASSERT(aPO, "Pointer is null!"); for (auto& childBC : aBrowsingContext->GetChildren()) { // if we no longer have a nsFrameLoader for this BrowsingContext, it's // probably being torn down. nsCOMPtr flo = do_QueryInterface(childBC->GetEmbedderElement()); RefPtr frameLoader = flo ? flo->GetFrameLoader() : nullptr; if (!frameLoader) { continue; } // Finish performing the static clone for this BrowsingContext. nsresult rv = frameLoader->FinishStaticClone(); if (NS_WARN_IF(NS_FAILED(rv))) { continue; } auto window = childBC->GetDOMWindow(); if (!window) { // XXXfission - handle OOP-iframes continue; } auto childPO = MakeUnique(); rv = childPO->InitAsNestedObject(childBC->GetDocShell(), window->GetExtantDoc(), aPO.get()); if (NS_FAILED(rv)) { MOZ_ASSERT_UNREACHABLE("Init failed?"); } aPO->mKids.AppendElement(std::move(childPO)); aDocList->AppendElement(aPO->mKids.LastElement().get()); BuildNestedPrintObjects(childBC, aPO->mKids.LastElement(), aDocList); } } /** * On platforms that support it, sets the printer name stored in the * nsIPrintSettings to the default printer if a printer name is not already * set. * XXXjwatt: Why is this necessary? Can't the code that reads the printer * name later "just" use the default printer if a name isn't specified? Then * we wouldn't have this inconsistency between platforms and processes. */ static nsresult EnsureSettingsHasPrinterNameSet( nsIPrintSettings* aPrintSettings) { #if defined(XP_MACOSX) || defined(ANDROID) // Mac doesn't support retrieving a printer list. return NS_OK; #else # 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 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 } static bool DocHasPrintCallbackCanvas(Document* aDoc, void* aData) { if (!aDoc) { return true; } Element* root = aDoc->GetRootElement(); if (!root) { return true; } RefPtr 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(aData) = true; return false; } } return true; } static bool AnySubdocHasPrintCallbackCanvas(Document* aDoc) { bool result = false; aDoc->EnumerateSubDocuments(&DocHasPrintCallbackCanvas, static_cast(&result)); return result; } //------------------------------------------------------- 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* aDocShell, Document* aOriginalDoc, float aScreenDPI) { NS_ENSURE_ARG_POINTER(aDocViewerPrint); NS_ENSURE_ARG_POINTER(aDocShell); NS_ENSURE_ARG_POINTER(aOriginalDoc); mDocViewerPrint = aDocViewerPrint; mDocShell = do_GetWeakReference(aDocShell); mScreenDPI = aScreenDPI; // XXX We should not be storing this. The original document that the user // selected to print can be mutated while print preview is open. Anything // we need to know about the original document should be checked and stored // here instead. mOriginalDoc = aOriginalDoc; // Anything state that we need from aOriginalDoc must be fetched and stored // here, since the document that the user selected to print may mutate // across consecutive PrintPreview() calls. Element* root = aOriginalDoc->GetRootElement(); mDisallowSelectionPrint = root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint); nsCOMPtr owner; aDocShell->GetTreeOwner(getter_AddRefs(owner)); nsCOMPtr browserChrome = do_GetInterface(owner); if (browserChrome) { browserChrome->IsWindowModal(&mIsForModalWindow); } bool hasMozPrintCallback = false; DocHasPrintCallbackCanvas(aOriginalDoc, static_cast(&hasMozPrintCallback)); mHasMozPrintCallback = hasMozPrintCallback || AnySubdocHasPrintCallbackCanvas(aOriginalDoc); return NS_OK; } //------------------------------------------------------- nsresult nsPrintJob::Cancel() { 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::SuppressPrintPreviewUserEvents() { if (!mPrt->mPPEventSuppressor) { nsCOMPtr docShell = do_QueryReferent(mDocShell); if (!docShell) { return; } if (nsPIDOMWindowOuter* win = docShell->GetWindow()) { nsCOMPtr target = win->GetFrameElementInternal(); mPrt->mPPEventSuppressor = new PrintPreviewUserEventSuppressor(target); } } } //----------------------------------------------------------------- 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 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& aPO, nsACString& aDocStr, nsACString& aURLStr); static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel, FILE* aFD); static void DumpPrintObjectsList(const nsTArray& aDocList); static void RootFrameList(nsPresContext* aPresContext, FILE* out, const char* aPrefix); static void DumpViews(nsIDocShell* aDocShell, FILE* out); static void DumpLayoutData(const char* aTitleStr, const char* aURLStr, nsPresContext* aPresContext, nsDeviceContext* aDC, nsIFrame* aRootFrame, nsIDocShell* aDocShell, FILE* aFD); #endif //-------------------------------------------------------------------------------- nsresult nsPrintJob::CommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener, Document* aSourceDoc) { // Callers must hold a strong reference to |this| to ensure that we stay // alive for the duration of this method, because our main owning reference // (on nsDocumentViewer) might be cleared during this function (if we cause // script to run and it cancels the print operation). nsresult rv = DoCommonPrint(aIsPrintPreview, aPrintSettings, aWebProgressListener, aSourceDoc); if (NS_FAILED(rv)) { if (aIsPrintPreview) { mIsCreatingPrintPreview = false; SetIsPrintPreview(false); } else { SetIsPrinting(false); } if (mProgressDialogIsShown) CloseProgressDialog(aWebProgressListener); if (rv != NS_ERROR_ABORT && rv != NS_ERROR_OUT_OF_MEMORY) { FirePrintingErrorEvent(rv); } mPrt = nullptr; } return rv; } nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener, Document* aSourceDoc) { nsresult rv; if (aIsPrintPreview) { // The WebProgressListener can be QI'ed to nsIPrintingPromptService // then that means the progress dialog is already being shown. nsCOMPtr pps( do_QueryInterface(aWebProgressListener)); mProgressDialogIsShown = pps != nullptr; if (mIsDoingPrintPreview) { mOldPrtPreview = std::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 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 = EnsureSettingsHasPrinterNameSet(printData->mPrintSettings); NS_ENSURE_SUCCESS(rv, rv); printData->mPrintSettings->SetIsCancelled(false); printData->mPrintSettings->GetShrinkToFit(&printData->mShrinkToFit); if (aIsPrintPreview) { mIsCreatingPrintPreview = true; SetIsPrintPreview(true); nsCOMPtr viewer = do_QueryInterface(mDocViewerPrint); if (viewer) { viewer->SetTextZoom(1.0f); viewer->SetFullZoom(1.0f); } } // 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 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 remotePrintJob = printSession->GetRemotePrintJob(); if (remotePrintJob) { // If we have a RemotePrintJob add it to the print progress listeners, // so it can forward to the parent. printData->mPrintProgressListeners.AppendElement(remotePrintJob); remotePrintJobListening = true; } } } 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 docShell(do_QueryReferent(mDocShell, &rv)); NS_ENSURE_SUCCESS(rv, rv); { nsAutoScriptBlocker scriptBlocker; printData->mPrintObject = MakeUnique(); rv = printData->mPrintObject->InitAsRootObject(docShell, aSourceDoc, aIsPrintPreview); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE( printData->mPrintDocList.AppendElement(printData->mPrintObject.get()), NS_ERROR_OUT_OF_MEMORY); printData->mIsParentAFrameSet = IsParentAFrameSet(docShell); printData->mPrintObject->mFrameType = printData->mIsParentAFrameSet ? eFrameSet : eDoc; BuildNestedPrintObjects( printData->mPrintObject->mDocShell->GetBrowsingContext(), printData->mPrintObject, &printData->mPrintDocList); } // 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( docShell, printData->mCurrentFocusWin, printData->mIsParentAFrameSet); // 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 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 printPromptService( do_GetService(kPrintingPromptService)); if (printPromptService) { nsPIDOMWindowOuter* domWin = nullptr; // We leave domWin as nullptr to indicate a call for print preview. if (!aIsPrintPreview) { domWin = mOriginalDoc->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 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 remotePrintJob = printSession->GetRemotePrintJob(); if (remotePrintJob) { printData->mPrintProgressListeners.AppendElement( remotePrintJob); remotePrintJobListening = true; } } } } else if (rv == NS_ERROR_NOT_IMPLEMENTED) { // This means the Dialog service was there, // but they choose not to implement this dialog and // are looking for default behavior from the toolkit rv = NS_OK; } } else { // No dialog service available rv = NS_ERROR_NOT_IMPLEMENTED; } } else { // 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 self(this); printData->mPrintDC->RegisterPageDoneCallback( [self](nsresult aResult) { self->PageDone(aResult); }); } if (aIsPrintPreview) { // 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); } if (NS_FAILED(EnablePOsForPrinting())) { return NS_ERROR_FAILURE; } // Attach progressListener to catch network requests. nsCOMPtr webProgress = do_QueryInterface(printData->mPrintObject->mDocShell); webProgress->AddProgressListener(static_cast(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) { SuppressPrintPreviewUserEvents(); 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; } //--------------------------------------------------------------------------------- nsresult nsPrintJob::Print(Document* aSourceDoc, nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener) { // If we have a print preview document, use that instead of the original // mDocument. That way animated images etc. get printed using the same state // as in print preview. Document* doc = mPrtPreview && mPrtPreview->mPrintObject ? mPrtPreview->mPrintObject->mDocument.get() : aSourceDoc; return CommonPrint(false, aPrintSettings, aWebProgressListener, doc); } nsresult nsPrintJob::PrintPreview( Document* aSourceDoc, nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener) { // Get the DocShell and see if it is busy // (We can't Print Preview this document if it is still busy) nsCOMPtr docShell(do_QueryReferent(mDocShell)); NS_ENSURE_STATE(docShell); auto busyFlags = docShell->GetBusyFlags(); if (busyFlags != nsIDocShell::BUSY_FLAGS_NONE) { CloseProgressDialog(aWebProgressListener); FirePrintingErrorEvent(NS_ERROR_GFX_PRINTER_DOC_IS_BUSY); return NS_ERROR_FAILURE; } // Document is not busy -- go ahead with the Print Preview return CommonPrint(true, aPrintSettings, aWebProgressListener, aSourceDoc); } //---------------------------------------------------------------------------------- bool nsPrintJob::IsIFrameSelected() { // Get the docshell for this documentviewer nsCOMPtr docShell(do_QueryReferent(mDocShell)); // Get the currently focused window nsCOMPtr currentFocusWin = FindFocusedDOMWindow(); if (currentFocusWin && docShell) { // Get whether the doc contains a frameset // Also, check to see if the currently focus docshell // is a child of this docshell bool isParentFrameSet; return IsThereAnIFrameSelected(docShell, currentFocusWin, isParentFrameSet); } return false; } //---------------------------------------------------------------------------------- bool nsPrintJob::IsRangeSelection() { // Get the currently focused window nsCOMPtr currentFocusWin = FindFocusedDOMWindow(); return IsThereARangeSelection(currentFocusWin); } //---------------------------------------------------------------------------------- int32_t nsPrintJob::GetPrintPreviewNumPages() { // When calling this function, the FinishPrintPreview() function might not // been called as there are still some RefPtr printData = mPrtPreview ? mPrtPreview : mPrt; if (NS_WARN_IF(!printData)) { return 0; } nsIFrame* seqFrame = nullptr; int32_t numPages = 0; nsresult rv = GetSeqFrameAndCountPagesInternal(printData->mPrintObject, seqFrame, numPages); if (NS_WARN_IF(NS_FAILED(rv))) { return 0; } return numPages; } nsresult nsPrintJob::GetDocumentName(nsAString& aDocName) { nsAutoString docURLStr; GetDocumentTitleAndURL(mPrt->mPrintObject->mDocument, aDocName, docURLStr); if (aDocName.IsEmpty() && !docURLStr.IsEmpty()) { aDocName = docURLStr; } return NS_OK; } //---------------------------------------------------------------------------------- nsresult nsPrintJob::GetGlobalPrintSettings( nsIPrintSettings** aGlobalPrintSettings) { NS_ENSURE_ARG_POINTER(aGlobalPrintSettings); nsresult rv = NS_ERROR_FAILURE; nsCOMPtr printSettingsService = do_GetService(sPrintSettingsServiceContractID, &rv); if (NS_SUCCEEDED(rv)) { rv = printSettingsService->GetGlobalPrintSettings(aGlobalPrintSettings); } return rv; } //---------------------------------------------------------------------------------- already_AddRefed nsPrintJob::GetCurrentPrintSettings() { if (mPrt) { return do_AddRef(mPrt->mPrintSettings); } if (mPrtPreview) { return do_AddRef(mPrtPreview->mPrintSettings); } return nullptr; } //----------------------------------------------------------------- //-- Section: Pre-Reflow Methods //----------------------------------------------------------------- //---------------------------------------------------------------------- // Set up to use the "pluggable" Print Progress Dialog void nsPrintJob::ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify) { // 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 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 printPromptService( do_GetService(kPrintingPromptService)); if (printPromptService) { if (mIsForModalWindow) { // Showing a print progress dialog when printing a modal window // isn't supported. See bug 301560. return; } nsPIDOMWindowOuter* domWin = mOriginalDoc->GetWindow(); if (!domWin) return; nsCOMPtr printProgressListener; nsCOMPtr wbp(do_QueryInterface(mDocViewerPrint)); nsresult rv = printPromptService->ShowPrintProgressDialog( 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) { SetURLAndTitleOnProgressParams(printData->mPrintObject, printData->mPrintProgressParams); } } } } } //--------------------------------------------------------------------- bool nsPrintJob::IsThereARangeSelection(nsPIDOMWindowOuter* aDOMWin) { if (mDisallowSelectionPrint || !aDOMWin) { return false; } PresShell* 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(); } //--------------------------------------------------------------------- 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& 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& 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() { // 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(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 cv = do_QueryInterface(mDocViewerPrint); if (NS_WARN_IF(!cv)) { return; } nsCOMPtr doc = cv->GetDocument(); RefPtr event = NS_NewDOMCustomEvent(doc, nullptr, nullptr); MOZ_ASSERT(event); AutoJSAPI jsapi; if (!jsapi.Init(event->GetParentObject())) { return; } JSContext* cx = jsapi.cx(); JS::Rooted detail( cx, JS::NumberValue(static_cast(aPrintError))); event->InitCustomEvent(cx, NS_LITERAL_STRING("PrintingError"), false, false, detail); event->SetTrusted(true); RefPtr asyncDispatcher = new AsyncEventDispatcher(doc, event); asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes; asyncDispatcher->RunDOMEventWhenSafe(); // Inform any progress listeners of the Error. if (mPrt) { // Note that nsPrintData::DoOnStatusChange() will call some listeners. // So, mPrt can be cleared or recreated. RefPtr 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 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(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; } } RefPtr presShell = po->mPresShell; presShell->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 element and it's printing only content in a // element or all 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 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 nsPageSequenceFrame* seqFrame = printData->mPrintObject->mPresShell->GetPageSequenceFrame(); if (seqFrame) { seqFrame->StartPrint(printData->mPrintObject->mPresContext, printData->mPrintSettings, docTitleStr, docURLStr); } } PR_PL(("****************** Begin Document ************************\n")); if (NS_FAILED(rv)) { NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT, "Failed to begin document for printing"); return rv; } // This will print the docshell document // when it completes asynchronously in the 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& 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& 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 cv = do_QueryInterface(mDocViewerPrint); (new AsyncEventDispatcher(cv->GetDocument(), NS_LITERAL_STRING("printPreviewUpdate"), CanBubble::eYes, ChromeOnlyDispatch::eYes)) ->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 printData = mPrt; rv = ReflowDocList(printData->mPrintObject, DoSetPixelScale()); NS_ENSURE_SUCCESS(rv, rv); FirePrintPreviewUpdateEvent(); if (mLoadCounter == 0) { ResumePrintAfterResourcesLoaded(aHandleError); } return rv; } nsresult nsPrintJob::ResumePrintAfterResourcesLoaded(bool aCleanupOnError) { // 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 webProgress = do_QueryInterface(mPrt->mPrintObject->mDocShell); webProgress->RemoveProgressListener( static_cast(this)); nsresult rv; if (mIsDoingPrinting) { rv = DocumentReadyForPrinting(); } else { rv = FinishPrintPreview(); } /* cleaup on failure + notify user */ if (aCleanupOnError && NS_FAILED(rv)) { NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT, "nsPrintJob::ResumePrintAfterResourcesLoaded failed"); CleanupOnFailure(rv, !mIsDoingPrinting); } return rv; } //////////////////////////////////////////////////////////////////////////////// // nsIWebProgressListener 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) { ResumePrintAfterResourcesLoaded(/* aCleanupOnError */ true); } } return NS_OK; } NS_IMETHODIMP nsPrintJob::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP nsPrintJob::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsIURI* aLocation, uint32_t aFlags) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP nsPrintJob::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus, const char16_t* aMessage) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP nsPrintJob::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aState) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP nsPrintJob::OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aEvent) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } //------------------------------------------------------- void nsPrintJob::UpdateZoomRatio(nsPrintObject* aPO, bool aSetPixelScale) { // Here is where we set the shrinkage value into the DC // and this is what actually makes it shrink if (aSetPixelScale && aPO->mFrameType != eIFrame) { int16_t printRangeType = nsIPrintSettings::kRangeAllPages; mPrt->mPrintSettings->GetPrintRange(&printRangeType); float ratio; if (printRangeType != nsIPrintSettings::kRangeSelection) { 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) { PresShell* displayPresShell = aPO->mDocShell->GetPresShell(); // Transfer Selection Ranges to the new Print PresShell RefPtr selection, selectionPS; // It's okay if there is no display shell, just skip copying the selection if (displayPresShell) { selection = displayPresShell->GetCurrentSelection(SelectionType::eNormal); } selectionPS = aPO->mPresShell->GetCurrentSelection(SelectionType::eNormal); // Reset all existing selection ranges that might have been added by calling // this function before. if (selectionPS) { selectionPS->RemoveAllRanges(IgnoreErrors()); } if (selection && selectionPS) { int32_t cnt = selection->RangeCount(); int32_t inx; for (inx = 0; inx < cnt; ++inx) { selectionPS->AddRangeAndSelectFramesAndNotifyListeners( *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) { nsPageSequenceFrame* pageSeqFrame = aPO->mPresShell->GetPageSequenceFrame(); NS_ENSURE_STATE(pageSeqFrame); aPO->mShrinkRatio = pageSeqFrame->GetSTFPercent(); // Limit the shrink-to-fit scaling for some text-ish type of documents. nsAutoString contentType; aPO->mPresShell->GetDocument()->GetContentType(contentType); if (contentType.EqualsLiteral("application/xhtml+xml") || StringBeginsWith(contentType, 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 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& 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 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); aPO->mPresShell = aPO->mDocument->CreatePresShell(aPO->mPresContext, aPO->mViewManager); 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)); 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(AppUnitsPerCSSInch()) / float(printData->mPrintDC->AppUnitsPerDevPixel()); aPO->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI); if (mIsCreatingPrintPreview && documentIsTopLevel) { mDocViewerPrint->SetPrintPreviewPresentation( aPO->mViewManager, aPO->mPresContext, aPO->mPresShell.get()); } rv = aPO->mPresShell->Initialize(); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(aPO->mPresShell, "Presshell should still be here"); // Process the reflow event Initialize posted RefPtr presShell = aPO->mPresShell; presShell->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 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()) { nsPageSequenceFrame* seqFrame = po->mPresShell->GetPageSequenceFrame(); if (seqFrame) { aNumPages += seqFrame->PrincipalChildList().GetLength(); } } } } //----------------------------------------------------------------- //-- Done: Reflow Methods //----------------------------------------------------------------- //----------------------------------------------------------------- //-- Section: Printing Methods //----------------------------------------------------------------- //------------------------------------------------------- // Called for each DocShell that needs to be printed bool nsPrintJob::PrintDocContent(const UniquePtr& 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& po : aPO->mKids) { bool printed = PrintDocContent(po, aStatus); if (printed || NS_FAILED(aStatus)) { return true; } } } return false; } static nsINode* GetCorrespondingNodeInDocument(const nsINode* aNode, Document* aDoc) { MOZ_ASSERT(aNode); MOZ_ASSERT(aDoc); // Selections in anonymous subtrees aren't supported. if (aNode->IsInAnonymousSubtree()) { return nullptr; } nsTArray 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(Document* aOrigDoc, Document* aDoc) { PresShell* origPresShell = aOrigDoc->GetPresShell(); PresShell* presShell = aDoc->GetPresShell(); NS_ENSURE_STATE(origPresShell && presShell); RefPtr origSelection = origPresShell->GetCurrentSelection(SelectionType::eNormal); RefPtr selection = presShell->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 nsRange::Create() will fail and we won't remove // that section. RefPtr range = nsRange::Create(startNode, startOffset, endNode, endOffset, IgnoreErrors()); if (range && !range->Collapsed()) { selection->AddRangeAndSelectFramesAndNotifyListeners(*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 lastRange = nsRange::Create(startNode, startOffset, bodyNode, bodyNode->GetChildCount(), IgnoreErrors()); if (lastRange && !lastRange->Collapsed()) { selection->AddRangeAndSelectFramesAndNotifyListeners(*lastRange, IgnoreErrors()); } selection->DeleteFromDocument(IgnoreErrors()); return NS_OK; } //------------------------------------------------------- nsresult nsPrintJob::DoPrint(const UniquePtr& aPO) { PR_PL(("\n")); PR_PL(("**************************** %s ****************************\n", gFrameTypesStr[aPO->mFrameType])); PR_PL(("****** In DV::DoPrint PO: %p \n", aPO.get())); PresShell* poPresShell = aPO->mPresShell; nsPresContext* poPresContext = aPO->mPresContext; NS_ASSERTION(poPresContext, "PrintObject has not been reflowed"); NS_ASSERTION(poPresContext->Type() != nsPresContext::eContext_PrintPreview, "How did this context end up here?"); // Guarantee that mPrt and the objects it owns won't be deleted in this method // because it might be cleared if other modules called from here may fire // events, notifying observers and/or listeners. RefPtr printData = mPrt; if (printData->mPrintProgressParams) { SetURLAndTitleOnProgressParams(aPO, printData->mPrintProgressParams); } { // Ask the page sequence frame to print all the pages nsPageSequenceFrame* seqFrame = poPresShell->GetPageSequenceFrame(); MOZ_ASSERT(seqFrame, "no page sequence frame"); // We are done preparing for printing, so we can turn this off printData->mPreparingForPrint = false; #ifdef EXTENDED_DEBUG_PRINTING nsIFrame* rootFrame = poPresShell->GetRootFrame(); if (aPO->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); if (!seqFrame) { SetIsPrinting(false); return NS_ERROR_FAILURE; } mPageSeqFrame = seqFrame; seqFrame->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::SetURLAndTitleOnProgressParams( const UniquePtr& 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("..."); } } } //------------------------------------------------------- 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 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; nsPageSequenceFrame* 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 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; nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame()); pageNum = pageSeqFrame->GetCurrentPageNum(); numPages = pageSeqFrame->GetNumPages(); bool donePrinting; bool isDoingPrintRange = pageSeqFrame->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; } 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 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); } } /** --------------------------------------------------- * Get the Focused Frame for a documentviewer */ already_AddRefed nsPrintJob::FindFocusedDOMWindow() const { nsIFocusManager* fm = nsFocusManager::GetFocusManager(); NS_ENSURE_TRUE(fm, nullptr); nsPIDOMWindowOuter* window = mOriginalDoc->GetWindow(); NS_ENSURE_TRUE(window, nullptr); nsCOMPtr rootWindow = window->GetPrivateRoot(); NS_ENSURE_TRUE(rootWindow, nullptr); nsCOMPtr 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) const { if (window) { nsCOMPtr ourDocShell(do_QueryReferent(mDocShell)); if (ourDocShell) { BrowsingContext* ourBC = ourDocShell->GetBrowsingContext(); BrowsingContext* bc = window->GetBrowsingContext(); while (bc) { if (bc == ourBC) { return true; } bc = bc->GetParent(); } } } return false; } //------------------------------------------------------- bool nsPrintJob::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()) { nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame()); pageSeqFrame->ResetPrintCanvasList(); } // Guarantee that mPrt and mPrt->mPrintObject won't be deleted during a // call of PrintDocContent() and FirePrintCompletionEvent(). RefPtr 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 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; } int16_t printRangeType = nsIPrintSettings::kRangeAllPages; printData->mPrintSettings->GetPrintRange(&printRangeType); PR_PL(("\n")); PR_PL(("********* nsPrintJob::EnablePOsForPrinting *********\n")); PR_PL(("PrintRange: %s \n", gPrintRangeStr[printRangeType])); PR_PL(("----\n")); bool treatAsNonFrameset = !printData->mIsParentAFrameSet || printRangeType == nsIPrintSettings::kRangeSelection; if (treatAsNonFrameset && (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& po : printData->mPrintObject->mKids) { NS_ASSERTION(po, "nsPrintObject can't be null!"); SetPrintAsIs(po.get()); } } 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(("PrintRange: %s \n", gPrintRangeStr[printRangeType])); return NS_OK; } } else if (treatAsNonFrameset) { 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 domWin = po->mDocShell->GetWindow(); if (IsThereARangeSelection(domWin)) { printData->mCurrentFocusWin = domWin.forget(); SetPrintPO(po, true); break; } } return NS_OK; } } if (printRangeType != nsIPrintSettings::kRangeSelection) { SetPrintAsIs(printData->mPrintObject.get()); SetPrintPO(printData->mPrintObject.get(), true); return NS_OK; } 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; } //------------------------------------------------------- // 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 printData = mPrt ? mPrt : mPrtPreview; if (!printData) { return; } // 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); Document* doc = po->mDocument; if (!doc) { continue; } if (nsCOMPtr window = doc->GetInnerWindow()) { nsCOMPtr go = window->AsGlobal(); NS_WARNING_ASSERTION(go->HasJSGlobal(), "Window has no 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->HasJSGlobal()) { xpc::Scriptability::Get(go->GetGlobalJSObjectPreserveColor()) .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->HasJSGlobal()) { xpc::Scriptability::Get(go->GetGlobalJSObjectPreserveColor()) .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 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 = std::move(mPrt); #endif // NS_PRINT_PREVIEW return NS_OK; } //----------------------------------------------------------------- //-- Done: Finishing up or Cleaning up //----------------------------------------------------------------- /*=============== Timer Related Code ======================*/ nsresult nsPrintJob::StartPagePrintTimer(const UniquePtr& 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 cv = do_QueryInterface(mDocViewerPrint); NS_ENSURE_TRUE(cv, NS_ERROR_FAILURE); nsCOMPtr doc = cv->GetDocument(); NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); RefPtr timer = new nsPagePrintTimer(this, mDocViewerPrint, doc, printPageDelay); timer.forget(&mPagePrintTimer); nsCOMPtr printSession; nsresult rv = mPrt->mPrintSettings->GetPrintSession(getter_AddRefs(printSession)); if (NS_SUCCEEDED(rv) && printSession) { RefPtr remotePrintJob = printSession->GetRemotePrintJob(); if (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 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 mDocViewerPrint; }; //----------------------------------------------------------- void nsPrintJob::FirePrintCompletionEvent() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr event = new nsPrintCompletionEvent(mDocViewerPrint); nsCOMPtr cv = do_QueryInterface(mDocViewerPrint); NS_ENSURE_TRUE_VOID(cv); nsCOMPtr 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; if (PresShell* presShell = aPresContext->GetPresShell()) { nsIFrame* frame = presShell->GetRootFrame(); if (frame) { frame->List(out, aPrefix); } } } /** --------------------------------------------------- * Dumps Frames for Printing */ static void DumpFrames(FILE* out, nsPresContext* aPresContext, gfxContext* aRendContext, nsIFrame* aFrame, int32_t aLevel) { NS_ASSERTION(out, "Pointer is null!"); NS_ASSERTION(aPresContext, "Pointer is null!"); NS_ASSERTION(aRendContext, "Pointer is null!"); NS_ASSERTION(aFrame, "Pointer is null!"); nsIFrame* child = aFrame->PrincipalChildList().FirstChild(); while (child != nullptr) { for (int32_t i = 0; i < aLevel; i++) { fprintf(out, " "); } nsAutoString tmp; child->GetFrameName(tmp); fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out); bool isSelected; if (child->IsVisibleForPainting()) { fprintf(out, " %p %s", child, isSelected ? "VIS" : "UVS"); nsRect rect = child->GetRect(); fprintf(out, "[%d,%d,%d,%d] ", rect.x, rect.y, rect.width, rect.height); fprintf(out, "v: %p ", (void*)child->GetView()); fprintf(out, "\n"); DumpFrames(out, aPresContext, aRendContext, child, aLevel + 1); child = child->GetNextSibling(); } } } /** --------------------------------------------------- * Dumps the Views from the DocShell */ static void DumpViews(nsIDocShell* aDocShell, FILE* out) { NS_ASSERTION(aDocShell, "Pointer is null!"); NS_ASSERTION(out, "Pointer is null!"); if (nullptr != aDocShell) { fprintf(out, "docshell=%p \n", aDocShell); if (PresShell* presShell = aDocShell->GetPresShell()) { nsViewManager* vm = presShell->GetViewManager(); if (vm) { nsView* root = vm->GetRootView(); if (root) { root->List(out); } } } else { fputs("null pres shell\n", out); } // dump the views of the sub documents int32_t i, n; aDocShell->GetChildCount(&n); for (i = 0; i < n; i++) { nsCOMPtr child; aDocShell->GetChildAt(i, getter_AddRefs(child)); nsCOMPtr 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 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& aDocList) { if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) { return; } const char types[][3] = {"DC", "FR", "IF", "FS"}; PR_PL(("Doc List\n***************************************************\n")); PR_PL( ("T P A H PO DocShell Seq Page Root Page# " "Rect\n")); for (nsPrintObject* po : aDocList) { NS_ASSERTION(po, "nsPrintObject can't be null!"); nsIFrame* rootFrame = nullptr; if (po->mPresShell) { rootFrame = po->mPresShell->GetRootFrame(); while (rootFrame != nullptr) { nsPageSequenceFrame* sqf = do_QueryFrame(rootFrame); if (sqf) { break; } rootFrame = rootFrame->PrincipalChildList().FirstChild(); } } PR_PL(("%s %d %d %p %p %p\n", types[po->mFrameType], po->IsPrintable(), 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& 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& 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& 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 //---------------------------------------------------------------