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