Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / printing / nsPrintJob.cpp
blob490be9cb89fbfb83dba7694088f7870684200b13
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/HTMLCanvasElement.h"
24 #include "mozilla/dom/ScriptSettings.h"
25 #include "mozilla/IntegerRange.h"
26 #include "mozilla/PresShell.h"
27 #include "mozilla/PresShellInlines.h"
28 #include "mozilla/StaticPrefs_print.h"
29 #include "mozilla/Telemetry.h"
30 #include "mozilla/Try.h"
31 #include "nsIBrowserChild.h"
32 #include "nsIOService.h"
33 #include "nsIScriptGlobalObject.h"
34 #include "nsIStringBundle.h"
35 #include "nsPIDOMWindow.h"
36 #include "nsPrintData.h"
37 #include "nsPrintObject.h"
38 #include "nsIDocShell.h"
39 #include "nsIURI.h"
40 #include "nsITextToSubURI.h"
41 #include "nsError.h"
43 #include "nsView.h"
44 #include <algorithm>
46 // Print Options
47 #include "nsIPrintSettings.h"
48 #include "nsIPrintSettingsService.h"
49 #include "nsGkAtoms.h"
50 #include "nsXPCOM.h"
52 static const char sPrintSettingsServiceContractID[] =
53 "@mozilla.org/gfx/printsettings-service;1";
55 // Printing Timer
56 #include "nsPagePrintTimer.h"
58 // FrameSet
59 #include "mozilla/dom/Document.h"
60 #include "mozilla/dom/DocumentInlines.h"
62 // Misc
63 #include "gfxContext.h"
64 #include "mozilla/gfx/DrawEventRecorder.h"
65 #include "mozilla/layout/RemotePrintJobChild.h"
66 #include "nsISupportsUtils.h"
67 #include "nsIScriptContext.h"
68 #include "nsComponentManagerUtils.h"
69 #include "mozilla/Preferences.h"
70 #include "mozilla/PresShell.h"
71 #include "Text.h"
73 #include "nsIDeviceContextSpec.h"
74 #include "nsDeviceContextSpecProxy.h"
75 #include "nsViewManager.h"
77 #include "nsPageSequenceFrame.h"
78 #include "nsIInterfaceRequestor.h"
79 #include "nsIInterfaceRequestorUtils.h"
80 #include "nsIWebBrowserChrome.h"
81 #include "mozilla/ReflowInput.h"
82 #include "nsIContentViewer.h"
83 #include "nsIDocumentViewerPrint.h"
85 #include "nsFocusManager.h"
86 #include "nsRange.h"
87 #include "mozilla/Components.h"
88 #include "mozilla/dom/Element.h"
89 #include "mozilla/dom/HTMLFrameElement.h"
90 #include "mozilla/ServoStyleSet.h"
92 using namespace mozilla;
93 using namespace mozilla::dom;
95 //-----------------------------------------------------
96 // PR LOGGING
97 #include "mozilla/Logging.h"
99 #ifdef DEBUG
100 // PR_LOGGING is force to always be on (even in release builds)
101 // but we only want some of it on,
102 // #define EXTENDED_DEBUG_PRINTING
103 #endif
105 // this log level turns on the dumping of each document's layout info
106 #define DUMP_LAYOUT_LEVEL (static_cast<mozilla::LogLevel>(9))
108 #ifndef PR_PL
109 static mozilla::LazyLogModule gPrintingLog("printing");
111 # define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
112 #endif
114 #ifdef EXTENDED_DEBUG_PRINTING
115 static uint32_t gDumpFileNameCnt = 0;
116 static uint32_t gDumpLOFileNameCnt = 0;
117 #endif
119 #define PRT_YESNO(_p) ((_p) ? "YES" : "NO")
121 inline const char* LoggableTypeOfPO(const nsPrintObject* aPO) {
122 // TODO(dholbert): These strings used to represent actual enum values, but
123 // the enum type has been removed. We could replace them with more
124 // descriptive names, if anyone uses this logging and cares to do so.
125 return aPO->mParent ? "eIFrame" : "eDoc";
128 inline const char* ShortLoggableTypeOfPO(const nsPrintObject* aPO) {
129 return aPO->mParent ? "IF" : "DC";
132 // This processes the selection on aOrigDoc and creates an inverted selection on
133 // aDoc, which it then deletes. If the start or end of the inverted selection
134 // ranges occur in text nodes then an ellipsis is added.
135 static nsresult DeleteNonSelectedNodes(Document& aDoc);
137 #ifdef EXTENDED_DEBUG_PRINTING
138 // Forward Declarations
139 static void DumpPrintObjectsListStart(const char* aStr,
140 const nsTArray<nsPrintObject*>& aDocList);
141 static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel = 0,
142 FILE* aFD = nullptr);
143 static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
144 nsDeviceContext* aDC, int aLevel = 0,
145 FILE* aFD = nullptr);
147 # define DUMP_DOC_LIST(_title) \
148 DumpPrintObjectsListStart((_title), mPrintDocList);
149 # define DUMP_DOC_TREE DumpPrintObjectsTree(mPrintObject.get());
150 # define DUMP_DOC_TREELAYOUT \
151 DumpPrintObjectsTreeLayout(mPrintObject, mPrt->mPrintDC);
152 #else
153 # define DUMP_DOC_LIST(_title)
154 # define DUMP_DOC_TREE
155 # define DUMP_DOC_TREELAYOUT
156 #endif
158 // -------------------------------------------------------
159 // Helpers
160 // -------------------------------------------------------
163 * Build a tree of nsPrintObjects under aPO. It also appends a (depth first)
164 * flat list of all the nsPrintObjects created to mPrintDocList. If
165 * one of the nsPrintObject's document is the focused document, then the print
166 * object is set as mSelectionRoot.
167 * @param aParentPO The parent nsPrintObject to populate, must not be null.
169 void nsPrintJob::BuildNestedPrintObjects(
170 const UniquePtr<nsPrintObject>& aParentPO) {
171 MOZ_ASSERT(aParentPO);
173 // If aParentPO is for an iframe and its original document was the document
174 // that had focus then always set as the selection root.
175 if (aParentPO->mParent &&
176 aParentPO->mDocument->GetProperty(nsGkAtoms::printisfocuseddoc)) {
177 mSelectionRoot = aParentPO.get();
178 } else if (!mSelectionRoot && aParentPO->HasSelection()) {
179 // If there is no focused iframe but there is a selection in one or more
180 // frames then we want to set the root nsPrintObject as the focus root so
181 // that later EnablePrintingSelectionOnly can search for and enable all
182 // nsPrintObjects containing selections.
183 mSelectionRoot = mPrintObject.get();
186 for (auto& bc : aParentPO->mDocShell->GetBrowsingContext()->Children()) {
187 nsCOMPtr<nsIDocShell> docShell = bc->GetDocShell();
188 if (!docShell) {
189 if (auto* cc = dom::ContentChild::GetSingleton()) {
190 nsCOMPtr<nsIPrintSettingsService> printSettingsService =
191 do_GetService(sPrintSettingsServiceContractID);
192 embedding::PrintData printData;
193 printSettingsService->SerializeToPrintData(mPrintSettings, &printData);
194 Unused << cc->SendUpdateRemotePrintSettings(bc, printData);
196 continue;
199 RefPtr<Document> doc = docShell->GetDocument();
200 MOZ_DIAGNOSTIC_ASSERT(doc);
201 // We might find non-static documents here if the fission remoteness change
202 // hasn't happened / finished yet. In that case, just skip them, the same
203 // way we do for remote frames above.
204 MOZ_DIAGNOSTIC_ASSERT(doc->IsStaticDocument() || doc->IsInitialDocument());
205 if (!doc || !doc->IsStaticDocument()) {
206 continue;
209 // Note: docShell and doc are known-non-null at this point; they've been
210 // null-checked above (with null leading to 'continue' statements).
211 auto childPO = MakeUnique<nsPrintObject>(*docShell, *doc, aParentPO.get());
213 mPrintDocList.AppendElement(childPO.get());
214 BuildNestedPrintObjects(childPO);
215 aParentPO->mKids.AppendElement(std::move(childPO));
219 static nsresult GetDefaultPrintSettings(nsIPrintSettings** aSettings) {
220 *aSettings = nullptr;
222 nsresult rv = NS_ERROR_FAILURE;
223 nsCOMPtr<nsIPrintSettingsService> printSettingsService =
224 do_GetService(sPrintSettingsServiceContractID, &rv);
225 NS_ENSURE_SUCCESS(rv, rv);
227 return printSettingsService->GetDefaultPrintSettingsForPrinting(aSettings);
230 //-------------------------------------------------------
232 NS_IMPL_ISUPPORTS(nsPrintJob, nsIWebProgressListener, nsISupportsWeakReference)
234 //-------------------------------------------------------
235 nsPrintJob::~nsPrintJob() {
236 Destroy(); // for insurance
237 DisconnectPagePrintTimer();
240 bool nsPrintJob::CheckBeforeDestroy() const { return mPreparingForPrint; }
242 //-------------------------------------------------------
243 void nsPrintJob::Destroy() {
244 if (mIsDestroying) {
245 return;
247 mIsDestroying = true;
249 DestroyPrintingData();
251 mDocViewerPrint = nullptr;
254 //-------------------------------------------------------
255 void nsPrintJob::DestroyPrintingData() {
256 mPrintObject = nullptr;
257 mPrt = nullptr;
260 nsPrintJob::nsPrintJob(nsIDocumentViewerPrint& aDocViewerPrint,
261 nsIDocShell& aDocShell, Document& aOriginalDoc,
262 float aScreenDPI)
263 : mDocViewerPrint(&aDocViewerPrint),
264 mDocShell(do_GetWeakReference(&aDocShell)),
265 mScreenDPI(aScreenDPI) {
266 // Any state that we need from aOriginalDoc must be fetched and stored
267 // here, since the document that the user selected to print may mutate
268 // across consecutive PrintPreview() calls.
270 Element* root = aOriginalDoc.GetRootElement();
271 mDisallowSelectionPrint =
272 root && root->HasAttr(nsGkAtoms::mozdisallowselectionprint);
275 //-----------------------------------------------------------------
276 std::tuple<nsPageSequenceFrame*, int32_t>
277 nsPrintJob::GetSeqFrameAndCountSheets() const {
278 if (NS_WARN_IF(!mPrt)) {
279 return {nullptr, 0};
282 const nsPrintObject* po = mPrintObject.get();
283 if (NS_WARN_IF(!po)) {
284 return {nullptr, 0};
287 // This is sometimes incorrectly called before the pres shell has been created
288 // (bug 1141756). MOZ_DIAGNOSTIC_ASSERT so we'll still see the crash in
289 // Nightly/Aurora in case the other patch fixes this.
290 if (!po->mPresShell) {
291 MOZ_DIAGNOSTIC_ASSERT(
292 false, "GetSeqFrameAndCountSheets needs a non-null pres shell");
293 return {nullptr, 0};
296 nsPageSequenceFrame* seqFrame = po->mPresShell->GetPageSequenceFrame();
297 if (!seqFrame) {
298 return {nullptr, 0};
301 // count the total number of sheets
302 return {seqFrame, seqFrame->PrincipalChildList().GetLength()};
305 // Foward decl for Debug Helper Functions
306 #ifdef EXTENDED_DEBUG_PRINTING
307 # ifdef XP_WIN
308 static int RemoveFilesInDir(const char* aDir);
309 # endif
310 static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
311 nsACString& aDocStr, nsACString& aURLStr);
312 static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel, FILE* aFD);
313 static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList);
314 static void RootFrameList(nsPresContext* aPresContext, FILE* out,
315 const char* aPrefix);
316 static void DumpViews(nsIDocShell* aDocShell, FILE* out);
317 static void DumpLayoutData(const char* aTitleStr, const char* aURLStr,
318 nsPresContext* aPresContext, nsDeviceContext* aDC,
319 nsIFrame* aRootFrame, nsIDocShell* aDocShell,
320 FILE* aFD);
321 #endif
323 //--------------------------------------------------------------------------------
325 nsresult nsPrintJob::CommonPrint(bool aIsPrintPreview,
326 nsIPrintSettings* aPrintSettings,
327 nsIWebProgressListener* aWebProgressListener,
328 Document& aSourceDoc) {
329 // Callers must hold a strong reference to |this| to ensure that we stay
330 // alive for the duration of this method, because our main owning reference
331 // (on nsDocumentViewer) might be cleared during this function (if we cause
332 // script to run and it cancels the print operation).
334 nsresult rv = DoCommonPrint(aIsPrintPreview, aPrintSettings,
335 aWebProgressListener, aSourceDoc);
336 if (NS_FAILED(rv)) {
337 if (aIsPrintPreview) {
338 mIsCreatingPrintPreview = false;
339 SetIsPrintPreview(false);
340 } else {
341 SetIsPrinting(false);
343 if (rv != NS_ERROR_ABORT && rv != NS_ERROR_OUT_OF_MEMORY) {
344 FirePrintingErrorEvent(rv);
346 DestroyPrintingData();
349 return rv;
352 nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview,
353 nsIPrintSettings* aPrintSettings,
354 nsIWebProgressListener* aWebProgressListener,
355 Document& aDoc) {
356 MOZ_ASSERT(aDoc.IsStaticDocument());
358 nsresult rv;
360 // Grab the new instance with local variable to guarantee that it won't be
361 // deleted during this method.
362 // Note: Methods we call early below rely on mPrt being set.
363 mPrt = new nsPrintData(aIsPrintPreview ? nsPrintData::eIsPrintPreview
364 : nsPrintData::eIsPrinting);
365 RefPtr<nsPrintData> printData = mPrt;
367 if (aIsPrintPreview) {
368 mIsCreatingPrintPreview = true;
369 SetIsPrintPreview(true);
370 } else {
371 SetIsPrinting(true);
374 if (aWebProgressListener) {
375 printData->mPrintProgressListeners.AppendObject(aWebProgressListener);
377 if (mRemotePrintJob) {
378 // If we have a RemotePrintJob add it to the print progress listeners,
379 // so it can forward to the parent.
380 printData->mPrintProgressListeners.AppendElement(mRemotePrintJob);
383 // Get the docshell for this documentviewer
384 nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell, &rv));
385 NS_ENSURE_SUCCESS(rv, rv);
387 // if they don't pass in a PrintSettings, then get the Global PS
388 mPrintSettings = aPrintSettings;
389 if (!mPrintSettings) {
390 MOZ_TRY(GetDefaultPrintSettings(getter_AddRefs(mPrintSettings)));
394 nsAutoScriptBlocker scriptBlocker;
395 // Note: docShell is implicitly non-null via do_QueryReferent necessarily
396 // having succeeded (if we got here).
397 mPrintObject = MakeUnique<nsPrintObject>(*docShell, aDoc);
398 mPrintDocList.AppendElement(mPrintObject.get());
400 BuildNestedPrintObjects(mPrintObject);
403 // The nsAutoScriptBlocker above will now have been destroyed, which may
404 // cause our print/print-preview operation to finish. In this case, we
405 // should immediately return an error code so that the root caller knows
406 // it shouldn't continue to do anything with this instance.
407 if (mIsDestroying) {
408 return NS_ERROR_FAILURE;
411 // XXX This isn't really correct...
412 if (!mPrintObject->mDocument || !mPrintObject->mDocument->GetRootElement())
413 return NS_ERROR_GFX_PRINTER_STARTDOC;
415 mPrintSettings->GetShrinkToFit(&mShrinkToFit);
417 nsCOMPtr<nsIDeviceContextSpec> devspec;
418 if (XRE_IsContentProcess()) {
419 devspec = new nsDeviceContextSpecProxy(mRemotePrintJob);
420 } else {
421 devspec = do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv);
422 NS_ENSURE_SUCCESS(rv, rv);
425 bool printSilently = false;
426 mPrintSettings->GetPrintSilent(&printSilently);
427 if (StaticPrefs::print_always_print_silent()) {
428 printSilently = true;
431 if (mIsDoingPrinting && printSilently) {
432 Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_SILENT_PRINT, 1);
435 MOZ_TRY(devspec->Init(mPrintSettings, mIsCreatingPrintPreview));
437 printData->mPrintDC = new nsDeviceContext();
438 MOZ_TRY(printData->mPrintDC->InitForPrinting(devspec));
440 MOZ_TRY(EnablePOsForPrinting());
442 if (!mIsCreatingPrintPreview) {
443 printData->OnStartPrinting();
445 InitPrintDocConstruction(false);
447 return NS_OK;
450 //---------------------------------------------------------------------------------
451 nsresult nsPrintJob::Print(Document& aDoc, nsIPrintSettings* aPrintSettings,
452 RemotePrintJobChild* aRemotePrintJob,
453 nsIWebProgressListener* aWebProgressListener) {
454 mRemotePrintJob = aRemotePrintJob;
455 return CommonPrint(false, aPrintSettings, aWebProgressListener, aDoc);
458 nsresult nsPrintJob::PrintPreview(Document& aDoc,
459 nsIPrintSettings* aPrintSettings,
460 nsIWebProgressListener* aWebProgressListener,
461 PrintPreviewResolver&& aCallback) {
462 // Take ownership of aCallback, otherwise a function further up the call
463 // stack will call it to signal failure (by passing zero).
464 mPrintPreviewCallback = std::move(aCallback);
466 nsresult rv = CommonPrint(true, aPrintSettings, aWebProgressListener, aDoc);
467 if (NS_FAILED(rv)) {
468 if (mPrintPreviewCallback) {
469 // signal error
470 mPrintPreviewCallback(
471 PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
472 mPrintPreviewCallback = nullptr;
475 return rv;
478 int32_t nsPrintJob::GetRawNumPages() const {
479 auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
480 Unused << numSheets;
481 return seqFrame ? seqFrame->GetRawNumPages() : 0;
484 bool nsPrintJob::GetIsEmpty() const {
485 auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
486 if (!seqFrame) {
487 return true;
489 if (numSheets > 1) {
490 return false;
492 return !seqFrame->GetPagesInFirstSheet();
495 int32_t nsPrintJob::GetPrintPreviewNumSheets() const {
496 auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
497 Unused << seqFrame;
498 return numSheets;
501 //-----------------------------------------------------------------
502 //-- Section: Pre-Reflow Methods
503 //-----------------------------------------------------------------
505 // static
506 void nsPrintJob::GetDisplayTitleAndURL(Document& aDoc,
507 nsIPrintSettings* aSettings,
508 DocTitleDefault aTitleDefault,
509 nsAString& aTitle, nsAString& aURLStr) {
510 aTitle.Truncate();
511 aURLStr.Truncate();
513 if (aSettings) {
514 aSettings->GetTitle(aTitle);
515 aSettings->GetDocURL(aURLStr);
518 if (aTitle.IsEmpty()) {
519 aDoc.GetTitle(aTitle);
520 if (aTitle.IsEmpty()) {
521 if (!aURLStr.IsEmpty() &&
522 aTitleDefault == DocTitleDefault::eDocURLElseFallback) {
523 aTitle = aURLStr;
524 } else {
525 nsCOMPtr<nsIStringBundle> brandBundle;
526 nsCOMPtr<nsIStringBundleService> svc =
527 mozilla::components::StringBundle::Service();
528 if (svc) {
529 svc->CreateBundle("chrome://branding/locale/brand.properties",
530 getter_AddRefs(brandBundle));
531 if (brandBundle) {
532 brandBundle->GetStringFromName("brandShortName", aTitle);
535 if (aTitle.IsEmpty()) {
536 aTitle.AssignLiteral(u"Mozilla Document");
542 if (aURLStr.IsEmpty()) {
543 nsIURI* url = aDoc.GetDocumentURI();
544 if (!url) {
545 return;
548 nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(url);
549 nsAutoCString urlCStr;
550 nsresult rv = exposableURI->GetSpec(urlCStr);
551 if (NS_FAILED(rv)) {
552 return;
555 nsCOMPtr<nsITextToSubURI> textToSubURI =
556 do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
557 if (NS_FAILED(rv)) {
558 return;
561 textToSubURI->UnEscapeURIForUI(urlCStr, aURLStr);
565 //---------------------------------------------------------------------
566 nsresult nsPrintJob::DocumentReadyForPrinting() {
567 // Send the document to the printer...
568 nsresult rv = SetupToPrintContent();
569 if (NS_FAILED(rv)) {
570 // The print job was canceled or there was a problem
571 // So remove all other documents from the print list
572 DonePrintingSheets(nullptr, rv);
574 return rv;
577 /** ---------------------------------------------------
578 * Cleans up when an error occurred
580 nsresult nsPrintJob::CleanupOnFailure(nsresult aResult, bool aIsPrinting) {
581 PR_PL(("**** Failed %s - rv 0x%" PRIX32,
582 aIsPrinting ? "Printing" : "Print Preview",
583 static_cast<uint32_t>(aResult)));
585 /* cleanup... */
586 if (mPagePrintTimer) {
587 mPagePrintTimer->Stop();
588 DisconnectPagePrintTimer();
591 if (aIsPrinting) {
592 SetIsPrinting(false);
593 } else {
594 SetIsPrintPreview(false);
595 mIsCreatingPrintPreview = false;
598 /* cleanup done, let's fire-up an error dialog to notify the user
599 * what went wrong...
601 * When rv == NS_ERROR_ABORT, it means we want out of the
602 * print job without displaying any error messages
604 if (aResult != NS_ERROR_ABORT) {
605 FirePrintingErrorEvent(aResult);
608 FirePrintCompletionEvent();
610 return aResult;
613 //---------------------------------------------------------------------
614 void nsPrintJob::FirePrintingErrorEvent(nsresult aPrintError) {
615 if (mPrintPreviewCallback) {
616 // signal error
617 mPrintPreviewCallback(
618 PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {}));
619 mPrintPreviewCallback = nullptr;
622 nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
623 if (NS_WARN_IF(!cv)) {
624 return;
627 const RefPtr<Document> doc = cv->GetDocument();
628 const RefPtr<CustomEvent> event = NS_NewDOMCustomEvent(doc, nullptr, nullptr);
630 MOZ_ASSERT(event);
632 AutoJSAPI jsapi;
633 if (!jsapi.Init(event->GetParentObject())) {
634 return;
636 JSContext* cx = jsapi.cx();
638 JS::Rooted<JS::Value> detail(
639 cx, JS::NumberValue(static_cast<double>(aPrintError)));
640 event->InitCustomEvent(cx, u"PrintingError"_ns, false, false, detail);
641 event->SetTrusted(true);
643 // Event listeners in chrome shouldn't delete this.
644 AsyncEventDispatcher::RunDOMEventWhenSafe(*doc, *event,
645 ChromeOnlyDispatch::eYes);
647 // Inform any progress listeners of the Error.
648 if (mPrt) {
649 // Note that nsPrintData::DoOnStatusChange() will call some listeners.
650 // So, mPrt can be cleared or recreated.
651 RefPtr<nsPrintData> printData = mPrt;
652 printData->DoOnStatusChange(aPrintError);
656 //-----------------------------------------------------------------
657 //-- Section: Reflow Methods
658 //-----------------------------------------------------------------
660 nsresult nsPrintJob::ReconstructAndReflow() {
661 if (NS_WARN_IF(!mPrt)) {
662 return NS_ERROR_FAILURE;
665 #if defined(XP_WIN) && defined(EXTENDED_DEBUG_PRINTING)
666 // We need to clear all the output files here
667 // because they will be re-created with second reflow of the docs
668 if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
669 RemoveFilesInDir(".\\");
670 gDumpFileNameCnt = 0;
671 gDumpLOFileNameCnt = 0;
673 #endif
675 // In this loop, it's conceivable that one of our helpers might clear mPrt,
676 // while we're using it & its members! So we capture it in an owning local
677 // reference & use that instead of using mPrt directly.
678 RefPtr<nsPrintData> printData = mPrt;
679 for (nsPrintObject* po : mPrintDocList) {
680 if (!po->PrintingIsEnabled() || po->mInvisible) {
681 continue;
684 // When the print object has been marked as "print the document" (i.e,
685 // po->PrintingIsEnabled() is true), mPresContext and mPresShell should be
686 // non-nullptr (i.e., should've been created for the print) since they
687 // are necessary to print the document.
688 MOZ_ASSERT(po->mPresContext && po->mPresShell,
689 "mPresContext and mPresShell shouldn't be nullptr when the "
690 "print object "
691 "has been marked as \"print the document\"");
693 UpdateZoomRatio(po);
695 po->mPresContext->SetPageScale(po->mZoomRatio);
697 // Calculate scale factor from printer to screen
698 float printDPI = float(AppUnitsPerCSSInch()) /
699 float(printData->mPrintDC->AppUnitsPerDevPixel());
700 po->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);
702 RefPtr<PresShell> presShell(po->mPresShell);
703 if (NS_WARN_IF(presShell->IsDestroying())) {
704 return NS_ERROR_FAILURE;
707 presShell->ReconstructFrames();
709 // If the printing was canceled or restarted with different data,
710 // let's stop doing this printing.
711 if (NS_WARN_IF(mPrt != printData)) {
712 return NS_ERROR_FAILURE;
715 // For all views except the first one, setup the root view.
716 // ??? Can there be multiple po for the top-level-document?
717 bool documentIsTopLevel = true;
718 if (po->mParent) {
719 nsSize adjSize;
720 bool doReturn;
721 nsresult rv = SetRootView(po, doReturn, documentIsTopLevel, adjSize);
723 MOZ_ASSERT(!documentIsTopLevel, "How could this happen?");
725 if (NS_FAILED(rv) || doReturn) {
726 return rv;
730 presShell->FlushPendingNotifications(FlushType::Layout);
732 if (NS_WARN_IF(presShell->IsDestroying())) {
733 return NS_ERROR_FAILURE;
736 // If the printing was canceled or restarted with different data,
737 // let's stop doing this printing.
738 if (NS_WARN_IF(mPrt != printData)) {
739 return NS_ERROR_FAILURE;
742 nsresult rv = UpdateSelectionAndShrinkPrintObject(po, documentIsTopLevel);
743 NS_ENSURE_SUCCESS(rv, rv);
745 return NS_OK;
748 //-------------------------------------------------------
749 nsresult nsPrintJob::SetupToPrintContent() {
750 // This method may be called while DoCommonPrint() initializes the instance
751 // when its script blocker goes out of scope. In such case, this cannot do
752 // its job as expected because some objects in mPrt have not been initialized
753 // yet but they are necessary.
754 // Note: it shouldn't be possible for mPrintObject to be null; we check
755 // it for good measure (after we check its owner) before we start
756 // dereferencing it below.
757 if (NS_WARN_IF(!mPrt) || NS_WARN_IF(!mPrintObject)) {
758 return NS_ERROR_FAILURE;
761 // If this is creating print preview, mPrintObject->mPresContext and
762 // mPrintObject->mPresShell need to be non-nullptr because this cannot
763 // initialize page sequence frame without them at end of this method since
764 // page sequence frame has already been destroyed or not been created yet.
765 if (mIsCreatingPrintPreview && (NS_WARN_IF(!mPrintObject->mPresContext) ||
766 NS_WARN_IF(!mPrintObject->mPresShell))) {
767 return NS_ERROR_FAILURE;
770 // If this is printing some documents (not print-previewing the documents),
771 // mPrintObject->mPresContext and mPrintObject->mPresShell can be
772 // nullptr only when mPrintObject->PrintingIsEnabled() is false. E.g.,
773 // if the document has a <frameset> element and it's printing only content in
774 // a <frame> element or all <frame> elements separately.
775 MOZ_ASSERT(
776 (!mIsCreatingPrintPreview && !mPrintObject->PrintingIsEnabled()) ||
777 (mPrintObject->mPresContext && mPrintObject->mPresShell),
778 "mPresContext and mPresShell shouldn't be nullptr when printing the "
779 "document or creating print-preview");
781 bool didReconstruction = false;
783 // This method works with mPrintObject. So, we need to guarantee that
784 // it won't be deleted in this method. We achieve this by holding a strong
785 // local reference to mPrt, which in turn keeps mPrintObject alive.
786 RefPtr<nsPrintData> printData = mPrt;
788 // If some new content got loaded since the initial reflow rebuild
789 // everything.
790 if (mDidLoadDataForPrinting) {
791 nsresult rv = ReconstructAndReflow();
792 if (NS_WARN_IF(NS_FAILED(rv))) {
793 return rv;
795 // If the printing was canceled or restarted with different data,
796 // let's stop doing this printing.
797 if (NS_WARN_IF(mPrt != printData)) {
798 return NS_ERROR_FAILURE;
800 didReconstruction = true;
803 // Here is where we figure out if extra reflow for shrinking the content
804 // is required.
805 if (mShrinkToFit) {
806 mShrinkToFitFactor = mPrintObject->mShrinkRatio;
808 if (mShrinkToFitFactor < 0.998f) {
809 nsresult rv = ReconstructAndReflow();
810 if (NS_WARN_IF(NS_FAILED(rv))) {
811 return rv;
813 // If the printing was canceled or restarted with different data,
814 // let's stop doing this printing.
815 if (NS_WARN_IF(mPrt != printData)) {
816 return NS_ERROR_FAILURE;
818 didReconstruction = true;
821 if (MOZ_LOG_TEST(gPrintingLog, LogLevel::Debug)) {
822 float calcRatio = mPrintObject->mShrinkRatio;
823 PR_PL(
824 ("*******************************************************************"
825 "*******\n"));
826 PR_PL(("STF Ratio is: %8.5f Effective Ratio: %8.5f Diff: %8.5f\n",
827 mShrinkToFitFactor, calcRatio, mShrinkToFitFactor - calcRatio));
828 PR_PL(
829 ("*******************************************************************"
830 "*******\n"));
834 // If the frames got reconstructed and reflowed the number of pages might
835 // has changed.
836 if (didReconstruction) {
837 FirePrintPreviewUpdateEvent();
838 // If the printing was canceled or restarted with different data,
839 // let's stop doing this printing.
840 if (NS_WARN_IF(mPrt != printData)) {
841 return NS_ERROR_FAILURE;
845 DUMP_DOC_LIST(("\nAfter Reflow------------------------------------------"));
846 PR_PL(("\n"));
847 PR_PL(("-------------------------------------------------------\n"));
848 PR_PL(("\n"));
850 CalcNumPrintablePages(mNumPrintablePages);
852 PR_PL(("--- Printing %d pages\n", mNumPrintablePages));
853 DUMP_DOC_TREELAYOUT;
855 // Print listener setup...
856 printData->OnStartPrinting();
858 // If the printing was canceled or restarted with different data,
859 // let's stop doing this printing.
860 if (NS_WARN_IF(mPrt != printData)) {
861 return NS_ERROR_FAILURE;
864 nsAutoString fileNameStr;
865 // check to see if we are printing to a file
866 if (mPrintSettings->GetOutputDestination() ==
867 nsIPrintSettings::kOutputDestinationFile) {
868 // On some platforms the BeginDocument needs to know the name of the file.
869 mPrintSettings->GetToFileName(fileNameStr);
872 nsAutoString docTitleStr;
873 nsAutoString docURLStr;
874 GetDisplayTitleAndURL(*mPrintObject->mDocument, mPrintSettings,
875 DocTitleDefault::eDocURLElseFallback, docTitleStr,
876 docURLStr);
878 int32_t startPage = 1;
879 int32_t endPage = mNumPrintablePages;
881 nsTArray<int32_t> ranges;
882 mPrintSettings->GetPageRanges(ranges);
883 for (size_t i = 0; i < ranges.Length(); i += 2) {
884 startPage = std::max(1, std::min(startPage, ranges[i]));
885 endPage = std::min(mNumPrintablePages, std::max(endPage, ranges[i + 1]));
888 nsresult rv = NS_OK;
889 // BeginDocument may pass back a FAILURE code
890 // i.e. On Windows, if you are printing to a file and hit "Cancel"
891 // to the "File Name" dialog, this comes back as an error
892 // Don't start printing when regression test are executed
893 if (mIsDoingPrinting) {
894 rv = printData->mPrintDC->BeginDocument(docTitleStr, fileNameStr, startPage,
895 endPage);
898 if (mIsCreatingPrintPreview) {
899 // Copy docTitleStr and docURLStr to the pageSequenceFrame, to be displayed
900 // in the header
901 nsPageSequenceFrame* seqFrame =
902 mPrintObject->mPresShell->GetPageSequenceFrame();
903 if (seqFrame) {
904 seqFrame->StartPrint(mPrintObject->mPresContext, mPrintSettings,
905 docTitleStr, docURLStr);
909 PR_PL(("****************** Begin Document ************************\n"));
911 if (NS_FAILED(rv)) {
912 NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
913 "Failed to begin document for printing");
914 return rv;
917 // This will print the docshell document
918 // when it completes asynchronously in the DonePrintingSheets method
919 // it will check to see if there are more docshells to be printed and
920 // then PrintDocContent will be called again.
922 if (mIsDoingPrinting) {
923 // Double-check that mPrintObject is non-null, because it could have
924 // gotten cleared while running script since we last checked it.
925 if (NS_WARN_IF(!mPrintObject)) {
926 return NS_ERROR_FAILURE;
929 PrintDocContent(mPrintObject, rv); // ignore return value
932 return rv;
935 //-------------------------------------------------------
936 // Recursively reflow each sub-doc and then calc
937 // all the frame locations of the sub-docs
938 nsresult nsPrintJob::ReflowDocList(const UniquePtr<nsPrintObject>& aPO) {
939 NS_ENSURE_ARG_POINTER(aPO);
941 // Check to see if the subdocument's element has been hidden by the parent
942 // document
943 if (aPO->mParent && aPO->mParent->mPresShell) {
944 nsIFrame* frame =
945 aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr;
946 if (!frame || !frame->StyleVisibility()->IsVisible()) {
947 aPO->EnablePrinting(false);
948 aPO->mInvisible = true;
949 return NS_OK;
953 UpdateZoomRatio(aPO.get());
955 // Reflow the PO
956 MOZ_TRY(ReflowPrintObject(aPO));
958 for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
959 MOZ_TRY(ReflowDocList(kid));
961 return NS_OK;
964 void nsPrintJob::FirePrintPreviewUpdateEvent() {
965 // Dispatch the event only while in PrintPreview. When printing, there is no
966 // listener bound to this event and therefore no need to dispatch it.
967 if (mCreatedForPrintPreview && !mIsDoingPrinting) {
968 nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
969 if (Document* document = cv->GetDocument()) {
970 AsyncEventDispatcher::RunDOMEventWhenSafe(
971 *document, u"printPreviewUpdate"_ns, CanBubble::eYes,
972 ChromeOnlyDispatch::eYes);
977 nsresult nsPrintJob::InitPrintDocConstruction(bool aHandleError) {
978 // Guarantee that mPrintObject won't be deleted.
979 RefPtr<nsPrintData> printData = mPrt;
981 if (NS_WARN_IF(!printData)) {
982 return NS_ERROR_FAILURE;
985 // Attach progressListener to catch network requests.
986 mDidLoadDataForPrinting = false;
989 AutoRestore<bool> restore{mDoingInitialReflow};
990 mDoingInitialReflow = true;
992 nsCOMPtr<nsIWebProgress> webProgress =
993 do_QueryInterface(mPrintObject->mDocShell);
994 webProgress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
995 nsIWebProgress::NOTIFY_STATE_REQUEST);
997 MOZ_TRY(ReflowDocList(mPrintObject));
999 FirePrintPreviewUpdateEvent();
1002 MaybeResumePrintAfterResourcesLoaded(aHandleError);
1003 return NS_OK;
1006 bool nsPrintJob::ShouldResumePrint() const {
1007 if (mDoingInitialReflow) {
1008 return false;
1010 Document* doc = mPrintObject->mDocument;
1011 MOZ_ASSERT(doc);
1012 NS_ENSURE_TRUE(doc, true);
1013 nsCOMPtr<nsILoadGroup> lg = doc->GetDocumentLoadGroup();
1014 NS_ENSURE_TRUE(lg, true);
1015 bool pending = false;
1016 nsresult rv = lg->IsPending(&pending);
1017 NS_ENSURE_SUCCESS(rv, true);
1018 return !pending;
1021 nsresult nsPrintJob::MaybeResumePrintAfterResourcesLoaded(
1022 bool aCleanupOnError) {
1023 if (!ShouldResumePrint()) {
1024 mDidLoadDataForPrinting = true;
1025 return NS_OK;
1027 // If Destroy() has already been called, mPtr is nullptr. Then, the instance
1028 // needs to do nothing anymore in this method.
1029 // Note: it shouldn't be possible for mPrintObject to be null; we
1030 // just check it for good measure, as we check its owner.
1031 // Note: it shouldn't be possible for mPrintObject->mDocShell to be
1032 // null; we just check it for good measure, as we check its owner.
1033 if (!mPrt || NS_WARN_IF(!mPrintObject) ||
1034 NS_WARN_IF(!mPrintObject->mDocShell)) {
1035 return NS_ERROR_FAILURE;
1038 nsCOMPtr<nsIWebProgress> webProgress =
1039 do_QueryInterface(mPrintObject->mDocShell);
1041 webProgress->RemoveProgressListener(
1042 static_cast<nsIWebProgressListener*>(this));
1044 nsresult rv;
1045 if (mIsDoingPrinting) {
1046 rv = DocumentReadyForPrinting();
1047 } else {
1048 rv = FinishPrintPreview();
1051 /* cleaup on failure + notify user */
1052 if (aCleanupOnError && NS_FAILED(rv)) {
1053 NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
1054 "nsPrintJob::ResumePrintAfterResourcesLoaded failed");
1055 CleanupOnFailure(rv, !mIsDoingPrinting);
1058 return rv;
1061 ////////////////////////////////////////////////////////////////////////////////
1062 // nsIWebProgressListener
1064 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
1065 nsPrintJob::OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1066 uint32_t aStateFlags, nsresult aStatus) {
1067 if (aStateFlags & STATE_STOP) {
1068 // If all resources are loaded, then finish and reflow.
1069 MaybeResumePrintAfterResourcesLoaded(/* aCleanupOnError */ true);
1071 return NS_OK;
1074 NS_IMETHODIMP
1075 nsPrintJob::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1076 int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
1077 int32_t aCurTotalProgress,
1078 int32_t aMaxTotalProgress) {
1079 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1080 return NS_OK;
1083 NS_IMETHODIMP
1084 nsPrintJob::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1085 nsIURI* aLocation, uint32_t aFlags) {
1086 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1087 return NS_OK;
1090 NS_IMETHODIMP
1091 nsPrintJob::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1092 nsresult aStatus, const char16_t* aMessage) {
1093 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1094 return NS_OK;
1097 NS_IMETHODIMP
1098 nsPrintJob::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
1099 uint32_t aState) {
1100 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1101 return NS_OK;
1104 NS_IMETHODIMP
1105 nsPrintJob::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
1106 nsIRequest* aRequest, uint32_t aEvent) {
1107 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
1108 return NS_OK;
1111 //-------------------------------------------------------
1113 void nsPrintJob::UpdateZoomRatio(nsPrintObject* aPO) {
1114 if (!aPO->mParent) {
1115 if (mShrinkToFit) {
1116 aPO->mZoomRatio = mShrinkToFitFactor;
1117 // If we're actually going to scale (the factor is less than 1), we
1118 // reduce the scale factor slightly to avoid the possibility of floating
1119 // point rounding error causing slight clipping of the longest lines.
1120 if (aPO->mZoomRatio != 1.0f) {
1121 aPO->mZoomRatio -= 0.005f;
1123 } else {
1124 double scaling;
1125 mPrintSettings->GetScaling(&scaling);
1126 aPO->mZoomRatio = float(scaling);
1131 nsresult nsPrintJob::UpdateSelectionAndShrinkPrintObject(
1132 nsPrintObject* aPO, bool aDocumentIsTopLevel) {
1133 PresShell* displayPresShell = aPO->mDocShell->GetPresShell();
1134 // Transfer Selection Ranges to the new Print PresShell
1135 RefPtr<Selection> selection, selectionPS;
1136 // It's okay if there is no display shell, just skip copying the selection
1137 if (displayPresShell) {
1138 selection = displayPresShell->GetCurrentSelection(SelectionType::eNormal);
1140 selectionPS = aPO->mPresShell->GetCurrentSelection(SelectionType::eNormal);
1142 // Reset all existing selection ranges that might have been added by calling
1143 // this function before.
1144 if (selectionPS) {
1145 selectionPS->RemoveAllRanges(IgnoreErrors());
1147 if (selection && selectionPS) {
1148 const uint32_t rangeCount = selection->RangeCount();
1149 for (const uint32_t inx : IntegerRange(rangeCount)) {
1150 MOZ_ASSERT(selection->RangeCount() == rangeCount);
1151 const RefPtr<nsRange> range{selection->GetRangeAt(inx)};
1152 selectionPS->AddRangeAndSelectFramesAndNotifyListeners(*range,
1153 IgnoreErrors());
1157 // If we are trying to shrink the contents to fit on the page
1158 // we must first locate the "pageContent" frame
1159 // Then we walk the frame tree and look for the "xmost" frame
1160 // this is the frame where the right-hand side of the frame extends
1161 // the furthest
1162 if (mShrinkToFit && aDocumentIsTopLevel) {
1163 nsPageSequenceFrame* pageSeqFrame = aPO->mPresShell->GetPageSequenceFrame();
1164 NS_ENSURE_STATE(pageSeqFrame);
1165 aPO->mShrinkRatio = pageSeqFrame->GetSTFPercent();
1166 // Limit the shrink-to-fit scaling for some text-ish type of documents.
1167 nsAutoString contentType;
1168 aPO->mPresShell->GetDocument()->GetContentType(contentType);
1169 if (contentType.EqualsLiteral("application/xhtml+xml") ||
1170 StringBeginsWith(contentType, u"text/"_ns)) {
1171 int32_t limitPercent =
1172 Preferences::GetInt("print.shrink-to-fit.scale-limit-percent", 20);
1173 limitPercent = std::max(0, limitPercent);
1174 limitPercent = std::min(100, limitPercent);
1175 float minShrinkRatio = float(limitPercent) / 100;
1176 aPO->mShrinkRatio = std::max(aPO->mShrinkRatio, minShrinkRatio);
1179 return NS_OK;
1182 nsView* nsPrintJob::GetParentViewForRoot() {
1183 if (mIsCreatingPrintPreview) {
1184 if (nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint)) {
1185 return cv->FindContainerView();
1188 return nullptr;
1191 nsresult nsPrintJob::SetRootView(nsPrintObject* aPO, bool& doReturn,
1192 bool& documentIsTopLevel, nsSize& adjSize) {
1193 bool canCreateScrollbars = true;
1195 nsView* rootView;
1196 nsView* parentView = nullptr;
1198 doReturn = false;
1200 if (aPO->mParent && aPO->mParent->PrintingIsEnabled()) {
1201 nsIFrame* frame =
1202 aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr;
1203 // Without a frame, this document can't be displayed; therefore, there is no
1204 // point to reflowing it
1205 if (!frame) {
1206 aPO->EnablePrinting(false);
1207 doReturn = true;
1208 return NS_OK;
1211 // XXX If printing supported printing document hierarchies with non-constant
1212 // zoom this would be wrong as we use the same mPrt->mPrintDC for all
1213 // subdocuments.
1214 adjSize = frame->GetContentRect().Size();
1215 documentIsTopLevel = false;
1216 // presshell exists because parent is printable
1218 // the top nsPrintObject's widget will always have scrollbars
1219 if (frame && frame->IsSubDocumentFrame()) {
1220 nsView* view = frame->GetView();
1221 NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
1222 view = view->GetFirstChild();
1223 NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
1224 parentView = view;
1225 canCreateScrollbars = false;
1227 } else {
1228 nscoord pageWidth, pageHeight;
1229 mPrt->mPrintDC->GetDeviceSurfaceDimensions(pageWidth, pageHeight);
1230 adjSize = nsSize(pageWidth, pageHeight);
1231 documentIsTopLevel = true;
1232 parentView = GetParentViewForRoot();
1235 if (aPO->mViewManager->GetRootView()) {
1236 // Reuse the root view that is already on the root frame.
1237 rootView = aPO->mViewManager->GetRootView();
1238 // Remove it from its existing parent if necessary
1239 aPO->mViewManager->RemoveChild(rootView);
1240 rootView->SetParent(parentView);
1241 } else {
1242 // Create a child window of the parent that is our "root view/window"
1243 nsRect tbounds = nsRect(nsPoint(0, 0), adjSize);
1244 rootView = aPO->mViewManager->CreateView(tbounds, parentView);
1245 NS_ENSURE_TRUE(rootView, NS_ERROR_OUT_OF_MEMORY);
1248 if (mIsCreatingPrintPreview && documentIsTopLevel) {
1249 aPO->mPresContext->SetPaginatedScrolling(canCreateScrollbars);
1252 // Setup hierarchical relationship in view manager
1253 aPO->mViewManager->SetRootView(rootView);
1255 return NS_OK;
1258 // Reflow a nsPrintObject
1259 nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) {
1260 NS_ENSURE_STATE(aPO);
1262 if (!aPO->PrintingIsEnabled()) {
1263 return NS_OK;
1266 NS_ASSERTION(!aPO->mPresContext, "Recreating prescontext");
1268 // Guarantee that mPrt and the objects it owns won't be deleted in this method
1269 // because it might be cleared if other modules called from here may fire
1270 // events, notifying observers and/or listeners.
1271 RefPtr<nsPrintData> printData = mPrt;
1273 // create the PresContext
1274 nsPresContext::nsPresContextType type =
1275 mIsCreatingPrintPreview ? nsPresContext::eContext_PrintPreview
1276 : nsPresContext::eContext_Print;
1277 const bool shouldBeRoot =
1278 (!aPO->mParent || !aPO->mParent->PrintingIsEnabled()) &&
1279 !GetParentViewForRoot();
1280 aPO->mPresContext = shouldBeRoot ? new nsRootPresContext(aPO->mDocument, type)
1281 : new nsPresContext(aPO->mDocument, type);
1282 aPO->mPresContext->SetPrintSettings(mPrintSettings);
1284 // init it with the DC
1285 MOZ_TRY(aPO->mPresContext->Init(printData->mPrintDC));
1287 aPO->mViewManager = new nsViewManager();
1289 MOZ_TRY(aPO->mViewManager->Init(printData->mPrintDC));
1291 bool doReturn = false;
1292 bool documentIsTopLevel = false;
1293 nsSize adjSize;
1295 nsresult rv = SetRootView(aPO.get(), doReturn, documentIsTopLevel, adjSize);
1297 if (NS_FAILED(rv) || doReturn) {
1298 return rv;
1301 // Here, we inform nsPresContext of the page size. Note that 'adjSize' is
1302 // *usually* the page size, but we need to check. Strictly speaking, adjSize
1303 // is the *device output size*, which is really the dimensions of a "sheet"
1304 // rather than a "page" (an important distinction in an N-pages-per-sheet
1305 // scenario). For some pages-per-sheet values, the pages are orthogonal to
1306 // the sheet; we adjust for that here by swapping the width with the height.
1307 nsSize pageSize = adjSize;
1308 if (mPrintSettings->HasOrthogonalSheetsAndPages()) {
1309 std::swap(pageSize.width, pageSize.height);
1311 // XXXalaskanemily: Is this actually necessary? We set it again before the
1312 // first reflow.
1313 aPO->mPresContext->SetPageSize(pageSize);
1315 int32_t p2a = aPO->mPresContext->DeviceContext()->AppUnitsPerDevPixel();
1316 if (documentIsTopLevel && mIsCreatingPrintPreview) {
1317 if (nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint)) {
1318 // If we're print-previewing and the top level document, use the bounds
1319 // from our doc viewer. Page bounds is not what we want.
1320 nsIntRect bounds;
1321 cv->GetBounds(bounds);
1322 adjSize = nsSize(bounds.width * p2a, bounds.height * p2a);
1325 aPO->mPresContext->SetIsRootPaginatedDocument(documentIsTopLevel);
1326 aPO->mPresContext->SetVisibleArea(nsRect(nsPoint(), adjSize));
1327 aPO->mPresContext->SetPageScale(aPO->mZoomRatio);
1328 // Calculate scale factor from printer to screen
1329 float printDPI = float(AppUnitsPerCSSInch()) / float(p2a);
1330 aPO->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);
1332 // Do CreatePresShell() after we setup the page size, the visible area, and
1333 // the flag |mIsRootPaginatedDocument|, to make sure we can resolve the
1334 // correct viewport size for the print preview page when notifying the media
1335 // feature values changed. See au_viewport_size_for_viewport_unit_resolution()
1336 // in media_queries.rs for more details.
1337 RefPtr<Document> doc = aPO->mDocument;
1338 RefPtr<nsPresContext> presContext = aPO->mPresContext;
1339 RefPtr<nsViewManager> viewManager = aPO->mViewManager;
1341 aPO->mPresShell = doc->CreatePresShell(presContext, viewManager);
1342 if (!aPO->mPresShell) {
1343 return NS_ERROR_FAILURE;
1346 // If we're printing selection then remove the nonselected nodes from our
1347 // cloned document.
1348 if (mPrintSettings->GetPrintSelectionOnly()) {
1349 // If we fail to remove the nodes then we should fail to print, because if
1350 // the user was trying to print a small selection from a large document,
1351 // sending the whole document to a real printer would be very frustrating.
1352 MOZ_TRY(DeleteNonSelectedNodes(*aPO->mDocument));
1355 aPO->mPresShell->BeginObservingDocument();
1357 PR_PL(
1358 ("In DV::ReflowPrintObject PO: %p pS: %p (%9s) Setting page size w,h to "
1359 "%d,%d\n",
1360 aPO.get(), aPO->mPresShell.get(), LoggableTypeOfPO(aPO.get()),
1361 pageSize.width, pageSize.height));
1363 if (mIsCreatingPrintPreview && documentIsTopLevel) {
1364 mDocViewerPrint->SetPrintPreviewPresentation(
1365 aPO->mViewManager, aPO->mPresContext, aPO->mPresShell.get());
1368 MOZ_TRY(aPO->mPresShell->Initialize());
1369 NS_ASSERTION(aPO->mPresShell, "Presshell should still be here");
1371 RefPtr<PresShell> presShell = aPO->mPresShell;
1373 // Get the initial page name. Even though we haven't done any page-name
1374 // fragmentation (that happens during block reflow), this will still be
1375 // valid to find the first page's name.
1376 const nsAtom* firstPageName = nsGkAtoms::_empty;
1377 if (const Element* const rootElement = aPO->mDocument->GetRootElement()) {
1378 if (const nsIFrame* const rootFrame = rootElement->GetPrimaryFrame()) {
1379 firstPageName = rootFrame->ComputePageValue();
1383 const ServoStyleSet::FirstPageSizeAndOrientation sizeAndOrientation =
1384 presShell->StyleSet()->GetFirstPageSizeAndOrientation(firstPageName);
1385 if (mPrintSettings->GetUsePageRuleSizeAsPaperSize()) {
1386 mMaybeCSSPageSize = sizeAndOrientation.size;
1387 if (sizeAndOrientation.size) {
1388 pageSize = sizeAndOrientation.size.value();
1389 aPO->mPresContext->SetPageSize(pageSize);
1393 // If the document has a specified CSS page-size, we rotate the page to
1394 // reflect this. Changing the orientation is reflected by the result of
1395 // FinishPrintPreview, so that the frontend can reflect this.
1396 // The new document has not yet been reflowed, so we have to query the
1397 // original document for any CSS page-size.
1398 if (sizeAndOrientation.orientation) {
1399 switch (sizeAndOrientation.orientation.value()) {
1400 case StylePageSizeOrientation::Landscape:
1401 if (pageSize.width < pageSize.height) {
1402 // Paper is in portrait, CSS page size is landscape.
1403 std::swap(pageSize.width, pageSize.height);
1405 break;
1406 case StylePageSizeOrientation::Portrait:
1407 if (pageSize.width > pageSize.height) {
1408 // Paper is in landscape, CSS page size is portrait.
1409 std::swap(pageSize.width, pageSize.height);
1411 break;
1413 mMaybeCSSPageLandscape = Some(sizeAndOrientation.orientation.value() ==
1414 StylePageSizeOrientation::Landscape);
1415 aPO->mPresContext->SetPageSize(pageSize);
1418 // Process the reflow event Initialize posted
1419 presShell->FlushPendingNotifications(FlushType::Layout);
1421 MOZ_TRY(UpdateSelectionAndShrinkPrintObject(aPO.get(), documentIsTopLevel));
1423 #ifdef EXTENDED_DEBUG_PRINTING
1424 if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
1425 nsAutoCString docStr;
1426 nsAutoCString urlStr;
1427 GetDocTitleAndURL(aPO, docStr, urlStr);
1428 char filename[256];
1429 sprintf(filename, "print_dump_%d.txt", gDumpFileNameCnt++);
1430 // Dump all the frames and view to a a file
1431 FILE* fd = fopen(filename, "w");
1432 if (fd) {
1433 nsIFrame* theRootFrame = aPO->mPresShell->GetRootFrame();
1434 fprintf(fd, "Title: %s\n", docStr.get());
1435 fprintf(fd, "URL: %s\n", urlStr.get());
1436 fprintf(fd, "--------------- Frames ----------------\n");
1437 // RefPtr<gfxContext> renderingContext =
1438 // printData->mPrintDocDC->CreateRenderingContext();
1439 RootFrameList(aPO->mPresContext, fd, 0);
1440 // DumpFrames(fd, aPO->mPresContext, renderingContext, theRootFrame, 0);
1441 fprintf(fd, "---------------------------------------\n\n");
1442 fprintf(fd, "--------------- Views From Root Frame----------------\n");
1443 nsView* v = theRootFrame->GetView();
1444 if (v) {
1445 v->List(fd);
1446 } else {
1447 printf("View is null!\n");
1449 if (aPO->mDocShell) {
1450 fprintf(fd, "--------------- All Views ----------------\n");
1451 DumpViews(aPO->mDocShell, fd);
1452 fprintf(fd, "---------------------------------------\n\n");
1454 fclose(fd);
1457 #endif
1459 return NS_OK;
1462 //-------------------------------------------------------
1463 // Figure out how many documents and how many total pages we are printing
1464 void nsPrintJob::CalcNumPrintablePages(int32_t& aNumPages) {
1465 aNumPages = 0;
1466 // Count the number of printable documents and printable pages
1467 for (nsPrintObject* po : mPrintDocList) {
1468 // Note: The po->mPresContext null-check below is necessary, because it's
1469 // possible po->mPresContext might never have been set. (e.g., if
1470 // PrintingIsEnabled() returns false, ReflowPrintObject bails before setting
1471 // mPresContext)
1472 if (po->mPresContext && po->mPresContext->IsRootPaginatedDocument()) {
1473 nsPageSequenceFrame* seqFrame = po->mPresShell->GetPageSequenceFrame();
1474 if (seqFrame) {
1475 aNumPages += seqFrame->PrincipalChildList().GetLength();
1481 //-----------------------------------------------------------------
1482 //-- Done: Reflow Methods
1483 //-----------------------------------------------------------------
1485 //-----------------------------------------------------------------
1486 //-- Section: Printing Methods
1487 //-----------------------------------------------------------------
1489 //-------------------------------------------------------
1490 // Called for each DocShell that needs to be printed
1491 bool nsPrintJob::PrintDocContent(const UniquePtr<nsPrintObject>& aPO,
1492 nsresult& aStatus) {
1493 NS_ASSERTION(aPO, "Pointer is null!");
1494 aStatus = NS_OK;
1496 if (!aPO->mHasBeenPrinted && aPO->PrintingIsEnabled()) {
1497 aStatus = DoPrint(aPO);
1498 return true;
1501 // If |aPO->mHasBeenPrinted| is true,
1502 // the kids frames are already processed in |PrintPage|.
1503 // XXX This should be removed. Since bug 1552785 it has no longer been
1504 // possible for us to have to print multiple subdocuments consecutively.
1505 if (!aPO->mHasBeenPrinted && !aPO->mInvisible) {
1506 for (const UniquePtr<nsPrintObject>& po : aPO->mKids) {
1507 bool printed = PrintDocContent(po, aStatus);
1508 if (printed || NS_FAILED(aStatus)) {
1509 return true;
1513 return false;
1516 // A helper struct to aid with DeleteNonSelectedNodes.
1517 struct MOZ_STACK_CLASS SelectionRangeState {
1518 explicit SelectionRangeState(RefPtr<Selection> aSelection)
1519 : mSelection(std::move(aSelection)) {
1520 MOZ_ASSERT(mSelection);
1521 MOZ_ASSERT(!mSelection->RangeCount());
1524 // Selects all the nodes that are _not_ included in a given set of ranges.
1525 MOZ_CAN_RUN_SCRIPT void SelectComplementOf(Span<const RefPtr<nsRange>>);
1526 // Removes the selected ranges from the document.
1527 MOZ_CAN_RUN_SCRIPT void RemoveSelectionFromDocument();
1529 private:
1530 struct Position {
1531 nsINode* mNode;
1532 uint32_t mOffset;
1535 MOZ_CAN_RUN_SCRIPT void SelectRange(nsRange*);
1536 MOZ_CAN_RUN_SCRIPT void SelectNodesExcept(const Position& aStart,
1537 const Position& aEnd);
1538 MOZ_CAN_RUN_SCRIPT void SelectNodesExceptInSubtree(const Position& aStart,
1539 const Position& aEnd);
1541 // A map from subtree root (document or shadow root) to the start position of
1542 // the non-selected content (so far).
1543 nsTHashMap<nsPtrHashKey<nsINode>, Position> mPositions;
1545 // The selection we're adding the ranges to.
1546 const RefPtr<Selection> mSelection;
1549 void SelectionRangeState::SelectComplementOf(
1550 Span<const RefPtr<nsRange>> aRanges) {
1551 for (const auto& range : aRanges) {
1552 auto start = Position{range->GetStartContainer(), range->StartOffset()};
1553 auto end = Position{range->GetEndContainer(), range->EndOffset()};
1554 SelectNodesExcept(start, end);
1558 void SelectionRangeState::SelectRange(nsRange* aRange) {
1559 if (aRange && !aRange->Collapsed()) {
1560 mSelection->AddRangeAndSelectFramesAndNotifyListeners(*aRange,
1561 IgnoreErrors());
1565 void SelectionRangeState::SelectNodesExcept(const Position& aStart,
1566 const Position& aEnd) {
1567 SelectNodesExceptInSubtree(aStart, aEnd);
1568 if (auto* shadow = ShadowRoot::FromNode(aStart.mNode->SubtreeRoot())) {
1569 auto* host = shadow->Host();
1570 SelectNodesExcept(Position{host, 0}, Position{host, host->GetChildCount()});
1571 } else {
1572 MOZ_ASSERT(aStart.mNode->IsInUncomposedDoc());
1576 void SelectionRangeState::SelectNodesExceptInSubtree(const Position& aStart,
1577 const Position& aEnd) {
1578 static constexpr auto kEllipsis = u"\x2026"_ns;
1580 nsINode* root = aStart.mNode->SubtreeRoot();
1581 auto& start =
1582 mPositions.WithEntryHandle(root, [&](auto&& entry) -> Position& {
1583 return entry.OrInsertWith([&] { return Position{root, 0}; });
1586 bool ellipsizedStart = false;
1587 if (auto* text = Text::FromNode(aStart.mNode)) {
1588 if (start.mNode != text && aStart.mOffset &&
1589 aStart.mOffset < text->Length()) {
1590 text->InsertData(aStart.mOffset, kEllipsis, IgnoreErrors());
1591 ellipsizedStart = true;
1595 RefPtr<nsRange> range = nsRange::Create(
1596 start.mNode, start.mOffset, aStart.mNode, aStart.mOffset, IgnoreErrors());
1597 SelectRange(range);
1599 start = aEnd;
1601 // If we added an ellipsis at the start and the end position was relative to
1602 // the same node account for it here.
1603 if (ellipsizedStart && aStart.mNode == aEnd.mNode) {
1604 start.mOffset += kEllipsis.Length();
1607 // If the end is mid text then add an ellipsis.
1608 if (auto* text = Text::FromNode(start.mNode)) {
1609 if (start.mOffset && start.mOffset < text->Length()) {
1610 text->InsertData(start.mOffset, kEllipsis, IgnoreErrors());
1611 start.mOffset += kEllipsis.Length();
1616 void SelectionRangeState::RemoveSelectionFromDocument() {
1617 for (auto& entry : mPositions) {
1618 const Position& pos = entry.GetData();
1619 nsINode* root = entry.GetKey();
1620 RefPtr<nsRange> range = nsRange::Create(
1621 pos.mNode, pos.mOffset, root, root->GetChildCount(), IgnoreErrors());
1622 SelectRange(range);
1624 mSelection->DeleteFromDocument(IgnoreErrors());
1628 * Builds the complement set of ranges and adds those to the selection.
1629 * Deletes all of the nodes contained in the complement set of ranges
1630 * leaving behind only nodes that were originally selected.
1631 * Adds ellipses to a selected node's text if text is truncated by a range.
1632 * This is used to implement the "Print Selection Only" user interface option.
1634 MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteNonSelectedNodes(
1635 Document& aDoc) {
1636 MOZ_ASSERT(aDoc.IsStaticDocument());
1637 const auto* printRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
1638 aDoc.GetProperty(nsGkAtoms::printselectionranges));
1639 if (!printRanges) {
1640 return NS_OK;
1643 PresShell* presShell = aDoc.GetPresShell();
1644 NS_ENSURE_STATE(presShell);
1645 RefPtr<Selection> selection =
1646 presShell->GetCurrentSelection(SelectionType::eNormal);
1647 NS_ENSURE_STATE(selection);
1649 SelectionRangeState state(std::move(selection));
1650 state.SelectComplementOf(*printRanges);
1651 state.RemoveSelectionFromDocument();
1652 return NS_OK;
1655 //-------------------------------------------------------
1656 nsresult nsPrintJob::DoPrint(const UniquePtr<nsPrintObject>& aPO) {
1657 PR_PL(("\n"));
1658 PR_PL(("**************************** %s ****************************\n",
1659 LoggableTypeOfPO(aPO.get())));
1660 PR_PL(("****** In DV::DoPrint PO: %p \n", aPO.get()));
1662 PresShell* poPresShell = aPO->mPresShell;
1663 nsPresContext* poPresContext = aPO->mPresContext;
1665 NS_ASSERTION(poPresContext, "PrintObject has not been reflowed");
1666 NS_ASSERTION(poPresContext->Type() != nsPresContext::eContext_PrintPreview,
1667 "How did this context end up here?");
1669 // Guarantee that mPrt and the objects it owns won't be deleted in this method
1670 // because it might be cleared if other modules called from here may fire
1671 // events, notifying observers and/or listeners.
1672 RefPtr<nsPrintData> printData = mPrt;
1673 if (NS_WARN_IF(!printData)) {
1674 return NS_ERROR_FAILURE;
1678 // Ask the page sequence frame to print all the pages
1679 nsPageSequenceFrame* seqFrame = poPresShell->GetPageSequenceFrame();
1680 MOZ_ASSERT(seqFrame, "no page sequence frame");
1682 // We are done preparing for printing, so we can turn this off
1683 mPreparingForPrint = false;
1685 #ifdef EXTENDED_DEBUG_PRINTING
1686 nsIFrame* rootFrame = poPresShell->GetRootFrame();
1687 if (aPO->PrintingIsEnabled()) {
1688 nsAutoCString docStr;
1689 nsAutoCString urlStr;
1690 GetDocTitleAndURL(aPO, docStr, urlStr);
1691 DumpLayoutData(docStr.get(), urlStr.get(), poPresContext,
1692 printData->mPrintDC, rootFrame, aPO->mDocShell, nullptr);
1694 #endif
1696 if (!mPrintSettings) {
1697 // not sure what to do here!
1698 SetIsPrinting(false);
1699 return NS_ERROR_FAILURE;
1702 nsAutoString docTitleStr;
1703 nsAutoString docURLStr;
1704 GetDisplayTitleAndURL(*aPO->mDocument, mPrintSettings,
1705 DocTitleDefault::eFallback, docTitleStr, docURLStr);
1707 if (!seqFrame) {
1708 SetIsPrinting(false);
1709 return NS_ERROR_FAILURE;
1712 // For telemetry, get paper size being used; convert the dimensions to
1713 // points and ensure they reflect portrait orientation.
1714 nsIPrintSettings* settings = mPrintSettings;
1715 double paperWidth, paperHeight;
1716 settings->GetPaperWidth(&paperWidth);
1717 settings->GetPaperHeight(&paperHeight);
1718 int16_t sizeUnit;
1719 settings->GetPaperSizeUnit(&sizeUnit);
1720 switch (sizeUnit) {
1721 case nsIPrintSettings::kPaperSizeInches:
1722 paperWidth *= 72.0;
1723 paperHeight *= 72.0;
1724 break;
1725 case nsIPrintSettings::kPaperSizeMillimeters:
1726 paperWidth *= 72.0 / 25.4;
1727 paperHeight *= 72.0 / 25.4;
1728 break;
1729 default:
1730 MOZ_ASSERT_UNREACHABLE("unknown paper size unit");
1731 break;
1733 if (paperWidth > paperHeight) {
1734 std::swap(paperWidth, paperHeight);
1736 // Use the paper size to build a Telemetry Scalar key.
1737 nsString key;
1738 key.AppendInt(int32_t(NS_round(paperWidth)));
1739 key.Append(u"x");
1740 key.AppendInt(int32_t(NS_round(paperHeight)));
1741 Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_PAPER_SIZE, key, 1);
1743 mPageSeqFrame = seqFrame;
1744 seqFrame->StartPrint(poPresContext, settings, docTitleStr, docURLStr);
1746 // Schedule Page to Print
1747 PR_PL(("Scheduling Print of PO: %p (%s) \n", aPO.get(),
1748 LoggableTypeOfPO(aPO.get())));
1749 StartPagePrintTimer(aPO);
1752 return NS_OK;
1755 //-------------------------------------------------------
1756 bool nsPrintJob::PrePrintSheet() {
1757 NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
1758 NS_ASSERTION(mPrt, "mPrt is null!");
1760 // Although these should NEVER be nullptr
1761 // This is added insurance, to make sure we don't crash in optimized builds
1762 if (!mPrt || !mPageSeqFrame.IsAlive()) {
1763 return true; // means we are done preparing the sheet.
1766 // Guarantee that mPrt won't be deleted during a call of
1767 // FirePrintingErrorEvent().
1768 RefPtr<nsPrintData> printData = mPrt;
1770 // Ask mPageSeqFrame if the sheet is ready to be printed.
1771 // If the sheet doesn't get printed at all, the |done| will be |true|.
1772 bool done = false;
1773 nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
1774 nsresult rv = pageSeqFrame->PrePrintNextSheet(mPagePrintTimer, &done);
1775 if (NS_FAILED(rv)) {
1776 // ??? ::PrintSheet doesn't set |printData->mIsAborted = true| if
1777 // rv != NS_ERROR_ABORT, but I don't really understand why this should be
1778 // the right thing to do? Shouldn't |printData->mIsAborted| set to true
1779 // all the time if something went wrong?
1780 if (rv != NS_ERROR_ABORT) {
1781 FirePrintingErrorEvent(rv);
1782 printData->mIsAborted = true;
1784 done = true;
1786 return done;
1789 bool nsPrintJob::PrintSheet(nsPrintObject* aPO, bool& aInRange) {
1790 NS_ASSERTION(aPO, "aPO is null!");
1791 NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
1792 NS_ASSERTION(mPrt, "mPrt is null!");
1794 // XXXdholbert Nowadays, this function doesn't need to concern itself with
1795 // page ranges -- page-range handling is now handled when we reflow our
1796 // PrintedSheetFrames, and all PrintedSheetFrames are "in-range" and should
1797 // be printed. So this outparam is unconditionally true. Bug 1669815 is filed
1798 // on removing it entirely.
1799 aInRange = true;
1801 // Although these should NEVER be nullptr
1802 // This is added insurance, to make sure we don't crash in optimized builds
1803 if (!mPrt || !aPO || !mPageSeqFrame.IsAlive()) {
1804 FirePrintingErrorEvent(NS_ERROR_FAILURE);
1805 return true; // means we are done printing
1808 // Guarantee that mPrt won't be deleted during a call of
1809 // nsPrintData::DoOnProgressChange() which runs some listeners,
1810 // which may clear (& might otherwise destroy).
1811 RefPtr<nsPrintData> printData = mPrt;
1813 PR_PL(("-----------------------------------\n"));
1814 PR_PL(("------ In DV::PrintSheet PO: %p (%s)\n", aPO, LoggableTypeOfPO(aPO)));
1816 if (printData->mIsAborted) {
1817 return true;
1820 nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
1821 const uint32_t sheetIdx = pageSeqFrame->GetCurrentSheetIdx();
1822 const uint32_t numSheets = pageSeqFrame->PrincipalChildList().GetLength();
1824 PR_PL(("****** Printing sheet index %d of %d sheets(s)\n", sheetIdx,
1825 numSheets));
1827 MOZ_ASSERT(numSheets > 0, "print operations must have at least 1 sheet");
1828 MOZ_ASSERT(sheetIdx < numSheets,
1829 "sheetIdx shouldn't be allowed to go out of bounds");
1830 printData->DoOnProgressChange(sheetIdx, numSheets, false, 0);
1831 if (NS_WARN_IF(mPrt != printData)) {
1832 // If current printing is canceled or new print is started, let's return
1833 // true to notify the caller of current printing is done.
1834 return true;
1837 // Print the sheet
1838 // if a print job was cancelled externally, an EndPage or BeginPage may
1839 // fail and the failure is passed back here.
1840 // Returning true means we are done printing.
1842 // When rv == NS_ERROR_ABORT, it means we want out of the
1843 // print job without displaying any error messages
1844 nsresult rv = pageSeqFrame->PrintNextSheet();
1845 if (NS_FAILED(rv)) {
1846 if (rv != NS_ERROR_ABORT) {
1847 FirePrintingErrorEvent(rv);
1848 printData->mIsAborted = true;
1850 return true;
1853 pageSeqFrame->DoPageEnd();
1855 // If we just printed the final sheet (the one with index "numSheets-1"),
1856 // then we're done!
1857 return (sheetIdx == numSheets - 1);
1860 void nsPrintJob::PageDone(nsresult aResult) {
1861 MOZ_ASSERT(mIsDoingPrinting);
1863 // mPagePrintTimer might be released during RemotePrintFinished, keep a
1864 // reference here to make sure it lives long enough.
1865 RefPtr<nsPagePrintTimer> timer = mPagePrintTimer;
1866 timer->RemotePrintFinished();
1869 //-----------------------------------------------------------------
1870 //-- Done: Printing Methods
1871 //-----------------------------------------------------------------
1873 //-----------------------------------------------------------------
1874 //-- Section: Misc Support Methods
1875 //-----------------------------------------------------------------
1877 //---------------------------------------------------------------------
1878 void nsPrintJob::SetIsPrinting(bool aIsPrinting) {
1879 mIsDoingPrinting = aIsPrinting;
1880 if (aIsPrinting) {
1881 mPreparingForPrint = true;
1885 //---------------------------------------------------------------------
1886 void nsPrintJob::SetIsPrintPreview(bool aIsPrintPreview) {
1887 mCreatedForPrintPreview = aIsPrintPreview;
1889 if (mDocViewerPrint) {
1890 mDocViewerPrint->SetIsPrintPreview(aIsPrintPreview);
1894 //-------------------------------------------------------
1895 bool nsPrintJob::DonePrintingSheets(nsPrintObject* aPO, nsresult aResult) {
1896 // NS_ASSERTION(aPO, "Pointer is null!");
1897 PR_PL(("****** In DV::DonePrintingSheets PO: %p (%s)\n", aPO,
1898 aPO ? LoggableTypeOfPO(aPO) : ""));
1900 // If there is a pageSeqFrame, make sure there are no more printCanvas active
1901 // that might call |Notify| on the pagePrintTimer after things are cleaned up
1902 // and printing was marked as being done.
1903 if (mPageSeqFrame.IsAlive()) {
1904 nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
1905 pageSeqFrame->ResetPrintCanvasList();
1908 // Guarantee that mPrt and mPrintObject won't be deleted during a
1909 // call of PrintDocContent() and FirePrintCompletionEvent().
1910 RefPtr<nsPrintData> printData = mPrt;
1912 if (aPO && !printData->mIsAborted) {
1913 aPO->mHasBeenPrinted = true;
1914 nsresult rv;
1915 bool didPrint = PrintDocContent(mPrintObject, rv);
1916 if (NS_SUCCEEDED(rv) && didPrint) {
1917 PR_PL(
1918 ("****** In DV::DonePrintingSheets PO: %p (%s) didPrint:%s (Not Done "
1919 "Printing)\n",
1920 aPO, LoggableTypeOfPO(aPO), PRT_YESNO(didPrint)));
1921 return false;
1925 if (NS_SUCCEEDED(aResult)) {
1926 FirePrintCompletionEvent();
1927 // XXX mPrt may be cleared or replaced with new instance here.
1928 // However, the following methods will clean up with new mPrt or will
1929 // do nothing due to no proper nsPrintData instance.
1932 SetIsPrinting(false);
1934 // Release reference to mPagePrintTimer; the timer object destroys itself
1935 // after this returns true
1936 DisconnectPagePrintTimer();
1938 return true;
1941 //-------------------------------------------------------
1942 nsresult nsPrintJob::EnablePOsForPrinting() {
1943 // Guarantee that mPrt and the objects it owns won't be deleted.
1944 RefPtr<nsPrintData> printData = mPrt;
1946 // NOTE: All POs have been "turned off" for printing
1947 // this is where we decided which POs get printed.
1949 if (!printData || !mPrintSettings) {
1950 return NS_ERROR_FAILURE;
1953 PR_PL(("\n"));
1954 PR_PL(("********* nsPrintJob::EnablePOsForPrinting *********\n"));
1956 if (!mPrintSettings->GetPrintSelectionOnly()) {
1957 mPrintObject->EnablePrinting(true);
1958 return NS_OK;
1961 // This means we are either printing a selected iframe or
1962 // we are printing the current selection.
1963 NS_ENSURE_STATE(!mDisallowSelectionPrint && mSelectionRoot);
1965 // If mSelectionRoot is a selected iframe without a selection, then just
1966 // enable normally from that point.
1967 if (mSelectionRoot->mParent && !mSelectionRoot->HasSelection()) {
1968 mSelectionRoot->EnablePrinting(true);
1969 } else {
1970 // Otherwise, only enable nsPrintObjects that have a selection.
1971 mSelectionRoot->EnablePrintingSelectionOnly();
1973 return NS_OK;
1976 //-----------------------------------------------------------------
1977 //-- Done: Misc Support Methods
1978 //-----------------------------------------------------------------
1980 //-----------------------------------------------------------------
1981 //-- Section: Finishing up or Cleaning up
1982 //-----------------------------------------------------------------
1984 //-----------------------------------------------------------------
1985 nsresult nsPrintJob::FinishPrintPreview() {
1986 nsresult rv = NS_OK;
1988 #ifdef NS_PRINT_PREVIEW
1990 // If mPrt is null we've already finished with print preview. If mPrt is not
1991 // null but mIsCreatingPrintPreview is false FinishPrintPreview must have
1992 // already failed due to DocumentReadyForPrinting failing.
1993 if (!mPrt || !mIsCreatingPrintPreview) {
1994 return rv;
1997 rv = DocumentReadyForPrinting();
1999 // Note that this method may be called while the instance is being
2000 // initialized. Some methods which initialize the instance (e.g.,
2001 // DoCommonPrint) may need to stop initializing and return error if
2002 // this is called. Therefore it's important to set mIsCreatingPrintPreview
2003 // state to false here. If you need to stop setting that here, you need to
2004 // keep them being able to check whether the owner stopped using this
2005 // instance.
2006 mIsCreatingPrintPreview = false;
2008 // mPrt may be cleared during a call of nsPrintData::OnEndPrinting()
2009 // because that method invokes some arbitrary listeners.
2010 // TODO(dshin): Does any listener attach to print preview? Doesn't seem like
2011 // we call matching `OnStartPrinting()` for previews.
2012 RefPtr<nsPrintData> printData = mPrt;
2013 if (NS_FAILED(rv)) {
2014 printData->OnEndPrinting();
2016 return rv;
2019 if (mPrintPreviewCallback) {
2020 const bool hasSelection = !mDisallowSelectionPrint && mSelectionRoot;
2022 Maybe<float> pageWidth;
2023 Maybe<float> pageHeight;
2024 if (mMaybeCSSPageSize) {
2025 nsSize cssPageSize = *mMaybeCSSPageSize;
2026 pageWidth = Some(float(cssPageSize.width) / float(AppUnitsPerCSSInch()));
2027 pageHeight =
2028 Some(float(cssPageSize.height) / float(AppUnitsPerCSSInch()));
2031 mPrintPreviewCallback(PrintPreviewResultInfo(
2032 GetPrintPreviewNumSheets(), GetRawNumPages(), GetIsEmpty(),
2033 hasSelection, hasSelection && mPrintObject->HasSelection(),
2034 mMaybeCSSPageLandscape, pageWidth, pageHeight));
2035 mPrintPreviewCallback = nullptr;
2038 // At this point we are done preparing everything
2039 // before it is to be created
2041 printData->OnEndPrinting();
2043 #endif // NS_PRINT_PREVIEW
2045 return NS_OK;
2048 //-----------------------------------------------------------------
2049 //-- Done: Finishing up or Cleaning up
2050 //-----------------------------------------------------------------
2052 /*=============== Timer Related Code ======================*/
2053 nsresult nsPrintJob::StartPagePrintTimer(const UniquePtr<nsPrintObject>& aPO) {
2054 if (!mPagePrintTimer) {
2055 // Get the delay time in between the printing of each page
2056 // this gives the user more time to press cancel
2057 int32_t printPageDelay = mPrintSettings->GetPrintPageDelay();
2059 nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
2060 NS_ENSURE_TRUE(cv, NS_ERROR_FAILURE);
2061 nsCOMPtr<Document> doc = cv->GetDocument();
2062 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2064 mPagePrintTimer =
2065 new nsPagePrintTimer(this, mDocViewerPrint, doc, printPageDelay);
2067 if (mRemotePrintJob) {
2068 mRemotePrintJob->SetPagePrintTimer(mPagePrintTimer);
2069 mRemotePrintJob->SetPrintJob(this);
2073 return mPagePrintTimer->Start(aPO.get());
2076 //---------------------------------------------------------------
2077 //-- PLEvent Notification
2078 //---------------------------------------------------------------
2079 class nsPrintCompletionEvent : public Runnable {
2080 public:
2081 explicit nsPrintCompletionEvent(nsIDocumentViewerPrint* docViewerPrint)
2082 : mozilla::Runnable("nsPrintCompletionEvent"),
2083 mDocViewerPrint(docViewerPrint) {
2084 NS_ASSERTION(mDocViewerPrint, "mDocViewerPrint is null.");
2087 NS_IMETHOD Run() override {
2088 if (mDocViewerPrint) mDocViewerPrint->OnDonePrinting();
2089 return NS_OK;
2092 private:
2093 nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint;
2096 //-----------------------------------------------------------
2097 void nsPrintJob::FirePrintCompletionEvent() {
2098 MOZ_ASSERT(NS_IsMainThread());
2099 nsCOMPtr<nsIRunnable> event = new nsPrintCompletionEvent(mDocViewerPrint);
2100 nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
2101 NS_ENSURE_TRUE_VOID(cv);
2102 nsCOMPtr<Document> doc = cv->GetDocument();
2103 NS_ENSURE_TRUE_VOID(doc);
2104 NS_ENSURE_SUCCESS_VOID(doc->Dispatch(event.forget()));
2107 void nsPrintJob::DisconnectPagePrintTimer() {
2108 if (mPagePrintTimer) {
2109 mPagePrintTimer->Disconnect();
2110 mPagePrintTimer = nullptr;
2114 //---------------------------------------------------------------
2115 //---------------------------------------------------------------
2116 //-- Debug helper routines
2117 //---------------------------------------------------------------
2118 //---------------------------------------------------------------
2119 #if defined(XP_WIN) && defined(EXTENDED_DEBUG_PRINTING)
2120 # include <windows.h>
2121 # include <process.h>
2122 # include <direct.h>
2124 # define MY_FINDFIRST(a, b) FindFirstFile(a, b)
2125 # define MY_FINDNEXT(a, b) FindNextFile(a, b)
2126 # define ISDIR(a) (a.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
2127 # define MY_FINDCLOSE(a) FindClose(a)
2128 # define MY_FILENAME(a) a.cFileName
2129 # define MY_FILESIZE(a) (a.nFileSizeHigh * MAXDWORD) + a.nFileSizeLow
2131 int RemoveFilesInDir(const char* aDir) {
2132 WIN32_FIND_DATA data_ptr;
2133 HANDLE find_handle;
2135 char path[MAX_PATH];
2137 strcpy(path, aDir);
2139 // Append slash to the end of the directory names if not there
2140 if (path[strlen(path) - 1] != '\\') strcat(path, "\\");
2142 char findPath[MAX_PATH];
2143 strcpy(findPath, path);
2144 strcat(findPath, "*.*");
2146 find_handle = MY_FINDFIRST(findPath, &data_ptr);
2148 if (find_handle != INVALID_HANDLE_VALUE) {
2149 do {
2150 if (ISDIR(data_ptr) && (stricmp(MY_FILENAME(data_ptr), ".")) &&
2151 (stricmp(MY_FILENAME(data_ptr), ".."))) {
2152 // skip
2153 } else if (!ISDIR(data_ptr)) {
2154 if (!strncmp(MY_FILENAME(data_ptr), "print_dump", 10)) {
2155 char fileName[MAX_PATH];
2156 strcpy(fileName, aDir);
2157 strcat(fileName, "\\");
2158 strcat(fileName, MY_FILENAME(data_ptr));
2159 printf("Removing %s\n", fileName);
2160 remove(fileName);
2163 } while (MY_FINDNEXT(find_handle, &data_ptr));
2164 MY_FINDCLOSE(find_handle);
2166 return TRUE;
2168 #endif
2170 #ifdef EXTENDED_DEBUG_PRINTING
2172 /** ---------------------------------------------------
2173 * Dumps Frames for Printing
2175 static void RootFrameList(nsPresContext* aPresContext, FILE* out,
2176 const char* aPrefix) {
2177 if (!aPresContext || !out) return;
2179 if (PresShell* presShell = aPresContext->GetPresShell()) {
2180 nsIFrame* frame = presShell->GetRootFrame();
2181 if (frame) {
2182 frame->List(out, aPrefix);
2187 /** ---------------------------------------------------
2188 * Dumps Frames for Printing
2190 static void DumpFrames(FILE* out, nsPresContext* aPresContext,
2191 gfxContext* aRendContext, nsIFrame* aFrame,
2192 int32_t aLevel) {
2193 NS_ASSERTION(out, "Pointer is null!");
2194 NS_ASSERTION(aPresContext, "Pointer is null!");
2195 NS_ASSERTION(aRendContext, "Pointer is null!");
2196 NS_ASSERTION(aFrame, "Pointer is null!");
2198 nsIFrame* child = aFrame->PrincipalChildList().FirstChild();
2199 while (child != nullptr) {
2200 for (int32_t i = 0; i < aLevel; i++) {
2201 fprintf(out, " ");
2203 nsAutoString tmp;
2204 child->GetFrameName(tmp);
2205 fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
2206 bool isSelected;
2207 if (child->IsVisibleForPainting()) {
2208 fprintf(out, " %p %s", child, isSelected ? "VIS" : "UVS");
2209 nsRect rect = child->GetRect();
2210 fprintf(out, "[%d,%d,%d,%d] ", rect.x, rect.y, rect.width, rect.height);
2211 fprintf(out, "v: %p ", (void*)child->GetView());
2212 fprintf(out, "\n");
2213 DumpFrames(out, aPresContext, aRendContext, child, aLevel + 1);
2214 child = child->GetNextSibling();
2219 /** ---------------------------------------------------
2220 * Dumps the Views from the DocShell
2222 static void DumpViews(nsIDocShell* aDocShell, FILE* out) {
2223 NS_ASSERTION(aDocShell, "Pointer is null!");
2224 NS_ASSERTION(out, "Pointer is null!");
2226 if (nullptr != aDocShell) {
2227 fprintf(out, "docshell=%p \n", aDocShell);
2228 if (PresShell* presShell = aDocShell->GetPresShell()) {
2229 nsViewManager* vm = presShell->GetViewManager();
2230 if (vm) {
2231 nsView* root = vm->GetRootView();
2232 if (root) {
2233 root->List(out);
2236 } else {
2237 fputs("null pres shell\n", out);
2240 // dump the views of the sub documents
2241 int32_t i, n;
2242 BrowsingContext* bc = nsDocShell::Cast(aDocShell)->GetBrowsingContext();
2243 for (auto& child : bc->Children()) {
2244 if (auto childDS = child->GetDocShell()) {
2245 DumpViews(childAsShell, out);
2251 /** ---------------------------------------------------
2252 * Dumps the Views and Frames
2254 void DumpLayoutData(const char* aTitleStr, const char* aURLStr,
2255 nsPresContext* aPresContext, nsDeviceContext* aDC,
2256 nsIFrame* aRootFrame, nsIDocShell* aDocShell,
2257 FILE* aFD = nullptr) {
2258 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2259 return;
2262 if (aPresContext == nullptr || aDC == nullptr) {
2263 return;
2266 # ifdef NS_PRINT_PREVIEW
2267 if (aPresContext->Type() == nsPresContext::eContext_PrintPreview) {
2268 return;
2270 # endif
2272 NS_ASSERTION(aRootFrame, "Pointer is null!");
2273 NS_ASSERTION(aDocShell, "Pointer is null!");
2275 // Dump all the frames and view to a a file
2276 char filename[256];
2277 sprintf(filename, "print_dump_layout_%d.txt", gDumpLOFileNameCnt++);
2278 FILE* fd = aFD ? aFD : fopen(filename, "w");
2279 if (fd) {
2280 fprintf(fd, "Title: %s\n", aTitleStr ? aTitleStr : "");
2281 fprintf(fd, "URL: %s\n", aURLStr ? aURLStr : "");
2282 fprintf(fd, "--------------- Frames ----------------\n");
2283 fprintf(fd, "--------------- Frames ----------------\n");
2284 // RefPtr<gfxContext> renderingContext =
2285 // aDC->CreateRenderingContext();
2286 RootFrameList(aPresContext, fd, "");
2287 // DumpFrames(fd, aPresContext, renderingContext, aRootFrame, 0);
2288 fprintf(fd, "---------------------------------------\n\n");
2289 fprintf(fd, "--------------- Views From Root Frame----------------\n");
2290 nsView* v = aRootFrame->GetView();
2291 if (v) {
2292 v->List(fd);
2293 } else {
2294 printf("View is null!\n");
2296 if (aDocShell) {
2297 fprintf(fd, "--------------- All Views ----------------\n");
2298 DumpViews(aDocShell, fd);
2299 fprintf(fd, "---------------------------------------\n\n");
2301 if (aFD == nullptr) {
2302 fclose(fd);
2307 //-------------------------------------------------------------
2308 static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList) {
2309 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2310 return;
2313 PR_PL(("Doc List\n***************************************************\n"));
2314 PR_PL(
2315 ("T P A H PO DocShell Seq Page Root Page# "
2316 "Rect\n"));
2317 for (nsPrintObject* po : aDocList) {
2318 NS_ASSERTION(po, "nsPrintObject can't be null!");
2319 nsIFrame* rootFrame = nullptr;
2320 if (po->mPresShell) {
2321 rootFrame = po->mPresShell->GetRootFrame();
2322 while (rootFrame != nullptr) {
2323 nsPageSequenceFrame* sqf = do_QueryFrame(rootFrame);
2324 if (sqf) {
2325 break;
2327 rootFrame = rootFrame->PrincipalChildList().FirstChild();
2331 PR_PL(("%s %d %d %p %p %p\n", ShortLoggableTypeOfPO(po),
2332 po->PrintingIsEnabled(), po->mHasBeenPrinted, po,
2333 po->mDocShell.get(), rootFrame));
2337 //-------------------------------------------------------------
2338 static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel, FILE* aFD) {
2339 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2340 return;
2343 NS_ASSERTION(aPO, "Pointer is null!");
2345 FILE* fd = aFD ? aFD : stdout;
2346 if (aLevel == 0) {
2347 fprintf(fd,
2348 "DocTree\n***************************************************\n");
2349 fprintf(fd, "T PO DocShell Seq Page Page# Rect\n");
2351 for (const auto& po : aPO->mKids) {
2352 NS_ASSERTION(po, "nsPrintObject can't be null!");
2353 for (int32_t k = 0; k < aLevel; k++) fprintf(fd, " ");
2354 fprintf(fd, "%s %p %p\n", ShortLoggableTypeOfPO(po.get()), po.get(),
2355 po->mDocShell.get());
2359 //-------------------------------------------------------------
2360 static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
2361 nsACString& aDocStr, nsACString& aURLStr) {
2362 nsAutoString docTitleStr;
2363 nsAutoString docURLStr;
2364 GetDocumentTitleAndURL(aPO->mDocument, docTitleStr, docURLStr);
2365 CopyUTF16toUTF8(docTitleStr, aDocStr);
2366 CopyUTF16toUTF8(docURLStr, aURLStr);
2369 //-------------------------------------------------------------
2370 static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
2371 nsDeviceContext* aDC, int aLevel,
2372 FILE* aFD) {
2373 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2374 return;
2377 NS_ASSERTION(aPO, "Pointer is null!");
2378 NS_ASSERTION(aDC, "Pointer is null!");
2380 FILE* fd = nullptr;
2381 if (aLevel == 0) {
2382 fd = fopen("tree_layout.txt", "w");
2383 fprintf(fd,
2384 "DocTree\n***************************************************\n");
2385 fprintf(fd, "***************************************************\n");
2386 fprintf(fd, "T PO DocShell Seq Page Page# Rect\n");
2387 } else {
2388 fd = aFD;
2390 if (fd) {
2391 nsIFrame* rootFrame = nullptr;
2392 if (aPO->mPresShell) {
2393 rootFrame = aPO->mPresShell->GetRootFrame();
2395 for (int32_t k = 0; k < aLevel; k++) fprintf(fd, " ");
2396 fprintf(fd, "%s %p %p\n", ShortLoggableTypeOfPO(aPO.get()), aPO.get(),
2397 aPO->mDocShell.get());
2398 if (aPO->PrintingIsEnabled()) {
2399 nsAutoCString docStr;
2400 nsAutoCString urlStr;
2401 GetDocTitleAndURL(aPO, docStr, urlStr);
2402 DumpLayoutData(docStr.get(), urlStr.get(), aPO->mPresContext, aDC,
2403 rootFrame, aPO->mDocShell, fd);
2405 fprintf(fd, "<***************************************************>\n");
2407 for (const auto& po : aPO->mKids) {
2408 NS_ASSERTION(po, "nsPrintObject can't be null!");
2409 DumpPrintObjectsTreeLayout(po, aDC, aLevel + 1, fd);
2412 if (aLevel == 0 && fd) {
2413 fclose(fd);
2417 //-------------------------------------------------------------
2418 static void DumpPrintObjectsListStart(
2419 const char* aStr, const nsTArray<nsPrintObject*>& aDocList) {
2420 if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
2421 return;
2424 NS_ASSERTION(aStr, "Pointer is null!");
2426 PR_PL(("%s\n", aStr));
2427 DumpPrintObjectsList(aDocList);
2430 #endif
2432 //---------------------------------------------------------------
2433 //---------------------------------------------------------------
2434 //-- End of debug helper routines
2435 //---------------------------------------------------------------