michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsSHEntryShared.h" michael@0: michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsISHistory.h" michael@0: #include "nsISHistoryInternal.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsDocShellEditorData.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsILayoutHistoryState.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsISupportsArray.h" michael@0: michael@0: namespace dom = mozilla::dom; michael@0: michael@0: namespace { michael@0: michael@0: uint64_t gSHEntrySharedID = 0; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: // Hardcode this to time out unused content viewers after 30 minutes michael@0: // XXX jlebar shouldn't this be a pref? michael@0: #define CONTENT_VIEWER_TIMEOUT_SECONDS (30*60) michael@0: michael@0: typedef nsExpirationTracker HistoryTrackerBase; michael@0: class HistoryTracker MOZ_FINAL : public HistoryTrackerBase { michael@0: public: michael@0: // Expire cached contentviewers after 20-30 minutes in the cache. michael@0: HistoryTracker() michael@0: : HistoryTrackerBase(1000 * CONTENT_VIEWER_TIMEOUT_SECONDS / 2) michael@0: { michael@0: } michael@0: michael@0: protected: michael@0: virtual void NotifyExpired(nsSHEntryShared *aObj) { michael@0: RemoveObject(aObj); michael@0: aObj->Expire(); michael@0: } michael@0: }; michael@0: michael@0: static HistoryTracker *gHistoryTracker = nullptr; michael@0: michael@0: void michael@0: nsSHEntryShared::Startup() michael@0: { michael@0: gHistoryTracker = new HistoryTracker(); michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::Shutdown() michael@0: { michael@0: delete gHistoryTracker; michael@0: gHistoryTracker = nullptr; michael@0: } michael@0: michael@0: nsSHEntryShared::nsSHEntryShared() michael@0: : mDocShellID(0) michael@0: , mIsFrameNavigation(false) michael@0: , mSaveLayoutState(true) michael@0: , mSticky(true) michael@0: , mDynamicallyCreated(false) michael@0: , mLastTouched(0) michael@0: , mID(gSHEntrySharedID++) michael@0: , mExpired(false) michael@0: , mViewerBounds(0, 0, 0, 0) michael@0: { michael@0: } michael@0: michael@0: nsSHEntryShared::~nsSHEntryShared() michael@0: { michael@0: RemoveFromExpirationTracker(); michael@0: michael@0: #ifdef DEBUG michael@0: // Check that we're not still on track to expire. We shouldn't be, because michael@0: // we just removed ourselves! michael@0: nsExpirationTracker::Iterator michael@0: iterator(gHistoryTracker); michael@0: michael@0: nsSHEntryShared *elem; michael@0: while ((elem = iterator.Next()) != nullptr) { michael@0: NS_ASSERTION(elem != this, "Found dead entry still in the tracker!"); michael@0: } michael@0: #endif michael@0: michael@0: if (mContentViewer) { michael@0: RemoveFromBFCacheSync(); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver) michael@0: michael@0: already_AddRefed michael@0: nsSHEntryShared::Duplicate(nsSHEntryShared *aEntry) michael@0: { michael@0: nsRefPtr newEntry = new nsSHEntryShared(); michael@0: michael@0: newEntry->mDocShellID = aEntry->mDocShellID; michael@0: newEntry->mChildShells.AppendObjects(aEntry->mChildShells); michael@0: newEntry->mOwner = aEntry->mOwner; michael@0: newEntry->mContentType.Assign(aEntry->mContentType); michael@0: newEntry->mIsFrameNavigation = aEntry->mIsFrameNavigation; michael@0: newEntry->mSaveLayoutState = aEntry->mSaveLayoutState; michael@0: newEntry->mSticky = aEntry->mSticky; michael@0: newEntry->mDynamicallyCreated = aEntry->mDynamicallyCreated; michael@0: newEntry->mCacheKey = aEntry->mCacheKey; michael@0: newEntry->mLastTouched = aEntry->mLastTouched; michael@0: michael@0: return newEntry.forget(); michael@0: } michael@0: michael@0: void nsSHEntryShared::RemoveFromExpirationTracker() michael@0: { michael@0: if (GetExpirationState()->IsTracked()) { michael@0: gHistoryTracker->RemoveObject(this); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsSHEntryShared::SyncPresentationState() michael@0: { michael@0: if (mContentViewer && mWindowState) { michael@0: // If we have a content viewer and a window state, we should be ok. michael@0: return NS_OK; michael@0: } michael@0: michael@0: DropPresentationState(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::DropPresentationState() michael@0: { michael@0: nsRefPtr kungFuDeathGrip = this; michael@0: michael@0: if (mDocument) { michael@0: mDocument->SetBFCacheEntry(nullptr); michael@0: mDocument->RemoveMutationObserver(this); michael@0: mDocument = nullptr; michael@0: } michael@0: if (mContentViewer) { michael@0: mContentViewer->ClearHistoryEntry(); michael@0: } michael@0: michael@0: RemoveFromExpirationTracker(); michael@0: mContentViewer = nullptr; michael@0: mSticky = true; michael@0: mWindowState = nullptr; michael@0: mViewerBounds.SetRect(0, 0, 0, 0); michael@0: mChildShells.Clear(); michael@0: mRefreshURIList = nullptr; michael@0: mEditorData = nullptr; michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::Expire() michael@0: { michael@0: // This entry has timed out. If we still have a content viewer, we need to michael@0: // evict it. michael@0: if (!mContentViewer) { michael@0: return; michael@0: } michael@0: nsCOMPtr container; michael@0: mContentViewer->GetContainer(getter_AddRefs(container)); michael@0: nsCOMPtr treeItem = do_QueryInterface(container); michael@0: if (!treeItem) { michael@0: return; michael@0: } michael@0: // We need to find the root DocShell since only that object has an michael@0: // SHistory and we need the SHistory to evict content viewers michael@0: nsCOMPtr root; michael@0: treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root)); michael@0: nsCOMPtr webNav = do_QueryInterface(root); michael@0: nsCOMPtr history; michael@0: webNav->GetSessionHistory(getter_AddRefs(history)); michael@0: nsCOMPtr historyInt = do_QueryInterface(history); michael@0: if (!historyInt) { michael@0: return; michael@0: } michael@0: historyInt->EvictExpiredContentViewerForEntry(this); michael@0: } michael@0: michael@0: nsresult michael@0: nsSHEntryShared::SetContentViewer(nsIContentViewer *aViewer) michael@0: { michael@0: NS_PRECONDITION(!aViewer || !mContentViewer, michael@0: "SHEntryShared already contains viewer"); michael@0: michael@0: if (mContentViewer || !aViewer) { michael@0: DropPresentationState(); michael@0: } michael@0: michael@0: mContentViewer = aViewer; michael@0: michael@0: if (mContentViewer) { michael@0: gHistoryTracker->AddObject(this); michael@0: michael@0: nsCOMPtr domDoc; michael@0: mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); michael@0: // Store observed document in strong pointer in case it is removed from michael@0: // the contentviewer michael@0: mDocument = do_QueryInterface(domDoc); michael@0: if (mDocument) { michael@0: mDocument->SetBFCacheEntry(this); michael@0: mDocument->AddMutationObserver(this); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSHEntryShared::RemoveFromBFCacheSync() michael@0: { michael@0: NS_ASSERTION(mContentViewer && mDocument, michael@0: "we're not in the bfcache!"); michael@0: michael@0: nsCOMPtr viewer = mContentViewer; michael@0: DropPresentationState(); michael@0: michael@0: // Warning! The call to DropPresentationState could have dropped the last michael@0: // reference to this object, so don't access members beyond here. michael@0: michael@0: if (viewer) { michael@0: viewer->Destroy(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class DestroyViewerEvent : public nsRunnable michael@0: { michael@0: public: michael@0: DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument) michael@0: : mViewer(aViewer), michael@0: mDocument(aDocument) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mViewer) { michael@0: mViewer->Destroy(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr mViewer; michael@0: nsCOMPtr mDocument; michael@0: }; michael@0: michael@0: nsresult michael@0: nsSHEntryShared::RemoveFromBFCacheAsync() michael@0: { michael@0: NS_ASSERTION(mContentViewer && mDocument, michael@0: "we're not in the bfcache!"); michael@0: michael@0: // Release the reference to the contentviewer asynchronously so that the michael@0: // document doesn't get nuked mid-mutation. michael@0: michael@0: nsCOMPtr evt = michael@0: new DestroyViewerEvent(mContentViewer, mDocument); michael@0: nsresult rv = NS_DispatchToCurrentThread(evt); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("failed to dispatch DestroyViewerEvent"); michael@0: } else { michael@0: // Drop presentation. Only do this if we succeeded in posting the event michael@0: // since otherwise the document could be torn down mid-mutation, causing michael@0: // crashes. michael@0: DropPresentationState(); michael@0: } michael@0: michael@0: // Careful! The call to DropPresentationState could have dropped the last michael@0: // reference to this nsSHEntryShared, so don't access members beyond here. michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSHEntryShared::GetID(uint64_t *aID) michael@0: { michael@0: *aID = mID; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //***************************************************************************** michael@0: // nsSHEntryShared: nsIMutationObserver michael@0: //***************************************************************************** michael@0: michael@0: void michael@0: nsSHEntryShared::NodeWillBeDestroyed(const nsINode* aNode) michael@0: { michael@0: NS_NOTREACHED("Document destroyed while we're holding a strong ref to it"); michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::CharacterDataWillChange(nsIDocument* aDocument, michael@0: nsIContent* aContent, michael@0: CharacterDataChangeInfo* aInfo) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::CharacterDataChanged(nsIDocument* aDocument, michael@0: nsIContent* aContent, michael@0: CharacterDataChangeInfo* aInfo) michael@0: { michael@0: RemoveFromBFCacheAsync(); michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::AttributeWillChange(nsIDocument* aDocument, michael@0: dom::Element* aContent, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::AttributeChanged(nsIDocument* aDocument, michael@0: dom::Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: RemoveFromBFCacheAsync(); michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::ContentAppended(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t /* unused */) michael@0: { michael@0: RemoveFromBFCacheAsync(); michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::ContentInserted(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t /* unused */) michael@0: { michael@0: RemoveFromBFCacheAsync(); michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::ContentRemoved(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: RemoveFromBFCacheAsync(); michael@0: } michael@0: michael@0: void michael@0: nsSHEntryShared::ParentChainChanged(nsIContent *aContent) michael@0: { michael@0: }