nsDocumentViewer.cpp (109940B)
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/. */ 6 7 /* container for a document and its presentation */ 8 9 #include "gfxContext.h" 10 #include "mozilla/PresShell.h" 11 #include "mozilla/PresShellWidgetListener.h" 12 #include "mozilla/RestyleManager.h" 13 #include "mozilla/ServoStyleSet.h" 14 #include "mozilla/dom/AutoSuppressEventHandlingAndSuspend.h" 15 #include "mozilla/dom/BeforeUnloadEvent.h" 16 #include "mozilla/dom/BrowsingContext.h" 17 #include "mozilla/dom/Document.h" 18 #include "mozilla/dom/DocumentInlines.h" 19 #include "mozilla/dom/FragmentDirective.h" 20 #include "mozilla/dom/PopupBlocker.h" 21 #include "mozilla/dom/Selection.h" 22 #include "mozilla/widget/Screen.h" 23 #include "nsCOMPtr.h" 24 #include "nsContentUtils.h" 25 #include "nsDeviceContext.h" 26 #include "nsFrameSelection.h" 27 #include "nsGenericHTMLElement.h" 28 #include "nsIContent.h" 29 #include "nsIDocumentViewer.h" 30 #include "nsIDocumentViewerPrint.h" 31 #include "nsIFrame.h" 32 #include "nsIScreen.h" 33 #include "nsISelectionListener.h" 34 #include "nsPresContext.h" 35 #include "nsReadableUtils.h" 36 #include "nsStubMutationObserver.h" 37 #include "nsSubDocumentFrame.h" 38 #include "nsThreadUtils.h" 39 #include "nscore.h" 40 #ifdef ACCESSIBILITY 41 # include "mozilla/a11y/DocAccessible.h" 42 #endif 43 #include "imgIContainer.h" // image animation mode constants 44 #include "mozilla/BasicEvents.h" 45 #include "mozilla/Encoding.h" 46 #include "mozilla/ErrorResult.h" 47 #include "mozilla/Preferences.h" 48 #include "mozilla/ReflowInput.h" 49 #include "mozilla/ScrollContainerFrame.h" 50 #include "mozilla/SpinEventLoopUntil.h" 51 #include "mozilla/StaticPrefs_dom.h" 52 #include "mozilla/StaticPrefs_fission.h" 53 #include "mozilla/StaticPrefs_javascript.h" 54 #include "mozilla/StaticPrefs_print.h" 55 #include "mozilla/StyleSheet.h" 56 #include "mozilla/StyleSheetInlines.h" 57 #include "mozilla/ThrottledEventQueue.h" 58 #include "mozilla/Try.h" 59 #include "mozilla/WeakPtr.h" 60 #include "mozilla/css/Loader.h" 61 #include "nsCharsetSource.h" 62 #include "nsCopySupport.h" 63 #include "nsDOMNavigationTiming.h" 64 #include "nsDocShell.h" 65 #include "nsFocusManager.h" 66 #include "nsGlobalWindowInner.h" 67 #include "nsGlobalWindowOuter.h" 68 #include "nsIBaseWindow.h" 69 #include "nsIClipboard.h" 70 #include "nsIClipboardHelper.h" 71 #include "nsIDocumentViewerEdit.h" 72 #include "nsIImageLoadingContent.h" 73 #include "nsIInterfaceRequestor.h" 74 #include "nsIInterfaceRequestorUtils.h" 75 #include "nsILayoutHistoryState.h" 76 #include "nsILoadContext.h" 77 #include "nsIPromptCollection.h" 78 #include "nsIPromptService.h" 79 #include "nsIXULRuntime.h" 80 #include "nsJSEnvironment.h" 81 #include "nsNetUtil.h" 82 #include "nsPIDOMWindow.h" 83 #include "nsPIWindowRoot.h" 84 #include "nsPageSequenceFrame.h" 85 #include "nsSandboxFlags.h" 86 #include "nsStyleSheetService.h" 87 #include "nsXULPopupManager.h" 88 89 //-------------------------- 90 // Printing Include 91 //--------------------------- 92 #ifdef NS_PRINTING 93 94 # include "nsDeviceContextSpecProxy.h" 95 # include "nsIWebBrowserPrint.h" 96 # include "nsPrintJob.h" 97 98 // Print Options 99 # include "nsIPrintSettings.h" 100 # include "nsIPrintSettingsService.h" 101 # include "nsISimpleEnumerator.h" 102 103 #endif // NS_PRINTING 104 105 // focus 106 #include "mozilla/EventDispatcher.h" 107 #include "mozilla/dom/XMLHttpRequestMainThread.h" 108 #include "nsIDOMEventListener.h" 109 #include "nsISHEntry.h" 110 #include "nsISHistory.h" 111 #include "nsISelectionController.h" 112 #include "nsIWebNavigation.h" 113 114 // paint forcing 115 #include <stdio.h> 116 117 #include "mozilla/BasePrincipal.h" 118 #include "mozilla/dom/Element.h" 119 #include "mozilla/dom/Event.h" 120 #include "mozilla/dom/ScriptLoader.h" 121 #include "mozilla/dom/WindowGlobalChild.h" 122 123 namespace mozilla::dom { 124 class PrintPreviewResultInfo; 125 } // namespace mozilla::dom 126 127 using namespace mozilla; 128 using namespace mozilla::dom; 129 130 using mozilla::layout::RemotePrintJobChild; 131 using PrintPreviewResolver = 132 std::function<void(const mozilla::dom::PrintPreviewResultInfo&)>; 133 134 //----------------------------------------------------- 135 // LOGGING 136 #include "LayoutLogging.h" 137 #include "mozilla/Logging.h" 138 139 extern mozilla::LazyLogModule gPageCacheLog; 140 141 #ifdef NS_PRINTING 142 mozilla::LazyLogModule gPrintingLog("printing"); 143 144 # define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1); 145 #endif // NS_PRINTING 146 147 #define PRT_YESNO(_p) ((_p) ? "YES" : "NO") 148 //----------------------------------------------------- 149 150 class nsDocumentViewer; 151 152 // a small delegate class used to avoid circular references 153 154 class nsDocViewerSelectionListener final : public nsISelectionListener { 155 public: 156 // nsISupports interface... 157 NS_DECL_ISUPPORTS 158 159 // nsISelectionListerner interface 160 NS_DECL_NSISELECTIONLISTENER 161 162 explicit nsDocViewerSelectionListener(nsDocumentViewer* aDocViewer) 163 : mDocViewer(aDocViewer), mSelectionWasCollapsed(true) {} 164 165 void Disconnect() { mDocViewer = nullptr; } 166 167 protected: 168 virtual ~nsDocViewerSelectionListener() = default; 169 170 nsDocumentViewer* mDocViewer; 171 bool mSelectionWasCollapsed; 172 }; 173 174 /** editor Implementation of the FocusListener interface */ 175 class nsDocViewerFocusListener final : public nsIDOMEventListener { 176 public: 177 explicit nsDocViewerFocusListener(nsDocumentViewer* aDocViewer) 178 : mDocViewer(aDocViewer) {} 179 180 NS_DECL_ISUPPORTS 181 NS_DECL_NSIDOMEVENTLISTENER 182 183 void Disconnect() { mDocViewer = nullptr; } 184 185 protected: 186 virtual ~nsDocViewerFocusListener() = default; 187 188 nsDocumentViewer* mDocViewer; 189 }; 190 191 namespace viewer_detail { 192 193 /** 194 * Mutation observer for use until we hand ourselves over to our SHEntry. 195 */ 196 class BFCachePreventionObserver final : public nsStubMutationObserver { 197 public: 198 explicit BFCachePreventionObserver(Document* aDocument) 199 : mDocument(aDocument) {} 200 201 NS_DECL_ISUPPORTS 202 203 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED 204 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED 205 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED 206 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED 207 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED 208 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED 209 210 // Stop observing the document. 211 void Disconnect(); 212 213 private: 214 ~BFCachePreventionObserver() = default; 215 216 // Helper for the work that needs to happen when mutations happen. 217 void MutationHappened(); 218 219 Document* mDocument; // Weak; we get notified if it dies 220 }; 221 222 NS_IMPL_ISUPPORTS(BFCachePreventionObserver, nsIMutationObserver) 223 224 void BFCachePreventionObserver::CharacterDataChanged( 225 nsIContent* aContent, const CharacterDataChangeInfo&) { 226 if (aContent->IsInNativeAnonymousSubtree()) { 227 return; 228 } 229 MutationHappened(); 230 } 231 232 void BFCachePreventionObserver::AttributeChanged(Element* aElement, 233 int32_t aNameSpaceID, 234 nsAtom* aAttribute, 235 AttrModType, 236 const nsAttrValue* aOldValue) { 237 if (aElement->IsInNativeAnonymousSubtree()) { 238 return; 239 } 240 MutationHappened(); 241 } 242 243 void BFCachePreventionObserver::ContentAppended(nsIContent* aFirstNewContent, 244 const ContentAppendInfo&) { 245 if (aFirstNewContent->IsInNativeAnonymousSubtree()) { 246 return; 247 } 248 MutationHappened(); 249 } 250 251 void BFCachePreventionObserver::ContentInserted(nsIContent* aChild, 252 const ContentInsertInfo&) { 253 if (aChild->IsInNativeAnonymousSubtree()) { 254 return; 255 } 256 MutationHappened(); 257 } 258 259 void BFCachePreventionObserver::ContentWillBeRemoved(nsIContent* aChild, 260 const ContentRemoveInfo&) { 261 if (aChild->IsInNativeAnonymousSubtree()) { 262 return; 263 } 264 MutationHappened(); 265 } 266 267 void BFCachePreventionObserver::NodeWillBeDestroyed(nsINode* aNode) { 268 mDocument = nullptr; 269 } 270 271 void BFCachePreventionObserver::Disconnect() { 272 if (mDocument) { 273 mDocument->RemoveMutationObserver(this); 274 // It will no longer tell us when it goes away, so make sure we're 275 // not holding a dangling ref. 276 mDocument = nullptr; 277 } 278 } 279 280 void BFCachePreventionObserver::MutationHappened() { 281 MOZ_ASSERT( 282 mDocument, 283 "How can we not have a document but be getting notified for mutations?"); 284 mDocument->DisallowBFCaching(); 285 Disconnect(); 286 } 287 288 } // namespace viewer_detail 289 290 using viewer_detail::BFCachePreventionObserver; 291 292 //------------------------------------------------------------- 293 class nsDocumentViewer final : public nsIDocumentViewer, 294 public nsIDocumentViewerEdit, 295 public nsIDocumentViewerPrint 296 #ifdef NS_PRINTING 297 , 298 public nsIWebBrowserPrint 299 #endif 300 301 { 302 friend class nsDocViewerSelectionListener; 303 friend class nsPagePrintTimer; 304 friend class nsPrintJob; 305 306 public: 307 nsDocumentViewer(); 308 309 // nsISupports interface... 310 NS_DECL_ISUPPORTS 311 312 // nsIDocumentViewer interface... 313 NS_DECL_NSIDOCUMENTVIEWER 314 315 // nsIDocumentViewerEdit 316 NS_DECL_NSIDOCUMENTVIEWEREDIT 317 318 #ifdef NS_PRINTING 319 // nsIWebBrowserPrint 320 NS_DECL_NSIWEBBROWSERPRINT 321 #endif 322 323 // nsIDocumentViewerPrint Printing Methods 324 NS_DECL_NSIDOCUMENTVIEWERPRINT 325 326 protected: 327 virtual ~nsDocumentViewer(); 328 329 private: 330 void MakeWindow(); 331 nsresult CreateDeviceContext(nsSubDocumentFrame* aContainerFrame); 332 333 /** 334 * If aDoCreation is true, this creates the device context, creates a 335 * prescontext if necessary, and calls MakeWindow. 336 * 337 * If aForceSetNewDocument is false, then SetNewDocument won't be 338 * called if the window's current document is already mDocument. 339 */ 340 nsresult InitInternal(nsIWidget* aParentWidget, nsISupports* aState, 341 mozilla::dom::WindowGlobalChild* aActor, 342 const LayoutDeviceIntRect& aBounds, bool aDoCreation, 343 bool aNeedMakeCX = true, 344 bool aForceSetNewDocument = true); 345 /** 346 * @param aDoInitialReflow set to true if you want to kick off the initial 347 * reflow 348 */ 349 MOZ_CAN_RUN_SCRIPT_BOUNDARY 350 nsresult InitPresentationStuff(bool aDoInitialReflow); 351 352 already_AddRefed<nsINode> GetPopupNode(); 353 already_AddRefed<nsINode> GetPopupLinkNode(); 354 already_AddRefed<nsIImageLoadingContent> GetPopupImageNode(); 355 356 void PrepareToStartLoad(void); 357 358 nsresult SyncParentSubDocMap(); 359 360 void RemoveFocusListener(); 361 void ReinitializeFocusListener(); 362 363 mozilla::dom::Selection* GetDocumentSelection(); 364 365 void DestroyPresShell(); 366 void DestroyPresContext(); 367 368 void InvalidatePotentialSubDocDisplayItem(); 369 370 std::tuple<const nsIFrame*, int32_t> GetCurrentSheetFrameAndNumber() const; 371 372 protected: 373 // This will make mPresShell stop listener to any widget it's currently 374 // listening to, and start listening to mWindow for events. Note that 375 // the previous mWindow listener might be kept around as the 376 // PreviouslyAttachedWidgetListener so that we are able to paint the old page 377 // while the new one loads. 378 void AttachToTopLevelWidget(); 379 void DetachFromTopLevelWidget(); 380 381 // IMPORTANT: The ownership implicit in the following member 382 // variables has been explicitly checked and set using nsCOMPtr 383 // for owning pointers and raw COM interface pointers for weak 384 // (ie, non owning) references. If you add any members to this 385 // class, please make the ownership explicit (pinkerton, scc). 386 387 WeakPtr<nsDocShell> mContainer; // it owns me! 388 RefPtr<nsDeviceContext> mDeviceContext; // We create and own this baby 389 390 // the following six items are explicitly in this order 391 // so they will be destroyed in the reverse order (pinkerton, scc) 392 nsCOMPtr<Document> mDocument; 393 nsCOMPtr<nsIWidget> mWindow; // may be null 394 RefPtr<nsPresContext> mPresContext; 395 RefPtr<PresShell> mPresShell; 396 397 RefPtr<nsDocViewerSelectionListener> mSelectionListener; 398 RefPtr<nsDocViewerFocusListener> mFocusListener; 399 400 nsCOMPtr<nsIDocumentViewer> mPreviousViewer; 401 nsCOMPtr<nsISHEntry> mSHEntry; 402 // Observer that will prevent bfcaching if it gets notified. This 403 // is non-null precisely when mSHEntry is non-null. 404 RefPtr<BFCachePreventionObserver> mBFCachePreventionObserver; 405 406 nsIWidget* mParentWidget; // purposely won't be ref counted. May be null 407 408 LayoutDeviceIntRect mBounds; 409 410 int16_t mNumURLStarts; 411 int16_t mDestroyBlockedCount; 412 413 unsigned mStopped : 1; 414 unsigned mLoaded : 1; 415 unsigned mDeferredWindowClose : 1; 416 // document management data 417 // these items are specific to markup documents (html and xml) 418 // may consider splitting these out into a subclass 419 unsigned mIsSticky : 1; 420 unsigned mInPermitUnload : 1; 421 unsigned mInPermitUnloadPrompt : 1; 422 423 #ifdef NS_PRINTING 424 unsigned mClosingWhilePrinting : 1; 425 unsigned mCloseWindowAfterPrint : 1; 426 427 # if NS_PRINT_PREVIEW 428 RefPtr<nsPrintJob> mPrintJob; 429 # endif // NS_PRINT_PREVIEW 430 431 #endif // NS_PRINTING 432 433 /* character set member data */ 434 int32_t mReloadEncodingSource; 435 const Encoding* mReloadEncoding; 436 437 bool mIsPageMode; 438 bool mInitializedForPrintPreview; 439 bool mHidden; 440 }; 441 442 class nsDocumentShownDispatcher : public Runnable { 443 public: 444 explicit nsDocumentShownDispatcher(nsCOMPtr<Document> aDocument) 445 : Runnable("nsDocumentShownDispatcher"), mDocument(aDocument) {} 446 447 NS_IMETHOD Run() override; 448 449 private: 450 nsCOMPtr<Document> mDocument; 451 }; 452 453 //------------------------------------------------------------------ 454 // nsDocumentViewer 455 //------------------------------------------------------------------ 456 457 //------------------------------------------------------------------ 458 already_AddRefed<nsIDocumentViewer> NS_NewDocumentViewer() { 459 return MakeAndAddRef<nsDocumentViewer>(); 460 } 461 462 void nsDocumentViewer::PrepareToStartLoad() { 463 MOZ_DIAGNOSTIC_ASSERT(!GetIsPrintPreview(), 464 "Print preview tab should never navigate"); 465 466 mStopped = false; 467 mLoaded = false; 468 mDeferredWindowClose = false; 469 470 #ifdef NS_PRINTING 471 mClosingWhilePrinting = false; 472 473 // Make sure we have destroyed it and cleared the data member 474 if (mPrintJob) { 475 mPrintJob->Destroy(); 476 mPrintJob = nullptr; 477 } 478 479 #endif // NS_PRINTING 480 } 481 482 nsDocumentViewer::nsDocumentViewer() 483 : mParentWidget(nullptr), 484 mNumURLStarts(0), 485 mDestroyBlockedCount(0), 486 mStopped(false), 487 mLoaded(false), 488 mDeferredWindowClose(false), 489 mIsSticky(true), 490 mInPermitUnload(false), 491 mInPermitUnloadPrompt(false), 492 #ifdef NS_PRINTING 493 mClosingWhilePrinting(false), 494 mCloseWindowAfterPrint(false), 495 #endif // NS_PRINTING 496 mReloadEncodingSource(kCharsetUninitialized), 497 mReloadEncoding(nullptr), 498 mIsPageMode(false), 499 mInitializedForPrintPreview(false), 500 mHidden(false) { 501 PrepareToStartLoad(); 502 } 503 504 NS_IMPL_ADDREF(nsDocumentViewer) 505 NS_IMPL_RELEASE(nsDocumentViewer) 506 507 NS_INTERFACE_MAP_BEGIN(nsDocumentViewer) 508 NS_INTERFACE_MAP_ENTRY(nsIDocumentViewer) 509 NS_INTERFACE_MAP_ENTRY(nsIDocumentViewerEdit) 510 NS_INTERFACE_MAP_ENTRY(nsIDocumentViewerPrint) 511 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentViewer) 512 #ifdef NS_PRINTING 513 NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPrint) 514 #endif 515 NS_INTERFACE_MAP_END 516 517 nsDocumentViewer::~nsDocumentViewer() { 518 if (mDocument) { 519 Close(nullptr); 520 mDocument->Destroy(); 521 } 522 523 #ifdef NS_PRINTING 524 if (mPrintJob) { 525 mPrintJob->Destroy(); 526 mPrintJob = nullptr; 527 } 528 #endif 529 530 MOZ_RELEASE_ASSERT(mDestroyBlockedCount == 0); 531 NS_ASSERTION(!mPresShell && !mPresContext, 532 "User did not call nsIDocumentViewer::Destroy"); 533 if (mPresShell || mPresContext) { 534 // Make sure we don't hand out a reference to the content viewer to 535 // the SHEntry! 536 mSHEntry = nullptr; 537 538 Destroy(); 539 } 540 541 if (mSelectionListener) { 542 mSelectionListener->Disconnect(); 543 } 544 545 RemoveFocusListener(); 546 547 // XXX(?) Revoke pending invalidate events 548 } 549 550 /* 551 * This method is called by the Document Loader once a document has 552 * been created for a particular data stream... The content viewer 553 * must cache this document for later use when Init(...) is called. 554 * 555 * This method is also called when an out of band document.write() happens. 556 * In that case, the document passed in is the same as the previous document. 557 */ 558 /* virtual */ 559 void nsDocumentViewer::LoadStart(Document* aDocument) { 560 MOZ_ASSERT(aDocument); 561 562 if (!mDocument) { 563 mDocument = aDocument; 564 } 565 } 566 567 void nsDocumentViewer::RemoveFocusListener() { 568 if (RefPtr<nsDocViewerFocusListener> oldListener = 569 std::move(mFocusListener)) { 570 oldListener->Disconnect(); 571 if (mDocument) { 572 mDocument->RemoveEventListener(u"focus"_ns, oldListener, false); 573 mDocument->RemoveEventListener(u"blur"_ns, oldListener, false); 574 } 575 } 576 } 577 578 void nsDocumentViewer::ReinitializeFocusListener() { 579 RemoveFocusListener(); 580 mFocusListener = new nsDocViewerFocusListener(this); 581 if (mDocument) { 582 mDocument->AddEventListener(u"focus"_ns, mFocusListener, false, false); 583 mDocument->AddEventListener(u"blur"_ns, mFocusListener, false, false); 584 } 585 } 586 587 nsresult nsDocumentViewer::SyncParentSubDocMap() { 588 nsCOMPtr<nsIDocShell> docShell(mContainer); 589 if (!docShell) { 590 return NS_OK; 591 } 592 593 nsCOMPtr<nsPIDOMWindowOuter> pwin(docShell->GetWindow()); 594 if (!mDocument || !pwin) { 595 return NS_OK; 596 } 597 598 nsCOMPtr<Element> element = pwin->GetFrameElementInternal(); 599 if (!element) { 600 return NS_OK; 601 } 602 603 nsCOMPtr<nsIDocShellTreeItem> parent; 604 docShell->GetInProcessParent(getter_AddRefs(parent)); 605 606 nsCOMPtr<nsPIDOMWindowOuter> parent_win = 607 parent ? parent->GetWindow() : nullptr; 608 if (!parent_win) { 609 return NS_OK; 610 } 611 612 nsCOMPtr<Document> parent_doc = parent_win->GetDoc(); 613 if (!parent_doc) { 614 return NS_OK; 615 } 616 617 if (mDocument && parent_doc->GetSubDocumentFor(element) != mDocument && 618 parent_doc->EventHandlingSuppressed()) { 619 mDocument->SuppressEventHandling(parent_doc->EventHandlingSuppressed()); 620 } 621 return parent_doc->SetSubDocumentFor(element, mDocument); 622 } 623 624 NS_IMETHODIMP 625 nsDocumentViewer::SetContainer(nsIDocShell* aContainer) { 626 mContainer = static_cast<nsDocShell*>(aContainer); 627 628 // We're loading a new document into the window where this document 629 // viewer lives, sync the parent document's frame element -> sub 630 // document map 631 632 return SyncParentSubDocMap(); 633 } 634 635 NS_IMETHODIMP 636 nsDocumentViewer::GetContainer(nsIDocShell** aResult) { 637 NS_ENSURE_ARG_POINTER(aResult); 638 639 nsCOMPtr<nsIDocShell> container(mContainer); 640 container.swap(*aResult); 641 return NS_OK; 642 } 643 644 NS_IMETHODIMP 645 nsDocumentViewer::Init(nsIWidget* aParentWidget, 646 const LayoutDeviceIntRect& aBounds, 647 WindowGlobalChild* aActor) { 648 return InitInternal(aParentWidget, nullptr, aActor, aBounds, true); 649 } 650 651 nsresult nsDocumentViewer::InitPresentationStuff(bool aDoInitialReflow) { 652 // We assert this because initializing the pres shell could otherwise cause 653 // re-entrancy into nsDocumentViewer methods, which might cause a different 654 // pres shell to be created. Callers of InitPresentationStuff should ensure 655 // the call is appropriately bounded by an nsAutoScriptBlocker to decide 656 // when it is safe for these re-entrant calls to be made. 657 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), 658 "InitPresentationStuff must only be called when scripts are " 659 "blocked"); 660 661 #ifdef NS_PRINTING 662 // When getting printed, either for print or print preview, the print job 663 // takes care of setting up the presentation of the document. 664 if (mPrintJob) { 665 return NS_OK; 666 } 667 #endif 668 669 NS_ASSERTION(!mPresShell, "Someone should have destroyed the presshell!"); 670 671 // Now make the shell for the document 672 nsCOMPtr<Document> doc = mDocument; 673 RefPtr<nsPresContext> presContext = mPresContext; 674 mPresShell = doc->CreatePresShell(presContext, FindContainerFrame()); 675 if (!mPresShell) { 676 return NS_ERROR_FAILURE; 677 } 678 679 AttachToTopLevelWidget(); 680 681 if (aDoInitialReflow) { 682 // Since Initialize() will create frames for *all* items 683 // that are currently in the document tree, we need to flush 684 // any pending notifications to prevent the content sink from 685 // duplicating layout frames for content it has added to the tree 686 // but hasn't notified the document about. (Bug 154018) 687 // 688 // Note that we are flushing before we add mPresShell as an observer 689 // to avoid bogus notifications. 690 mDocument->FlushPendingNotifications(FlushType::ContentAndNotify); 691 } 692 693 mPresShell->BeginObservingDocument(); 694 695 // Initialize our view manager 696 697 { 698 int32_t p2a = mPresContext->AppUnitsPerDevPixel(); 699 MOZ_ASSERT( 700 p2a == 701 mPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()); 702 703 const nsSize size = LayoutDevicePixel::ToAppUnits(mBounds.Size(), p2a); 704 mPresContext->SetInitialVisibleArea(nsRect(nsPoint(), size)); 705 // We rely on the default zoom not being initialized until here. 706 mPresContext->RecomputeBrowsingContextDependentData(); 707 } 708 709 if (mWindow && mDocument->IsTopLevelContentDocument()) { 710 // Set initial safe area insets 711 LayoutDeviceIntMargin windowSafeAreaInsets; 712 LayoutDeviceIntRect windowRect = mWindow->GetScreenBounds(); 713 if (nsCOMPtr<nsIScreen> screen = mWindow->GetWidgetScreen()) { 714 windowSafeAreaInsets = nsContentUtils::GetWindowSafeAreaInsets( 715 screen, mWindow->GetSafeAreaInsets(), windowRect); 716 } 717 mPresContext->SetSafeAreaInsets(windowSafeAreaInsets); 718 } 719 720 if (aDoInitialReflow) { 721 RefPtr<PresShell> presShell = mPresShell; 722 // Initial reflow 723 presShell->Initialize(); 724 } 725 726 // now register ourselves as a selection listener, so that we get 727 // called when the selection changes in the window 728 if (!mSelectionListener) { 729 mSelectionListener = new nsDocViewerSelectionListener(this); 730 } 731 732 RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection(); 733 if (!selection) { 734 return NS_ERROR_FAILURE; 735 } 736 737 selection->AddSelectionListener(mSelectionListener); 738 739 ReinitializeFocusListener(); 740 741 if (aDoInitialReflow && mDocument) { 742 nsCOMPtr<Document> document = mDocument; 743 document->ScrollToRef(); 744 } 745 746 return NS_OK; 747 } 748 749 static already_AddRefed<nsPresContext> CreatePresContext( 750 Document* aDocument, nsPresContext::nsPresContextType aType, 751 nsIFrame* aContainerFrame) { 752 RefPtr<nsPresContext> result = aContainerFrame 753 ? new nsPresContext(aDocument, aType) 754 : new nsRootPresContext(aDocument, aType); 755 756 return result.forget(); 757 } 758 759 //----------------------------------------------- 760 // This method can be used to initial the "presentation" 761 // The aDoCreation indicates whether it should create 762 // all the new objects or just initialize the existing ones 763 nsresult nsDocumentViewer::InitInternal( 764 nsIWidget* aParentWidget, nsISupports* aState, WindowGlobalChild* aActor, 765 const LayoutDeviceIntRect& aBounds, bool aDoCreation, 766 bool aNeedMakeCX /*= true*/, bool aForceSetNewDocument /* = true*/) { 767 // We don't want any scripts to run here. That can cause flushing, 768 // which can cause reentry into initialization of this document viewer, 769 // which would be disastrous. 770 nsAutoScriptBlocker blockScripts; 771 772 mParentWidget = aParentWidget; // not ref counted 773 774 mBounds = aBounds; 775 776 nsresult rv = NS_OK; 777 NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER); 778 779 nsSubDocumentFrame* containerFrame = FindContainerFrame(); 780 781 bool makeCX = false; 782 if (aDoCreation) { 783 nsresult rv = CreateDeviceContext(containerFrame); 784 NS_ENSURE_SUCCESS(rv, rv); 785 786 // XXXbz this is a nasty hack to do with the fact that we create 787 // presentations both in Init() and in Show()... Ideally we would only do 788 // it in one place (Show()) and require that callers call init(), open(), 789 // show() in that order or something. 790 if (!mPresContext && 791 (aParentWidget || containerFrame || mDocument->IsBeingUsedAsImage() || 792 (mDocument->GetDisplayDocument() && 793 mDocument->GetDisplayDocument()->GetPresShell()))) { 794 // Create presentation context 795 if (mIsPageMode) { 796 // Presentation context already created in SetPageModeForTesting which 797 // is calling this method 798 } else { 799 mPresContext = CreatePresContext( 800 mDocument, nsPresContext::eContext_Galley, containerFrame); 801 } 802 NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY); 803 804 nsresult rv = mPresContext->Init(mDeviceContext); 805 if (NS_FAILED(rv)) { 806 mPresContext = nullptr; 807 return rv; 808 } 809 810 #if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW) 811 makeCX = !GetIsPrintPreview() && 812 aNeedMakeCX; // needs to be true except when we are already in 813 // PP or we are enabling/disabling paginated mode. 814 #else 815 makeCX = true; 816 #endif 817 } 818 819 if (mPresContext) { 820 // Create the ViewManager and Root View... 821 822 // We must do this before we tell the script global object about 823 // this new document since doing that will cause us to re-enter 824 // into nsSubDocumentFrame code through reflows caused by 825 // FlushPendingNotifications() calls down the road... 826 Hide(); 827 828 #ifdef NS_PRINT_PREVIEW 829 if (mIsPageMode) { 830 // I'm leaving this in a broken state for the moment; we should 831 // be measuring/scaling with the print device context, not the 832 // screen device context, but this is good enough to allow 833 // printing reftests to work. 834 double pageWidth = 0, pageHeight = 0; 835 mPresContext->GetPrintSettings()->GetEffectivePageSize(&pageWidth, 836 &pageHeight); 837 mPresContext->SetPageSize( 838 nsSize(mPresContext->CSSTwipsToAppUnits(NSToIntFloor(pageWidth)), 839 mPresContext->CSSTwipsToAppUnits(NSToIntFloor(pageHeight)))); 840 mPresContext->SetIsRootPaginatedDocument(true); 841 mPresContext->SetPageScale(1.0f); 842 } 843 #endif 844 } else { 845 // Avoid leaking the old viewer. 846 if (mPreviousViewer) { 847 mPreviousViewer->Destroy(); 848 mPreviousViewer = nullptr; 849 } 850 } 851 } 852 853 nsCOMPtr<nsIInterfaceRequestor> requestor(mContainer); 854 if (requestor) { 855 // Set script-context-owner in the document 856 857 nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(requestor); 858 859 if (window) { 860 nsCOMPtr<Document> curDoc = window->GetExtantDoc(); 861 if (aForceSetNewDocument || curDoc != mDocument) { 862 rv = window->SetNewDocument(mDocument, aState, false, aActor); 863 if (NS_FAILED(rv)) { 864 Destroy(); 865 return rv; 866 } 867 } 868 } 869 } 870 871 if (aDoCreation && mPresContext) { 872 rv = InitPresentationStuff(!makeCX); 873 } 874 875 return rv; 876 } 877 878 void nsDocumentViewer::SetNavigationTiming(nsDOMNavigationTiming* timing) { 879 NS_ASSERTION(mDocument, "Must have a document to set navigation timing."); 880 if (mDocument) { 881 mDocument->SetNavigationTiming(timing); 882 } 883 } 884 885 // 886 // LoadComplete(aStatus) 887 // 888 // aStatus - The status returned from loading the document. 889 // 890 // This method is called by the container when the document has been 891 // completely loaded. 892 // 893 NS_IMETHODIMP 894 nsDocumentViewer::LoadComplete(nsresult aStatus) { 895 /* We need to protect ourself against auto-destruction in case the 896 window is closed while processing the OnLoad event. See bug 897 http://bugzilla.mozilla.org/show_bug.cgi?id=78445 for more 898 explanation. 899 */ 900 RefPtr<nsDocumentViewer> kungFuDeathGrip(this); 901 902 // Flush out layout so it's up-to-date by the time onload is called. 903 // Note that this could destroy the window, so do this before 904 // checking for our mDocument and its window. 905 if (mPresShell && !mStopped) { 906 // Hold strong ref because this could conceivably run script 907 RefPtr<PresShell> presShell = mPresShell; 908 presShell->FlushPendingNotifications(FlushType::Layout); 909 } 910 911 nsresult rv = NS_OK; 912 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); 913 914 // First, get the window from the document... 915 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow(); 916 RefPtr<nsDocShell> docShell = nsDocShell::Cast(window->GetDocShell()); 917 918 mLoaded = true; 919 920 // Now, fire either an OnLoad or OnError event to the document... 921 bool restoring = false; 922 // XXXbz imagelib kills off the document load for a full-page image with 923 // NS_ERROR_PARSED_DATA_CACHED if it's in the cache. So we want to treat 924 // that one as a success code; otherwise whether we fire onload for the image 925 // will depend on whether it's cached! 926 if (window && 927 (NS_SUCCEEDED(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED)) { 928 // If this code changes, the code in nsDocLoader::DocLoaderIsEmpty 929 // that fires load events for document.open() cases might need to 930 // be updated too. 931 nsEventStatus status = nsEventStatus_eIgnore; 932 WidgetEvent event(true, eLoad); 933 event.mFlags.mBubbles = false; 934 event.mFlags.mCancelable = false; 935 // XXX Dispatching to |window|, but using |document| as the target. 936 event.mTarget = mDocument; 937 938 // If the document presentation is being restored, we don't want to fire 939 // onload to the document content since that would likely confuse scripts 940 // on the page. 941 942 NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED); 943 944 // Unfortunately, docShell->GetRestoringDocument() might no longer be set 945 // correctly. In particular, it can be false by now if someone took it upon 946 // themselves to block onload from inside restoration and unblock it later. 947 // But we can detect the restoring case very simply: by whether our 948 // document's readyState is COMPLETE. 949 restoring = 950 (mDocument->GetReadyStateEnum() == Document::READYSTATE_COMPLETE) && 951 !mDocument->InitialAboutBlankLoadCompleting(); 952 if (!restoring) { 953 NS_ASSERTION( 954 mDocument->GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE || 955 // test_stricttransportsecurity.html has old-style 956 // docshell-generated about:blank docs reach this code! 957 (mDocument->GetReadyStateEnum() == 958 Document::READYSTATE_COMPLETE && 959 mDocument->InitialAboutBlankLoadCompleting()), 960 "Bad readystate"); 961 #ifdef DEBUG 962 bool docShellThinksWeAreRestoring; 963 docShell->GetRestoringDocument(&docShellThinksWeAreRestoring); 964 MOZ_ASSERT(!docShellThinksWeAreRestoring, 965 "How can docshell think we are restoring if we don't have a " 966 "READYSTATE_COMPLETE document?"); 967 #endif // DEBUG 968 nsCOMPtr<Document> d = mDocument; 969 if (!mDocument->InitialAboutBlankLoadCompleting()) { 970 mDocument->SetReadyStateInternal(Document::READYSTATE_COMPLETE); 971 } 972 973 RefPtr<nsDOMNavigationTiming> timing(d->GetNavigationTiming()); 974 if (timing) { 975 timing->NotifyLoadEventStart(); 976 } 977 978 // Dispatch observer notification to notify observers document load is 979 // complete. 980 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 981 if (os) { 982 nsIPrincipal* principal = d->NodePrincipal(); 983 os->NotifyObservers(ToSupports(d), 984 principal->IsSystemPrincipal() 985 ? "chrome-document-loaded" 986 : "content-document-loaded", 987 nullptr); 988 } 989 990 nsPIDOMWindowInner* innerWindow = window->GetCurrentInnerWindow(); 991 d->SetLoadEventFiring(true); 992 RefPtr<nsPresContext> presContext = mPresContext; 993 // MOZ_KnownLive due to bug 1506441 994 EventDispatcher::Dispatch( 995 MOZ_KnownLive(nsGlobalWindowOuter::Cast(window)), presContext, &event, 996 nullptr, &status); 997 d->SetLoadEventFiring(false); 998 999 if (timing) { 1000 timing->NotifyLoadEventEnd(); 1001 } 1002 1003 if (innerWindow) { 1004 innerWindow->QueuePerformanceNavigationTiming(); 1005 } 1006 } 1007 } else { 1008 // XXX: Should fire error event to the document... 1009 1010 // If our load was explicitly aborted, then we want to set our 1011 // readyState to COMPLETE, and fire a readystatechange event. 1012 if (aStatus == NS_BINDING_ABORTED && mDocument) { 1013 mDocument->NotifyAbortedLoad(); 1014 } 1015 } 1016 1017 // Notify the document that it has been shown (regardless of whether 1018 // it was just loaded). Note: mDocument may be null now if the above 1019 // firing of onload caused the document to unload. Or, mDocument may not be 1020 // the "current active" document, if the above firing of onload caused our 1021 // docshell to navigate away. NOTE: In this latter scenario, it's likely that 1022 // we fired pagehide (when navigating away) without ever having fired 1023 // pageshow, and that's pretty broken... Fortunately, this should be rare. 1024 // (It requires us to spin the event loop in onload handler, e.g. via sync 1025 // XHR, in order for the navigation-away to happen before onload completes.) 1026 // We skip firing pageshow if we're currently handling unload, or if loading 1027 // was explicitly aborted. 1028 if (mDocument && mDocument->IsCurrentActiveDocument() && 1029 aStatus != NS_BINDING_ABORTED) { 1030 // Re-get window, since it might have changed during above firing of onload 1031 window = mDocument->GetWindow(); 1032 if (window) { 1033 docShell = nsDocShell::Cast(window->GetDocShell()); 1034 bool isInUnload; 1035 if (docShell && NS_SUCCEEDED(docShell->GetIsInUnload(&isInUnload)) && 1036 !isInUnload) { 1037 mDocument->OnPageShow(restoring, nullptr); 1038 } 1039 } 1040 } 1041 1042 if (!mStopped) { 1043 if (mDocument) { 1044 // This is the final attempt to scroll to an anchor / text directive. 1045 // This is the last iteration of the algorithm described in the spec for 1046 // trying to scroll to a fragment. 1047 // https://html.spec.whatwg.org/#try-to-scroll-to-the-fragment 1048 nsCOMPtr<Document> document = mDocument; 1049 document->ScrollToRef(); 1050 } 1051 1052 // Now that the document has loaded, we can tell the presshell 1053 // to unsuppress painting. 1054 if (mPresShell) { 1055 if (mDocument && mDocument->IsInitialDocument() && docShell && 1056 !docShell->HasStartedLoadingOtherThanInitialBlankURI()) { 1057 // Delay paint unsuppression in case a new load elsewhere is started 1058 // in the same task that permitted the initial about:blank to fire 1059 // its load event. This is important for the front end, which assumes 1060 // that it's performance-wise OK to create an empty XUL browser, 1061 // append it in a document, and only then make it start navigating 1062 // away from the initial about:blank. 1063 nsCOMPtr<nsIRunnable> task = NewRunnableMethod<RefPtr<PresShell>>( 1064 "nsDocShell::UnsuppressPaintingIfNoNavigationAwayFromAboutBlank", 1065 docShell, 1066 &nsDocShell::UnsuppressPaintingIfNoNavigationAwayFromAboutBlank, 1067 mPresShell); 1068 mDocument->Dispatch(task.forget()); 1069 } else { 1070 RefPtr<PresShell> presShell = mPresShell; 1071 presShell->UnsuppressPainting(); 1072 // mPresShell could have been removed now, see bug 378682/421432 1073 if (mPresShell) { 1074 mPresShell->LoadComplete(); 1075 } 1076 } 1077 } 1078 } 1079 1080 // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives 1081 // Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment: 1082 // 2.1 If the user agent has reason to believe the user is no longer 1083 // interested in scrolling to the fragment, then: 1084 // 2.1.1 Set pending text directives to null. 1085 // 1086 // Gecko's implementation differs from the spec (ie., it implements its 1087 // intention but doesn't follow step by step), therefore the mentioned steps 1088 // are not applied in the same manner. 1089 // However, this should be the right place to do this. 1090 if (mDocument) { 1091 mDocument->FragmentDirective()->ClearUninvokedDirectives(); 1092 } 1093 if (mDocument && !restoring) { 1094 mDocument->LoadEventFired(); 1095 } 1096 1097 // It's probably a good idea to GC soon since we have finished loading. 1098 nsJSContext::PokeGC( 1099 JS::GCReason::LOAD_END, 1100 mDocument ? mDocument->GetWrapperPreserveColor() : nullptr); 1101 1102 #ifdef NS_PRINTING 1103 // Check to see if someone tried to print during the load 1104 if (window) { 1105 auto* outerWin = nsGlobalWindowOuter::Cast(window); 1106 outerWin->StopDelayingPrintingUntilAfterLoad(); 1107 if (outerWin->DelayedPrintUntilAfterLoad()) { 1108 // We call into the inner because it ensures there's an active document 1109 // and such, and it also waits until the whole thing completes, which is 1110 // nice because it allows us to close if needed right here. 1111 if (RefPtr inner = 1112 nsGlobalWindowInner::Cast(window->GetCurrentInnerWindow())) { 1113 inner->Print(IgnoreErrors()); 1114 } 1115 if (outerWin->DelayedCloseForPrinting()) { 1116 outerWin->Close(); 1117 } 1118 } else { 1119 MOZ_ASSERT(!outerWin->DelayedCloseForPrinting()); 1120 } 1121 } 1122 #endif 1123 1124 return rv; 1125 } 1126 1127 bool nsDocumentViewer::GetLoadCompleted() { return mLoaded; } 1128 1129 bool nsDocumentViewer::GetIsStopped() { return mStopped; } 1130 1131 NS_IMETHODIMP 1132 nsDocumentViewer::PermitUnload(PermitUnloadAction aAction, 1133 bool* aPermitUnload) { 1134 // We're going to be running JS and nested event loops, which could cause our 1135 // DocShell to be destroyed. Make sure we stay alive until the end of the 1136 // function. 1137 RefPtr<nsDocumentViewer> kungFuDeathGrip(this); 1138 1139 if (StaticPrefs::dom_disable_beforeunload()) { 1140 aAction = eDontPromptAndUnload; 1141 } 1142 1143 *aPermitUnload = true; 1144 1145 NS_ENSURE_STATE(mContainer); 1146 1147 RefPtr<BrowsingContext> bc = mContainer->GetBrowsingContext(); 1148 if (!bc) { 1149 return NS_OK; 1150 } 1151 1152 if (bc->GetIsDocumentPiP()) { 1153 // https://wicg.github.io/document-picture-in-picture/#close-document-pip-window 1154 return NS_OK; 1155 } 1156 1157 // Per spec, we need to increase the ignore-opens-during-unload counter while 1158 // dispatching the "beforeunload" event on both the document we're currently 1159 // dispatching the event to and the document that we explicitly asked to 1160 // unload. 1161 IgnoreOpensDuringUnload ignoreOpens(mDocument); 1162 1163 bool foundBlocker = false; 1164 bool foundOOPListener = false; 1165 bc->PreOrderWalk([&](BrowsingContext* aBC) { 1166 if (!aBC->IsInProcess()) { 1167 WindowContext* wc = aBC->GetCurrentWindowContext(); 1168 if (wc && wc->NeedsBeforeUnload()) { 1169 foundOOPListener = true; 1170 } 1171 } else if (aBC->GetDocShell()) { 1172 nsCOMPtr<nsIDocumentViewer> viewer(aBC->GetDocShell()->GetDocViewer()); 1173 if (viewer && viewer->DispatchBeforeUnload() != eContinue) { 1174 foundBlocker = true; 1175 } 1176 } 1177 }); 1178 1179 if (!foundOOPListener) { 1180 if (!foundBlocker) { 1181 return NS_OK; 1182 } 1183 if (aAction != ePrompt) { 1184 *aPermitUnload = aAction == eDontPromptAndUnload; 1185 return NS_OK; 1186 } 1187 } 1188 1189 // NB: we nullcheck mDocument because it might now be dead as a result of 1190 // the event being dispatched. 1191 RefPtr<WindowGlobalChild> wgc(mDocument ? mDocument->GetWindowGlobalChild() 1192 : nullptr); 1193 if (!wgc) { 1194 return NS_OK; 1195 } 1196 1197 nsAutoSyncOperation sync(mDocument, SyncOperationBehavior::eSuspendInput); 1198 AutoSuppressEventHandlingAndSuspend seh(bc->Group()); 1199 1200 mInPermitUnloadPrompt = true; 1201 1202 bool done = false; 1203 wgc->SendCheckPermitUnload( 1204 foundBlocker, aAction, 1205 [&](bool aPermit) { 1206 done = true; 1207 *aPermitUnload = aPermit; 1208 }, 1209 [&](auto) { 1210 // If the prompt aborted, we tell our consumer that it is not allowed 1211 // to unload the page. One reason that prompts abort is that the user 1212 // performed some action that caused the page to unload while our prompt 1213 // was active. In those cases we don't want our consumer to also unload 1214 // the page. 1215 // 1216 // XXX: Are there other cases where prompts can abort? Is it ok to 1217 // prevent unloading the page in those cases? 1218 done = true; 1219 *aPermitUnload = false; 1220 }); 1221 1222 SpinEventLoopUntil("nsDocumentViewer::PermitUnload"_ns, 1223 [&]() { return done; }); 1224 1225 mInPermitUnloadPrompt = false; 1226 return NS_OK; 1227 } 1228 1229 MOZ_CAN_RUN_SCRIPT_BOUNDARY PermitUnloadResult 1230 nsDocumentViewer::DispatchBeforeUnload() { 1231 AutoDontWarnAboutSyncXHR disableSyncXHRWarning; 1232 1233 if (!mDocument || mInPermitUnload || mInPermitUnloadPrompt || !mContainer) { 1234 return eContinue; 1235 } 1236 1237 // First, get the script global object from the document... 1238 RefPtr<nsGlobalWindowOuter> window = 1239 nsGlobalWindowOuter::Cast(mDocument->GetWindow()); 1240 if (!window) { 1241 // This is odd, but not fatal 1242 NS_WARNING("window not set for document!"); 1243 return eContinue; 1244 } 1245 1246 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "This is unsafe"); 1247 1248 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#prompt-to-unload-a-document 1249 // Create an RAII object on mDocument that will increment the 1250 // should-ignore-opens-during-unload counter on initialization 1251 // and decrement it again when it goes out of score (regardless 1252 // of how we exit this function). 1253 IgnoreOpensDuringUnload ignoreOpens(mDocument); 1254 1255 // Now, fire an BeforeUnload event to the document and see if it's ok 1256 // to unload... 1257 nsPresContext* presContext = mDocument->GetPresContext(); 1258 auto event = MakeRefPtr<BeforeUnloadEvent>(mDocument, presContext, nullptr); 1259 event->InitEvent(u"beforeunload"_ns, false, true); 1260 1261 // Dispatching to |window|, but using |document| as the target. 1262 event->SetTarget(mDocument); 1263 event->SetTrusted(true); 1264 1265 // In evil cases we might be destroyed while handling the 1266 // onbeforeunload event, don't let that happen. (see also bug#331040) 1267 RefPtr<nsDocumentViewer> kungFuDeathGrip(this); 1268 1269 { 1270 // Never permit popups from the beforeunload handler, no matter 1271 // how we get here. 1272 AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true); 1273 1274 RefPtr<BrowsingContext> bc = mContainer->GetBrowsingContext(); 1275 NS_ASSERTION(bc, "should have a browsing context in document viewer"); 1276 1277 // Never permit dialogs from the beforeunload handler 1278 nsGlobalWindowOuter::TemporarilyDisableDialogs disableDialogs(bc); 1279 1280 Document::PageUnloadingEventTimeStamp timestamp(mDocument); 1281 1282 mInPermitUnload = true; 1283 RefPtr<nsPresContext> presContext = mPresContext; 1284 EventDispatcher::DispatchDOMEvent(window, nullptr, event, presContext, 1285 nullptr); 1286 mInPermitUnload = false; 1287 } 1288 1289 nsAutoString text; 1290 event->GetReturnValue(text); 1291 1292 // NB: we nullcheck mDocument because it might now be dead as a result of 1293 // the event being dispatched. 1294 if (window->AreDialogsEnabled() && mDocument && 1295 !(mDocument->GetSandboxFlags() & SANDBOXED_MODALS) && 1296 (!StaticPrefs::dom_require_user_interaction_for_beforeunload() || 1297 mDocument->ChromeRulesEnabled() || mDocument->UserHasInteracted()) && 1298 (event->WidgetEventPtr()->DefaultPrevented() || !text.IsEmpty())) { 1299 return eCanceledByBeforeUnload; 1300 } 1301 return eContinue; 1302 } 1303 1304 NS_IMETHODIMP 1305 nsDocumentViewer::GetBeforeUnloadFiring(bool* aInEvent) { 1306 *aInEvent = mInPermitUnload; 1307 return NS_OK; 1308 } 1309 1310 NS_IMETHODIMP 1311 nsDocumentViewer::GetInPermitUnload(bool* aInEvent) { 1312 *aInEvent = mInPermitUnloadPrompt; 1313 return NS_OK; 1314 } 1315 1316 NS_IMETHODIMP 1317 nsDocumentViewer::PageHide(bool aIsUnload) { 1318 AutoDontWarnAboutSyncXHR disableSyncXHRWarning; 1319 1320 mHidden = true; 1321 1322 if (!mDocument) { 1323 return NS_ERROR_NULL_POINTER; 1324 } 1325 1326 if (aIsUnload) { 1327 // Poke the GC. The window might be collectable garbage now. 1328 nsJSContext::PokeGC(JS::GCReason::PAGE_HIDE, 1329 mDocument->GetWrapperPreserveColor(), 1330 TimeDuration::FromMilliseconds( 1331 StaticPrefs::javascript_options_gc_delay() * 2)); 1332 } 1333 1334 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#unload-a-document 1335 // Create an RAII object on mDocument that will increment the 1336 // should-ignore-opens-during-unload counter on initialization 1337 // and decrement it again when it goes out of scope. 1338 IgnoreOpensDuringUnload ignoreOpens(mDocument); 1339 1340 mDocument->OnPageHide(!aIsUnload, nullptr); 1341 1342 // inform the window so that the focus state is reset. 1343 NS_ENSURE_STATE(mDocument); 1344 nsPIDOMWindowOuter* window = mDocument->GetWindow(); 1345 if (window) { 1346 window->PageHidden(!aIsUnload); 1347 } 1348 1349 if (aIsUnload) { 1350 // if Destroy() was called during OnPageHide(), mDocument is nullptr. 1351 NS_ENSURE_STATE(mDocument); 1352 1353 // First, get the window from the document... 1354 RefPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow(); 1355 1356 if (!window) { 1357 // Fail if no window is available... 1358 NS_WARNING("window not set for document!"); 1359 return NS_ERROR_NULL_POINTER; 1360 } 1361 1362 // Now, fire an Unload event to the document... 1363 nsEventStatus status = nsEventStatus_eIgnore; 1364 WidgetEvent event(true, eUnload); 1365 event.mFlags.mBubbles = false; 1366 // XXX Dispatching to |window|, but using |document| as the target. 1367 event.mTarget = mDocument; 1368 1369 // Never permit popups from the unload handler, no matter how we get 1370 // here. 1371 AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true); 1372 1373 Document::PageUnloadingEventTimeStamp timestamp(mDocument); 1374 1375 RefPtr<nsPresContext> presContext = mPresContext; 1376 // MOZ_KnownLive due to bug 1506441 1377 EventDispatcher::Dispatch(MOZ_KnownLive(nsGlobalWindowOuter::Cast(window)), 1378 presContext, &event, nullptr, &status); 1379 } 1380 1381 // look for open menupopups and close them after the unload event, in case 1382 // the unload event listeners open any new popups 1383 nsContentUtils::HidePopupsInDocument(mDocument); 1384 1385 return NS_OK; 1386 } 1387 1388 static void AttachContainerRecurse(nsIDocShell* aShell) { 1389 nsCOMPtr<nsIDocumentViewer> viewer; 1390 aShell->GetDocViewer(getter_AddRefs(viewer)); 1391 if (viewer) { 1392 viewer->SetIsHidden(false); 1393 Document* doc = viewer->GetDocument(); 1394 if (doc) { 1395 doc->SetContainer(static_cast<nsDocShell*>(aShell)); 1396 } 1397 if (PresShell* presShell = viewer->GetPresShell()) { 1398 presShell->SetForwardingContainer(WeakPtr<nsDocShell>()); 1399 } 1400 } 1401 1402 // Now recurse through the children 1403 int32_t childCount; 1404 aShell->GetInProcessChildCount(&childCount); 1405 for (int32_t i = 0; i < childCount; ++i) { 1406 nsCOMPtr<nsIDocShellTreeItem> childItem; 1407 aShell->GetInProcessChildAt(i, getter_AddRefs(childItem)); 1408 nsCOMPtr<nsIDocShell> shell = do_QueryInterface(childItem); 1409 AttachContainerRecurse(shell); 1410 } 1411 } 1412 1413 NS_IMETHODIMP 1414 nsDocumentViewer::Open(nsISupports* aState, nsISHEntry* aSHEntry) { 1415 NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED); 1416 1417 if (mDocument) { 1418 mDocument->SetContainer(mContainer); 1419 } 1420 1421 nsresult rv = InitInternal(mParentWidget, aState, nullptr, mBounds, false); 1422 NS_ENSURE_SUCCESS(rv, rv); 1423 1424 mHidden = false; 1425 1426 if (mPresShell) { 1427 mPresShell->SetForwardingContainer(WeakPtr<nsDocShell>()); 1428 } 1429 1430 // Rehook the child presentations. The child shells are still in 1431 // session history, so get them from there. 1432 1433 if (aSHEntry) { 1434 nsCOMPtr<nsIDocShellTreeItem> item; 1435 int32_t itemIndex = 0; 1436 while (NS_SUCCEEDED( 1437 aSHEntry->ChildShellAt(itemIndex++, getter_AddRefs(item))) && 1438 item) { 1439 nsCOMPtr<nsIDocShell> shell = do_QueryInterface(item); 1440 AttachContainerRecurse(shell); 1441 } 1442 } 1443 1444 SyncParentSubDocMap(); 1445 1446 ReinitializeFocusListener(); 1447 1448 // XXX re-enable image animations once that works correctly 1449 1450 PrepareToStartLoad(); 1451 1452 // When loading a page from the bfcache with puppet widgets, we do the 1453 // widget attachment here (it is otherwise done in MakeWindow, which is 1454 // called for non-bfcache pages in the history, but not bfcache pages). 1455 // Attachment is necessary, since we get detached when another page 1456 // is browsed to. That is, if we are one page A, then when we go to 1457 // page B, we detach. So page A's view has no widget. If we then go 1458 // back to it, and it is in the bfcache, we will use that view, which 1459 // doesn't have a widget. The attach call here will properly attach us. 1460 AttachToTopLevelWidget(); 1461 1462 return NS_OK; 1463 } 1464 1465 NS_IMETHODIMP 1466 nsDocumentViewer::Close(nsISHEntry* aSHEntry) { 1467 // All callers are supposed to call close to break circular 1468 // references. If we do this stuff in the destructor, the 1469 // destructor might never be called (especially if we're being 1470 // used from JS. 1471 1472 mSHEntry = aSHEntry; 1473 1474 // Close is also needed to disable scripts during paint suppression, 1475 // since we transfer the existing global object to the new document 1476 // that is loaded. In the future, the global object may become a proxy 1477 // for an object that can be switched in and out so that we don't need 1478 // to disable scripts during paint suppression. 1479 1480 if (!mDocument) { 1481 return NS_OK; 1482 } 1483 1484 if (mSHEntry) { 1485 if (mBFCachePreventionObserver) { 1486 mBFCachePreventionObserver->Disconnect(); 1487 } 1488 mBFCachePreventionObserver = new BFCachePreventionObserver(mDocument); 1489 mDocument->AddMutationObserver(mBFCachePreventionObserver); 1490 } 1491 1492 #ifdef NS_PRINTING 1493 // A Close was called while we were printing 1494 // so don't clear the ScriptGlobalObject 1495 // or clear the mDocument below 1496 if (mPrintJob && !mClosingWhilePrinting) { 1497 mClosingWhilePrinting = true; 1498 } else 1499 #endif 1500 { 1501 // out of band cleanup of docshell 1502 mDocument->SetScriptGlobalObject(nullptr); 1503 1504 if (!mSHEntry && mDocument) { 1505 mDocument->RemovedFromDocShell(); 1506 } 1507 } 1508 1509 RemoveFocusListener(); 1510 return NS_OK; 1511 } 1512 1513 static void DetachContainerRecurse(nsIDocShell* aShell) { 1514 // Unhook this docshell's presentation 1515 aShell->SynchronizeLayoutHistoryState(); 1516 nsCOMPtr<nsIDocumentViewer> viewer; 1517 aShell->GetDocViewer(getter_AddRefs(viewer)); 1518 if (viewer) { 1519 if (Document* doc = viewer->GetDocument()) { 1520 doc->SetContainer(nullptr); 1521 } 1522 if (PresShell* presShell = viewer->GetPresShell()) { 1523 auto weakShell = static_cast<nsDocShell*>(aShell); 1524 presShell->SetForwardingContainer(weakShell); 1525 } 1526 } 1527 1528 // Now recurse through the children 1529 int32_t childCount; 1530 aShell->GetInProcessChildCount(&childCount); 1531 for (int32_t i = 0; i < childCount; ++i) { 1532 nsCOMPtr<nsIDocShellTreeItem> childItem; 1533 aShell->GetInProcessChildAt(i, getter_AddRefs(childItem)); 1534 nsCOMPtr<nsIDocShell> shell = do_QueryInterface(childItem); 1535 DetachContainerRecurse(shell); 1536 } 1537 } 1538 1539 NS_IMETHODIMP 1540 nsDocumentViewer::Destroy() { 1541 // Don't let the document get unloaded while we are printing. 1542 // this could happen if we hit the back button during printing. 1543 // We also keep the viewer from being cached in session history, since 1544 // we require all documents there to be sanitized. 1545 if (mDestroyBlockedCount != 0) { 1546 return NS_OK; 1547 } 1548 1549 #ifdef NS_PRINTING 1550 // Here is where we check to see if the document was still being prepared 1551 // for printing when it was asked to be destroy from someone externally 1552 // This usually happens if the document is unloaded while the user is in the 1553 // Print Dialog 1554 // 1555 // So we flip the bool to remember that the document is going away 1556 // and we can clean up and abort later after returning from the Print Dialog 1557 if (mPrintJob && mPrintJob->CheckBeforeDestroy()) { 1558 return NS_OK; 1559 } 1560 #endif 1561 1562 // We want to make sure to disconnect mBFCachePreventionObserver before we 1563 // Sanitize() below. 1564 if (mBFCachePreventionObserver) { 1565 mBFCachePreventionObserver->Disconnect(); 1566 mBFCachePreventionObserver = nullptr; 1567 } 1568 1569 if (mSHEntry && mDocument && !mDocument->IsBFCachingAllowed()) { 1570 // Just drop the SHEntry now and pretend like we never even tried to bfcache 1571 // this viewer. This should only happen when someone calls 1572 // DisallowBFCaching() after CanSavePresentation() already ran. Ensure that 1573 // the SHEntry has no viewer and its state is synced up. We want to do this 1574 // via a stack reference, in case those calls mess with our members. 1575 MOZ_LOG(gPageCacheLog, LogLevel::Debug, 1576 ("BFCache not allowed, dropping SHEntry")); 1577 nsCOMPtr<nsISHEntry> shEntry = std::move(mSHEntry); 1578 shEntry->SetDocumentViewer(nullptr); 1579 shEntry->SyncPresentationState(); 1580 } 1581 1582 // If we were told to put ourselves into session history instead of destroy 1583 // the presentation, do that now. 1584 if (mSHEntry) { 1585 if (mPresShell) { 1586 mPresShell->Freeze(); 1587 } 1588 1589 // Make sure the presentation isn't torn down by Hide(). 1590 mSHEntry->SetSticky(mIsSticky); 1591 mIsSticky = true; 1592 1593 // Clear our display items. 1594 if (nsSubDocumentFrame* f = FindContainerFrame()) { 1595 f->ClearDisplayItems(); 1596 } 1597 1598 Hide(); 1599 1600 // This is after Hide() so that the user doesn't see the inputs clear. 1601 if (mDocument) { 1602 mDocument->Sanitize(); 1603 } 1604 1605 // Reverse ownership. Do this *after* calling sanitize so that sanitize 1606 // doesn't cause mutations that make the SHEntry drop the presentation 1607 1608 // Grab a reference to mSHEntry before calling into things like 1609 // SyncPresentationState that might mess with our members. 1610 nsCOMPtr<nsISHEntry> shEntry = 1611 std::move(mSHEntry); // we'll need this below 1612 1613 MOZ_LOG(gPageCacheLog, LogLevel::Debug, 1614 ("Storing content viewer into cache entry")); 1615 shEntry->SetDocumentViewer(this); 1616 1617 // Always sync the presentation state. That way even if someone screws up 1618 // and shEntry has no window state at this point we'll be ok; we just won't 1619 // cache ourselves. 1620 shEntry->SyncPresentationState(); 1621 // XXX Synchronize layout history state to parent once bfcache is supported 1622 // in session-history-in-parent. 1623 1624 // Shut down accessibility for the document before we start to tear it down. 1625 #ifdef ACCESSIBILITY 1626 if (mPresShell) { 1627 a11y::DocAccessible* docAcc = mPresShell->GetDocAccessible(); 1628 if (docAcc) { 1629 docAcc->Shutdown(); 1630 } 1631 } 1632 #endif 1633 1634 // Break the link from the document/presentation to the docshell, so that 1635 // link traversals cannot affect the currently-loaded document. 1636 // When the presentation is restored, Open() and InitInternal() will reset 1637 // these pointers to their original values. 1638 1639 if (mDocument) { 1640 mDocument->SetContainer(nullptr); 1641 } 1642 if (mPresShell) { 1643 mPresShell->SetForwardingContainer(mContainer); 1644 } 1645 1646 // Do the same for our children. Note that we need to get the child 1647 // docshells from the SHEntry now; the docshell will have cleared them. 1648 nsCOMPtr<nsIDocShellTreeItem> item; 1649 int32_t itemIndex = 0; 1650 while (NS_SUCCEEDED( 1651 shEntry->ChildShellAt(itemIndex++, getter_AddRefs(item))) && 1652 item) { 1653 nsCOMPtr<nsIDocShell> shell = do_QueryInterface(item); 1654 DetachContainerRecurse(shell); 1655 } 1656 1657 return NS_OK; 1658 } 1659 1660 // The document was not put in the bfcache 1661 1662 // Protect against pres shell destruction running scripts and re-entrantly 1663 // creating a new presentation. 1664 nsAutoScriptBlocker scriptBlocker; 1665 1666 if (mPresShell) { 1667 DestroyPresShell(); 1668 } 1669 if (mDocument) { 1670 mDocument->Destroy(); 1671 mDocument = nullptr; 1672 } 1673 1674 // All callers are supposed to call destroy to break circular 1675 // references. If we do this stuff in the destructor, the 1676 // destructor might never be called (especially if we're being 1677 // used from JS. 1678 1679 #ifdef NS_PRINTING 1680 if (mPrintJob) { 1681 RefPtr<nsPrintJob> printJob = std::move(mPrintJob); 1682 # ifdef NS_PRINT_PREVIEW 1683 if (printJob->CreatedForPrintPreview()) { 1684 printJob->FinishPrintPreview(); 1685 } 1686 # endif 1687 printJob->Destroy(); 1688 MOZ_ASSERT(!mPrintJob, 1689 "mPrintJob shouldn't be recreated while destroying it"); 1690 } 1691 #endif 1692 1693 // Avoid leaking the old viewer. 1694 if (mPreviousViewer) { 1695 mPreviousViewer->Destroy(); 1696 mPreviousViewer = nullptr; 1697 } 1698 1699 mDeviceContext = nullptr; 1700 1701 if (mPresContext) { 1702 DestroyPresContext(); 1703 } 1704 1705 mWindow = nullptr; 1706 mContainer = WeakPtr<nsDocShell>(); 1707 1708 return NS_OK; 1709 } 1710 1711 NS_IMETHODIMP 1712 nsDocumentViewer::Stop(void) { 1713 NS_ASSERTION(mDocument, "Stop called too early or too late"); 1714 if (mDocument) { 1715 mDocument->StopDocumentLoad(); 1716 } 1717 1718 mStopped = true; 1719 1720 if (!mLoaded && mPresShell) { 1721 // Well, we might as well paint what we have so far. 1722 RefPtr<PresShell> presShell = mPresShell; // bug 378682 1723 presShell->UnsuppressPainting(); 1724 } 1725 1726 return NS_OK; 1727 } 1728 1729 NS_IMETHODIMP 1730 nsDocumentViewer::GetDOMDocument(Document** aResult) { 1731 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); 1732 nsCOMPtr<Document> document = mDocument; 1733 document.forget(aResult); 1734 return NS_OK; 1735 } 1736 1737 Document* nsDocumentViewer::GetDocument() { return mDocument; } 1738 1739 nsresult nsDocumentViewer::SetDocument(Document* aDocument) { 1740 // Assumptions: 1741 // 1742 // 1) this document viewer has been initialized with a call to Init(). 1743 // 2) the stylesheets associated with the document have been added 1744 // to the document. 1745 1746 // XXX Right now, this method assumes that the layout of the current 1747 // document hasn't started yet. More cleanup will probably be 1748 // necessary to make this method work for the case when layout *has* 1749 // occurred for the current document. 1750 // That work can happen when and if it is needed. 1751 1752 if (!aDocument) { 1753 return NS_ERROR_NULL_POINTER; 1754 } 1755 1756 return SetDocumentInternal(aDocument, false); 1757 } 1758 1759 NS_IMETHODIMP 1760 nsDocumentViewer::SetDocumentInternal(Document* aDocument, 1761 bool aForceReuseInnerWindow) { 1762 MOZ_ASSERT(aDocument); 1763 1764 // Set new container 1765 aDocument->SetContainer(mContainer); 1766 1767 if (mDocument != aDocument) { 1768 if (aForceReuseInnerWindow) { 1769 // Transfer the navigation timing information to the new document, since 1770 // we're keeping the same inner and hence should really have the same 1771 // timing information. 1772 aDocument->SetNavigationTiming(mDocument->GetNavigationTiming()); 1773 } 1774 1775 if (mDocument && 1776 (mDocument->IsStaticDocument() || aDocument->IsStaticDocument())) { 1777 nsContentUtils::AddScriptRunner(NewRunnableMethod( 1778 "Document::Destroy", mDocument, &Document::Destroy)); 1779 } 1780 1781 // Clear the list of old child docshells. Child docshells for the new 1782 // document will be constructed as frames are created. 1783 if (!aDocument->IsStaticDocument()) { 1784 nsCOMPtr<nsIDocShell> node(mContainer); 1785 if (node) { 1786 int32_t count; 1787 node->GetInProcessChildCount(&count); 1788 for (int32_t i = 0; i < count; ++i) { 1789 nsCOMPtr<nsIDocShellTreeItem> child; 1790 node->GetInProcessChildAt(0, getter_AddRefs(child)); 1791 node->RemoveChild(child); 1792 } 1793 } 1794 } 1795 1796 // Replace the old document with the new one. Do this only when 1797 // the new document really is a new document. 1798 mDocument = aDocument; 1799 1800 // Set the script global object on the new document 1801 nsCOMPtr<nsPIDOMWindowOuter> window = 1802 mContainer ? mContainer->GetWindow() : nullptr; 1803 if (window) { 1804 nsresult rv = 1805 window->SetNewDocument(aDocument, nullptr, aForceReuseInnerWindow); 1806 if (NS_FAILED(rv)) { 1807 Destroy(); 1808 return rv; 1809 } 1810 } 1811 } 1812 1813 nsresult rv = SyncParentSubDocMap(); 1814 NS_ENSURE_SUCCESS(rv, rv); 1815 1816 // Replace the current pres shell with a new shell for the new document 1817 1818 // Protect against pres shell destruction running scripts and re-entrantly 1819 // creating a new presentation. 1820 nsAutoScriptBlocker scriptBlocker; 1821 1822 if (mPresShell) { 1823 DestroyPresShell(); 1824 } 1825 1826 if (mPresContext) { 1827 DestroyPresContext(); 1828 1829 mWindow = nullptr; 1830 rv = InitInternal(mParentWidget, nullptr, nullptr, mBounds, true, true, 1831 false); 1832 } 1833 1834 return rv; 1835 } 1836 1837 PresShell* nsDocumentViewer::GetPresShell() { return mPresShell; } 1838 1839 nsPresContext* nsDocumentViewer::GetPresContext() { return mPresContext; } 1840 1841 NS_IMETHODIMP 1842 nsDocumentViewer::GetBounds(LayoutDeviceIntRect& aResult) { 1843 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); 1844 aResult = mBounds; 1845 return NS_OK; 1846 } 1847 1848 nsIDocumentViewer* nsDocumentViewer::GetPreviousViewer() { 1849 return mPreviousViewer; 1850 } 1851 1852 void nsDocumentViewer::SetPreviousViewer(nsIDocumentViewer* aViewer) { 1853 // NOTE: |Show| sets |mPreviousViewer| to null without calling this 1854 // function. 1855 1856 if (aViewer) { 1857 NS_ASSERTION(!mPreviousViewer, 1858 "can't set previous viewer when there already is one"); 1859 1860 // In a multiple chaining situation (which occurs when running a thrashing 1861 // test like i-bench or jrgm's tests with no delay), we can build up a 1862 // whole chain of viewers. In order to avoid this, we always set our 1863 // previous viewer to the MOST previous viewer in the chain, and then dump 1864 // the intermediate link from the chain. This ensures that at most only 2 1865 // documents are alive and undestroyed at any given time (the one that is 1866 // showing and the one that is loading with painting suppressed). It's very 1867 // important that if this ever gets changed the code before the 1868 // RestorePresentation call in nsDocShell::InternalLoad be changed 1869 // accordingly. 1870 // 1871 // Make sure we hold a strong ref to prevViewer here, since we'll 1872 // tell aViewer to drop it. 1873 nsCOMPtr<nsIDocumentViewer> prevViewer = aViewer->GetPreviousViewer(); 1874 if (prevViewer) { 1875 aViewer->SetPreviousViewer(nullptr); 1876 aViewer->Destroy(); 1877 return SetPreviousViewer(prevViewer); 1878 } 1879 } 1880 1881 mPreviousViewer = aViewer; 1882 } 1883 1884 NS_IMETHODIMP 1885 nsDocumentViewer::SetBoundsWithFlags(const LayoutDeviceIntRect& aBounds, 1886 uint32_t aFlags) { 1887 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); 1888 1889 bool boundsChanged = !mBounds.IsEqualEdges(aBounds); 1890 mBounds = aBounds; 1891 1892 if (mPresContext) { 1893 // Ensure presContext's deviceContext is up to date, as we sometimes get 1894 // here before a resolution-change notification has been fully handled 1895 // during display configuration changes, especially when there are lots 1896 // of windows/widgets competing to handle the notifications. 1897 // (See bug 1154125.) 1898 if (mPresContext->DeviceContext()->CheckDPIChange()) { 1899 mPresContext->UIResolutionChangedSync(); 1900 } 1901 1902 int32_t p2a = mPresContext->AppUnitsPerDevPixel(); 1903 const nsSize size = LayoutDeviceSize::ToAppUnits(mBounds.Size(), p2a); 1904 if (boundsChanged && mPresContext->GetVisibleArea().Size() == size) { 1905 // If the view/frame tree and prescontext visible area already has the new 1906 // size but we did not, then it's likely that we got reflowed in response 1907 // to a call to GetContentSize. Thus there is a disconnect between the 1908 // size on the document viewer/docshell/containing widget and view 1909 // tree/frame tree/prescontext visible area). SetLayoutViewportSize 1910 // compares to the pres context visible area to determine if it needs to 1911 // do anything; if they are the same as the new size it won't do anything, 1912 // but we still need to invalidate because what we want to draw to the 1913 // screen has changed. 1914 if (nsIFrame* f = mPresShell->GetRootFrame()) { 1915 f->InvalidateFrame(); 1916 1917 // Forcibly refresh the viewport sizes even if the view size is not 1918 // changed since it is possible that the |mBounds| change means that 1919 // the software keyboard appeared/disappeared. In such cases we might 1920 // need to fire visual viewport events. 1921 mPresShell->RefreshViewportSize(); 1922 } 1923 } 1924 1925 RefPtr ps = mPresShell; 1926 ps->SetLayoutViewportSize(size, 1927 !!(aFlags & nsIDocumentViewer::eDelayResize)); 1928 } 1929 1930 // If there's a previous viewer, it's the one that's actually showing, 1931 // so be sure to resize it as well so it paints over the right area. 1932 // This may slow down the performance of the new page load, but resize 1933 // during load is also probably a relatively unusual condition 1934 // relating to things being hidden while something is loaded. It so 1935 // happens that Firefox does this a good bit with its infobar, and it 1936 // looks ugly if we don't do this. 1937 if (mPreviousViewer) { 1938 nsCOMPtr<nsIDocumentViewer> previousViewer = mPreviousViewer; 1939 previousViewer->SetBounds(aBounds); 1940 } 1941 1942 return NS_OK; 1943 } 1944 1945 NS_IMETHODIMP 1946 nsDocumentViewer::SetBounds(const LayoutDeviceIntRect& aBounds) { 1947 return SetBoundsWithFlags(aBounds, 0); 1948 } 1949 1950 NS_IMETHODIMP 1951 nsDocumentViewer::Move(int32_t aX, int32_t aY) { 1952 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); 1953 mBounds.MoveTo(aX, aY); 1954 if (mWindow) { 1955 mWindow->Move(mBounds.TopLeft() / mWindow->GetDesktopToDeviceScale()); 1956 } 1957 return NS_OK; 1958 } 1959 1960 NS_IMETHODIMP 1961 nsDocumentViewer::Show() { 1962 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); 1963 1964 // We don't need the previous viewer anymore since we're not 1965 // displaying it. 1966 if (mPreviousViewer) { 1967 // This little dance *may* only be to keep 1968 // PresShell::EndObservingDocument happy, but I'm not sure. 1969 nsCOMPtr<nsIDocumentViewer> prevViewer(mPreviousViewer); 1970 mPreviousViewer = nullptr; 1971 prevViewer->Destroy(); 1972 1973 // Make sure we don't have too many cached DocumentViewers 1974 nsCOMPtr<nsIDocShellTreeItem> treeItem(mContainer); 1975 if (treeItem) { 1976 // We need to find the root DocShell since only that object has an 1977 // SHistory and we need the SHistory to evict content viewers 1978 nsCOMPtr<nsIDocShellTreeItem> root; 1979 treeItem->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root)); 1980 nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root); 1981 RefPtr<ChildSHistory> history = webNav->GetSessionHistory(); 1982 if (!mozilla::SessionHistoryInParent() && history) { 1983 int32_t prevIndex, loadedIndex; 1984 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(treeItem); 1985 docShell->GetPreviousEntryIndex(&prevIndex); 1986 docShell->GetLoadedEntryIndex(&loadedIndex); 1987 MOZ_LOG(gPageCacheLog, LogLevel::Verbose, 1988 ("About to evict content viewers: prev=%d, loaded=%d", 1989 prevIndex, loadedIndex)); 1990 history->LegacySHistory()->EvictOutOfRangeDocumentViewers(loadedIndex); 1991 } 1992 } 1993 } 1994 1995 // Hold on to the document so we can use it after the script blocker below 1996 // has been released (which might re-entrantly call into other 1997 // nsDocumentViewer methods). 1998 nsCOMPtr<Document> document = mDocument; 1999 2000 if (mDocument && !mPresShell) { 2001 // The InitPresentationStuff call below requires a script blocker, because 2002 // its PresShell::Initialize call can cause scripts to run and therefore 2003 // re-entrant calls to nsDocumentViewer methods to be made. 2004 nsAutoScriptBlocker scriptBlocker; 2005 2006 NS_ASSERTION(!mWindow, "Window already created but no presshell?"); 2007 2008 nsCOMPtr<nsIBaseWindow> base_win(mContainer); 2009 if (base_win) { 2010 base_win->GetParentWidget(&mParentWidget); 2011 if (mParentWidget) { 2012 // GetParentWidget AddRefs, but mParentWidget is weak 2013 mParentWidget->Release(); 2014 } 2015 } 2016 2017 nsSubDocumentFrame* containerFrame = FindContainerFrame(); 2018 2019 nsresult rv = CreateDeviceContext(containerFrame); 2020 NS_ENSURE_SUCCESS(rv, rv); 2021 2022 // Create presentation context 2023 NS_ASSERTION(!mPresContext, 2024 "Shouldn't have a prescontext if we have no shell!"); 2025 mPresContext = CreatePresContext(mDocument, nsPresContext::eContext_Galley, 2026 containerFrame); 2027 NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY); 2028 2029 rv = mPresContext->Init(mDeviceContext); 2030 if (NS_FAILED(rv)) { 2031 mPresContext = nullptr; 2032 return rv; 2033 } 2034 2035 if (mPresContext) { 2036 Hide(); 2037 2038 rv = InitPresentationStuff(mDocument->MayStartLayout()); 2039 } 2040 2041 // If we get here the document load has already started and the 2042 // window is shown because some JS on the page caused it to be 2043 // shown... 2044 2045 if (mPresShell) { 2046 RefPtr<PresShell> presShell = mPresShell; // bug 378682 2047 presShell->UnsuppressPainting(); 2048 } 2049 } 2050 2051 // Notify observers that a new page has been shown. This will get run 2052 // from the event loop after we actually draw the page. 2053 auto event = MakeRefPtr<nsDocumentShownDispatcher>(document); 2054 document->Dispatch(event.forget()); 2055 2056 return NS_OK; 2057 } 2058 2059 NS_IMETHODIMP 2060 nsDocumentViewer::Hide() { 2061 if (!mPresShell) { 2062 return NS_OK; 2063 } 2064 2065 NS_ASSERTION(mPresContext, "Can't have a presshell and no prescontext!"); 2066 2067 // Avoid leaking the old viewer. 2068 if (mPreviousViewer) { 2069 mPreviousViewer->Destroy(); 2070 mPreviousViewer = nullptr; 2071 } 2072 2073 if (mIsSticky) { 2074 // This window is sticky, that means that it might be shown again 2075 // and we don't want the presshell n' all that to be thrown away 2076 // just because the window is hidden. 2077 2078 return NS_OK; 2079 } 2080 2081 nsCOMPtr<nsIDocShell> docShell(mContainer); 2082 if (docShell) { 2083 #ifdef DEBUG 2084 nsCOMPtr<nsIDocumentViewer> currentViewer; 2085 docShell->GetDocViewer(getter_AddRefs(currentViewer)); 2086 MOZ_ASSERT(currentViewer == this); 2087 #endif 2088 nsCOMPtr<nsILayoutHistoryState> layoutState; 2089 mPresShell->CaptureHistoryState(getter_AddRefs(layoutState)); 2090 } 2091 2092 // Do not run ScriptRunners queued by DestroyPresShell() in the intermediate 2093 // state before we're done destroying PresShell, PresContext, ViewManager, 2094 // etc. 2095 nsAutoScriptBlocker scriptBlocker; 2096 2097 DestroyPresShell(); 2098 2099 DestroyPresContext(); 2100 2101 mWindow = nullptr; 2102 mDeviceContext = nullptr; 2103 mParentWidget = nullptr; 2104 return NS_OK; 2105 } 2106 2107 NS_IMETHODIMP 2108 nsDocumentViewer::GetSticky(bool* aSticky) { 2109 *aSticky = mIsSticky; 2110 2111 return NS_OK; 2112 } 2113 2114 NS_IMETHODIMP 2115 nsDocumentViewer::SetSticky(bool aSticky) { 2116 mIsSticky = aSticky; 2117 2118 return NS_OK; 2119 } 2120 2121 NS_IMETHODIMP 2122 nsDocumentViewer::ClearHistoryEntry() { 2123 if (mDocument) { 2124 nsJSContext::PokeGC(JS::GCReason::PAGE_HIDE, 2125 mDocument->GetWrapperPreserveColor(), 2126 TimeDuration::FromMilliseconds( 2127 StaticPrefs::javascript_options_gc_delay() * 2)); 2128 } 2129 2130 mSHEntry = nullptr; 2131 return NS_OK; 2132 } 2133 2134 //------------------------------------------------------- 2135 2136 void nsDocumentViewer::DetachFromTopLevelWidget() { 2137 if (mPresShell) { 2138 if (auto* listener = mPresShell->GetWidgetListener(); 2139 listener && listener->HasWidget()) { 2140 listener->DetachFromTopLevelWidget(); 2141 } 2142 } 2143 } 2144 2145 void nsDocumentViewer::AttachToTopLevelWidget() { 2146 DetachFromTopLevelWidget(); 2147 if (mPresShell && mParentWidget) { 2148 auto* listener = mPresShell->GetWidgetListener(); 2149 listener->AttachToTopLevelWidget(mParentWidget); 2150 mWindow = mParentWidget; 2151 } 2152 } 2153 2154 nsSubDocumentFrame* nsDocumentViewer::FindContainerFrame() { 2155 if (!mContainer) { 2156 return nullptr; 2157 } 2158 2159 nsCOMPtr<nsIDocShell> docShell(mContainer); 2160 nsCOMPtr<nsPIDOMWindowOuter> pwin(docShell->GetWindow()); 2161 if (!pwin) { 2162 return nullptr; 2163 } 2164 2165 nsCOMPtr<Element> containerElement = pwin->GetFrameElementInternal(); 2166 if (!containerElement) { 2167 return nullptr; 2168 } 2169 2170 nsIFrame* subdocFrame = containerElement->GetPrimaryFrame(); 2171 if (!subdocFrame) { 2172 // XXX Silenced by default in bug 1175289 2173 LAYOUT_WARNING("Subdocument container has no frame"); 2174 return nullptr; 2175 } 2176 2177 // Check subdocFrame just to be safe. If this somehow fails we treat that as 2178 // display:none, the document is not displayed. 2179 if (!subdocFrame->IsSubDocumentFrame()) { 2180 NS_WARNING_ASSERTION(subdocFrame->Type() == LayoutFrameType::None, 2181 "Subdocument container has non-subdocument frame"); 2182 return nullptr; 2183 } 2184 2185 return static_cast<nsSubDocumentFrame*>(subdocFrame); 2186 } 2187 2188 nsresult nsDocumentViewer::CreateDeviceContext( 2189 nsSubDocumentFrame* aContainerFrame) { 2190 MOZ_ASSERT(!mPresShell && !mWindow, 2191 "This will screw up our existing presentation"); 2192 MOZ_ASSERT(mDocument, "Gotta have a document here"); 2193 2194 if (Document* doc = mDocument->GetDisplayDocument()) { 2195 NS_ASSERTION(!aContainerFrame, 2196 "External resource document embedded somewhere?"); 2197 // We want to use our display document's device context if possible 2198 if (nsPresContext* ctx = doc->GetPresContext()) { 2199 mDeviceContext = ctx->DeviceContext(); 2200 return NS_OK; 2201 } 2202 } 2203 2204 // Create a device context even if we already have one, since our widget 2205 // might have changed. 2206 nsIWidget* widget = nullptr; 2207 if (aContainerFrame) { 2208 widget = aContainerFrame->GetNearestWidget(); 2209 } 2210 if (!widget) { 2211 widget = mParentWidget; 2212 } 2213 if (widget) { 2214 widget = widget->GetTopLevelWidget(); 2215 } 2216 2217 mDeviceContext = new nsDeviceContext(); 2218 mDeviceContext->Init(widget); 2219 return NS_OK; 2220 } 2221 2222 // Return the selection for the document. Note that text fields have their 2223 // own selection, which cannot be accessed with this method. 2224 mozilla::dom::Selection* nsDocumentViewer::GetDocumentSelection() { 2225 if (!mPresShell) { 2226 return nullptr; 2227 } 2228 2229 return mPresShell->GetCurrentSelection(SelectionType::eNormal); 2230 } 2231 2232 /* ============================================================================ 2233 * nsIDocumentViewerEdit 2234 * ============================================================================ 2235 */ 2236 2237 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsDocumentViewer::ClearSelection() { 2238 // use nsCopySupport::GetSelectionForCopy() ? 2239 RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection(); 2240 if (!selection) { 2241 return NS_ERROR_FAILURE; 2242 } 2243 2244 ErrorResult rv; 2245 selection->CollapseToStart(rv); 2246 return rv.StealNSResult(); 2247 } 2248 2249 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsDocumentViewer::SelectAll() { 2250 // XXX this is a temporary implementation copied from nsWebShell 2251 // for now. I think Document and friends should have some helper 2252 // functions to make this easier. 2253 2254 // use nsCopySupport::GetSelectionForCopy() ? 2255 RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection(); 2256 if (!selection) { 2257 return NS_ERROR_FAILURE; 2258 } 2259 2260 if (!mDocument) { 2261 return NS_ERROR_FAILURE; 2262 } 2263 2264 nsCOMPtr<nsINode> bodyNode; 2265 if (mDocument->IsHTMLOrXHTML()) { 2266 // XXXbz why not just do GetBody() for all documents, then GetRootElement() 2267 // if GetBody() is null? 2268 bodyNode = mDocument->GetBody(); 2269 } else { 2270 bodyNode = mDocument->GetRootElement(); 2271 } 2272 if (!bodyNode) { 2273 return NS_ERROR_FAILURE; 2274 } 2275 2276 ErrorResult err; 2277 selection->RemoveAllRanges(err); 2278 if (err.Failed()) { 2279 return err.StealNSResult(); 2280 } 2281 2282 mozilla::dom::Selection::AutoUserInitiated userSelection(selection); 2283 selection->SelectAllChildren(*bodyNode, err); 2284 return err.StealNSResult(); 2285 } 2286 2287 NS_IMETHODIMP nsDocumentViewer::CopySelection() { 2288 RefPtr<PresShell> presShell = mPresShell; 2289 nsCopySupport::FireClipboardEvent(eCopy, Some(nsIClipboard::kGlobalClipboard), 2290 presShell, nullptr, nullptr); 2291 return NS_OK; 2292 } 2293 2294 NS_IMETHODIMP nsDocumentViewer::CopyLinkLocation() { 2295 NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED); 2296 nsCOMPtr<nsINode> node = GetPopupLinkNode(); 2297 // make noise if we're not in a link 2298 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); 2299 2300 nsCOMPtr<dom::Element> elm(do_QueryInterface(node)); 2301 NS_ENSURE_TRUE(elm, NS_ERROR_FAILURE); 2302 2303 nsAutoString locationText; 2304 nsContentUtils::GetLinkLocation(elm, locationText); 2305 if (locationText.IsEmpty()) { 2306 return NS_ERROR_FAILURE; 2307 } 2308 2309 nsresult rv = NS_OK; 2310 nsCOMPtr<nsIClipboardHelper> clipboard( 2311 do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv)); 2312 NS_ENSURE_SUCCESS(rv, rv); 2313 2314 // copy the href onto the clipboard 2315 return clipboard->CopyString(locationText, mDocument->GetWindowContext()); 2316 } 2317 2318 NS_IMETHODIMP nsDocumentViewer::CopyImage(int32_t aCopyFlags) { 2319 NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED); 2320 nsCOMPtr<nsIImageLoadingContent> node = GetPopupImageNode(); 2321 // make noise if we're not in an image 2322 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); 2323 2324 nsCOMPtr<nsILoadContext> loadContext(mContainer); 2325 return nsCopySupport::ImageCopy(node, loadContext, aCopyFlags, 2326 mDocument->GetWindowContext()); 2327 } 2328 2329 NS_IMETHODIMP nsDocumentViewer::GetCopyable(bool* aCopyable) { 2330 NS_ENSURE_ARG_POINTER(aCopyable); 2331 *aCopyable = nsCopySupport::CanCopy(mDocument); 2332 return NS_OK; 2333 } 2334 2335 NS_IMETHODIMP nsDocumentViewer::GetContents(const char* mimeType, 2336 bool selectionOnly, 2337 nsAString& aOutValue) { 2338 aOutValue.Truncate(); 2339 2340 NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED); 2341 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED); 2342 2343 // Now we have the selection. Make sure it's nonzero: 2344 RefPtr<Selection> sel; 2345 if (selectionOnly) { 2346 sel = nsCopySupport::GetSelectionForCopy(mDocument); 2347 NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE); 2348 2349 if (NS_WARN_IF(sel->IsCollapsed())) { 2350 return NS_OK; 2351 } 2352 } 2353 2354 // call the copy code 2355 return nsCopySupport::GetContents(nsDependentCString(mimeType), 0, sel, 2356 mDocument, aOutValue); 2357 } 2358 2359 NS_IMETHODIMP nsDocumentViewer::GetCanGetContents(bool* aCanGetContents) { 2360 NS_ENSURE_ARG_POINTER(aCanGetContents); 2361 *aCanGetContents = mDocument && nsCopySupport::CanCopy(mDocument); 2362 return NS_OK; 2363 } 2364 2365 NS_IMETHODIMP nsDocumentViewer::SetCommandNode(nsINode* aNode) { 2366 Document* document = GetDocument(); 2367 NS_ENSURE_STATE(document); 2368 2369 nsCOMPtr<nsPIDOMWindowOuter> window(document->GetWindow()); 2370 NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE); 2371 2372 nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot(); 2373 NS_ENSURE_STATE(root); 2374 2375 root->SetPopupNode(aNode); 2376 return NS_OK; 2377 } 2378 2379 NS_IMETHODIMP 2380 nsDocumentViewer::GetDeviceFullZoomForTest(float* aDeviceFullZoom) { 2381 NS_ENSURE_ARG_POINTER(aDeviceFullZoom); 2382 nsPresContext* pc = GetPresContext(); 2383 *aDeviceFullZoom = pc ? pc->GetDeviceFullZoom() : 1.0; 2384 return NS_OK; 2385 } 2386 2387 NS_IMETHODIMP 2388 nsDocumentViewer::SetAuthorStyleDisabled(bool aStyleDisabled) { 2389 if (mPresShell) { 2390 mPresShell->SetAuthorStyleDisabled(aStyleDisabled); 2391 } 2392 return NS_OK; 2393 } 2394 2395 NS_IMETHODIMP 2396 nsDocumentViewer::GetAuthorStyleDisabled(bool* aStyleDisabled) { 2397 if (mPresShell) { 2398 *aStyleDisabled = mPresShell->GetAuthorStyleDisabled(); 2399 } else { 2400 *aStyleDisabled = false; 2401 } 2402 return NS_OK; 2403 } 2404 2405 /* [noscript,notxpcom] Encoding getHintCharset (); */ 2406 NS_IMETHODIMP_(const Encoding*) 2407 nsDocumentViewer::GetReloadEncodingAndSource(int32_t* aSource) { 2408 *aSource = mReloadEncodingSource; 2409 if (kCharsetUninitialized == mReloadEncodingSource) { 2410 return nullptr; 2411 } 2412 return mReloadEncoding; 2413 } 2414 2415 NS_IMETHODIMP_(void) 2416 nsDocumentViewer::SetReloadEncodingAndSource(const Encoding* aEncoding, 2417 int32_t aSource) { 2418 MOZ_ASSERT( 2419 aSource == kCharsetUninitialized || 2420 (aSource >= 2421 kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII && 2422 aSource <= 2423 kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII) || 2424 aSource == kCharsetFromFinalUserForcedAutoDetection); 2425 mReloadEncoding = aEncoding; 2426 mReloadEncodingSource = aSource; 2427 } 2428 2429 NS_IMETHODIMP_(void) 2430 nsDocumentViewer::ForgetReloadEncoding() { 2431 mReloadEncoding = nullptr; 2432 mReloadEncodingSource = kCharsetUninitialized; 2433 } 2434 2435 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsDocumentViewer::GetContentSize( 2436 int32_t aMaxWidth, int32_t aMaxHeight, int32_t aPrefWidth, int32_t* aWidth, 2437 int32_t* aHeight) { 2438 NS_ENSURE_STATE(mContainer); 2439 2440 RefPtr<BrowsingContext> bc = mContainer->GetBrowsingContext(); 2441 NS_ENSURE_TRUE(bc, NS_ERROR_NOT_AVAILABLE); 2442 2443 // It's only valid to access this from a top frame. Doesn't work from 2444 // sub-frames. 2445 NS_ENSURE_TRUE(bc->IsTop(), NS_ERROR_FAILURE); 2446 2447 // Convert max-width/height and pref-width to app units. 2448 if (aMaxWidth > 0) { 2449 aMaxWidth = CSSPixel::ToAppUnits(aMaxWidth); 2450 } else { 2451 aMaxWidth = NS_UNCONSTRAINEDSIZE; 2452 } 2453 if (aMaxHeight > 0) { 2454 aMaxHeight = CSSPixel::ToAppUnits(aMaxHeight); 2455 } else { 2456 aMaxHeight = NS_UNCONSTRAINEDSIZE; 2457 } 2458 if (aPrefWidth > 0) { 2459 aPrefWidth = CSSPixel::ToAppUnits(aPrefWidth); 2460 } else { 2461 aPrefWidth = 0; 2462 } 2463 2464 RefPtr<PresShell> presShell = GetPresShell(); 2465 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); 2466 2467 // Flush out all content and style updates. We can't use a resize reflow 2468 // because it won't change some sizes that a style change reflow will. 2469 mDocument->FlushPendingNotifications(FlushType::Layout); 2470 2471 nsIFrame* root = presShell->GetRootFrame(); 2472 NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); 2473 2474 WritingMode wm = root->GetWritingMode(); 2475 2476 nscoord prefISize; 2477 { 2478 const auto& constraints = presShell->GetWindowSizeConstraints(); 2479 aMaxHeight = std::min(aMaxHeight, constraints.mMaxSize.height); 2480 aMaxWidth = std::min(aMaxWidth, constraints.mMaxSize.width); 2481 2482 UniquePtr<gfxContext> rcx(presShell->CreateReferenceRenderingContext()); 2483 const nscoord minISize = wm.IsVertical() ? constraints.mMinSize.height 2484 : constraints.mMinSize.width; 2485 const nscoord maxISize = wm.IsVertical() ? aMaxHeight : aMaxWidth; 2486 const IntrinsicSizeInput input(rcx.get(), Nothing(), Nothing()); 2487 if (aPrefWidth) { 2488 prefISize = std::max(root->GetMinISize(input), aPrefWidth); 2489 } else { 2490 prefISize = root->GetPrefISize(input); 2491 } 2492 prefISize = nsPresContext::RoundUpAppUnitsToCSSPixel( 2493 CSSMinMax(prefISize, minISize, maxISize)); 2494 } 2495 2496 // We should never intentionally get here with this sentinel value, but it's 2497 // possible that a document with huge sizes might inadvertently have a 2498 // prefISize that exactly matches NS_UNCONSTRAINEDSIZE. 2499 // Just bail if that happens. 2500 NS_ENSURE_TRUE(prefISize != NS_UNCONSTRAINEDSIZE, NS_ERROR_FAILURE); 2501 2502 const nsSize size(wm.IsVertical() ? aMaxWidth : prefISize, 2503 wm.IsVertical() ? prefISize : aMaxHeight); 2504 presShell->ResizeReflow(size, ResizeReflowOptions::BSizeLimit); 2505 2506 RefPtr<nsPresContext> presContext = GetPresContext(); 2507 NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); 2508 2509 // Protect against bogus returns here 2510 nsRect shellArea = presContext->GetVisibleArea(); 2511 NS_ENSURE_TRUE(shellArea.width != NS_UNCONSTRAINEDSIZE && 2512 shellArea.height != NS_UNCONSTRAINEDSIZE, 2513 NS_ERROR_FAILURE); 2514 2515 // Leave our viewport in a consistent state. 2516 { 2517 auto newBounds = LayoutDeviceIntRect::FromAppUnitsToOutside( 2518 shellArea, presContext->AppUnitsPerDevPixel()); 2519 newBounds.MoveTo(mBounds.TopLeft()); 2520 SetBounds(newBounds); 2521 } 2522 2523 // Ceil instead of rounding here, so we can actually guarantee showing all the 2524 // content. 2525 *aWidth = std::ceil(CSSPixel::FromAppUnits(shellArea.width)); 2526 *aHeight = std::ceil(CSSPixel::FromAppUnits(shellArea.height)); 2527 2528 return NS_OK; 2529 } 2530 2531 NS_IMPL_ISUPPORTS(nsDocViewerSelectionListener, nsISelectionListener) 2532 2533 /* 2534 * GetPopupNode, GetPopupLinkNode and GetPopupImageNode are helpers 2535 * for the cmd_copyLink / cmd_copyImageLocation / cmd_copyImageContents family 2536 * of commands. The focus controller stores the popup node, these retrieve 2537 * them and munge appropriately. Note that we have to store the popup node 2538 * rather than retrieving it from EventStateManager::GetFocusedContent because 2539 * not all content (images included) can receive focus. 2540 */ 2541 2542 already_AddRefed<nsINode> nsDocumentViewer::GetPopupNode() { 2543 // get the document 2544 Document* document = GetDocument(); 2545 NS_ENSURE_TRUE(document, nullptr); 2546 2547 // get the private dom window 2548 nsCOMPtr<nsPIDOMWindowOuter> window(document->GetWindow()); 2549 NS_ENSURE_TRUE(window, nullptr); 2550 if (window) { 2551 nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot(); 2552 NS_ENSURE_TRUE(root, nullptr); 2553 2554 // get the popup node 2555 nsCOMPtr<nsINode> node = root->GetPopupNode(); 2556 if (!node) { 2557 nsPIDOMWindowOuter* rootWindow = root->GetWindow(); 2558 if (rootWindow) { 2559 nsCOMPtr<Document> rootDoc = rootWindow->GetExtantDoc(); 2560 if (rootDoc) { 2561 nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 2562 if (pm) { 2563 node = pm->GetLastTriggerPopupNode(rootDoc); 2564 } 2565 } 2566 } 2567 } 2568 return node.forget(); 2569 } 2570 2571 return nullptr; 2572 } 2573 2574 // GetPopupLinkNode: return popup link node or fail 2575 already_AddRefed<nsINode> nsDocumentViewer::GetPopupLinkNode() { 2576 // find popup node 2577 nsCOMPtr<nsINode> node = GetPopupNode(); 2578 2579 // find out if we have a link in our ancestry 2580 while (node) { 2581 if (const auto* element = Element::FromNode(*node)) { 2582 if (element->IsLink()) { 2583 return node.forget(); 2584 } 2585 } 2586 2587 // get our parent and keep trying... 2588 node = node->GetParentNode(); 2589 } 2590 2591 // if we have no node, fail 2592 return nullptr; 2593 } 2594 2595 // GetPopupLinkNode: return popup image node or fail 2596 already_AddRefed<nsIImageLoadingContent> nsDocumentViewer::GetPopupImageNode() { 2597 // find popup node 2598 nsCOMPtr<nsINode> node = GetPopupNode(); 2599 nsCOMPtr<nsIImageLoadingContent> img = do_QueryInterface(node); 2600 return img.forget(); 2601 } 2602 2603 /* 2604 * XXX dr 2605 * ------ 2606 * These two functions -- GetInLink and GetInImage -- are kind of annoying 2607 * in that they only get called from the controller (in 2608 * nsDOMWindowController::IsCommandEnabled). The actual construction of the 2609 * context menus in communicator (nsContextMenu.js) has its own, redundant 2610 * tests. No big deal, but good to keep in mind if we ever clean context 2611 * menus. 2612 */ 2613 2614 NS_IMETHODIMP nsDocumentViewer::GetInLink(bool* aInLink) { 2615 NS_ENSURE_ARG_POINTER(aInLink); 2616 nsCOMPtr<nsINode> node = GetPopupLinkNode(); 2617 *aInLink = !!node; 2618 return NS_OK; 2619 } 2620 2621 NS_IMETHODIMP nsDocumentViewer::GetInImage(bool* aInImage) { 2622 NS_ENSURE_ARG_POINTER(aInImage); 2623 *aInImage = false; 2624 // get the popup image 2625 nsCOMPtr<nsIImageLoadingContent> node = GetPopupImageNode(); 2626 if (!node) { 2627 return NS_OK; 2628 } 2629 2630 // Make sure there is a URI assigned. This allows <input type="image"> to 2631 // be an image but rejects other <input> types. This matches what 2632 // nsContextMenu.js does. 2633 nsCOMPtr<nsIURI> uri; 2634 node->GetCurrentURI(getter_AddRefs(uri)); 2635 if (uri) { 2636 // if we made it here, we're in an image 2637 *aInImage = true; 2638 } 2639 return NS_OK; 2640 } 2641 2642 NS_IMETHODIMP nsDocViewerSelectionListener::NotifySelectionChanged( 2643 Document*, Selection*, int16_t aReason, int32_t aAmount) { 2644 if (!mDocViewer) { 2645 return NS_OK; 2646 } 2647 2648 // get the selection state 2649 RefPtr<mozilla::dom::Selection> selection = 2650 mDocViewer->GetDocumentSelection(); 2651 if (!selection) { 2652 return NS_ERROR_FAILURE; 2653 } 2654 2655 Document* theDoc = mDocViewer->GetDocument(); 2656 if (!theDoc) { 2657 return NS_ERROR_FAILURE; 2658 } 2659 2660 nsCOMPtr<nsPIDOMWindowOuter> domWindow = theDoc->GetWindow(); 2661 if (!domWindow) { 2662 return NS_ERROR_FAILURE; 2663 } 2664 2665 bool selectionCollapsed = selection->IsCollapsed(); 2666 // We only call UpdateCommands when the selection changes from collapsed to 2667 // non-collapsed or vice versa, however we skip the initializing collapse. We 2668 // might need another update string for simple selection changes, but that 2669 // would be expenseive. 2670 if (mSelectionWasCollapsed != selectionCollapsed) { 2671 domWindow->UpdateCommands(u"select"_ns); 2672 mSelectionWasCollapsed = selectionCollapsed; 2673 } 2674 2675 return NS_OK; 2676 } 2677 2678 // nsDocViewerFocusListener 2679 NS_IMPL_ISUPPORTS(nsDocViewerFocusListener, nsIDOMEventListener) 2680 2681 nsresult nsDocViewerFocusListener::HandleEvent(Event* aEvent) { 2682 NS_ENSURE_STATE(mDocViewer); 2683 2684 RefPtr<PresShell> presShell = mDocViewer->GetPresShell(); 2685 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); 2686 2687 RefPtr<nsFrameSelection> selection = 2688 presShell->GetLastFocusedFrameSelection(); 2689 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); 2690 auto selectionStatus = selection->GetDisplaySelection(); 2691 nsAutoString eventType; 2692 aEvent->GetType(eventType); 2693 if (eventType.EqualsLiteral("focus")) { 2694 // If selection was disabled, re-enable it. 2695 if (selectionStatus == nsISelectionController::SELECTION_DISABLED || 2696 selectionStatus == nsISelectionController::SELECTION_HIDDEN) { 2697 selection->SetDisplaySelection(nsISelectionController::SELECTION_ON); 2698 selection->RepaintSelection(SelectionType::eNormal); 2699 } 2700 // See EditorBase::FinalizeSelection. This fixes up the case where focus 2701 // left the editor's selection but returned to something else. 2702 if (selection != presShell->ConstFrameSelection()) { 2703 RefPtr<Document> doc = presShell->GetDocument(); 2704 const bool selectionMatchesFocus = 2705 selection->IsIndependentSelection() && 2706 selection->GetIndependentSelectionRootParentElement() == 2707 doc->GetUnretargetedFocusedContent(); 2708 if (NS_WARN_IF(!selectionMatchesFocus)) { 2709 presShell->FrameSelectionWillLoseFocus(*selection); 2710 presShell->SelectionWillTakeFocus(); 2711 } 2712 } 2713 } else { 2714 MOZ_ASSERT(eventType.EqualsLiteral("blur"), "Unexpected event type"); 2715 // If selection was on, disable it. 2716 if (selectionStatus == nsISelectionController::SELECTION_ON || 2717 selectionStatus == nsISelectionController::SELECTION_ATTENTION) { 2718 selection->SetDisplaySelection( 2719 nsISelectionController::SELECTION_DISABLED); 2720 selection->RepaintSelection(SelectionType::eNormal); 2721 } 2722 } 2723 2724 return NS_OK; 2725 } 2726 2727 /** --------------------------------------------------- 2728 * From nsIWebBrowserPrint 2729 */ 2730 2731 #ifdef NS_PRINTING 2732 2733 NS_IMETHODIMP 2734 nsDocumentViewer::Print(nsIPrintSettings* aPrintSettings, 2735 RemotePrintJobChild* aRemotePrintJob, 2736 nsIWebProgressListener* aWebProgressListener) { 2737 if (NS_WARN_IF(!mContainer)) { 2738 PR_PL(("Container was destroyed yet we are still trying to use it!")); 2739 return NS_ERROR_FAILURE; 2740 } 2741 2742 if (NS_WARN_IF(!mDocument) || NS_WARN_IF(!mDeviceContext)) { 2743 PR_PL(("Can't Print without a document and a device context")); 2744 return NS_ERROR_FAILURE; 2745 } 2746 2747 if (NS_WARN_IF(mPrintJob && mPrintJob->GetIsPrinting())) { 2748 // If we are printing another URL, then exit. 2749 // The reason we check here is because this method can be called while 2750 // another is still in here (the printing dialog is a good example). the 2751 // only time we can print more than one job at a time is the regression 2752 // tests. 2753 nsresult rv = NS_ERROR_NOT_AVAILABLE; 2754 RefPtr<nsPrintJob>(mPrintJob)->FirePrintingErrorEvent(rv); 2755 return rv; 2756 } 2757 2758 OnDonePrinting(); 2759 2760 // Note: mContainer and mDocument are known to be non-null via null-checks 2761 // earlier in this function. 2762 // TODO(dholbert) Do we need to bother with this stack-owned local RefPtr? 2763 // (Is there an edge case where it's needed to keep the nsPrintJob alive?) 2764 auto printJob = 2765 MakeRefPtr<nsPrintJob>(*this, *mContainer, *mDocument, 2766 float(AppUnitsPerCSSInch()) / 2767 float(mDeviceContext->AppUnitsPerDevPixel())); 2768 mPrintJob = printJob; 2769 2770 nsresult rv = printJob->Print(*mDocument, aPrintSettings, aRemotePrintJob, 2771 aWebProgressListener); 2772 if (NS_WARN_IF(NS_FAILED(rv))) { 2773 OnDonePrinting(); 2774 } 2775 return rv; 2776 } 2777 2778 NS_IMETHODIMP 2779 nsDocumentViewer::PrintPreview(nsIPrintSettings* aPrintSettings, 2780 nsIWebProgressListener* aWebProgressListener, 2781 PrintPreviewResolver&& aCallback) { 2782 # ifdef NS_PRINT_PREVIEW 2783 RefPtr<Document> doc = mDocument.get(); 2784 NS_ENSURE_STATE(doc); 2785 2786 if (NS_WARN_IF(GetIsPrinting())) { 2787 return NS_ERROR_FAILURE; 2788 } 2789 2790 nsCOMPtr<nsIDocShell> docShell(mContainer); 2791 if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDeviceContext)) { 2792 PR_PL(("Can't Print Preview without device context and docshell")); 2793 return NS_ERROR_FAILURE; 2794 } 2795 2796 NS_ENSURE_STATE(!GetIsPrinting()); 2797 // beforeprint event may have caused DocumentViewer to be shutdown. 2798 NS_ENSURE_STATE(mContainer); 2799 NS_ENSURE_STATE(mDeviceContext); 2800 2801 OnDonePrinting(); 2802 2803 // Note: mContainer and doc are known to be non-null via null-checks earlier 2804 // in this function. 2805 // TODO(dholbert) Do we need to bother with this stack-owned local RefPtr? 2806 // (Is there an edge case where it's needed to keep the nsPrintJob alive?) 2807 auto printJob = 2808 MakeRefPtr<nsPrintJob>(*this, *mContainer, *doc, 2809 float(AppUnitsPerCSSInch()) / 2810 float(mDeviceContext->AppUnitsPerDevPixel())); 2811 mPrintJob = printJob; 2812 2813 nsresult rv = printJob->PrintPreview( 2814 *doc, aPrintSettings, aWebProgressListener, std::move(aCallback)); 2815 if (NS_WARN_IF(NS_FAILED(rv))) { 2816 OnDonePrinting(); 2817 } 2818 return rv; 2819 # else 2820 return NS_ERROR_FAILURE; 2821 # endif // NS_PRINT_PREVIEW 2822 } 2823 2824 static const nsIFrame* GetTargetPageFrame(int32_t aTargetPageNum, 2825 nsPageSequenceFrame* aSequenceFrame) { 2826 MOZ_ASSERT(aTargetPageNum > 0 && 2827 aTargetPageNum <= 2828 aSequenceFrame->PrincipalChildList().GetLength()); 2829 return aSequenceFrame->PrincipalChildList().FrameAt(aTargetPageNum - 1); 2830 } 2831 2832 // Calculate the scroll position where the center of |aFrame| is positioned at 2833 // the center of |aScrollContainerFrame|'s scroll port for the print preview. 2834 // So what we do for that is; 2835 // 1) Calculate the position of the center of |aFrame| in the print preview 2836 // coordinates. 2837 // 2) Reduce the half height of the scroll port from the result of 1. 2838 static nscoord ScrollPositionForFrame( 2839 const nsIFrame* aFrame, ScrollContainerFrame* aScrollContainerFrame, 2840 float aPreviewScale) { 2841 // Note that even if the computed scroll position is out of the range of 2842 // the scroll port, it gets clamped in ScrollContainerFrame::ScrollTo. 2843 return nscoord(aPreviewScale * aFrame->GetRect().Center().y - 2844 float(aScrollContainerFrame->GetScrollPortRect().height) / 2845 2.0f); 2846 } 2847 2848 //---------------------------------------------------------------------- 2849 NS_IMETHODIMP 2850 nsDocumentViewer::PrintPreviewScrollToPage(int16_t aType, int32_t aPageNum) { 2851 if (!GetIsPrintPreview() || mPrintJob->GetIsCreatingPrintPreview()) { 2852 return NS_ERROR_FAILURE; 2853 } 2854 2855 ScrollContainerFrame* sf = mPresShell->GetRootScrollContainerFrame(); 2856 if (!sf) { 2857 return NS_OK; 2858 } 2859 2860 auto [seqFrame, sheetCount] = mPrintJob->GetSeqFrameAndCountSheets(); 2861 (void)sheetCount; 2862 if (!seqFrame) { 2863 return NS_ERROR_FAILURE; 2864 } 2865 2866 float previewScale = seqFrame->GetPrintPreviewScale(); 2867 2868 nsPoint dest = sf->GetScrollPosition(); 2869 2870 switch (aType) { 2871 case nsIWebBrowserPrint::PRINTPREVIEW_HOME: 2872 dest.y = 0; 2873 break; 2874 case nsIWebBrowserPrint::PRINTPREVIEW_END: 2875 dest.y = sf->GetScrollRange().YMost(); 2876 break; 2877 case nsIWebBrowserPrint::PRINTPREVIEW_PREV_PAGE: 2878 case nsIWebBrowserPrint::PRINTPREVIEW_NEXT_PAGE: { 2879 auto [currentFrame, currentSheetNumber] = GetCurrentSheetFrameAndNumber(); 2880 (void)currentSheetNumber; 2881 if (!currentFrame) { 2882 return NS_OK; 2883 } 2884 2885 const nsIFrame* targetFrame = nullptr; 2886 if (aType == nsIWebBrowserPrint::PRINTPREVIEW_PREV_PAGE) { 2887 targetFrame = currentFrame->GetPrevInFlow(); 2888 } else { 2889 targetFrame = currentFrame->GetNextInFlow(); 2890 } 2891 if (!targetFrame) { 2892 return NS_OK; 2893 } 2894 2895 dest.y = ScrollPositionForFrame(targetFrame, sf, previewScale); 2896 break; 2897 } 2898 case nsIWebBrowserPrint::PRINTPREVIEW_GOTO_PAGENUM: { 2899 if (aPageNum <= 0 || aPageNum > sheetCount) { 2900 return NS_ERROR_INVALID_ARG; 2901 } 2902 2903 const nsIFrame* targetFrame = GetTargetPageFrame(aPageNum, seqFrame); 2904 MOZ_ASSERT(targetFrame); 2905 2906 dest.y = ScrollPositionForFrame(targetFrame, sf, previewScale); 2907 break; 2908 } 2909 default: 2910 return NS_ERROR_INVALID_ARG; 2911 break; 2912 } 2913 2914 sf->ScrollTo(dest, ScrollMode::Instant); 2915 2916 return NS_OK; 2917 } 2918 2919 std::tuple<const nsIFrame*, int32_t> 2920 nsDocumentViewer::GetCurrentSheetFrameAndNumber() const { 2921 MOZ_ASSERT(mPrintJob); 2922 MOZ_ASSERT(GetIsPrintPreview() && !mPrintJob->GetIsCreatingPrintPreview()); 2923 2924 // in PP mPrtPreview->mPrintObject->mSeqFrame is null 2925 auto [seqFrame, sheetCount] = mPrintJob->GetSeqFrameAndCountSheets(); 2926 (void)sheetCount; 2927 if (!seqFrame) { 2928 return {nullptr, 0}; 2929 } 2930 2931 ScrollContainerFrame* sf = mPresShell->GetRootScrollContainerFrame(); 2932 if (!sf) { 2933 // No scrollable contents, returns 1 even if there are multiple sheets. 2934 return {seqFrame->PrincipalChildList().FirstChild(), 1}; 2935 } 2936 2937 nsPoint currentScrollPosition = sf->GetScrollPosition(); 2938 float halfwayPoint = 2939 currentScrollPosition.y + float(sf->GetScrollPortRect().height) / 2.0f; 2940 float lastDistanceFromHalfwayPoint = std::numeric_limits<float>::max(); 2941 int32_t sheetNumber = 0; 2942 const nsIFrame* currentSheet = nullptr; 2943 float previewScale = seqFrame->GetPrintPreviewScale(); 2944 for (const nsIFrame* sheetFrame : seqFrame->PrincipalChildList()) { 2945 nsRect sheetRect = sheetFrame->GetRect(); 2946 sheetNumber++; 2947 currentSheet = sheetFrame; 2948 2949 float bottomOfSheet = sheetRect.YMost() * previewScale; 2950 if (bottomOfSheet < halfwayPoint) { 2951 // If the bottom of the sheet is not yet over the halfway point, iterate 2952 // the next frame to see if the next frame is over the halfway point and 2953 // compare the distance from the halfway point. 2954 lastDistanceFromHalfwayPoint = halfwayPoint - bottomOfSheet; 2955 continue; 2956 } 2957 2958 float topOfSheet = sheetRect.Y() * previewScale; 2959 if (topOfSheet <= halfwayPoint) { 2960 // If the top of the sheet is not yet over the halfway point or on the 2961 // point, it's the current sheet. 2962 break; 2963 } 2964 2965 // Now the sheet rect is completely over the halfway point, compare the 2966 // distances from the halfway point. 2967 if ((topOfSheet - halfwayPoint) >= lastDistanceFromHalfwayPoint) { 2968 // If the previous sheet distance is less than or equal to the current 2969 // sheet distance, choose the previous one as the current. 2970 sheetNumber--; 2971 MOZ_ASSERT(sheetNumber > 0); 2972 currentSheet = currentSheet->GetPrevInFlow(); 2973 MOZ_ASSERT(currentSheet); 2974 } 2975 break; 2976 } 2977 2978 MOZ_ASSERT(sheetNumber <= sheetCount); 2979 return {currentSheet, sheetNumber}; 2980 } 2981 2982 // XXXdholbert As noted in nsIWebBrowserPrint.idl, this API (the IDL attr 2983 // 'printPreviewCurrentPageNumber') is misnamed and needs s/Page/Sheet/. See 2984 // bug 1669762. 2985 NS_IMETHODIMP 2986 nsDocumentViewer::GetPrintPreviewCurrentPageNumber(int32_t* aNumber) { 2987 NS_ENSURE_ARG_POINTER(aNumber); 2988 NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE); 2989 if (!GetIsPrintPreview() || mPrintJob->GetIsCreatingPrintPreview()) { 2990 return NS_ERROR_FAILURE; 2991 } 2992 2993 auto [currentFrame, currentSheetNumber] = GetCurrentSheetFrameAndNumber(); 2994 (void)currentFrame; 2995 if (!currentSheetNumber) { 2996 return NS_ERROR_FAILURE; 2997 } 2998 2999 *aNumber = currentSheetNumber; 3000 3001 return NS_OK; 3002 } 3003 3004 // XXX This always returns false for subdocuments 3005 NS_IMETHODIMP 3006 nsDocumentViewer::GetDoingPrint(bool* aDoingPrint) { 3007 NS_ENSURE_ARG_POINTER(aDoingPrint); 3008 3009 // XXX shouldn't this be GetDoingPrint() ? 3010 *aDoingPrint = mPrintJob ? mPrintJob->CreatedForPrintPreview() : false; 3011 return NS_OK; 3012 } 3013 3014 // XXX This always returns false for subdocuments 3015 NS_IMETHODIMP 3016 nsDocumentViewer::GetDoingPrintPreview(bool* aDoingPrintPreview) { 3017 NS_ENSURE_ARG_POINTER(aDoingPrintPreview); 3018 3019 *aDoingPrintPreview = mPrintJob ? mPrintJob->CreatedForPrintPreview() : false; 3020 return NS_OK; 3021 } 3022 3023 NS_IMETHODIMP 3024 nsDocumentViewer::GetCloseWindowAfterPrint(bool* aCloseWindowAfterPrint) { 3025 NS_ENSURE_ARG_POINTER(aCloseWindowAfterPrint); 3026 3027 *aCloseWindowAfterPrint = mCloseWindowAfterPrint; 3028 return NS_OK; 3029 } 3030 3031 NS_IMETHODIMP 3032 nsDocumentViewer::SetCloseWindowAfterPrint(bool aCloseWindowAfterPrint) { 3033 mCloseWindowAfterPrint = aCloseWindowAfterPrint; 3034 return NS_OK; 3035 } 3036 3037 NS_IMETHODIMP 3038 nsDocumentViewer::ExitPrintPreview() { 3039 NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE); 3040 3041 if (GetIsPrinting()) { 3042 // Block exiting the print preview window if we're in the middle of an 3043 // actual print. 3044 return NS_ERROR_FAILURE; 3045 } 3046 3047 if (!GetIsPrintPreview()) { 3048 NS_ERROR("Wow, we should never get here!"); 3049 return NS_OK; 3050 } 3051 3052 # ifdef NS_PRINT_PREVIEW 3053 mPrintJob->Destroy(); 3054 mPrintJob = nullptr; 3055 3056 // Since the print preview implementation discards the window that was used 3057 // to show the print preview, we skip certain cleanup that we would otherwise 3058 // want to do. Specifically, we do not call `SetIsPrintPreview(false)` to 3059 // unblock navigation, we do not call `SetOverrideDPPX` to reset the 3060 // devicePixelRatio, and we do not call `Show` to make such changes take 3061 // affect. 3062 # endif // NS_PRINT_PREVIEW 3063 3064 return NS_OK; 3065 } 3066 3067 NS_IMETHODIMP 3068 nsDocumentViewer::GetRawNumPages(int32_t* aRawNumPages) { 3069 NS_ENSURE_ARG_POINTER(aRawNumPages); 3070 NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE); 3071 3072 *aRawNumPages = mPrintJob->GetRawNumPages(); 3073 return *aRawNumPages > 0 ? NS_OK : NS_ERROR_FAILURE; 3074 } 3075 3076 // XXXdholbert As noted in nsIWebBrowserPrint.idl, this API (the IDL attr 3077 // 'printPreviewNumPages') is misnamed and needs s/Page/Sheet/. 3078 // See bug 1669762. 3079 NS_IMETHODIMP 3080 nsDocumentViewer::GetPrintPreviewNumPages(int32_t* aPrintPreviewNumPages) { 3081 NS_ENSURE_ARG_POINTER(aPrintPreviewNumPages); 3082 NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE); 3083 *aPrintPreviewNumPages = mPrintJob->GetPrintPreviewNumSheets(); 3084 return *aPrintPreviewNumPages > 0 ? NS_OK : NS_ERROR_FAILURE; 3085 } 3086 3087 //---------------------------------------------------------------------------------- 3088 // Printing/Print Preview Helpers 3089 //---------------------------------------------------------------------------------- 3090 3091 //---------------------------------------------------------------------------------- 3092 // Walks the document tree and tells each DocShell whether Printing/PP is 3093 // happening 3094 #endif // NS_PRINTING 3095 3096 //------------------------------------------------------------ 3097 // XXX this always returns false for subdocuments 3098 bool nsDocumentViewer::GetIsPrinting() const { 3099 #ifdef NS_PRINTING 3100 if (mPrintJob) { 3101 return mPrintJob->GetIsPrinting(); 3102 } 3103 #endif 3104 return false; 3105 } 3106 3107 //------------------------------------------------------------ 3108 // The PrintJob holds the current value 3109 // this called from inside the DocViewer. 3110 // XXX it always returns false for subdocuments 3111 bool nsDocumentViewer::GetIsPrintPreview() const { 3112 #ifdef NS_PRINTING 3113 return mPrintJob && mPrintJob->CreatedForPrintPreview(); 3114 #else 3115 return false; 3116 #endif 3117 } 3118 3119 //------------------------------------------------------------ 3120 // Notification from the PrintJob of the current PP status 3121 void nsDocumentViewer::SetIsPrintPreview(bool aIsPrintPreview) { 3122 // Protect against pres shell destruction running scripts. 3123 nsAutoScriptBlocker scriptBlocker; 3124 3125 if (!aIsPrintPreview) { 3126 InvalidatePotentialSubDocDisplayItem(); 3127 if (mPresShell) { 3128 DestroyPresShell(); 3129 } 3130 mWindow = nullptr; 3131 mPresContext = nullptr; 3132 mPresShell = nullptr; 3133 } 3134 } 3135 3136 //---------------------------------------------------------------------------------- 3137 // nsIDocumentViewerPrint IFace 3138 //---------------------------------------------------------------------------------- 3139 3140 //------------------------------------------------------------ 3141 void nsDocumentViewer::IncrementDestroyBlockedCount() { 3142 ++mDestroyBlockedCount; 3143 } 3144 3145 void nsDocumentViewer::DecrementDestroyBlockedCount() { 3146 --mDestroyBlockedCount; 3147 } 3148 3149 //------------------------------------------------------------ 3150 // This called ONLY when printing has completed and the DV 3151 // is being notified that it should get rid of the nsPrintJob. 3152 // 3153 // BUT, if we are in Print Preview then we want to ignore the 3154 // notification (we do not get rid of the nsPrintJob) 3155 // 3156 // One small caveat: 3157 // This IS called from two places in this module for cleaning 3158 // up when an error occurred during the start up printing 3159 // and print preview 3160 // 3161 void nsDocumentViewer::OnDonePrinting() { 3162 #if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW) 3163 // If Destroy() has been called during calling nsPrintJob::Print() or 3164 // nsPrintJob::PrintPreview(), mPrintJob is already nullptr here. 3165 // So, the following clean up does nothing in such case. 3166 // (Do we need some of this for that case?) 3167 if (mPrintJob) { 3168 RefPtr<nsPrintJob> printJob = std::move(mPrintJob); 3169 if (GetIsPrintPreview()) { 3170 printJob->DestroyPrintingData(); 3171 } else { 3172 printJob->Destroy(); 3173 } 3174 3175 // We are done printing, now clean up. 3176 // 3177 // If the original document to print was not a static clone, we opened a new 3178 // window and are responsible for cleaning up the whole <browser> or window 3179 // (see the OPEN_PRINT_BROWSER code, specifically 3180 // handleStaticCloneCreatedForPrint()), so gotta run window.close(), which 3181 // will take care of this. 3182 // 3183 // Otherwise the front-end code is responsible for cleaning the UI. 3184 # ifdef ANDROID 3185 // Android doesn't support Content Analysis and prints in a different way, 3186 // so use different logic to clean up. 3187 bool closeWindowAfterPrint = !printJob->CreatedForPrintPreview(); 3188 # else 3189 bool closeWindowAfterPrint = GetCloseWindowAfterPrint(); 3190 # endif 3191 if (closeWindowAfterPrint) { 3192 if (mContainer) { 3193 if (nsCOMPtr<nsPIDOMWindowOuter> win = mContainer->GetWindow()) { 3194 win->Close(); 3195 } 3196 } 3197 } else if (mClosingWhilePrinting) { 3198 if (mDocument) { 3199 mDocument->Destroy(); 3200 mDocument = nullptr; 3201 } 3202 mClosingWhilePrinting = false; 3203 } 3204 } 3205 #endif // NS_PRINTING && NS_PRINT_PREVIEW 3206 } 3207 3208 NS_IMETHODIMP nsDocumentViewer::SetPrintSettingsForSubdocument( 3209 nsIPrintSettings* aPrintSettings, RemotePrintJobChild* aRemotePrintJob) { 3210 #ifdef NS_PRINTING 3211 { 3212 nsAutoScriptBlocker scriptBlocker; 3213 3214 if (mPresShell) { 3215 DestroyPresShell(); 3216 } 3217 3218 if (mPresContext) { 3219 DestroyPresContext(); 3220 } 3221 3222 MOZ_ASSERT(!mPresContext); 3223 MOZ_ASSERT(!mPresShell); 3224 3225 if (MOZ_UNLIKELY(!mDocument)) { 3226 return NS_ERROR_NOT_AVAILABLE; 3227 } 3228 3229 auto devspec = MakeRefPtr<nsDeviceContextSpecProxy>(aRemotePrintJob); 3230 nsresult rv = devspec->Init(aPrintSettings, /* aIsPrintPreview = */ true); 3231 NS_ENSURE_SUCCESS(rv, rv); 3232 3233 mDeviceContext = new nsDeviceContext(); 3234 rv = mDeviceContext->InitForPrinting(devspec); 3235 3236 NS_ENSURE_SUCCESS(rv, rv); 3237 3238 mPresContext = CreatePresContext( 3239 mDocument, nsPresContext::eContext_PrintPreview, FindContainerFrame()); 3240 mPresContext->SetPrintSettings(aPrintSettings); 3241 MOZ_TRY(mPresContext->Init(mDeviceContext)); 3242 MOZ_TRY(InitPresentationStuff(true)); 3243 } 3244 3245 RefPtr<PresShell> shell = mPresShell; 3246 shell->FlushPendingNotifications(FlushType::Layout); 3247 #endif 3248 return NS_OK; 3249 } 3250 3251 NS_IMETHODIMP nsDocumentViewer::SetPageModeForTesting( 3252 bool aPageMode, nsIPrintSettings* aPrintSettings) { 3253 // XXX Page mode is only partially working; it's currently used for 3254 // reftests that require a paginated context 3255 mIsPageMode = aPageMode; 3256 3257 // The DestroyPresShell call requires a script blocker, since the 3258 // PresShell::Destroy call it does can cause scripts to run, which could 3259 // re-entrantly call methods on the nsDocumentViewer. 3260 nsAutoScriptBlocker scriptBlocker; 3261 3262 if (mPresShell) { 3263 DestroyPresShell(); 3264 } 3265 3266 if (mPresContext) { 3267 DestroyPresContext(); 3268 } 3269 3270 mWindow = nullptr; 3271 3272 NS_ENSURE_STATE(mDocument); 3273 if (aPageMode) { 3274 mPresContext = CreatePresContext( 3275 mDocument, nsPresContext::eContext_PageLayout, FindContainerFrame()); 3276 NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY); 3277 mPresContext->SetPaginatedScrolling(true); 3278 mPresContext->SetPrintSettings(aPrintSettings); 3279 nsresult rv = mPresContext->Init(mDeviceContext); 3280 NS_ENSURE_SUCCESS(rv, rv); 3281 } 3282 NS_ENSURE_SUCCESS(InitInternal(mParentWidget, nullptr, nullptr, mBounds, true, 3283 false, false), 3284 NS_ERROR_FAILURE); 3285 3286 Show(); 3287 return NS_OK; 3288 } 3289 3290 NS_IMETHODIMP 3291 nsDocumentViewer::GetHistoryEntry(nsISHEntry** aHistoryEntry) { 3292 NS_IF_ADDREF(*aHistoryEntry = mSHEntry); 3293 return NS_OK; 3294 } 3295 3296 NS_IMETHODIMP 3297 nsDocumentViewer::GetIsTabModalPromptAllowed(bool* aAllowed) { 3298 *aAllowed = !mHidden; 3299 return NS_OK; 3300 } 3301 3302 NS_IMETHODIMP 3303 nsDocumentViewer::GetIsHidden(bool* aHidden) { 3304 *aHidden = mHidden; 3305 return NS_OK; 3306 } 3307 3308 NS_IMETHODIMP 3309 nsDocumentViewer::SetIsHidden(bool aHidden) { 3310 mHidden = aHidden; 3311 return NS_OK; 3312 } 3313 3314 void nsDocumentViewer::DestroyPresShell() { 3315 // We assert this because destroying the pres shell could otherwise cause 3316 // re-entrancy into nsDocumentViewer methods, and all callers of 3317 // DestroyPresShell need to do other cleanup work afterwards before it 3318 // is safe for those re-entrant method calls to be made. 3319 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), 3320 "DestroyPresShell must only be called when scripts are blocked"); 3321 3322 // Break circular reference (or something) 3323 mPresShell->EndObservingDocument(); 3324 3325 RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection(); 3326 if (selection && mSelectionListener) { 3327 selection->RemoveSelectionListener(mSelectionListener); 3328 } 3329 3330 mPresShell->Destroy(); 3331 mPresShell = nullptr; 3332 } 3333 3334 void nsDocumentViewer::InvalidatePotentialSubDocDisplayItem() { 3335 if (nsSubDocumentFrame* f = FindContainerFrame()) { 3336 f->MarkNeedsDisplayItemRebuild(); 3337 } 3338 } 3339 3340 void nsDocumentViewer::DestroyPresContext() { 3341 InvalidatePotentialSubDocDisplayItem(); 3342 mPresContext = nullptr; 3343 } 3344 3345 void nsDocumentViewer::SetPrintPreviewPresentation(nsPresContext* aPresContext, 3346 PresShell* aPresShell) { 3347 // Protect against pres shell destruction running scripts and re-entrantly 3348 // creating a new presentation. 3349 nsAutoScriptBlocker scriptBlocker; 3350 3351 if (mPresShell) { 3352 DestroyPresShell(); 3353 } 3354 3355 mWindow = nullptr; 3356 mPresContext = aPresContext; 3357 mPresShell = aPresShell; 3358 3359 AttachToTopLevelWidget(); 3360 } 3361 3362 // Fires the "document-shown" event so that interested parties are aware of it. 3363 NS_IMETHODIMP 3364 nsDocumentShownDispatcher::Run() { 3365 nsCOMPtr<nsIObserverService> observerService = 3366 mozilla::services::GetObserverService(); 3367 if (observerService) { 3368 observerService->NotifyObservers(ToSupports(mDocument), "document-shown", 3369 nullptr); 3370 } 3371 return NS_OK; 3372 }