fune/layout/base/nsDocumentViewer.cpp
Emilio Cobos Álvarez a59f4ed1fb Bug 1528914 - Tidy the relationship between the doc shell and the pres context. r=qdot
Make it always forward to the document's docshell. We rely on it being setup by
the time our stuff runs, and we cannot have multiple pres contexts per document
in different docshells anymore.

This allows me to also move some state to the document (about whether it's
currently loaded in a chrome docshell (nsPresContext::mIsChrome) and whether
it's a chrome origin image (nsPresContext::mIsChromeOriginImage), which will
help for bug 1490401 / bug 1418159.

The pres context already relies on having the docshell available on `Init` and
we don't properly handle dynamic changes to it.

The reason I store some state like whether the doc URI is chrome:// and whether
we're in a chrome docshell is not (only) to avoid recomputing it over and over,
but also to allow me to read them from Stylo (main-thread blocked, but poke at
that from multiple non-main-threads).

Differential Revision: https://phabricator.services.mozilla.com/D20301

--HG--
extra : moz-landing-system : lando
2019-02-19 22:53:40 +00:00

4299 lines
132 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "gfxContext.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoStyleSet.h"
#include "nscore.h"
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsIContent.h"
#include "nsIContentViewer.h"
#include "nsIDocumentViewerPrint.h"
#include "mozilla/dom/BeforeUnloadEvent.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/Document.h"
#include "nsPresContext.h"
#include "nsIPresShell.h"
#include "nsIFrame.h"
#include "nsIWritablePropertyBag2.h"
#include "nsSubDocumentFrame.h"
#include "nsGenericHTMLElement.h"
#include "nsStubMutationObserver.h"
#include "nsILinkHandler.h"
#include "nsISelectionListener.h"
#include "mozilla/dom/Selection.h"
#include "nsContentUtils.h"
#include "nsLayoutStylesheetCache.h"
#ifdef ACCESSIBILITY
# include "mozilla/a11y/DocAccessible.h"
#endif
#include "mozilla/BasicEvents.h"
#include "mozilla/Encoding.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Preferences.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 "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"
#ifdef MOZ_XUL
# include "nsXULPopupManager.h"
#endif
#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 "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 "nsPrintJob.h"
// Print Options
# include "nsIPrintSettings.h"
# include "nsIPrintSettingsService.h"
# include "nsISimpleEnumerator.h"
# include "nsIPluginDocument.h"
#endif // NS_PRINTING
// focus
#include "nsIDOMEventListener.h"
#include "nsISelectionController.h"
#include "mozilla/EventDispatcher.h"
#include "nsISHEntry.h"
#include "nsISHistory.h"
#include "nsIWebNavigation.h"
#include "mozilla/dom/XMLHttpRequestMainThread.h"
// paint forcing
#include <stdio.h>
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/ScriptLoader.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 final : 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 final : 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;
};
namespace viewer_detail {
/**
* Mutation observer for use until we hand ourselves over to our SHEntry.
*/
class BFCachePreventionObserver final : public nsStubMutationObserver {
public:
explicit BFCachePreventionObserver(Document* aDocument)
: mDocument(aDocument) {}
NS_DECL_ISUPPORTS
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
// Stop observing the document.
void Disconnect();
private:
~BFCachePreventionObserver() = default;
// Helper for the work that needs to happen when mutations happen.
void MutationHappened();
Document* mDocument; // Weak; we get notified if it dies
};
NS_IMPL_ISUPPORTS(BFCachePreventionObserver, nsIMutationObserver)
void BFCachePreventionObserver::CharacterDataChanged(
nsIContent* aContent, const CharacterDataChangeInfo&) {
MutationHappened();
}
void BFCachePreventionObserver::AttributeChanged(Element* aElement,
int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue) {
MutationHappened();
}
void BFCachePreventionObserver::ContentAppended(nsIContent* aFirstNewContent) {
MutationHappened();
}
void BFCachePreventionObserver::ContentInserted(nsIContent* aChild) {
MutationHappened();
}
void BFCachePreventionObserver::ContentRemoved(nsIContent* aChild,
nsIContent* aPreviousSibling) {
MutationHappened();
}
void BFCachePreventionObserver::NodeWillBeDestroyed(const nsINode* aNode) {
mDocument = nullptr;
}
void BFCachePreventionObserver::Disconnect() {
if (mDocument) {
mDocument->RemoveMutationObserver(this);
// It will no longer tell us when it goes away, so make sure we're
// not holding a dangling ref.
mDocument = nullptr;
}
}
void BFCachePreventionObserver::MutationHappened() {
MOZ_ASSERT(
mDocument,
"How can we not have a document but be getting notified for mutations?");
mDocument->DisallowBFCaching();
Disconnect();
}
} // namespace viewer_detail
using viewer_detail::BFCachePreventionObserver;
//-------------------------------------------------------------
class nsDocumentViewer final : public nsIContentViewer,
public nsIContentViewerEdit,
public nsIDocumentViewerPrint
#ifdef NS_PRINTING
,
public nsIWebBrowserPrint
#endif
{
friend class nsDocViewerSelectionListener;
friend class nsPagePrintTimer;
friend class nsPrintJob;
public:
nsDocumentViewer();
// nsISupports interface...
NS_DECL_ISUPPORTS
// nsIContentViewer interface...
NS_DECL_NSICONTENTVIEWER
// nsIContentViewerEdit
NS_DECL_NSICONTENTVIEWEREDIT
#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);
already_AddRefed<nsINode> GetPopupNode();
already_AddRefed<nsINode> GetPopupLinkNode();
already_AddRefed<nsIImageLoadingContent> GetPopupImageNode();
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
void ReturnToGalleyPresentation();
// 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:
// Returns the current viewmanager. Might be null.
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<nsDocShell> mContainer; // it owns me!
nsWeakPtr mTopContainerWhilePrinting;
RefPtr<nsDeviceContext> 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<Document> mDocument;
nsCOMPtr<nsIWidget> mWindow; // may be null
RefPtr<nsViewManager> mViewManager;
RefPtr<nsPresContext> mPresContext;
nsCOMPtr<nsIPresShell> mPresShell;
RefPtr<nsDocViewerSelectionListener> mSelectionListener;
RefPtr<nsDocViewerFocusListener> mFocusListener;
nsCOMPtr<nsIContentViewer> mPreviousViewer;
nsCOMPtr<nsISHEntry> mSHEntry;
// Observer that will prevent bfcaching if it gets notified. This
// is non-null precisely when mSHEntry is non-null.
RefPtr<BFCachePreventionObserver> mBFCachePreventionObserver;
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
int16_t mNumURLStarts;
int16_t mDestroyBlockedCount;
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<nsIPrintSettings> mCachedPrintSettings;
nsCOMPtr<nsIWebProgressListener> mCachedPrintWebProgressListner;
RefPtr<nsPrintJob> mPrintJob;
float mOriginalPrintPreviewScale;
float mPrintPreviewZoom;
UniquePtr<AutoPrintEventDispatcher> mAutoBeforeAndAfterPrint;
# endif // NS_PRINT_PREVIEW
#endif // NS_PRINTING
/* character set member data */
int32_t mHintCharsetSource;
const Encoding* mHintCharset;
const Encoding* 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(Document* aTop) : mTop(aTop) {
DispatchEventToWindowTree(NS_LITERAL_STRING("beforeprint"));
}
~AutoPrintEventDispatcher() {
DispatchEventToWindowTree(NS_LITERAL_STRING("afterprint"));
}
private:
void DispatchEventToWindowTree(const nsAString& aEvent) {
nsTArray<nsCOMPtr<Document>> targets;
CollectDocuments(mTop, &targets);
for (nsCOMPtr<Document>& doc : targets) {
nsContentUtils::DispatchTrustedEvent(doc, doc->GetWindow(), aEvent,
CanBubble::eNo, Cancelable::eNo,
nullptr);
}
}
static bool CollectDocuments(Document* aDocument, void* aData) {
if (aDocument) {
static_cast<nsTArray<nsCOMPtr<Document>>*>(aData)->AppendElement(
aDocument);
aDocument->EnumerateSubDocuments(CollectDocuments, aData);
}
return true;
}
nsCOMPtr<Document> mTop;
};
} // namespace mozilla
class nsDocumentShownDispatcher : public Runnable {
public:
explicit nsDocumentShownDispatcher(nsCOMPtr<Document> aDocument)
: Runnable("nsDocumentShownDispatcher"), mDocument(aDocument) {}
NS_IMETHOD Run() override;
private:
nsCOMPtr<Document> mDocument;
};
//------------------------------------------------------------------
// nsDocumentViewer
//------------------------------------------------------------------
//------------------------------------------------------------------
already_AddRefed<nsIContentViewer> NS_NewContentViewer() {
RefPtr<nsDocumentViewer> 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 (mPrintJob) {
mPrintJob->Destroy();
mPrintJob = nullptr;
# ifdef NS_PRINT_PREVIEW
SetIsPrintPreview(false);
# endif
}
#endif // NS_PRINTING
}
nsDocumentViewer::nsDocumentViewer()
: mParentWidget(nullptr),
mAttachedToParent(false),
mTextZoom(1.0),
mPageZoom(1.0),
mOverrideDPPX(0.0),
mNumURLStarts(0),
mDestroyBlockedCount(0),
mStopped(false),
mLoaded(false),
mDeferredWindowClose(false),
mIsSticky(true),
mInPermitUnload(false),
mInPermitUnloadPrompt(false),
#ifdef NS_PRINTING
mClosingWhilePrinting(false),
# if NS_PRINT_PREVIEW
mPrintPreviewZoomed(false),
mPrintIsPending(false),
mPrintDocIsFullyLoaded(false),
mOriginalPrintPreviewScale(0.0),
mPrintPreviewZoom(1.0),
# endif // NS_PRINT_PREVIEW
#endif // NS_PRINTING
mHintCharsetSource(kCharsetUninitialized),
mHintCharset(nullptr),
mForceCharacterSet(nullptr),
mIsPageMode(false),
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(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();
}
if (mPrintJob) {
mPrintJob->Destroy();
mPrintJob = nullptr;
}
MOZ_RELEASE_ASSERT(mDestroyBlockedCount == 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(Document* aDocument) {
MOZ_ASSERT(aDocument);
if (!mDocument) {
mDocument = aDocument;
}
}
nsresult nsDocumentViewer::SyncParentSubDocMap() {
nsCOMPtr<nsIDocShell> docShell(mContainer);
if (!docShell) {
return NS_OK;
}
nsCOMPtr<nsPIDOMWindowOuter> pwin(docShell->GetWindow());
if (!mDocument || !pwin) {
return NS_OK;
}
nsCOMPtr<Element> element = pwin->GetFrameElementInternal();
if (!element) {
return NS_OK;
}
nsCOMPtr<nsIDocShellTreeItem> parent;
docShell->GetParent(getter_AddRefs(parent));
nsCOMPtr<nsPIDOMWindowOuter> parent_win =
parent ? parent->GetWindow() : nullptr;
if (!parent_win) {
return NS_OK;
}
nsCOMPtr<Document> parent_doc = parent_win->GetDoc();
if (!parent_doc) {
return NS_OK;
}
if (mDocument && parent_doc->GetSubDocumentFor(element) != mDocument &&
parent_doc->EventHandlingSuppressed()) {
mDocument->SuppressEventHandling(parent_doc->EventHandlingSuppressed());
}
return parent_doc->SetSubDocumentFor(element, mDocument);
}
NS_IMETHODIMP
nsDocumentViewer::SetContainer(nsIDocShell* aContainer) {
mContainer = static_cast<nsDocShell*>(aContainer);
// 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<nsIDocShell> 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...
UniquePtr<ServoStyleSet> styleSet = CreateStyleSet(mDocument);
// Now make the shell for the document
mPresShell =
mDocument->CreateShell(mPresContext, mViewManager, std::move(styleSet));
if (!mPresShell) {
return NS_ERROR_FAILURE;
}
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(FlushType::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->SetVisibleArea(nsRect(0, 0, width, height));
mPresContext->SetTextZoom(mTextZoom);
mPresContext->SetFullZoom(mPageZoom);
mPresContext->SetOverrideDPPX(mOverrideDPPX);
}
p2a = mPresContext->AppUnitsPerDevPixel(); // zoom may have changed it
if (aDoInitialReflow) {
nsCOMPtr<nsIPresShell> shell = mPresShell;
// Initial reflow
shell->Initialize();
}
// 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<mozilla::dom::Selection> selection = GetDocumentSelection();
if (!selection) {
return NS_ERROR_FAILURE;
}
selection->AddSelectionListener(mSelectionListener);
// Save old listener so we can unregister it
RefPtr<nsDocViewerFocusListener> 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(Document* 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<nsIInterfaceRequestor> requestor(mContainer);
if (requestor) {
if (mPresContext) {
nsCOMPtr<nsILinkHandler> linkHandler;
requestor->GetInterface(NS_GET_IID(nsILinkHandler),
getter_AddRefs(linkHandler));
mPresContext->SetLinkHandler(linkHandler);
}
// Set script-context-owner in the document
nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(requestor);
if (window) {
nsCOMPtr<Document> curDoc = window->GetExtantDoc();
if (aForceSetNewDocument || curDoc != mDocument) {
rv = window->SetNewDocument(mDocument, aState, false);
if (NS_FAILED(rv)) {
Destroy();
return rv;
}
}
}
}
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<nsDocumentViewer> 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<nsIPresShell> shell = mPresShell;
shell->FlushPendingNotifications(FlushType::Layout);
}
nsresult rv = NS_OK;
NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
// First, get the window from the document...
nsCOMPtr<nsPIDOMWindowOuter> 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)) {
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() == Document::READYSTATE_COMPLETE);
if (!restoring) {
NS_ASSERTION(mDocument->IsXULDocument() || // readyState for XUL is bogus
mDocument->GetReadyStateEnum() ==
Document::READYSTATE_INTERACTIVE ||
// test_stricttransportsecurity.html has old-style
// docshell-generated about:blank docs reach this code!
(mDocument->GetReadyStateEnum() ==
Document::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<Document> d = mDocument;
mDocument->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
RefPtr<nsDOMNavigationTiming> timing(d->GetNavigationTiming());
if (timing) {
timing->NotifyLoadEventStart();
}
// Dispatch observer notification to notify observers document load is
// complete.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
nsIPrincipal* principal = d->NodePrincipal();
os->NotifyObservers(ToSupports(d),
nsContentUtils::IsSystemPrincipal(principal)
? "chrome-document-loaded"
: "content-document-loaded",
nullptr);
}
// Notify any devtools about the load.
RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
if (timelines && timelines->HasConsumer(docShell)) {
timelines->AddMarkerForDocShell(
docShell, MakeUnique<DocLoadingTimelineMarker>("document::Load"));
}
EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
if (timing) {
timing->NotifyLoadEventEnd();
}
nsPIDOMWindowInner* innerWindow = window->GetCurrentInnerWindow();
if (innerWindow) {
innerWindow->QueuePerformanceNavigationTiming();
}
}
} 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<nsIPresShell> shell(mPresShell);
shell->UnsuppressPainting();
// mPresShell could have been removed now, see bug 378682/421432
if (mPresShell) {
mPresShell->LoadComplete();
}
}
}
// Release the JS bytecode cache from its wait on the load event, and
// potentially dispatch the encoding of the bytecode.
if (mDocument && mDocument->ScriptLoader()) {
mDocument->ScriptLoader()->LoadEventFired();
}
// It's probably a good idea to GC soon since we have finished loading.
nsJSContext::PokeGC(
JS::GCReason::LOAD_END,
mDocument ? mDocument->GetWrapperPreserveColor() : nullptr);
#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;
}
bool nsDocumentViewer::GetLoadCompleted() { return mLoaded; }
bool nsDocumentViewer::GetIsStopped() { return mStopped; }
NS_IMETHODIMP
nsDocumentViewer::PermitUnload(uint32_t aPermitUnloadFlags,
bool* aPermitUnload) {
return PermitUnloadInternal(&aPermitUnloadFlags, aPermitUnload);
}
nsresult nsDocumentViewer::PermitUnloadInternal(uint32_t* aPermitUnloadFlags,
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");
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#prompt-to-unload-a-document
// Create an RAII object on mDocument that will increment the
// should-ignore-opens-during-unload counter on initialization
// and decrement it again when it goes out of score (regardless
// of how we exit this function).
IgnoreOpensDuringUnload ignoreOpens(mDocument);
// Now, fire an BeforeUnload event to the document and see if it's ok
// to unload...
nsPresContext* presContext = mDocument->GetPresContext();
RefPtr<BeforeUnloadEvent> event =
new BeforeUnloadEvent(mDocument, presContext, nullptr);
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<nsDocumentViewer> kungFuDeathGrip(this);
bool dialogsAreEnabled = false;
{
// Never permit popups from the beforeunload handler, no matter
// how we get here.
nsAutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
// Never permit dialogs from the beforeunload handler
nsGlobalWindowOuter* globalWindow = nsGlobalWindowOuter::Cast(window);
dialogsAreEnabled = globalWindow->AreDialogsEnabled();
nsGlobalWindowOuter::TemporarilyDisableDialogs disableDialogs(globalWindow);
Document::PageUnloadingEventTimeStamp timestamp(mDocument);
mInPermitUnload = true;
EventDispatcher::DispatchDOMEvent(window, nullptr, event, mPresContext,
nullptr);
mInPermitUnload = false;
}
nsCOMPtr<nsIDocShell> docShell(mContainer);
nsAutoString text;
event->GetReturnValue(text);
if (sIsBeforeUnloadDisabled) {
*aPermitUnloadFlags = eDontPromptAndUnload;
}
// NB: we nullcheck mDocument because it might now be dead as a result of
// the event being dispatched.
if (*aPermitUnloadFlags != eDontPromptAndUnload && dialogsAreEnabled &&
mDocument && !(mDocument->GetSandboxFlags() & SANDBOXED_MODALS) &&
(!sBeforeUnloadRequiresInteraction || mDocument->UserHasInteracted()) &&
(event->WidgetEventPtr()->DefaultPrevented() || !text.IsEmpty())) {
// If the consumer wants prompt requests to just stop unloading, we don't
// need to prompt and can return immediately.
if (*aPermitUnloadFlags == eDontPromptAndDontUnload) {
*aPermitUnload = false;
return NS_OK;
}
// Ask the user if it's ok to unload the current page
nsCOMPtr<nsIPrompt> prompt = do_GetInterface(docShell);
if (prompt) {
nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt);
if (promptBag) {
bool isTabModalPromptAllowed;
GetIsTabModalPromptAllowed(&isTabModalPromptAllowed);
promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"),
isTabModalPromptAllowed);
}
nsAutoString 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)) {
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;
mozilla::Telemetry::Accumulate(
mozilla::Telemetry::ONBEFOREUNLOAD_PROMPT_COUNT, 1);
rv = prompt->ConfirmEx(title.get(), message.get(), buttonFlags,
leaveLabel.get(), stayLabel.get(), 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)) {
mozilla::Telemetry::Accumulate(
mozilla::Telemetry::ONBEFOREUNLOAD_PROMPT_ACTION, 2);
*aPermitUnload = false;
return NS_OK;
}
// Button 0 == leave, button 1 == stay
*aPermitUnload = (buttonPressed == 0);
mozilla::Telemetry::Accumulate(
mozilla::Telemetry::ONBEFOREUNLOAD_PROMPT_ACTION,
(*aPermitUnload ? 1 : 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) {
*aPermitUnloadFlags = eDontPromptAndUnload;
}
}
}
if (docShell) {
int32_t childCount;
docShell->GetChildCount(&childCount);
for (int32_t i = 0; i < childCount && *aPermitUnload; ++i) {
nsCOMPtr<nsIDocShellTreeItem> item;
docShell->GetChildAt(i, getter_AddRefs(item));
nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(item));
if (docShell) {
nsCOMPtr<nsIContentViewer> cv;
docShell->GetContentViewer(getter_AddRefs(cv));
if (cv) {
cv->PermitUnloadInternal(aPermitUnloadFlags, 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;
}
if (aIsUnload) {
// Poke the GC. The window might be collectable garbage now.
nsJSContext::PokeGC(JS::GCReason::PAGE_HIDE,
mDocument->GetWrapperPreserveColor(), NS_GC_DELAY * 2);
}
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) {
// 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;
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#unload-a-document
// Create an RAII object on mDocument that will increment the
// should-ignore-opens-during-unload counter on initialization
// and decrement it again when it goes out of scope.
IgnoreOpensDuringUnload ignoreOpens(mDocument);
// 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(PopupBlocker::openAbused, true);
Document::PageUnloadingEventTimeStamp timestamp(mDocument);
EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
}
#ifdef MOZ_XUL
// look for open menupopups and close them after the unload event, in case
// the unload event listeners open any new popups
nsContentUtils::HidePopupsInDocument(mDocument);
#endif
return NS_OK;
}
static void AttachContainerRecurse(nsIDocShell* aShell) {
nsCOMPtr<nsIContentViewer> viewer;
aShell->GetContentViewer(getter_AddRefs(viewer));
if (viewer) {
viewer->SetIsHidden(false);
Document* doc = viewer->GetDocument();
if (doc) {
doc->SetContainer(static_cast<nsDocShell*>(aShell));
}
RefPtr<nsPresContext> pc = viewer->GetPresContext();
if (pc) {
nsCOMPtr<nsILinkHandler> handler = do_QueryInterface(aShell);
pc->SetLinkHandler(handler);
}
nsCOMPtr<nsIPresShell> presShell = viewer->GetPresShell();
if (presShell) {
presShell->SetForwardingContainer(WeakPtr<nsDocShell>());
}
}
// Now recurse through the children
int32_t childCount;
aShell->GetChildCount(&childCount);
for (int32_t i = 0; i < childCount; ++i) {
nsCOMPtr<nsIDocShellTreeItem> childItem;
aShell->GetChildAt(i, getter_AddRefs(childItem));
nsCOMPtr<nsIDocShell> 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<nsDocShell>());
// Rehook the child presentations. The child shells are still in
// session history, so get them from there.
if (aSHEntry) {
nsCOMPtr<nsIDocShellTreeItem> item;
int32_t itemIndex = 0;
while (NS_SUCCEEDED(
aSHEntry->ChildShellAt(itemIndex++, getter_AddRefs(item))) &&
item) {
nsCOMPtr<nsIDocShell> shell = do_QueryInterface(item);
AttachContainerRecurse(shell);
}
}
SyncParentSubDocMap();
if (mFocusListener && mDocument) {
// The focus listener may have been disconnected.
mFocusListener->Init(this);
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 (mSHEntry) {
if (mBFCachePreventionObserver) {
mBFCachePreventionObserver->Disconnect();
}
mBFCachePreventionObserver = new BFCachePreventionObserver(mDocument);
mDocument->AddMutationObserver(mBFCachePreventionObserver);
}
#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW)
// Turn scripting back on
// after PrintPreview had turned it off
if (GetIsPrintPreview() && mPrintJob) {
mPrintJob->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 (mPrintJob && !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<nsIContentViewer> viewer;
aShell->GetContentViewer(getter_AddRefs(viewer));
if (viewer) {
Document* doc = viewer->GetDocument();
if (doc) {
doc->SetContainer(nullptr);
}
RefPtr<nsPresContext> pc = viewer->GetPresContext();
if (pc) {
pc->Detach();
}
nsCOMPtr<nsIPresShell> presShell = viewer->GetPresShell();
if (presShell) {
auto weakShell = static_cast<nsDocShell*>(aShell);
presShell->SetForwardingContainer(weakShell);
}
}
// Now recurse through the children
int32_t childCount;
aShell->GetChildCount(&childCount);
for (int32_t i = 0; i < childCount; ++i) {
nsCOMPtr<nsIDocShellTreeItem> childItem;
aShell->GetChildAt(i, getter_AddRefs(childItem));
nsCOMPtr<nsIDocShell> shell = do_QueryInterface(childItem);
DetachContainerRecurse(shell);
}
}
NS_IMETHODIMP
nsDocumentViewer::Destroy() {
NS_ASSERTION(mDocument, "No document in Destroy()!");
// 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 (mDestroyBlockedCount != 0) {
return NS_OK;
}
#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 (mPrintJob) {
if (mPrintJob->CheckBeforeDestroy()) {
return NS_OK;
}
}
// Dispatch the 'afterprint' event now, if pending:
mAutoBeforeAndAfterPrint = nullptr;
#endif
// We want to make sure to disconnect mBFCachePreventionObserver before we
// Sanitize() below.
if (mBFCachePreventionObserver) {
mBFCachePreventionObserver->Disconnect();
mBFCachePreventionObserver = nullptr;
}
if (mSHEntry && mDocument && !mDocument->IsBFCachingAllowed()) {
// Just drop the SHEntry now and pretend like we never even tried to bfcache
// this viewer. This should only happen when someone calls
// DisallowBFCaching() after CanSavePresentation() already ran. Ensure that
// the SHEntry has no viewer and its state is synced up. We want to do this
// via a stack reference, in case those calls mess with our members.
nsCOMPtr<nsISHEntry> shEntry = mSHEntry.forget();
shEntry->SetContentViewer(nullptr);
shEntry->SyncPresentationState();
}
// 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;
// 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<nsISHEntry> shEntry = mSHEntry.forget(); // we'll need this below
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<nsIDocShellTreeItem> item;
int32_t itemIndex = 0;
while (NS_SUCCEEDED(
shEntry->ChildShellAt(itemIndex++, getter_AddRefs(item))) &&
item) {
nsCOMPtr<nsIDocShell> 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 (mPrintJob) {
RefPtr<nsPrintJob> printJob = std::move(mPrintJob);
# ifdef NS_PRINT_PREVIEW
bool doingPrintPreview;
printJob->GetDoingPrintPreview(&doingPrintPreview);
if (doingPrintPreview) {
printJob->FinishPrintPreview();
}
# endif
printJob->Destroy();
MOZ_ASSERT(!mPrintJob,
"mPrintJob 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<nsDocShell>();
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<nsIPresShell> shell(mPresShell); // bug 378682
shell->UnsuppressPainting();
}
return NS_OK;
}
NS_IMETHODIMP
nsDocumentViewer::GetDOMDocument(Document** aResult) {
NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<Document> document = mDocument;
document.forget(aResult);
return NS_OK;
}
Document* nsDocumentViewer::GetDocument() { return mDocument; }
nsresult nsDocumentViewer::SetDocument(Document* 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;
return SetDocumentInternal(aDocument, false);
}
NS_IMETHODIMP
nsDocumentViewer::SetDocumentInternal(Document* 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<nsIDocShell> node(mContainer);
if (node) {
int32_t count;
node->GetChildCount(&count);
for (int32_t i = 0; i < count; ++i) {
nsCOMPtr<nsIDocShellTreeItem> 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<nsPIDOMWindowOuter> 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::GetBounds(nsIntRect& aResult) {
NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
aResult = mBounds;
return NS_OK;
}
nsIContentViewer* nsDocumentViewer::GetPreviousViewer() {
return mPreviousViewer;
}
void 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.
//
// Make sure we hold a strong ref to prevViewer here, since we'll
// tell aViewer to drop it.
nsCOMPtr<nsIContentViewer> prevViewer = aViewer->GetPreviousViewer();
if (prevViewer) {
aViewer->SetPreviousViewer(nullptr);
aViewer->Destroy();
return SetPreviousViewer(prevViewer);
}
}
mPreviousViewer = aViewer;
}
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<nsIContentViewer> 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<nsIContentViewer> prevViewer(mPreviousViewer);
mPreviousViewer = nullptr;
prevViewer->Destroy();
// Make sure we don't have too many cached ContentViewers
nsCOMPtr<nsIDocShellTreeItem> 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<nsIDocShellTreeItem> root;
treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
RefPtr<ChildSHistory> history = webNav->GetSessionHistory();
if (history) {
int32_t prevIndex, loadedIndex;
nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(treeItem);
docShell->GetPreviousEntryIndex(&prevIndex);
docShell->GetLoadedEntryIndex(&loadedIndex);
#ifdef DEBUG_PAGE_CACHE
printf("About to evict content viewers: prev=%d, loaded=%d\n",
prevIndex, loadedIndex);
#endif
history->LegacySHistory()->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> 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<nsIBaseWindow> base_win(mContainer);
if (base_win) {
base_win->GetParentWidget(&mParentWidget);
if (mParentWidget) {
// GetParentWidget AddRefs, but mParentWidget is weak
mParentWidget->Release();
}
}
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<nsILinkHandler> linkHandler(do_GetInterface(base_win));
if (linkHandler) {
mPresContext->SetLinkHandler(linkHandler);
}
}
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<nsIPresShell> 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.
RefPtr<nsDocumentShownDispatcher> event =
new nsDocumentShownDispatcher(document);
document->Dispatch(TaskCategory::Other, event.forget());
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<nsIDocShell> docShell(mContainer);
if (docShell) {
#ifdef DEBUG
nsCOMPtr<nsIContentViewer> currentViewer;
docShell->GetContentViewer(getter_AddRefs(currentViewer));
MOZ_ASSERT(currentViewer == this);
#endif
nsCOMPtr<nsILayoutHistoryState> 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<nsIBaseWindow> 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 || (mPrintJob && mPrintJob->GetIsPrinting())) {
*aCanClose = false;
mDeferredWindowClose = true;
} else
#endif
*aCanClose = true;
return NS_OK;
}
UniquePtr<ServoStyleSet> nsDocumentViewer::CreateStyleSet(Document* aDocument) {
// Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
// ordering.
// The document will fill in the document sheets when we create the presshell
auto cache = nsLayoutStylesheetCache::Singleton();
nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
auto styleSet = MakeUnique<ServoStyleSet>();
// User sheets
for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
styleSet->AppendStyleSheet(SheetType::User, sheet);
}
StyleSheet* sheet = nsContentUtils::IsInChromeDocshell(aDocument)
? cache->GetUserChromeSheet()
: cache->GetUserContentSheet();
if (sheet) {
styleSet->AppendStyleSheet(SheetType::User, sheet);
}
// Agent sheets
styleSet->AppendStyleSheet(SheetType::Agent, cache->UASheet());
if (MOZ_LIKELY(mDocument->NodeInfoManager()->MathMLEnabled())) {
styleSet->AppendStyleSheet(SheetType::Agent, cache->MathMLSheet());
}
if (MOZ_LIKELY(mDocument->NodeInfoManager()->SVGEnabled())) {
styleSet->AppendStyleSheet(SheetType::Agent, cache->SVGSheet());
}
styleSet->AppendStyleSheet(SheetType::Agent, cache->HTMLSheet());
// We don't add quirk.css here; nsPresContext::CompatibilityModeChanged will
// append it if needed.
if (nsLayoutUtils::ShouldUseNoFramesSheet(aDocument)) {
styleSet->AppendStyleSheet(SheetType::Agent, cache->NoFramesSheet());
}
if (nsLayoutUtils::ShouldUseNoScriptSheet(aDocument)) {
styleSet->AppendStyleSheet(SheetType::Agent, cache->NoScriptSheet());
}
styleSet->AppendStyleSheet(SheetType::Agent, cache->CounterStylesSheet());
// Load the minimal XUL rules for scrollbars and a few other XUL things
// that non-XUL (typically HTML) documents commonly use.
styleSet->AppendStyleSheet(SheetType::Agent, cache->MinimalXULSheet());
// Only load the full XUL sheet if we'll need it.
if (aDocument->LoadsFullXULStyleSheetUpFront()) {
styleSet->AppendStyleSheet(SheetType::Agent, cache->XULSheet());
}
styleSet->AppendStyleSheet(SheetType::Agent, cache->FormsSheet());
styleSet->AppendStyleSheet(SheetType::Agent, cache->ScrollbarsSheet());
styleSet->AppendStyleSheet(SheetType::Agent, cache->PluginProblemSheet());
for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
styleSet->AppendStyleSheet(SheetType::Agent, sheet);
}
return styleSet;
}
NS_IMETHODIMP
nsDocumentViewer::ClearHistoryEntry() {
if (mDocument) {
nsJSContext::PokeGC(JS::GCReason::PAGE_HIDE,
mDocument->GetWrapperPreserveColor(), NS_GC_DELAY * 2);
}
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<nsIDocShell> docShell(mContainer);
nsCOMPtr<nsPIDOMWindowOuter> pwin(docShell->GetWindow());
if (!pwin) {
return nullptr;
}
nsCOMPtr<Element> 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 <frame> as an inline in some XBL
// cases. Treat that as display:none, the document is not
// displayed.
if (!subdocFrame->IsSubDocumentFrame()) {
NS_WARNING_ASSERTION(subdocFrame->Type() == LayoutFrameType::None,
"Subdocument container has non-subdocument frame");
return nullptr;
}
NS_ASSERTION(subdocFrame->GetView(), "Subdoc frames must have views");
return static_cast<nsSubDocumentFrame*>(subdocFrame)->EnsureInnerView();
}
nsresult nsDocumentViewer::CreateDeviceContext(nsView* aContainerView) {
MOZ_ASSERT(!mPresShell && !mWindow,
"This will screw up our existing presentation");
MOZ_ASSERT(mDocument, "Gotta have a document here");
Document* 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
nsPresContext* ctx = doc->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<mozilla::dom::Selection> selection = GetDocumentSelection();
if (!selection) {
return NS_ERROR_FAILURE;
}
ErrorResult rv;
selection->CollapseToStart(rv);
return rv.StealNSResult();
}
NS_IMETHODIMP nsDocumentViewer::SelectAll() {
// XXX this is a temporary implementation copied from nsWebShell
// for now. I think Document and friends should have some helper
// functions to make this easier.
// use nsCopySupport::GetSelectionForCopy() ?
RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection();
if (!selection) {
return NS_ERROR_FAILURE;
}
if (!mDocument) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsINode> bodyNode;
if (mDocument->IsHTMLOrXHTML()) {
// XXXbz why not just do GetBody() for all documents, then GetRootElement()
// if GetBody() is null?
bodyNode = mDocument->GetBody();
} else {
bodyNode = mDocument->GetRootElement();
}
if (!bodyNode) return NS_ERROR_FAILURE;
ErrorResult err;
selection->RemoveAllRanges(err);
if (err.Failed()) {
return err.StealNSResult();
}
mozilla::dom::Selection::AutoUserInitiated userSelection(selection);
selection->SelectAllChildren(*bodyNode, err);
return err.StealNSResult();
}
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<nsINode> node = GetPopupLinkNode();
// make noise if we're not in a link
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
nsCOMPtr<dom::Element> 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<nsIClipboardHelper> 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<nsIImageLoadingContent> node = GetPopupImageNode();
// make noise if we're not in an image
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
nsCOMPtr<nsILoadContext> 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:
RefPtr<Selection> sel;
if (selectionOnly) {
nsCopySupport::GetSelectionForCopy(mDocument, getter_AddRefs(sel));
NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE);
if (sel->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(nsINode* aNode) {
Document* document = GetDocument();
NS_ENSURE_STATE(document);
nsCOMPtr<nsPIDOMWindowOuter> window(document->GetWindow());
NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
NS_ENSURE_STATE(root);
root->SetPopupNode(aNode);
return NS_OK;
}
NS_IMETHODIMP nsDocumentViewer::ScrollToNode(nsINode* aNode) {
NS_ENSURE_ARG(aNode);
NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsIPresShell> presShell = GetPresShell();
// Get the nsIContent interface, because that's what we need to
// get the primary frame
nsCOMPtr<nsIContent> 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<nsIDocShell> docShell(mContainer);
if (docShell) {
int32_t i;
int32_t n;
docShell->GetChildCount(&n);
for (i = 0; i < n; i++) {
nsCOMPtr<nsIDocShellTreeItem> child;
docShell->GetChildAt(i, getter_AddRefs(child));
nsCOMPtr<nsIDocShell> childAsShell(do_QueryInterface(child));
NS_ASSERTION(childAsShell, "null child in docshell");
if (childAsShell) {
nsCOMPtr<nsIContentViewer> 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 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(Document* aDocument, void* aClosure) {
// Would it be better to enumerate external resource viewers instead?
nsPresContext* ctxt = aDocument->GetPresContext();
if (ctxt) {
struct ZoomInfo* ZoomInfo = static_cast<struct ZoomInfo*>(aClosure);
ctxt->SetTextZoom(ZoomInfo->mZoom);
}
return true;
}
static bool SetExtResourceFullZoom(Document* aDocument, void* aClosure) {
// Would it be better to enumerate external resource viewers instead?
nsPresContext* ctxt = aDocument->GetPresContext();
if (ctxt) {
struct ZoomInfo* ZoomInfo = static_cast<struct ZoomInfo*>(aClosure);
ctxt->SetFullZoom(ZoomInfo->mZoom);
}
return true;
}
static bool SetExtResourceOverrideDPPX(Document* aDocument, void* aClosure) {
nsPresContext* ctxt = aDocument->GetPresContext();
if (ctxt) {
struct ZoomInfo* ZoomInfo = static_cast<struct ZoomInfo*>(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;
}
bool textZoomChange = (mTextZoom != aTextZoom);
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);
// Dispatch TextZoomChange event only if text zoom value has changed.
if (textZoomChange) {
nsContentUtils::DispatchChromeEvent(mDocument, ToSupports(mDocument),
NS_LITERAL_STRING("TextZoomChange"),
CanBubble::eYes, Cancelable::eYes);
}
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::GetEffectiveTextZoom(float* aEffectiveTextZoom) {
NS_ENSURE_ARG_POINTER(aEffectiveTextZoom);
nsPresContext* pc = GetPresContext();
*aEffectiveTextZoom = pc ? pc->EffectiveTextZoom() : 1.0f;
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<nsIPresShell> 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, ToSupports(mDocument),
NS_LITERAL_STRING("FullZoomChange"),
CanBubble::eYes, Cancelable::eYes);
}
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::GetDeviceFullZoom(float* aDeviceFullZoom) {
NS_ENSURE_ARG_POINTER(aDeviceFullZoom);
#ifdef NS_PRINT_PREVIEW
if (GetIsPrintPreview()) {
// Print Preview overrides all zoom; if specified, we use the print preview
// zoom, no matter what.
*aDeviceFullZoom = mPrintPreviewZoom;
return NS_OK;
}
#endif
// If not in print preview, ask the prescontext for the device zoom, if a
// prescontext is available.
nsPresContext* pc = GetPresContext();
*aDeviceFullZoom = pc ? pc->GetDeviceFullZoom() : 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<bool*>(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(Document* aDocument, void* aClosure) {
nsPresContext* ctxt = aDocument->GetPresContext();
if (ctxt) {
const nsAString* mediaType = static_cast<nsAString*>(aClosure);
ctxt->EmulateMedium(*mediaType);
}
return true;
}
static void ChildEmulateMedium(nsIContentViewer* aChild, void* aClosure) {
const nsAString* mediaType = static_cast<nsAString*>(aClosure);
aChild->EmulateMedium(*mediaType);
}
NS_IMETHODIMP
nsDocumentViewer::EmulateMedium(const nsAString& aMediaType) {
if (mPresContext) {
mPresContext->EmulateMedium(aMediaType);
}
CallChildren(ChildEmulateMedium, const_cast<nsAString*>(&aMediaType));
if (mDocument) {
mDocument->EnumerateExternalResources(ExtResourceEmulateMedium,
const_cast<nsAString*>(&aMediaType));
}
return NS_OK;
}
static bool ExtResourceStopEmulatingMedium(Document* aDocument,
void* aClosure) {
nsPresContext* ctxt = aDocument->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) {
auto encoding = nsDocumentViewer::GetForceCharset();
if (encoding) {
encoding->Name(aForceCharacterSet);
} else {
aForceCharacterSet.Truncate();
}
return NS_OK;
}
/* [noscript,notxpcom] Encoding getForceCharset (); */
NS_IMETHODIMP_(const Encoding*)
nsDocumentViewer::GetForceCharset() { return mForceCharacterSet; }
static void SetChildForceCharacterSet(nsIContentViewer* aChild,
void* aClosure) {
auto encoding = static_cast<const Encoding*>(aClosure);
aChild->SetForceCharset(encoding);
}
NS_IMETHODIMP
nsDocumentViewer::SetForceCharacterSet(const nsACString& aForceCharacterSet) {
// The empty string means no hint.
const Encoding* encoding = nullptr;
if (!aForceCharacterSet.IsEmpty()) {
if (!(encoding = Encoding::ForLabel(aForceCharacterSet))) {
// Reject unknown labels
return NS_ERROR_INVALID_ARG;
}
}
nsDocumentViewer::SetForceCharset(encoding);
return NS_OK;
}
/* [noscript,notxpcom] void setForceCharset (in Encoding aEncoding); */
NS_IMETHODIMP_(void)
nsDocumentViewer::SetForceCharset(const Encoding* aEncoding) {
mForceCharacterSet = aEncoding;
// now set the force char set on all children of mContainer
CallChildren(SetChildForceCharacterSet, (void*)aEncoding);
}
NS_IMETHODIMP nsDocumentViewer::GetHintCharacterSet(
nsACString& aHintCharacterSet) {
auto encoding = nsDocumentViewer::GetHintCharset();
if (encoding) {
encoding->Name(aHintCharacterSet);
} else {
aHintCharacterSet.Truncate();
}
return NS_OK;
}
/* [noscript,notxpcom] Encoding getHintCharset (); */
NS_IMETHODIMP_(const Encoding*)
nsDocumentViewer::GetHintCharset() {
if (kCharsetUninitialized == mHintCharsetSource) {
return nullptr;
}
// this can't possibly be right. we can't set a value just because somebody
// got a related value!
// mHintCharsetSource = kCharsetUninitialized;
return mHintCharset;
}
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) {
auto encoding = static_cast<const Encoding*>(aClosure);
aChild->SetHintCharset(encoding);
}
NS_IMETHODIMP
nsDocumentViewer::SetHintCharacterSet(const nsACString& aHintCharacterSet) {
// The empty string means no hint.
const Encoding* encoding = nullptr;
if (!aHintCharacterSet.IsEmpty()) {
if (!(encoding = Encoding::ForLabel(aHintCharacterSet))) {
// Reject unknown labels
return NS_ERROR_INVALID_ARG;
}
}
nsDocumentViewer::SetHintCharset(encoding);
return NS_OK;
}
/* [noscript,notxpcom] void setHintCharset (in Encoding aEncoding); */
NS_IMETHODIMP_(void)
nsDocumentViewer::SetHintCharset(const Encoding* aEncoding) {
mHintCharset = aEncoding;
// now set the hint char set on all children of mContainer
CallChildren(SetChildHintCharacterSet, (void*)aEncoding);
}
static void AppendChildSubtree(nsIContentViewer* aChild, void* aClosure) {
nsTArray<nsCOMPtr<nsIContentViewer>>& array =
*static_cast<nsTArray<nsCOMPtr<nsIContentViewer>>*>(aClosure);
aChild->AppendSubtree(array);
}
NS_IMETHODIMP nsDocumentViewer::AppendSubtree(
nsTArray<nsCOMPtr<nsIContentViewer>>& 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<nsIPresShell> presShell = GetPresShell();
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(FlushType::Layout);
nsIFrame* root = presShell->GetRootFrame();
NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
nscoord prefWidth;
{
RefPtr<gfxContext> rcx(presShell->CreateReferenceRenderingContext());
prefWidth = root->GetPrefISize(rcx);
}
if (prefWidth > aMaxWidth) {
prefWidth = aMaxWidth;
}
nsresult rv =
presShell->ResizeReflow(prefWidth, aMaxHeight, 0, 0,
nsIPresShell::ResizeReflowOptions::eBSizeLimit);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsPresContext> presContext = GetPresContext();
NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
// Protect against bogus returns here
nsRect shellArea = presContext->GetVisibleArea();
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<nsIDocShellTreeItem> docShellAsItem(mContainer);
NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsIDocShellTreeItem> 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<nsPresContext> presContext = GetPresContext();
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.
*/
already_AddRefed<nsINode> nsDocumentViewer::GetPopupNode() {
// get the document
Document* document = GetDocument();
NS_ENSURE_TRUE(document, nullptr);
// get the private dom window
nsCOMPtr<nsPIDOMWindowOuter> window(document->GetWindow());
NS_ENSURE_TRUE(window, nullptr);
if (window) {
nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
NS_ENSURE_TRUE(root, nullptr);
// get the popup node
nsCOMPtr<nsINode> node = root->GetPopupNode();
#ifdef MOZ_XUL
if (!node) {
nsPIDOMWindowOuter* rootWindow = root->GetWindow();
if (rootWindow) {
nsCOMPtr<Document> rootDoc = rootWindow->GetExtantDoc();
if (rootDoc) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
node = pm->GetLastTriggerPopupNode(rootDoc);
}
}
}
}
#endif
return node.forget();
}
return nullptr;
}
// GetPopupLinkNode: return popup link node or fail
already_AddRefed<nsINode> nsDocumentViewer::GetPopupLinkNode() {
// find popup node
nsCOMPtr<nsINode> node = GetPopupNode();
// find out if we have a link in our ancestry
while (node) {
nsCOMPtr<nsIContent> content(do_QueryInterface(node));
if (content) {
nsCOMPtr<nsIURI> hrefURI = content->GetHrefURI();
if (hrefURI) {
return node.forget();
}
}
// get our parent and keep trying...
node = node->GetParentNode();
}
// if we have no node, fail
return nullptr;
}
// GetPopupLinkNode: return popup image node or fail
already_AddRefed<nsIImageLoadingContent> nsDocumentViewer::GetPopupImageNode() {
// find popup node
nsCOMPtr<nsINode> node = GetPopupNode();
nsCOMPtr<nsIImageLoadingContent> img = do_QueryInterface(node);
return img.forget();
}
/*
* 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<nsINode> node = GetPopupLinkNode();
if (!node) {
return 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<nsIImageLoadingContent> node = GetPopupImageNode();
if (!node) {
return NS_ERROR_FAILURE;
}
// Make sure there is a URI assigned. This allows <input type="image"> to
// be an image but rejects other <input> types. This matches what
// nsContextMenu.js does.
nsCOMPtr<nsIURI> 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(
Document*, Selection*, int16_t aReason) {
if (!mDocViewer) {
return NS_OK;
}
// get the selection state
RefPtr<mozilla::dom::Selection> selection =
mDocViewer->GetDocumentSelection();
if (!selection) {
return NS_ERROR_FAILURE;
}
Document* theDoc = mDocViewer->GetDocument();
if (!theDoc) return NS_ERROR_FAILURE;
nsCOMPtr<nsPIDOMWindowOuter> domWindow = theDoc->GetWindow();
if (!domWindow) return NS_ERROR_FAILURE;
bool selectionCollapsed = selection->IsCollapsed();
// 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(Event* aEvent) {
NS_ENSURE_STATE(mDocViewer);
nsCOMPtr<nsIPresShell> shell = mDocViewer->GetPresShell();
NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
nsCOMPtr<nsISelectionController> 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.
if (mDocument && mDocument->IsXULDocument()) {
return NS_ERROR_FAILURE;
}
if (!mContainer) {
PR_PL(("Container was destroyed yet we are still trying to use it!"));
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDocShell> 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
auto busyFlags = docShell->GetBusyFlags();
if (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 (mPrintJob) {
mPrintJob->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");
auto autoBeforeAndAfterPrint =
MakeUnique<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<nsIPluginDocument> pDoc(do_QueryInterface(mDocument));
if (pDoc) return pDoc->Print();
// Our call to nsPrintJob::Print() may cause mPrintJob to be
// Release()'d in Destroy(). Therefore, we need to grab the instance with
// a local variable, so that it won't be deleted during its own method.
RefPtr<nsPrintJob> printJob = mPrintJob;
if (!printJob) {
NS_ENSURE_STATE(mDeviceContext);
printJob = new nsPrintJob();
rv = printJob->Initialize(this, mContainer, mDocument,
float(AppUnitsPerCSSInch()) /
float(mDeviceContext->AppUnitsPerDevPixel()) /
mPageZoom);
if (NS_FAILED(rv)) {
printJob->Destroy();
return rv;
}
mPrintJob = printJob;
}
if (printJob->HasPrintCallbackCanvas()) {
// Postpone the 'afterprint' event until after the mozPrintCallback
// callbacks have been called:
mAutoBeforeAndAfterPrint = std::move(autoBeforeAndAfterPrint);
}
dom::Element* root = mDocument->GetRootElement();
if (root &&
root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint)) {
printJob->SetDisallowSelectionPrint(true);
}
rv = printJob->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)
MOZ_ASSERT(IsInitializedForPrintPreview(),
"For print preview nsIWebBrowserPrint must be from "
"docshell.printPreview!");
NS_ENSURE_ARG_POINTER(aChildDOMWin);
nsresult rv = NS_OK;
if (GetIsPrinting()) {
nsPrintJob::CloseProgressDialog(aWebProgressListener);
return NS_ERROR_FAILURE;
}
// Printing XUL documents is not supported.
if (mDocument && mDocument->IsXULDocument()) {
nsPrintJob::CloseProgressDialog(aWebProgressListener);
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDocShell> docShell(mContainer);
if (!docShell || !mDeviceContext) {
PR_PL(("Can't Print Preview without device context and docshell"));
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aChildDOMWin);
MOZ_ASSERT(window);
nsCOMPtr<Document> 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).
UniquePtr<AutoPrintEventDispatcher> autoBeforeAndAfterPrint;
if (!mAutoBeforeAndAfterPrint) {
autoBeforeAndAfterPrint = MakeUnique<AutoPrintEventDispatcher>(doc);
}
NS_ENSURE_STATE(!GetIsPrinting());
// beforeprint event may have caused ContentViewer to be shutdown.
NS_ENSURE_STATE(mContainer);
NS_ENSURE_STATE(mDeviceContext);
// Our call to nsPrintJob::PrintPreview() may cause mPrintJob to be
// Release()'d in Destroy(). Therefore, we need to grab the instance with
// a local variable, so that it won't be deleted during its own method.
RefPtr<nsPrintJob> printJob = mPrintJob;
if (!printJob) {
printJob = new nsPrintJob();
rv = printJob->Initialize(this, mContainer, doc,
float(AppUnitsPerCSSInch()) /
float(mDeviceContext->AppUnitsPerDevPixel()) /
mPageZoom);
if (NS_FAILED(rv)) {
printJob->Destroy();
return rv;
}
mPrintJob = printJob;
}
if (autoBeforeAndAfterPrint && printJob->HasPrintCallbackCanvas()) {
// Postpone the 'afterprint' event until after the mozPrintCallback
// callbacks have been called:
mAutoBeforeAndAfterPrint = std::move(autoBeforeAndAfterPrint);
}
dom::Element* root = doc->GetRootElement();
if (root &&
root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint)) {
PR_PL(("PrintPreview: found mozdisallowselectionprint"));
printJob->SetDisallowSelectionPrint(true);
}
rv = printJob->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() || mPrintJob->GetIsCreatingPrintPreview())
return NS_ERROR_FAILURE;
nsIScrollableFrame* sf =
mPrintJob->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(mPrintJob->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(mPrintJob->GetPrintPreviewScale() *
fndPageFrame->GetPosition().y);
sf->ScrollTo(nsPoint(pt.x, newYPosn), nsIScrollableFrame::INSTANT);
}
return NS_OK;
}
NS_IMETHODIMP
nsDocumentViewer::GetGlobalPrintSettings(
nsIPrintSettings** aGlobalPrintSettings) {
return nsPrintJob::GetGlobalPrintSettings(aGlobalPrintSettings);
}
// XXX This always returns false for subdocuments
NS_IMETHODIMP
nsDocumentViewer::GetDoingPrint(bool* aDoingPrint) {
NS_ENSURE_ARG_POINTER(aDoingPrint);
*aDoingPrint = false;
if (mPrintJob) {
// XXX shouldn't this be GetDoingPrint() ?
return mPrintJob->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 (mPrintJob) {
return mPrintJob->GetDoingPrintPreview(aDoingPrintPreview);
}
return NS_OK;
}
NS_IMETHODIMP
nsDocumentViewer::GetCurrentPrintSettings(
nsIPrintSettings** aCurrentPrintSettings) {
NS_ENSURE_ARG_POINTER(aCurrentPrintSettings);
*aCurrentPrintSettings = nullptr;
NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
return mPrintJob->GetCurrentPrintSettings(aCurrentPrintSettings);
}
NS_IMETHODIMP
nsDocumentViewer::Cancel() {
NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
return mPrintJob->Cancelled();
}
NS_IMETHODIMP
nsDocumentViewer::ExitPrintPreview() {
if (GetIsPrinting()) return NS_ERROR_FAILURE;
NS_ENSURE_TRUE(mPrintJob, 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(mPrintJob, NS_ERROR_FAILURE);
return mPrintJob->EnumerateDocumentNames(aCount, aResult);
# else
return NS_ERROR_FAILURE;
# endif
}
NS_IMETHODIMP
nsDocumentViewer::GetIsFramesetFrameSelected(bool* aIsFramesetFrameSelected) {
# ifdef NS_PRINTING
*aIsFramesetFrameSelected = false;
NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
return mPrintJob->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(mPrintJob, NS_ERROR_FAILURE);
return mPrintJob->GetPrintPreviewNumPages(aPrintPreviewNumPages);
# else
return NS_ERROR_FAILURE;
# endif
}
NS_IMETHODIMP
nsDocumentViewer::GetIsFramesetDocument(bool* aIsFramesetDocument) {
# ifdef NS_PRINTING
*aIsFramesetDocument = false;
NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
return mPrintJob->GetIsFramesetDocument(aIsFramesetDocument);
# else
return NS_ERROR_FAILURE;
# endif
}
NS_IMETHODIMP
nsDocumentViewer::GetIsIFrameSelected(bool* aIsIFrameSelected) {
# ifdef NS_PRINTING
*aIsIFrameSelected = false;
NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
return mPrintJob->GetIsIFrameSelected(aIsIFrameSelected);
# else
return NS_ERROR_FAILURE;
# endif
}
NS_IMETHODIMP
nsDocumentViewer::GetIsRangeSelection(bool* aIsRangeSelection) {
# ifdef NS_PRINTING
*aIsRangeSelection = false;
NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
return mPrintJob->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<nsIDocShellTreeItem> parentItem(aParentNode);
// find top of "same parent" tree
if (aStartAtTop) {
if (aIsPrintingOrPP) {
while (parentItem) {
nsCOMPtr<nsIDocShellTreeItem> parent;
parentItem->GetSameTypeParent(getter_AddRefs(parent));
if (!parent) {
break;
}
parentItem = parent;
}
mTopContainerWhilePrinting = do_GetWeakReference(parentItem);
} else {
parentItem = do_QueryReferent(mTopContainerWhilePrinting);
}
}
// Check to see if the DocShell's ContentViewer is printing/PP
nsCOMPtr<nsIDocShell> 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<nsIDocShellTreeItem> 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<nsIDocShellTreeItem> 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_ANDROID) || 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 (mPrintJob) {
return mPrintJob->GetIsPrinting();
}
#endif
return false;
}
//------------------------------------------------------------
// Notification from the PrintJob 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<nsIDocShell> 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 PrintJob holds the current value
// this called from inside the DocViewer.
// XXX it always returns false for subdocuments
bool nsDocumentViewer::GetIsPrintPreview() {
#ifdef NS_PRINTING
if (mPrintJob) {
return mPrintJob->GetIsPrintPreview();
}
#endif
return false;
}
//------------------------------------------------------------
// Notification from the PrintJob 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<nsIDocShell> 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::IncrementDestroyBlockedCount() {
++mDestroyBlockedCount;
}
void nsDocumentViewer::DecrementDestroyBlockedCount() {
--mDestroyBlockedCount;
}
//------------------------------------------------------------
#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<nsISimpleEnumerator> docShellEnumerator;
aDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent,
nsIDocShell::ENUMERATE_FORWARDS,
getter_AddRefs(docShellEnumerator));
nsCOMPtr<nsISupports> currentContainer;
bool hasMoreDocShells;
while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) &&
hasMoreDocShells) {
docShellEnumerator->GetNext(getter_AddRefs(currentContainer));
nsCOMPtr<nsPIDOMWindowOuter> 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);
mPrintJob->TurnScriptingOn(true);
mPrintJob->Destroy();
mPrintJob = nullptr;
nsCOMPtr<nsIDocShell> docShell(mContainer);
ResetFocusState(docShell);
SetTextZoom(mTextZoom);
SetFullZoom(mPageZoom);
SetOverrideDPPX(mOverrideDPPX);
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 nsPrintJob.
//
// BUT, if we are in Print Preview then we want to ignore the
// notification (we do not get rid of the nsPrintJob)
//
// 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 Destroy() has been called during calling nsPrintJob::Print() or
// nsPrintJob::PrintPreview(), mPrintJob is already nullptr here.
// So, the following clean up does nothing in such case.
// (Do we need some of this for that case?)
if (mPrintJob) {
RefPtr<nsPrintJob> printJob = mPrintJob;
if (GetIsPrintPreview()) {
printJob->DestroyPrintingData();
} else {
mPrintJob = nullptr;
printJob->Destroy();
}
// We are done printing, now cleanup
if (mDeferredWindowClose) {
mDeferredWindowClose = false;
if (mContainer) {
if (nsCOMPtr<nsPIDOMWindowOuter> win = 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<mozilla::dom::Selection> 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<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(ToSupports(mDocument), "document-shown",
nullptr);
}
return NS_OK;
}