/* -*- 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/. */ #include "nsSHEntry.h" #include "nsIDocShellLoadInfo.h" #include "nsIDocShellTreeItem.h" #include "nsDocShellEditorData.h" #include "nsSHEntryShared.h" #include "nsILayoutHistoryState.h" #include "nsIContentViewer.h" #include "nsISupportsArray.h" #include "nsIStructuredCloneContainer.h" #include "nsIInputStream.h" #include "nsIURI.h" #include "mozilla/net/ReferrerPolicy.h" #include namespace dom = mozilla::dom; static uint32_t gEntryID = 0; nsSHEntry::nsSHEntry() : mShared(new nsSHEntryShared()) , mReferrerPolicy(mozilla::net::RP_Default) , mLoadType(0) , mID(gEntryID++) , mScrollPositionX(0) , mScrollPositionY(0) , mParent(nullptr) , mURIWasModified(false) , mIsSrcdocEntry(false) { } nsSHEntry::nsSHEntry(const nsSHEntry& aOther) : mShared(aOther.mShared) , mURI(aOther.mURI) , mOriginalURI(aOther.mOriginalURI) , mLoadReplace(aOther.mLoadReplace) , mReferrerURI(aOther.mReferrerURI) , mReferrerPolicy(aOther.mReferrerPolicy) , mTitle(aOther.mTitle) , mPostData(aOther.mPostData) , mLoadType(0) // XXX why not copy? , mID(aOther.mID) , mScrollPositionX(0) // XXX why not copy? , mScrollPositionY(0) // XXX why not copy? , mParent(aOther.mParent) , mURIWasModified(aOther.mURIWasModified) , mStateData(aOther.mStateData) , mIsSrcdocEntry(aOther.mIsSrcdocEntry) , mSrcdocData(aOther.mSrcdocData) , mBaseURI(aOther.mBaseURI) { } static bool ClearParentPtr(nsISHEntry* aEntry, void* /* aData */) { if (aEntry) { aEntry->SetParent(nullptr); } return true; } nsSHEntry::~nsSHEntry() { // Null out the mParent pointers on all our kids. mChildren.EnumerateForwards(ClearParentPtr, nullptr); } NS_IMPL_ISUPPORTS(nsSHEntry, nsISHContainer, nsISHEntry, nsISHEntryInternal) NS_IMETHODIMP nsSHEntry::SetScrollPosition(int32_t aX, int32_t aY) { mScrollPositionX = aX; mScrollPositionY = aY; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetScrollPosition(int32_t* aX, int32_t* aY) { *aX = mScrollPositionX; *aY = mScrollPositionY; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetURIWasModified(bool* aOut) { *aOut = mURIWasModified; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetURIWasModified(bool aIn) { mURIWasModified = aIn; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetURI(nsIURI** aURI) { *aURI = mURI; NS_IF_ADDREF(*aURI); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetURI(nsIURI* aURI) { mURI = aURI; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetOriginalURI(nsIURI** aOriginalURI) { *aOriginalURI = mOriginalURI; NS_IF_ADDREF(*aOriginalURI); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetOriginalURI(nsIURI* aOriginalURI) { mOriginalURI = aOriginalURI; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetLoadReplace(bool* aLoadReplace) { *aLoadReplace = mLoadReplace; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetLoadReplace(bool aLoadReplace) { mLoadReplace = aLoadReplace; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetReferrerURI(nsIURI** aReferrerURI) { *aReferrerURI = mReferrerURI; NS_IF_ADDREF(*aReferrerURI); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetReferrerURI(nsIURI* aReferrerURI) { mReferrerURI = aReferrerURI; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetReferrerPolicy(uint32_t* aReferrerPolicy) { *aReferrerPolicy = mReferrerPolicy; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetReferrerPolicy(uint32_t aReferrerPolicy) { mReferrerPolicy = aReferrerPolicy; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetContentViewer(nsIContentViewer* aViewer) { return mShared->SetContentViewer(aViewer); } NS_IMETHODIMP nsSHEntry::GetContentViewer(nsIContentViewer** aResult) { *aResult = mShared->mContentViewer; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::GetAnyContentViewer(nsISHEntry** aOwnerEntry, nsIContentViewer** aResult) { // Find a content viewer in the root node or any of its children, // assuming that there is only one content viewer total in any one // nsSHEntry tree GetContentViewer(aResult); if (*aResult) { #ifdef DEBUG_PAGE_CACHE printf("Found content viewer\n"); #endif *aOwnerEntry = this; NS_ADDREF(*aOwnerEntry); return NS_OK; } // The root SHEntry doesn't have a ContentViewer, so check child nodes for (int32_t i = 0; i < mChildren.Count(); i++) { nsISHEntry* child = mChildren[i]; if (child) { #ifdef DEBUG_PAGE_CACHE printf("Evaluating SHEntry child %d\n", i); #endif child->GetAnyContentViewer(aOwnerEntry, aResult); if (*aResult) { return NS_OK; } } } return NS_OK; } NS_IMETHODIMP nsSHEntry::SetSticky(bool aSticky) { mShared->mSticky = aSticky; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetSticky(bool* aSticky) { *aSticky = mShared->mSticky; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetTitle(char16_t** aTitle) { // Check for empty title... if (mTitle.IsEmpty() && mURI) { // Default title is the URL. nsAutoCString spec; if (NS_SUCCEEDED(mURI->GetSpec(spec))) { AppendUTF8toUTF16(spec, mTitle); } } *aTitle = ToNewUnicode(mTitle); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetTitle(const nsAString& aTitle) { mTitle = aTitle; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetPostData(nsIInputStream** aResult) { *aResult = mPostData; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetPostData(nsIInputStream* aPostData) { mPostData = aPostData; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) { *aResult = mShared->mLayoutHistoryState; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) { mShared->mLayoutHistoryState = aState; if (mShared->mLayoutHistoryState) { mShared->mLayoutHistoryState->SetScrollPositionOnly( !mShared->mSaveLayoutState); } return NS_OK; } NS_IMETHODIMP nsSHEntry::GetLoadType(uint32_t* aResult) { *aResult = mLoadType; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetLoadType(uint32_t aLoadType) { mLoadType = aLoadType; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetID(uint32_t* aResult) { *aResult = mID; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetID(uint32_t aID) { mID = aID; return NS_OK; } nsSHEntryShared* nsSHEntry::GetSharedState() { return mShared; } NS_IMETHODIMP nsSHEntry::GetIsSubFrame(bool* aFlag) { *aFlag = mShared->mIsFrameNavigation; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetIsSubFrame(bool aFlag) { mShared->mIsFrameNavigation = aFlag; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetCacheKey(nsISupports** aResult) { *aResult = mShared->mCacheKey; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetCacheKey(nsISupports* aCacheKey) { mShared->mCacheKey = aCacheKey; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetSaveLayoutStateFlag(bool* aFlag) { *aFlag = mShared->mSaveLayoutState; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetSaveLayoutStateFlag(bool aFlag) { mShared->mSaveLayoutState = aFlag; if (mShared->mLayoutHistoryState) { mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag); } return NS_OK; } NS_IMETHODIMP nsSHEntry::GetExpirationStatus(bool* aFlag) { *aFlag = mShared->mExpired; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetExpirationStatus(bool aFlag) { mShared->mExpired = aFlag; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetContentType(nsACString& aContentType) { aContentType = mShared->mContentType; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetContentType(const nsACString& aContentType) { mShared->mContentType = aContentType; return NS_OK; } NS_IMETHODIMP nsSHEntry::Create(nsIURI* aURI, const nsAString& aTitle, nsIInputStream* aInputStream, nsILayoutHistoryState* aLayoutHistoryState, nsISupports* aCacheKey, const nsACString& aContentType, nsISupports* aOwner, uint64_t aDocShellID, bool aDynamicCreation) { mURI = aURI; mTitle = aTitle; mPostData = aInputStream; // Set the LoadType by default to loadHistory during creation mLoadType = (uint32_t)nsIDocShellLoadInfo::loadHistory; mShared->mCacheKey = aCacheKey; mShared->mContentType = aContentType; mShared->mOwner = aOwner; mShared->mDocShellID = aDocShellID; mShared->mDynamicallyCreated = aDynamicCreation; // By default all entries are set false for subframe flag. // nsDocShell::CloneAndReplace() which creates entries for // all subframe navigations, sets the flag to true. mShared->mIsFrameNavigation = false; // By default we save LayoutHistoryState mShared->mSaveLayoutState = true; mShared->mLayoutHistoryState = aLayoutHistoryState; // By default the page is not expired mShared->mExpired = false; mIsSrcdocEntry = false; mSrcdocData = NullString(); return NS_OK; } NS_IMETHODIMP nsSHEntry::Clone(nsISHEntry** aResult) { *aResult = new nsSHEntry(*this); NS_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::GetParent(nsISHEntry** aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = mParent; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetParent(nsISHEntry* aParent) { /* parent not Addrefed on purpose to avoid cyclic reference * Null parent is OK * * XXX this method should not be scriptable if this is the case!! */ mParent = aParent; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetWindowState(nsISupports* aState) { mShared->mWindowState = aState; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetWindowState(nsISupports** aState) { NS_IF_ADDREF(*aState = mShared->mWindowState); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetViewerBounds(const nsIntRect& aBounds) { mShared->mViewerBounds = aBounds; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetViewerBounds(nsIntRect& aBounds) { aBounds = mShared->mViewerBounds; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetOwner(nsISupports** aOwner) { NS_IF_ADDREF(*aOwner = mShared->mOwner); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetOwner(nsISupports* aOwner) { mShared->mOwner = aOwner; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetBFCacheEntry(nsIBFCacheEntry** aEntry) { NS_ENSURE_ARG_POINTER(aEntry); NS_IF_ADDREF(*aEntry = mShared); return NS_OK; } bool nsSHEntry::HasBFCacheEntry(nsIBFCacheEntry* aEntry) { return static_cast(mShared) == aEntry; } NS_IMETHODIMP nsSHEntry::AdoptBFCacheEntry(nsISHEntry* aEntry) { nsCOMPtr shEntry = do_QueryInterface(aEntry); NS_ENSURE_STATE(shEntry); nsSHEntryShared* shared = shEntry->GetSharedState(); NS_ENSURE_STATE(shared); mShared = shared; return NS_OK; } NS_IMETHODIMP nsSHEntry::SharesDocumentWith(nsISHEntry* aEntry, bool* aOut) { NS_ENSURE_ARG_POINTER(aOut); nsCOMPtr internal = do_QueryInterface(aEntry); NS_ENSURE_STATE(internal); *aOut = mShared == internal->GetSharedState(); return NS_OK; } NS_IMETHODIMP nsSHEntry::AbandonBFCacheEntry() { mShared = nsSHEntryShared::Duplicate(mShared); return NS_OK; } NS_IMETHODIMP nsSHEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry) { *aIsSrcdocEntry = mIsSrcdocEntry; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetSrcdocData(nsAString& aSrcdocData) { aSrcdocData = mSrcdocData; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetSrcdocData(const nsAString& aSrcdocData) { mSrcdocData = aSrcdocData; mIsSrcdocEntry = true; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetBaseURI(nsIURI** aBaseURI) { *aBaseURI = mBaseURI; NS_IF_ADDREF(*aBaseURI); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetBaseURI(nsIURI* aBaseURI) { mBaseURI = aBaseURI; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetChildCount(int32_t* aCount) { *aCount = mChildren.Count(); return NS_OK; } NS_IMETHODIMP nsSHEntry::AddChild(nsISHEntry* aChild, int32_t aOffset) { if (aChild) { NS_ENSURE_SUCCESS(aChild->SetParent(this), NS_ERROR_FAILURE); } if (aOffset < 0) { mChildren.AppendObject(aChild); return NS_OK; } // // Bug 52670: Ensure children are added in order. // // Later frames in the child list may load faster and get appended // before earlier frames, causing session history to be scrambled. // By growing the list here, they are added to the right position. // // Assert that aOffset will not be so high as to grow us a lot. // NS_ASSERTION(aOffset < (mChildren.Count() + 1023), "Large frames array!\n"); bool newChildIsDyn = false; if (aChild) { aChild->IsDynamicallyAdded(&newChildIsDyn); } // If the new child is dynamically added, try to add it to aOffset, but if // there are non-dynamically added children, the child must be after those. if (newChildIsDyn) { int32_t lastNonDyn = aOffset - 1; for (int32_t i = aOffset; i < mChildren.Count(); ++i) { nsISHEntry* entry = mChildren[i]; if (entry) { bool dyn = false; entry->IsDynamicallyAdded(&dyn); if (dyn) { break; } else { lastNonDyn = i; } } } // InsertObjectAt allows only appending one object. // If aOffset is larger than Count(), we must first manually // set the capacity. if (aOffset > mChildren.Count()) { mChildren.SetCount(aOffset); } if (!mChildren.InsertObjectAt(aChild, lastNonDyn + 1)) { NS_WARNING("Adding a child failed!"); aChild->SetParent(nullptr); return NS_ERROR_FAILURE; } } else { // If the new child isn't dynamically added, it should be set to aOffset. // If there are dynamically added children before that, those must be // moved to be after aOffset. if (mChildren.Count() > 0) { int32_t start = std::min(mChildren.Count() - 1, aOffset); int32_t dynEntryIndex = -1; nsISHEntry* dynEntry = nullptr; for (int32_t i = start; i >= 0; --i) { nsISHEntry* entry = mChildren[i]; if (entry) { bool dyn = false; entry->IsDynamicallyAdded(&dyn); if (dyn) { dynEntryIndex = i; dynEntry = entry; } else { break; } } } if (dynEntry) { nsCOMArray tmp; tmp.SetCount(aOffset - dynEntryIndex + 1); mChildren.InsertObjectsAt(tmp, dynEntryIndex); NS_ASSERTION(mChildren[aOffset + 1] == dynEntry, "Whaat?"); } } // Make sure there isn't anything at aOffset. if (aOffset < mChildren.Count()) { nsISHEntry* oldChild = mChildren[aOffset]; if (oldChild && oldChild != aChild) { NS_ERROR("Adding a child where we already have a child? This may misbehave"); oldChild->SetParent(nullptr); } } if (!mChildren.ReplaceObjectAt(aChild, aOffset)) { NS_WARNING("Adding a child failed!"); aChild->SetParent(nullptr); return NS_ERROR_FAILURE; } } return NS_OK; } NS_IMETHODIMP nsSHEntry::RemoveChild(nsISHEntry* aChild) { NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE); bool childRemoved = false; bool dynamic = false; aChild->IsDynamicallyAdded(&dynamic); if (dynamic) { childRemoved = mChildren.RemoveObject(aChild); } else { int32_t index = mChildren.IndexOfObject(aChild); if (index >= 0) { childRemoved = mChildren.ReplaceObjectAt(nullptr, index); } } if (childRemoved) { aChild->SetParent(nullptr); // reduce the child count, i.e. remove empty children at the end for (int32_t i = mChildren.Count() - 1; i >= 0 && !mChildren[i]; --i) { if (!mChildren.RemoveObjectAt(i)) { break; } } } return NS_OK; } NS_IMETHODIMP nsSHEntry::GetChildAt(int32_t aIndex, nsISHEntry** aResult) { if (aIndex >= 0 && aIndex < mChildren.Count()) { *aResult = mChildren[aIndex]; // yes, mChildren can have holes in it. AddChild's offset parameter makes // that possible. NS_IF_ADDREF(*aResult); } else { *aResult = nullptr; } return NS_OK; } NS_IMETHODIMP nsSHEntry::ReplaceChild(nsISHEntry* aNewEntry) { NS_ENSURE_STATE(aNewEntry); uint64_t docshellID; aNewEntry->GetDocshellID(&docshellID); uint64_t otherID; for (int32_t i = 0; i < mChildren.Count(); ++i) { if (mChildren[i] && NS_SUCCEEDED(mChildren[i]->GetDocshellID(&otherID)) && docshellID == otherID) { mChildren[i]->SetParent(nullptr); if (mChildren.ReplaceObjectAt(aNewEntry, i)) { return aNewEntry->SetParent(this); } } } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsSHEntry::AddChildShell(nsIDocShellTreeItem* aShell) { NS_ASSERTION(aShell, "Null child shell added to history entry"); mShared->mChildShells.AppendObject(aShell); return NS_OK; } NS_IMETHODIMP nsSHEntry::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell) { NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex)); return NS_OK; } NS_IMETHODIMP nsSHEntry::ClearChildShells() { mShared->mChildShells.Clear(); return NS_OK; } NS_IMETHODIMP nsSHEntry::GetRefreshURIList(nsISupportsArray** aList) { NS_IF_ADDREF(*aList = mShared->mRefreshURIList); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetRefreshURIList(nsISupportsArray* aList) { mShared->mRefreshURIList = aList; return NS_OK; } NS_IMETHODIMP nsSHEntry::SyncPresentationState() { return mShared->SyncPresentationState(); } void nsSHEntry::RemoveFromBFCacheSync() { mShared->RemoveFromBFCacheSync(); } void nsSHEntry::RemoveFromBFCacheAsync() { mShared->RemoveFromBFCacheAsync(); } nsDocShellEditorData* nsSHEntry::ForgetEditorData() { // XXX jlebar Check how this is used. return mShared->mEditorData.forget(); } void nsSHEntry::SetEditorData(nsDocShellEditorData* aData) { NS_ASSERTION(!(aData && mShared->mEditorData), "We're going to overwrite an owning ref!"); if (mShared->mEditorData != aData) { mShared->mEditorData = aData; } } bool nsSHEntry::HasDetachedEditor() { return mShared->mEditorData != nullptr; } NS_IMETHODIMP nsSHEntry::GetStateData(nsIStructuredCloneContainer** aContainer) { NS_ENSURE_ARG_POINTER(aContainer); NS_IF_ADDREF(*aContainer = mStateData); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetStateData(nsIStructuredCloneContainer* aContainer) { mStateData = aContainer; return NS_OK; } NS_IMETHODIMP nsSHEntry::IsDynamicallyAdded(bool* aAdded) { *aAdded = mShared->mDynamicallyCreated; return NS_OK; } NS_IMETHODIMP nsSHEntry::HasDynamicallyAddedChild(bool* aAdded) { *aAdded = false; for (int32_t i = 0; i < mChildren.Count(); ++i) { nsISHEntry* entry = mChildren[i]; if (entry) { entry->IsDynamicallyAdded(aAdded); if (*aAdded) { break; } } } return NS_OK; } NS_IMETHODIMP nsSHEntry::GetDocshellID(uint64_t* aID) { *aID = mShared->mDocShellID; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetDocshellID(uint64_t aID) { mShared->mDocShellID = aID; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetLastTouched(uint32_t* aLastTouched) { *aLastTouched = mShared->mLastTouched; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetLastTouched(uint32_t aLastTouched) { mShared->mLastTouched = aLastTouched; return NS_OK; }