Bug 1918529 - fix some subpixel misalignment issues with gfx.webrender.svg-filter...
[gecko.git] / layout / printing / nsPrintJob.cpp
blob8a1deeb62f7f49c141a6058348ca324fbac94579
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsPrintJob.h"
9 #include "nsDebug.h"
10 #include "nsDocShell.h"
11 #include "nsReadableUtils.h"
12 #include "nsQueryObject.h"
14 #include "mozilla/AsyncEventDispatcher.h"
15 #include "mozilla/ResultExtensions.h"
16 #include "mozilla/ComputedStyleInlines.h"
17 #include "mozilla/dom/BrowsingContext.h"
18 #include "mozilla/dom/PBrowser.h"
19 #include "mozilla/dom/Selection.h"
20 #include "mozilla/dom/ShadowRoot.h"
21 #include "mozilla/dom/CustomEvent.h"
22 #include "mozilla/dom/ContentChild.h"
23 #include "mozilla/dom/DocumentTimeline.h"
24 #include "mozilla/dom/HTMLCanvasElement.h"
25 #include "mozilla/dom/ScriptSettings.h"
26 #include "mozilla/IntegerRange.h"
27 #include "mozilla/PresShell.h"
28 #include "mozilla/PresShellInlines.h"
29 #include "mozilla/StaticPrefs_print.h"
30 #include "mozilla/glean/PrintingMetrics.h"
31 #include "mozilla/Try.h"
32 #include "nsIBrowserChild.h"
33 #include "nsIOService.h"
34 #include "nsIScriptGlobalObject.h"
35 #include "nsIStringBundle.h"
36 #include "nsPIDOMWindow.h"
37 #include "nsPrintData.h"
38 #include "nsPrintObject.h"
39 #include "nsIDocShell.h"
40 #include "nsIURI.h"
41 #include "nsITextToSubURI.h"
42 #include "nsError.h"
44 #include "nsView.h"
45 #include <algorithm>
47 // Print Options
48 #include "nsIPrintSettings.h"
49 #include "nsIPrintSettingsService.h"
50 #include "nsGkAtoms.h"
51 #include "nsXPCOM.h"
53 static const char sPrintSettingsServiceContractID[] =
54 "@mozilla.org/gfx/printsettings-service;1";
56 // Printing Timer
57 #include "nsPagePrintTimer.h"
59 // FrameSet
60 #include "mozilla/dom/Document.h"
61 #include "mozilla/dom/DocumentInlines.h"
63 // Misc
64 #include "gfxContext.h"
65 #include "mozilla/gfx/DrawEventRecorder.h"
66 #include "mozilla/layout/RemotePrintJobChild.h"
67 #include "nsISupportsUtils.h"
68 #include "nsIScriptContext.h"
69 #include "nsComponentManagerUtils.h"
70 #include "mozilla/Preferences.h"
71 #include "mozilla/PresShell.h"
72 #include "Text.h"
74 #include "nsIDeviceContextSpec.h"
75 #include "nsDeviceContextSpecProxy.h"
76 #include "nsViewManager.h"
78 #include "nsPageSequenceFrame.h"
79 #include "nsIInterfaceRequestor.h"
80 #include "nsIInterfaceRequestorUtils.h"
81 #include "nsIWebBrowserChrome.h"
82 #include "mozilla/ReflowInput.h"
83 #include "nsIDocumentViewer.h"
84 #include "nsIDocumentViewerPrint.h"
86 #include "nsFocusManager.h"
87 #include "nsRange.h"
88 #include "mozilla/Components.h"
89 #include "mozilla/dom/Element.h"
90 #include "mozilla/dom/HTMLFrameElement.h"
91 #include "mozilla/ServoStyleSet.h"
93 using namespace mozilla;
94 using namespace mozilla::dom;
96 //-----------------------------------------------------
97 // PR LOGGING
98 #include "mozilla/Logging.h"
100 #ifdef DEBUG
101 // PR_LOGGING is force to always be on (even in release builds)
102 // but we only want some of it on,
103 // #define EXTENDED_DEBUG_PRINTING
104 #endif
106 // this log level turns on the dumping of each document's layout info
107 #define DUMP_LAYOUT_LEVEL (static_cast<mozilla::LogLevel>(9))
109 #ifndef PR_PL
110 static mozilla::LazyLogModule gPrintingLog("printing");
112 # define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
113 #endif
115 #ifdef EXTENDED_DEBUG_PRINTING
116 static uint32_t gDumpFileNameCnt = 0;
117 static uint32_t gDumpLOFileNameCnt = 0;
118 #endif
120 #define PRT_YESNO(_p) ((_p) ? "YES" : "NO")
122 inline const char* LoggableTypeOfPO(const nsPrintObject* aPO) {
123 // TODO(dholbert): These strings used to represent actual enum values, but
124 // the enum type has been removed. We could replace them with more
125 // descriptive names, if anyone uses this logging and cares to do so.
126 return aPO->mParent ? "eIFrame" : "eDoc";
129 inline const char* ShortLoggableTypeOfPO(const nsPrintObject* aPO) {
130 return aPO->mParent ? "IF" : "DC";
133 // This processes the selection on aOrigDoc and creates an inverted selection on
134 // aDoc, which it then deletes. If the start or end of the inverted selection
135 // ranges occur in text nodes then an ellipsis is added.
136 static nsresult DeleteNonSelectedNodes(Document& aDoc);
138 #ifdef EXTENDED_DEBUG_PRINTING
139 // Forward Declarations
140 static void DumpPrintObjectsListStart(const char* aStr,
141 const nsTArray<nsPrintObject*>& aDocList);
142 static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel = 0,
143 FILE* aFD = nullptr);
144 static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
145 nsDeviceContext* aDC, int aLevel = 0,
146 FILE* aFD = nullptr);
148 # define DUMP_DOC_LIST(_title) \
149 DumpPrintObjectsListStart((_title), mPrintDocList);
150 # define DUMP_DOC_TREE DumpPrintObjectsTree(mPrintObject.get());
151 # define DUMP_DOC_TREELAYOUT \
152 DumpPrintObjectsTreeLayout(mPrintObject, mPrt->mPrintDC);
153 #else
154 # define DUMP_DOC_LIST(_title)
155 # define DUMP_DOC_TREE
156 # define DUMP_DOC_TREELAYOUT
157 #endif
159 // -------------------------------------------------------
160 // Helpers
161 // -------------------------------------------------------
164 * Build a tree of nsPrintObjects under aPO. It also appends a (depth first)
165 * flat list of all the nsPrintObjects created to mPrintDocList. If
166 * one of the nsPrintObject's document is the focused document, then the print
167 * object is set as mSelectionRoot.
168 * @param aParentPO The parent nsPrintObject to populate, must not be null.
170 void nsPrintJob::BuildNestedPrintObjects(
171 const UniquePtr<nsPrintObject>& aParentPO) {
172 MOZ_ASSERT(aParentPO);
174 // If aParentPO is for an iframe and its original document was the document
175 // that had focus then always set as the selection root.
176 if (aParentPO->mParent &&
177 aParentPO->mDocument->GetProperty(nsGkAtoms::printisfocuseddoc)) {
178 mSelectionRoot = aParentPO.get();
179 } else if (!mSelectionRoot && aParentPO->HasSelection()) {
180 // If there is no focused iframe but there is a selection in one or more
181 // frames then we want to set the root nsPrintObject as the focus root so
182 // that later EnablePrintingSelectionOnly can search for and enable all
183 // nsPrintObjects containing selections.
184 mSelectionRoot = mPrintObject.get();
187 for (auto& bc : aParentPO->mDocShell->GetBrowsingContext()->Children()) {
188 nsCOMPtr<nsIDocShell> docShell = bc->GetDocShell();
189 if (!docShell) {
190 if (auto* cc = dom::ContentChild::GetSingleton()) {
191 nsCOMPtr<nsIPrintSettingsService> printSettingsService =
192 do_GetService(sPrintSettingsServiceContractID);
193 embedding::PrintData printData;
194 printSettingsService->SerializeToPrintData(mPrintSettings, &printData);
195 Unused << cc->SendUpdateRemotePrintSettings(bc, printData);
197 continue;
200 RefPtr<Document> doc = docShell->GetDocument();
201 MOZ_DIAGNOSTIC_ASSERT(doc);
202 // We might find non-static documents here if the fission remoteness change
203 // hasn't happened / finished yet. In that case, just skip them, the same
204 // way we do for remote frames above.
205 MOZ_DIAGNOSTIC_ASSERT(doc->IsStaticDocument() || doc->IsInitialDocument());
206 if (!doc || !doc->IsStaticDocument()) {
207 continue;
210 // Note: docShell and doc are known-non-null at this point; they've been
211 // null-checked above (with null leading to 'continue' statements).
212 auto childPO = MakeUnique<nsPrintObject>(*docShell, *doc, aParentPO.get());
214 mPrintDocList.AppendElement(childPO.get());
215 BuildNestedPrintObjects(childPO);
216 aParentPO->mKids.AppendElement(std::move(childPO));
220 static nsresult GetDefaultPrintSettings(nsIPrintSettings** aSettings) {
221 *aSettings = nullptr;
223 nsresult rv = NS_ERROR_FAILURE;
224 nsCOMPtr<nsIPrintSettingsService> printSettingsService =
225 do_GetService(sPrintSettingsServiceContractID, &rv);
226 NS_ENSURE_SUCCESS(rv, rv);
228 return printSettingsService->GetDefaultPrintSettingsForPrinting(aSettings);
231 //-------------------------------------------------------
233 NS_IMPL_ISUPPORTS(nsPrintJob, nsIWebProgressListener, nsISupportsWeakReference)
235 //-------------------------------------------------------
236 nsPrintJob::~nsPrintJob() {
237 Destroy(); // for insurance
238 DisconnectPagePrintTimer();
241 bool nsPrintJob::CheckBeforeDestroy() const { return mPreparingForPrint; }
243 //-------------------------------------------------------
244 void nsPrintJob::Destroy() {
245 if (mIsDestroying) {
246 return;
248 mIsDestroying = true;
250 DestroyPrintingData();
252 mDocViewerPrint = nullptr;
255 //-------------------------------------------------------
256 void nsPrintJob::DestroyPrintingData() {
257 mPrintObject = nullptr;
258 mPrt = nullptr;
261 nsPrintJob::nsPrintJob(nsIDocumentViewerPrint& aDocViewerPrint,
262 nsIDocShell& aDocShell, Document& aOriginalDoc,
263 float aScreenDPI)
264 : mDocViewerPrint(&aDocViewerPrint),
265 mDocShell(do_GetWeakReference(&aDocShell)),
266 mScreenDPI(aScreenDPI) {
267 // Any state that we need from aOriginalDoc must be fetched and stored
268 // here, since the document that the user selected to print may mutate
269 // across consecutive PrintPreview() calls.
271 Element* root = aOriginalDoc.GetRootElement();
272 mDisallowSelectionPrint =
273 root && root->HasAttr(nsGkAtoms::mozdisallowselectionprint);
276 //-----------------------------------------------------------------
277 std::tuple<nsPageSequenceFrame*, int32_t>
278 nsPrintJob::GetSeqFrameAndCountSheets() const {
279 if (NS_WARN_IF(!mPrt)) {
280 return {nullptr, 0};
283 const nsPrintObject* po = mPrintObject.get();
284 if (NS_WARN_IF(!po)) {
285 return {nullptr, 0};
288 // This is sometimes incorrectly called before the pres shell has been created
289 // (bug 1141756). MOZ_DIAGNOSTIC_ASSERT so we'll still see the crash in
290 // Nightly/Aurora in case the other patch fixes this.
291 if (!po->mPresShell) {
292 MOZ_DIAGNOSTIC_ASSERT(
293 false, "GetSeqFrameAndCountSheets needs a non-null pres shell");
294 return {nullptr, 0};
297 nsPageSequenceFrame* seqFrame = po->mPresShell->GetPageSequenceFrame();
298 if (!seqFrame) {
299 return {nullptr, 0};
302 // count the total number of sheets
303 return {seqFrame, seqFrame->PrincipalChildList().GetLength()};
306 // Foward decl for Debug Helper Functions
307 #ifdef EXTENDED_DEBUG_PRINTING
308 # ifdef XP_WIN
309 static int RemoveFilesInDir(const char* aDir);
310 # endif
311 static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
312 nsACString& aDocStr, nsACString& aURLStr);
313 static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel, FILE* aFD);
314 static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList);
315 static void RootFrameList(nsPresContext* aPresContext, FILE* out,
316 const char* aPrefix);
317 static void DumpViews(nsIDocShell* aDocShell, FILE* out);
318 static void DumpLayoutData(const char* aTitleStr, const char* aURLStr,
319 nsPresContext* aPresContext, nsDeviceContext* aDC,
320 nsIFrame* aRootFrame, nsIDocShell* aDocShell,
321 FILE* aFD);
322 #endif
324 //--------------------------------------------------------------------------------
326 nsresult nsPrintJob::CommonPrint(bool aIsPrintPreview,
327 nsIPrintSettings* aPrintSettings,
328 nsIWebProgressListener* aWebProgressListener,
329 Document& aSourceDoc) {
330 // Callers must hold a strong reference to |this| to ensure that we stay
331 // alive for the duration of this method, because our main owning reference
332 // (on nsDocumentViewer) might be cleared during this function (if we cause
333 // script to run and it cancels the print operation).
335 nsresult rv = DoCommonPrint(aIsPrintPreview, aPrintSettings,
336 aWebProgressListener, aSourceDoc);
337 if (NS_FAILED(rv)) {
338 if (aIsPrintPreview) {
339 mIsCreatingPrintPreview = false;
340 SetIsPrintPreview(false);
341 } else {
342 SetIsPrinting(false);
344 if (rv != NS_ERROR_ABORT && rv != NS_ERROR_OUT_OF_MEMORY) {
345 FirePrintingErrorEvent(rv);
347 DestroyPrintingData();
350 return rv;
353 nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview,
354 nsIPrintSettings* aPrintSettings,
355 nsIWebProgressListener* aWebProgressListener,
356 Document& aDoc) {
357 MOZ_ASSERT(aDoc.IsStaticDocument());
359 nsresult rv;
361 // Grab the new instance with local variable to guarantee that it won't be
362 // deleted during this method.
363 // Note: Methods we call early below rely on mPrt being set.
364 mPrt = new nsPrintData(aIsPrintPreview ? nsPrintData::eIsPrintPreview
365 : nsPrintData::eIsPrinting);
366 RefPtr<nsPrintData> printData = mPrt;
368 if (aIsPrintPreview) {
369 mIsCreatingPrintPreview = true;
370 SetIsPrintPreview(true);
371 } else {
372 SetIsPrinting(true);
375 if (aWebProgressListener) {
376 printData->mPrintProgressListeners.AppendObject(aWebProgressListener);
378 if (mRemotePrintJob) {
379 // If we have a RemotePrintJob add it to the print progress listeners,
380 // so it can forward to the parent.
381 printData->mPrintProgressListeners.AppendElement(mRemotePrintJob);
384 // Get the docshell for this documentviewer
385 nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell, &rv));
386 NS_ENSURE_SUCCESS(rv, rv);
388 // if they don't pass in a PrintSettings, then get the Global PS
389 mPrintSettings = aPrintSettings;
390 if (!mPrintSettings) {
391 MOZ_TRY(GetDefaultPrintSettings(getter_AddRefs(mPrintSettings)));
395 nsAutoScriptBlocker scriptBlocker;
396 // Note: docShell is implicitly non-null via do_QueryReferent necessarily
397 // having succeeded (if we got here).
398 mPrintObject = MakeUnique<nsPrintObject>(*docShell, aDoc);
399 mPrintDocList.AppendElement(mPrintObject.get());
401 BuildNestedPrintObjects(mPrintObject);
404 // The nsAutoScriptBlocker above will now have been destroyed, which may
405 // cause our print/print-preview operation to finish. In this case, we
406 // should immediately return an error code so that the root caller knows
407 // it shouldn't continue to do anything with this instance.
408 if (mIsDestroying) {
409 return NS_ERROR_FAILURE;
412 // XXX This isn't really correct...
413 if (!mPrintObject->mDocument || !mPrintObject->mDocument->GetRootElement()) {
414 return NS_ERROR_GFX_PRINTER_STARTDOC;
417 mPrintSettings->GetShrinkToFit(&mShrinkToFit);
419 nsCOMPtr<nsIDeviceContextSpec> devspec;
420 if (XRE_IsContentProcess()) {
421 devspec = new nsDeviceContextSpecProxy(mRemotePrintJob);
422 } else {
423 devspec = do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv);
424 NS_ENSURE_SUCCESS(rv, rv);
427 bool printSilently = false;
428 mPrintSettings->GetPrintSilent(&printSilently);
429 if (StaticPrefs::print_always_print_silent()) {
430 printSilently = true;
433 if (mIsDoingPrinting && printSilently) {
434 glean::printing::silent_print.Add(1);
437 MOZ_TRY(devspec->Init(mPrintSettings, mIsCreatingPrintPreview));
439 printData->mPrintDC = new nsDeviceContext();
440 MOZ_TRY(printData->mPrintDC->InitForPrinting(devspec));
442 MOZ_TRY(EnablePOsForPrinting());
444 if (!mIsCreatingPrintPreview) {
445 printData->OnStartPrinting();
447 InitPrintDocConstruction(false);
449 return NS_OK;
452 //---------------------------------------------------------------------------------
453 nsresult nsPrintJob::Print(Document& aDoc, nsIPrintSettings* aPrintSettings,
454 RemotePrintJobChild* aRemotePrintJob,
455 nsIWebProgressListener* aWebProgressListener) {
456 mRemotePrintJob = aRemotePrintJob;
457 return CommonPrint(false, aPrintSettings, aWebProgressListener, aDoc);
460 nsresult nsPrintJob::PrintPreview(Document& aDoc,
461 nsIPrintSettings* aPrintSettings,
462 nsIWebProgressListener* aWebProgressListener,
463 PrintPreviewResolver&& aCallback) {
464 // Take ownership of aCallback, otherwise a function further up the call
465 // stack will call it to signal failure (by passing zero).
466 mPrintPreviewCallback = std::move(aCallback);
468 nsresult rv = CommonPrint(true, aPrintSettings, aWebProgressListener, aDoc);
469 if (NS_FAILED(rv)) {
470 if (mPrintPreviewCallback) {
471 // signal error
472 mPrintPreviewCallback(
473 PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
474 mPrintPreviewCallback = nullptr;
477 return rv;
480 int32_t nsPrintJob::GetRawNumPages() const {
481 auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
482 Unused << numSheets;
483 return seqFrame ? seqFrame->GetRawNumPages() : 0;
486 bool nsPrintJob::GetIsEmpty() const {
487 auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
488 if (!seqFrame) {
489 return true;
491 if (numSheets > 1) {
492 return false;
494 return !seqFrame->GetPagesInFirstSheet();
497 int32_t nsPrintJob::GetPrintPreviewNumSheets() const {
498 auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
499 Unused << seqFrame;
500 return numSheets;
503 //-----------------------------------------------------------------
504 //-- Section: Pre-Reflow Methods
505 //-----------------------------------------------------------------
507 // static
508 void nsPrintJob::GetDisplayTitleAndURL(Document& aDoc,
509 nsIPrintSettings* aSettings,
510 DocTitleDefault aTitleDefault,
511 nsAString& aTitle, nsAString& aURLStr) {
512 aTitle.Truncate();
513 aURLStr.Truncate();
515 if (aSettings) {
516 aSettings->GetTitle(aTitle);
517 aSettings->GetDocURL(aURLStr);
520 if (aTitle.IsEmpty()) {
521 aDoc.GetTitle(aTitle);
522 if (aTitle.IsEmpty()) {
523 if (!aURLStr.IsEmpty() &&
524 aTitleDefault == DocTitleDefault::eDocURLElseFallback) {
525 aTitle = aURLStr;
526 } else {
527 nsCOMPtr<nsIStringBundle> brandBundle;
528 nsCOMPtr<nsIStringBundleService> svc =
529 mozilla::components::StringBundle::Service();
530 if (svc) {
531 svc->CreateBundle("chrome://branding/locale/brand.properties",
532 getter_AddRefs(brandBundle));
533 if (brandBundle) {
534 brandBundle->GetStringFromName("brandShortName", aTitle);
537 if (aTitle.IsEmpty()) {
538 aTitle.AssignLiteral(u"Mozilla Document");
544 if (aURLStr.IsEmpty()) {
545 nsIURI* url = aDoc.GetDocumentURI();
546 if (!url) {
547 return;
550 nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(url);
551 nsAutoCString urlCStr;
552 nsresult rv = exposableURI->GetSpec(urlCStr);
553 if (NS_FAILED(rv)) {
554 return;
557 nsCOMPtr<nsITextToSubURI> textToSubURI =
558 do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
559 if (NS_FAILED(rv)) {
560 return;
563 textToSubURI->UnEscapeURIForUI(urlCStr, aURLStr);
567 //---------------------------------------------------------------------
568 nsresult nsPrintJob::DocumentReadyForPrinting() {
569 // Send the document to the printer...
570 nsresult rv = SetupToPrintContent();
571 if (NS_FAILED(rv)) {
572 // The print job was canceled or there was a problem
573 // So remove all other documents from the print list
574 DonePrintingSheets(nullptr, rv);
576 return rv;
579 /** ---------------------------------------------------
580 * Cleans up when an error occurred
582 nsresult nsPrintJob::CleanupOnFailure(nsresult aResult, bool aIsPrinting) {
583 PR_PL(("**** Failed %s - rv 0x%" PRIX32,
584 aIsPrinting ? "Printing" : "Print Preview",
585 static_cast<uint32_t>(aResult)));
586 PROFILER_MARKER_TEXT("PrintJob", LAYOUT_Printing, MarkerStack::Capture(),
587 "nsPrintJob::CleanupOnFailure"_ns);
589 /* cleanup... */
590 if (mPagePrintTimer) {
591 mPagePrintTimer->Stop();
592 DisconnectPagePrintTimer();
595 if (aIsPrinting) {
596 SetIsPrinting(false);
597 } else {
598 SetIsPrintPreview(false);
599 mIsCreatingPrintPreview = false;
602 /* cleanup done, let's fire-up an error dialog to notify the user
603 * what went wrong...
605 * When rv == NS_ERROR_ABORT, it means we want out of the
606 * print job without displaying any error messages
608 if (aResult != NS_ERROR_ABORT) {
609 FirePrintingErrorEvent(aResult);
612 FirePrintCompletionEvent();
614 return aResult;
617 //---------------------------------------------------------------------
618 void nsPrintJob::FirePrintingErrorEvent(nsresult aPrintError) {
619 if (mPrintPreviewCallback) {
620 // signal error
621 mPrintPreviewCallback(
622 PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
623 mPrintPreviewCallback = nullptr;
626 nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
627 if (NS_WARN_IF(!viewer)) {
628 return;
631 const RefPtr<Document> doc = viewer->GetDocument();
632 const RefPtr<CustomEvent> event = NS_NewDOMCustomEvent(doc, nullptr, nullptr);
634 MOZ_ASSERT(event);
636 AutoJSAPI jsapi;
637 if (!jsapi.Init(event->GetParentObject())) {
638 return;
640 JSContext* cx = jsapi.cx();
642 JS::Rooted<JS::Value> detail(
643 cx, JS::NumberValue(static_cast<double>(aPrintError)));
644 event->InitCustomEvent(cx, u"PrintingError"_ns, false, false, detail);
645 event->SetTrusted(true);
647 // Event listeners in chrome shouldn't delete this.
648 AsyncEventDispatcher::RunDOMEventWhenSafe(*doc, *event,
649 ChromeOnlyDispatch::eYes);
651 // Inform any progress listeners of the Error.
652 if (mPrt) {
653 // Note that nsPrintData::DoOnStatusChange() will call some listeners.
654 // So, mPrt can be cleared or recreated.
655 RefPtr<nsPrintData> printData = mPrt;
656 printData->DoOnStatusChange(aPrintError);
660 //-----------------------------------------------------------------
661 //-- Section: Reflow Methods
662 //-----------------------------------------------------------------
664 nsresult nsPrintJob::ReconstructAndReflow() {
665 if (NS_WARN_IF(!mPrt)) {
666 return NS_ERROR_FAILURE;
669 #if defined(XP_WIN) && defined(EXTENDED_DEBUG_PRINTING)
670 // We need to clear all the output files here
671 // because they will be re-created with second reflow of the docs
672 if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
673 RemoveFilesInDir(".\\");
674 gDumpFileNameCnt = 0;
675 gDumpLOFileNameCnt = 0;
677 #endif
679 // In this loop, it's conceivable that one of our helpers might clear mPrt,
680 // while we're using it & its members! So we capture it in an owning local
681 // reference & use that instead of using mPrt directly.
682 RefPtr<nsPrintData> printData = mPrt;
683 for (nsPrintObject* po : mPrintDocList) {
684 if (!po->PrintingIsEnabled() || po->mInvisible) {
685 continue;
688 // When the print object has been marked as "print the document" (i.e,
689 // po->PrintingIsEnabled() is true), mPresContext and mPresShell should be
690 // non-nullptr (i.e., should've been created for the print) since they
691 // are necessary to print the document.
692 MOZ_ASSERT(po->mPresContext && po->mPresShell,
693 "mPresContext and mPresShell shouldn't be nullptr when the "
694 "print object "
695 "has been marked as \"print the document\"");
697 UpdateZoomRatio(po);
699 po->mPresContext->SetPageScale(po->mZoomRatio);
701 // Calculate scale factor from printer to screen
702 float printDPI = float(AppUnitsPerCSSInch()) /
703 float(printData->mPrintDC->AppUnitsPerDevPixel());
704 po->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);
706 RefPtr<PresShell> presShell(po->mPresShell);
707 if (NS_WARN_IF(presShell->IsDestroying())) {
708 return NS_ERROR_FAILURE;
711 presShell->ReconstructFrames();
713 // If the printing was canceled or restarted with different data,
714 // let's stop doing this printing.
715 if (NS_WARN_IF(mPrt != printData)) {
716 return NS_ERROR_FAILURE;
719 // For all views except the first one, setup the root view.
720 // ??? Can there be multiple po for the top-level-document?
721 bool documentIsTopLevel = true;
722 if (po->mParent) {
723 nsSize adjSize;
724 bool doReturn;
725 nsresult rv = SetRootView(po, doReturn, documentIsTopLevel, adjSize);
727 MOZ_ASSERT(!documentIsTopLevel, "How could this happen?");
729 if (NS_FAILED(rv) || doReturn) {
730 return rv;
734 presShell->FlushPendingNotifications(FlushType::Layout);
736 if (NS_WARN_IF(presShell->IsDestroying())) {
737 return NS_ERROR_FAILURE;
740 // If the printing was canceled or restarted with different data,
741 // let's stop doing this printing.
742 if (NS_WARN_IF(mPrt != printData)) {
743 return NS_ERROR_FAILURE;
746 po->mDocument->UpdateRemoteFrameEffects();
747 MOZ_TRY(UpdateSelectionAndShrinkPrintObject(po, documentIsTopLevel));
749 return NS_OK;
752 //-------------------------------------------------------
753 nsresult nsPrintJob::SetupToPrintContent() {
754 // This method may be called while DoCommonPrint() initializes the instance
755 // when its script blocker goes out of scope. In such case, this cannot do
756 // its job as expected because some objects in mPrt have not been initialized
757 // yet but they are necessary.
758 // Note: it shouldn't be possible for mPrintObject to be null; we check
759 // it for good measure (after we check its owner) before we start
760 // dereferencing it below.
761 if (NS_WARN_IF(!mPrt) || NS_WARN_IF(!mPrintObject)) {
762 return NS_ERROR_FAILURE;
765 // If this is creating print preview, mPrintObject->mPresContext and
766 // mPrintObject->mPresShell need to be non-nullptr because this cannot
767 // initialize page sequence frame without them at end of this method since
768 // page sequence frame has already been destroyed or not been created yet.
769 if (mIsCreatingPrintPreview && (NS_WARN_IF(!mPrintObject->mPresContext) ||
770 NS_WARN_IF(!mPrintObject->mPresShell))) {
771 return NS_ERROR_FAILURE;
774 // If this is printing some documents (not print-previewing the documents),
775 // mPrintObject->mPresContext and mPrintObject->mPresShell can be
776 // nullptr only when mPrintObject->PrintingIsEnabled() is false. E.g.,
777 // if the document has a <frameset> element and it's printing only content in
778 // a <frame> element or all <frame> elements separately.
779 MOZ_ASSERT(
780 (!mIsCreatingPrintPreview && !mPrintObject->PrintingIsEnabled()) ||
781 (mPrintObject->mPresContext && mPrintObject->mPresShell),
782 "mPresContext and mPresShell shouldn't be nullptr when printing the "
783 "document or creating print-preview");
785 bool didReconstruction = false;
787 // This method works with mPrintObject. So, we need to guarantee that
788 // it won't be deleted in this method. We achieve this by holding a strong
789 // local reference to mPrt, which in turn keeps mPrintObject alive.
790 RefPtr<nsPrintData> printData = mPrt;
792 // If some new content got loaded since the initial reflow rebuild
793 // everything.
794 if (mDidLoadDataForPrinting) {
795 nsresult rv = ReconstructAndReflow();
796 if (NS_WARN_IF(NS_FAILED(rv))) {
797 return rv;
799 // If the printing was canceled or restarted with different data,
800 // let's stop doing this printing.
801 if (NS_WARN_IF(mPrt != printData)) {
802 return NS_ERROR_FAILURE;
804 didReconstruction = true;
807 // Here is where we figure out if extra reflow for shrinking the content
808 // is required.
809 if (mShrinkToFit) {
810 mShrinkToFitFactor = mPrintObject->mShrinkRatio;
812 if (mShrinkToFitFactor < 0.998f) {
813 nsresult rv = ReconstructAndReflow();
814 if (NS_WARN_IF(NS_FAILED(rv))) {
815 return rv;
817 // If the printing was canceled or restarted with different data,
818 // let's stop doing this printing.
819 if (NS_WARN_IF(mPrt != printData)) {
820 return NS_ERROR_FAILURE;
822 didReconstruction = true;
825 if (MOZ_LOG_TEST(gPrintingLog, LogLevel::Debug)) {
826 float calcRatio = mPrintObject->mShrinkRatio;
827 PR_PL(
828 ("*******************************************************************"
829 "*******\n"));
830 PR_PL(("STF Ratio is: %8.5f Effective Ratio: %8.5f Diff: %8.5f\n",
831 mShrinkToFitFactor, calcRatio, mShrinkToFitFactor - calcRatio));
832 PR_PL(
833 ("*******************************************************************"
834 "*******\n"));
838 // If the frames got reconstructed and reflowed the number of pages might
839 // has changed.
840 if (didReconstruction) {
841 FirePrintPreviewUpdateEvent();
842 // If the printing was canceled or restarted with different data,
843 // let's stop doing this printing.
844 if (NS_WARN_IF(mPrt != printData)) {
845 return NS_ERROR_FAILURE;
849 DUMP_DOC_LIST(("\nAfter Reflow------------------------------------------"));
850 PR_PL(("\n"));
851 PR_PL(("-------------------------------------------------------\n"));
852 PR_PL(("\n"));
854 CalcNumPrintablePages(mNumPrintablePages);
856 PR_PL(("--- Printing %d pages\n", mNumPrintablePages));
857 DUMP_DOC_TREELAYOUT;
859 // Print listener setup...
860 printData->OnStartPrinting();
862 // If the printing was canceled or restarted with different data,
863 // let's stop doing this printing.
864 if (NS_WARN_IF(mPrt != printData)) {
865 return NS_ERROR_FAILURE;
868 nsAutoString fileNameStr;
869 // check to see if we are printing to a file
870 if (mPrintSettings->GetOutputDestination() ==
871 nsIPrintSettings::kOutputDestinationFile) {
872 // On some platforms the BeginDocument needs to know the name of the file.
873 mPrintSettings->GetToFileName(fileNameStr);
876 nsAutoString docTitleStr;
877 nsAutoString docURLStr;
878 GetDisplayTitleAndURL(*mPrintObject->mDocument, mPrintSettings,
879 DocTitleDefault::eDocURLElseFallback, docTitleStr,
880 docURLStr);
882 int32_t startPage = 1;
883 int32_t endPage = mNumPrintablePages;
885 nsTArray<int32_t> ranges;
886 mPrintSettings->GetPageRanges(ranges);
887 for (size_t i = 0; i < ranges.Length(); i += 2) {
888 startPage = std::max(1, std::min(startPage, ranges[i]));
889 endPage = std::min(mNumPrintablePages, std::max(endPage, ranges[i + 1]));
892 nsresult rv = NS_OK;
893 // BeginDocument may pass back a FAILURE code
894 // i.e. On Windows, if you are printing to a file and hit "Cancel"
895 // to the "File Name" dialog, this comes back as an error
896 // Don't start printing when regression test are executed
897 if (mIsDoingPrinting) {
898 rv = printData->mPrintDC->BeginDocument(docTitleStr, fileNameStr, startPage,
899 endPage);
902 if (mIsCreatingPrintPreview) {
903 // Copy docTitleStr and docURLStr to the pageSequenceFrame, to be displayed
904 // in the header
905 nsPageSequenceFrame* seqFrame =
906 mPrintObject->mPresShell->GetPageSequenceFrame();
907 if (seqFrame) {
908 seqFrame->StartPrint(mPrintObject->mPresContext, mPrintSettings,
909 docTitleStr, docURLStr);
913 PR_PL(("****************** Begin Document ************************\n"));
915 if (NS_FAILED(rv)) {
916 NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
917 "Failed to begin document for printing");
918 return rv;
921 // This will print the docshell document
922 // when it completes asynchronously in the DonePrintingSheets method
923 // it will check to see if there are more docshells to be printed and
924 // then PrintDocContent will be called again.
926 if (mIsDoingPrinting) {
927 // Double-check that mPrintObject is non-null, because it could have
928 // gotten cleared while running script since we last checked it.
929 if (NS_WARN_IF(!mPrintObject)) {
930 return NS_ERROR_FAILURE;
933 PrintDocContent(mPrintObject, rv); // ignore return value
936 return rv;
939 //-------------------------------------------------------
940 // Recursively reflow each sub-doc and then calc
941 // all the frame locations of the sub-docs
942 nsresult nsPrintJob::ReflowDocList(const UniquePtr<nsPrintObject>& aPO) {
943 NS_ENSURE_ARG_POINTER(aPO);
945 // Check to see if the subdocument's element has been hidden by the parent
946 // document
947 if (aPO->mParent && aPO->mParent->mPresShell) {
948 nsIFrame* frame =
949 aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr;
950 if (!frame || !frame->StyleVisibility()->IsVisible()) {
951 aPO->EnablePrinting(false);
952 aPO->mInvisible = true;
953 return NS_OK;
957 UpdateZoomRatio(aPO.get());
959 // Reflow the PO
960 MOZ_TRY(ReflowPrintObject(aPO));
962 for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
963 MOZ_TRY(ReflowDocList(kid));
965 return NS_OK;
968 void nsPrintJob::FirePrintPreviewUpdateEvent() {
969 // Dispatch the event only while in PrintPreview. When printing, there is no
970 // listener bound to this event and therefore no need to dispatch it.
971 if (mCreatedForPrintPreview && !mIsDoingPrinting) {
972 nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
973 if (Document* document = viewer->GetDocument()) {
974 AsyncEventDispatcher::RunDOMEventWhenSafe(
975 *document, u"printPreviewUpdate"_ns, CanBubble::eYes,
976 ChromeOnlyDispatch::eYes);
981 nsresult nsPrintJob::InitPrintDocConstruction(bool aHandleError) {
982 // Guarantee that mPrintObject won't be deleted.
983 RefPtr<nsPrintData> printData = mPrt;
985 if (NS_WARN_IF(!printData)) {
986 return NS_ERROR_FAILURE;
989 // Attach progressListener to catch network requests.
990 mDidLoadDataForPrinting = false;
993 AutoRestore<bool> restore{mDoingInitialReflow};
994 mDoingInitialReflow = true;
996 nsCOMPtr<nsIWebProgress> webProgress =
997 do_QueryInterface(mPrintObject->mDocShell);
998 webProgress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
999 nsIWebProgress::NOTIFY_STATE_REQUEST);
1001 MOZ_TRY(ReflowDocList(mPrintObject));
1003 FirePrintPreviewUpdateEvent();
1006 MaybeResumePrintAfterResourcesLoaded(aHandleError);
1007 return NS_OK;
1010 bool nsPrintJob::ShouldResumePrint() const {
1011 if (mDoingInitialReflow) {
1012 return false;
1014 Document* doc = mPrintObject->mDocument;
1015 MOZ_ASSERT(doc);
1016 NS_ENSURE_TRUE(doc, true);
1017 nsCOMPtr<nsILoadGroup> lg = doc->GetDocumentLoadGroup();
1018 NS_ENSURE_TRUE(lg, true);
1019 bool pending = false;
1020 nsresult rv = lg->IsPending(&pending);
1021 NS_ENSURE_SUCCESS(rv, true);
1022 return !pending;
1025 nsresult nsPrintJob::MaybeResumePrintAfterResourcesLoaded(
1026 bool aCleanupOnError) {
1027 if (!ShouldResumePrint()) {
1028 mDidLoadDataForPrinting = true;
1029 return NS_OK;
1031 // If Destroy() has already been called, mPtr is nullptr. Then, the instance
1032 // needs to do nothing anymore in this method.
1033 // Note: it shouldn't be possible for mPrintObject to be null; we
1034 // just check it for good measure, as we check its owner.
1035 // Note: it shouldn't be possible for mPrintObject->mDocShell to be
1036 // null; we just check it for good measure, as we check its owner.
1037 if (!mPrt || NS_WARN_IF(!mPrintObject) ||
1038 NS_WARN_IF(!mPrintObject->mDocShell)) {
1039 return NS_ERROR_FAILURE;
1042 nsCOMPtr<nsIWebProgress> webProgress =
1043 do_QueryInterface(mPrintObject->mDocShell);
1045 webProgress->RemoveProgressListener(
1046 static_cast<nsIWebProgressListener*>(this));
1048 nsresult rv;
1049 if (mIsDoingPrinting) {
1050 rv = DocumentReadyForPrinting();
1051 } else {
1052 rv = FinishPrintPreview();
1055 /* cleaup on failure + notify user */
1056 if (aCleanupOnError && NS_FAILED(rv)) {
1057 NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
1058 "nsPrintJob::ResumePrintAfterResourcesLoaded failed");
1059 CleanupOnFailure(rv, !mIsDoingPrinting);
1062 return rv;
1065 ////////////////////////////////////////////////////////////////////////////////
1066 // nsIWebProgressListener
1068 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
1069 nsPrintJob::OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1070 uint32_t aStateFlags, nsresult aStatus) {
1071 if (aStateFlags & STATE_STOP) {
1072 // If all resources are loaded, then finish and reflow.
1073 MaybeResumePrintAfterResourcesLoaded(/* aCleanupOnError */ true);
1075 return NS_OK;
1078 NS_IMETHODIMP
1079 nsPrintJob::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1080 int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
1081 int32_t aCurTotalProgress,
1082 int32_t aMaxTotalProgress) {
1083 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1084 return NS_OK;
1087 NS_IMETHODIMP
1088 nsPrintJob::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1089 nsIURI* aLocation, uint32_t aFlags) {
1090 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1091 return NS_OK;
1094 NS_IMETHODIMP
1095 nsPrintJob::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1096 nsresult aStatus, const char16_t* aMessage) {
1097 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1098 return NS_OK;
1101 NS_IMETHODIMP
1102 nsPrintJob::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1103 uint32_t aState) {
1104 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1105 return NS_OK;
1108 NS_IMETHODIMP
1109 nsPrintJob::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
1110 nsIRequest* aRequest, uint32_t aEvent) {
1111 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1112 return NS_OK;
1115 //-------------------------------------------------------
1117 void nsPrintJob::UpdateZoomRatio(nsPrintObject* aPO) {
1118 if (!aPO->mParent) {
1119 if (mShrinkToFit) {
1120 aPO->mZoomRatio = mShrinkToFitFactor;
1121 // If we're actually going to scale (the factor is less than 1), we
1122 // reduce the scale factor slightly to avoid the possibility of floating
1123 // point rounding error causing slight clipping of the longest lines.
1124 if (aPO->mZoomRatio != 1.0f) {
1125 aPO->mZoomRatio -= 0.005f;
1127 } else {
1128 double scaling;
1129 mPrintSettings->GetScaling(&scaling);
1130 aPO->mZoomRatio = float(scaling);
1135 nsresult nsPrintJob::UpdateSelectionAndShrinkPrintObject(
1136 nsPrintObject* aPO, bool aDocumentIsTopLevel) {
1137 PresShell* displayPresShell = aPO->mDocShell->GetPresShell();
1138 // Transfer Selection Ranges to the new Print PresShell
1139 RefPtr<Selection> selection, selectionPS;
1140 // It's okay if there is no display shell, just skip copying the selection
1141 if (displayPresShell) {
1142 selection = displayPresShell->GetCurrentSelection(SelectionType::eNormal);
1144 selectionPS = aPO->mPresShell->GetCurrentSelection(SelectionType::eNormal);
1146 // Reset all existing selection ranges that might have been added by calling
1147 // this function before.
1148 if (selectionPS) {
1149 selectionPS->RemoveAllRanges(IgnoreErrors());
1151 if (selection && selectionPS) {
1152 const uint32_t rangeCount = selection->RangeCount();
1153 for (const uint32_t inx : IntegerRange(rangeCount)) {
1154 MOZ_ASSERT(selection->RangeCount() == rangeCount);
1155 const RefPtr<nsRange> range{selection->GetRangeAt(inx)};
1156 selectionPS->AddRangeAndSelectFramesAndNotifyListeners(*range,
1157 IgnoreErrors());
1161 // If we are trying to shrink the contents to fit on the page
1162 // we must first locate the "pageContent" frame
1163 // Then we walk the frame tree and look for the "xmost" frame
1164 // this is the frame where the right-hand side of the frame extends
1165 // the furthest
1166 if (mShrinkToFit && aDocumentIsTopLevel) {
1167 nsPageSequenceFrame* pageSeqFrame = aPO->mPresShell->GetPageSequenceFrame();
1168 NS_ENSURE_STATE(pageSeqFrame);
1169 aPO->mShrinkRatio = pageSeqFrame->GetSTFPercent();
1170 // Limit the shrink-to-fit scaling for some text-ish type of documents.
1171 nsAutoString contentType;
1172 aPO->mPresShell->GetDocument()->GetContentType(contentType);
1173 if (contentType.EqualsLiteral("application/xhtml+xml") ||
1174 StringBeginsWith(contentType, u"text/"_ns)) {
1175 int32_t limitPercent =
1176 Preferences::GetInt("print.shrink-to-fit.scale-limit-percent", 20);
1177 limitPercent = std::max(0, limitPercent);
1178 limitPercent = std::min(100, limitPercent);
1179 float minShrinkRatio = float(limitPercent) / 100;
1180 aPO->mShrinkRatio = std::max(aPO->mShrinkRatio, minShrinkRatio);
1183 return NS_OK;
1186 nsView* nsPrintJob::GetParentViewForRoot() {
1187 if (mIsCreatingPrintPreview) {
1188 if (nsCOMPtr<nsIDocumentViewer> viewer =
1189 do_QueryInterface(mDocViewerPrint)) {
1190 return viewer->FindContainerView();
1193 return nullptr;
1196 nsresult nsPrintJob::SetRootView(nsPrintObject* aPO, bool& doReturn,
1197 bool& documentIsTopLevel, nsSize& adjSize) {
1198 bool canCreateScrollbars = true;
1200 nsView* rootView;
1201 nsView* parentView = nullptr;
1203 doReturn = false;
1205 if (aPO->mParent && aPO->mParent->PrintingIsEnabled()) {
1206 nsIFrame* frame =
1207 aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr;
1208 // Without a frame, this document can't be displayed; therefore, there is no
1209 // point to reflowing it
1210 if (!frame) {
1211 aPO->EnablePrinting(false);
1212 doReturn = true;
1213 return NS_OK;
1216 // XXX If printing supported printing document hierarchies with non-constant
1217 // zoom this would be wrong as we use the same mPrt->mPrintDC for all
1218 // subdocuments.
1219 adjSize = frame->GetContentRect().Size();
1220 documentIsTopLevel = false;
1221 // presshell exists because parent is printable
1223 // the top nsPrintObject's widget will always have scrollbars
1224 if (frame && frame->IsSubDocumentFrame()) {
1225 nsView* view = frame->GetView();
1226 NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
1227 view = view->GetFirstChild();
1228 NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
1229 parentView = view;
1230 canCreateScrollbars = false;
1232 } else {
1233 adjSize = mPrt->mPrintDC->GetDeviceSurfaceDimensions();
1234 documentIsTopLevel = true;
1235 parentView = GetParentViewForRoot();
1238 if (aPO->mViewManager->GetRootView()) {
1239 // Reuse the root view that is already on the root frame.
1240 rootView = aPO->mViewManager->GetRootView();
1241 // Remove it from its existing parent if necessary
1242 aPO->mViewManager->RemoveChild(rootView);
1243 rootView->SetParent(parentView);
1244 } else {
1245 // Create a child window of the parent that is our "root view/window"
1246 nsRect tbounds = nsRect(nsPoint(0, 0), adjSize);
1247 rootView = aPO->mViewManager->CreateView(tbounds, parentView);
1248 NS_ENSURE_TRUE(rootView, NS_ERROR_OUT_OF_MEMORY);
1251 if (mIsCreatingPrintPreview && documentIsTopLevel) {
1252 aPO->mPresContext->SetPaginatedScrolling(canCreateScrollbars);
1255 // Setup hierarchical relationship in view manager
1256 aPO->mViewManager->SetRootView(rootView);
1258 return NS_OK;
1261 // Reflow a nsPrintObject
1262 nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) {
1263 NS_ENSURE_STATE(aPO);
1265 if (!aPO->PrintingIsEnabled()) {
1266 return NS_OK;
1269 NS_ASSERTION(!aPO->mPresContext, "Recreating prescontext");
1271 // Guarantee that mPrt and the objects it owns won't be deleted in this method
1272 // because it might be cleared if other modules called from here may fire
1273 // events, notifying observers and/or listeners.
1274 RefPtr<nsPrintData> printData = mPrt;
1276 // create the PresContext
1277 nsPresContext::nsPresContextType type =
1278 mIsCreatingPrintPreview ? nsPresContext::eContext_PrintPreview
1279 : nsPresContext::eContext_Print;
1280 const bool shouldBeRoot =
1281 (!aPO->mParent || !aPO->mParent->PrintingIsEnabled()) &&
1282 !GetParentViewForRoot();
1283 aPO->mPresContext = shouldBeRoot ? new nsRootPresContext(aPO->mDocument, type)
1284 : new nsPresContext(aPO->mDocument, type);
1285 aPO->mPresContext->SetPrintSettings(mPrintSettings);
1287 // init it with the DC
1288 MOZ_TRY(aPO->mPresContext->Init(printData->mPrintDC));
1290 aPO->mViewManager = new nsViewManager();
1292 MOZ_TRY(aPO->mViewManager->Init(printData->mPrintDC));
1294 bool doReturn = false;
1295 bool documentIsTopLevel = false;
1296 nsSize adjSize;
1298 nsresult rv = SetRootView(aPO.get(), doReturn, documentIsTopLevel, adjSize);
1300 if (NS_FAILED(rv) || doReturn) {
1301 return rv;
1304 // Here, we inform nsPresContext of the page size. Note that 'adjSize' is
1305 // *usually* the page size, but we need to check. Strictly speaking, adjSize
1306 // is the *device output size*, which is really the dimensions of a "sheet"
1307 // rather than a "page" (an important distinction in an N-pages-per-sheet
1308 // scenario). For some pages-per-sheet values, the pages are orthogonal to
1309 // the sheet; we adjust for that here by swapping the width with the height.
1310 nsSize pageSize = adjSize;
1311 if (mPrintSettings->HasOrthogonalPagesPerSheet()) {
1312 std::swap(pageSize.width, pageSize.height);
1314 // XXXalaskanemily: Is this actually necessary? We set it again before the
1315 // first reflow.
1316 aPO->mPresContext->SetPageSize(pageSize);
1318 int32_t p2a = aPO->mPresContext->DeviceContext()->AppUnitsPerDevPixel();
1319 if (documentIsTopLevel && mIsCreatingPrintPreview) {
1320 if (nsCOMPtr<nsIDocumentViewer> viewer =
1321 do_QueryInterface(mDocViewerPrint)) {
1322 // If we're print-previewing and the top level document, use the bounds
1323 // from our doc viewer. Page bounds is not what we want.
1324 LayoutDeviceIntRect bounds;
1325 viewer->GetBounds(bounds);
1326 adjSize = nsSize(bounds.width * p2a, bounds.height * p2a);
1329 aPO->mPresContext->SetIsRootPaginatedDocument(documentIsTopLevel);
1330 aPO->mPresContext->SetVisibleArea(nsRect(nsPoint(), adjSize));
1331 aPO->mPresContext->SetPageScale(aPO->mZoomRatio);
1332 // Calculate scale factor from printer to screen
1333 float printDPI = float(AppUnitsPerCSSInch()) / float(p2a);
1334 aPO->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);
1336 // Do CreatePresShell() after we setup the page size, the visible area, and
1337 // the flag |mIsRootPaginatedDocument|, to make sure we can resolve the
1338 // correct viewport size for the print preview page when notifying the media
1339 // feature values changed. See au_viewport_size_for_viewport_unit_resolution()
1340 // in media_queries.rs for more details.
1341 RefPtr<Document> doc = aPO->mDocument;
1342 RefPtr<nsPresContext> presContext = aPO->mPresContext;
1343 RefPtr<nsViewManager> viewManager = aPO->mViewManager;
1345 aPO->mPresShell = doc->CreatePresShell(presContext, viewManager);
1346 if (!aPO->mPresShell) {
1347 return NS_ERROR_FAILURE;
1350 // If we're printing selection then remove the nonselected nodes from our
1351 // cloned document.
1352 if (mPrintSettings->GetPrintSelectionOnly()) {
1353 // If we fail to remove the nodes then we should fail to print, because if
1354 // the user was trying to print a small selection from a large document,
1355 // sending the whole document to a real printer would be very frustrating.
1356 MOZ_TRY(DeleteNonSelectedNodes(*aPO->mDocument));
1359 aPO->mPresShell->BeginObservingDocument();
1361 PR_PL(
1362 ("In DV::ReflowPrintObject PO: %p pS: %p (%9s) Setting page size w,h to "
1363 "%d,%d\n",
1364 aPO.get(), aPO->mPresShell.get(), LoggableTypeOfPO(aPO.get()),
1365 pageSize.width, pageSize.height));
1367 if (mIsCreatingPrintPreview && documentIsTopLevel) {
1368 mDocViewerPrint->SetPrintPreviewPresentation(
1369 aPO->mViewManager, aPO->mPresContext, aPO->mPresShell.get());
1372 MOZ_TRY(aPO->mPresShell->Initialize());
1373 NS_ASSERTION(aPO->mPresShell, "Presshell should still be here");
1375 RefPtr<PresShell> presShell = aPO->mPresShell;
1377 const ServoStyleSet::PageSizeAndOrientation sizeAndOrientation =
1378 presShell->StyleSet()->GetDefaultPageSizeAndOrientation();
1379 // XXX Should we enable this for known save-to-PDF pseudo-printers once
1380 // bug 1826301 is fixed?
1381 if (mPrintSettings->GetOutputFormat() ==
1382 nsIPrintSettings::kOutputFormatPDF &&
1383 StaticPrefs::
1384 print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) {
1385 mMaybeCSSPageSize = sizeAndOrientation.size;
1386 if (sizeAndOrientation.size) {
1387 pageSize = sizeAndOrientation.size.value();
1388 aPO->mPresContext->SetPageSize(pageSize);
1392 // If the document has a specified CSS page-size, we rotate the page to
1393 // reflect this. Changing the orientation is reflected by the result of
1394 // FinishPrintPreview, so that the frontend can reflect this.
1395 // The new document has not yet been reflowed, so we have to query the
1396 // original document for any CSS page-size.
1397 if (sizeAndOrientation.orientation) {
1398 switch (sizeAndOrientation.orientation.value()) {
1399 case StylePageSizeOrientation::Landscape:
1400 if (pageSize.width < pageSize.height) {
1401 // Paper is in portrait, CSS page size is landscape.
1402 std::swap(pageSize.width, pageSize.height);
1404 break;
1405 case StylePageSizeOrientation::Portrait:
1406 if (pageSize.width > pageSize.height) {
1407 // Paper is in landscape, CSS page size is portrait.
1408 std::swap(pageSize.width, pageSize.height);
1410 break;
1412 mMaybeCSSPageLandscape = Some(sizeAndOrientation.orientation.value() ==
1413 StylePageSizeOrientation::Landscape);
1414 aPO->mPresContext->SetPageSize(pageSize);
1417 // Make sure animations are active.
1418 for (DocumentTimeline* tl : aPO->mDocument->Timelines()) {
1419 tl->TriggerAllPendingAnimationsNow();
1421 // Process the reflow event Initialize posted
1422 presShell->FlushPendingNotifications(FlushType::Layout);
1423 aPO->mDocument->UpdateRemoteFrameEffects();
1425 MOZ_TRY(UpdateSelectionAndShrinkPrintObject(aPO.get(), documentIsTopLevel));
1427 #ifdef EXTENDED_DEBUG_PRINTING
1428 if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
1429 nsAutoCString docStr;
1430 nsAutoCString urlStr;
1431 GetDocTitleAndURL(aPO, docStr, urlStr);
1432 char filename[256];
1433 sprintf(filename, "print_dump_%d.txt", gDumpFileNameCnt++);
1434 // Dump all the frames and view to a a file
1435 FILE* fd = fopen(filename, "w");
1436 if (fd) {
1437 nsIFrame* theRootFrame = aPO->mPresShell->GetRootFrame();
1438 fprintf(fd, "Title: %s\n", docStr.get());
1439 fprintf(fd, "URL: %s\n", urlStr.get());
1440 fprintf(fd, "--------------- Frames ----------------\n");
1441 // RefPtr<gfxContext> renderingContext =
1442 // printData->mPrintDocDC->CreateRenderingContext();
1443 RootFrameList(aPO->mPresContext, fd, 0);
1444 // DumpFrames(fd, aPO->mPresContext, renderingContext, theRootFrame, 0);
1445 fprintf(fd, "---------------------------------------\n\n");
1446 fprintf(fd, "--------------- Views From Root Frame----------------\n");
1447 nsView* v = theRootFrame->GetView();
1448 if (v) {
1449 v->List(fd);
1450 } else {
1451 printf("View is null!\n");
1453 if (aPO->mDocShell) {
1454 fprintf(fd, "--------------- All Views ----------------\n");
1455 DumpViews(aPO->mDocShell, fd);
1456 fprintf(fd, "---------------------------------------\n\n");
1458 fclose(fd);
1461 #endif
1463 return NS_OK;
1466 //-------------------------------------------------------
1467 // Figure out how many documents and how many total pages we are printing
1468 void nsPrintJob::CalcNumPrintablePages(int32_t& aNumPages) {
1469 aNumPages = 0;
1470 // Count the number of printable documents and printable pages
1471 for (nsPrintObject* po : mPrintDocList) {
1472 // Note: The po->mPresContext null-check below is necessary, because it's
1473 // possible po->mPresContext might never have been set. (e.g., if
1474 // PrintingIsEnabled() returns false, ReflowPrintObject bails before setting
1475 // mPresContext)
1476 if (po->mPresContext && po->mPresContext->IsRootPaginatedDocument()) {
1477 nsPageSequenceFrame* seqFrame = po->mPresShell->GetPageSequenceFrame();
1478 if (seqFrame) {
1479 aNumPages += seqFrame->PrincipalChildList().GetLength();
1485 //-----------------------------------------------------------------
1486 //-- Done: Reflow Methods
1487 //-----------------------------------------------------------------
1489 //-----------------------------------------------------------------
1490 //-- Section: Printing Methods
1491 //-----------------------------------------------------------------
1493 //-------------------------------------------------------
1494 // Called for each DocShell that needs to be printed
1495 bool nsPrintJob::PrintDocContent(const UniquePtr<nsPrintObject>& aPO,
1496 nsresult& aStatus) {
1497 NS_ASSERTION(aPO, "Pointer is null!");
1498 aStatus = NS_OK;
1500 if (!aPO->mHasBeenPrinted && aPO->PrintingIsEnabled()) {
1501 aStatus = DoPrint(aPO);
1502 return true;
1505 // If |aPO->mHasBeenPrinted| is true,
1506 // the kids frames are already processed in |PrintPage|.
1507 // XXX This should be removed. Since bug 1552785 it has no longer been
1508 // possible for us to have to print multiple subdocuments consecutively.
1509 if (!aPO->mHasBeenPrinted && !aPO->mInvisible) {
1510 for (const UniquePtr<nsPrintObject>& po : aPO->mKids) {
1511 bool printed = PrintDocContent(po, aStatus);
1512 if (printed || NS_FAILED(aStatus)) {
1513 return true;
1517 return false;
1520 // A helper struct to aid with DeleteNonSelectedNodes.
1521 struct MOZ_STACK_CLASS SelectionRangeState {
1522 explicit SelectionRangeState(RefPtr<Selection> aSelection)
1523 : mSelection(std::move(aSelection)) {
1524 MOZ_ASSERT(mSelection);
1525 MOZ_ASSERT(!mSelection->RangeCount());
1528 // Selects all the nodes that are _not_ included in a given set of ranges.
1529 MOZ_CAN_RUN_SCRIPT void SelectComplementOf(Span<const RefPtr<nsRange>>);
1530 // Removes the selected ranges from the document.
1531 MOZ_CAN_RUN_SCRIPT void RemoveSelectionFromDocument();
1533 private:
1534 struct Position {
1535 nsINode* mNode;
1536 uint32_t mOffset;
1539 MOZ_CAN_RUN_SCRIPT void SelectRange(nsRange*);
1540 MOZ_CAN_RUN_SCRIPT void SelectNodesExcept(const Position& aStart,
1541 const Position& aEnd);
1542 MOZ_CAN_RUN_SCRIPT void SelectNodesExceptInSubtree(const Position& aStart,
1543 const Position& aEnd);
1545 // A map from subtree root (document or shadow root) to the start position of
1546 // the non-selected content (so far).
1547 nsTHashMap<nsPtrHashKey<nsINode>, Position> mPositions;
1549 // The selection we're adding the ranges to.
1550 const RefPtr<Selection> mSelection;
1553 void SelectionRangeState::SelectComplementOf(
1554 Span<const RefPtr<nsRange>> aRanges) {
1555 for (const auto& range : aRanges) {
1556 auto start = Position{range->GetMayCrossShadowBoundaryStartContainer(),
1557 range->MayCrossShadowBoundaryStartOffset()};
1558 auto end = Position{range->GetMayCrossShadowBoundaryEndContainer(),
1559 range->MayCrossShadowBoundaryEndOffset()};
1560 SelectNodesExcept(start, end);
1564 void SelectionRangeState::SelectRange(nsRange* aRange) {
1565 if (aRange && !aRange->AreNormalRangeAndCrossShadowBoundaryRangeCollapsed()) {
1566 mSelection->AddRangeAndSelectFramesAndNotifyListeners(*aRange,
1567 IgnoreErrors());
1571 void SelectionRangeState::SelectNodesExcept(const Position& aStart,
1572 const Position& aEnd) {
1573 SelectNodesExceptInSubtree(aStart, aEnd);
1574 if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
1575 if (auto* shadow = ShadowRoot::FromNode(aStart.mNode->SubtreeRoot())) {
1576 auto* host = shadow->Host();
1577 // Can't just select other nodes except the host, because other nodes that
1578 // are not in this particular shadow tree could also be selected
1579 SelectNodesExcept(Position{host, 0},
1580 Position{host, host->GetChildCount()});
1581 } else {
1582 MOZ_ASSERT(aStart.mNode->IsInUncomposedDoc());
1587 void SelectionRangeState::SelectNodesExceptInSubtree(const Position& aStart,
1588 const Position& aEnd) {
1589 static constexpr auto kEllipsis = u"\x2026"_ns;
1591 // Finish https://bugzilla.mozilla.org/show_bug.cgi?id=1903871 once the pref
1592 // is shipped, so that we only need one position.
1593 nsINode* root = StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
1594 ? aStart.mNode->OwnerDoc()
1595 : aStart.mNode->SubtreeRoot();
1596 auto& start =
1597 mPositions.WithEntryHandle(root, [&](auto&& entry) -> Position& {
1598 return entry.OrInsertWith([&] { return Position{root, 0}; });
1601 bool ellipsizedStart = false;
1602 if (auto* text = Text::FromNode(aStart.mNode)) {
1603 if (start.mNode != text && aStart.mOffset &&
1604 aStart.mOffset < text->Length()) {
1605 text->InsertData(aStart.mOffset, kEllipsis, IgnoreErrors());
1606 ellipsizedStart = true;
1610 RefPtr<nsRange> range = nsRange::Create(
1611 start.mNode, start.mOffset, aStart.mNode, aStart.mOffset, IgnoreErrors());
1612 SelectRange(range);
1614 start = aEnd;
1616 // If we added an ellipsis at the start and the end position was relative to
1617 // the same node account for it here.
1618 if (ellipsizedStart && aStart.mNode == aEnd.mNode) {
1619 start.mOffset += kEllipsis.Length();
1622 // If the end is mid text then add an ellipsis.
1623 if (auto* text = Text::FromNode(start.mNode)) {
1624 if (start.mOffset && start.mOffset < text->Length()) {
1625 text->InsertData(start.mOffset, kEllipsis, IgnoreErrors());
1626 start.mOffset += kEllipsis.Length();
1631 void SelectionRangeState::RemoveSelectionFromDocument() {
1632 for (auto& entry : mPositions) {
1633 const Position& pos = entry.GetData();
1634 nsINode* root = entry.GetKey();
1635 RefPtr<nsRange> range = nsRange::Create(
1636 pos.mNode, pos.mOffset, root, root->GetChildCount(), IgnoreErrors());
1637 SelectRange(range);
1639 mSelection->DeleteFromDocument(IgnoreErrors());
1643 * Builds the complement set of ranges and adds those to the selection.
1644 * Deletes all of the nodes contained in the complement set of ranges
1645 * leaving behind only nodes that were originally selected.
1646 * Adds ellipses to a selected node's text if text is truncated by a range.
1647 * This is used to implement the "Print Selection Only" user interface option.
1649 MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteNonSelectedNodes(
1650 Document& aDoc) {
1651 MOZ_ASSERT(aDoc.IsStaticDocument());
1652 const auto* printRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
1653 aDoc.GetProperty(nsGkAtoms::printselectionranges));
1654 if (!printRanges) {
1655 return NS_OK;
1658 PresShell* presShell = aDoc.GetPresShell();
1659 NS_ENSURE_STATE(presShell);
1660 RefPtr<Selection> selection =
1661 presShell->GetCurrentSelection(SelectionType::eNormal);
1662 NS_ENSURE_STATE(selection);
1664 SelectionRangeState state(std::move(selection));
1665 state.SelectComplementOf(*printRanges);
1666 state.RemoveSelectionFromDocument();
1667 return NS_OK;
1670 //-------------------------------------------------------
1671 nsresult nsPrintJob::DoPrint(const UniquePtr<nsPrintObject>& aPO) {
1672 PR_PL(("\n"));
1673 PR_PL(("**************************** %s ****************************\n",
1674 LoggableTypeOfPO(aPO.get())));
1675 PR_PL(("****** In DV::DoPrint PO: %p \n", aPO.get()));
1677 PresShell* poPresShell = aPO->mPresShell;
1678 nsPresContext* poPresContext = aPO->mPresContext;
1680 NS_ASSERTION(poPresContext, "PrintObject has not been reflowed");
1681 NS_ASSERTION(poPresContext->Type() != nsPresContext::eContext_PrintPreview,
1682 "How did this context end up here?");
1684 // Guarantee that mPrt and the objects it owns won't be deleted in this method
1685 // because it might be cleared if other modules called from here may fire
1686 // events, notifying observers and/or listeners.
1687 RefPtr<nsPrintData> printData = mPrt;
1688 if (NS_WARN_IF(!printData)) {
1689 return NS_ERROR_FAILURE;
1693 // Ask the page sequence frame to print all the pages
1694 nsPageSequenceFrame* seqFrame = poPresShell->GetPageSequenceFrame();
1695 MOZ_ASSERT(seqFrame, "no page sequence frame");
1697 // We are done preparing for printing, so we can turn this off
1698 mPreparingForPrint = false;
1700 #ifdef EXTENDED_DEBUG_PRINTING
1701 nsIFrame* rootFrame = poPresShell->GetRootFrame();
1702 if (aPO->PrintingIsEnabled()) {
1703 nsAutoCString docStr;
1704 nsAutoCString urlStr;
1705 GetDocTitleAndURL(aPO, docStr, urlStr);
1706 DumpLayoutData(docStr.get(), urlStr.get(), poPresContext,
1707 printData->mPrintDC, rootFrame, aPO->mDocShell, nullptr);
1709 #endif
1711 if (!mPrintSettings) {
1712 // not sure what to do here!
1713 SetIsPrinting(false);
1714 return NS_ERROR_FAILURE;
1717 nsAutoString docTitleStr;
1718 nsAutoString docURLStr;
1719 GetDisplayTitleAndURL(*aPO->mDocument, mPrintSettings,
1720 DocTitleDefault::eFallback, docTitleStr, docURLStr);
1722 if (!seqFrame) {
1723 SetIsPrinting(false);
1724 return NS_ERROR_FAILURE;
1727 mPageSeqFrame = seqFrame;
1728 seqFrame->StartPrint(poPresContext, mPrintSettings, docTitleStr, docURLStr);
1730 // Schedule Page to Print
1731 PR_PL(("Scheduling Print of PO: %p (%s) \n", aPO.get(),
1732 LoggableTypeOfPO(aPO.get())));
1733 StartPagePrintTimer(aPO);
1736 return NS_OK;
1739 //-------------------------------------------------------
1740 bool nsPrintJob::PrePrintSheet() {
1741 NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
1742 NS_ASSERTION(mPrt, "mPrt is null!");
1744 // Although these should NEVER be nullptr
1745 // This is added insurance, to make sure we don't crash in optimized builds
1746 if (!mPrt || !mPageSeqFrame.IsAlive()) {
1747 return true; // means we are done preparing the sheet.
1750 // Guarantee that mPrt won't be deleted during a call of
1751 // FirePrintingErrorEvent().
1752 RefPtr<nsPrintData> printData = mPrt;
1754 // Ask mPageSeqFrame if the sheet is ready to be printed.
1755 // If the sheet doesn't get printed at all, the |done| will be |true|.
1756 bool done = false;
1757 nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
1758 nsresult rv = pageSeqFrame->PrePrintNextSheet(mPagePrintTimer, &done);
1759 if (NS_FAILED(rv)) {
1760 // ??? ::PrintSheet doesn't set |printData->mIsAborted = true| if
1761 // rv != NS_ERROR_ABORT, but I don't really understand why this should be
1762 // the right thing to do? Shouldn't |printData->mIsAborted| set to true
1763 // all the time if something went wrong?
1764 if (rv != NS_ERROR_ABORT) {
1765 FirePrintingErrorEvent(rv);
1766 printData->mIsAborted = true;
1768 done = true;
1770 return done;
1773 bool nsPrintJob::PrintSheet(nsPrintObject* aPO) {
1774 NS_ASSERTION(aPO, "aPO is null!");
1775 NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
1776 NS_ASSERTION(mPrt, "mPrt is null!");
1778 // Although these should NEVER be nullptr
1779 // This is added insurance, to make sure we don't crash in optimized builds
1780 if (!mPrt || !aPO || !mPageSeqFrame.IsAlive()) {
1781 FirePrintingErrorEvent(NS_ERROR_FAILURE);
1782 return true; // means we are done printing
1785 // Guarantee that mPrt won't be deleted during a call of
1786 // nsPrintData::DoOnProgressChange() which runs some listeners,
1787 // which may clear (& might otherwise destroy).
1788 RefPtr<nsPrintData> printData = mPrt;
1790 PR_PL(("-----------------------------------\n"));
1791 PR_PL(("------ In DV::PrintSheet PO: %p (%s)\n", aPO, LoggableTypeOfPO(aPO)));
1793 if (printData->mIsAborted) {
1794 return true;
1797 nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
1798 const uint32_t sheetIdx = pageSeqFrame->GetCurrentSheetIdx();
1799 const uint32_t numSheets = pageSeqFrame->PrincipalChildList().GetLength();
1801 PR_PL(("****** Printing sheet index %d of %d sheets(s)\n", sheetIdx,
1802 numSheets));
1804 MOZ_ASSERT(numSheets > 0, "print operations must have at least 1 sheet");
1805 MOZ_ASSERT(sheetIdx < numSheets,
1806 "sheetIdx shouldn't be allowed to go out of bounds");
1807 printData->DoOnProgressChange(sheetIdx, numSheets, false, 0);
1808 if (NS_WARN_IF(mPrt != printData)) {
1809 // If current printing is canceled or new print is started, let's return
1810 // true to notify the caller of current printing is done.
1811 return true;
1814 // Print the sheet
1815 // if a print job was cancelled externally, an EndPage or BeginPage may
1816 // fail and the failure is passed back here.
1817 // Returning true means we are done printing.
1819 // When rv == NS_ERROR_ABORT, it means we want out of the
1820 // print job without displaying any error messages
1821 nsresult rv = pageSeqFrame->PrintNextSheet();
1822 if (NS_FAILED(rv)) {
1823 if (rv != NS_ERROR_ABORT) {
1824 FirePrintingErrorEvent(rv);
1825 printData->mIsAborted = true;
1827 return true;
1830 pageSeqFrame->DoPageEnd();
1832 // If we just printed the final sheet (the one with index "numSheets-1"),
1833 // then we're done!
1834 return (sheetIdx == numSheets - 1);
1837 void nsPrintJob::PageDone(nsresult aResult) {
1838 MOZ_ASSERT(mIsDoingPrinting);
1840 // mPagePrintTimer might be released during RemotePrintFinished, keep a
1841 // reference here to make sure it lives long enough.
1842 RefPtr<nsPagePrintTimer> timer = mPagePrintTimer;
1843 timer->RemotePrintFinished();
1846 //-----------------------------------------------------------------
1847 //-- Done: Printing Methods
1848 //-----------------------------------------------------------------
1850 //-----------------------------------------------------------------
1851 //-- Section: Misc Support Methods
1852 //-----------------------------------------------------------------
1854 //---------------------------------------------------------------------
1855 void nsPrintJob::SetIsPrinting(bool aIsPrinting) {
1856 mIsDoingPrinting = aIsPrinting;
1857 if (aIsPrinting) {
1858 mPreparingForPrint = true;
1862 //---------------------------------------------------------------------
1863 void nsPrintJob::SetIsPrintPreview(bool aIsPrintPreview) {
1864 mCreatedForPrintPreview = aIsPrintPreview;
1866 if (mDocViewerPrint) {
1867 mDocViewerPrint->SetIsPrintPreview(aIsPrintPreview);
1871 //-------------------------------------------------------
1872 bool nsPrintJob::DonePrintingSheets(nsPrintObject* aPO, nsresult aResult) {
1873 // NS_ASSERTION(aPO, "Pointer is null!");
1874 PR_PL(("****** In DV::DonePrintingSheets PO: %p (%s)\n", aPO,
1875 aPO ? LoggableTypeOfPO(aPO) : ""));
1877 // If there is a pageSeqFrame, make sure there are no more printCanvas active
1878 // that might call |Notify| on the pagePrintTimer after things are cleaned up
1879 // and printing was marked as being done.
1880 if (mPageSeqFrame.IsAlive()) {
1881 nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
1882 pageSeqFrame->ResetPrintCanvasList();
1885 // Guarantee that mPrt and mPrintObject won't be deleted during a
1886 // call of PrintDocContent() and FirePrintCompletionEvent().
1887 RefPtr<nsPrintData> printData = mPrt;
1889 if (aPO && !printData->mIsAborted) {
1890 aPO->mHasBeenPrinted = true;
1891 nsresult rv;
1892 bool didPrint = PrintDocContent(mPrintObject, rv);
1893 if (NS_SUCCEEDED(rv) && didPrint) {
1894 PR_PL(
1895 ("****** In DV::DonePrintingSheets PO: %p (%s) didPrint:%s (Not Done "
1896 "Printing)\n",
1897 aPO, LoggableTypeOfPO(aPO), PRT_YESNO(didPrint)));
1898 return false;
1902 if (NS_SUCCEEDED(aResult)) {
1903 FirePrintCompletionEvent();
1904 // XXX mPrt may be cleared or replaced with new instance here.
1905 // However, the following methods will clean up with new mPrt or will
1906 // do nothing due to no proper nsPrintData instance.
1909 SetIsPrinting(false);
1911 // Release reference to mPagePrintTimer; the timer object destroys itself
1912 // after this returns true
1913 DisconnectPagePrintTimer();
1915 return true;
1918 //-------------------------------------------------------
1919 nsresult nsPrintJob::EnablePOsForPrinting() {
1920 // Guarantee that mPrt and the objects it owns won't be deleted.
1921 RefPtr<nsPrintData> printData = mPrt;
1923 // NOTE: All POs have been "turned off" for printing
1924 // this is where we decided which POs get printed.
1926 if (!printData || !mPrintSettings) {
1927 return NS_ERROR_FAILURE;
1930 PR_PL(("\n"));
1931 PR_PL(("********* nsPrintJob::EnablePOsForPrinting *********\n"));
1933 if (!mPrintSettings->GetPrintSelectionOnly()) {
1934 mPrintObject->EnablePrinting(true);
1935 return NS_OK;
1938 // This means we are either printing a selected iframe or
1939 // we are printing the current selection.
1940 NS_ENSURE_STATE(!mDisallowSelectionPrint && mSelectionRoot);
1942 // If mSelectionRoot is a selected iframe without a selection, then just
1943 // enable normally from that point.
1944 if (mSelectionRoot->mParent && !mSelectionRoot->HasSelection()) {
1945 mSelectionRoot->EnablePrinting(true);
1946 } else {
1947 // Otherwise, only enable nsPrintObjects that have a selection.
1948 mSelectionRoot->EnablePrintingSelectionOnly();
1950 return NS_OK;
1953 //-----------------------------------------------------------------
1954 //-- Done: Misc Support Methods
1955 //-----------------------------------------------------------------
1957 //-----------------------------------------------------------------
1958 //-- Section: Finishing up or Cleaning up
1959 //-----------------------------------------------------------------
1961 //-----------------------------------------------------------------
1962 nsresult nsPrintJob::FinishPrintPreview() {
1963 nsresult rv = NS_OK;
1965 #ifdef NS_PRINT_PREVIEW
1967 // If mPrt is null we've already finished with print preview. If mPrt is not
1968 // null but mIsCreatingPrintPreview is false FinishPrintPreview must have
1969 // already failed due to DocumentReadyForPrinting failing.
1970 if (!mPrt || !mIsCreatingPrintPreview) {
1971 return rv;
1974 rv = DocumentReadyForPrinting();
1976 // Note that this method may be called while the instance is being
1977 // initialized. Some methods which initialize the instance (e.g.,
1978 // DoCommonPrint) may need to stop initializing and return error if
1979 // this is called. Therefore it's important to set mIsCreatingPrintPreview
1980 // state to false here. If you need to stop setting that here, you need to
1981 // keep them being able to check whether the owner stopped using this
1982 // instance.
1983 mIsCreatingPrintPreview = false;
1985 // mPrt may be cleared during a call of nsPrintData::OnEndPrinting()
1986 // because that method invokes some arbitrary listeners.
1987 // TODO(dshin): Does any listener attach to print preview? Doesn't seem like
1988 // we call matching `OnStartPrinting()` for previews.
1989 RefPtr<nsPrintData> printData = mPrt;
1990 if (NS_FAILED(rv)) {
1991 printData->OnEndPrinting();
1993 return rv;
1996 if (mPrintPreviewCallback) {
1997 const bool hasSelection = !mDisallowSelectionPrint && mSelectionRoot;
1999 Maybe<float> pageWidth;
2000 Maybe<float> pageHeight;
2001 if (mMaybeCSSPageSize) {
2002 nsSize cssPageSize = *mMaybeCSSPageSize;
2003 pageWidth = Some(float(cssPageSize.width) / float(AppUnitsPerCSSInch()));
2004 pageHeight =
2005 Some(float(cssPageSize.height) / float(AppUnitsPerCSSInch()));
2008 mPrintPreviewCallback(PrintPreviewResultInfo(
2009 GetPrintPreviewNumSheets(), GetRawNumPages(), GetIsEmpty(),
2010 hasSelection, hasSelection && mPrintObject->HasSelection(),
2011 mMaybeCSSPageLandscape, pageWidth, pageHeight));
2012 mPrintPreviewCallback = nullptr;
2015 // At this point we are done preparing everything
2016 // before it is to be created
2018 printData->OnEndPrinting();
2020 #endif // NS_PRINT_PREVIEW
2022 return NS_OK;
2025 //-----------------------------------------------------------------
2026 //-- Done: Finishing up or Cleaning up
2027 //-----------------------------------------------------------------
2029 /*=============== Timer Related Code ======================*/
2030 nsresult nsPrintJob::StartPagePrintTimer(const UniquePtr<nsPrintObject>& aPO) {
2031 if (!mPagePrintTimer) {
2032 // Get the delay time in between the printing of each page
2033 // this gives the user more time to press cancel
2034 int32_t printPageDelay = mPrintSettings->GetPrintPageDelay();
2036 nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
2037 NS_ENSURE_TRUE(viewer, NS_ERROR_FAILURE);
2038 nsCOMPtr<Document> doc = viewer->GetDocument();
2039 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2041 mPagePrintTimer =
2042 new nsPagePrintTimer(this, mDocViewerPrint, doc, printPageDelay);
2044 if (mRemotePrintJob) {
2045 mRemotePrintJob->SetPagePrintTimer(mPagePrintTimer);
2046 mRemotePrintJob->SetPrintJob(this);
2050 return mPagePrintTimer->Start(aPO.get());
2053 //---------------------------------------------------------------
2054 //-- PLEvent Notification
2055 //---------------------------------------------------------------
2056 class nsPrintCompletionEvent : public Runnable {
2057 public:
2058 explicit nsPrintCompletionEvent(nsIDocumentViewerPrint* docViewerPrint)
2059 : mozilla::Runnable("nsPrintCompletionEvent"),
2060 mDocViewerPrint(docViewerPrint) {
2061 NS_ASSERTION(mDocViewerPrint, "mDocViewerPrint is null.");
2064 NS_IMETHOD Run() override {
2065 if (mDocViewerPrint) {
2066 mDocViewerPrint->OnDonePrinting();
2068 return NS_OK;
2071 private:
2072 nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint;
2075 //-----------------------------------------------------------
2076 void nsPrintJob::FirePrintCompletionEvent() {
2077 MOZ_ASSERT(NS_IsMainThread());
2078 nsCOMPtr<nsIRunnable> event = new nsPrintCompletionEvent(mDocViewerPrint);
2079 nsCOMPtr<nsIDocumentViewer> viewer = do_QueryInterface(mDocViewerPrint);
2080 NS_ENSURE_TRUE_VOID(viewer);
2081 nsCOMPtr<Document> doc = viewer->GetDocument();
2082 NS_ENSURE_TRUE_VOID(doc);
2083 NS_ENSURE_SUCCESS_VOID(doc->Dispatch(event.forget()));
2086 void nsPrintJob::DisconnectPagePrintTimer() {
2087 if (mPagePrintTimer) {
2088 mPagePrintTimer->Disconnect();
2089 mPagePrintTimer = nullptr;
2093 //---------------------------------------------------------------
2094 //---------------------------------------------------------------
2095 //-- Debug helper routines
2096 //---------------------------------------------------------------
2097 //---------------------------------------------------------------
2098 #if defined(XP_WIN) && defined(EXTENDED_DEBUG_PRINTING)
2099 # include <windows.h>
2100 # include <process.h>
2101 # include <direct.h>
2103 # define MY_FINDFIRST(a, b) FindFirstFile(a, b)
2104 # define MY_FINDNEXT(a, b) FindNextFile(a, b)
2105 # define ISDIR(a) (a.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
2106 # define MY_FINDCLOSE(a) FindClose(a)
2107 # define MY_FILENAME(a) a.cFileName
2108 # define MY_FILESIZE(a) (a.nFileSizeHigh * MAXDWORD) + a.nFileSizeLow
2110 int RemoveFilesInDir(const char* aDir) {
2111 WIN32_FIND_DATA data_ptr;
2112 HANDLE find_handle;
2114 char path[MAX_PATH];
2116 strcpy(path, aDir);
2118 // Append slash to the end of the directory names if not there
2119 if (path[strlen(path) - 1] != '\\') strcat(path, "\\");
2121 char findPath[MAX_PATH];
2122 strcpy(findPath, path);
2123 strcat(findPath, "*.*");
2125 find_handle = MY_FINDFIRST(findPath, &data_ptr);
2127 if (find_handle != INVALID_HANDLE_VALUE) {
2128 do {
2129 if (ISDIR(data_ptr) && (stricmp(MY_FILENAME(data_ptr), ".")) &&
2130 (stricmp(MY_FILENAME(data_ptr), ".."))) {
2131 // skip
2132 } else if (!ISDIR(data_ptr)) {
2133 if (!strncmp(MY_FILENAME(data_ptr), "print_dump", 10)) {
2134 char fileName[MAX_PATH];
2135 strcpy(fileName, aDir);
2136 strcat(fileName, "\\");
2137 strcat(fileName, MY_FILENAME(data_ptr));
2138 printf("Removing %s\n", fileName);
2139 remove(fileName);
2142 } while (MY_FINDNEXT(find_handle, &data_ptr));
2143 MY_FINDCLOSE(find_handle);
2145 return TRUE;
2147 #endif
2149 #ifdef EXTENDED_DEBUG_PRINTING
2151 /** ---------------------------------------------------
2152 * Dumps Frames for Printing
2154 static void RootFrameList(nsPresContext* aPresContext, FILE* out,
2155 const char* aPrefix) {
2156 if (!aPresContext || !out) return;
2158 if (PresShell* presShell = aPresContext->GetPresShell()) {
2159 nsIFrame* frame = presShell->GetRootFrame();
2160 if (frame) {
2161 frame->List(out, aPrefix);
2166 /** ---------------------------------------------------
2167 * Dumps Frames for Printing
2169 static void DumpFrames(FILE* out, nsPresContext* aPresContext,
2170 gfxContext* aRendContext, nsIFrame* aFrame,
2171 int32_t aLevel) {
2172 NS_ASSERTION(out, "Pointer is null!");
2173 NS_ASSERTION(aPresContext, "Pointer is null!");
2174 NS_ASSERTION(aRendContext, "Pointer is null!");
2175 NS_ASSERTION(aFrame, "Pointer is null!");
2177 nsIFrame* child = aFrame->PrincipalChildList().FirstChild();
2178 while (child != nullptr) {
2179 for (int32_t i = 0; i < aLevel; i++) {
2180 fprintf(out, " ");
2182 nsAutoString tmp;
2183 child->GetFrameName(tmp);
2184 fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
2185 bool isSelected;
2186 if (child->IsVisibleForPainting()) {
2187 fprintf(out, " %p %s", child, isSelected ? "VIS" : "UVS");
2188 nsRect rect = child->GetRect();
2189 fprintf(out, "[%d,%d,%d,%d] ", rect.x, rect.y, rect.width, rect.height);
2190 fprintf(out, "v: %p ", (void*)child->GetView());
2191 fprintf(out, "\n");
2192 DumpFrames(out, aPresContext, aRendContext, child, aLevel + 1);
2193 child = child->GetNextSibling();
2198 /** ---------------------------------------------------
2199 * Dumps the Views from the DocShell
2201 static void DumpViews(nsIDocShell* aDocShell, FILE* out) {
2202 NS_ASSERTION(aDocShell, "Pointer is null!");
2203 NS_ASSERTION(out, "Pointer is null!");
2205 if (nullptr != aDocShell) {
2206 fprintf(out, "docshell=%p \n", aDocShell);
2207 if (PresShell* presShell = aDocShell->GetPresShell()) {
2208 nsViewManager* vm = presShell->GetViewManager();
2209 if (vm) {
2210 nsView* root = vm->GetRootView();
2211 if (root) {
2212 root->List(out);
2215 } else {
2216 fputs("null pres shell\n", out);
2219 // dump the views of the sub documents
2220 int32_t i, n;
2221 BrowsingContext* bc = nsDocShell::Cast(aDocShell)->GetBrowsingContext();
2222 for (auto& child : bc->Children()) {
2223 if (auto childDS = child->GetDocShell()) {
2224 DumpViews(childAsShell, out);
2230 /** ---------------------------------------------------
2231 * Dumps the Views and Frames
2233 void DumpLayoutData(const char* aTitleStr, const char* aURLStr,
2234 nsPresContext* aPresContext, nsDeviceContext* aDC,
2235 nsIFrame* aRootFrame, nsIDocShell* aDocShell,
2236 FILE* aFD = nullptr) {
2237 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2238 return;
2241 if (aPresContext == nullptr || aDC == nullptr) {
2242 return;
2245 # ifdef NS_PRINT_PREVIEW
2246 if (aPresContext->Type() == nsPresContext::eContext_PrintPreview) {
2247 return;
2249 # endif
2251 NS_ASSERTION(aRootFrame, "Pointer is null!");
2252 NS_ASSERTION(aDocShell, "Pointer is null!");
2254 // Dump all the frames and view to a a file
2255 char filename[256];
2256 sprintf(filename, "print_dump_layout_%d.txt", gDumpLOFileNameCnt++);
2257 FILE* fd = aFD ? aFD : fopen(filename, "w");
2258 if (fd) {
2259 fprintf(fd, "Title: %s\n", aTitleStr ? aTitleStr : "");
2260 fprintf(fd, "URL: %s\n", aURLStr ? aURLStr : "");
2261 fprintf(fd, "--------------- Frames ----------------\n");
2262 fprintf(fd, "--------------- Frames ----------------\n");
2263 // RefPtr<gfxContext> renderingContext =
2264 // aDC->CreateRenderingContext();
2265 RootFrameList(aPresContext, fd, "");
2266 // DumpFrames(fd, aPresContext, renderingContext, aRootFrame, 0);
2267 fprintf(fd, "---------------------------------------\n\n");
2268 fprintf(fd, "--------------- Views From Root Frame----------------\n");
2269 nsView* v = aRootFrame->GetView();
2270 if (v) {
2271 v->List(fd);
2272 } else {
2273 printf("View is null!\n");
2275 if (aDocShell) {
2276 fprintf(fd, "--------------- All Views ----------------\n");
2277 DumpViews(aDocShell, fd);
2278 fprintf(fd, "---------------------------------------\n\n");
2280 if (aFD == nullptr) {
2281 fclose(fd);
2286 //-------------------------------------------------------------
2287 static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList) {
2288 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2289 return;
2292 PR_PL(("Doc List\n***************************************************\n"));
2293 PR_PL(
2294 ("T P A H PO DocShell Seq Page Root Page# "
2295 "Rect\n"));
2296 for (nsPrintObject* po : aDocList) {
2297 NS_ASSERTION(po, "nsPrintObject can't be null!");
2298 nsIFrame* rootFrame = nullptr;
2299 if (po->mPresShell) {
2300 rootFrame = po->mPresShell->GetRootFrame();
2301 while (rootFrame != nullptr) {
2302 nsPageSequenceFrame* sqf = do_QueryFrame(rootFrame);
2303 if (sqf) {
2304 break;
2306 rootFrame = rootFrame->PrincipalChildList().FirstChild();
2310 PR_PL(("%s %d %d %p %p %p\n", ShortLoggableTypeOfPO(po),
2311 po->PrintingIsEnabled(), po->mHasBeenPrinted, po,
2312 po->mDocShell.get(), rootFrame));
2316 //-------------------------------------------------------------
2317 static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel, FILE* aFD) {
2318 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2319 return;
2322 NS_ASSERTION(aPO, "Pointer is null!");
2324 FILE* fd = aFD ? aFD : stdout;
2325 if (aLevel == 0) {
2326 fprintf(fd,
2327 "DocTree\n***************************************************\n");
2328 fprintf(fd, "T PO DocShell Seq Page Page# Rect\n");
2330 for (const auto& po : aPO->mKids) {
2331 NS_ASSERTION(po, "nsPrintObject can't be null!");
2332 for (int32_t k = 0; k < aLevel; k++) fprintf(fd, " ");
2333 fprintf(fd, "%s %p %p\n", ShortLoggableTypeOfPO(po.get()), po.get(),
2334 po->mDocShell.get());
2338 //-------------------------------------------------------------
2339 static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
2340 nsACString& aDocStr, nsACString& aURLStr) {
2341 nsAutoString docTitleStr;
2342 nsAutoString docURLStr;
2343 GetDocumentTitleAndURL(aPO->mDocument, docTitleStr, docURLStr);
2344 CopyUTF16toUTF8(docTitleStr, aDocStr);
2345 CopyUTF16toUTF8(docURLStr, aURLStr);
2348 //-------------------------------------------------------------
2349 static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
2350 nsDeviceContext* aDC, int aLevel,
2351 FILE* aFD) {
2352 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2353 return;
2356 NS_ASSERTION(aPO, "Pointer is null!");
2357 NS_ASSERTION(aDC, "Pointer is null!");
2359 FILE* fd = nullptr;
2360 if (aLevel == 0) {
2361 fd = fopen("tree_layout.txt", "w");
2362 fprintf(fd,
2363 "DocTree\n***************************************************\n");
2364 fprintf(fd, "***************************************************\n");
2365 fprintf(fd, "T PO DocShell Seq Page Page# Rect\n");
2366 } else {
2367 fd = aFD;
2369 if (fd) {
2370 nsIFrame* rootFrame = nullptr;
2371 if (aPO->mPresShell) {
2372 rootFrame = aPO->mPresShell->GetRootFrame();
2374 for (int32_t k = 0; k < aLevel; k++) fprintf(fd, " ");
2375 fprintf(fd, "%s %p %p\n", ShortLoggableTypeOfPO(aPO.get()), aPO.get(),
2376 aPO->mDocShell.get());
2377 if (aPO->PrintingIsEnabled()) {
2378 nsAutoCString docStr;
2379 nsAutoCString urlStr;
2380 GetDocTitleAndURL(aPO, docStr, urlStr);
2381 DumpLayoutData(docStr.get(), urlStr.get(), aPO->mPresContext, aDC,
2382 rootFrame, aPO->mDocShell, fd);
2384 fprintf(fd, "<***************************************************>\n");
2386 for (const auto& po : aPO->mKids) {
2387 NS_ASSERTION(po, "nsPrintObject can't be null!");
2388 DumpPrintObjectsTreeLayout(po, aDC, aLevel + 1, fd);
2391 if (aLevel == 0 && fd) {
2392 fclose(fd);
2396 //-------------------------------------------------------------
2397 static void DumpPrintObjectsListStart(
2398 const char* aStr, const nsTArray<nsPrintObject*>& aDocList) {
2399 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2400 return;
2403 NS_ASSERTION(aStr, "Pointer is null!");
2405 PR_PL(("%s\n", aStr));
2406 DumpPrintObjectsList(aDocList);
2409 #endif
2411 //---------------------------------------------------------------
2412 //---------------------------------------------------------------
2413 //-- End of debug helper routines
2414 //---------------------------------------------------------------