/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* container for a document and its presentation */ #include "mozilla/ServoStyleSet.h" #include "nsAutoPtr.h" #include "nscore.h" #include "nsCOMPtr.h" #include "nsCRT.h" #include "nsString.h" #include "nsReadableUtils.h" #include "nsIContent.h" #include "nsIContentViewerContainer.h" #include "nsIContentViewer.h" #include "nsIDocumentViewerPrint.h" #include "nsIDOMBeforeUnloadEvent.h" #include "nsIDocument.h" #include "nsPresContext.h" #include "nsIPresShell.h" #include "mozilla/StyleSetHandle.h" #include "mozilla/StyleSetHandleInlines.h" #include "nsIFrame.h" #include "nsIWritablePropertyBag2.h" #include "nsSubDocumentFrame.h" #include "nsILinkHandler.h" #include "nsIDOMDocument.h" #include "nsISelectionListener.h" #include "mozilla/dom/Selection.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMHTMLElement.h" #include "nsContentUtils.h" #include "nsLayoutStylesheetCache.h" #ifdef ACCESSIBILITY #include "mozilla/a11y/DocAccessible.h" #endif #include "mozilla/BasicEvents.h" #include "mozilla/Preferences.h" #include "mozilla/dom/EncodingUtils.h" #include "mozilla/WeakPtr.h" #include "mozilla/StyleSheet.h" #include "mozilla/StyleSheetInlines.h" #include "nsViewManager.h" #include "nsView.h" #include "nsIPageSequenceFrame.h" #include "nsNetUtil.h" #include "nsIContentViewerEdit.h" #include "nsIContentViewerFile.h" #include "mozilla/StyleSheetInlines.h" #include "mozilla/css/Loader.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsDocShell.h" #include "nsIBaseWindow.h" #include "nsILayoutHistoryState.h" #include "nsCharsetSource.h" #include "mozilla/ReflowInput.h" #include "nsIImageLoadingContent.h" #include "nsCopySupport.h" #include "nsIDOMHTMLFrameSetElement.h" #include "nsIDOMHTMLImageElement.h" #include "nsIXULDocument.h" #include "nsXULPopupManager.h" #include "nsIClipboardHelper.h" #include "nsPIDOMWindow.h" #include "nsGlobalWindow.h" #include "nsDOMNavigationTiming.h" #include "nsPIWindowRoot.h" #include "nsJSEnvironment.h" #include "nsFocusManager.h" #include "nsIScrollableFrame.h" #include "nsStyleSheetService.h" #include "nsRenderingContext.h" #include "nsILoadContext.h" #include "nsIPrompt.h" #include "imgIContainer.h" // image animation mode constants #include "nsSandboxFlags.h" #include "mozilla/DocLoadingTimelineMarker.h" //-------------------------- // Printing Include //--------------------------- #ifdef NS_PRINTING #include "nsIWebBrowserPrint.h" #include "nsPrintEngine.h" // Print Options #include "nsIPrintSettings.h" #include "nsIPrintSettingsService.h" #include "nsISimpleEnumerator.h" #include "nsIPluginDocument.h" #endif // NS_PRINTING //focus #include "nsIDOMEventTarget.h" #include "nsIDOMEventListener.h" #include "nsISelectionController.h" #include "mozilla/EventDispatcher.h" #include "nsISHEntry.h" #include "nsISHistory.h" #include "nsISHistoryInternal.h" #include "nsIWebNavigation.h" #include "mozilla/dom/XMLHttpRequestMainThread.h" //paint forcing #include #include "mozilla/dom/Element.h" using namespace mozilla; using namespace mozilla::dom; #define BEFOREUNLOAD_DISABLED_PREFNAME "dom.disable_beforeunload" #define BEFOREUNLOAD_REQUIRES_INTERACTION_PREFNAME "dom.require_user_interaction_for_beforeunload" //----------------------------------------------------- // LOGGING #include "LayoutLogging.h" #include "mozilla/Logging.h" #ifdef NS_PRINTING static mozilla::LazyLogModule gPrintingLog("printing"); #define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1); #endif // NS_PRINTING #define PRT_YESNO(_p) ((_p)?"YES":"NO") //----------------------------------------------------- class nsDocumentViewer; namespace mozilla { class AutoPrintEventDispatcher; } // a small delegate class used to avoid circular references class nsDocViewerSelectionListener : public nsISelectionListener { public: // nsISupports interface... NS_DECL_ISUPPORTS // nsISelectionListerner interface NS_DECL_NSISELECTIONLISTENER nsDocViewerSelectionListener() : mDocViewer(nullptr) , mSelectionWasCollapsed(true) { } nsresult Init(nsDocumentViewer *aDocViewer); void Disconnect() { mDocViewer = nullptr; } protected: virtual ~nsDocViewerSelectionListener() {} nsDocumentViewer* mDocViewer; bool mSelectionWasCollapsed; }; /** editor Implementation of the FocusListener interface */ class nsDocViewerFocusListener : public nsIDOMEventListener { public: /** default constructor */ nsDocViewerFocusListener(); NS_DECL_ISUPPORTS NS_DECL_NSIDOMEVENTLISTENER nsresult Init(nsDocumentViewer *aDocViewer); void Disconnect() { mDocViewer = nullptr; } protected: /** default destructor */ virtual ~nsDocViewerFocusListener(); private: nsDocumentViewer* mDocViewer; }; //------------------------------------------------------------- class nsDocumentViewer final : public nsIContentViewer, public nsIContentViewerEdit, public nsIContentViewerFile, public nsIDocumentViewerPrint #ifdef NS_PRINTING , public nsIWebBrowserPrint #endif { friend class nsDocViewerSelectionListener; friend class nsPagePrintTimer; friend class nsPrintEngine; public: nsDocumentViewer(); NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW // nsISupports interface... NS_DECL_ISUPPORTS // nsIContentViewer interface... NS_DECL_NSICONTENTVIEWER // nsIContentViewerEdit NS_DECL_NSICONTENTVIEWEREDIT // nsIContentViewerFile NS_DECL_NSICONTENTVIEWERFILE #ifdef NS_PRINTING // nsIWebBrowserPrint NS_DECL_NSIWEBBROWSERPRINT #endif typedef void (*CallChildFunc)(nsIContentViewer* aViewer, void* aClosure); void CallChildren(CallChildFunc aFunc, void* aClosure); // nsIDocumentViewerPrint Printing Methods NS_DECL_NSIDOCUMENTVIEWERPRINT protected: virtual ~nsDocumentViewer(); private: /** * Creates a view manager, root view, and widget for the root view, setting * mViewManager and mWindow. * @param aSize the initial size in appunits * @param aContainerView the container view to hook our root view up * to as a child, or null if this will be the root view manager */ nsresult MakeWindow(const nsSize& aSize, nsView* aContainerView); /** * Create our device context */ nsresult CreateDeviceContext(nsView* aContainerView); /** * If aDoCreation is true, this creates the device context, creates a * prescontext if necessary, and calls MakeWindow. * * If aForceSetNewDocument is false, then SetNewDocument won't be * called if the window's current document is already mDocument. */ nsresult InitInternal(nsIWidget* aParentWidget, nsISupports *aState, const nsIntRect& aBounds, bool aDoCreation, bool aNeedMakeCX = true, bool aForceSetNewDocument = true); /** * @param aDoInitialReflow set to true if you want to kick off the initial * reflow */ nsresult InitPresentationStuff(bool aDoInitialReflow); nsresult GetPopupNode(nsIDOMNode** aNode); nsresult GetPopupLinkNode(nsIDOMNode** aNode); nsresult GetPopupImageNode(nsIImageLoadingContent** aNode); nsresult GetContentSizeInternal(int32_t* aWidth, int32_t* aHeight, nscoord aMaxWidth, nscoord aMaxHeight); void PrepareToStartLoad(void); nsresult SyncParentSubDocMap(); mozilla::dom::Selection* GetDocumentSelection(); void DestroyPresShell(); void DestroyPresContext(); #ifdef NS_PRINTING // Called when the DocViewer is notified that the state // of Printing or PP has changed void SetIsPrintingInDocShellTree(nsIDocShellTreeItem* aParentNode, bool aIsPrintingOrPP, bool aStartAtTop); #endif // NS_PRINTING // Whether we should attach to the top level widget. This is true if we // are sharing/recycling a single base widget and not creating multiple // child widgets. bool ShouldAttachToTopLevel(); protected: // These return the current shell/prescontext etc. nsIPresShell* GetPresShell(); nsPresContext* GetPresContext(); nsViewManager* GetViewManager(); void DetachFromTopLevelWidget(); // IMPORTANT: The ownership implicit in the following member // variables has been explicitly checked and set using nsCOMPtr // for owning pointers and raw COM interface pointers for weak // (ie, non owning) references. If you add any members to this // class, please make the ownership explicit (pinkerton, scc). WeakPtr mContainer; // it owns me! nsWeakPtr mTopContainerWhilePrinting; RefPtr mDeviceContext; // We create and own this baby // the following six items are explicitly in this order // so they will be destroyed in the reverse order (pinkerton, scc) nsCOMPtr mDocument; nsCOMPtr mWindow; // may be null RefPtr mViewManager; RefPtr mPresContext; nsCOMPtr mPresShell; RefPtr mSelectionListener; RefPtr mFocusListener; nsCOMPtr mPreviousViewer; nsCOMPtr mSHEntry; nsIWidget* mParentWidget; // purposely won't be ref counted. May be null bool mAttachedToParent; // view is attached to the parent widget nsIntRect mBounds; // mTextZoom/mPageZoom record the textzoom/pagezoom of the first (galley) // presshell only. float mTextZoom; // Text zoom, defaults to 1.0 float mPageZoom; float mOverrideDPPX; // DPPX overrided, defaults to 0.0 int mMinFontSize; int16_t mNumURLStarts; int16_t mDestroyRefCount; // a second "refcount" for the document viewer's "destroy" unsigned mStopped : 1; unsigned mLoaded : 1; unsigned mDeferredWindowClose : 1; // document management data // these items are specific to markup documents (html and xml) // may consider splitting these out into a subclass unsigned mIsSticky : 1; unsigned mInPermitUnload : 1; unsigned mInPermitUnloadPrompt: 1; #ifdef NS_PRINTING unsigned mClosingWhilePrinting : 1; #if NS_PRINT_PREVIEW unsigned mPrintPreviewZoomed : 1; // These data members support delayed printing when the document is loading unsigned mPrintIsPending : 1; unsigned mPrintDocIsFullyLoaded : 1; nsCOMPtr mCachedPrintSettings; nsCOMPtr mCachedPrintWebProgressListner; RefPtr mPrintEngine; float mOriginalPrintPreviewScale; float mPrintPreviewZoom; nsAutoPtr mAutoBeforeAndAfterPrint; #endif // NS_PRINT_PREVIEW #ifdef DEBUG FILE* mDebugFile; #endif // DEBUG #endif // NS_PRINTING /* character set member data */ int32_t mHintCharsetSource; nsCString mHintCharset; nsCString mForceCharacterSet; bool mIsPageMode; bool mInitializedForPrintPreview; bool mHidden; }; namespace mozilla { /** * A RAII class for automatic dispatch of the 'beforeprint' and 'afterprint' * events ('beforeprint' on construction, 'afterprint' on destruction). * * https://developer.mozilla.org/en-US/docs/Web/Events/beforeprint * https://developer.mozilla.org/en-US/docs/Web/Events/afterprint */ class AutoPrintEventDispatcher { public: explicit AutoPrintEventDispatcher(nsIDocument* aTop) : mTop(aTop) { DispatchEventToWindowTree(NS_LITERAL_STRING("beforeprint")); } ~AutoPrintEventDispatcher() { DispatchEventToWindowTree(NS_LITERAL_STRING("afterprint")); } private: void DispatchEventToWindowTree(const nsAString& aEvent) { nsCOMArray targets; CollectDocuments(mTop, &targets); for (int32_t i = 0; i < targets.Count(); ++i) { nsIDocument* d = targets[i]; nsContentUtils::DispatchTrustedEvent(d, d->GetWindow(), aEvent, false, false, nullptr); } } static bool CollectDocuments(nsIDocument* aDocument, void* aData) { if (aDocument) { static_cast*>(aData)->AppendObject(aDocument); aDocument->EnumerateSubDocuments(CollectDocuments, aData); } return true; } nsCOMPtr mTop; }; } class nsDocumentShownDispatcher : public Runnable { public: explicit nsDocumentShownDispatcher(nsCOMPtr aDocument) : mDocument(aDocument) {} NS_IMETHOD Run() override; private: nsCOMPtr mDocument; }; //------------------------------------------------------------------ // nsDocumentViewer //------------------------------------------------------------------ //------------------------------------------------------------------ already_AddRefed NS_NewContentViewer() { RefPtr viewer = new nsDocumentViewer(); return viewer.forget(); } void nsDocumentViewer::PrepareToStartLoad() { mStopped = false; mLoaded = false; mAttachedToParent = false; mDeferredWindowClose = false; #ifdef NS_PRINTING mPrintIsPending = false; mPrintDocIsFullyLoaded = false; mClosingWhilePrinting = false; // Make sure we have destroyed it and cleared the data member if (mPrintEngine) { mPrintEngine->Destroy(); mPrintEngine = nullptr; #ifdef NS_PRINT_PREVIEW SetIsPrintPreview(false); #endif } #ifdef DEBUG mDebugFile = nullptr; #endif #endif // NS_PRINTING } // Note: operator new zeros our memory, so no need to init things to null. nsDocumentViewer::nsDocumentViewer() : mTextZoom(1.0), mPageZoom(1.0), mOverrideDPPX(0.0), mMinFontSize(0), mIsSticky(true), #ifdef NS_PRINT_PREVIEW mPrintPreviewZoom(1.0), #endif mHintCharsetSource(kCharsetUninitialized), mInitializedForPrintPreview(false), mHidden(false) { PrepareToStartLoad(); } NS_IMPL_ADDREF(nsDocumentViewer) NS_IMPL_RELEASE(nsDocumentViewer) NS_INTERFACE_MAP_BEGIN(nsDocumentViewer) NS_INTERFACE_MAP_ENTRY(nsIContentViewer) NS_INTERFACE_MAP_ENTRY(nsIContentViewerFile) NS_INTERFACE_MAP_ENTRY(nsIContentViewerEdit) NS_INTERFACE_MAP_ENTRY(nsIDocumentViewerPrint) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentViewer) #ifdef NS_PRINTING NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPrint) #endif NS_INTERFACE_MAP_END nsDocumentViewer::~nsDocumentViewer() { if (mDocument) { Close(nullptr); mDocument->Destroy(); } #ifdef NS_PRINTING if (mPrintEngine) { mPrintEngine->Destroy(); mPrintEngine = nullptr; } #endif MOZ_RELEASE_ASSERT(mDestroyRefCount == 0); NS_ASSERTION(!mPresShell && !mPresContext, "User did not call nsIContentViewer::Destroy"); if (mPresShell || mPresContext) { // Make sure we don't hand out a reference to the content viewer to // the SHEntry! mSHEntry = nullptr; Destroy(); } if (mSelectionListener) { mSelectionListener->Disconnect(); } if (mFocusListener) { mFocusListener->Disconnect(); } // XXX(?) Revoke pending invalidate events } /* * This method is called by the Document Loader once a document has * been created for a particular data stream... The content viewer * must cache this document for later use when Init(...) is called. * * This method is also called when an out of band document.write() happens. * In that case, the document passed in is the same as the previous document. */ /* virtual */ void nsDocumentViewer::LoadStart(nsIDocument* aDocument) { MOZ_ASSERT(aDocument); if (!mDocument) { mDocument = aDocument; } } nsresult nsDocumentViewer::SyncParentSubDocMap() { nsCOMPtr docShell(mContainer); if (!docShell) { return NS_OK; } nsCOMPtr pwin(docShell->GetWindow()); if (!mDocument || !pwin) { return NS_OK; } nsCOMPtr element = pwin->GetFrameElementInternal(); if (!element) { return NS_OK; } nsCOMPtr parent; docShell->GetParent(getter_AddRefs(parent)); nsCOMPtr parent_win = parent ? parent->GetWindow() : nullptr; if (!parent_win) { return NS_OK; } nsCOMPtr parent_doc = parent_win->GetDoc(); if (!parent_doc) { return NS_OK; } if (mDocument && parent_doc->GetSubDocumentFor(element) != mDocument && parent_doc->EventHandlingSuppressed()) { mDocument->SuppressEventHandling(nsIDocument::eEvents, parent_doc->EventHandlingSuppressed()); } return parent_doc->SetSubDocumentFor(element, mDocument); } NS_IMETHODIMP nsDocumentViewer::SetContainer(nsIDocShell* aContainer) { mContainer = static_cast(aContainer); if (mPresContext) { mPresContext->SetContainer(mContainer); } // We're loading a new document into the window where this document // viewer lives, sync the parent document's frame element -> sub // document map return SyncParentSubDocMap(); } NS_IMETHODIMP nsDocumentViewer::GetContainer(nsIDocShell** aResult) { NS_ENSURE_ARG_POINTER(aResult); nsCOMPtr container(mContainer); container.swap(*aResult); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::Init(nsIWidget* aParentWidget, const nsIntRect& aBounds) { return InitInternal(aParentWidget, nullptr, aBounds, true); } nsresult nsDocumentViewer::InitPresentationStuff(bool aDoInitialReflow) { // We assert this because initializing the pres shell could otherwise cause // re-entrancy into nsDocumentViewer methods, which might cause a different // pres shell to be created. Callers of InitPresentationStuff should ensure // the call is appropriately bounded by an nsAutoScriptBlocker to decide // when it is safe for these re-entrant calls to be made. MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "InitPresentationStuff must only be called when scripts are " "blocked"); if (GetIsPrintPreview()) return NS_OK; NS_ASSERTION(!mPresShell, "Someone should have destroyed the presshell!"); // Create the style set... StyleSetHandle styleSet = CreateStyleSet(mDocument); // Now make the shell for the document mPresShell = mDocument->CreateShell(mPresContext, mViewManager, styleSet); if (!mPresShell) { styleSet->Delete(); return NS_ERROR_FAILURE; } // We're done creating the style set styleSet->EndUpdate(); if (aDoInitialReflow) { // Since Initialize() will create frames for *all* items // that are currently in the document tree, we need to flush // any pending notifications to prevent the content sink from // duplicating layout frames for content it has added to the tree // but hasn't notified the document about. (Bug 154018) // // Note that we are flushing before we add mPresShell as an observer // to avoid bogus notifications. mDocument->FlushPendingNotifications(Flush_ContentAndNotify); } mPresShell->BeginObservingDocument(); // Initialize our view manager int32_t p2a = mPresContext->AppUnitsPerDevPixel(); MOZ_ASSERT(p2a == mPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()); nscoord width = p2a * mBounds.width; nscoord height = p2a * mBounds.height; mViewManager->SetWindowDimensions(width, height); mPresContext->SetTextZoom(mTextZoom); mPresContext->SetFullZoom(mPageZoom); mPresContext->SetOverrideDPPX(mOverrideDPPX); mPresContext->SetBaseMinFontSize(mMinFontSize); p2a = mPresContext->AppUnitsPerDevPixel(); // zoom may have changed it width = p2a * mBounds.width; height = p2a * mBounds.height; if (aDoInitialReflow) { nsCOMPtr shell = mPresShell; // Initial reflow shell->Initialize(width, height); } else { // Store the visible area so it's available for other callers of // Initialize, like nsContentSink::StartLayout. mPresContext->SetVisibleArea(nsRect(0, 0, width, height)); } // now register ourselves as a selection listener, so that we get // called when the selection changes in the window if (!mSelectionListener) { nsDocViewerSelectionListener *selectionListener = new nsDocViewerSelectionListener(); selectionListener->Init(this); // mSelectionListener is a owning reference mSelectionListener = selectionListener; } RefPtr selection = GetDocumentSelection(); if (!selection) { return NS_ERROR_FAILURE; } nsresult rv = selection->AddSelectionListener(mSelectionListener); if (NS_FAILED(rv)) return rv; // Save old listener so we can unregister it RefPtr oldFocusListener = mFocusListener; if (oldFocusListener) { oldFocusListener->Disconnect(); } // focus listener // // now register ourselves as a focus listener, so that we get called // when the focus changes in the window nsDocViewerFocusListener *focusListener = new nsDocViewerFocusListener(); focusListener->Init(this); // mFocusListener is a strong reference mFocusListener = focusListener; if (mDocument) { mDocument->AddEventListener(NS_LITERAL_STRING("focus"), mFocusListener, false, false); mDocument->AddEventListener(NS_LITERAL_STRING("blur"), mFocusListener, false, false); if (oldFocusListener) { mDocument->RemoveEventListener(NS_LITERAL_STRING("focus"), oldFocusListener, false); mDocument->RemoveEventListener(NS_LITERAL_STRING("blur"), oldFocusListener, false); } } if (aDoInitialReflow && mDocument) { mDocument->ScrollToRef(); } return NS_OK; } static nsPresContext* CreatePresContext(nsIDocument* aDocument, nsPresContext::nsPresContextType aType, nsView* aContainerView) { if (aContainerView) return new nsPresContext(aDocument, aType); return new nsRootPresContext(aDocument, aType); } //----------------------------------------------- // This method can be used to initial the "presentation" // The aDoCreation indicates whether it should create // all the new objects or just initialize the existing ones nsresult nsDocumentViewer::InitInternal(nsIWidget* aParentWidget, nsISupports *aState, const nsIntRect& aBounds, bool aDoCreation, bool aNeedMakeCX /*= true*/, bool aForceSetNewDocument /* = true*/) { if (mIsPageMode) { // XXXbz should the InitInternal in SetPageMode just pass false // here itself? aForceSetNewDocument = false; } // We don't want any scripts to run here. That can cause flushing, // which can cause reentry into initialization of this document viewer, // which would be disastrous. nsAutoScriptBlocker blockScripts; mParentWidget = aParentWidget; // not ref counted mBounds = aBounds; nsresult rv = NS_OK; NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER); nsView* containerView = FindContainerView(); bool makeCX = false; if (aDoCreation) { nsresult rv = CreateDeviceContext(containerView); NS_ENSURE_SUCCESS(rv, rv); // XXXbz this is a nasty hack to do with the fact that we create // presentations both in Init() and in Show()... Ideally we would only do // it in one place (Show()) and require that callers call init(), open(), // show() in that order or something. if (!mPresContext && (aParentWidget || containerView || mDocument->IsBeingUsedAsImage() || (mDocument->GetDisplayDocument() && mDocument->GetDisplayDocument()->GetShell()))) { // Create presentation context if (mIsPageMode) { //Presentation context already created in SetPageMode which is calling this method } else { mPresContext = CreatePresContext(mDocument, nsPresContext::eContext_Galley, containerView); } NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY); nsresult rv = mPresContext->Init(mDeviceContext); if (NS_FAILED(rv)) { mPresContext = nullptr; return rv; } #if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW) makeCX = !GetIsPrintPreview() && aNeedMakeCX; // needs to be true except when we are already in PP or we are enabling/disabling paginated mode. #else makeCX = true; #endif } if (mPresContext) { // Create the ViewManager and Root View... // We must do this before we tell the script global object about // this new document since doing that will cause us to re-enter // into nsSubDocumentFrame code through reflows caused by // FlushPendingNotifications() calls down the road... rv = MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(aBounds.width), mPresContext->DevPixelsToAppUnits(aBounds.height)), containerView); NS_ENSURE_SUCCESS(rv, rv); Hide(); #ifdef NS_PRINT_PREVIEW if (mIsPageMode) { // I'm leaving this in a broken state for the moment; we should // be measuring/scaling with the print device context, not the // screen device context, but this is good enough to allow // printing reftests to work. double pageWidth = 0, pageHeight = 0; mPresContext->GetPrintSettings()->GetEffectivePageSize(&pageWidth, &pageHeight); mPresContext->SetPageSize( nsSize(mPresContext->CSSTwipsToAppUnits(NSToIntFloor(pageWidth)), mPresContext->CSSTwipsToAppUnits(NSToIntFloor(pageHeight)))); mPresContext->SetIsRootPaginatedDocument(true); mPresContext->SetPageScale(1.0f); } #endif } else { // Avoid leaking the old viewer. if (mPreviousViewer) { mPreviousViewer->Destroy(); mPreviousViewer = nullptr; } } } nsCOMPtr requestor(mContainer); if (requestor) { if (mPresContext) { nsCOMPtr linkHandler; requestor->GetInterface(NS_GET_IID(nsILinkHandler), getter_AddRefs(linkHandler)); mPresContext->SetContainer(mContainer); mPresContext->SetLinkHandler(linkHandler); } // Set script-context-owner in the document nsCOMPtr window = do_GetInterface(requestor); if (window) { nsCOMPtr curDoc = window->GetExtantDoc(); if (aForceSetNewDocument || curDoc != mDocument) { rv = window->SetNewDocument(mDocument, aState, false); if (NS_FAILED(rv)) { Destroy(); return rv; } nsJSContext::LoadStart(); } } } if (aDoCreation && mPresContext) { // The ViewManager and Root View was created above (in // MakeWindow())... rv = InitPresentationStuff(!makeCX); } return rv; } void nsDocumentViewer::SetNavigationTiming(nsDOMNavigationTiming* timing) { NS_ASSERTION(mDocument, "Must have a document to set navigation timing."); if (mDocument) { mDocument->SetNavigationTiming(timing); } } // // LoadComplete(aStatus) // // aStatus - The status returned from loading the document. // // This method is called by the container when the document has been // completely loaded. // NS_IMETHODIMP nsDocumentViewer::LoadComplete(nsresult aStatus) { /* We need to protect ourself against auto-destruction in case the window is closed while processing the OnLoad event. See bug http://bugzilla.mozilla.org/show_bug.cgi?id=78445 for more explanation. */ RefPtr kungFuDeathGrip(this); // Flush out layout so it's up-to-date by the time onload is called. // Note that this could destroy the window, so do this before // checking for our mDocument and its window. if (mPresShell && !mStopped) { // Hold strong ref because this could conceivably run script nsCOMPtr shell = mPresShell; shell->FlushPendingNotifications(Flush_Layout); } nsresult rv = NS_OK; NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); // First, get the window from the document... nsCOMPtr window = mDocument->GetWindow(); mLoaded = true; // Now, fire either an OnLoad or OnError event to the document... bool restoring = false; // XXXbz imagelib kills off the document load for a full-page image with // NS_ERROR_PARSED_DATA_CACHED if it's in the cache. So we want to treat // that one as a success code; otherwise whether we fire onload for the image // will depend on whether it's cached! if(window && (NS_SUCCEEDED(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED)) { // If this code changes, the code in nsDocLoader::DocLoaderIsEmpty // that fires load events for document.open() cases might need to // be updated too. nsEventStatus status = nsEventStatus_eIgnore; WidgetEvent event(true, eLoad); event.mFlags.mBubbles = false; event.mFlags.mCancelable = false; // XXX Dispatching to |window|, but using |document| as the target. event.mTarget = mDocument; // If the document presentation is being restored, we don't want to fire // onload to the document content since that would likely confuse scripts // on the page. nsIDocShell *docShell = window->GetDocShell(); NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED); // Unfortunately, docShell->GetRestoringDocument() might no longer be set // correctly. In particular, it can be false by now if someone took it upon // themselves to block onload from inside restoration and unblock it later. // But we can detect the restoring case very simply: by whether our // document's readyState is COMPLETE. restoring = (mDocument->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE); if (!restoring) { NS_ASSERTION(mDocument->IsXULDocument() || // readyState for XUL is bogus mDocument->GetReadyStateEnum() == nsIDocument::READYSTATE_INTERACTIVE || // test_stricttransportsecurity.html has old-style // docshell-generated about:blank docs reach this code! (mDocument->GetReadyStateEnum() == nsIDocument::READYSTATE_UNINITIALIZED && NS_IsAboutBlank(mDocument->GetDocumentURI())), "Bad readystate"); #ifdef DEBUG bool docShellThinksWeAreRestoring; docShell->GetRestoringDocument(&docShellThinksWeAreRestoring); MOZ_ASSERT(!docShellThinksWeAreRestoring, "How can docshell think we are restoring if we don't have a " "READYSTATE_COMPLETE document?"); #endif // DEBUG nsCOMPtr d = mDocument; mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE); RefPtr timing(d->GetNavigationTiming()); if (timing) { timing->NotifyLoadEventStart(); } // Dispatch observer notification to notify observers document load is complete. nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { nsIPrincipal *principal = d->NodePrincipal(); os->NotifyObservers(d, nsContentUtils::IsSystemPrincipal(principal) ? "chrome-document-loaded" : "content-document-loaded", nullptr); } // Notify any devtools about the load. RefPtr timelines = TimelineConsumers::Get(); if (timelines && timelines->HasConsumer(docShell)) { timelines->AddMarkerForDocShell(docShell, MakeUnique("document::Load")); } d->SetLoadEventFiring(true); EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status); d->SetLoadEventFiring(false); if (timing) { timing->NotifyLoadEventEnd(); } } } else { // XXX: Should fire error event to the document... } // Notify the document that it has been shown (regardless of whether // it was just loaded). Note: mDocument may be null now if the above // firing of onload caused the document to unload. if (mDocument) { // Re-get window, since it might have changed during above firing of onload window = mDocument->GetWindow(); if (window) { nsIDocShell *docShell = window->GetDocShell(); bool isInUnload; if (docShell && NS_SUCCEEDED(docShell->GetIsInUnload(&isInUnload)) && !isInUnload) { mDocument->OnPageShow(restoring, nullptr); } } } if (!mStopped) { if (mDocument) { mDocument->ScrollToRef(); } // Now that the document has loaded, we can tell the presshell // to unsuppress painting. if (mPresShell) { nsCOMPtr shell(mPresShell); shell->UnsuppressPainting(); // mPresShell could have been removed now, see bug 378682/421432 if (mPresShell) { mPresShell->LoadComplete(); } } } nsJSContext::LoadEnd(); #ifdef NS_PRINTING // Check to see if someone tried to print during the load if (mPrintIsPending) { mPrintIsPending = false; mPrintDocIsFullyLoaded = true; Print(mCachedPrintSettings, mCachedPrintWebProgressListner); mCachedPrintSettings = nullptr; mCachedPrintWebProgressListner = nullptr; } #endif return rv; } NS_IMETHODIMP nsDocumentViewer::GetLoadCompleted(bool *aOutLoadCompleted) { *aOutLoadCompleted = mLoaded; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::PermitUnload(bool *aPermitUnload) { bool shouldPrompt = true; return PermitUnloadInternal(&shouldPrompt, aPermitUnload); } nsresult nsDocumentViewer::PermitUnloadInternal(bool *aShouldPrompt, bool *aPermitUnload) { AutoDontWarnAboutSyncXHR disableSyncXHRWarning; nsresult rv = NS_OK; *aPermitUnload = true; if (!mDocument || mInPermitUnload || mInPermitUnloadPrompt) { return NS_OK; } static bool sIsBeforeUnloadDisabled; static bool sBeforeUnloadRequiresInteraction; static bool sBeforeUnloadPrefsCached = false; if (!sBeforeUnloadPrefsCached) { sBeforeUnloadPrefsCached = true; Preferences::AddBoolVarCache(&sIsBeforeUnloadDisabled, BEFOREUNLOAD_DISABLED_PREFNAME); Preferences::AddBoolVarCache(&sBeforeUnloadRequiresInteraction, BEFOREUNLOAD_REQUIRES_INTERACTION_PREFNAME); } // First, get the script global object from the document... nsPIDOMWindowOuter* window = mDocument->GetWindow(); if (!window) { // This is odd, but not fatal NS_WARNING("window not set for document!"); return NS_OK; } NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "This is unsafe"); // Now, fire an BeforeUnload event to the document and see if it's ok // to unload... nsCOMPtr domDoc = do_QueryInterface(mDocument); nsCOMPtr event; domDoc->CreateEvent(NS_LITERAL_STRING("beforeunloadevent"), getter_AddRefs(event)); nsCOMPtr beforeUnload = do_QueryInterface(event); NS_ENSURE_STATE(beforeUnload); event->InitEvent(NS_LITERAL_STRING("beforeunload"), false, true); // Dispatching to |window|, but using |document| as the target. event->SetTarget(mDocument); event->SetTrusted(true); // In evil cases we might be destroyed while handling the // onbeforeunload event, don't let that happen. (see also bug#331040) RefPtr kungFuDeathGrip(this); bool dialogsAreEnabled = false; { // Never permit popups from the beforeunload handler, no matter // how we get here. nsAutoPopupStatePusher popupStatePusher(openAbused, true); // Never permit dialogs from the beforeunload handler nsGlobalWindow* globalWindow = nsGlobalWindow::Cast(window); dialogsAreEnabled = globalWindow->AreDialogsEnabled(); nsGlobalWindow::TemporarilyDisableDialogs disableDialogs(globalWindow); nsIDocument::PageUnloadingEventTimeStamp timestamp(mDocument); mInPermitUnload = true; EventDispatcher::DispatchDOMEvent(window, nullptr, event, mPresContext, nullptr); mInPermitUnload = false; } nsCOMPtr docShell(mContainer); nsAutoString text; beforeUnload->GetReturnValue(text); // NB: we nullcheck mDocument because it might now be dead as a result of // the event being dispatched. if (!sIsBeforeUnloadDisabled && *aShouldPrompt && dialogsAreEnabled && mDocument && !(mDocument->GetSandboxFlags() & SANDBOXED_MODALS) && (!sBeforeUnloadRequiresInteraction || mDocument->UserHasInteracted()) && (event->WidgetEventPtr()->DefaultPrevented() || !text.IsEmpty())) { // Ask the user if it's ok to unload the current page nsCOMPtr prompt = do_GetInterface(docShell); if (prompt) { nsCOMPtr promptBag = do_QueryInterface(prompt); if (promptBag) { bool isTabModalPromptAllowed; GetIsTabModalPromptAllowed(&isTabModalPromptAllowed); promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), isTabModalPromptAllowed); } nsXPIDLString title, message, stayLabel, leaveLabel; rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, "OnBeforeUnloadTitle", title); nsresult tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, "OnBeforeUnloadMessage", message); if (NS_FAILED(tmp)) { rv = tmp; } tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, "OnBeforeUnloadLeaveButton", leaveLabel); if (NS_FAILED(tmp)) { rv = tmp; } tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, "OnBeforeUnloadStayButton", stayLabel); if (NS_FAILED(tmp)) { rv = tmp; } if (NS_FAILED(rv) || !title || !message || !stayLabel || !leaveLabel) { NS_ERROR("Failed to get strings from dom.properties!"); return NS_OK; } // Although the exact value is ignored, we must not pass invalid // bool values through XPConnect. bool dummy = false; int32_t buttonPressed = 0; uint32_t buttonFlags = (nsIPrompt::BUTTON_POS_0_DEFAULT | (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) | (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_1)); nsAutoSyncOperation sync(mDocument); mInPermitUnloadPrompt = true; rv = prompt->ConfirmEx(title, message, buttonFlags, leaveLabel, stayLabel, nullptr, nullptr, &dummy, &buttonPressed); mInPermitUnloadPrompt = false; // If the prompt aborted, we tell our consumer that it is not allowed // to unload the page. One reason that prompts abort is that the user // performed some action that caused the page to unload while our prompt // was active. In those cases we don't want our consumer to also unload // the page. // // XXX: Are there other cases where prompts can abort? Is it ok to // prevent unloading the page in those cases? if (NS_FAILED(rv)) { *aPermitUnload = false; return NS_OK; } // Button 0 == leave, button 1 == stay *aPermitUnload = (buttonPressed == 0); // If the user decided to go ahead, make sure not to prompt the user again // by toggling the internal prompting bool to false: if (*aPermitUnload) { *aShouldPrompt = false; } } } if (docShell) { int32_t childCount; docShell->GetChildCount(&childCount); for (int32_t i = 0; i < childCount && *aPermitUnload; ++i) { nsCOMPtr item; docShell->GetChildAt(i, getter_AddRefs(item)); nsCOMPtr docShell(do_QueryInterface(item)); if (docShell) { nsCOMPtr cv; docShell->GetContentViewer(getter_AddRefs(cv)); if (cv) { cv->PermitUnloadInternal(aShouldPrompt, aPermitUnload); } } } } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetBeforeUnloadFiring(bool* aInEvent) { *aInEvent = mInPermitUnload; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetInPermitUnload(bool* aInEvent) { *aInEvent = mInPermitUnloadPrompt; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::PageHide(bool aIsUnload) { AutoDontWarnAboutSyncXHR disableSyncXHRWarning; mHidden = true; if (!mDocument) { return NS_ERROR_NULL_POINTER; } mDocument->OnPageHide(!aIsUnload, nullptr); // inform the window so that the focus state is reset. NS_ENSURE_STATE(mDocument); nsPIDOMWindowOuter* window = mDocument->GetWindow(); if (window) window->PageHidden(); if (aIsUnload) { // Poke the GC. The window might be collectable garbage now. nsJSContext::PokeGC(JS::gcreason::PAGE_HIDE, NS_GC_DELAY * 2); // if Destroy() was called during OnPageHide(), mDocument is nullptr. NS_ENSURE_STATE(mDocument); // First, get the window from the document... nsPIDOMWindowOuter* window = mDocument->GetWindow(); if (!window) { // Fail if no window is available... NS_WARNING("window not set for document!"); return NS_ERROR_NULL_POINTER; } // Now, fire an Unload event to the document... nsEventStatus status = nsEventStatus_eIgnore; WidgetEvent event(true, eUnload); event.mFlags.mBubbles = false; // XXX Dispatching to |window|, but using |document| as the target. event.mTarget = mDocument; // Never permit popups from the unload handler, no matter how we get // here. nsAutoPopupStatePusher popupStatePusher(openAbused, true); nsIDocument::PageUnloadingEventTimeStamp timestamp(mDocument); EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status); } // look for open menupopups and close them after the unload event, in case // the unload event listeners open any new popups nsContentUtils::HidePopupsInDocument(mDocument); return NS_OK; } static void AttachContainerRecurse(nsIDocShell* aShell) { nsCOMPtr viewer; aShell->GetContentViewer(getter_AddRefs(viewer)); if (viewer) { viewer->SetIsHidden(false); nsIDocument* doc = viewer->GetDocument(); if (doc) { doc->SetContainer(static_cast(aShell)); } RefPtr pc; viewer->GetPresContext(getter_AddRefs(pc)); if (pc) { pc->SetContainer(static_cast(aShell)); nsCOMPtr handler = do_QueryInterface(aShell); pc->SetLinkHandler(handler); } nsCOMPtr presShell; viewer->GetPresShell(getter_AddRefs(presShell)); if (presShell) { presShell->SetForwardingContainer(WeakPtr()); } } // Now recurse through the children int32_t childCount; aShell->GetChildCount(&childCount); for (int32_t i = 0; i < childCount; ++i) { nsCOMPtr childItem; aShell->GetChildAt(i, getter_AddRefs(childItem)); nsCOMPtr shell = do_QueryInterface(childItem); AttachContainerRecurse(shell); } } NS_IMETHODIMP nsDocumentViewer::Open(nsISupports *aState, nsISHEntry *aSHEntry) { NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED); if (mDocument) mDocument->SetContainer(mContainer); nsresult rv = InitInternal(mParentWidget, aState, mBounds, false); NS_ENSURE_SUCCESS(rv, rv); mHidden = false; if (mPresShell) mPresShell->SetForwardingContainer(WeakPtr()); // Rehook the child presentations. The child shells are still in // session history, so get them from there. if (aSHEntry) { nsCOMPtr item; int32_t itemIndex = 0; while (NS_SUCCEEDED(aSHEntry->ChildShellAt(itemIndex++, getter_AddRefs(item))) && item) { nsCOMPtr shell = do_QueryInterface(item); AttachContainerRecurse(shell); } } SyncParentSubDocMap(); if (mFocusListener && mDocument) { mDocument->AddEventListener(NS_LITERAL_STRING("focus"), mFocusListener, false, false); mDocument->AddEventListener(NS_LITERAL_STRING("blur"), mFocusListener, false, false); } // XXX re-enable image animations once that works correctly PrepareToStartLoad(); // When loading a page from the bfcache with puppet widgets, we do the // widget attachment here (it is otherwise done in MakeWindow, which is // called for non-bfcache pages in the history, but not bfcache pages). // Attachment is necessary, since we get detached when another page // is browsed to. That is, if we are one page A, then when we go to // page B, we detach. So page A's view has no widget. If we then go // back to it, and it is in the bfcache, we will use that view, which // doesn't have a widget. The attach call here will properly attach us. if (nsIWidget::UsePuppetWidgets() && mPresContext && ShouldAttachToTopLevel()) { // If the old view is already attached to our parent, detach DetachFromTopLevelWidget(); nsViewManager *vm = GetViewManager(); MOZ_ASSERT(vm, "no view manager"); nsView* v = vm->GetRootView(); MOZ_ASSERT(v, "no root view"); MOZ_ASSERT(mParentWidget, "no mParentWidget to set"); v->AttachToTopLevelWidget(mParentWidget); mAttachedToParent = true; } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::Close(nsISHEntry *aSHEntry) { // All callers are supposed to call close to break circular // references. If we do this stuff in the destructor, the // destructor might never be called (especially if we're being // used from JS. mSHEntry = aSHEntry; // Close is also needed to disable scripts during paint suppression, // since we transfer the existing global object to the new document // that is loaded. In the future, the global object may become a proxy // for an object that can be switched in and out so that we don't need // to disable scripts during paint suppression. if (!mDocument) return NS_OK; #if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW) // Turn scripting back on // after PrintPreview had turned it off if (GetIsPrintPreview() && mPrintEngine) { mPrintEngine->TurnScriptingOn(true); } #endif #ifdef NS_PRINTING // A Close was called while we were printing // so don't clear the ScriptGlobalObject // or clear the mDocument below if (mPrintEngine && !mClosingWhilePrinting) { mClosingWhilePrinting = true; } else #endif { // out of band cleanup of docshell mDocument->SetScriptGlobalObject(nullptr); if (!mSHEntry && mDocument) mDocument->RemovedFromDocShell(); } if (mFocusListener) { mFocusListener->Disconnect(); if (mDocument) { mDocument->RemoveEventListener(NS_LITERAL_STRING("focus"), mFocusListener, false); mDocument->RemoveEventListener(NS_LITERAL_STRING("blur"), mFocusListener, false); } } return NS_OK; } static void DetachContainerRecurse(nsIDocShell *aShell) { // Unhook this docshell's presentation nsCOMPtr viewer; aShell->GetContentViewer(getter_AddRefs(viewer)); if (viewer) { nsIDocument* doc = viewer->GetDocument(); if (doc) { doc->SetContainer(nullptr); } RefPtr pc; viewer->GetPresContext(getter_AddRefs(pc)); if (pc) { pc->Detach(); } nsCOMPtr presShell; viewer->GetPresShell(getter_AddRefs(presShell)); if (presShell) { auto weakShell = static_cast(aShell); presShell->SetForwardingContainer(weakShell); } } // Now recurse through the children int32_t childCount; aShell->GetChildCount(&childCount); for (int32_t i = 0; i < childCount; ++i) { nsCOMPtr childItem; aShell->GetChildAt(i, getter_AddRefs(childItem)); nsCOMPtr shell = do_QueryInterface(childItem); DetachContainerRecurse(shell); } } NS_IMETHODIMP nsDocumentViewer::Destroy() { #ifdef NS_PRINTING // Here is where we check to see if the document was still being prepared // for printing when it was asked to be destroy from someone externally // This usually happens if the document is unloaded while the user is in the // Print Dialog // // So we flip the bool to remember that the document is going away // and we can clean up and abort later after returning from the Print Dialog if (mPrintEngine) { if (mPrintEngine->CheckBeforeDestroy()) { return NS_OK; } } // Dispatch the 'afterprint' event now, if pending: mAutoBeforeAndAfterPrint = nullptr; #endif // Don't let the document get unloaded while we are printing. // this could happen if we hit the back button during printing. // We also keep the viewer from being cached in session history, since // we require all documents there to be sanitized. if (mDestroyRefCount != 0) { return NS_OK; } // If we were told to put ourselves into session history instead of destroy // the presentation, do that now. if (mSHEntry) { if (mPresShell) mPresShell->Freeze(); // Make sure the presentation isn't torn down by Hide(). mSHEntry->SetSticky(mIsSticky); mIsSticky = true; bool savePresentation = mDocument ? mDocument->IsBFCachingAllowed() : true; // Remove our root view from the view hierarchy. if (mPresShell) { nsViewManager *vm = mPresShell->GetViewManager(); if (vm) { nsView *rootView = vm->GetRootView(); if (rootView) { nsView *rootViewParent = rootView->GetParent(); if (rootViewParent) { nsViewManager *parentVM = rootViewParent->GetViewManager(); if (parentVM) { parentVM->RemoveChild(rootView); } } } } } Hide(); // This is after Hide() so that the user doesn't see the inputs clear. if (mDocument) { mDocument->Sanitize(); } // Reverse ownership. Do this *after* calling sanitize so that sanitize // doesn't cause mutations that make the SHEntry drop the presentation // Grab a reference to mSHEntry before calling into things like // SyncPresentationState that might mess with our members. nsCOMPtr shEntry = mSHEntry; // we'll need this below mSHEntry = nullptr; if (savePresentation) { shEntry->SetContentViewer(this); } // Always sync the presentation state. That way even if someone screws up // and shEntry has no window state at this point we'll be ok; we just won't // cache ourselves. shEntry->SyncPresentationState(); // Shut down accessibility for the document before we start to tear it down. #ifdef ACCESSIBILITY if (mPresShell) { a11y::DocAccessible* docAcc = mPresShell->GetDocAccessible(); if (docAcc) { docAcc->Shutdown(); } } #endif // Break the link from the document/presentation to the docshell, so that // link traversals cannot affect the currently-loaded document. // When the presentation is restored, Open() and InitInternal() will reset // these pointers to their original values. if (mDocument) { mDocument->SetContainer(nullptr); } if (mPresContext) { mPresContext->Detach(); } if (mPresShell) { mPresShell->SetForwardingContainer(mContainer); } // Do the same for our children. Note that we need to get the child // docshells from the SHEntry now; the docshell will have cleared them. nsCOMPtr item; int32_t itemIndex = 0; while (NS_SUCCEEDED(shEntry->ChildShellAt(itemIndex++, getter_AddRefs(item))) && item) { nsCOMPtr shell = do_QueryInterface(item); DetachContainerRecurse(shell); } return NS_OK; } // The document was not put in the bfcache // Protect against pres shell destruction running scripts and re-entrantly // creating a new presentation. nsAutoScriptBlocker scriptBlocker; if (mPresShell) { DestroyPresShell(); } if (mDocument) { mDocument->Destroy(); mDocument = nullptr; } // All callers are supposed to call destroy to break circular // references. If we do this stuff in the destructor, the // destructor might never be called (especially if we're being // used from JS. #ifdef NS_PRINTING if (mPrintEngine) { RefPtr printEngine = mozilla::Move(mPrintEngine); #ifdef NS_PRINT_PREVIEW bool doingPrintPreview; printEngine->GetDoingPrintPreview(&doingPrintPreview); if (doingPrintPreview) { printEngine->FinishPrintPreview(); } #endif printEngine->Destroy(); MOZ_ASSERT(!mPrintEngine, "mPrintEngine shouldn't be recreated while destroying it"); } #endif // Avoid leaking the old viewer. if (mPreviousViewer) { mPreviousViewer->Destroy(); mPreviousViewer = nullptr; } mDeviceContext = nullptr; if (mPresContext) { DestroyPresContext(); } mWindow = nullptr; mViewManager = nullptr; mContainer = WeakPtr(); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::Stop(void) { NS_ASSERTION(mDocument, "Stop called too early or too late"); if (mDocument) { mDocument->StopDocumentLoad(); } if (!mHidden && (mLoaded || mStopped) && mPresContext && !mSHEntry) mPresContext->SetImageAnimationMode(imgIContainer::kDontAnimMode); mStopped = true; if (!mLoaded && mPresShell) { // Well, we might as well paint what we have so far. nsCOMPtr shell(mPresShell); // bug 378682 shell->UnsuppressPainting(); } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetDOMDocument(nsIDOMDocument **aResult) { NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); return CallQueryInterface(mDocument, aResult); } NS_IMETHODIMP_(nsIDocument *) nsDocumentViewer::GetDocument() { return mDocument; } NS_IMETHODIMP nsDocumentViewer::SetDOMDocument(nsIDOMDocument *aDocument) { // Assumptions: // // 1) this document viewer has been initialized with a call to Init(). // 2) the stylesheets associated with the document have been added // to the document. // XXX Right now, this method assumes that the layout of the current // document hasn't started yet. More cleanup will probably be // necessary to make this method work for the case when layout *has* // occurred for the current document. // That work can happen when and if it is needed. if (!aDocument) return NS_ERROR_NULL_POINTER; nsCOMPtr newDoc = do_QueryInterface(aDocument); NS_ENSURE_TRUE(newDoc, NS_ERROR_UNEXPECTED); return SetDocumentInternal(newDoc, false); } NS_IMETHODIMP nsDocumentViewer::SetDocumentInternal(nsIDocument* aDocument, bool aForceReuseInnerWindow) { MOZ_ASSERT(aDocument); // Set new container aDocument->SetContainer(mContainer); if (mDocument != aDocument) { if (aForceReuseInnerWindow) { // Transfer the navigation timing information to the new document, since // we're keeping the same inner and hence should really have the same // timing information. aDocument->SetNavigationTiming(mDocument->GetNavigationTiming()); } if (mDocument->IsStaticDocument()) { mDocument->Destroy(); } // Clear the list of old child docshells. Child docshells for the new // document will be constructed as frames are created. if (!aDocument->IsStaticDocument()) { nsCOMPtr node(mContainer); if (node) { int32_t count; node->GetChildCount(&count); for (int32_t i = 0; i < count; ++i) { nsCOMPtr child; node->GetChildAt(0, getter_AddRefs(child)); node->RemoveChild(child); } } } // Replace the old document with the new one. Do this only when // the new document really is a new document. mDocument = aDocument; // Set the script global object on the new document nsCOMPtr window = mContainer ? mContainer->GetWindow() : nullptr; if (window) { nsresult rv = window->SetNewDocument(aDocument, nullptr, aForceReuseInnerWindow); if (NS_FAILED(rv)) { Destroy(); return rv; } } } nsresult rv = SyncParentSubDocMap(); NS_ENSURE_SUCCESS(rv, rv); // Replace the current pres shell with a new shell for the new document // Protect against pres shell destruction running scripts and re-entrantly // creating a new presentation. nsAutoScriptBlocker scriptBlocker; if (mPresShell) { DestroyPresShell(); } if (mPresContext) { DestroyPresContext(); mWindow = nullptr; rv = InitInternal(mParentWidget, nullptr, mBounds, true, true, false); } return rv; } nsIPresShell* nsDocumentViewer::GetPresShell() { return mPresShell; } nsPresContext* nsDocumentViewer::GetPresContext() { return mPresContext; } nsViewManager* nsDocumentViewer::GetViewManager() { return mViewManager; } NS_IMETHODIMP nsDocumentViewer::GetPresShell(nsIPresShell** aResult) { nsIPresShell* shell = GetPresShell(); NS_IF_ADDREF(*aResult = shell); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetPresContext(nsPresContext** aResult) { nsPresContext* pc = GetPresContext(); NS_IF_ADDREF(*aResult = pc); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetBounds(nsIntRect& aResult) { NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); aResult = mBounds; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetPreviousViewer(nsIContentViewer** aViewer) { *aViewer = mPreviousViewer; NS_IF_ADDREF(*aViewer); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::SetPreviousViewer(nsIContentViewer* aViewer) { // NOTE: |Show| sets |mPreviousViewer| to null without calling this // function. if (aViewer) { NS_ASSERTION(!mPreviousViewer, "can't set previous viewer when there already is one"); // In a multiple chaining situation (which occurs when running a thrashing // test like i-bench or jrgm's tests with no delay), we can build up a // whole chain of viewers. In order to avoid this, we always set our previous // viewer to the MOST previous viewer in the chain, and then dump the intermediate // link from the chain. This ensures that at most only 2 documents are alive // and undestroyed at any given time (the one that is showing and the one that // is loading with painting suppressed). // It's very important that if this ever gets changed the code // before the RestorePresentation call in nsDocShell::InternalLoad // be changed accordingly. nsCOMPtr prevViewer; aViewer->GetPreviousViewer(getter_AddRefs(prevViewer)); if (prevViewer) { aViewer->SetPreviousViewer(nullptr); aViewer->Destroy(); return SetPreviousViewer(prevViewer); } } mPreviousViewer = aViewer; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::SetBoundsWithFlags(const nsIntRect& aBounds, uint32_t aFlags) { NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); mBounds = aBounds; if (mWindow && !mAttachedToParent) { // Resize the widget, but don't trigger repaint. Layout will generate // repaint requests during reflow. mWindow->Resize(aBounds.x, aBounds.y, aBounds.width, aBounds.height, false); } else if (mPresContext && mViewManager) { // Ensure presContext's deviceContext is up to date, as we sometimes get // here before a resolution-change notification has been fully handled // during display configuration changes, especially when there are lots // of windows/widgets competing to handle the notifications. // (See bug 1154125.) if (mPresContext->DeviceContext()->CheckDPIChange()) { mPresContext->UIResolutionChanged(); } int32_t p2a = mPresContext->AppUnitsPerDevPixel(); mViewManager->SetWindowDimensions(NSIntPixelsToAppUnits(mBounds.width, p2a), NSIntPixelsToAppUnits(mBounds.height, p2a), !!(aFlags & nsIContentViewer::eDelayResize)); } // If there's a previous viewer, it's the one that's actually showing, // so be sure to resize it as well so it paints over the right area. // This may slow down the performance of the new page load, but resize // during load is also probably a relatively unusual condition // relating to things being hidden while something is loaded. It so // happens that Firefox does this a good bit with its infobar, and it // looks ugly if we don't do this. if (mPreviousViewer) { nsCOMPtr previousViewer = mPreviousViewer; previousViewer->SetBounds(aBounds); } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::SetBounds(const nsIntRect& aBounds) { return SetBoundsWithFlags(aBounds, 0); } NS_IMETHODIMP nsDocumentViewer::Move(int32_t aX, int32_t aY) { NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); mBounds.MoveTo(aX, aY); if (mWindow) { mWindow->Move(aX, aY); } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::Show(void) { NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); // We don't need the previous viewer anymore since we're not // displaying it. if (mPreviousViewer) { // This little dance *may* only be to keep // PresShell::EndObservingDocument happy, but I'm not sure. nsCOMPtr prevViewer(mPreviousViewer); mPreviousViewer = nullptr; prevViewer->Destroy(); // Make sure we don't have too many cached ContentViewers nsCOMPtr treeItem(mContainer); if (treeItem) { // We need to find the root DocShell since only that object has an // SHistory and we need the SHistory to evict content viewers nsCOMPtr root; treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root)); nsCOMPtr webNav = do_QueryInterface(root); nsCOMPtr history; webNav->GetSessionHistory(getter_AddRefs(history)); nsCOMPtr historyInt = do_QueryInterface(history); if (historyInt) { int32_t prevIndex,loadedIndex; nsCOMPtr docShell = do_QueryInterface(treeItem); docShell->GetPreviousTransIndex(&prevIndex); docShell->GetLoadedTransIndex(&loadedIndex); #ifdef DEBUG_PAGE_CACHE printf("About to evict content viewers: prev=%d, loaded=%d\n", prevIndex, loadedIndex); #endif historyInt->EvictOutOfRangeContentViewers(loadedIndex); } } } if (mWindow) { // When attached to a top level xul window, we do not need to call // Show on the widget. Underlying window management code handles // this when the window is initialized. if (!mAttachedToParent) { mWindow->Show(true); } } // Hold on to the document so we can use it after the script blocker below // has been released (which might re-entrantly call into other // nsDocumentViewer methods). nsCOMPtr document = mDocument; if (mDocument && !mPresShell) { // The InitPresentationStuff call below requires a script blocker, because // its PresShell::Initialize call can cause scripts to run and therefore // re-entrant calls to nsDocumentViewer methods to be made. nsAutoScriptBlocker scriptBlocker; NS_ASSERTION(!mWindow, "Window already created but no presshell?"); nsCOMPtr base_win(mContainer); if (base_win) { base_win->GetParentWidget(&mParentWidget); if (mParentWidget) { mParentWidget->Release(); // GetParentWidget AddRefs, but mParentWidget is weak } } nsView* containerView = FindContainerView(); nsresult rv = CreateDeviceContext(containerView); NS_ENSURE_SUCCESS(rv, rv); // Create presentation context NS_ASSERTION(!mPresContext, "Shouldn't have a prescontext if we have no shell!"); mPresContext = CreatePresContext(mDocument, nsPresContext::eContext_Galley, containerView); NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY); rv = mPresContext->Init(mDeviceContext); if (NS_FAILED(rv)) { mPresContext = nullptr; return rv; } rv = MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(mBounds.width), mPresContext->DevPixelsToAppUnits(mBounds.height)), containerView); if (NS_FAILED(rv)) return rv; if (mPresContext && base_win) { nsCOMPtr linkHandler(do_GetInterface(base_win)); if (linkHandler) { mPresContext->SetLinkHandler(linkHandler); } mPresContext->SetContainer(mContainer); } if (mPresContext) { Hide(); rv = InitPresentationStuff(mDocument->MayStartLayout()); } // If we get here the document load has already started and the // window is shown because some JS on the page caused it to be // shown... if (mPresShell) { nsCOMPtr shell(mPresShell); // bug 378682 shell->UnsuppressPainting(); } } // Notify observers that a new page has been shown. This will get run // from the event loop after we actually draw the page. NS_DispatchToMainThread(new nsDocumentShownDispatcher(document)); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::Hide(void) { if (!mAttachedToParent && mWindow) { mWindow->Show(false); } if (!mPresShell) return NS_OK; NS_ASSERTION(mPresContext, "Can't have a presshell and no prescontext!"); // Avoid leaking the old viewer. if (mPreviousViewer) { mPreviousViewer->Destroy(); mPreviousViewer = nullptr; } if (mIsSticky) { // This window is sticky, that means that it might be shown again // and we don't want the presshell n' all that to be thrown away // just because the window is hidden. return NS_OK; } nsCOMPtr docShell(mContainer); if (docShell) { #ifdef DEBUG nsCOMPtr currentViewer; docShell->GetContentViewer(getter_AddRefs(currentViewer)); MOZ_ASSERT(currentViewer == this); #endif nsCOMPtr layoutState; mPresShell->CaptureHistoryState(getter_AddRefs(layoutState)); } // Do not run ScriptRunners queued by DestroyPresShell() in the intermediate // state before we're done destroying PresShell, PresContext, ViewManager, etc. nsAutoScriptBlocker scriptBlocker; DestroyPresShell(); DestroyPresContext(); mViewManager = nullptr; mWindow = nullptr; mDeviceContext = nullptr; mParentWidget = nullptr; nsCOMPtr base_win(mContainer); if (base_win && !mAttachedToParent) { base_win->SetParentWidget(nullptr); } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetSticky(bool *aSticky) { *aSticky = mIsSticky; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::SetSticky(bool aSticky) { mIsSticky = aSticky; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::RequestWindowClose(bool* aCanClose) { #ifdef NS_PRINTING if (mPrintIsPending || (mPrintEngine && mPrintEngine->GetIsPrinting())) { *aCanClose = false; mDeferredWindowClose = true; } else #endif *aCanClose = true; return NS_OK; } StyleSetHandle nsDocumentViewer::CreateStyleSet(nsIDocument* aDocument) { // Make sure this does the same thing as PresShell::AddSheet wrt ordering. // this should eventually get expanded to allow for creating // different sets for different media StyleBackendType backendType = aDocument->GetStyleBackendType(); StyleSetHandle styleSet; if (backendType == StyleBackendType::Gecko) { styleSet = new nsStyleSet(); } else { styleSet = new ServoStyleSet(); } styleSet->BeginUpdate(); // The document will fill in the document sheets when we create the presshell if (aDocument->IsBeingUsedAsImage()) { MOZ_ASSERT(aDocument->IsSVGDocument(), "Do we want to skip most sheets for this new image type?"); // SVG-as-an-image must be kept as light and small as possible. We // deliberately skip loading everything and leave svg.css (and html.css and // xul.css) to be loaded on-demand. // XXXjwatt Nothing else is loaded on-demand, but I don't think that // should matter for SVG-as-an-image. If it does, I want to know why! // Caller will handle calling EndUpdate, per contract. return styleSet; } auto cache = nsLayoutStylesheetCache::For(backendType); // Handle the user sheets. StyleSheet* sheet = nullptr; if (nsContentUtils::IsInChromeDocshell(aDocument)) { sheet = cache->UserChromeSheet(); } else { sheet = cache->UserContentSheet(); } if (sheet) styleSet->AppendStyleSheet(SheetType::User, sheet); // Append chrome sheets (scrollbars + forms). bool shouldOverride = false; // We don't want a docshell here for external resource docs, so just // look at mContainer. nsCOMPtr ds(mContainer); nsCOMPtr chromeHandler; nsCOMPtr uri; RefPtr chromeSheet; if (ds) { ds->GetChromeEventHandler(getter_AddRefs(chromeHandler)); } if (chromeHandler) { nsCOMPtr elt(do_QueryInterface(chromeHandler)); nsCOMPtr content(do_QueryInterface(elt)); if (elt && content) { nsCOMPtr baseURI = content->GetBaseURI(); nsAutoString sheets; elt->GetAttribute(NS_LITERAL_STRING("usechromesheets"), sheets); if (!sheets.IsEmpty() && baseURI) { RefPtr cssLoader = new mozilla::css::Loader(backendType); char *str = ToNewCString(sheets); char *newStr = str; char *token; while ( (token = nsCRT::strtok(newStr, ", ", &newStr)) ) { NS_NewURI(getter_AddRefs(uri), nsDependentCString(token), nullptr, baseURI); if (!uri) continue; cssLoader->LoadSheetSync(uri, &chromeSheet); if (!chromeSheet) continue; styleSet->PrependStyleSheet(SheetType::Agent, chromeSheet); shouldOverride = true; } free(str); } } } if (!shouldOverride) { sheet = cache->ScrollbarsSheet(); if (sheet) { styleSet->PrependStyleSheet(SheetType::Agent, sheet); } } if (!aDocument->IsSVGDocument()) { // !!! IMPORTANT - KEEP THIS BLOCK IN SYNC WITH // !!! SVGDocument::EnsureNonSVGUserAgentStyleSheetsLoaded. // SVGForeignObjectElement::BindToTree calls SVGDocument:: // EnsureNonSVGUserAgentStyleSheetsLoaded to loads these UA sheet // on-demand. (Excluding the quirks sheet, which should never be loaded for // an SVG document, and excluding xul.css which will be loaded on demand by // nsXULElement::BindToTree.) sheet = cache->NumberControlSheet(); if (sheet) { styleSet->PrependStyleSheet(SheetType::Agent, sheet); } sheet = cache->FormsSheet(); if (sheet) { styleSet->PrependStyleSheet(SheetType::Agent, sheet); } if (aDocument->LoadsFullXULStyleSheetUpFront()) { // nsXULElement::BindToTree loads xul.css on-demand if we don't load it // up-front here. sheet = cache->XULSheet(); if (sheet) { styleSet->PrependStyleSheet(SheetType::Agent, sheet); } } sheet = cache->MinimalXULSheet(); if (sheet) { // Load the minimal XUL rules for scrollbars and a few other XUL things // that non-XUL (typically HTML) documents commonly use. styleSet->PrependStyleSheet(SheetType::Agent, sheet); } sheet = cache->CounterStylesSheet(); if (sheet) { styleSet->PrependStyleSheet(SheetType::Agent, sheet); } if (nsLayoutUtils::ShouldUseNoScriptSheet(aDocument)) { sheet = cache->NoScriptSheet(); if (sheet) { styleSet->PrependStyleSheet(SheetType::Agent, sheet); } } if (nsLayoutUtils::ShouldUseNoFramesSheet(aDocument)) { sheet = cache->NoFramesSheet(); if (sheet) { styleSet->PrependStyleSheet(SheetType::Agent, sheet); } } // We don't add quirk.css here; nsPresContext::CompatibilityModeChanged will // append it if needed. sheet = cache->HTMLSheet(); if (sheet) { styleSet->PrependStyleSheet(SheetType::Agent, sheet); } styleSet->PrependStyleSheet(SheetType::Agent, cache->UASheet()); } else { // SVG documents may have scrollbars and need the scrollbar styling. sheet = cache->MinimalXULSheet(); if (sheet) { styleSet->PrependStyleSheet(SheetType::Agent, sheet); } } if (styleSet->IsGecko()) { nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); if (sheetService) { for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) { styleSet->AppendStyleSheet(SheetType::Agent, sheet); } for (StyleSheet* sheet : Reversed(*sheetService->UserStyleSheets())) { styleSet->PrependStyleSheet(SheetType::User, sheet); } } } else { NS_WARNING("stylo: Not yet checking nsStyleSheetService for Servo-backed " "documents. See bug 1290224"); } // Caller will handle calling EndUpdate, per contract. return styleSet; } NS_IMETHODIMP nsDocumentViewer::ClearHistoryEntry() { mSHEntry = nullptr; return NS_OK; } //------------------------------------------------------- nsresult nsDocumentViewer::MakeWindow(const nsSize& aSize, nsView* aContainerView) { if (GetIsPrintPreview()) return NS_OK; bool shouldAttach = ShouldAttachToTopLevel(); if (shouldAttach) { // If the old view is already attached to our parent, detach DetachFromTopLevelWidget(); } mViewManager = new nsViewManager(); nsDeviceContext *dx = mPresContext->DeviceContext(); nsresult rv = mViewManager->Init(dx); if (NS_FAILED(rv)) return rv; // The root view is always at 0,0. nsRect tbounds(nsPoint(0, 0), aSize); // Create a view nsView* view = mViewManager->CreateView(tbounds, aContainerView); if (!view) return NS_ERROR_OUT_OF_MEMORY; // Create a widget if we were given a parent widget or don't have a // container view that we can hook up to without a widget. // Don't create widgets for ResourceDocs (external resources & svg images), // because when they're displayed, they're painted into *another* document's // widget. if (!mDocument->IsResourceDoc() && (mParentWidget || !aContainerView)) { // pass in a native widget to be the parent widget ONLY if the view hierarchy will stand alone. // otherwise the view will find its own parent widget and "do the right thing" to // establish a parent/child widget relationship nsWidgetInitData initData; nsWidgetInitData* initDataPtr; if (!mParentWidget) { initDataPtr = &initData; initData.mWindowType = eWindowType_invisible; } else { initDataPtr = nullptr; } if (shouldAttach) { // Reuse the top level parent widget. rv = view->AttachToTopLevelWidget(mParentWidget); mAttachedToParent = true; } else if (!aContainerView && mParentWidget) { rv = view->CreateWidgetForParent(mParentWidget, initDataPtr, true, false); } else { rv = view->CreateWidget(initDataPtr, true, false); } if (NS_FAILED(rv)) return rv; } // Setup hierarchical relationship in view manager mViewManager->SetRootView(view); mWindow = view->GetWidget(); // This SetFocus is necessary so the Arrow Key and Page Key events // go to the scrolled view as soon as the Window is created instead of going to // the browser window (this enables keyboard scrolling of the document) // mWindow->SetFocus(); return rv; } void nsDocumentViewer::DetachFromTopLevelWidget() { if (mViewManager) { nsView* oldView = mViewManager->GetRootView(); if (oldView && oldView->IsAttachedToTopLevel()) { oldView->DetachFromTopLevelWidget(); } } mAttachedToParent = false; } nsView* nsDocumentViewer::FindContainerView() { if (!mContainer) { return nullptr; } nsCOMPtr docShell(mContainer); nsCOMPtr pwin(docShell->GetWindow()); if (!pwin) { return nullptr; } nsCOMPtr containerElement = pwin->GetFrameElementInternal(); if (!containerElement) { return nullptr; } nsIFrame* subdocFrame = nsLayoutUtils::GetRealPrimaryFrameFor(containerElement); if (!subdocFrame) { // XXX Silenced by default in bug 1175289 LAYOUT_WARNING("Subdocument container has no frame"); return nullptr; } // subdocFrame might not be a subdocument frame; the frame // constructor can treat a as an inline in some XBL // cases. Treat that as display:none, the document is not // displayed. if (subdocFrame->GetType() != nsGkAtoms::subDocumentFrame) { NS_WARNING_ASSERTION(!subdocFrame->GetType(), "Subdocument container has non-subdocument frame"); return nullptr; } NS_ASSERTION(subdocFrame->GetView(), "Subdoc frames must have views"); return static_cast(subdocFrame)->EnsureInnerView(); } nsresult nsDocumentViewer::CreateDeviceContext(nsView* aContainerView) { NS_PRECONDITION(!mPresShell && !mWindow, "This will screw up our existing presentation"); NS_PRECONDITION(mDocument, "Gotta have a document here"); nsIDocument* doc = mDocument->GetDisplayDocument(); if (doc) { NS_ASSERTION(!aContainerView, "External resource document embedded somewhere?"); // We want to use our display document's device context if possible nsIPresShell* shell = doc->GetShell(); if (shell) { nsPresContext* ctx = shell->GetPresContext(); if (ctx) { mDeviceContext = ctx->DeviceContext(); return NS_OK; } } } // Create a device context even if we already have one, since our widget // might have changed. nsIWidget* widget = nullptr; if (aContainerView) { widget = aContainerView->GetNearestWidget(nullptr); } if (!widget) { widget = mParentWidget; } if (widget) { widget = widget->GetTopLevelWidget(); } mDeviceContext = new nsDeviceContext(); mDeviceContext->Init(widget); return NS_OK; } // Return the selection for the document. Note that text fields have their // own selection, which cannot be accessed with this method. mozilla::dom::Selection* nsDocumentViewer::GetDocumentSelection() { if (!mPresShell) { return nullptr; } return mPresShell->GetCurrentSelection(SelectionType::eNormal); } /* ======================================================================================== * nsIContentViewerEdit * ======================================================================================== */ NS_IMETHODIMP nsDocumentViewer::ClearSelection() { // use nsCopySupport::GetSelectionForCopy() ? RefPtr selection = GetDocumentSelection(); if (!selection) { return NS_ERROR_FAILURE; } return selection->CollapseToStart(); } NS_IMETHODIMP nsDocumentViewer::SelectAll() { // XXX this is a temporary implementation copied from nsWebShell // for now. I think nsDocument and friends should have some helper // functions to make this easier. // use nsCopySupport::GetSelectionForCopy() ? RefPtr selection = GetDocumentSelection(); if (!selection) { return NS_ERROR_FAILURE; } nsCOMPtr htmldoc = do_QueryInterface(mDocument); nsCOMPtr bodyNode; nsresult rv; if (htmldoc) { nsCOMPtrbodyElement; rv = htmldoc->GetBody(getter_AddRefs(bodyElement)); if (NS_FAILED(rv) || !bodyElement) return rv; bodyNode = do_QueryInterface(bodyElement); } else if (mDocument) { bodyNode = do_QueryInterface(mDocument->GetRootElement()); } if (!bodyNode) return NS_ERROR_FAILURE; rv = selection->RemoveAllRanges(); if (NS_FAILED(rv)) return rv; mozilla::dom::Selection::AutoUserInitiated userSelection(selection); rv = selection->SelectAllChildren(bodyNode); return rv; } NS_IMETHODIMP nsDocumentViewer::CopySelection() { nsCopySupport::FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard, mPresShell, nullptr); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::CopyLinkLocation() { NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED); nsCOMPtr node; GetPopupLinkNode(getter_AddRefs(node)); // make noise if we're not in a link NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); nsCOMPtr elm(do_QueryInterface(node)); NS_ENSURE_TRUE(elm, NS_ERROR_FAILURE); nsAutoString locationText; nsContentUtils::GetLinkLocation(elm, locationText); if (locationText.IsEmpty()) return NS_ERROR_FAILURE; nsresult rv = NS_OK; nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); // copy the href onto the clipboard return clipboard->CopyString(locationText); } NS_IMETHODIMP nsDocumentViewer::CopyImage(int32_t aCopyFlags) { NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED); nsCOMPtr node; GetPopupImageNode(getter_AddRefs(node)); // make noise if we're not in an image NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); nsCOMPtr loadContext(mContainer); return nsCopySupport::ImageCopy(node, loadContext, aCopyFlags); } NS_IMETHODIMP nsDocumentViewer::GetCopyable(bool *aCopyable) { NS_ENSURE_ARG_POINTER(aCopyable); *aCopyable = nsCopySupport::CanCopy(mDocument); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetContents(const char *mimeType, bool selectionOnly, nsAString& aOutValue) { aOutValue.Truncate(); NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED); NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED); // Now we have the selection. Make sure it's nonzero: nsCOMPtr sel; if (selectionOnly) { nsCopySupport::GetSelectionForCopy(mDocument, getter_AddRefs(sel)); NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE); bool isCollapsed; sel->GetIsCollapsed(&isCollapsed); if (isCollapsed) return NS_OK; } // call the copy code return nsCopySupport::GetContents(nsDependentCString(mimeType), 0, sel, mDocument, aOutValue); } NS_IMETHODIMP nsDocumentViewer::GetCanGetContents(bool *aCanGetContents) { NS_ENSURE_ARG_POINTER(aCanGetContents); *aCanGetContents = false; NS_ENSURE_STATE(mDocument); *aCanGetContents = nsCopySupport::CanCopy(mDocument); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::SetCommandNode(nsIDOMNode* aNode) { nsIDocument* document = GetDocument(); NS_ENSURE_STATE(document); nsCOMPtr window(document->GetWindow()); NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE); nsCOMPtr root = window->GetTopWindowRoot(); NS_ENSURE_STATE(root); root->SetPopupNode(aNode); return NS_OK; } /* ======================================================================================== * nsIContentViewerFile * ======================================================================================== */ /** --------------------------------------------------- * See documentation above in the nsIContentViewerfile class definition * @update 01/24/00 dwc */ NS_IMETHODIMP nsDocumentViewer::Print(bool aSilent, FILE * aDebugFile, nsIPrintSettings* aPrintSettings) { #ifdef NS_PRINTING nsCOMPtr printSettings; #ifdef DEBUG nsresult rv = NS_ERROR_FAILURE; mDebugFile = aDebugFile; // if they don't pass in a PrintSettings, then make one // it will have all the default values printSettings = aPrintSettings; nsCOMPtr printSettingsSvc = do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv); if (NS_SUCCEEDED(rv)) { // if they don't pass in a PrintSettings, then make one if (printSettings == nullptr) { printSettingsSvc->GetNewPrintSettings(getter_AddRefs(printSettings)); } NS_ASSERTION(printSettings, "You can't PrintPreview without a PrintSettings!"); } if (printSettings) printSettings->SetPrintSilent(aSilent); if (printSettings) printSettings->SetShowPrintProgress(false); #endif return Print(printSettings, nullptr); #else return NS_ERROR_FAILURE; #endif } // nsIContentViewerFile interface NS_IMETHODIMP nsDocumentViewer::GetPrintable(bool *aPrintable) { NS_ENSURE_ARG_POINTER(aPrintable); *aPrintable = !GetIsPrinting(); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::ScrollToNode(nsIDOMNode* aNode) { NS_ENSURE_ARG(aNode); NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); nsCOMPtr presShell; NS_ENSURE_SUCCESS(GetPresShell(getter_AddRefs(presShell)), NS_ERROR_FAILURE); // Get the nsIContent interface, because that's what we need to // get the primary frame nsCOMPtr content(do_QueryInterface(aNode)); NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); // Tell the PresShell to scroll to the primary frame of the content. NS_ENSURE_SUCCESS( presShell->ScrollContentIntoView(content, nsIPresShell::ScrollAxis( nsIPresShell::SCROLL_TOP, nsIPresShell::SCROLL_ALWAYS), nsIPresShell::ScrollAxis(), nsIPresShell::SCROLL_OVERFLOW_HIDDEN), NS_ERROR_FAILURE); return NS_OK; } void nsDocumentViewer::CallChildren(CallChildFunc aFunc, void* aClosure) { nsCOMPtr docShell(mContainer); if (docShell) { int32_t i; int32_t n; docShell->GetChildCount(&n); for (i=0; i < n; i++) { nsCOMPtr child; docShell->GetChildAt(i, getter_AddRefs(child)); nsCOMPtr childAsShell(do_QueryInterface(child)); NS_ASSERTION(childAsShell, "null child in docshell"); if (childAsShell) { nsCOMPtr childCV; childAsShell->GetContentViewer(getter_AddRefs(childCV)); if (childCV) { (*aFunc)(childCV, aClosure); } } } } } static void ChangeChildPaintingEnabled(nsIContentViewer* aChild, void* aClosure) { bool* enablePainting = (bool*) aClosure; if (*enablePainting) { aChild->ResumePainting(); } else { aChild->PausePainting(); } } struct ZoomInfo { float mZoom; }; static void SetChildTextZoom(nsIContentViewer* aChild, void* aClosure) { struct ZoomInfo* ZoomInfo = (struct ZoomInfo*) aClosure; aChild->SetTextZoom(ZoomInfo->mZoom); } static void SetChildMinFontSize(nsIContentViewer* aChild, void* aClosure) { aChild->SetMinFontSize(NS_PTR_TO_INT32(aClosure)); } static void SetChildFullZoom(nsIContentViewer* aChild, void* aClosure) { struct ZoomInfo* ZoomInfo = (struct ZoomInfo*) aClosure; aChild->SetFullZoom(ZoomInfo->mZoom); } static void SetChildOverrideDPPX(nsIContentViewer* aChild, void* aClosure) { struct ZoomInfo* ZoomInfo = (struct ZoomInfo*) aClosure; aChild->SetOverrideDPPX(ZoomInfo->mZoom); } static bool SetExtResourceTextZoom(nsIDocument* aDocument, void* aClosure) { // Would it be better to enumerate external resource viewers instead? nsIPresShell* shell = aDocument->GetShell(); if (shell) { nsPresContext* ctxt = shell->GetPresContext(); if (ctxt) { struct ZoomInfo* ZoomInfo = static_cast(aClosure); ctxt->SetTextZoom(ZoomInfo->mZoom); } } return true; } static bool SetExtResourceMinFontSize(nsIDocument* aDocument, void* aClosure) { nsIPresShell* shell = aDocument->GetShell(); if (shell) { nsPresContext* ctxt = shell->GetPresContext(); if (ctxt) { ctxt->SetBaseMinFontSize(NS_PTR_TO_INT32(aClosure)); } } return true; } static bool SetExtResourceFullZoom(nsIDocument* aDocument, void* aClosure) { // Would it be better to enumerate external resource viewers instead? nsIPresShell* shell = aDocument->GetShell(); if (shell) { nsPresContext* ctxt = shell->GetPresContext(); if (ctxt) { struct ZoomInfo* ZoomInfo = static_cast(aClosure); ctxt->SetFullZoom(ZoomInfo->mZoom); } } return true; } static bool SetExtResourceOverrideDPPX(nsIDocument* aDocument, void* aClosure) { nsIPresShell* shell = aDocument->GetShell(); if (shell) { nsPresContext* ctxt = shell->GetPresContext(); if (ctxt) { struct ZoomInfo* ZoomInfo = static_cast(aClosure); ctxt->SetOverrideDPPX(ZoomInfo->mZoom); } } return true; } NS_IMETHODIMP nsDocumentViewer::SetTextZoom(float aTextZoom) { // If we don't have a document, then we need to bail. if (!mDocument) { return NS_ERROR_FAILURE; } if (GetIsPrintPreview()) { return NS_OK; } mTextZoom = aTextZoom; // Set the text zoom on all children of mContainer (even if our zoom didn't // change, our children's zoom may be different, though it would be unusual). // Do this first, in case kids are auto-sizing and post reflow commands on // our presshell (which should be subsumed into our own style change reflow). struct ZoomInfo ZoomInfo = { aTextZoom }; CallChildren(SetChildTextZoom, &ZoomInfo); // Now change our own zoom nsPresContext* pc = GetPresContext(); if (pc && aTextZoom != mPresContext->TextZoom()) { pc->SetTextZoom(aTextZoom); } // And do the external resources mDocument->EnumerateExternalResources(SetExtResourceTextZoom, &ZoomInfo); nsContentUtils::DispatchChromeEvent(mDocument, static_cast(mDocument), NS_LITERAL_STRING("TextZoomChange"), true, true); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetTextZoom(float* aTextZoom) { NS_ENSURE_ARG_POINTER(aTextZoom); nsPresContext* pc = GetPresContext(); *aTextZoom = pc ? pc->TextZoom() : 1.0f; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::SetMinFontSize(int32_t aMinFontSize) { // If we don't have a document, then we need to bail. if (!mDocument) { return NS_ERROR_FAILURE; } if (GetIsPrintPreview()) { return NS_OK; } mMinFontSize = aMinFontSize; // Set the min font on all children of mContainer (even if our min font didn't // change, our children's min font may be different, though it would be unusual). // Do this first, in case kids are auto-sizing and post reflow commands on // our presshell (which should be subsumed into our own style change reflow). CallChildren(SetChildMinFontSize, NS_INT32_TO_PTR(aMinFontSize)); // Now change our own min font nsPresContext* pc = GetPresContext(); if (pc && aMinFontSize != mPresContext->MinFontSize(nullptr)) { pc->SetBaseMinFontSize(aMinFontSize); } // And do the external resources mDocument->EnumerateExternalResources(SetExtResourceMinFontSize, NS_INT32_TO_PTR(aMinFontSize)); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetMinFontSize(int32_t* aMinFontSize) { NS_ENSURE_ARG_POINTER(aMinFontSize); nsPresContext* pc = GetPresContext(); *aMinFontSize = pc ? pc->BaseMinFontSize() : 0; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::SetFullZoom(float aFullZoom) { #ifdef NS_PRINT_PREVIEW if (GetIsPrintPreview()) { nsPresContext* pc = GetPresContext(); NS_ENSURE_TRUE(pc, NS_OK); nsCOMPtr shell = pc->GetPresShell(); NS_ENSURE_TRUE(shell, NS_OK); if (!mPrintPreviewZoomed) { mOriginalPrintPreviewScale = pc->GetPrintPreviewScale(); mPrintPreviewZoomed = true; } mPrintPreviewZoom = aFullZoom; pc->SetPrintPreviewScale(aFullZoom * mOriginalPrintPreviewScale); nsIPageSequenceFrame* pf = shell->GetPageSequenceFrame(); if (pf) { nsIFrame* f = do_QueryFrame(pf); shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); } nsIFrame* rootFrame = shell->GetRootFrame(); if (rootFrame) { rootFrame->InvalidateFrame(); } return NS_OK; } #endif // If we don't have a document, then we need to bail. if (!mDocument) { return NS_ERROR_FAILURE; } bool fullZoomChange = (mPageZoom != aFullZoom); mPageZoom = aFullZoom; struct ZoomInfo ZoomInfo = { aFullZoom }; CallChildren(SetChildFullZoom, &ZoomInfo); nsPresContext* pc = GetPresContext(); if (pc) { pc->SetFullZoom(aFullZoom); } // And do the external resources mDocument->EnumerateExternalResources(SetExtResourceFullZoom, &ZoomInfo); // Dispatch FullZoomChange event only if fullzoom value really was been changed if (fullZoomChange) { nsContentUtils::DispatchChromeEvent(mDocument, static_cast(mDocument), NS_LITERAL_STRING("FullZoomChange"), true, true); } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetFullZoom(float* aFullZoom) { NS_ENSURE_ARG_POINTER(aFullZoom); #ifdef NS_PRINT_PREVIEW if (GetIsPrintPreview()) { *aFullZoom = mPrintPreviewZoom; return NS_OK; } #endif // Check the prescontext first because it might have a temporary // setting for print-preview nsPresContext* pc = GetPresContext(); *aFullZoom = pc ? pc->GetFullZoom() : mPageZoom; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::SetOverrideDPPX(float aDPPX) { // If we don't have a document, then we need to bail. if (!mDocument) { return NS_ERROR_FAILURE; } mOverrideDPPX = aDPPX; struct ZoomInfo ZoomInfo = { aDPPX }; CallChildren(SetChildOverrideDPPX, &ZoomInfo); nsPresContext* pc = GetPresContext(); if (pc) { pc->SetOverrideDPPX(aDPPX); } // And do the external resources mDocument->EnumerateExternalResources(SetExtResourceOverrideDPPX, &ZoomInfo); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetOverrideDPPX(float* aDPPX) { NS_ENSURE_ARG_POINTER(aDPPX); nsPresContext* pc = GetPresContext(); *aDPPX = pc ? pc->GetOverrideDPPX() : mOverrideDPPX; return NS_OK; } static void SetChildAuthorStyleDisabled(nsIContentViewer* aChild, void* aClosure) { bool styleDisabled = *static_cast(aClosure); aChild->SetAuthorStyleDisabled(styleDisabled); } NS_IMETHODIMP nsDocumentViewer::SetAuthorStyleDisabled(bool aStyleDisabled) { if (mPresShell) { mPresShell->SetAuthorStyleDisabled(aStyleDisabled); } CallChildren(SetChildAuthorStyleDisabled, &aStyleDisabled); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetAuthorStyleDisabled(bool* aStyleDisabled) { if (mPresShell) { *aStyleDisabled = mPresShell->GetAuthorStyleDisabled(); } else { *aStyleDisabled = false; } return NS_OK; } static bool ExtResourceEmulateMedium(nsIDocument* aDocument, void* aClosure) { nsIPresShell* shell = aDocument->GetShell(); if (shell) { nsPresContext* ctxt = shell->GetPresContext(); if (ctxt) { const nsAString* mediaType = static_cast(aClosure); ctxt->EmulateMedium(*mediaType); } } return true; } static void ChildEmulateMedium(nsIContentViewer* aChild, void* aClosure) { const nsAString* mediaType = static_cast(aClosure); aChild->EmulateMedium(*mediaType); } NS_IMETHODIMP nsDocumentViewer::EmulateMedium(const nsAString& aMediaType) { if (mPresContext) { mPresContext->EmulateMedium(aMediaType); } CallChildren(ChildEmulateMedium, const_cast(&aMediaType)); if (mDocument) { mDocument->EnumerateExternalResources(ExtResourceEmulateMedium, const_cast(&aMediaType)); } return NS_OK; } static bool ExtResourceStopEmulatingMedium(nsIDocument* aDocument, void* aClosure) { nsIPresShell* shell = aDocument->GetShell(); if (shell) { nsPresContext* ctxt = shell->GetPresContext(); if (ctxt) { ctxt->StopEmulatingMedium(); } } return true; } static void ChildStopEmulatingMedium(nsIContentViewer* aChild, void* aClosure) { aChild->StopEmulatingMedium(); } NS_IMETHODIMP nsDocumentViewer::StopEmulatingMedium() { if (mPresContext) { mPresContext->StopEmulatingMedium(); } CallChildren(ChildStopEmulatingMedium, nullptr); if (mDocument) { mDocument->EnumerateExternalResources(ExtResourceStopEmulatingMedium, nullptr); } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetForceCharacterSet(nsACString& aForceCharacterSet) { aForceCharacterSet = mForceCharacterSet; return NS_OK; } static void SetChildForceCharacterSet(nsIContentViewer* aChild, void* aClosure) { const nsACString* charset = static_cast(aClosure); aChild->SetForceCharacterSet(*charset); } NS_IMETHODIMP nsDocumentViewer::SetForceCharacterSet(const nsACString& aForceCharacterSet) { // This method is scriptable, so add-ons could pass in something other // than a canonical name. However, in case where the input is a canonical // name, "replacement" doesn't survive label resolution. Additionally, the // empty string means no hint. nsAutoCString encoding; if (!aForceCharacterSet.IsEmpty()) { if (aForceCharacterSet.EqualsLiteral("replacement")) { encoding.AssignLiteral("replacement"); } else if (!EncodingUtils::FindEncodingForLabel(aForceCharacterSet, encoding)) { // Reject unknown labels return NS_ERROR_INVALID_ARG; } } mForceCharacterSet = encoding; // now set the force char set on all children of mContainer CallChildren(SetChildForceCharacterSet, (void*) &aForceCharacterSet); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetHintCharacterSet(nsACString& aHintCharacterSet) { if(kCharsetUninitialized == mHintCharsetSource) { aHintCharacterSet.Truncate(); } else { aHintCharacterSet = mHintCharset; // this can't possibly be right. we can't set a value just because somebody got a related value! //mHintCharsetSource = kCharsetUninitialized; } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetHintCharacterSetSource(int32_t *aHintCharacterSetSource) { NS_ENSURE_ARG_POINTER(aHintCharacterSetSource); *aHintCharacterSetSource = mHintCharsetSource; return NS_OK; } static void SetChildHintCharacterSetSource(nsIContentViewer* aChild, void* aClosure) { aChild->SetHintCharacterSetSource(NS_PTR_TO_INT32(aClosure)); } NS_IMETHODIMP nsDocumentViewer::SetHintCharacterSetSource(int32_t aHintCharacterSetSource) { mHintCharsetSource = aHintCharacterSetSource; // now set the hint char set source on all children of mContainer CallChildren(SetChildHintCharacterSetSource, NS_INT32_TO_PTR(aHintCharacterSetSource)); return NS_OK; } static void SetChildHintCharacterSet(nsIContentViewer* aChild, void* aClosure) { const nsACString* charset = static_cast(aClosure); aChild->SetHintCharacterSet(*charset); } NS_IMETHODIMP nsDocumentViewer::SetHintCharacterSet(const nsACString& aHintCharacterSet) { // This method is scriptable, so add-ons could pass in something other // than a canonical name. However, in case where the input is a canonical // name, "replacement" doesn't survive label resolution. Additionally, the // empty string means no hint. nsAutoCString encoding; if (!aHintCharacterSet.IsEmpty()) { if (aHintCharacterSet.EqualsLiteral("replacement")) { encoding.AssignLiteral("replacement"); } else if (!EncodingUtils::FindEncodingForLabel(aHintCharacterSet, encoding)) { // Reject unknown labels return NS_ERROR_INVALID_ARG; } } mHintCharset = encoding; // now set the hint char set on all children of mContainer CallChildren(SetChildHintCharacterSet, (void*) &aHintCharacterSet); return NS_OK; } static void AppendChildSubtree(nsIContentViewer* aChild, void* aClosure) { nsTArray >& array = *static_cast >*>(aClosure); aChild->AppendSubtree(array); } NS_IMETHODIMP nsDocumentViewer::AppendSubtree(nsTArray >& aArray) { aArray.AppendElement(this); CallChildren(AppendChildSubtree, &aArray); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::PausePainting() { bool enablePaint = false; CallChildren(ChangeChildPaintingEnabled, &enablePaint); nsIPresShell* presShell = GetPresShell(); if (presShell) { presShell->PausePainting(); } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::ResumePainting() { bool enablePaint = true; CallChildren(ChangeChildPaintingEnabled, &enablePaint); nsIPresShell* presShell = GetPresShell(); if (presShell) { presShell->ResumePainting(); } return NS_OK; } nsresult nsDocumentViewer::GetContentSizeInternal(int32_t* aWidth, int32_t* aHeight, nscoord aMaxWidth, nscoord aMaxHeight) { NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); nsCOMPtr presShell; GetPresShell(getter_AddRefs(presShell)); NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); // Flush out all content and style updates. We can't use a resize reflow // because it won't change some sizes that a style change reflow will. mDocument->FlushPendingNotifications(Flush_Layout); nsIFrame *root = presShell->GetRootFrame(); NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); nscoord prefWidth; { nsRenderingContext rcx(presShell->CreateReferenceRenderingContext()); prefWidth = root->GetPrefISize(&rcx); } if (prefWidth > aMaxWidth) { prefWidth = aMaxWidth; } nsresult rv = presShell->ResizeReflow(prefWidth, NS_UNCONSTRAINEDSIZE); NS_ENSURE_SUCCESS(rv, rv); RefPtr presContext; GetPresContext(getter_AddRefs(presContext)); NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); // so how big is it? nsRect shellArea = presContext->GetVisibleArea(); if (shellArea.height > aMaxHeight) { // Reflow to max height if we would up too tall. rv = presShell->ResizeReflow(prefWidth, aMaxHeight); NS_ENSURE_SUCCESS(rv, rv); shellArea = presContext->GetVisibleArea(); } // Protect against bogus returns here NS_ENSURE_TRUE(shellArea.width != NS_UNCONSTRAINEDSIZE && shellArea.height != NS_UNCONSTRAINEDSIZE, NS_ERROR_FAILURE); *aWidth = presContext->AppUnitsToDevPixels(shellArea.width); *aHeight = presContext->AppUnitsToDevPixels(shellArea.height); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetContentSize(int32_t* aWidth, int32_t* aHeight) { // Skip doing this on docshell-less documents for now nsCOMPtr docShellAsItem(mContainer); NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_NOT_AVAILABLE); nsCOMPtr docShellParent; docShellAsItem->GetSameTypeParent(getter_AddRefs(docShellParent)); // It's only valid to access this from a top frame. Doesn't work from // sub-frames. NS_ENSURE_TRUE(!docShellParent, NS_ERROR_FAILURE); return GetContentSizeInternal(aWidth, aHeight, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); } NS_IMETHODIMP nsDocumentViewer::GetContentSizeConstrained(int32_t aMaxWidth, int32_t aMaxHeight, int32_t* aWidth, int32_t* aHeight) { RefPtr presContext; GetPresContext(getter_AddRefs(presContext)); NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); nscoord maxWidth = NS_UNCONSTRAINEDSIZE; nscoord maxHeight = NS_UNCONSTRAINEDSIZE; if (aMaxWidth > 0) { maxWidth = presContext->DevPixelsToAppUnits(aMaxWidth); } if (aMaxHeight > 0) { maxHeight = presContext->DevPixelsToAppUnits(aMaxHeight); } return GetContentSizeInternal(aWidth, aHeight, maxWidth, maxHeight); } NS_IMPL_ISUPPORTS(nsDocViewerSelectionListener, nsISelectionListener) nsresult nsDocViewerSelectionListener::Init(nsDocumentViewer *aDocViewer) { mDocViewer = aDocViewer; return NS_OK; } /* * GetPopupNode, GetPopupLinkNode and GetPopupImageNode are helpers * for the cmd_copyLink / cmd_copyImageLocation / cmd_copyImageContents family * of commands. The focus controller stores the popup node, these retrieve * them and munge appropriately. Note that we have to store the popup node * rather than retrieving it from EventStateManager::GetFocusedContent because * not all content (images included) can receive focus. */ nsresult nsDocumentViewer::GetPopupNode(nsIDOMNode** aNode) { NS_ENSURE_ARG_POINTER(aNode); *aNode = nullptr; // get the document nsIDocument* document = GetDocument(); NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); // get the private dom window nsCOMPtr window(document->GetWindow()); NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE); if (window) { nsCOMPtr root = window->GetTopWindowRoot(); NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); // get the popup node nsCOMPtr node = root->GetPopupNode(); if (!node) { nsPIDOMWindowOuter* rootWindow = root->GetWindow(); if (rootWindow) { nsCOMPtr rootDoc = rootWindow->GetExtantDoc(); if (rootDoc) { nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); if (pm) { node = pm->GetLastTriggerPopupNode(rootDoc); } } } } node.swap(*aNode); } return NS_OK; } // GetPopupLinkNode: return popup link node or fail nsresult nsDocumentViewer::GetPopupLinkNode(nsIDOMNode** aNode) { NS_ENSURE_ARG_POINTER(aNode); // you get null unless i say so *aNode = nullptr; // find popup node nsCOMPtr node; nsresult rv = GetPopupNode(getter_AddRefs(node)); NS_ENSURE_SUCCESS(rv, rv); // find out if we have a link in our ancestry while (node) { nsCOMPtr content(do_QueryInterface(node)); if (content) { nsCOMPtr hrefURI = content->GetHrefURI(); if (hrefURI) { *aNode = node; NS_IF_ADDREF(*aNode); // addref return NS_OK; } } // get our parent and keep trying... nsCOMPtr parentNode; node->GetParentNode(getter_AddRefs(parentNode)); node = parentNode; } // if we have no node, fail return NS_ERROR_FAILURE; } // GetPopupLinkNode: return popup image node or fail nsresult nsDocumentViewer::GetPopupImageNode(nsIImageLoadingContent** aNode) { NS_ENSURE_ARG_POINTER(aNode); // you get null unless i say so *aNode = nullptr; // find popup node nsCOMPtr node; nsresult rv = GetPopupNode(getter_AddRefs(node)); NS_ENSURE_SUCCESS(rv, rv); if (node) CallQueryInterface(node, aNode); return NS_OK; } /* * XXX dr * ------ * These two functions -- GetInLink and GetInImage -- are kind of annoying * in that they only get called from the controller (in * nsDOMWindowController::IsCommandEnabled). The actual construction of the * context menus in communicator (nsContextMenu.js) has its own, redundant * tests. No big deal, but good to keep in mind if we ever clean context * menus. */ NS_IMETHODIMP nsDocumentViewer::GetInLink(bool* aInLink) { #ifdef DEBUG_dr printf("dr :: nsDocumentViewer::GetInLink\n"); #endif NS_ENSURE_ARG_POINTER(aInLink); // we're not in a link unless i say so *aInLink = false; // get the popup link nsCOMPtr node; nsresult rv = GetPopupLinkNode(getter_AddRefs(node)); if (NS_FAILED(rv)) return rv; NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); // if we made it here, we're in a link *aInLink = true; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetInImage(bool* aInImage) { #ifdef DEBUG_dr printf("dr :: nsDocumentViewer::GetInImage\n"); #endif NS_ENSURE_ARG_POINTER(aInImage); // we're not in an image unless i say so *aInImage = false; // get the popup image nsCOMPtr node; nsresult rv = GetPopupImageNode(getter_AddRefs(node)); if (NS_FAILED(rv)) return rv; if (!node) { return NS_ERROR_FAILURE; } // Make sure there is a URI assigned. This allows to // be an image but rejects other types. This matches what // nsContextMenu.js does. nsCOMPtr uri; node->GetCurrentURI(getter_AddRefs(uri)); if (uri) { // if we made it here, we're in an image *aInImage = true; } return NS_OK; } NS_IMETHODIMP nsDocViewerSelectionListener::NotifySelectionChanged(nsIDOMDocument *, nsISelection *, int16_t aReason) { if (!mDocViewer) { return NS_OK; } // get the selection state RefPtr selection = mDocViewer->GetDocumentSelection(); if (!selection) { return NS_ERROR_FAILURE; } nsIDocument* theDoc = mDocViewer->GetDocument(); if (!theDoc) return NS_ERROR_FAILURE; nsCOMPtr domWindow = theDoc->GetWindow(); if (!domWindow) return NS_ERROR_FAILURE; bool selectionCollapsed; selection->GetIsCollapsed(&selectionCollapsed); // We only call UpdateCommands when the selection changes from collapsed to // non-collapsed or vice versa, however we skip the initializing collapse. We // might need another update string for simple selection changes, but that // would be expenseive. if (mSelectionWasCollapsed != selectionCollapsed) { domWindow->UpdateCommands(NS_LITERAL_STRING("select"), selection, aReason); mSelectionWasCollapsed = selectionCollapsed; } return NS_OK; } //nsDocViewerFocusListener NS_IMPL_ISUPPORTS(nsDocViewerFocusListener, nsIDOMEventListener) nsDocViewerFocusListener::nsDocViewerFocusListener() :mDocViewer(nullptr) { } nsDocViewerFocusListener::~nsDocViewerFocusListener(){} nsresult nsDocViewerFocusListener::HandleEvent(nsIDOMEvent* aEvent) { NS_ENSURE_STATE(mDocViewer); nsCOMPtr shell; mDocViewer->GetPresShell(getter_AddRefs(shell)); NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); nsCOMPtr selCon = do_QueryInterface(shell); int16_t selectionStatus; selCon->GetDisplaySelection(&selectionStatus); nsAutoString eventType; aEvent->GetType(eventType); if (eventType.EqualsLiteral("focus")) { // If selection was disabled, re-enable it. if(selectionStatus == nsISelectionController::SELECTION_DISABLED || selectionStatus == nsISelectionController::SELECTION_HIDDEN) { selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL); } } else { MOZ_ASSERT(eventType.EqualsLiteral("blur"), "Unexpected event type"); // If selection was on, disable it. if(selectionStatus == nsISelectionController::SELECTION_ON || selectionStatus == nsISelectionController::SELECTION_ATTENTION) { selCon->SetDisplaySelection(nsISelectionController::SELECTION_DISABLED); selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL); } } return NS_OK; } nsresult nsDocViewerFocusListener::Init(nsDocumentViewer *aDocViewer) { mDocViewer = aDocViewer; return NS_OK; } /** --------------------------------------------------- * From nsIWebBrowserPrint */ #ifdef NS_PRINTING NS_IMETHODIMP nsDocumentViewer::Print(nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener) { // Printing XUL documents is not supported. nsCOMPtr xulDoc(do_QueryInterface(mDocument)); if (xulDoc) { return NS_ERROR_FAILURE; } if (!mContainer) { PR_PL(("Container was destroyed yet we are still trying to use it!")); return NS_ERROR_FAILURE; } nsCOMPtr docShell(mContainer); NS_ENSURE_STATE(docShell); // Check to see if this document is still busy // If it is busy and we aren't already "queued" up to print then // Indicate there is a print pending and cache the args for later uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE; if ((NS_FAILED(docShell->GetBusyFlags(&busyFlags)) || (busyFlags != nsIDocShell::BUSY_FLAGS_NONE && busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING)) && !mPrintDocIsFullyLoaded) { if (!mPrintIsPending) { mCachedPrintSettings = aPrintSettings; mCachedPrintWebProgressListner = aWebProgressListener; mPrintIsPending = true; } PR_PL(("Printing Stopped - document is still busy!")); return NS_ERROR_GFX_PRINTER_DOC_IS_BUSY; } if (!mDocument || !mDeviceContext) { PR_PL(("Can't Print without a document and a device context")); return NS_ERROR_FAILURE; } nsresult rv; // if we are printing another URL, then exit // the reason we check here is because this method can be called while // another is still in here (the printing dialog is a good example). // the only time we can print more than one job at a time is the regression tests if (GetIsPrinting()) { // Let the user know we are not ready to print. rv = NS_ERROR_NOT_AVAILABLE; if (mPrintEngine) { mPrintEngine->FirePrintingErrorEvent(rv); } return rv; } // Dispatch 'beforeprint' event and ensure 'afterprint' will be dispatched: MOZ_ASSERT(!mAutoBeforeAndAfterPrint, "We don't want to dispatch nested beforeprint/afterprint"); nsAutoPtr autoBeforeAndAfterPrint( new AutoPrintEventDispatcher(mDocument)); NS_ENSURE_STATE(!GetIsPrinting()); // If we are hosting a full-page plugin, tell it to print // first. It shows its own native print UI. nsCOMPtr pDoc(do_QueryInterface(mDocument)); if (pDoc) return pDoc->Print(); if (!mPrintEngine) { NS_ENSURE_STATE(mDeviceContext); mPrintEngine = new nsPrintEngine(); rv = mPrintEngine->Initialize(this, mContainer, mDocument, float(mDeviceContext->AppUnitsPerCSSInch()) / float(mDeviceContext->AppUnitsPerDevPixel()) / mPageZoom, #ifdef DEBUG mDebugFile #else nullptr #endif ); if (NS_FAILED(rv)) { mPrintEngine->Destroy(); mPrintEngine = nullptr; return rv; } } if (mPrintEngine->HasPrintCallbackCanvas()) { // Postpone the 'afterprint' event until after the mozPrintCallback // callbacks have been called: mAutoBeforeAndAfterPrint = autoBeforeAndAfterPrint; } dom::Element* root = mDocument->GetRootElement(); if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint)) { mPrintEngine->SetDisallowSelectionPrint(true); } rv = mPrintEngine->Print(aPrintSettings, aWebProgressListener); if (NS_FAILED(rv)) { OnDonePrinting(); } return rv; } NS_IMETHODIMP nsDocumentViewer::PrintPreview(nsIPrintSettings* aPrintSettings, mozIDOMWindowProxy* aChildDOMWin, nsIWebProgressListener* aWebProgressListener) { #if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW) NS_WARNING_ASSERTION( IsInitializedForPrintPreview(), "Using docshell.printPreview is the preferred way for print previewing!"); NS_ENSURE_ARG_POINTER(aChildDOMWin); nsresult rv = NS_OK; if (GetIsPrinting()) { nsPrintEngine::CloseProgressDialog(aWebProgressListener); return NS_ERROR_FAILURE; } // Printing XUL documents is not supported. nsCOMPtr xulDoc(do_QueryInterface(mDocument)); if (xulDoc) { nsPrintEngine::CloseProgressDialog(aWebProgressListener); return NS_ERROR_FAILURE; } nsCOMPtr docShell(mContainer); if (!docShell || !mDeviceContext) { PR_PL(("Can't Print Preview without device context and docshell")); return NS_ERROR_FAILURE; } nsCOMPtr window = do_QueryInterface(aChildDOMWin); MOZ_ASSERT(window); nsCOMPtr doc = window->GetDoc(); NS_ENSURE_STATE(doc); // Dispatch 'beforeprint' event and ensure 'afterprint' will be dispatched: // XXX Currently[1] when the user switches between portrait and landscape // mode in print preview, we re-enter this function before // mAutoBeforeAndAfterPrint (if set) is cleared to dispatch the 'afterprint' // event. To avoid sending multiple 'beforeprint'/'afterprint' events we // must avoid creating a new AutoPrintEventDispatcher object here if we // already have one saved in mAutoBeforeAndAfterPrint. // [1] Until PDF.js is removed (though, maybe after that as well). nsAutoPtr autoBeforeAndAfterPrint; if (!mAutoBeforeAndAfterPrint) { autoBeforeAndAfterPrint = new AutoPrintEventDispatcher(doc); } NS_ENSURE_STATE(!GetIsPrinting()); // beforeprint event may have caused ContentViewer to be shutdown. NS_ENSURE_STATE(mContainer); NS_ENSURE_STATE(mDeviceContext); if (!mPrintEngine) { mPrintEngine = new nsPrintEngine(); rv = mPrintEngine->Initialize(this, mContainer, doc, float(mDeviceContext->AppUnitsPerCSSInch()) / float(mDeviceContext->AppUnitsPerDevPixel()) / mPageZoom, #ifdef DEBUG mDebugFile #else nullptr #endif ); if (NS_FAILED(rv)) { mPrintEngine->Destroy(); mPrintEngine = nullptr; return rv; } } if (autoBeforeAndAfterPrint && mPrintEngine->HasPrintCallbackCanvas()) { // Postpone the 'afterprint' event until after the mozPrintCallback // callbacks have been called: mAutoBeforeAndAfterPrint = autoBeforeAndAfterPrint; } dom::Element* root = doc->GetRootElement(); if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint)) { PR_PL(("PrintPreview: found mozdisallowselectionprint")); mPrintEngine->SetDisallowSelectionPrint(true); } rv = mPrintEngine->PrintPreview(aPrintSettings, aChildDOMWin, aWebProgressListener); mPrintPreviewZoomed = false; if (NS_FAILED(rv)) { OnDonePrinting(); } return rv; #else return NS_ERROR_FAILURE; #endif } //---------------------------------------------------------------------- NS_IMETHODIMP nsDocumentViewer::PrintPreviewNavigate(int16_t aType, int32_t aPageNum) { if (!GetIsPrintPreview() || mPrintEngine->GetIsCreatingPrintPreview()) return NS_ERROR_FAILURE; nsIScrollableFrame* sf = mPrintEngine->GetPrintPreviewPresShell()->GetRootScrollFrameAsScrollable(); if (!sf) return NS_OK; // Check to see if we can short circut scrolling to the top if (aType == nsIWebBrowserPrint::PRINTPREVIEW_HOME || (aType == nsIWebBrowserPrint::PRINTPREVIEW_GOTO_PAGENUM && aPageNum == 1)) { sf->ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT); return NS_OK; } // Finds the SimplePageSequencer frame // in PP mPrtPreview->mPrintObject->mSeqFrame is null nsIFrame* seqFrame = nullptr; int32_t pageCount = 0; if (NS_FAILED(mPrintEngine->GetSeqFrameAndCountPages(seqFrame, pageCount))) { return NS_ERROR_FAILURE; } // Figure where we are currently scrolled to nsPoint pt = sf->GetScrollPosition(); int32_t pageNum = 1; nsIFrame * fndPageFrame = nullptr; nsIFrame * currentPage = nullptr; // If it is "End" then just do a "goto" to the last page if (aType == nsIWebBrowserPrint::PRINTPREVIEW_END) { aType = nsIWebBrowserPrint::PRINTPREVIEW_GOTO_PAGENUM; aPageNum = pageCount; } // Now, locate the current page we are on and // and the page of the page number for (nsIFrame* pageFrame : seqFrame->PrincipalChildList()) { nsRect pageRect = pageFrame->GetRect(); if (pageRect.Contains(pageRect.x, pt.y)) { currentPage = pageFrame; } if (pageNum == aPageNum) { fndPageFrame = pageFrame; break; } pageNum++; } if (aType == nsIWebBrowserPrint::PRINTPREVIEW_PREV_PAGE) { if (currentPage) { fndPageFrame = currentPage->GetPrevInFlow(); if (!fndPageFrame) { return NS_OK; } } else { return NS_OK; } } else if (aType == nsIWebBrowserPrint::PRINTPREVIEW_NEXT_PAGE) { if (currentPage) { fndPageFrame = currentPage->GetNextInFlow(); if (!fndPageFrame) { return NS_OK; } } else { return NS_OK; } } else { // If we get here we are doing "GoTo" if (aPageNum < 0 || aPageNum > pageCount) { return NS_OK; } } if (fndPageFrame) { nscoord newYPosn = nscoord(mPrintEngine->GetPrintPreviewScale() * fndPageFrame->GetPosition().y); sf->ScrollTo(nsPoint(pt.x, newYPosn), nsIScrollableFrame::INSTANT); } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetGlobalPrintSettings(nsIPrintSettings * *aGlobalPrintSettings) { return nsPrintEngine::GetGlobalPrintSettings(aGlobalPrintSettings); } // XXX This always returns false for subdocuments NS_IMETHODIMP nsDocumentViewer::GetDoingPrint(bool *aDoingPrint) { NS_ENSURE_ARG_POINTER(aDoingPrint); *aDoingPrint = false; if (mPrintEngine) { // XXX shouldn't this be GetDoingPrint() ? return mPrintEngine->GetDoingPrintPreview(aDoingPrint); } return NS_OK; } // XXX This always returns false for subdocuments NS_IMETHODIMP nsDocumentViewer::GetDoingPrintPreview(bool *aDoingPrintPreview) { NS_ENSURE_ARG_POINTER(aDoingPrintPreview); *aDoingPrintPreview = false; if (mPrintEngine) { return mPrintEngine->GetDoingPrintPreview(aDoingPrintPreview); } return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetCurrentPrintSettings(nsIPrintSettings * *aCurrentPrintSettings) { NS_ENSURE_ARG_POINTER(aCurrentPrintSettings); *aCurrentPrintSettings = nullptr; NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE); return mPrintEngine->GetCurrentPrintSettings(aCurrentPrintSettings); } NS_IMETHODIMP nsDocumentViewer::GetCurrentChildDOMWindow(mozIDOMWindowProxy** aCurrentChildDOMWindow) { NS_ENSURE_ARG_POINTER(aCurrentChildDOMWindow); *aCurrentChildDOMWindow = nullptr; return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocumentViewer::Cancel() { NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE); return mPrintEngine->Cancelled(); } NS_IMETHODIMP nsDocumentViewer::ExitPrintPreview() { if (GetIsPrinting()) return NS_ERROR_FAILURE; NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE); if (GetIsPrintPreview()) { ReturnToGalleyPresentation(); } return NS_OK; } //---------------------------------------------------------------------------------- // Enumerate all the documents for their titles NS_IMETHODIMP nsDocumentViewer::EnumerateDocumentNames(uint32_t* aCount, char16_t*** aResult) { #ifdef NS_PRINTING NS_ENSURE_ARG(aCount); NS_ENSURE_ARG_POINTER(aResult); NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE); return mPrintEngine->EnumerateDocumentNames(aCount, aResult); #else return NS_ERROR_FAILURE; #endif } NS_IMETHODIMP nsDocumentViewer::GetIsFramesetFrameSelected(bool *aIsFramesetFrameSelected) { #ifdef NS_PRINTING *aIsFramesetFrameSelected = false; NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE); return mPrintEngine->GetIsFramesetFrameSelected(aIsFramesetFrameSelected); #else return NS_ERROR_FAILURE; #endif } NS_IMETHODIMP nsDocumentViewer::GetPrintPreviewNumPages(int32_t *aPrintPreviewNumPages) { #ifdef NS_PRINTING NS_ENSURE_ARG_POINTER(aPrintPreviewNumPages); NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE); return mPrintEngine->GetPrintPreviewNumPages(aPrintPreviewNumPages); #else return NS_ERROR_FAILURE; #endif } NS_IMETHODIMP nsDocumentViewer::GetIsFramesetDocument(bool *aIsFramesetDocument) { #ifdef NS_PRINTING *aIsFramesetDocument = false; NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE); return mPrintEngine->GetIsFramesetDocument(aIsFramesetDocument); #else return NS_ERROR_FAILURE; #endif } NS_IMETHODIMP nsDocumentViewer::GetIsIFrameSelected(bool *aIsIFrameSelected) { #ifdef NS_PRINTING *aIsIFrameSelected = false; NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE); return mPrintEngine->GetIsIFrameSelected(aIsIFrameSelected); #else return NS_ERROR_FAILURE; #endif } NS_IMETHODIMP nsDocumentViewer::GetIsRangeSelection(bool *aIsRangeSelection) { #ifdef NS_PRINTING *aIsRangeSelection = false; NS_ENSURE_TRUE(mPrintEngine, NS_ERROR_FAILURE); return mPrintEngine->GetIsRangeSelection(aIsRangeSelection); #else return NS_ERROR_FAILURE; #endif } //---------------------------------------------------------------------------------- // Printing/Print Preview Helpers //---------------------------------------------------------------------------------- //---------------------------------------------------------------------------------- // Walks the document tree and tells each DocShell whether Printing/PP is happening void nsDocumentViewer::SetIsPrintingInDocShellTree(nsIDocShellTreeItem* aParentNode, bool aIsPrintingOrPP, bool aStartAtTop) { nsCOMPtr parentItem(do_QueryInterface(aParentNode)); // find top of "same parent" tree if (aStartAtTop) { if (aIsPrintingOrPP) { while (parentItem) { nsCOMPtr parent; parentItem->GetSameTypeParent(getter_AddRefs(parent)); if (!parent) { break; } parentItem = do_QueryInterface(parent); } mTopContainerWhilePrinting = do_GetWeakReference(parentItem); } else { parentItem = do_QueryReferent(mTopContainerWhilePrinting); } } // Check to see if the DocShell's ContentViewer is printing/PP nsCOMPtr viewerContainer(do_QueryInterface(parentItem)); if (viewerContainer) { viewerContainer->SetIsPrinting(aIsPrintingOrPP); } if (!aParentNode) { return; } // Traverse children to see if any of them are printing. int32_t n; aParentNode->GetChildCount(&n); for (int32_t i=0; i < n; i++) { nsCOMPtr child; aParentNode->GetChildAt(i, getter_AddRefs(child)); NS_ASSERTION(child, "child isn't nsIDocShell"); if (child) { SetIsPrintingInDocShellTree(child, aIsPrintingOrPP, false); } } } #endif // NS_PRINTING bool nsDocumentViewer::ShouldAttachToTopLevel() { if (!mParentWidget) return false; nsCOMPtr containerItem(mContainer); if (!containerItem) return false; // We always attach when using puppet widgets if (nsIWidget::UsePuppetWidgets()) return true; #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_UIKIT) // On windows, in the parent process we also attach, but just to // chrome items nsWindowType winType = mParentWidget->WindowType(); if ((winType == eWindowType_toplevel || winType == eWindowType_dialog || winType == eWindowType_invisible) && containerItem->ItemType() == nsIDocShellTreeItem::typeChrome) { return true; } #endif return false; } //------------------------------------------------------------ // XXX this always returns false for subdocuments bool nsDocumentViewer::GetIsPrinting() { #ifdef NS_PRINTING if (mPrintEngine) { return mPrintEngine->GetIsPrinting(); } #endif return false; } //------------------------------------------------------------ // Notification from the PrintEngine of the current Printing status void nsDocumentViewer::SetIsPrinting(bool aIsPrinting) { #ifdef NS_PRINTING // Set all the docShells in the docshell tree to be printing. // that way if anyone of them tries to "navigate" it can't nsCOMPtr docShell(mContainer); if (docShell || !aIsPrinting) { SetIsPrintingInDocShellTree(docShell, aIsPrinting, true); } else { NS_WARNING("Did you close a window before printing?"); } if (!aIsPrinting) { // Dispatch the 'afterprint' event now, if pending: mAutoBeforeAndAfterPrint = nullptr; } #endif } //------------------------------------------------------------ // The PrintEngine holds the current value // this called from inside the DocViewer. // XXX it always returns false for subdocuments bool nsDocumentViewer::GetIsPrintPreview() { #ifdef NS_PRINTING if (mPrintEngine) { return mPrintEngine->GetIsPrintPreview(); } #endif return false; } //------------------------------------------------------------ // Notification from the PrintEngine of the current PP status void nsDocumentViewer::SetIsPrintPreview(bool aIsPrintPreview) { #ifdef NS_PRINTING // Set all the docShells in the docshell tree to be printing. // that way if anyone of them tries to "navigate" it can't nsCOMPtr docShell(mContainer); if (docShell || !aIsPrintPreview) { SetIsPrintingInDocShellTree(docShell, aIsPrintPreview, true); } if (!aIsPrintPreview) { // Dispatch the 'afterprint' event now, if pending: mAutoBeforeAndAfterPrint = nullptr; } #endif // Protect against pres shell destruction running scripts. nsAutoScriptBlocker scriptBlocker; if (!aIsPrintPreview) { if (mPresShell) { DestroyPresShell(); } mWindow = nullptr; mViewManager = nullptr; mPresContext = nullptr; mPresShell = nullptr; } } //---------------------------------------------------------------------------------- // nsIDocumentViewerPrint IFace //---------------------------------------------------------------------------------- //------------------------------------------------------------ void nsDocumentViewer::IncrementDestroyRefCount() { ++mDestroyRefCount; } void nsDocumentViewer::DecrementDestroyRefCount() { --mDestroyRefCount; } //------------------------------------------------------------ #if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW) //------------------------------------------------------------ // Reset ESM focus for all descendent doc shells. static void ResetFocusState(nsIDocShell* aDocShell) { nsIFocusManager* fm = nsFocusManager::GetFocusManager(); if (!fm) return; nsCOMPtr docShellEnumerator; aDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, nsIDocShell::ENUMERATE_FORWARDS, getter_AddRefs(docShellEnumerator)); nsCOMPtr currentContainer; bool hasMoreDocShells; while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) && hasMoreDocShells) { docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); nsCOMPtr win = do_GetInterface(currentContainer); if (win) fm->ClearFocus(win); } } #endif // NS_PRINTING && NS_PRINT_PREVIEW void nsDocumentViewer::ReturnToGalleyPresentation() { #if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW) if (!GetIsPrintPreview()) { NS_ERROR("Wow, we should never get here!"); return; } SetIsPrintPreview(false); mPrintEngine->TurnScriptingOn(true); mPrintEngine->Destroy(); mPrintEngine = nullptr; nsCOMPtr docShell(mContainer); ResetFocusState(docShell); SetTextZoom(mTextZoom); SetFullZoom(mPageZoom); SetOverrideDPPX(mOverrideDPPX); SetMinFontSize(mMinFontSize); Show(); #endif // NS_PRINTING && NS_PRINT_PREVIEW } //------------------------------------------------------------ // This called ONLY when printing has completed and the DV // is being notified that it should get rid of the PrintEngine. // // BUT, if we are in Print Preview then we want to ignore the // notification (we do not get rid of the PrintEngine) // // One small caveat: // This IS called from two places in this module for cleaning // up when an error occurred during the start up printing // and print preview // void nsDocumentViewer::OnDonePrinting() { #if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW) if (mPrintEngine) { RefPtr pe = mPrintEngine; if (GetIsPrintPreview()) { pe->DestroyPrintingData(); } else { mPrintEngine = nullptr; pe->Destroy(); } // We are done printing, now cleanup if (mDeferredWindowClose) { mDeferredWindowClose = false; if (mContainer) { if (nsCOMPtr win = do_QueryInterface(mContainer->GetWindow())) { win->Close(); } } } else if (mClosingWhilePrinting) { if (mDocument) { mDocument->Destroy(); mDocument = nullptr; } mClosingWhilePrinting = false; } } #endif // NS_PRINTING && NS_PRINT_PREVIEW } NS_IMETHODIMP nsDocumentViewer::SetPageMode(bool aPageMode, nsIPrintSettings* aPrintSettings) { // XXX Page mode is only partially working; it's currently used for // reftests that require a paginated context mIsPageMode = aPageMode; // The DestroyPresShell call requires a script blocker, since the // PresShell::Destroy call it does can cause scripts to run, which could // re-entrantly call methods on the nsDocumentViewer. nsAutoScriptBlocker scriptBlocker; if (mPresShell) { DestroyPresShell(); } if (mPresContext) { DestroyPresContext(); } mViewManager = nullptr; mWindow = nullptr; NS_ENSURE_STATE(mDocument); if (aPageMode) { mPresContext = CreatePresContext(mDocument, nsPresContext::eContext_PageLayout, FindContainerView()); NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY); mPresContext->SetPaginatedScrolling(true); mPresContext->SetPrintSettings(aPrintSettings); nsresult rv = mPresContext->Init(mDeviceContext); NS_ENSURE_SUCCESS(rv, rv); } NS_ENSURE_SUCCESS(InitInternal(mParentWidget, nullptr, mBounds, true, false), NS_ERROR_FAILURE); Show(); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetHistoryEntry(nsISHEntry **aHistoryEntry) { NS_IF_ADDREF(*aHistoryEntry = mSHEntry); return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetIsTabModalPromptAllowed(bool *aAllowed) { *aAllowed = !mHidden; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::GetIsHidden(bool *aHidden) { *aHidden = mHidden; return NS_OK; } NS_IMETHODIMP nsDocumentViewer::SetIsHidden(bool aHidden) { mHidden = aHidden; return NS_OK; } void nsDocumentViewer::DestroyPresShell() { // We assert this because destroying the pres shell could otherwise cause // re-entrancy into nsDocumentViewer methods, and all callers of // DestroyPresShell need to do other cleanup work afterwards before it // is safe for those re-entrant method calls to be made. MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "DestroyPresShell must only be called when scripts are blocked"); // Break circular reference (or something) mPresShell->EndObservingDocument(); RefPtr selection = GetDocumentSelection(); if (selection && mSelectionListener) selection->RemoveSelectionListener(mSelectionListener); mPresShell->Destroy(); mPresShell = nullptr; } void nsDocumentViewer::DestroyPresContext() { mPresContext->Detach(); mPresContext = nullptr; } bool nsDocumentViewer::IsInitializedForPrintPreview() { return mInitializedForPrintPreview; } void nsDocumentViewer::InitializeForPrintPreview() { mInitializedForPrintPreview = true; } void nsDocumentViewer::SetPrintPreviewPresentation(nsViewManager* aViewManager, nsPresContext* aPresContext, nsIPresShell* aPresShell) { // Protect against pres shell destruction running scripts and re-entrantly // creating a new presentation. nsAutoScriptBlocker scriptBlocker; if (mPresShell) { DestroyPresShell(); } mWindow = nullptr; mViewManager = aViewManager; mPresContext = aPresContext; mPresShell = aPresShell; if (ShouldAttachToTopLevel()) { DetachFromTopLevelWidget(); nsView* rootView = mViewManager->GetRootView(); rootView->AttachToTopLevelWidget(mParentWidget); mAttachedToParent = true; } } // Fires the "document-shown" event so that interested parties are aware of it. NS_IMETHODIMP nsDocumentShownDispatcher::Run() { nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->NotifyObservers(mDocument, "document-shown", nullptr); } return NS_OK; }