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