From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- docshell/shistory/ChildSHistory.cpp | 291 ++++ docshell/shistory/ChildSHistory.h | 149 ++ docshell/shistory/SessionHistoryEntry.cpp | 1836 ++++++++++++++++++++++ docshell/shistory/SessionHistoryEntry.h | 495 ++++++ docshell/shistory/moz.build | 42 + docshell/shistory/nsIBFCacheEntry.idl | 16 + docshell/shistory/nsISHEntry.idl | 475 ++++++ docshell/shistory/nsISHistory.idl | 291 ++++ docshell/shistory/nsISHistoryListener.idl | 88 ++ docshell/shistory/nsSHEntry.cpp | 1131 ++++++++++++++ docshell/shistory/nsSHEntry.h | 72 + docshell/shistory/nsSHEntryShared.cpp | 343 ++++ docshell/shistory/nsSHEntryShared.h | 219 +++ docshell/shistory/nsSHistory.cpp | 2408 +++++++++++++++++++++++++++++ docshell/shistory/nsSHistory.h | 343 ++++ 15 files changed, 8199 insertions(+) create mode 100644 docshell/shistory/ChildSHistory.cpp create mode 100644 docshell/shistory/ChildSHistory.h create mode 100644 docshell/shistory/SessionHistoryEntry.cpp create mode 100644 docshell/shistory/SessionHistoryEntry.h create mode 100644 docshell/shistory/moz.build create mode 100644 docshell/shistory/nsIBFCacheEntry.idl create mode 100644 docshell/shistory/nsISHEntry.idl create mode 100644 docshell/shistory/nsISHistory.idl create mode 100644 docshell/shistory/nsISHistoryListener.idl create mode 100644 docshell/shistory/nsSHEntry.cpp create mode 100644 docshell/shistory/nsSHEntry.h create mode 100644 docshell/shistory/nsSHEntryShared.cpp create mode 100644 docshell/shistory/nsSHEntryShared.h create mode 100644 docshell/shistory/nsSHistory.cpp create mode 100644 docshell/shistory/nsSHistory.h (limited to 'docshell/shistory') diff --git a/docshell/shistory/ChildSHistory.cpp b/docshell/shistory/ChildSHistory.cpp new file mode 100644 index 0000000000..85708d6278 --- /dev/null +++ b/docshell/shistory/ChildSHistory.cpp @@ -0,0 +1,291 @@ +/* -*- 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 "mozilla/dom/ChildSHistory.h" +#include "mozilla/dom/ChildSHistoryBinding.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentFrameMessageManager.h" +#include "nsIXULRuntime.h" +#include "nsComponentManagerUtils.h" +#include "nsSHEntry.h" +#include "nsSHistory.h" +#include "nsDocShell.h" +#include "nsXULAppAPI.h" + +extern mozilla::LazyLogModule gSHLog; + +namespace mozilla { +namespace dom { + +ChildSHistory::ChildSHistory(BrowsingContext* aBrowsingContext) + : mBrowsingContext(aBrowsingContext) {} + +ChildSHistory::~ChildSHistory() { + if (mHistory) { + static_cast(mHistory.get())->SetBrowsingContext(nullptr); + } +} + +void ChildSHistory::SetBrowsingContext(BrowsingContext* aBrowsingContext) { + mBrowsingContext = aBrowsingContext; +} + +void ChildSHistory::SetIsInProcess(bool aIsInProcess) { + if (!aIsInProcess) { + MOZ_ASSERT_IF(mozilla::SessionHistoryInParent(), !mHistory); + if (!mozilla::SessionHistoryInParent()) { + RemovePendingHistoryNavigations(); + if (mHistory) { + static_cast(mHistory.get())->SetBrowsingContext(nullptr); + mHistory = nullptr; + } + } + + return; + } + + if (mHistory || mozilla::SessionHistoryInParent()) { + return; + } + + mHistory = new nsSHistory(mBrowsingContext); +} + +int32_t ChildSHistory::Count() { + if (mozilla::SessionHistoryInParent()) { + uint32_t length = mLength; + for (uint32_t i = 0; i < mPendingSHistoryChanges.Length(); ++i) { + length += mPendingSHistoryChanges[i].mLengthDelta; + } + + return length; + } + return mHistory->GetCount(); +} + +int32_t ChildSHistory::Index() { + if (mozilla::SessionHistoryInParent()) { + uint32_t index = mIndex; + for (uint32_t i = 0; i < mPendingSHistoryChanges.Length(); ++i) { + index += mPendingSHistoryChanges[i].mIndexDelta; + } + + return index; + } + int32_t index; + mHistory->GetIndex(&index); + return index; +} + +nsID ChildSHistory::AddPendingHistoryChange() { + int32_t indexDelta = 1; + int32_t lengthDelta = (Index() + indexDelta) - (Count() - 1); + return AddPendingHistoryChange(indexDelta, lengthDelta); +} + +nsID ChildSHistory::AddPendingHistoryChange(int32_t aIndexDelta, + int32_t aLengthDelta) { + nsID changeID = nsID::GenerateUUID(); + PendingSHistoryChange change = {changeID, aIndexDelta, aLengthDelta}; + mPendingSHistoryChanges.AppendElement(change); + return changeID; +} + +void ChildSHistory::SetIndexAndLength(uint32_t aIndex, uint32_t aLength, + const nsID& aChangeID) { + mIndex = aIndex; + mLength = aLength; + mPendingSHistoryChanges.RemoveElementsBy( + [aChangeID](const PendingSHistoryChange& aChange) { + return aChange.mChangeID == aChangeID; + }); +} + +void ChildSHistory::Reload(uint32_t aReloadFlags, ErrorResult& aRv) { + if (mozilla::SessionHistoryInParent()) { + if (XRE_IsParentProcess()) { + nsISHistory* shistory = + mBrowsingContext->Canonical()->GetSessionHistory(); + if (shistory) { + aRv = shistory->Reload(aReloadFlags); + } + } else { + ContentChild::GetSingleton()->SendHistoryReload(mBrowsingContext, + aReloadFlags); + } + + return; + } + aRv = mHistory->Reload(aReloadFlags); +} + +bool ChildSHistory::CanGo(int32_t aOffset) { + CheckedInt index = Index(); + index += aOffset; + if (!index.isValid()) { + return false; + } + return index.value() < Count() && index.value() >= 0; +} + +void ChildSHistory::Go(int32_t aOffset, bool aRequireUserInteraction, + bool aUserActivation, ErrorResult& aRv) { + CheckedInt index = Index(); + MOZ_LOG( + gSHLog, LogLevel::Debug, + ("ChildSHistory::Go(%d), current index = %d", aOffset, index.value())); + if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) { + NS_ERROR( + "aRequireUserInteraction may only be used with an offset of -1 or 1"); + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + + while (true) { + index += aOffset; + if (!index.isValid()) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + // Check for user interaction if desired, except for the first and last + // history entries. We compare with >= to account for the case where + // aOffset >= Count(). + if (!aRequireUserInteraction || index.value() >= Count() - 1 || + index.value() <= 0) { + break; + } + if (mHistory && mHistory->HasUserInteractionAtIndex(index.value())) { + break; + } + } + + GotoIndex(index.value(), aOffset, aRequireUserInteraction, aUserActivation, + aRv); +} + +void ChildSHistory::AsyncGo(int32_t aOffset, bool aRequireUserInteraction, + bool aUserActivation, CallerType aCallerType, + ErrorResult& aRv) { + CheckedInt index = Index(); + MOZ_LOG(gSHLog, LogLevel::Debug, + ("ChildSHistory::AsyncGo(%d), current index = %d", aOffset, + index.value())); + nsresult rv = mBrowsingContext->CheckLocationChangeRateLimit(aCallerType); + if (NS_FAILED(rv)) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("Rejected")); + aRv.Throw(rv); + return; + } + + RefPtr asyncNav = + new PendingAsyncHistoryNavigation(this, aOffset, aRequireUserInteraction, + aUserActivation); + mPendingNavigations.insertBack(asyncNav); + NS_DispatchToCurrentThread(asyncNav.forget()); +} + +void ChildSHistory::GotoIndex(int32_t aIndex, int32_t aOffset, + bool aRequireUserInteraction, + bool aUserActivation, ErrorResult& aRv) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("ChildSHistory::GotoIndex(%d, %d), epoch %" PRIu64, aIndex, aOffset, + mHistoryEpoch)); + if (mozilla::SessionHistoryInParent()) { + if (!mPendingEpoch) { + mPendingEpoch = true; + RefPtr self(this); + NS_DispatchToCurrentThread( + NS_NewRunnableFunction("UpdateEpochRunnable", [self] { + self->mHistoryEpoch++; + self->mPendingEpoch = false; + })); + } + + nsCOMPtr shistory = mHistory; + mBrowsingContext->HistoryGo( + aOffset, mHistoryEpoch, aRequireUserInteraction, aUserActivation, + [shistory](Maybe&& aRequestedIndex) { + // FIXME Should probably only do this for non-fission. + if (aRequestedIndex.isSome() && shistory) { + shistory->InternalSetRequestedIndex(aRequestedIndex.value()); + } + }); + } else { + aRv = mHistory->GotoIndex(aIndex, aUserActivation); + } +} + +void ChildSHistory::RemovePendingHistoryNavigations() { + // Per the spec, this generally shouldn't remove all navigations - it + // depends if they're in the same document family or not. We don't do + // that. Also with SessionHistoryInParent, this can only abort AsyncGo's + // that have not yet been sent to the parent - see discussion of point + // 2.2 in comments in nsDocShell::UpdateURLAndHistory() + MOZ_LOG(gSHLog, LogLevel::Debug, + ("ChildSHistory::RemovePendingHistoryNavigations: %zu", + mPendingNavigations.length())); + mPendingNavigations.clear(); +} + +void ChildSHistory::EvictLocalContentViewers() { + if (!mozilla::SessionHistoryInParent()) { + mHistory->EvictAllContentViewers(); + } +} + +nsISHistory* ChildSHistory::GetLegacySHistory(ErrorResult& aError) { + if (mozilla::SessionHistoryInParent()) { + aError.ThrowTypeError( + "legacySHistory is not available with session history in the parent."); + return nullptr; + } + + MOZ_RELEASE_ASSERT(mHistory); + return mHistory; +} + +nsISHistory* ChildSHistory::LegacySHistory() { + IgnoredErrorResult ignore; + nsISHistory* shistory = GetLegacySHistory(ignore); + MOZ_RELEASE_ASSERT(shistory); + return shistory; +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChildSHistory) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(ChildSHistory) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ChildSHistory) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ChildSHistory) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ChildSHistory) + if (tmp->mHistory) { + static_cast(tmp->mHistory.get())->SetBrowsingContext(nullptr); + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext, mHistory) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ChildSHistory) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext, mHistory) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +JSObject* ChildSHistory::WrapObject(JSContext* cx, + JS::Handle aGivenProto) { + return ChildSHistory_Binding::Wrap(cx, this, aGivenProto); +} + +nsISupports* ChildSHistory::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +} // namespace dom +} // namespace mozilla diff --git a/docshell/shistory/ChildSHistory.h b/docshell/shistory/ChildSHistory.h new file mode 100644 index 0000000000..031c91c7da --- /dev/null +++ b/docshell/shistory/ChildSHistory.h @@ -0,0 +1,149 @@ +/* -*- 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/. */ + +/** + * ChildSHistory represents a view of session history from a child process. It + * exposes getters for some cached history state, and mutators which are + * implemented by communicating with the actual history storage. + * + * NOTE: Currently session history is in transition, meaning that we're still + * using the legacy nsSHistory class internally. The API exposed from this class + * should be only the API which we expect to expose when this transition is + * complete, and special cases will need to call through the LegacySHistory() + * getters. + */ + +#ifndef mozilla_dom_ChildSHistory_h +#define mozilla_dom_ChildSHistory_h + +#include "nsCOMPtr.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsWrapperCache.h" +#include "nsThreadUtils.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/LinkedList.h" +#include "nsID.h" + +class nsISHEntry; +class nsISHistory; + +namespace mozilla::dom { + +class BrowsingContext; + +class ChildSHistory : public nsISupports, public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ChildSHistory) + nsISupports* GetParentObject() const; + JSObject* WrapObject(JSContext* cx, + JS::Handle aGivenProto) override; + + explicit ChildSHistory(BrowsingContext* aBrowsingContext); + + void SetBrowsingContext(BrowsingContext* aBrowsingContext); + + // Create or destroy the session history implementation in the child process. + // This can be removed once session history is stored exclusively in the + // parent process. + void SetIsInProcess(bool aIsInProcess); + bool IsInProcess() { return !!mHistory; } + + int32_t Count(); + int32_t Index(); + + /** + * Reload the current entry in the session history. + */ + void Reload(uint32_t aReloadFlags, ErrorResult& aRv); + + /** + * The CanGo and Go methods are called with an offset from the current index. + * Positive numbers go forward in history, while negative numbers go + * backwards. + */ + bool CanGo(int32_t aOffset); + void Go(int32_t aOffset, bool aRequireUserInteraction, bool aUserActivation, + ErrorResult& aRv); + void AsyncGo(int32_t aOffset, bool aRequireUserInteraction, + bool aUserActivation, CallerType aCallerType, ErrorResult& aRv); + + // aIndex is the new index, and aOffset is the offset between new and current. + void GotoIndex(int32_t aIndex, int32_t aOffset, bool aRequireUserInteraction, + bool aUserActivation, ErrorResult& aRv); + + void RemovePendingHistoryNavigations(); + + /** + * Evicts all content viewers within the current process. + */ + void EvictLocalContentViewers(); + + // GetLegacySHistory and LegacySHistory have been deprecated. Don't + // use these, but instead handle the interaction with nsISHistory in + // the parent process. + nsISHistory* GetLegacySHistory(ErrorResult& aError); + nsISHistory* LegacySHistory(); + + void SetIndexAndLength(uint32_t aIndex, uint32_t aLength, + const nsID& aChangeId); + nsID AddPendingHistoryChange(); + nsID AddPendingHistoryChange(int32_t aIndexDelta, int32_t aLengthDelta); + + private: + virtual ~ChildSHistory(); + + class PendingAsyncHistoryNavigation + : public Runnable, + public mozilla::LinkedListElement { + public: + PendingAsyncHistoryNavigation(ChildSHistory* aHistory, int32_t aOffset, + bool aRequireUserInteraction, + bool aUserActivation) + : Runnable("PendingAsyncHistoryNavigation"), + mHistory(aHistory), + mRequireUserInteraction(aRequireUserInteraction), + mUserActivation(aUserActivation), + mOffset(aOffset) {} + + NS_IMETHOD Run() override { + if (isInList()) { + remove(); + mHistory->Go(mOffset, mRequireUserInteraction, mUserActivation, + IgnoreErrors()); + } + return NS_OK; + } + + private: + RefPtr mHistory; + bool mRequireUserInteraction; + bool mUserActivation; + int32_t mOffset; + }; + + RefPtr mBrowsingContext; + nsCOMPtr mHistory; + // Can be removed once history-in-parent is the only way + mozilla::LinkedList mPendingNavigations; + int32_t mIndex = -1; + int32_t mLength = 0; + + struct PendingSHistoryChange { + nsID mChangeID; + int32_t mIndexDelta; + int32_t mLengthDelta; + }; + AutoTArray mPendingSHistoryChanges; + + // Needs to start 1 above default epoch in parent + uint64_t mHistoryEpoch = 1; + bool mPendingEpoch = false; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_ChildSHistory_h */ diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp new file mode 100644 index 0000000000..97df3a9e73 --- /dev/null +++ b/docshell/shistory/SessionHistoryEntry.cpp @@ -0,0 +1,1836 @@ +/* -*- 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 "SessionHistoryEntry.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsFrameLoader.h" +#include "nsIFormPOSTActionChannel.h" +#include "nsIHttpChannel.h" +#include "nsIUploadChannel2.h" +#include "nsIXULRuntime.h" +#include "nsSHEntryShared.h" +#include "nsSHistory.h" +#include "nsStreamUtils.h" +#include "nsStructuredCloneContainer.h" +#include "nsXULAppAPI.h" +#include "mozilla/PresState.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/Tuple.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/CSPMessageUtils.h" +#include "mozilla/dom/DocumentBinding.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/ReferrerInfoUtils.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ipc/URIUtils.h" + +extern mozilla::LazyLogModule gSHLog; + +namespace mozilla { +namespace dom { + +SessionHistoryInfo::SessionHistoryInfo(nsDocShellLoadState* aLoadState, + nsIChannel* aChannel) + : mURI(aLoadState->URI()), + mOriginalURI(aLoadState->OriginalURI()), + mResultPrincipalURI(aLoadState->ResultPrincipalURI()), + mUnstrippedURI(aLoadState->GetUnstrippedURI()), + mLoadType(aLoadState->LoadType()), + mSrcdocData(aLoadState->SrcdocData().IsVoid() + ? Nothing() + : Some(aLoadState->SrcdocData())), + mBaseURI(aLoadState->BaseURI()), + mLoadReplace(aLoadState->LoadReplace()), + mHasUserInteraction(false), + mHasUserActivation(aLoadState->HasValidUserGestureActivation()), + mSharedState(SharedState::Create( + aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(), + aLoadState->PartitionedPrincipalToInherit(), aLoadState->Csp(), + /* FIXME Is this correct? */ + aLoadState->TypeHint())) { + // Pull the upload stream off of the channel instead of the load state, as + // ownership has already been transferred from the load state to the channel. + if (nsCOMPtr postChannel = do_QueryInterface(aChannel)) { + int64_t contentLength; + MOZ_ALWAYS_SUCCEEDS(postChannel->CloneUploadStream( + &contentLength, getter_AddRefs(mPostData))); + MOZ_ASSERT_IF(mPostData, NS_InputStreamIsCloneable(mPostData)); + } + + if (nsCOMPtr httpChannel = do_QueryInterface(aChannel)) { + mReferrerInfo = httpChannel->GetReferrerInfo(); + } + + MaybeUpdateTitleFromURI(); +} + +SessionHistoryInfo::SessionHistoryInfo( + const SessionHistoryInfo& aSharedStateFrom, nsIURI* aURI) + : mURI(aURI), mSharedState(aSharedStateFrom.mSharedState) { + MaybeUpdateTitleFromURI(); +} + +SessionHistoryInfo::SessionHistoryInfo( + nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, const nsACString& aContentType) + : mURI(aURI), + mSharedState(SharedState::Create( + aTriggeringPrincipal, aPrincipalToInherit, + aPartitionedPrincipalToInherit, aCsp, aContentType)) { + MaybeUpdateTitleFromURI(); +} + +SessionHistoryInfo::SessionHistoryInfo( + nsIChannel* aChannel, uint32_t aLoadType, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp) { + aChannel->GetURI(getter_AddRefs(mURI)); + mLoadType = aLoadType; + + nsCOMPtr loadInfo; + aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); + + loadInfo->GetResultPrincipalURI(getter_AddRefs(mResultPrincipalURI)); + loadInfo->GetUnstrippedURI(getter_AddRefs(mUnstrippedURI)); + loadInfo->GetTriggeringPrincipal( + getter_AddRefs(mSharedState.Get()->mTriggeringPrincipal)); + loadInfo->GetPrincipalToInherit( + getter_AddRefs(mSharedState.Get()->mPrincipalToInherit)); + + mSharedState.Get()->mPartitionedPrincipalToInherit = + aPartitionedPrincipalToInherit; + mSharedState.Get()->mCsp = aCsp; + aChannel->GetContentType(mSharedState.Get()->mContentType); + aChannel->GetOriginalURI(getter_AddRefs(mOriginalURI)); + + uint32_t loadFlags; + aChannel->GetLoadFlags(&loadFlags); + mLoadReplace = !!(loadFlags & nsIChannel::LOAD_REPLACE); + + MaybeUpdateTitleFromURI(); + + if (nsCOMPtr httpChannel = do_QueryInterface(aChannel)) { + mReferrerInfo = httpChannel->GetReferrerInfo(); + } +} + +void SessionHistoryInfo::Reset(nsIURI* aURI, const nsID& aDocShellID, + bool aDynamicCreation, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, + const nsACString& aContentType) { + mURI = aURI; + mOriginalURI = nullptr; + mResultPrincipalURI = nullptr; + mUnstrippedURI = nullptr; + mReferrerInfo = nullptr; + // Default title is the URL. + nsAutoCString spec; + if (NS_SUCCEEDED(mURI->GetSpec(spec))) { + CopyUTF8toUTF16(spec, mTitle); + } + mPostData = nullptr; + mLoadType = 0; + mScrollPositionX = 0; + mScrollPositionY = 0; + mStateData = nullptr; + mSrcdocData = Nothing(); + mBaseURI = nullptr; + mLoadReplace = false; + mURIWasModified = false; + mScrollRestorationIsManual = false; + mPersist = false; + mHasUserInteraction = false; + mHasUserActivation = false; + + mSharedState.Get()->mTriggeringPrincipal = aTriggeringPrincipal; + mSharedState.Get()->mPrincipalToInherit = aPrincipalToInherit; + mSharedState.Get()->mPartitionedPrincipalToInherit = + aPartitionedPrincipalToInherit; + mSharedState.Get()->mCsp = aCsp; + mSharedState.Get()->mContentType = aContentType; + mSharedState.Get()->mLayoutHistoryState = nullptr; +} + +void SessionHistoryInfo::MaybeUpdateTitleFromURI() { + if (mTitle.IsEmpty() && mURI) { + // Default title is the URL. + nsAutoCString spec; + if (NS_SUCCEEDED(mURI->GetSpec(spec))) { + AppendUTF8toUTF16(spec, mTitle); + } + } +} + +already_AddRefed SessionHistoryInfo::GetPostData() const { + // Return a clone of our post data stream. Our caller will either be + // transferring this stream to a different SessionHistoryInfo, or passing it + // off to necko/another process which will consume it, and we want to preserve + // our local instance. + nsCOMPtr postData; + if (mPostData) { + MOZ_ALWAYS_SUCCEEDS( + NS_CloneInputStream(mPostData, getter_AddRefs(postData))); + } + return postData.forget(); +} + +void SessionHistoryInfo::SetPostData(nsIInputStream* aPostData) { + MOZ_ASSERT_IF(aPostData, NS_InputStreamIsCloneable(aPostData)); + mPostData = aPostData; +} + +uint64_t SessionHistoryInfo::SharedId() const { + return mSharedState.Get()->mId; +} + +nsILayoutHistoryState* SessionHistoryInfo::GetLayoutHistoryState() { + return mSharedState.Get()->mLayoutHistoryState; +} + +void SessionHistoryInfo::SetLayoutHistoryState(nsILayoutHistoryState* aState) { + mSharedState.Get()->mLayoutHistoryState = aState; + if (mSharedState.Get()->mLayoutHistoryState) { + mSharedState.Get()->mLayoutHistoryState->SetScrollPositionOnly( + !mSharedState.Get()->mSaveLayoutState); + } +} + +nsIPrincipal* SessionHistoryInfo::GetTriggeringPrincipal() const { + return mSharedState.Get()->mTriggeringPrincipal; +} + +nsIPrincipal* SessionHistoryInfo::GetPrincipalToInherit() const { + return mSharedState.Get()->mPrincipalToInherit; +} + +nsIPrincipal* SessionHistoryInfo::GetPartitionedPrincipalToInherit() const { + return mSharedState.Get()->mPartitionedPrincipalToInherit; +} + +nsIContentSecurityPolicy* SessionHistoryInfo::GetCsp() const { + return mSharedState.Get()->mCsp; +} + +uint32_t SessionHistoryInfo::GetCacheKey() const { + return mSharedState.Get()->mCacheKey; +} + +void SessionHistoryInfo::SetCacheKey(uint32_t aCacheKey) { + mSharedState.Get()->mCacheKey = aCacheKey; +} + +bool SessionHistoryInfo::IsSubFrame() const { + return mSharedState.Get()->mIsFrameNavigation; +} + +void SessionHistoryInfo::SetSaveLayoutStateFlag(bool aSaveLayoutStateFlag) { + MOZ_ASSERT(XRE_IsParentProcess()); + static_cast(mSharedState.Get())->mSaveLayoutState = + aSaveLayoutStateFlag; +} + +void SessionHistoryInfo::FillLoadInfo(nsDocShellLoadState& aLoadState) const { + aLoadState.SetOriginalURI(mOriginalURI); + aLoadState.SetMaybeResultPrincipalURI(Some(mResultPrincipalURI)); + aLoadState.SetUnstrippedURI(mUnstrippedURI); + aLoadState.SetLoadReplace(mLoadReplace); + nsCOMPtr postData = GetPostData(); + aLoadState.SetPostDataStream(postData); + aLoadState.SetReferrerInfo(mReferrerInfo); + + aLoadState.SetTypeHint(mSharedState.Get()->mContentType); + aLoadState.SetTriggeringPrincipal(mSharedState.Get()->mTriggeringPrincipal); + aLoadState.SetPrincipalToInherit(mSharedState.Get()->mPrincipalToInherit); + aLoadState.SetPartitionedPrincipalToInherit( + mSharedState.Get()->mPartitionedPrincipalToInherit); + aLoadState.SetCsp(mSharedState.Get()->mCsp); + + // Do not inherit principal from document (security-critical!); + uint32_t flags = nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_NONE; + + // Passing nullptr as aSourceDocShell gives the same behaviour as before + // aSourceDocShell was introduced. According to spec we should be passing + // the source browsing context that was used when the history entry was + // first created. bug 947716 has been created to address this issue. + nsAutoString srcdoc; + nsCOMPtr baseURI; + if (mSrcdocData) { + srcdoc = mSrcdocData.value(); + baseURI = mBaseURI; + flags |= nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC; + } else { + srcdoc = VoidString(); + } + aLoadState.SetSrcdocData(srcdoc); + aLoadState.SetBaseURI(baseURI); + aLoadState.SetInternalLoadFlags(flags); + + aLoadState.SetFirstParty(true); + + // When we create a load state from the history info we already know if + // https-first was able to upgrade the request from http to https. There is no + // point in re-retrying to upgrade. + aLoadState.SetIsExemptFromHTTPSOnlyMode(true); +} +/* static */ +SessionHistoryInfo::SharedState SessionHistoryInfo::SharedState::Create( + nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, const nsACString& aContentType) { + if (XRE_IsParentProcess()) { + return SharedState(new SHEntrySharedParentState( + aTriggeringPrincipal, aPrincipalToInherit, + aPartitionedPrincipalToInherit, aCsp, aContentType)); + } + + return SharedState(MakeUnique( + aTriggeringPrincipal, aPrincipalToInherit, aPartitionedPrincipalToInherit, + aCsp, aContentType)); +} + +SessionHistoryInfo::SharedState::SharedState() { Init(); } + +SessionHistoryInfo::SharedState::SharedState( + const SessionHistoryInfo::SharedState& aOther) { + Init(aOther); +} + +SessionHistoryInfo::SharedState::SharedState( + const Maybe& aOther) { + if (aOther.isSome()) { + Init(aOther.ref()); + } else { + Init(); + } +} + +SessionHistoryInfo::SharedState::~SharedState() { + if (XRE_IsParentProcess()) { + mParent + .RefPtr::~RefPtr(); + } else { + mChild.UniquePtr::~UniquePtr(); + } +} + +SessionHistoryInfo::SharedState& SessionHistoryInfo::SharedState::operator=( + const SessionHistoryInfo::SharedState& aOther) { + if (this != &aOther) { + if (XRE_IsParentProcess()) { + mParent = aOther.mParent; + } else { + mChild = MakeUnique(*aOther.mChild); + } + } + return *this; +} + +SHEntrySharedState* SessionHistoryInfo::SharedState::Get() const { + if (XRE_IsParentProcess()) { + return mParent; + } + + return mChild.get(); +} + +void SessionHistoryInfo::SharedState::ChangeId(uint64_t aId) { + if (XRE_IsParentProcess()) { + mParent->ChangeId(aId); + } else { + mChild->mId = aId; + } +} + +void SessionHistoryInfo::SharedState::Init() { + if (XRE_IsParentProcess()) { + new (&mParent) + RefPtr(new SHEntrySharedParentState()); + } else { + new (&mChild) + UniquePtr(MakeUnique()); + } +} + +void SessionHistoryInfo::SharedState::Init( + const SessionHistoryInfo::SharedState& aOther) { + if (XRE_IsParentProcess()) { + new (&mParent) RefPtr(aOther.mParent); + } else { + new (&mChild) UniquePtr( + MakeUnique(*aOther.mChild)); + } +} + +static uint64_t gLoadingSessionHistoryInfoLoadId = 0; + +nsTHashMap* + SessionHistoryEntry::sLoadIdToEntry = nullptr; + +LoadingSessionHistoryInfo::LoadingSessionHistoryInfo( + SessionHistoryEntry* aEntry) + : mInfo(aEntry->Info()), mLoadId(++gLoadingSessionHistoryInfoLoadId) { + SessionHistoryEntry::SetByLoadId(mLoadId, aEntry); +} + +LoadingSessionHistoryInfo::LoadingSessionHistoryInfo( + SessionHistoryEntry* aEntry, const LoadingSessionHistoryInfo* aInfo) + : mInfo(aEntry->Info()), + mLoadId(aInfo->mLoadId), + mLoadIsFromSessionHistory(aInfo->mLoadIsFromSessionHistory), + mOffset(aInfo->mOffset), + mLoadingCurrentEntry(aInfo->mLoadingCurrentEntry) { + MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(mLoadId) == aEntry); +} + +LoadingSessionHistoryInfo::LoadingSessionHistoryInfo( + const SessionHistoryInfo& aInfo) + : mInfo(aInfo), mLoadId(UINT64_MAX) {} + +already_AddRefed +LoadingSessionHistoryInfo::CreateLoadInfo() const { + RefPtr loadState( + new nsDocShellLoadState(mInfo.GetURI())); + + mInfo.FillLoadInfo(*loadState); + + loadState->SetLoadingSessionHistoryInfo(*this); + + return loadState.forget(); +} + +static uint32_t gEntryID; + +SessionHistoryEntry* SessionHistoryEntry::GetByLoadId(uint64_t aLoadId) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!sLoadIdToEntry) { + return nullptr; + } + + if (auto entry = sLoadIdToEntry->Lookup(aLoadId)) { + return entry->mEntry; + } + return nullptr; +} + +const SessionHistoryInfo* +SessionHistoryEntry::GetInfoSnapshotForValidationByLoadId(uint64_t aLoadId) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!sLoadIdToEntry) { + return nullptr; + } + + if (auto entry = sLoadIdToEntry->Lookup(aLoadId)) { + return entry->mInfoSnapshotForValidation.get(); + } + return nullptr; +} + +void SessionHistoryEntry::SetByLoadId(uint64_t aLoadId, + SessionHistoryEntry* aEntry) { + if (!sLoadIdToEntry) { + sLoadIdToEntry = new nsTHashMap(); + } + + MOZ_LOG( + gSHLog, LogLevel::Verbose, + ("SessionHistoryEntry::SetByLoadId(%" PRIu64 " - %p)", aLoadId, aEntry)); + sLoadIdToEntry->InsertOrUpdate( + aLoadId, LoadingEntry{ + .mEntry = aEntry, + .mInfoSnapshotForValidation = + MakeUnique(aEntry->Info()), + }); +} + +void SessionHistoryEntry::RemoveLoadId(uint64_t aLoadId) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!sLoadIdToEntry) { + return; + } + + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("SHEntry::RemoveLoadId(%" PRIu64 ")", aLoadId)); + sLoadIdToEntry->Remove(aLoadId); +} + +SessionHistoryEntry::SessionHistoryEntry() + : mInfo(new SessionHistoryInfo()), mID(++gEntryID) { + MOZ_ASSERT(mozilla::SessionHistoryInParent()); +} + +SessionHistoryEntry::SessionHistoryEntry(nsDocShellLoadState* aLoadState, + nsIChannel* aChannel) + : mInfo(new SessionHistoryInfo(aLoadState, aChannel)), mID(++gEntryID) { + MOZ_ASSERT(mozilla::SessionHistoryInParent()); +} + +SessionHistoryEntry::SessionHistoryEntry(SessionHistoryInfo* aInfo) + : mInfo(MakeUnique(*aInfo)), mID(++gEntryID) { + MOZ_ASSERT(mozilla::SessionHistoryInParent()); +} + +SessionHistoryEntry::SessionHistoryEntry(const SessionHistoryEntry& aEntry) + : mInfo(MakeUnique(*aEntry.mInfo)), + mParent(aEntry.mParent), + mID(aEntry.mID), + mBCHistoryLength(aEntry.mBCHistoryLength) { + MOZ_ASSERT(mozilla::SessionHistoryInParent()); +} + +SessionHistoryEntry::~SessionHistoryEntry() { + // Null out the mParent pointers on all our kids. + for (nsISHEntry* entry : mChildren) { + if (entry) { + entry->SetParent(nullptr); + } + } + + if (sLoadIdToEntry) { + sLoadIdToEntry->RemoveIf( + [this](auto& aIter) { return aIter.Data().mEntry == this; }); + if (sLoadIdToEntry->IsEmpty()) { + delete sLoadIdToEntry; + sLoadIdToEntry = nullptr; + } + } +} + +NS_IMPL_ISUPPORTS(SessionHistoryEntry, nsISHEntry, SessionHistoryEntry) + +NS_IMETHODIMP +SessionHistoryEntry::GetURI(nsIURI** aURI) { + nsCOMPtr uri = mInfo->mURI; + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetURI(nsIURI* aURI) { + mInfo->mURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetOriginalURI(nsIURI** aOriginalURI) { + nsCOMPtr originalURI = mInfo->mOriginalURI; + originalURI.forget(aOriginalURI); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetOriginalURI(nsIURI* aOriginalURI) { + mInfo->mOriginalURI = aOriginalURI; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetResultPrincipalURI(nsIURI** aResultPrincipalURI) { + nsCOMPtr resultPrincipalURI = mInfo->mResultPrincipalURI; + resultPrincipalURI.forget(aResultPrincipalURI); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) { + mInfo->mResultPrincipalURI = aResultPrincipalURI; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetUnstrippedURI(nsIURI** aUnstrippedURI) { + nsCOMPtr unstrippedURI = mInfo->mUnstrippedURI; + unstrippedURI.forget(aUnstrippedURI); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetUnstrippedURI(nsIURI* aUnstrippedURI) { + mInfo->mUnstrippedURI = aUnstrippedURI; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetLoadReplace(bool* aLoadReplace) { + *aLoadReplace = mInfo->mLoadReplace; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetLoadReplace(bool aLoadReplace) { + mInfo->mLoadReplace = aLoadReplace; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetTitle(nsAString& aTitle) { + aTitle = mInfo->mTitle; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetTitle(const nsAString& aTitle) { + mInfo->SetTitle(aTitle); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetName(nsAString& aName) { + aName = mInfo->mName; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetName(const nsAString& aName) { + mInfo->mName = aName; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetIsSubFrame(bool* aIsSubFrame) { + *aIsSubFrame = SharedInfo()->mIsFrameNavigation; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetIsSubFrame(bool aIsSubFrame) { + SharedInfo()->mIsFrameNavigation = aIsSubFrame; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetHasUserInteraction(bool* aFlag) { + // The back button and menulist deal with root/top-level + // session history entries, thus we annotate only the root entry. + if (!mParent) { + *aFlag = mInfo->mHasUserInteraction; + } else { + nsCOMPtr root = nsSHistory::GetRootSHEntry(this); + root->GetHasUserInteraction(aFlag); + } + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetHasUserInteraction(bool aFlag) { + // The back button and menulist deal with root/top-level + // session history entries, thus we annotate only the root entry. + if (!mParent) { + mInfo->mHasUserInteraction = aFlag; + } else { + nsCOMPtr root = nsSHistory::GetRootSHEntry(this); + root->SetHasUserInteraction(aFlag); + } + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetHasUserActivation(bool* aFlag) { + *aFlag = mInfo->mHasUserActivation; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetHasUserActivation(bool aFlag) { + mInfo->mHasUserActivation = aFlag; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) { + nsCOMPtr referrerInfo = mInfo->mReferrerInfo; + referrerInfo.forget(aReferrerInfo); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) { + mInfo->mReferrerInfo = aReferrerInfo; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetContentViewer(nsIContentViewer** aContentViewer) { + *aContentViewer = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetContentViewer(nsIContentViewer* aContentViewer) { + MOZ_CRASH("This lives in the child process"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetIsInBFCache(bool* aResult) { + *aResult = !!SharedInfo()->mFrameLoader; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetSticky(bool* aSticky) { + *aSticky = SharedInfo()->mSticky; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetSticky(bool aSticky) { + SharedInfo()->mSticky = aSticky; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetWindowState(nsISupports** aWindowState) { + MOZ_CRASH("This lives in the child process"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetWindowState(nsISupports* aWindowState) { + MOZ_CRASH("This lives in the child process"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetRefreshURIList(nsIMutableArray** aRefreshURIList) { + MOZ_CRASH("This lives in the child process"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetRefreshURIList(nsIMutableArray* aRefreshURIList) { + MOZ_CRASH("This lives in the child process"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetPostData(nsIInputStream** aPostData) { + *aPostData = mInfo->GetPostData().take(); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetPostData(nsIInputStream* aPostData) { + mInfo->SetPostData(aPostData); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetLayoutHistoryState( + nsILayoutHistoryState** aLayoutHistoryState) { + nsCOMPtr layoutHistoryState = + SharedInfo()->mLayoutHistoryState; + layoutHistoryState.forget(aLayoutHistoryState); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetLayoutHistoryState( + nsILayoutHistoryState* aLayoutHistoryState) { + SharedInfo()->mLayoutHistoryState = aLayoutHistoryState; + if (SharedInfo()->mLayoutHistoryState) { + SharedInfo()->mLayoutHistoryState->SetScrollPositionOnly( + !SharedInfo()->mSaveLayoutState); + } + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetParent(nsISHEntry** aParent) { + nsCOMPtr parent = mParent; + parent.forget(aParent); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetParent(nsISHEntry* aParent) { + mParent = aParent; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetLoadType(uint32_t* aLoadType) { + *aLoadType = mInfo->mLoadType; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetLoadType(uint32_t aLoadType) { + mInfo->mLoadType = aLoadType; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetID(uint32_t* aID) { + *aID = mID; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetID(uint32_t aID) { + mID = aID; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetCacheKey(uint32_t* aCacheKey) { + *aCacheKey = SharedInfo()->mCacheKey; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetCacheKey(uint32_t aCacheKey) { + SharedInfo()->mCacheKey = aCacheKey; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetSaveLayoutStateFlag(bool* aSaveLayoutStateFlag) { + *aSaveLayoutStateFlag = SharedInfo()->mSaveLayoutState; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetSaveLayoutStateFlag(bool aSaveLayoutStateFlag) { + SharedInfo()->mSaveLayoutState = aSaveLayoutStateFlag; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetContentType(nsACString& aContentType) { + aContentType = SharedInfo()->mContentType; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetContentType(const nsACString& aContentType) { + SharedInfo()->mContentType = aContentType; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetURIWasModified(bool* aURIWasModified) { + *aURIWasModified = mInfo->mURIWasModified; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetURIWasModified(bool aURIWasModified) { + mInfo->mURIWasModified = aURIWasModified; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetTriggeringPrincipal( + nsIPrincipal** aTriggeringPrincipal) { + nsCOMPtr triggeringPrincipal = + SharedInfo()->mTriggeringPrincipal; + triggeringPrincipal.forget(aTriggeringPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetTriggeringPrincipal( + nsIPrincipal* aTriggeringPrincipal) { + SharedInfo()->mTriggeringPrincipal = aTriggeringPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) { + nsCOMPtr principalToInherit = SharedInfo()->mPrincipalToInherit; + principalToInherit.forget(aPrincipalToInherit); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) { + SharedInfo()->mPrincipalToInherit = aPrincipalToInherit; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetPartitionedPrincipalToInherit( + nsIPrincipal** aPartitionedPrincipalToInherit) { + nsCOMPtr partitionedPrincipalToInherit = + SharedInfo()->mPartitionedPrincipalToInherit; + partitionedPrincipalToInherit.forget(aPartitionedPrincipalToInherit); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetPartitionedPrincipalToInherit( + nsIPrincipal* aPartitionedPrincipalToInherit) { + SharedInfo()->mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetCsp(nsIContentSecurityPolicy** aCsp) { + nsCOMPtr csp = SharedInfo()->mCsp; + csp.forget(aCsp); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetCsp(nsIContentSecurityPolicy* aCsp) { + SharedInfo()->mCsp = aCsp; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetStateData(nsIStructuredCloneContainer** aStateData) { + RefPtr stateData = mInfo->mStateData; + stateData.forget(aStateData); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetStateData(nsIStructuredCloneContainer* aStateData) { + mInfo->mStateData = static_cast(aStateData); + return NS_OK; +} + +const nsID& SessionHistoryEntry::DocshellID() const { + return SharedInfo()->mDocShellID; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetDocshellID(nsID& aDocshellID) { + aDocshellID = DocshellID(); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetDocshellID(const nsID& aDocshellID) { + SharedInfo()->mDocShellID = aDocshellID; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry) { + *aIsSrcdocEntry = mInfo->mSrcdocData.isSome(); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetSrcdocData(nsAString& aSrcdocData) { + aSrcdocData = mInfo->mSrcdocData.valueOr(EmptyString()); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetSrcdocData(const nsAString& aSrcdocData) { + mInfo->mSrcdocData = Some(nsString(aSrcdocData)); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetBaseURI(nsIURI** aBaseURI) { + nsCOMPtr baseURI = mInfo->mBaseURI; + baseURI.forget(aBaseURI); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetBaseURI(nsIURI* aBaseURI) { + mInfo->mBaseURI = aBaseURI; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetScrollRestorationIsManual( + bool* aScrollRestorationIsManual) { + *aScrollRestorationIsManual = mInfo->mScrollRestorationIsManual; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetScrollRestorationIsManual( + bool aScrollRestorationIsManual) { + mInfo->mScrollRestorationIsManual = aScrollRestorationIsManual; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetLoadedInThisProcess(bool* aLoadedInThisProcess) { + // FIXME + //*aLoadedInThisProcess = mInfo->mLoadedInThisProcess; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetShistory(nsISHistory** aShistory) { + nsCOMPtr sHistory = do_QueryReferent(SharedInfo()->mSHistory); + sHistory.forget(aShistory); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetShistory(nsISHistory* aShistory) { + nsWeakPtr shistory = do_GetWeakReference(aShistory); + // mSHistory can not be changed once it's set + MOZ_ASSERT(!SharedInfo()->mSHistory || (SharedInfo()->mSHistory == shistory)); + SharedInfo()->mSHistory = shistory; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetLastTouched(uint32_t* aLastTouched) { + *aLastTouched = SharedInfo()->mLastTouched; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetLastTouched(uint32_t aLastTouched) { + SharedInfo()->mLastTouched = aLastTouched; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetChildCount(int32_t* aChildCount) { + *aChildCount = mChildren.Length(); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetPersist(bool* aPersist) { + *aPersist = mInfo->mPersist; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetPersist(bool aPersist) { + mInfo->mPersist = aPersist; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetScrollPosition(int32_t* aX, int32_t* aY) { + *aX = mInfo->mScrollPositionX; + *aY = mInfo->mScrollPositionY; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetScrollPosition(int32_t aX, int32_t aY) { + mInfo->mScrollPositionX = aX; + mInfo->mScrollPositionY = aY; + return NS_OK; +} + +NS_IMETHODIMP_(void) +SessionHistoryEntry::GetViewerBounds(nsIntRect& bounds) { + bounds = SharedInfo()->mViewerBounds; +} + +NS_IMETHODIMP_(void) +SessionHistoryEntry::SetViewerBounds(const nsIntRect& bounds) { + SharedInfo()->mViewerBounds = bounds; +} + +NS_IMETHODIMP_(void) +SessionHistoryEntry::AddChildShell(nsIDocShellTreeItem* shell) { + MOZ_CRASH("This lives in the child process"); +} + +NS_IMETHODIMP +SessionHistoryEntry::ChildShellAt(int32_t index, + nsIDocShellTreeItem** _retval) { + MOZ_CRASH("This lives in the child process"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP_(void) +SessionHistoryEntry::ClearChildShells() { + MOZ_CRASH("This lives in the child process"); +} + +NS_IMETHODIMP_(void) +SessionHistoryEntry::SyncPresentationState() { + MOZ_CRASH("This lives in the child process"); +} + +NS_IMETHODIMP +SessionHistoryEntry::InitLayoutHistoryState( + nsILayoutHistoryState** aLayoutHistoryState) { + if (!SharedInfo()->mLayoutHistoryState) { + nsCOMPtr historyState; + historyState = NS_NewLayoutHistoryState(); + SetLayoutHistoryState(historyState); + } + + return GetLayoutHistoryState(aLayoutHistoryState); +} + +NS_IMETHODIMP +SessionHistoryEntry::Create( + nsIURI* aURI, const nsAString& aTitle, nsIInputStream* aInputStream, + uint32_t aCacheKey, const nsACString& aContentType, + nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, const nsID& aDocshellID, + bool aDynamicCreation, nsIURI* aOriginalURI, nsIURI* aResultPrincipalURI, + nsIURI* aUnstrippedURI, bool aLoadReplace, nsIReferrerInfo* aReferrerInfo, + const nsAString& aSrcdoc, bool aSrcdocEntry, nsIURI* aBaseURI, + bool aSaveLayoutState, bool aExpired, bool aUserActivation) { + MOZ_CRASH("Might need to implement this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SessionHistoryEntry::Clone(nsISHEntry** aEntry) { + RefPtr entry = new SessionHistoryEntry(*this); + + // These are not copied for some reason, we're not sure why. + entry->mInfo->mLoadType = 0; + entry->mInfo->mScrollPositionX = 0; + entry->mInfo->mScrollPositionY = 0; + entry->mInfo->mScrollRestorationIsManual = false; + + entry->mInfo->mHasUserInteraction = false; + + entry.forget(aEntry); + + return NS_OK; +} + +NS_IMETHODIMP_(nsDocShellEditorData*) +SessionHistoryEntry::ForgetEditorData() { + MOZ_CRASH("This lives in the child process"); + return nullptr; +} + +NS_IMETHODIMP_(void) +SessionHistoryEntry::SetEditorData(nsDocShellEditorData* aData) { + NS_WARNING("This lives in the child process"); +} + +NS_IMETHODIMP_(bool) +SessionHistoryEntry::HasDetachedEditor() { + NS_WARNING("This lives in the child process"); + return false; +} + +NS_IMETHODIMP_(bool) +SessionHistoryEntry::IsDynamicallyAdded() { + return SharedInfo()->mDynamicallyCreated; +} + +void SessionHistoryEntry::SetWireframe(const Maybe& aWireframe) { + mWireframe = aWireframe; +} + +void SessionHistoryEntry::SetIsDynamicallyAdded(bool aDynamic) { + MOZ_ASSERT_IF(SharedInfo()->mDynamicallyCreated, aDynamic); + SharedInfo()->mDynamicallyCreated = aDynamic; +} + +NS_IMETHODIMP +SessionHistoryEntry::HasDynamicallyAddedChild(bool* aHasDynamicallyAddedChild) { + for (const auto& child : mChildren) { + if (child && child->IsDynamicallyAdded()) { + *aHasDynamicallyAddedChild = true; + return NS_OK; + } + } + *aHasDynamicallyAddedChild = false; + return NS_OK; +} + +NS_IMETHODIMP_(bool) +SessionHistoryEntry::HasBFCacheEntry(SHEntrySharedParentState* aEntry) { + return SharedInfo() == aEntry; +} + +NS_IMETHODIMP +SessionHistoryEntry::AdoptBFCacheEntry(nsISHEntry* aEntry) { + nsCOMPtr she = do_QueryInterface(aEntry); + NS_ENSURE_STATE(she && she->mInfo->mSharedState.Get()); + + mInfo->mSharedState = + static_cast(aEntry)->mInfo->mSharedState; + + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::AbandonBFCacheEntry() { + MOZ_CRASH("This lives in the child process"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +SessionHistoryEntry::SharesDocumentWith(nsISHEntry* aEntry, + bool* aSharesDocumentWith) { + SessionHistoryEntry* entry = static_cast(aEntry); + + MOZ_ASSERT_IF(entry->SharedInfo() != SharedInfo(), + entry->SharedInfo()->GetId() != SharedInfo()->GetId()); + + *aSharesDocumentWith = entry->SharedInfo() == SharedInfo(); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetLoadTypeAsHistory() { + mInfo->mLoadType = LOAD_HISTORY; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::AddChild(nsISHEntry* aChild, int32_t aOffset, + bool aUseRemoteSubframes) { + nsCOMPtr child = do_QueryInterface(aChild); + MOZ_ASSERT_IF(aChild, child); + AddChild(child, aOffset, aUseRemoteSubframes); + + return NS_OK; +} + +void SessionHistoryEntry::AddChild(SessionHistoryEntry* aChild, int32_t aOffset, + bool aUseRemoteSubframes) { + if (aChild) { + aChild->SetParent(this); + } + + if (aOffset < 0) { + mChildren.AppendElement(aChild); + return; + } + + // + // 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. + + int32_t length = mChildren.Length(); + + // Assert that aOffset will not be so high as to grow us a lot. + NS_ASSERTION(aOffset < length + 1023, "Large frames array!\n"); + + // 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 (aChild && aChild->IsDynamicallyAdded()) { + int32_t lastNonDyn = aOffset - 1; + for (int32_t i = aOffset; i < length; ++i) { + SessionHistoryEntry* entry = mChildren[i]; + if (entry) { + if (entry->IsDynamicallyAdded()) { + break; + } + + lastNonDyn = i; + } + } + + // If aOffset is larger than Length(), we must first truncate the array. + if (aOffset > length) { + mChildren.SetLength(aOffset); + } + + mChildren.InsertElementAt(lastNonDyn + 1, aChild); + + return; + } + + // 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 (length > 0) { + int32_t start = std::min(length - 1, aOffset); + int32_t dynEntryIndex = -1; + DebugOnly dynEntry = nullptr; + for (int32_t i = start; i >= 0; --i) { + SessionHistoryEntry* entry = mChildren[i]; + if (entry) { + if (!entry->IsDynamicallyAdded()) { + break; + } + + dynEntryIndex = i; + dynEntry = entry; + } + } + + if (dynEntryIndex >= 0) { + mChildren.InsertElementsAt(dynEntryIndex, aOffset - dynEntryIndex + 1); + NS_ASSERTION(mChildren[aOffset + 1] == dynEntry, "Whaat?"); + } + } + + // Make sure there isn't anything at aOffset. + if ((uint32_t)aOffset < mChildren.Length()) { + SessionHistoryEntry* oldChild = mChildren[aOffset]; + if (oldChild && oldChild != aChild) { + // Under Fission, this can happen when a network-created iframe starts + // out in-process, moves out-of-process, and then switches back. At that + // point, we'll create a new network-created DocShell at the same index + // where we already have an entry for the original network-created + // DocShell. + // + // This should ideally stop being an issue once the Fission-aware + // session history rewrite is complete. + NS_ASSERTION( + aUseRemoteSubframes || NS_IsAboutBlank(oldChild->Info().GetURI()), + "Adding a child where we already have a child? This may misbehave"); + oldChild->SetParent(nullptr); + } + } else { + mChildren.SetLength(aOffset + 1); + } + + mChildren.ReplaceElementAt(aOffset, aChild); +} + +NS_IMETHODIMP +SessionHistoryEntry::RemoveChild(nsISHEntry* aChild) { + NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE); + + nsCOMPtr child = do_QueryInterface(aChild); + MOZ_ASSERT(child); + RemoveChild(child); + + return NS_OK; +} + +void SessionHistoryEntry::RemoveChild(SessionHistoryEntry* aChild) { + bool childRemoved = false; + if (aChild->IsDynamicallyAdded()) { + childRemoved = mChildren.RemoveElement(aChild); + } else { + int32_t index = mChildren.IndexOf(aChild); + if (index >= 0) { + // Other alive non-dynamic child docshells still keep mChildOffset, + // so we don't want to change the indices here. + mChildren.ReplaceElementAt(index, nullptr); + childRemoved = true; + } + } + + if (childRemoved) { + aChild->SetParent(nullptr); + + // reduce the child count, i.e. remove empty children at the end + for (int32_t i = mChildren.Length() - 1; i >= 0 && !mChildren[i]; --i) { + mChildren.RemoveElementAt(i); + } + } +} + +NS_IMETHODIMP +SessionHistoryEntry::GetChildAt(int32_t aIndex, nsISHEntry** aChild) { + nsCOMPtr child = mChildren.SafeElementAt(aIndex); + child.forget(aChild); + return NS_OK; +} + +NS_IMETHODIMP_(void) +SessionHistoryEntry::GetChildSHEntryIfHasNoDynamicallyAddedChild( + int32_t aChildOffset, nsISHEntry** aChild) { + *aChild = nullptr; + + bool dynamicallyAddedChild = false; + HasDynamicallyAddedChild(&dynamicallyAddedChild); + if (dynamicallyAddedChild) { + return; + } + + // If the user did a shift-reload on this frameset page, + // we don't want to load the subframes from history. + if (IsForceReloadType(mInfo->mLoadType) || mInfo->mLoadType == LOAD_REFRESH) { + return; + } + + /* Before looking for the subframe's url, check + * the expiration status of the parent. If the parent + * has expired from cache, then subframes will not be + * loaded from history in certain situations. + * If the user pressed reload and the parent frame has expired + * from cache, we do not want to load the child frame from history. + */ + if (SharedInfo()->mExpired && (mInfo->mLoadType == LOAD_RELOAD_NORMAL)) { + // The parent has expired. Return null. + *aChild = nullptr; + return; + } + // Get the child subframe from session history. + GetChildAt(aChildOffset, aChild); + if (*aChild) { + // Set the parent's Load Type on the child + (*aChild)->SetLoadType(mInfo->mLoadType); + } +} + +NS_IMETHODIMP +SessionHistoryEntry::ReplaceChild(nsISHEntry* aNewChild) { + NS_ENSURE_STATE(aNewChild); + + nsCOMPtr newChild = do_QueryInterface(aNewChild); + MOZ_ASSERT(newChild); + return ReplaceChild(newChild) ? NS_OK : NS_ERROR_FAILURE; +} + +bool SessionHistoryEntry::ReplaceChild(SessionHistoryEntry* aNewChild) { + const nsID& docshellID = aNewChild->DocshellID(); + + for (uint32_t i = 0; i < mChildren.Length(); ++i) { + if (mChildren[i] && docshellID == mChildren[i]->DocshellID()) { + mChildren[i]->SetParent(nullptr); + mChildren.ReplaceElementAt(i, aNewChild); + aNewChild->SetParent(this); + + return true; + } + } + + return false; +} + +NS_IMETHODIMP_(void) +SessionHistoryEntry::ClearEntry() { + int32_t childCount = GetChildCount(); + // Remove all children of this entry + for (int32_t i = childCount; i > 0; --i) { + nsCOMPtr child; + GetChildAt(i - 1, getter_AddRefs(child)); + RemoveChild(child); + } +} + +NS_IMETHODIMP +SessionHistoryEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) { + NS_WARNING("We shouldn't be calling this!"); + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetBfcacheID(uint64_t* aBfcacheID) { + *aBfcacheID = SharedInfo()->mId; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::GetWireframe(JSContext* aCx, + JS::MutableHandle aOut) { + if (mWireframe.isNothing()) { + aOut.set(JS::NullValue()); + } else if (NS_WARN_IF(!mWireframe->ToObjectInternal(aCx, aOut))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetWireframe(JSContext* aCx, JS::Handle aArg) { + if (aArg.isNullOrUndefined()) { + mWireframe = Nothing(); + return NS_OK; + } + + Wireframe wireframe; + if (aArg.isObject() && wireframe.Init(aCx, aArg)) { + mWireframe = Some(std::move(wireframe)); + return NS_OK; + } + + return NS_ERROR_INVALID_ARG; +} + +NS_IMETHODIMP_(void) +SessionHistoryEntry::SyncTreesForSubframeNavigation( + nsISHEntry* aEntry, mozilla::dom::BrowsingContext* aTopBC, + mozilla::dom::BrowsingContext* aIgnoreBC) { + // XXX Keep this in sync with nsSHEntry::SyncTreesForSubframeNavigation. + // + // We need to sync up the browsing context and session history trees for + // subframe navigation. If the load was in a subframe, we forward up to + // the top browsing context, which will then recursively sync up all browsing + // contexts to their corresponding entries in the new session history tree. If + // we don't do this, then we can cache a content viewer on the wrong cloned + // entry, and subsequently restore it at the wrong time. + nsCOMPtr newRootEntry = nsSHistory::GetRootSHEntry(aEntry); + if (newRootEntry) { + // newRootEntry is now the new root entry. + // Find the old root entry as well. + + // Need a strong ref. on |oldRootEntry| so it isn't destroyed when + // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639). + nsCOMPtr oldRootEntry = nsSHistory::GetRootSHEntry(this); + + if (oldRootEntry) { + nsSHistory::SwapEntriesData data = {aIgnoreBC, newRootEntry, nullptr}; + nsSHistory::SetChildHistoryEntry(oldRootEntry, aTopBC, 0, &data); + } + } +} + +void SessionHistoryEntry::ReplaceWith(const SessionHistoryEntry& aSource) { + mInfo = MakeUnique(*aSource.mInfo); + mChildren.Clear(); +} + +SHEntrySharedParentState* SessionHistoryEntry::SharedInfo() const { + return static_cast(mInfo->mSharedState.Get()); +} + +void SessionHistoryEntry::SetFrameLoader(nsFrameLoader* aFrameLoader) { + MOZ_DIAGNOSTIC_ASSERT(!aFrameLoader || !SharedInfo()->mFrameLoader); + // If the pref is disabled, we still allow evicting the existing entries. + MOZ_RELEASE_ASSERT(!aFrameLoader || mozilla::BFCacheInParent()); + SharedInfo()->SetFrameLoader(aFrameLoader); + if (aFrameLoader) { + if (BrowsingContext* bc = aFrameLoader->GetMaybePendingBrowsingContext()) { + bc->PreOrderWalk([&](BrowsingContext* aContext) { + if (BrowserParent* bp = aContext->Canonical()->GetBrowserParent()) { + bp->Deactivated(); + } + }); + } + + // When a new frameloader is stored, try to evict some older + // frameloaders. Non-SHIP session history has a similar call in + // nsDocumentViewer::Show. + nsCOMPtr shistory; + GetShistory(getter_AddRefs(shistory)); + if (shistory) { + int32_t index = 0; + shistory->GetIndex(&index); + shistory->EvictOutOfRangeContentViewers(index); + } + } +} + +nsFrameLoader* SessionHistoryEntry::GetFrameLoader() { + return SharedInfo()->mFrameLoader; +} + +void SessionHistoryEntry::SetInfo(SessionHistoryInfo* aInfo) { + // FIXME Assert that we're not changing shared state! + mInfo = MakeUnique(*aInfo); +} + +} // namespace dom + +namespace ipc { + +void IPDLParamTraits::Write( + IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::SessionHistoryInfo& aParam) { + nsCOMPtr postData = aParam.GetPostData(); + + Maybe> stateData; + if (aParam.mStateData) { + stateData.emplace(); + // FIXME: We should fail more aggressively if this fails, as currently we'll + // just early return and the deserialization will break. + NS_ENSURE_SUCCESS_VOID( + aParam.mStateData->GetFormatVersion(&Get<0>(*stateData))); + NS_ENSURE_TRUE_VOID( + aParam.mStateData->BuildClonedMessageData(Get<1>(*stateData))); + } + + WriteIPDLParam(aWriter, aActor, aParam.mURI); + WriteIPDLParam(aWriter, aActor, aParam.mOriginalURI); + WriteIPDLParam(aWriter, aActor, aParam.mResultPrincipalURI); + WriteIPDLParam(aWriter, aActor, aParam.mUnstrippedURI); + WriteIPDLParam(aWriter, aActor, aParam.mReferrerInfo); + WriteIPDLParam(aWriter, aActor, aParam.mTitle); + WriteIPDLParam(aWriter, aActor, aParam.mName); + WriteIPDLParam(aWriter, aActor, postData); + WriteIPDLParam(aWriter, aActor, aParam.mLoadType); + WriteIPDLParam(aWriter, aActor, aParam.mScrollPositionX); + WriteIPDLParam(aWriter, aActor, aParam.mScrollPositionY); + WriteIPDLParam(aWriter, aActor, stateData); + WriteIPDLParam(aWriter, aActor, aParam.mSrcdocData); + WriteIPDLParam(aWriter, aActor, aParam.mBaseURI); + WriteIPDLParam(aWriter, aActor, aParam.mLoadReplace); + WriteIPDLParam(aWriter, aActor, aParam.mURIWasModified); + WriteIPDLParam(aWriter, aActor, aParam.mScrollRestorationIsManual); + WriteIPDLParam(aWriter, aActor, aParam.mPersist); + WriteIPDLParam(aWriter, aActor, aParam.mHasUserInteraction); + WriteIPDLParam(aWriter, aActor, aParam.mHasUserActivation); + WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mId); + WriteIPDLParam(aWriter, aActor, + aParam.mSharedState.Get()->mTriggeringPrincipal); + WriteIPDLParam(aWriter, aActor, + aParam.mSharedState.Get()->mPrincipalToInherit); + WriteIPDLParam(aWriter, aActor, + aParam.mSharedState.Get()->mPartitionedPrincipalToInherit); + WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mCsp); + WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mContentType); + WriteIPDLParam(aWriter, aActor, + aParam.mSharedState.Get()->mLayoutHistoryState); + WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mCacheKey); + WriteIPDLParam(aWriter, aActor, + aParam.mSharedState.Get()->mIsFrameNavigation); + WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mSaveLayoutState); +} + +bool IPDLParamTraits::Read( + IPC::MessageReader* aReader, IProtocol* aActor, + dom::SessionHistoryInfo* aResult) { + Maybe> stateData; + uint64_t sharedId; + if (!ReadIPDLParam(aReader, aActor, &aResult->mURI) || + !ReadIPDLParam(aReader, aActor, &aResult->mOriginalURI) || + !ReadIPDLParam(aReader, aActor, &aResult->mResultPrincipalURI) || + !ReadIPDLParam(aReader, aActor, &aResult->mUnstrippedURI) || + !ReadIPDLParam(aReader, aActor, &aResult->mReferrerInfo) || + !ReadIPDLParam(aReader, aActor, &aResult->mTitle) || + !ReadIPDLParam(aReader, aActor, &aResult->mName) || + !ReadIPDLParam(aReader, aActor, &aResult->mPostData) || + !ReadIPDLParam(aReader, aActor, &aResult->mLoadType) || + !ReadIPDLParam(aReader, aActor, &aResult->mScrollPositionX) || + !ReadIPDLParam(aReader, aActor, &aResult->mScrollPositionY) || + !ReadIPDLParam(aReader, aActor, &stateData) || + !ReadIPDLParam(aReader, aActor, &aResult->mSrcdocData) || + !ReadIPDLParam(aReader, aActor, &aResult->mBaseURI) || + !ReadIPDLParam(aReader, aActor, &aResult->mLoadReplace) || + !ReadIPDLParam(aReader, aActor, &aResult->mURIWasModified) || + !ReadIPDLParam(aReader, aActor, &aResult->mScrollRestorationIsManual) || + !ReadIPDLParam(aReader, aActor, &aResult->mPersist) || + !ReadIPDLParam(aReader, aActor, &aResult->mHasUserInteraction) || + !ReadIPDLParam(aReader, aActor, &aResult->mHasUserActivation) || + !ReadIPDLParam(aReader, aActor, &sharedId)) { + aActor->FatalError("Error reading fields for SessionHistoryInfo"); + return false; + } + + nsCOMPtr triggeringPrincipal; + nsCOMPtr principalToInherit; + nsCOMPtr partitionedPrincipalToInherit; + nsCOMPtr csp; + nsCString contentType; + if (!ReadIPDLParam(aReader, aActor, &triggeringPrincipal) || + !ReadIPDLParam(aReader, aActor, &principalToInherit) || + !ReadIPDLParam(aReader, aActor, &partitionedPrincipalToInherit) || + !ReadIPDLParam(aReader, aActor, &csp) || + !ReadIPDLParam(aReader, aActor, &contentType)) { + aActor->FatalError("Error reading fields for SessionHistoryInfo"); + return false; + } + + // We should always see a cloneable input stream passed to SessionHistoryInfo. + // This is because it will be cloneable when first read in the parent process + // from the nsHttpChannel (which forces streams to be cloneable), and future + // streams in content will be wrapped in + // nsMIMEInputStream(RemoteLazyInputStream) which is also cloneable. + if (aResult->mPostData && !NS_InputStreamIsCloneable(aResult->mPostData)) { + aActor->FatalError( + "Unexpected non-cloneable postData for SessionHistoryInfo"); + return false; + } + + dom::SHEntrySharedParentState* sharedState = nullptr; + if (XRE_IsParentProcess()) { + sharedState = dom::SHEntrySharedParentState::Lookup(sharedId); + } + + if (sharedState) { + aResult->mSharedState.Set(sharedState); + + MOZ_ASSERT(triggeringPrincipal + ? triggeringPrincipal->Equals( + aResult->mSharedState.Get()->mTriggeringPrincipal) + : !aResult->mSharedState.Get()->mTriggeringPrincipal, + "We don't expect this to change!"); + MOZ_ASSERT(principalToInherit + ? principalToInherit->Equals( + aResult->mSharedState.Get()->mPrincipalToInherit) + : !aResult->mSharedState.Get()->mPrincipalToInherit, + "We don't expect this to change!"); + MOZ_ASSERT( + partitionedPrincipalToInherit + ? partitionedPrincipalToInherit->Equals( + aResult->mSharedState.Get()->mPartitionedPrincipalToInherit) + : !aResult->mSharedState.Get()->mPartitionedPrincipalToInherit, + "We don't expect this to change!"); + MOZ_ASSERT( + csp ? nsCSPContext::Equals(csp, aResult->mSharedState.Get()->mCsp) + : !aResult->mSharedState.Get()->mCsp, + "We don't expect this to change!"); + MOZ_ASSERT(contentType.Equals(aResult->mSharedState.Get()->mContentType), + "We don't expect this to change!"); + } else { + aResult->mSharedState.ChangeId(sharedId); + aResult->mSharedState.Get()->mTriggeringPrincipal = + triggeringPrincipal.forget(); + aResult->mSharedState.Get()->mPrincipalToInherit = + principalToInherit.forget(); + aResult->mSharedState.Get()->mPartitionedPrincipalToInherit = + partitionedPrincipalToInherit.forget(); + aResult->mSharedState.Get()->mCsp = csp.forget(); + aResult->mSharedState.Get()->mContentType = contentType; + } + + if (!ReadIPDLParam(aReader, aActor, + &aResult->mSharedState.Get()->mLayoutHistoryState) || + !ReadIPDLParam(aReader, aActor, + &aResult->mSharedState.Get()->mCacheKey) || + !ReadIPDLParam(aReader, aActor, + &aResult->mSharedState.Get()->mIsFrameNavigation) || + !ReadIPDLParam(aReader, aActor, + &aResult->mSharedState.Get()->mSaveLayoutState)) { + aActor->FatalError("Error reading fields for SessionHistoryInfo"); + return false; + } + + if (stateData.isSome()) { + uint32_t version = Get<0>(*stateData); + aResult->mStateData = new nsStructuredCloneContainer(version); + aResult->mStateData->StealFromClonedMessageData(Get<1>(*stateData)); + } + MOZ_ASSERT_IF(stateData.isNothing(), !aResult->mStateData); + return true; +} + +void IPDLParamTraits::Write( + IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::LoadingSessionHistoryInfo& aParam) { + WriteIPDLParam(aWriter, aActor, aParam.mInfo); + WriteIPDLParam(aWriter, aActor, aParam.mLoadId); + WriteIPDLParam(aWriter, aActor, aParam.mLoadIsFromSessionHistory); + WriteIPDLParam(aWriter, aActor, aParam.mOffset); + WriteIPDLParam(aWriter, aActor, aParam.mLoadingCurrentEntry); + WriteIPDLParam(aWriter, aActor, aParam.mForceMaybeResetName); +} + +bool IPDLParamTraits::Read( + IPC::MessageReader* aReader, IProtocol* aActor, + dom::LoadingSessionHistoryInfo* aResult) { + if (!ReadIPDLParam(aReader, aActor, &aResult->mInfo) || + !ReadIPDLParam(aReader, aActor, &aResult->mLoadId) || + !ReadIPDLParam(aReader, aActor, &aResult->mLoadIsFromSessionHistory) || + !ReadIPDLParam(aReader, aActor, &aResult->mOffset) || + !ReadIPDLParam(aReader, aActor, &aResult->mLoadingCurrentEntry) || + !ReadIPDLParam(aReader, aActor, &aResult->mForceMaybeResetName)) { + aActor->FatalError("Error reading fields for LoadingSessionHistoryInfo"); + return false; + } + + return true; +} + +void IPDLParamTraits::Write( + IPC::MessageWriter* aWriter, IProtocol* aActor, + nsILayoutHistoryState* aParam) { + if (aParam) { + WriteIPDLParam(aWriter, aActor, true); + bool scrollPositionOnly = false; + nsTArray keys; + nsTArray states; + aParam->GetContents(&scrollPositionOnly, keys, states); + WriteIPDLParam(aWriter, aActor, scrollPositionOnly); + WriteIPDLParam(aWriter, aActor, keys); + WriteIPDLParam(aWriter, aActor, states); + } else { + WriteIPDLParam(aWriter, aActor, false); + } +} + +bool IPDLParamTraits::Read( + IPC::MessageReader* aReader, IProtocol* aActor, + RefPtr* aResult) { + bool hasLayoutHistoryState = false; + if (!ReadIPDLParam(aReader, aActor, &hasLayoutHistoryState)) { + aActor->FatalError("Error reading fields for nsILayoutHistoryState"); + return false; + } + + if (hasLayoutHistoryState) { + bool scrollPositionOnly = false; + nsTArray keys; + nsTArray states; + if (!ReadIPDLParam(aReader, aActor, &scrollPositionOnly) || + !ReadIPDLParam(aReader, aActor, &keys) || + !ReadIPDLParam(aReader, aActor, &states)) { + aActor->FatalError("Error reading fields for nsILayoutHistoryState"); + } + + if (keys.Length() != states.Length()) { + aActor->FatalError("Error reading fields for nsILayoutHistoryState"); + return false; + } + + *aResult = NS_NewLayoutHistoryState(); + (*aResult)->SetScrollPositionOnly(scrollPositionOnly); + for (uint32_t i = 0; i < keys.Length(); ++i) { + PresState& state = states[i]; + UniquePtr newState = MakeUnique(state); + (*aResult)->AddState(keys[i], std::move(newState)); + } + } + return true; +} + +void IPDLParamTraits::Write( + IPC::MessageWriter* aWriter, IProtocol* aActor, + const mozilla::dom::Wireframe& aParam) { + WriteParam(aWriter, aParam.mCanvasBackground); + WriteParam(aWriter, aParam.mRects); +} + +bool IPDLParamTraits::Read( + IPC::MessageReader* aReader, IProtocol* aActor, + mozilla::dom::Wireframe* aResult) { + return ReadParam(aReader, &aResult->mCanvasBackground) && + ReadParam(aReader, &aResult->mRects); +} + +} // namespace ipc +} // namespace mozilla + +namespace IPC { +// Allow sending mozilla::dom::WireframeRectType enums over IPC. +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::WireframeRectType, + mozilla::dom::WireframeRectType::Image, + mozilla::dom::WireframeRectType::EndGuard_> {}; + +template <> +struct ParamTraits { + static void Write(MessageWriter* aWriter, + const mozilla::dom::WireframeTaggedRect& aParam); + static bool Read(MessageReader* aReader, + mozilla::dom::WireframeTaggedRect* aResult); +}; + +void ParamTraits::Write( + MessageWriter* aWriter, const mozilla::dom::WireframeTaggedRect& aParam) { + WriteParam(aWriter, aParam.mColor); + WriteParam(aWriter, aParam.mType); + WriteParam(aWriter, aParam.mX); + WriteParam(aWriter, aParam.mY); + WriteParam(aWriter, aParam.mWidth); + WriteParam(aWriter, aParam.mHeight); +} + +bool ParamTraits::Read( + IPC::MessageReader* aReader, mozilla::dom::WireframeTaggedRect* aResult) { + return ReadParam(aReader, &aResult->mColor) && + ReadParam(aReader, &aResult->mType) && + ReadParam(aReader, &aResult->mX) && ReadParam(aReader, &aResult->mY) && + ReadParam(aReader, &aResult->mWidth) && + ReadParam(aReader, &aResult->mHeight); +} +} // namespace IPC diff --git a/docshell/shistory/SessionHistoryEntry.h b/docshell/shistory/SessionHistoryEntry.h new file mode 100644 index 0000000000..2c5e91cd08 --- /dev/null +++ b/docshell/shistory/SessionHistoryEntry.h @@ -0,0 +1,495 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_SessionHistoryEntry_h +#define mozilla_dom_SessionHistoryEntry_h + +#include "mozilla/dom/DocumentBinding.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "nsILayoutHistoryState.h" +#include "nsISHEntry.h" +#include "nsSHEntryShared.h" +#include "nsStructuredCloneContainer.h" +#include "nsTHashMap.h" + +class nsDocShellLoadState; +class nsIChannel; +class nsIInputStream; +class nsIReferrerInfo; +class nsISHistory; +class nsIURI; + +namespace mozilla::ipc { +template +struct IPDLParamTraits; +} + +namespace mozilla { +namespace dom { + +struct LoadingSessionHistoryInfo; +class SessionHistoryEntry; +class SHEntrySharedParentState; + +// SessionHistoryInfo stores session history data for a load. It can be sent +// over IPC and is used in both the parent and the child processes. +class SessionHistoryInfo { + public: + SessionHistoryInfo() = default; + SessionHistoryInfo(const SessionHistoryInfo& aInfo) = default; + SessionHistoryInfo(nsDocShellLoadState* aLoadState, nsIChannel* aChannel); + SessionHistoryInfo(const SessionHistoryInfo& aSharedStateFrom, nsIURI* aURI); + SessionHistoryInfo(nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, + const nsACString& aContentType); + SessionHistoryInfo(nsIChannel* aChannel, uint32_t aLoadType, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp); + + void Reset(nsIURI* aURI, const nsID& aDocShellID, bool aDynamicCreation, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, const nsACString& aContentType); + + bool operator==(const SessionHistoryInfo& aInfo) const { + return false; // FIXME + } + + nsIURI* GetURI() const { return mURI; } + void SetURI(nsIURI* aURI) { mURI = aURI; } + + nsIURI* GetOriginalURI() const { return mOriginalURI; } + void SetOriginalURI(nsIURI* aOriginalURI) { mOriginalURI = aOriginalURI; } + + nsIURI* GetUnstrippedURI() const { return mUnstrippedURI; } + void SetUnstrippedURI(nsIURI* aUnstrippedURI) { + mUnstrippedURI = aUnstrippedURI; + } + + nsIURI* GetResultPrincipalURI() const { return mResultPrincipalURI; } + void SetResultPrincipalURI(nsIURI* aResultPrincipalURI) { + mResultPrincipalURI = aResultPrincipalURI; + } + + nsIReferrerInfo* GetReferrerInfo() { return mReferrerInfo; } + void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) { + mReferrerInfo = aReferrerInfo; + } + + bool HasPostData() const { return mPostData; } + already_AddRefed GetPostData() const; + void SetPostData(nsIInputStream* aPostData); + + void GetScrollPosition(int32_t* aScrollPositionX, int32_t* aScrollPositionY) { + *aScrollPositionX = mScrollPositionX; + *aScrollPositionY = mScrollPositionY; + } + + void SetScrollPosition(int32_t aScrollPositionX, int32_t aScrollPositionY) { + mScrollPositionX = aScrollPositionX; + mScrollPositionY = aScrollPositionY; + } + + bool GetScrollRestorationIsManual() const { + return mScrollRestorationIsManual; + } + const nsAString& GetTitle() { return mTitle; } + void SetTitle(const nsAString& aTitle) { + mTitle = aTitle; + MaybeUpdateTitleFromURI(); + } + + const nsAString& GetName() { return mName; } + void SetName(const nsAString& aName) { mName = aName; } + + void SetScrollRestorationIsManual(bool aIsManual) { + mScrollRestorationIsManual = aIsManual; + } + + nsStructuredCloneContainer* GetStateData() const { return mStateData; } + void SetStateData(nsStructuredCloneContainer* aStateData) { + mStateData = aStateData; + } + + void SetLoadReplace(bool aLoadReplace) { mLoadReplace = aLoadReplace; } + + void SetURIWasModified(bool aURIWasModified) { + mURIWasModified = aURIWasModified; + } + bool GetURIWasModified() const { return mURIWasModified; } + + void SetHasUserInteraction(bool aHasUserInteraction) { + mHasUserInteraction = aHasUserInteraction; + } + bool GetHasUserInteraction() const { return mHasUserInteraction; } + + uint64_t SharedId() const; + + nsILayoutHistoryState* GetLayoutHistoryState(); + void SetLayoutHistoryState(nsILayoutHistoryState* aState); + + nsIPrincipal* GetTriggeringPrincipal() const; + + nsIPrincipal* GetPrincipalToInherit() const; + + nsIPrincipal* GetPartitionedPrincipalToInherit() const; + + nsIContentSecurityPolicy* GetCsp() const; + + uint32_t GetCacheKey() const; + void SetCacheKey(uint32_t aCacheKey); + + bool IsSubFrame() const; + + bool SharesDocumentWith(const SessionHistoryInfo& aOther) const { + return SharedId() == aOther.SharedId(); + } + + void FillLoadInfo(nsDocShellLoadState& aLoadState) const; + + uint32_t LoadType() { return mLoadType; } + + void SetSaveLayoutStateFlag(bool aSaveLayoutStateFlag); + + private: + friend class SessionHistoryEntry; + friend struct mozilla::ipc::IPDLParamTraits; + + void MaybeUpdateTitleFromURI(); + + nsCOMPtr mURI; + nsCOMPtr mOriginalURI; + nsCOMPtr mResultPrincipalURI; + nsCOMPtr mUnstrippedURI; + nsCOMPtr mReferrerInfo; + nsString mTitle; + nsString mName; + nsCOMPtr mPostData; + uint32_t mLoadType = 0; + int32_t mScrollPositionX = 0; + int32_t mScrollPositionY = 0; + RefPtr mStateData; + Maybe mSrcdocData; + nsCOMPtr mBaseURI; + + bool mLoadReplace = false; + bool mURIWasModified = false; + bool mScrollRestorationIsManual = false; + bool mPersist = true; + bool mHasUserInteraction = false; + bool mHasUserActivation = false; + + union SharedState { + SharedState(); + explicit SharedState(const SharedState& aOther); + explicit SharedState(const Maybe& aOther); + ~SharedState(); + + SharedState& operator=(const SharedState& aOther); + + SHEntrySharedState* Get() const; + + void Set(SHEntrySharedParentState* aState) { mParent = aState; } + + void ChangeId(uint64_t aId); + + static SharedState Create(nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, + const nsACString& aContentType); + + private: + explicit SharedState(SHEntrySharedParentState* aParent) + : mParent(aParent) {} + explicit SharedState(UniquePtr&& aChild) + : mChild(std::move(aChild)) {} + + void Init(); + void Init(const SharedState& aOther); + + // In the parent process this holds a strong reference to the refcounted + // SHEntrySharedParentState. In the child processes this holds an owning + // pointer to a SHEntrySharedState. + RefPtr mParent; + UniquePtr mChild; + }; + + SharedState mSharedState; +}; + +struct LoadingSessionHistoryInfo { + LoadingSessionHistoryInfo() = default; + explicit LoadingSessionHistoryInfo(SessionHistoryEntry* aEntry); + // Initializes mInfo using aEntry and otherwise copies the values from aInfo. + LoadingSessionHistoryInfo(SessionHistoryEntry* aEntry, + const LoadingSessionHistoryInfo* aInfo); + // For about:blank only. + explicit LoadingSessionHistoryInfo(const SessionHistoryInfo& aInfo); + + already_AddRefed CreateLoadInfo() const; + + SessionHistoryInfo mInfo; + + uint64_t mLoadId = 0; + + // The following three member variables are used to inform about a load from + // the session history. The session-history-in-child approach has just + // an nsISHEntry in the nsDocShellLoadState and access to the nsISHistory, + // but session-history-in-parent needs to pass needed information explicitly + // to the relevant child process. + bool mLoadIsFromSessionHistory = false; + // mOffset and mLoadingCurrentEntry are relevant only if + // mLoadIsFromSessionHistory is true. + int32_t mOffset = 0; + // If we're loading from the current entry we want to treat it as not a + // same-document navigation (see nsDocShell::IsSameDocumentNavigation). + bool mLoadingCurrentEntry = false; + // If mForceMaybeResetName.isSome() is true then the parent process has + // determined whether the BC's name should be cleared and stored in session + // history (see https://html.spec.whatwg.org/#history-traversal step 4.2). + // This is used when we're replacing the BC for BFCache in the parent. In + // other cases mForceMaybeResetName.isSome() will be false and the child + // process should be able to make that determination itself. + Maybe mForceMaybeResetName; +}; + +// HistoryEntryCounterForBrowsingContext is used to count the number of entries +// which are added to the session history for a particular browsing context. +// If a SessionHistoryEntry is cloned because of navigation in some other +// browsing context, that doesn't cause the counter value to be increased. +// The browsing context specific counter is needed to make it easier to +// synchronously update history.length value in a child process when +// an iframe is removed from DOM. +class HistoryEntryCounterForBrowsingContext { + public: + HistoryEntryCounterForBrowsingContext() + : mCounter(new RefCountedCounter()), mHasModified(false) { + ++(*this); + } + + HistoryEntryCounterForBrowsingContext( + const HistoryEntryCounterForBrowsingContext& aOther) + : mCounter(aOther.mCounter), mHasModified(false) {} + + HistoryEntryCounterForBrowsingContext( + HistoryEntryCounterForBrowsingContext&& aOther) = delete; + + ~HistoryEntryCounterForBrowsingContext() { + if (mHasModified) { + --(*mCounter); + } + } + + void CopyValueFrom(const HistoryEntryCounterForBrowsingContext& aOther) { + if (mHasModified) { + --(*mCounter); + } + mCounter = aOther.mCounter; + mHasModified = false; + } + + HistoryEntryCounterForBrowsingContext& operator=( + const HistoryEntryCounterForBrowsingContext& aOther) = delete; + + HistoryEntryCounterForBrowsingContext& operator++() { + mHasModified = true; + ++(*mCounter); + return *this; + } + + operator uint32_t() const { return *mCounter; } + + bool Modified() { return mHasModified; } + + void SetModified(bool aModified) { mHasModified = aModified; } + + void Reset() { + if (mHasModified) { + --(*mCounter); + } + mCounter = new RefCountedCounter(); + mHasModified = false; + } + + private: + class RefCountedCounter { + public: + NS_INLINE_DECL_REFCOUNTING( + mozilla::dom::HistoryEntryCounterForBrowsingContext::RefCountedCounter) + + RefCountedCounter& operator++() { + ++mCounter; + return *this; + } + + RefCountedCounter& operator--() { + --mCounter; + return *this; + } + + operator uint32_t() const { return mCounter; } + + private: + ~RefCountedCounter() = default; + + uint32_t mCounter = 0; + }; + + RefPtr mCounter; + bool mHasModified; +}; + +// SessionHistoryEntry is used to store session history data in the parent +// process. It holds a SessionHistoryInfo, some state shared amongst multiple +// SessionHistoryEntries, a parent and children. +#define NS_SESSIONHISTORYENTRY_IID \ + { \ + 0x5b66a244, 0x8cec, 0x4caa, { \ + 0xaa, 0x0a, 0x78, 0x92, 0xfd, 0x17, 0xa6, 0x67 \ + } \ + } + +class SessionHistoryEntry : public nsISHEntry { + public: + SessionHistoryEntry(nsDocShellLoadState* aLoadState, nsIChannel* aChannel); + SessionHistoryEntry(); + explicit SessionHistoryEntry(SessionHistoryInfo* aInfo); + explicit SessionHistoryEntry(const SessionHistoryEntry& aEntry); + + NS_DECL_ISUPPORTS + NS_DECL_NSISHENTRY + NS_DECLARE_STATIC_IID_ACCESSOR(NS_SESSIONHISTORYENTRY_IID) + + void ReplaceWith(const SessionHistoryEntry& aSource); + + const SessionHistoryInfo& Info() const { return *mInfo; } + + SHEntrySharedParentState* SharedInfo() const; + + void SetFrameLoader(nsFrameLoader* aFrameLoader); + nsFrameLoader* GetFrameLoader(); + + void AddChild(SessionHistoryEntry* aChild, int32_t aOffset, + bool aUseRemoteSubframes); + void RemoveChild(SessionHistoryEntry* aChild); + // Finds the child with the same docshell ID as aNewChild, replaces it with + // aNewChild and returns true. If there is no child with the same docshell ID + // then it returns false. + bool ReplaceChild(SessionHistoryEntry* aNewChild); + + void SetInfo(SessionHistoryInfo* aInfo); + + bool ForInitialLoad() { return mForInitialLoad; } + void SetForInitialLoad(bool aForInitialLoad) { + mForInitialLoad = aForInitialLoad; + } + + const nsID& DocshellID() const; + + HistoryEntryCounterForBrowsingContext& BCHistoryLength() { + return mBCHistoryLength; + } + + void SetBCHistoryLength(HistoryEntryCounterForBrowsingContext& aCounter) { + mBCHistoryLength.CopyValueFrom(aCounter); + } + + void ClearBCHistoryLength() { mBCHistoryLength.Reset(); } + + void SetIsDynamicallyAdded(bool aDynamic); + + void SetWireframe(const Maybe& aWireframe); + + // Get an entry based on LoadingSessionHistoryInfo's mLoadId. Parent process + // only. + static SessionHistoryEntry* GetByLoadId(uint64_t aLoadId); + static const SessionHistoryInfo* GetInfoSnapshotForValidationByLoadId( + uint64_t aLoadId); + static void SetByLoadId(uint64_t aLoadId, SessionHistoryEntry* aEntry); + static void RemoveLoadId(uint64_t aLoadId); + + const nsTArray>& Children() { return mChildren; } + + private: + friend struct LoadingSessionHistoryInfo; + virtual ~SessionHistoryEntry(); + + UniquePtr mInfo; + nsISHEntry* mParent = nullptr; + uint32_t mID; + nsTArray> mChildren; + Maybe mWireframe; + + bool mForInitialLoad = false; + + HistoryEntryCounterForBrowsingContext mBCHistoryLength; + + struct LoadingEntry { + // A pointer to the entry being loaded. Will be cleared by the + // SessionHistoryEntry destructor, at latest. + SessionHistoryEntry* mEntry; + // Snapshot of the entry's SessionHistoryInfo when the load started, to be + // used for validation purposes only. + UniquePtr mInfoSnapshotForValidation; + }; + + static nsTHashMap* sLoadIdToEntry; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SessionHistoryEntry, NS_SESSIONHISTORYENTRY_IID) + +} // namespace dom + +namespace ipc { + +class IProtocol; + +// Allow sending SessionHistoryInfo objects over IPC. +template <> +struct IPDLParamTraits { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::SessionHistoryInfo& aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + dom::SessionHistoryInfo* aResult); +}; + +// Allow sending LoadingSessionHistoryInfo objects over IPC. +template <> +struct IPDLParamTraits { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::LoadingSessionHistoryInfo& aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + dom::LoadingSessionHistoryInfo* aResult); +}; + +// Allow sending nsILayoutHistoryState objects over IPC. +template <> +struct IPDLParamTraits { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + nsILayoutHistoryState* aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + RefPtr* aResult); +}; + +// Allow sending dom::Wireframe objects over IPC. +template <> +struct IPDLParamTraits { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const mozilla::dom::Wireframe& aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + mozilla::dom::Wireframe* aResult); +}; + +} // namespace ipc + +} // namespace mozilla + +#endif /* mozilla_dom_SessionHistoryEntry_h */ diff --git a/docshell/shistory/moz.build b/docshell/shistory/moz.build new file mode 100644 index 0000000000..f3b86c207b --- /dev/null +++ b/docshell/shistory/moz.build @@ -0,0 +1,42 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + "nsIBFCacheEntry.idl", + "nsISHEntry.idl", + "nsISHistory.idl", + "nsISHistoryListener.idl", +] + +XPIDL_MODULE = "shistory" + +EXPORTS += [ + "nsSHEntry.h", + "nsSHEntryShared.h", + "nsSHistory.h", +] + +EXPORTS.mozilla.dom += [ + "ChildSHistory.h", + "SessionHistoryEntry.h", +] + +UNIFIED_SOURCES += [ + "ChildSHistory.cpp", + "nsSHEntry.cpp", + "nsSHEntryShared.cpp", + "nsSHistory.cpp", + "SessionHistoryEntry.cpp", +] + +LOCAL_INCLUDES += [ + "/docshell/base", + "/dom/base", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/docshell/shistory/nsIBFCacheEntry.idl b/docshell/shistory/nsIBFCacheEntry.idl new file mode 100644 index 0000000000..2e24c67e35 --- /dev/null +++ b/docshell/shistory/nsIBFCacheEntry.idl @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface lets you evict a document from the back/forward cache. + */ +[scriptable, builtinclass, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)] +interface nsIBFCacheEntry : nsISupports +{ + void RemoveFromBFCacheSync(); + void RemoveFromBFCacheAsync(); +}; diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl new file mode 100644 index 0000000000..cdbaeaf83d --- /dev/null +++ b/docshell/shistory/nsISHEntry.idl @@ -0,0 +1,475 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/** + * The interface to nsISHentry. Each document or subframe in + * Session History will have a nsISHEntry associated with it which will + * hold all information required to recreate the document from history + */ + +#include "nsISupports.idl" + +interface nsIContentSecurityPolicy; +interface nsIMutableArray; +interface nsILayoutHistoryState; +interface nsIContentViewer; +interface nsIURI; +interface nsIInputStream; +interface nsIDocShellTreeItem; +interface nsIStructuredCloneContainer; +interface nsIBFCacheEntry; +interface nsIPrincipal; +interface nsISHistory; +interface nsIReferrerInfo; + +%{C++ +#include "nsRect.h" +class nsDocShellEditorData; + +namespace mozilla { +namespace dom { + +class SHEntrySharedParentState; + +} +} +class nsSHEntryShared; +class nsDocShellLoadState; +struct EntriesAndBrowsingContextData; +%} +[ref] native nsIntRect(nsIntRect); +[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData); +[ptr] native nsDocShellLoadStatePtr(nsDocShellLoadState); +[ptr] native SHEntrySharedParentStatePtr(mozilla::dom::SHEntrySharedParentState); +webidl BrowsingContext; + +[builtinclass, scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)] +interface nsISHEntry : nsISupports +{ + /** + * The URI of the current entry. + */ + [infallible] attribute nsIURI URI; + + /** + * The original URI of the current entry. If an entry is the result of a + * redirect this attribute holds the original URI. + */ + [infallible] attribute nsIURI originalURI; + + /** + * URL as stored from nsILoadInfo.resultPrincipalURI. See nsILoadInfo + * for more details. + */ + [infallible] attribute nsIURI resultPrincipalURI; + + /** + * If non-null, the URI as it was before query stripping was performed. + */ + [infallible] attribute nsIURI unstrippedURI; + + /** + * This flag remembers whether channel has LOAD_REPLACE set. + */ + [infallible] attribute boolean loadReplace; + + /** + * The title of the current entry. + */ + // XXX: make it [infallible] when AString supports that (bug 1491187). + attribute AString title; + + /** + * The name of the browsing context. + */ + attribute AString name; + + /** + * Was the entry created as a result of a subframe navigation? + * - Will be 'false' when a frameset page is visited for the first time. + * - Will be 'true' for all history entries created as a result of a + * subframe navigation. + */ + [infallible] attribute boolean isSubFrame; + + /** + * Whether the user interacted with the page while this entry was active. + * This includes interactions with subframe documents associated with + * child entries that are rooted at this entry. + * This field will only be set on top-level entries. + */ + [infallible] attribute boolean hasUserInteraction; + + /** + * Whether the load that created this entry was triggered by user activation. + * (e.g.: The user clicked a link) + * Remembering this flag enables replaying the sec-fetch-* headers. + */ + [infallible] attribute boolean hasUserActivation; + + /** Referrer Info*/ + [infallible] attribute nsIReferrerInfo referrerInfo; + + /** Content viewer, for fast restoration of presentation */ + [infallible] attribute nsIContentViewer contentViewer; + + [infallible] readonly attribute boolean isInBFCache; + + /** Whether the content viewer is marked "sticky" */ + [infallible] attribute boolean sticky; + + /** Saved state of the global window object */ + [infallible] attribute nsISupports windowState; + + /** Saved refresh URI list for the content viewer */ + [infallible] attribute nsIMutableArray refreshURIList; + + /** Post Data for the document */ + [infallible] attribute nsIInputStream postData; + + /** LayoutHistoryState for scroll position and form values */ + [infallible] attribute nsILayoutHistoryState layoutHistoryState; + + /** parent of this entry */ + [infallible] attribute nsISHEntry parent; + + /** + * The loadType for this entry. This is typically loadHistory except + * when reload is pressed, it has the appropriate reload flag + */ + [infallible] attribute unsigned long loadType; + + /** + * An ID to help identify this entry from others during + * subframe navigation + */ + [infallible] attribute unsigned long ID; + + /** The cache key for the entry */ + [infallible] attribute unsigned long cacheKey; + + /** Should the layoutHistoryState be saved? */ + [infallible] attribute boolean saveLayoutStateFlag; + + /** + * attribute to indicate the content-type of the document that this + * is a session history entry for + */ + // XXX: make it [infallible] when ACString supports that (bug 1491187). + attribute ACString contentType; + + /** + * If we created this SHEntry via history.pushState or modified it via + * history.replaceState, and if we changed the SHEntry's URI via the + * push/replaceState call, and if the SHEntry's new URI differs from its + * old URI by more than just the hash, then we set this field to true. + * + * Additionally, if this SHEntry was created by calling pushState from a + * SHEntry whose URI was modified, this SHEntry's URIWasModified field is + * true. + */ + [infallible] attribute boolean URIWasModified; + + /** + * Get the principal, if any, that was associated with the channel + * that the document that was loaded to create this history entry + * came from. + */ + [infallible] attribute nsIPrincipal triggeringPrincipal; + + /** + * Get the principal, if any, that is used when the inherit flag + * is set. + */ + [infallible] attribute nsIPrincipal principalToInherit; + + /** + * Get the storage principal, if any, that is used when the inherit flag is + * set. + */ + [infallible] attribute nsIPrincipal partitionedPrincipalToInherit; + + /** + * Get the csp, if any, that was used for this document load. That + * is not the CSP that was applied to subresource loads within the + * document, but the CSP that was applied to this document load. + */ + [infallible] attribute nsIContentSecurityPolicy csp; + + /** + * Get/set data associated with this history state via a pushState() call, + * serialized using structured clone. + **/ + [infallible] attribute nsIStructuredCloneContainer stateData; + + /** + * The history ID of the docshell. + */ + // Would be [infallible], but we don't support that property for nsIDPtr. + attribute nsIDRef docshellID; + + /** + * True if this SHEntry corresponds to a document created by a srcdoc + * iframe. Set when a value is assigned to srcdocData. + */ + [infallible] readonly attribute boolean isSrcdocEntry; + + /** + * Contents of the srcdoc attribute in a srcdoc iframe to be loaded instead + * of the URI. Similar to a Data URI, this information is needed to + * recreate the document at a later stage. + * Setting this sets isSrcdocEntry to true + */ + // XXX: make it [infallible] when AString supports that (bug 1491187). + attribute AString srcdocData; + + /** + * When isSrcdocEntry is true, this contains the baseURI of the srcdoc + * document for use in situations where it cannot otherwise be determined, + * for example with view-source. + */ + [infallible] attribute nsIURI baseURI; + + /** + * Sets/gets the current scroll restoration state, + * if true == "manual", false == "auto". + */ + [infallible] attribute boolean scrollRestorationIsManual; + + /** + * Flag to indicate that the history entry was originally loaded in the + * current process. This flag does not survive a browser process switch. + */ + [infallible] readonly attribute boolean loadedInThisProcess; + + /** + * The session history it belongs to. This is set only on the root entries. + */ + [noscript, infallible] attribute nsISHistory shistory; + + /** + * A number that is assigned by the sHistory when the entry is activated + */ + [noscript, infallible] attribute unsigned long lastTouched; + + /** + * The current number of nsISHEntries which are immediate children of this + * SHEntry. + */ + [infallible] readonly attribute long childCount; + + /** + * When an entry is serving is within nsISHistory's array of entries, this + * property specifies if it should persist. If not it will be replaced by + * new additions to the list. + */ + [infallible] attribute boolean persist; + + /** + * Set/Get the visual viewport scroll position if session history is + * changed through anchor navigation or pushState. + */ + void setScrollPosition(in long x, in long y); + void getScrollPosition(out long x, out long y); + + /** + * Saved position and dimensions of the content viewer; we must adjust the + * root view's widget accordingly if this has changed when the presentation + * is restored. + */ + [noscript, notxpcom] void getViewerBounds(in nsIntRect bounds); + [noscript, notxpcom] void setViewerBounds([const] in nsIntRect bounds); + + /** + * Saved child docshells corresponding to contentViewer. The child shells + * are restored as children of the parent docshell, in this order, when the + * parent docshell restores a saved presentation. + */ + + /** Append a child shell to the end of our list. */ + [noscript, notxpcom] void addChildShell(in nsIDocShellTreeItem shell); + + /** + * Get the child shell at |index|; returns null if |index| is out of bounds. + */ + [noscript] nsIDocShellTreeItem childShellAt(in long index); + + /** + * Clear the child shell list. + */ + [noscript, notxpcom] void clearChildShells(); + + /** + * Ensure that the cached presentation members are self-consistent. + * If either |contentViewer| or |windowState| are null, then all of the + * following members are cleared/reset: + * contentViewer, sticky, windowState, viewerBounds, childShells, + * refreshURIList. + */ + [noscript, notxpcom] void syncPresentationState(); + + /** + * Initialises `layoutHistoryState` if it doesn't already exist + * and returns a reference to it. + */ + nsILayoutHistoryState initLayoutHistoryState(); + + /** Additional ways to create an entry */ + [noscript] void create(in nsIURI URI, in AString title, + in nsIInputStream inputStream, + in unsigned long cacheKey, + in ACString contentType, + in nsIPrincipal triggeringPrincipal, + in nsIPrincipal principalToInherit, + in nsIPrincipal partitionedPrincipalToInherit, + in nsIContentSecurityPolicy aCsp, + in nsIDRef docshellID, + in boolean dynamicCreation, + in nsIURI originalURI, + in nsIURI resultPrincipalURI, + in nsIURI unstrippedURI, + in bool loadReplace, + in nsIReferrerInfo referrerInfo, + in AString srcdoc, + in bool srcdocEntry, + in nsIURI baseURI, + in bool saveLayoutState, + in bool expired, + in bool userActivation); + + nsISHEntry clone(); + + /** + * Gets the owning pointer to the editor data assosicated with + * this shistory entry. This forgets its pointer, so free it when + * you're done. + */ + [noscript, notxpcom] nsDocShellEditorDataPtr forgetEditorData(); + + /** + * Sets the owning pointer to the editor data assosicated with + * this shistory entry. Unless forgetEditorData() is called, this + * shentry will destroy the editor data when it's destroyed. + */ + [noscript, notxpcom] void setEditorData(in nsDocShellEditorDataPtr aData); + + /** Returns true if this shistory entry is storing a detached editor. */ + [noscript, notxpcom] boolean hasDetachedEditor(); + + /** + * Returns true if the related docshell was added because of + * dynamic addition of an iframe/frame. + */ + [noscript, notxpcom] boolean isDynamicallyAdded(); + + /** + * Returns true if any of the child entries returns true + * when isDynamicallyAdded is called on it. + */ + boolean hasDynamicallyAddedChild(); + + /** + * Does this SHEntry point to the given BFCache entry? If so, evicting + * the BFCache entry will evict the SHEntry, since the two entries + * correspond to the same document. + */ + [noscript, notxpcom] + boolean hasBFCacheEntry(in SHEntrySharedParentStatePtr aEntry); + + /** + * Adopt aEntry's BFCacheEntry, so now both this and aEntry point to + * aEntry's BFCacheEntry. + */ + void adoptBFCacheEntry(in nsISHEntry aEntry); + + /** + * Create a new BFCache entry and drop our reference to our old one. This + * call unlinks this SHEntry from any other SHEntries for its document. + */ + void abandonBFCacheEntry(); + + /** + * Does this SHEntry correspond to the same document as aEntry? This is + * true iff the two SHEntries have the same BFCacheEntry. So in particular, + * sharesDocumentWith(aEntry) is guaranteed to return true if it's + * preceded by a call to adoptBFCacheEntry(aEntry). + */ + boolean sharesDocumentWith(in nsISHEntry aEntry); + + /** + * Sets an SHEntry to reflect that it is a history type load. This is the + * equivalent to doing + * + * shEntry.loadType = 4; + * + * in js, but is easier to maintain and less opaque. + */ + void setLoadTypeAsHistory(); + + /** + * Add a new child SHEntry. If offset is -1 adds to the end of the list. + */ + void AddChild(in nsISHEntry aChild, in long aOffset, + [optional,default(false)] in bool aUseRemoteSubframes); + + /** + * Remove a child SHEntry. + */ + [noscript] void RemoveChild(in nsISHEntry aChild); + + /** + * Get child at an index. + */ + nsISHEntry GetChildAt(in long aIndex); + + /** + * If this entry has no dynamically added child, get the child SHEntry + * at the given offset. The loadtype of the returned entry is set + * to its parent's loadtype. + */ + [notxpcom] void GetChildSHEntryIfHasNoDynamicallyAddedChild(in long aChildOffset, + out nsISHEntry aChild); + + /** + * Replaces a child which is for the same docshell as aNewChild + * with aNewChild. + * @throw if nothing was replaced. + */ + [noscript] void ReplaceChild(in nsISHEntry aNewChild); + + /** + * Remove all children of this entry and call abandonBFCacheEntry. + */ + [notxpcom] void ClearEntry(); + + /** + * Create nsDocShellLoadState and fill it with information. + * Don't set nsSHEntry here to avoid serializing it. + */ + [noscript] nsDocShellLoadStatePtr CreateLoadInfo(); + + [infallible] readonly attribute unsigned long long bfcacheID; + + /** + * Sync up the docshell and session history trees for subframe navigation. + * + * @param aEntry new entry + * @param aTopBC top BC corresponding to the root ancestor + of the docshell that called this method + * @param aIgnoreBC current BC + */ + [notxpcom] void SyncTreesForSubframeNavigation(in nsISHEntry aEntry, + in BrowsingContext aTopBC, + in BrowsingContext aIgnoreBC); + + /** + * If browser.history.collectWireframes is true, this will get populated + * with a Wireframe upon document navigation / pushState. This will only + * be set for nsISHEntry's accessed in the parent process with + * sessionHistoryInParent enabled. See Document.webidl for more details on + * what a Wireframe is. + */ + [implicit_jscontext] attribute jsval wireframe; +}; diff --git a/docshell/shistory/nsISHistory.idl b/docshell/shistory/nsISHistory.idl new file mode 100644 index 0000000000..8ea23693d9 --- /dev/null +++ b/docshell/shistory/nsISHistory.idl @@ -0,0 +1,291 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIBFCacheEntry; +interface nsISHEntry; +interface nsISHistoryListener; +interface nsIURI; +webidl BrowsingContext; + +%{C++ +#include "nsTArrayForwardDeclare.h" +#include "mozilla/Maybe.h" +struct EntriesAndBrowsingContextData; +namespace mozilla { +namespace dom { +class SHEntrySharedParentState; +} // namespace dom +} // namespace mozilla +%} + +[ref] native nsDocshellIDArray(nsTArray); +native MaybeInt32(mozilla::Maybe); +[ptr] native SHEntrySharedParentStatePtr(mozilla::dom::SHEntrySharedParentState); +/** + * An interface to the primary properties of the Session History + * component. In an embedded browser environment, the nsIWebBrowser + * object creates an instance of session history for each open window. + * A handle to the session history object can be obtained from + * nsIWebNavigation. In a non-embedded situation, the owner of the + * session history component must create a instance of it and set + * it in the nsIWebNavigation object. + * This interface is accessible from javascript. + */ + +[builtinclass, scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)] +interface nsISHistory: nsISupports +{ + /** + * A readonly property of the interface that returns + * the number of toplevel documents currently available + * in session history. + */ + [infallible] readonly attribute long count; + + /** + * The index of the current document in session history. Not infallible + * because setting can fail if the assigned value is out of range. + */ + attribute long index; + + /** + * A readonly property of the interface that returns + * the index of the last document that started to load and + * didn't finished yet. When document finishes the loading + * value -1 is returned. + */ + [infallible] readonly attribute long requestedIndex; + + /** + * Artifically set the |requestedIndex| for this nsISHEntry to the given + * index. This is used when resuming a cross-process load from a different + * process. + */ + [noscript, notxpcom] + void internalSetRequestedIndex(in long aRequestedIndex); + + /** + * Get the history entry at a given index. Returns non-null on success. + * + * @param index The index value whose entry is requested. + * The oldest entry is located at index == 0. + * @return The found entry; never null. + */ + nsISHEntry getEntryAtIndex(in long aIndex); + + /** + * Called to purge older documents from history. + * Documents can be removed from session history for various + * reasons. For example to control memory usage of the browser, to + * prevent users from loading documents from history, to erase evidence of + * prior page loads etc... + * + * @param numEntries The number of toplevel documents to be + * purged from history. During purge operation, + * the latest documents are maintained and older + * 'numEntries' documents are removed from history. + * @throws NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA + * Purge was vetod. + * @throws NS_ERROR_FAILURE numEntries is + * invalid or out of bounds with the size of history. + */ + void purgeHistory(in long aNumEntries); + + /** + * Called to register a listener for the session history component. + * Listeners are notified when pages are loaded or purged from history. + * + * @param aListener Listener object to be notified for all + * page loads that initiate in session history. + * + * @note A listener object must implement + * nsISHistoryListener and nsSupportsWeakReference + * + * @see nsISHistoryListener + * @see nsSupportsWeakReference + */ + void addSHistoryListener(in nsISHistoryListener aListener); + + /** + * Called to remove a listener for the session history component. + * Listeners are notified when pages are loaded from history. + * + * @param aListener Listener object to be removed from + * session history. + * + * @note A listener object must implement + * nsISHistoryListener and nsSupportsWeakReference + * @see nsISHistoryListener + * @see nsSupportsWeakReference + */ + void removeSHistoryListener(in nsISHistoryListener aListener); + + void reloadCurrentEntry(); + + /** + * Load the entry at the particular index. + */ + [noscript] + void gotoIndex(in long aIndex, in boolean aUserActivation); + + /** + * If an element exists at the particular index and + * whether it has user interaction. + */ + [noscript,notxpcom] + boolean hasUserInteractionAtIndex(in long aIndex); + + /** + * Called to obtain the index to a given history entry. + * + * @param aEntry The entry to obtain the index of. + * + * @return NS_OK index for the history entry + * is obtained successfully. + * NS_ERROR_FAILURE Error in obtaining + * index for the given history entry. + */ + [noscript, notxpcom] + long getIndexOfEntry(in nsISHEntry aEntry); + + /** + * Add a new Entry to the History List. + * + * @param aEntry The entry to add. + * @param aPersist If true this specifies that the entry should + * persist in the list. If false, this means that + * when new entries are added this element will not + * appear in the session history list. + */ + void addEntry(in nsISHEntry aEntry, in boolean aPersist); + + /** + * Update the index maintained by sessionHistory + */ + void updateIndex(); + + /** + * Replace the nsISHEntry at a particular index + * + * @param aIndex The index at which the entry should be replaced. + * @param aReplaceEntry The replacement entry for the index. + */ + void replaceEntry(in long aIndex, in nsISHEntry aReplaceEntry); + + /** + * Notifies all registered session history listeners about an impending + * reload. + * + * @return Whether the operation can proceed. + */ + boolean notifyOnHistoryReload(); + + /** + * Evict content viewers which don't lie in the "safe" range around aIndex. + * In practice, this should leave us with no more than gHistoryMaxViewers + * viewers associated with this SHistory object. + * + * Also make sure that the total number of content viewers in all windows is + * not greater than our global max; if it is, evict viewers as appropriate. + * + * @param aIndex The index around which the "safe" range is + * centered. In general, if you just navigated the + * history, aIndex should be the index history was + * navigated to. + */ + void evictOutOfRangeContentViewers(in long aIndex); + + /** + * Evict the content viewer associated with a bfcache entry that has timed + * out. + */ + [noscript, notxpcom] + void evictExpiredContentViewerForEntry(in SHEntrySharedParentStatePtr aEntry); + + /** + * Evict all the content viewers in this session history + */ + void evictAllContentViewers(); + + /** + * Add a BFCache entry to expiration tracker so it gets evicted on + * expiration. + */ + [noscript, notxpcom] + void addToExpirationTracker(in SHEntrySharedParentStatePtr aEntry); + + /** + * Remove a BFCache entry from expiration tracker. + */ + [noscript, notxpcom] + void removeFromExpirationTracker(in SHEntrySharedParentStatePtr aEntry); + + /** + * Remove dynamic entries found at given index. + * + * @param aIndex Index to remove dynamic entries from. It will be + * passed to RemoveEntries as aStartIndex. + * @param aEntry (optional) The entry to start looking in for dynamic + * entries. Only the dynamic descendants of the + * entry will be removed. If not given, all dynamic + * entries at the index will be removed. + */ + [noscript, notxpcom] + void RemoveDynEntries(in long aIndex, in nsISHEntry aEntry); + + /** + * Similar to RemoveDynEntries, but instead of specifying an index, use the + * given BFCacheEntry to find the index and remove dynamic entries from the + * index. + * + * The method takes no effect if the bfcache entry is not or no longer hold + * by the SHistory instance. + * + * @param aEntry The bfcache entry to look up for index to remove + * dynamic entries from. + */ + [noscript, notxpcom] + void RemoveDynEntriesForBFCacheEntry(in nsIBFCacheEntry aEntry); + + /** + * Removes entries from the history if their docshellID is in + * aIDs array. + */ + [noscript, notxpcom] + void RemoveEntries(in nsDocshellIDArray aIDs, in long aStartIndex); + + /** + * Collect docshellIDs from aEntry's children and remove those + * entries from history. + * + * @param aEntry Children docshellID's will be collected from + * this entry and passed to RemoveEntries as aIDs. + */ + [noscript, notxpcom] + void RemoveFrameEntries(in nsISHEntry aEntry); + + [noscript] + void Reload(in unsigned long aReloadFlags); + + [notxpcom] void EnsureCorrectEntryAtCurrIndex(in nsISHEntry aEntry); + + [notxpcom] void EvictContentViewersOrReplaceEntry(in nsISHEntry aNewSHEntry, in bool aReplace); + + nsISHEntry createEntry(); + + [noscript] void AddToRootSessionHistory(in bool aCloneChildren, in nsISHEntry aOSHE, + in BrowsingContext aRootBC, in nsISHEntry aEntry, + in unsigned long aLoadType, + in bool aShouldPersist, + out MaybeInt32 aPreviousEntryIndex, + out MaybeInt32 aLoadedEntryIndex); + + [noscript] void AddChildSHEntryHelper(in nsISHEntry aCloneRef, in nsISHEntry aNewEntry, + in BrowsingContext aRootBC, in bool aCloneChildren); + + [noscript, notxpcom] boolean isEmptyOrHasEntriesForSingleTopLevelPage(); +}; diff --git a/docshell/shistory/nsISHistoryListener.idl b/docshell/shistory/nsISHistoryListener.idl new file mode 100644 index 0000000000..569cb25dca --- /dev/null +++ b/docshell/shistory/nsISHistoryListener.idl @@ -0,0 +1,88 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +/** + * nsISHistoryListener defines the interface one can implement to receive + * notifications about activities in session history and (for reloads) to be + * able to cancel them. + * + * A session history listener will be notified when pages are added, removed + * and loaded from session history. In the case of reloads, it can prevent them + * from happening by returning false from the corresponding callback method. + * + * A session history listener can be registered on a particular nsISHistory + * instance via the nsISHistory::addSHistoryListener() method. + * + * Listener methods should not alter the session history. Things are likely to + * go haywire if they do. + */ +[scriptable, uuid(125c0833-746a-400e-9b89-d2d18545c08a)] +interface nsISHistoryListener : nsISupports +{ + /** + * Called when a new document is added to session history. New documents are + * added to session history by docshell when new pages are loaded in a frame + * or content area, for example via nsIWebNavigation::loadURI() + * + * @param aNewURI The URI of the document to be added to session history. + * @param aOldIndex The index of the current history item before the + * operation. + */ + void OnHistoryNewEntry(in nsIURI aNewURI, in long aOldIndex); + + /** + * Called before the current document is reloaded, for example due to a + * nsIWebNavigation::reload() call. + */ + boolean OnHistoryReload(); + + /** + * Called before navigating to a session history entry by index, for example, + * when nsIWebNavigation::gotoIndex() is called. + */ + void OnHistoryGotoIndex(); + + /** + * Called before entries are removed from the start of session history. + * Entries can be removed from session history for various reasons, for + * example to control the memory usage of the browser, to prevent users from + * loading documents from history, to erase evidence of prior page loads, etc. + * + * To purge documents from session history call nsISHistory::PurgeHistory(). + * + * @param aNumEntries The number of entries being removed. + */ + void OnHistoryPurge(in long aNumEntries); + + /** + * Called before entries are removed from the end of session history. This + * occurs when navigating to a new page while on a previous session entry. + * + * @param aNumEntries The number of entries being removed. + */ + void OnHistoryTruncate(in long aNumEntries); + + /** + * Called before an entry is replaced in the session history. Entries are + * replaced when navigating away from non-persistent history entries (such as + * about pages) and when history.replaceState is called. + */ + void OnHistoryReplaceEntry(); + + + /** + * Called whenever a content viewer is evicted. A content viewer is evicted + * whenever a bfcache entry has timed out or the number of total content + * viewers has exceeded the global max. This is used for testing only. + * + * @param aNumEvicted - number of content viewers evicted + */ + void OnContentViewerEvicted(in unsigned long aNumEvicted); +}; diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp new file mode 100644 index 0000000000..b384d7f609 --- /dev/null +++ b/docshell/shistory/nsSHEntry.cpp @@ -0,0 +1,1131 @@ +/* -*- 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 + +#include "nsDocShell.h" +#include "nsDocShellEditorData.h" +#include "nsDocShellLoadState.h" +#include "nsDocShellLoadTypes.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIContentViewer.h" +#include "nsIDocShellTreeItem.h" +#include "nsIInputStream.h" +#include "nsILayoutHistoryState.h" +#include "nsIMutableArray.h" +#include "nsIStructuredCloneContainer.h" +#include "nsIURI.h" +#include "nsSHEntryShared.h" +#include "nsSHistory.h" + +#include "mozilla/Logging.h" +#include "nsIReferrerInfo.h" + +extern mozilla::LazyLogModule gPageCacheLog; + +static uint32_t gEntryID = 0; + +nsSHEntry::nsSHEntry() + : mShared(new nsSHEntryShared()), + mLoadType(0), + mID(++gEntryID), // SessionStore has special handling for 0 values. + mScrollPositionX(0), + mScrollPositionY(0), + mParent(nullptr), + mLoadReplace(false), + mURIWasModified(false), + mIsSrcdocEntry(false), + mScrollRestorationIsManual(false), + mLoadedInThisProcess(false), + mPersist(true), + mHasUserInteraction(false), + mHasUserActivation(false) {} + +nsSHEntry::nsSHEntry(const nsSHEntry& aOther) + : mShared(aOther.mShared), + mURI(aOther.mURI), + mOriginalURI(aOther.mOriginalURI), + mResultPrincipalURI(aOther.mResultPrincipalURI), + mUnstrippedURI(aOther.mUnstrippedURI), + mReferrerInfo(aOther.mReferrerInfo), + 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), + mStateData(aOther.mStateData), + mSrcdocData(aOther.mSrcdocData), + mBaseURI(aOther.mBaseURI), + mLoadReplace(aOther.mLoadReplace), + mURIWasModified(aOther.mURIWasModified), + mIsSrcdocEntry(aOther.mIsSrcdocEntry), + mScrollRestorationIsManual(false), + mLoadedInThisProcess(aOther.mLoadedInThisProcess), + mPersist(aOther.mPersist), + mHasUserInteraction(false), + mHasUserActivation(aOther.mHasUserActivation) {} + +nsSHEntry::~nsSHEntry() { + // Null out the mParent pointers on all our kids. + for (nsISHEntry* entry : mChildren) { + if (entry) { + entry->SetParent(nullptr); + } + } +} + +NS_IMPL_ISUPPORTS(nsSHEntry, nsISHEntry) + +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::GetResultPrincipalURI(nsIURI** aResultPrincipalURI) { + *aResultPrincipalURI = mResultPrincipalURI; + NS_IF_ADDREF(*aResultPrincipalURI); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) { + mResultPrincipalURI = aResultPrincipalURI; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetUnstrippedURI(nsIURI** aUnstrippedURI) { + *aUnstrippedURI = mUnstrippedURI; + NS_IF_ADDREF(*aUnstrippedURI); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetUnstrippedURI(nsIURI* aUnstrippedURI) { + mUnstrippedURI = aUnstrippedURI; + 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::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) { + *aReferrerInfo = mReferrerInfo; + NS_IF_ADDREF(*aReferrerInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) { + mReferrerInfo = aReferrerInfo; + 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(nsAString& 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 = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetTitle(const nsAString& aTitle) { + mTitle = aTitle; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetName(nsAString& aName) { + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetName(const nsAString& aName) { + mName = aName; + 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::InitLayoutHistoryState(nsILayoutHistoryState** aState) { + if (!mShared->mLayoutHistoryState) { + nsCOMPtr historyState; + historyState = NS_NewLayoutHistoryState(); + SetLayoutHistoryState(historyState); + } + + nsCOMPtr state = GetLayoutHistoryState(); + state.forget(aState); + 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; +} + +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::GetHasUserInteraction(bool* aFlag) { + // The back button and menulist deal with root/top-level + // session history entries, thus we annotate only the root entry. + if (!mParent) { + *aFlag = mHasUserInteraction; + } else { + nsCOMPtr root = nsSHistory::GetRootSHEntry(this); + root->GetHasUserInteraction(aFlag); + } + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetHasUserInteraction(bool aFlag) { + // The back button and menulist deal with root/top-level + // session history entries, thus we annotate only the root entry. + if (!mParent) { + mHasUserInteraction = aFlag; + } else { + nsCOMPtr root = nsSHistory::GetRootSHEntry(this); + root->SetHasUserInteraction(aFlag); + } + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetHasUserActivation(bool* aFlag) { + *aFlag = mHasUserActivation; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetHasUserActivation(bool aFlag) { + mHasUserActivation = aFlag; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetCacheKey(uint32_t* aResult) { + *aResult = mShared->mCacheKey; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetCacheKey(uint32_t aCacheKey) { + mShared->mCacheKey = aCacheKey; + 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, + uint32_t aCacheKey, const nsACString& aContentType, + nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, const nsID& aDocShellID, + bool aDynamicCreation, nsIURI* aOriginalURI, nsIURI* aResultPrincipalURI, + nsIURI* aUnstrippedURI, bool aLoadReplace, nsIReferrerInfo* aReferrerInfo, + const nsAString& aSrcdocData, bool aSrcdocEntry, nsIURI* aBaseURI, + bool aSaveLayoutState, bool aExpired, bool aUserActivation) { + MOZ_ASSERT( + aTriggeringPrincipal, + "need a valid triggeringPrincipal to create a session history entry"); + + mURI = aURI; + mTitle = aTitle; + mPostData = aInputStream; + + // Set the LoadType by default to loadHistory during creation + mLoadType = LOAD_HISTORY; + + mShared->mCacheKey = aCacheKey; + mShared->mContentType = aContentType; + mShared->mTriggeringPrincipal = aTriggeringPrincipal; + mShared->mPrincipalToInherit = aPrincipalToInherit; + mShared->mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit; + mShared->mCsp = aCsp; + 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; + + mHasUserInteraction = false; + + mShared->mExpired = aExpired; + + mIsSrcdocEntry = aSrcdocEntry; + mSrcdocData = aSrcdocData; + + mBaseURI = aBaseURI; + + mLoadedInThisProcess = true; + + mOriginalURI = aOriginalURI; + mResultPrincipalURI = aResultPrincipalURI; + mUnstrippedURI = aUnstrippedURI; + mLoadReplace = aLoadReplace; + mReferrerInfo = aReferrerInfo; + + mHasUserActivation = aUserActivation; + + mShared->mLayoutHistoryState = nullptr; + + mShared->mSaveLayoutState = aSaveLayoutState; + + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetParent(nsISHEntry** 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_(void) +nsSHEntry::SetViewerBounds(const nsIntRect& aBounds) { + mShared->mViewerBounds = aBounds; +} + +NS_IMETHODIMP_(void) +nsSHEntry::GetViewerBounds(nsIntRect& aBounds) { + aBounds = mShared->mViewerBounds; +} + +NS_IMETHODIMP +nsSHEntry::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) { + NS_IF_ADDREF(*aTriggeringPrincipal = mShared->mTriggeringPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal) { + mShared->mTriggeringPrincipal = aTriggeringPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) { + NS_IF_ADDREF(*aPrincipalToInherit = mShared->mPrincipalToInherit); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) { + mShared->mPrincipalToInherit = aPrincipalToInherit; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetPartitionedPrincipalToInherit( + nsIPrincipal** aPartitionedPrincipalToInherit) { + NS_IF_ADDREF(*aPartitionedPrincipalToInherit = + mShared->mPartitionedPrincipalToInherit); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetPartitionedPrincipalToInherit( + nsIPrincipal* aPartitionedPrincipalToInherit) { + mShared->mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetCsp(nsIContentSecurityPolicy** aCsp) { + NS_IF_ADDREF(*aCsp = mShared->mCsp); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetCsp(nsIContentSecurityPolicy* aCsp) { + mShared->mCsp = aCsp; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::AdoptBFCacheEntry(nsISHEntry* aEntry) { + nsSHEntryShared* shared = static_cast(aEntry)->mShared; + NS_ENSURE_STATE(shared); + + mShared = shared; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SharesDocumentWith(nsISHEntry* aEntry, bool* aOut) { + NS_ENSURE_ARG_POINTER(aOut); + + *aOut = mShared == static_cast(aEntry)->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::GetScrollRestorationIsManual(bool* aIsManual) { + *aIsManual = mScrollRestorationIsManual; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetScrollRestorationIsManual(bool aIsManual) { + mScrollRestorationIsManual = aIsManual; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetLoadedInThisProcess(bool* aLoadedInThisProcess) { + *aLoadedInThisProcess = mLoadedInThisProcess; + 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, + bool aUseRemoteSubframes) { + 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 = aChild ? aChild->IsDynamicallyAdded() : false; + + // 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) { + if (entry->IsDynamicallyAdded()) { + 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) { + if (entry->IsDynamicallyAdded()) { + 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) { + // Under Fission, this can happen when a network-created iframe starts + // out in-process, moves out-of-process, and then switches back. At that + // point, we'll create a new network-created DocShell at the same index + // where we already have an entry for the original network-created + // DocShell. + // + // This should ideally stop being an issue once the Fission-aware + // session history rewrite is complete. + NS_ASSERTION( + aUseRemoteSubframes, + "Adding a child where we already have a child? This may misbehave"); + oldChild->SetParent(nullptr); + } + } + + mChildren.ReplaceObjectAt(aChild, aOffset); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::RemoveChild(nsISHEntry* aChild) { + NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE); + bool childRemoved = false; + if (aChild->IsDynamicallyAdded()) { + childRemoved = mChildren.RemoveObject(aChild); + } else { + int32_t index = mChildren.IndexOfObject(aChild); + if (index >= 0) { + // Other alive non-dynamic child docshells still keep mChildOffset, + // so we don't want to change the indices here. + mChildren.ReplaceObjectAt(nullptr, index); + childRemoved = true; + } + } + 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_(void) +nsSHEntry::GetChildSHEntryIfHasNoDynamicallyAddedChild(int32_t aChildOffset, + nsISHEntry** aChild) { + *aChild = nullptr; + + bool dynamicallyAddedChild = false; + HasDynamicallyAddedChild(&dynamicallyAddedChild); + if (dynamicallyAddedChild) { + return; + } + + // If the user did a shift-reload on this frameset page, + // we don't want to load the subframes from history. + if (IsForceReloadType(mLoadType) || mLoadType == LOAD_REFRESH) { + return; + } + + /* Before looking for the subframe's url, check + * the expiration status of the parent. If the parent + * has expired from cache, then subframes will not be + * loaded from history in certain situations. + * If the user pressed reload and the parent frame has expired + * from cache, we do not want to load the child frame from history. + */ + if (mShared->mExpired && (mLoadType == LOAD_RELOAD_NORMAL)) { + // The parent has expired. Return null. + *aChild = nullptr; + return; + } + // Get the child subframe from session history. + GetChildAt(aChildOffset, aChild); + if (*aChild) { + // Set the parent's Load Type on the child + (*aChild)->SetLoadType(mLoadType); + } +} + +NS_IMETHODIMP +nsSHEntry::ReplaceChild(nsISHEntry* aNewEntry) { + NS_ENSURE_STATE(aNewEntry); + + nsID docshellID; + aNewEntry->GetDocshellID(docshellID); + + for (int32_t i = 0; i < mChildren.Count(); ++i) { + if (mChildren[i]) { + nsID childDocshellID; + nsresult rv = mChildren[i]->GetDocshellID(childDocshellID); + NS_ENSURE_SUCCESS(rv, rv); + if (docshellID == childDocshellID) { + mChildren[i]->SetParent(nullptr); + mChildren.ReplaceObjectAt(aNewEntry, i); + return aNewEntry->SetParent(this); + } + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP_(void) nsSHEntry::ClearEntry() { + int32_t childCount = GetChildCount(); + // Remove all children of this entry + for (int32_t i = childCount - 1; i >= 0; i--) { + nsCOMPtr child; + GetChildAt(i, getter_AddRefs(child)); + RemoveChild(child); + } + AbandonBFCacheEntry(); +} + +NS_IMETHODIMP +nsSHEntry::GetStateData(nsIStructuredCloneContainer** aContainer) { + NS_IF_ADDREF(*aContainer = mStateData); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetStateData(nsIStructuredCloneContainer* aContainer) { + mStateData = aContainer; + return NS_OK; +} + +NS_IMETHODIMP_(bool) +nsSHEntry::IsDynamicallyAdded() { return mShared->mDynamicallyCreated; } + +NS_IMETHODIMP +nsSHEntry::HasDynamicallyAddedChild(bool* aAdded) { + *aAdded = false; + for (int32_t i = 0; i < mChildren.Count(); ++i) { + nsISHEntry* entry = mChildren[i]; + if (entry) { + *aAdded = entry->IsDynamicallyAdded(); + if (*aAdded) { + break; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetDocshellID(nsID& aID) { + aID = mShared->mDocShellID; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetDocshellID(const nsID& 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; +} + +NS_IMETHODIMP +nsSHEntry::GetShistory(nsISHistory** aSHistory) { + nsCOMPtr shistory(do_QueryReferent(mShared->mSHistory)); + shistory.forget(aSHistory); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetShistory(nsISHistory* aSHistory) { + nsWeakPtr shistory = do_GetWeakReference(aSHistory); + // mSHistory can not be changed once it's set + MOZ_ASSERT(!mShared->mSHistory || (mShared->mSHistory == shistory)); + mShared->mSHistory = shistory; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetLoadTypeAsHistory() { + // Set the LoadType by default to loadHistory during creation + mLoadType = LOAD_HISTORY; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetPersist(bool* aPersist) { + *aPersist = mPersist; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetPersist(bool aPersist) { + mPersist = aPersist; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) { + nsCOMPtr uri = GetURI(); + RefPtr loadState(new nsDocShellLoadState(uri)); + + nsCOMPtr originalURI = GetOriginalURI(); + loadState->SetOriginalURI(originalURI); + + mozilla::Maybe> emplacedResultPrincipalURI; + nsCOMPtr resultPrincipalURI = GetResultPrincipalURI(); + emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI)); + loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI); + + nsCOMPtr unstrippedURI = GetUnstrippedURI(); + loadState->SetUnstrippedURI(unstrippedURI); + + loadState->SetLoadReplace(GetLoadReplace()); + nsCOMPtr postData = GetPostData(); + loadState->SetPostDataStream(postData); + + nsAutoCString contentType; + GetContentType(contentType); + loadState->SetTypeHint(contentType); + + nsCOMPtr triggeringPrincipal = GetTriggeringPrincipal(); + loadState->SetTriggeringPrincipal(triggeringPrincipal); + nsCOMPtr principalToInherit = GetPrincipalToInherit(); + loadState->SetPrincipalToInherit(principalToInherit); + nsCOMPtr partitionedPrincipalToInherit = + GetPartitionedPrincipalToInherit(); + loadState->SetPartitionedPrincipalToInherit(partitionedPrincipalToInherit); + nsCOMPtr csp = GetCsp(); + loadState->SetCsp(csp); + nsCOMPtr referrerInfo = GetReferrerInfo(); + loadState->SetReferrerInfo(referrerInfo); + + // Do not inherit principal from document (security-critical!); + uint32_t flags = nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_NONE; + + // Passing nullptr as aSourceDocShell gives the same behaviour as before + // aSourceDocShell was introduced. According to spec we should be passing + // the source browsing context that was used when the history entry was + // first created. bug 947716 has been created to address this issue. + nsAutoString srcdoc; + nsCOMPtr baseURI; + if (GetIsSrcdocEntry()) { + GetSrcdocData(srcdoc); + baseURI = GetBaseURI(); + flags |= nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC; + } else { + srcdoc = VoidString(); + } + loadState->SetSrcdocData(srcdoc); + loadState->SetBaseURI(baseURI); + loadState->SetInternalLoadFlags(flags); + + loadState->SetFirstParty(true); + + loadState->SetHasValidUserGestureActivation(GetHasUserActivation()); + + loadState->SetSHEntry(this); + + // When we create a load state from the history entry we already know if + // https-first was able to upgrade the request from http to https. There is no + // point in re-retrying to upgrade. + loadState->SetIsExemptFromHTTPSOnlyMode(true); + + loadState.forget(aLoadState); + return NS_OK; +} + +NS_IMETHODIMP_(void) +nsSHEntry::SyncTreesForSubframeNavigation( + nsISHEntry* aEntry, mozilla::dom::BrowsingContext* aTopBC, + mozilla::dom::BrowsingContext* aIgnoreBC) { + // XXX Keep this in sync with + // SessionHistoryEntry::SyncTreesForSubframeNavigation + // + // We need to sync up the browsing context and session history trees for + // subframe navigation. If the load was in a subframe, we forward up to + // the top browsing context, which will then recursively sync up all browsing + // contexts to their corresponding entries in the new session history tree. If + // we don't do this, then we can cache a content viewer on the wrong cloned + // entry, and subsequently restore it at the wrong time. + nsCOMPtr newRootEntry = nsSHistory::GetRootSHEntry(aEntry); + if (newRootEntry) { + // newRootEntry is now the new root entry. + // Find the old root entry as well. + + // Need a strong ref. on |oldRootEntry| so it isn't destroyed when + // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639). + nsCOMPtr oldRootEntry = nsSHistory::GetRootSHEntry(this); + + if (oldRootEntry) { + nsSHistory::SwapEntriesData data = {aIgnoreBC, newRootEntry, nullptr}; + nsSHistory::SetChildHistoryEntry(oldRootEntry, aTopBC, 0, &data); + } + } +} + +void nsSHEntry::EvictContentViewer() { + nsCOMPtr viewer = GetContentViewer(); + if (viewer) { + mShared->NotifyListenersContentViewerEvicted(); + // Drop the presentation state before destroying the viewer, so that + // document teardown is able to correctly persist the state. + SetContentViewer(nullptr); + SyncPresentationState(); + viewer->Destroy(); + } +} + +NS_IMETHODIMP +nsSHEntry::SetContentViewer(nsIContentViewer* aViewer) { + return GetState()->SetContentViewer(aViewer); +} + +NS_IMETHODIMP +nsSHEntry::GetContentViewer(nsIContentViewer** aResult) { + *aResult = GetState()->mContentViewer; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetIsInBFCache(bool* aResult) { + *aResult = !!GetState()->mContentViewer; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::Clone(nsISHEntry** aResult) { + nsCOMPtr entry = new nsSHEntry(*this); + entry.forget(aResult); + 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::SetWindowState(nsISupports* aState) { + GetState()->mWindowState = aState; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetWindowState(nsISupports** aState) { + NS_IF_ADDREF(*aState = GetState()->mWindowState); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetRefreshURIList(nsIMutableArray** aList) { + NS_IF_ADDREF(*aList = GetState()->mRefreshURIList); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetRefreshURIList(nsIMutableArray* aList) { + GetState()->mRefreshURIList = aList; + return NS_OK; +} + +NS_IMETHODIMP_(void) +nsSHEntry::AddChildShell(nsIDocShellTreeItem* aShell) { + MOZ_ASSERT(aShell, "Null child shell added to history entry"); + GetState()->mChildShells.AppendObject(aShell); +} + +NS_IMETHODIMP +nsSHEntry::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell) { + NS_IF_ADDREF(*aShell = GetState()->mChildShells.SafeObjectAt(aIndex)); + return NS_OK; +} + +NS_IMETHODIMP_(void) +nsSHEntry::ClearChildShells() { GetState()->mChildShells.Clear(); } + +NS_IMETHODIMP_(void) +nsSHEntry::SyncPresentationState() { GetState()->SyncPresentationState(); } + +nsDocShellEditorData* nsSHEntry::ForgetEditorData() { + // XXX jlebar Check how this is used. + return GetState()->mEditorData.release(); +} + +void nsSHEntry::SetEditorData(nsDocShellEditorData* aData) { + NS_ASSERTION(!(aData && GetState()->mEditorData), + "We're going to overwrite an owning ref!"); + if (GetState()->mEditorData != aData) { + GetState()->mEditorData = mozilla::WrapUnique(aData); + } +} + +bool nsSHEntry::HasDetachedEditor() { + return GetState()->mEditorData != nullptr; +} + +bool nsSHEntry::HasBFCacheEntry( + mozilla::dom::SHEntrySharedParentState* aEntry) { + return GetState() == aEntry; +} + +NS_IMETHODIMP +nsSHEntry::AbandonBFCacheEntry() { + mShared = GetState()->Duplicate(); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetBfcacheID(uint64_t* aBFCacheID) { + *aBFCacheID = mShared->GetId(); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetWireframe(JSContext* aCx, JS::MutableHandle aOut) { + aOut.set(JS::NullValue()); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetWireframe(JSContext* aCx, JS::Handle aArg) { + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h new file mode 100644 index 0000000000..335aef859a --- /dev/null +++ b/docshell/shistory/nsSHEntry.h @@ -0,0 +1,72 @@ +/* -*- 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/. */ + +#ifndef nsSHEntry_h +#define nsSHEntry_h + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsISHEntry.h" +#include "nsString.h" + +#include "mozilla/Attributes.h" + +class nsSHEntryShared; +class nsIInputStream; +class nsIURI; +class nsIReferrerInfo; + +class nsSHEntry : public nsISHEntry { + public: + nsSHEntry(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISHENTRY + + virtual void EvictContentViewer(); + + static nsresult Startup(); + static void Shutdown(); + + nsSHEntryShared* GetState() { return mShared; } + + protected: + explicit nsSHEntry(const nsSHEntry& aOther); + virtual ~nsSHEntry(); + + // We share the state in here with other SHEntries which correspond to the + // same document. + RefPtr mShared; + + // See nsSHEntry.idl for comments on these members. + nsCOMPtr mURI; + nsCOMPtr mOriginalURI; + nsCOMPtr mResultPrincipalURI; + nsCOMPtr mUnstrippedURI; + nsCOMPtr mReferrerInfo; + nsString mTitle; + nsString mName; + nsCOMPtr mPostData; + uint32_t mLoadType; + uint32_t mID; + int32_t mScrollPositionX; + int32_t mScrollPositionY; + nsISHEntry* mParent; + nsCOMArray mChildren; + nsCOMPtr mStateData; + nsString mSrcdocData; + nsCOMPtr mBaseURI; + bool mLoadReplace; + bool mURIWasModified; + bool mIsSrcdocEntry; + bool mScrollRestorationIsManual; + bool mLoadedInThisProcess; + bool mPersist; + bool mHasUserInteraction; + bool mHasUserActivation; +}; + +#endif /* nsSHEntry_h */ diff --git a/docshell/shistory/nsSHEntryShared.cpp b/docshell/shistory/nsSHEntryShared.cpp new file mode 100644 index 0000000000..9b8bc3936d --- /dev/null +++ b/docshell/shistory/nsSHEntryShared.cpp @@ -0,0 +1,343 @@ +/* -*- 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 "nsSHEntryShared.h" + +#include "nsArray.h" +#include "nsContentUtils.h" +#include "nsDocShellEditorData.h" +#include "nsIContentViewer.h" +#include "nsISHistory.h" +#include "mozilla/dom/Document.h" +#include "nsILayoutHistoryState.h" +#include "nsIWebNavigation.h" +#include "nsSHistory.h" +#include "nsThreadUtils.h" +#include "nsFrameLoader.h" +#include "mozilla/Attributes.h" +#include "mozilla/Preferences.h" + +namespace dom = mozilla::dom; + +namespace { +uint64_t gSHEntrySharedID = 0; +nsTHashMap* + sIdToSharedState = nullptr; +} // namespace + +namespace mozilla { +namespace dom { + +/* static */ +uint64_t SHEntrySharedState::GenerateId() { + return nsContentUtils::GenerateProcessSpecificId(++gSHEntrySharedID); +} + +/* static */ +SHEntrySharedParentState* SHEntrySharedParentState::Lookup(uint64_t aId) { + MOZ_ASSERT(aId != 0); + + return sIdToSharedState ? sIdToSharedState->Get(aId) : nullptr; +} + +static void AddSHEntrySharedParentState( + SHEntrySharedParentState* aSharedState) { + MOZ_ASSERT(aSharedState->mId != 0); + + if (!sIdToSharedState) { + sIdToSharedState = + new nsTHashMap(); + } + sIdToSharedState->InsertOrUpdate(aSharedState->mId, aSharedState); +} + +SHEntrySharedParentState::SHEntrySharedParentState() { + AddSHEntrySharedParentState(this); +} + +SHEntrySharedParentState::SHEntrySharedParentState( + nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, const nsACString& aContentType) + : SHEntrySharedState(aTriggeringPrincipal, aPrincipalToInherit, + aPartitionedPrincipalToInherit, aCsp, aContentType) { + AddSHEntrySharedParentState(this); +} + +SHEntrySharedParentState::~SHEntrySharedParentState() { + MOZ_ASSERT(mId != 0); + + RefPtr loader = mFrameLoader; + SetFrameLoader(nullptr); + if (loader) { + if (NS_FAILED(NS_DispatchToCurrentThread(NS_NewRunnableFunction( + "SHEntrySharedParentState::~SHEntrySharedParentState", + [loader]() -> void { loader->AsyncDestroy(); })))) { + // Trigger AsyncDestroy immediately during shutdown. + loader->AsyncDestroy(); + } + } + + sIdToSharedState->Remove(mId); + if (sIdToSharedState->IsEmpty()) { + delete sIdToSharedState; + sIdToSharedState = nullptr; + } +} + +void SHEntrySharedParentState::ChangeId(uint64_t aId) { + MOZ_ASSERT(aId != 0); + + sIdToSharedState->Remove(mId); + mId = aId; + sIdToSharedState->InsertOrUpdate(mId, this); +} + +void SHEntrySharedParentState::CopyFrom(SHEntrySharedParentState* aEntry) { + mDocShellID = aEntry->mDocShellID; + mTriggeringPrincipal = aEntry->mTriggeringPrincipal; + mPrincipalToInherit = aEntry->mPrincipalToInherit; + mPartitionedPrincipalToInherit = aEntry->mPartitionedPrincipalToInherit; + mCsp = aEntry->mCsp; + mSaveLayoutState = aEntry->mSaveLayoutState; + mContentType.Assign(aEntry->mContentType); + mIsFrameNavigation = aEntry->mIsFrameNavigation; + mSticky = aEntry->mSticky; + mDynamicallyCreated = aEntry->mDynamicallyCreated; + mCacheKey = aEntry->mCacheKey; + mLastTouched = aEntry->mLastTouched; +} + +void dom::SHEntrySharedParentState::NotifyListenersContentViewerEvicted() { + if (nsCOMPtr shistory = do_QueryReferent(mSHistory)) { + RefPtr nsshistory = static_cast(shistory.get()); + nsshistory->NotifyListenersContentViewerEvicted(1); + } +} + +void SHEntrySharedChildState::CopyFrom(SHEntrySharedChildState* aEntry) { + mChildShells.AppendObjects(aEntry->mChildShells); +} + +void SHEntrySharedParentState::SetFrameLoader(nsFrameLoader* aFrameLoader) { + // If expiration tracker is removing this object, IsTracked() returns false. + if (GetExpirationState()->IsTracked() && mFrameLoader) { + if (nsCOMPtr shistory = do_QueryReferent(mSHistory)) { + shistory->RemoveFromExpirationTracker(this); + } + } + + mFrameLoader = aFrameLoader; + + if (mFrameLoader) { + if (nsCOMPtr shistory = do_QueryReferent(mSHistory)) { + shistory->AddToExpirationTracker(this); + } + } +} + +nsFrameLoader* SHEntrySharedParentState::GetFrameLoader() { + return mFrameLoader; +} + +} // namespace dom +} // namespace mozilla + +void nsSHEntryShared::Shutdown() {} + +nsSHEntryShared::~nsSHEntryShared() { + // The destruction can be caused by either the entry is removed from session + // history and no one holds the reference, or the whole session history is on + // destruction. We want to ensure that we invoke + // shistory->RemoveFromExpirationTracker for the former case. + RemoveFromExpirationTracker(); + + // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since + // there couldn't be any SHEntry holding this shared entry, and we noticed + // that calling RemoveDynEntriesForBFCacheEntry in the middle of + // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly + // before RemoveFromBFCacheSync. + mSHistory = nullptr; + if (mContentViewer) { + RemoveFromBFCacheSync(); + } +} + +NS_IMPL_QUERY_INTERFACE(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver) +NS_IMPL_ADDREF_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState) +NS_IMPL_RELEASE_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState) + +already_AddRefed nsSHEntryShared::Duplicate() { + RefPtr newEntry = new nsSHEntryShared(); + + newEntry->dom::SHEntrySharedParentState::CopyFrom(this); + newEntry->dom::SHEntrySharedChildState::CopyFrom(this); + + return newEntry.forget(); +} + +void nsSHEntryShared::RemoveFromExpirationTracker() { + nsCOMPtr shistory = do_QueryReferent(mSHistory); + if (shistory && GetExpirationState()->IsTracked()) { + shistory->RemoveFromExpirationTracker(this); + } +} + +void nsSHEntryShared::SyncPresentationState() { + if (mContentViewer && mWindowState) { + // If we have a content viewer and a window state, we should be ok. + return; + } + + DropPresentationState(); +} + +void nsSHEntryShared::DropPresentationState() { + RefPtr kungFuDeathGrip = this; + + if (mDocument) { + mDocument->SetBFCacheEntry(nullptr); + mDocument->RemoveMutationObserver(this); + mDocument = nullptr; + } + if (mContentViewer) { + mContentViewer->ClearHistoryEntry(); + } + + RemoveFromExpirationTracker(); + mContentViewer = nullptr; + mSticky = true; + mWindowState = nullptr; + mViewerBounds.SetRect(0, 0, 0, 0); + mChildShells.Clear(); + mRefreshURIList = nullptr; + mEditorData = nullptr; +} + +nsresult nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer) { + MOZ_ASSERT(!aViewer || !mContentViewer, + "SHEntryShared already contains viewer"); + + if (mContentViewer || !aViewer) { + DropPresentationState(); + } + + // If we're setting mContentViewer to null, state should already be cleared + // in the DropPresentationState() call above; If we're setting it to a + // non-null content viewer, the entry shouldn't have been tracked either. + MOZ_ASSERT(!GetExpirationState()->IsTracked()); + mContentViewer = aViewer; + + if (mContentViewer) { + // mSHistory is only set for root entries, but in general bfcache only + // applies to root entries as well. BFCache for subframe navigation has been + // disabled since 2005 in bug 304860. + if (nsCOMPtr shistory = do_QueryReferent(mSHistory)) { + shistory->AddToExpirationTracker(this); + } + + // Store observed document in strong pointer in case it is removed from + // the contentviewer + mDocument = mContentViewer->GetDocument(); + if (mDocument) { + mDocument->SetBFCacheEntry(this); + mDocument->AddMutationObserver(this); + } + } + + return NS_OK; +} + +nsresult nsSHEntryShared::RemoveFromBFCacheSync() { + MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!"); + + // The call to DropPresentationState could drop the last reference, so hold + // |this| until RemoveDynEntriesForBFCacheEntry finishes. + RefPtr kungFuDeathGrip = this; + + // DropPresentationState would clear mContentViewer. + nsCOMPtr viewer = mContentViewer; + DropPresentationState(); + + if (viewer) { + viewer->Destroy(); + } + + // Now that we've dropped the viewer, we have to clear associated dynamic + // subframe entries. + nsCOMPtr shistory = do_QueryReferent(mSHistory); + if (shistory) { + shistory->RemoveDynEntriesForBFCacheEntry(this); + } + + return NS_OK; +} + +nsresult nsSHEntryShared::RemoveFromBFCacheAsync() { + MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!"); + + // Check it again to play safe in release builds. + if (!mDocument) { + return NS_ERROR_UNEXPECTED; + } + + // DropPresentationState would clear mContentViewer & mDocument. Capture and + // release the references asynchronously so that the document doesn't get + // nuked mid-mutation. + nsCOMPtr viewer = mContentViewer; + RefPtr document = mDocument; + RefPtr self = this; + nsresult rv = mDocument->Dispatch( + mozilla::TaskCategory::Other, + NS_NewRunnableFunction( + "nsSHEntryShared::RemoveFromBFCacheAsync", + [self, viewer, document]() { + if (viewer) { + viewer->Destroy(); + } + + nsCOMPtr shistory = do_QueryReferent(self->mSHistory); + if (shistory) { + shistory->RemoveDynEntriesForBFCacheEntry(self); + } + })); + + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable."); + } else { + // Drop presentation. Only do this if we succeeded in posting the event + // since otherwise the document could be torn down mid-mutation, causing + // crashes. + DropPresentationState(); + } + + return NS_OK; +} + +void nsSHEntryShared::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo&) { + RemoveFromBFCacheAsync(); +} + +void nsSHEntryShared::AttributeChanged(dom::Element* aElement, + int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) { + RemoveFromBFCacheAsync(); +} + +void nsSHEntryShared::ContentAppended(nsIContent* aFirstNewContent) { + RemoveFromBFCacheAsync(); +} + +void nsSHEntryShared::ContentInserted(nsIContent* aChild) { + RemoveFromBFCacheAsync(); +} + +void nsSHEntryShared::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + RemoveFromBFCacheAsync(); +} diff --git a/docshell/shistory/nsSHEntryShared.h b/docshell/shistory/nsSHEntryShared.h new file mode 100644 index 0000000000..6bb6344202 --- /dev/null +++ b/docshell/shistory/nsSHEntryShared.h @@ -0,0 +1,219 @@ +/* -*- 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/. */ + +#ifndef nsSHEntryShared_h__ +#define nsSHEntryShared_h__ + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsExpirationTracker.h" +#include "nsIBFCacheEntry.h" +#include "nsIWeakReferenceUtils.h" +#include "nsRect.h" +#include "nsString.h" +#include "nsStubMutationObserver.h" + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" + +class nsSHEntry; +class nsISHEntry; +class nsISHistory; +class nsIContentSecurityPolicy; +class nsIContentViewer; +class nsIDocShellTreeItem; +class nsILayoutHistoryState; +class nsIPrincipal; +class nsDocShellEditorData; +class nsFrameLoader; +class nsIMutableArray; +class nsSHistory; + +// A document may have multiple SHEntries, either due to hash navigations or +// calls to history.pushState. SHEntries corresponding to the same document +// share many members; in particular, they share state related to the +// back/forward cache. +// +// The classes defined here are the vehicle for this sharing. +// +// Some of the state can only be stored in the process where we did the actual +// load, because that's where the objects live (eg. the content viewer). + +namespace mozilla { +namespace dom { +class Document; + +/** + * SHEntrySharedState holds shared state both in the child process and in the + * parent process. + */ +struct SHEntrySharedState { + SHEntrySharedState() : mId(GenerateId()) {} + SHEntrySharedState(const SHEntrySharedState& aState) = default; + SHEntrySharedState(nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, + const nsACString& aContentType) + : mId(GenerateId()), + mTriggeringPrincipal(aTriggeringPrincipal), + mPrincipalToInherit(aPrincipalToInherit), + mPartitionedPrincipalToInherit(aPartitionedPrincipalToInherit), + mCsp(aCsp), + mContentType(aContentType) {} + + // These members aren't copied by SHEntrySharedParentState::CopyFrom() because + // they're specific to a particular content viewer. + uint64_t mId = 0; + + // These members are copied by SHEntrySharedParentState::CopyFrom(). If you + // add a member here, be sure to update the CopyFrom() implementation. + nsCOMPtr mTriggeringPrincipal; + nsCOMPtr mPrincipalToInherit; + nsCOMPtr mPartitionedPrincipalToInherit; + nsCOMPtr mCsp; + nsCString mContentType; + // Child side updates layout history state when page is being unloaded or + // moved to bfcache. + nsCOMPtr mLayoutHistoryState; + uint32_t mCacheKey = 0; + bool mIsFrameNavigation = false; + bool mSaveLayoutState = true; + + protected: + static uint64_t GenerateId(); +}; + +/** + * SHEntrySharedParentState holds the shared state that can live in the parent + * process. + */ +class SHEntrySharedParentState : public SHEntrySharedState { + public: + friend class SessionHistoryInfo; + + uint64_t GetId() const { return mId; } + void ChangeId(uint64_t aId); + + void SetFrameLoader(nsFrameLoader* aFrameLoader); + + nsFrameLoader* GetFrameLoader(); + + void NotifyListenersContentViewerEvicted(); + + nsExpirationState* GetExpirationState() { return &mExpirationState; } + + SHEntrySharedParentState(); + SHEntrySharedParentState(nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, + const nsACString& aContentType); + + // This returns the existing SHEntrySharedParentState that was registered for + // aId, if one exists. + static SHEntrySharedParentState* Lookup(uint64_t aId); + + protected: + virtual ~SHEntrySharedParentState(); + NS_INLINE_DECL_VIRTUAL_REFCOUNTING_WITH_DESTROY(SHEntrySharedParentState, + Destroy()) + + virtual void Destroy() { delete this; } + + void CopyFrom(SHEntrySharedParentState* aSource); + + // These members are copied by SHEntrySharedParentState::CopyFrom(). If you + // add a member here, be sure to update the CopyFrom() implementation. + nsID mDocShellID{}; + + nsIntRect mViewerBounds{0, 0, 0, 0}; + + uint32_t mLastTouched = 0; + + // These members aren't copied by SHEntrySharedParentState::CopyFrom() because + // they're specific to a particular content viewer. + nsWeakPtr mSHistory; + + RefPtr mFrameLoader; + + nsExpirationState mExpirationState; + + bool mSticky = true; + bool mDynamicallyCreated = false; + + // This flag is about necko cache, not bfcache. + bool mExpired = false; +}; + +/** + * SHEntrySharedChildState holds the shared state that needs to live in the + * process where the document was loaded. + */ +class SHEntrySharedChildState { + protected: + void CopyFrom(SHEntrySharedChildState* aSource); + + public: + // These members are copied by SHEntrySharedChildState::CopyFrom(). If you + // add a member here, be sure to update the CopyFrom() implementation. + nsCOMArray mChildShells; + + // These members aren't copied by SHEntrySharedChildState::CopyFrom() because + // they're specific to a particular content viewer. + nsCOMPtr mContentViewer; + RefPtr mDocument; + nsCOMPtr mWindowState; + // FIXME Move to parent? + nsCOMPtr mRefreshURIList; + UniquePtr mEditorData; +}; + +} // namespace dom +} // namespace mozilla + +/** + * nsSHEntryShared holds the shared state if the session history is not stored + * in the parent process, or if the load itself happens in the parent process. + * Note, since nsSHEntryShared inherits both SHEntrySharedParentState and + * SHEntrySharedChildState and those have some same member variables, + * the ones from SHEntrySharedParentState should be used. + */ +class nsSHEntryShared final : public nsIBFCacheEntry, + public nsStubMutationObserver, + public mozilla::dom::SHEntrySharedParentState, + public mozilla::dom::SHEntrySharedChildState { + public: + static void EnsureHistoryTracker(); + static void Shutdown(); + + using SHEntrySharedParentState::SHEntrySharedParentState; + + already_AddRefed Duplicate(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIBFCACHEENTRY + + // The nsIMutationObserver bits we actually care about. + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + + private: + ~nsSHEntryShared(); + + friend class nsSHEntry; + + void RemoveFromExpirationTracker(); + void SyncPresentationState(); + void DropPresentationState(); + + nsresult SetContentViewer(nsIContentViewer* aViewer); +}; + +#endif diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp new file mode 100644 index 0000000000..740b636e11 --- /dev/null +++ b/docshell/shistory/nsSHistory.cpp @@ -0,0 +1,2408 @@ +/* -*- 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 "nsSHistory.h" + +#include + +#include "nsContentUtils.h" +#include "nsCOMArray.h" +#include "nsComponentManagerUtils.h" +#include "nsDocShell.h" +#include "nsFrameLoaderOwner.h" +#include "nsHashKeys.h" +#include "nsIContentViewer.h" +#include "nsIDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsIDocShellTreeItem.h" +#include "nsILayoutHistoryState.h" +#include "nsIObserverService.h" +#include "nsISHEntry.h" +#include "nsISHistoryListener.h" +#include "nsIURI.h" +#include "nsIXULRuntime.h" +#include "nsNetUtil.h" +#include "nsTHashMap.h" +#include "nsSHEntry.h" +#include "SessionHistoryEntry.h" +#include "nsTArray.h" +#include "prsystem.h" + +#include "mozilla/Attributes.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/RemoteWebProgressRequest.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/LinkedList.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProcessPriorityManager.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "nsIWebNavigation.h" +#include "nsDocShellLoadTypes.h" +#include "base/process.h" + +using namespace mozilla; +using namespace mozilla::dom; + +#define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" +#define PREF_SHISTORY_MAX_TOTAL_VIEWERS \ + "browser.sessionhistory.max_total_viewers" +#define CONTENT_VIEWER_TIMEOUT_SECONDS \ + "browser.sessionhistory.contentViewerTimeout" +// Observe fission.bfcacheInParent so that BFCache can be enabled/disabled when +// the pref is changed. +#define PREF_FISSION_BFCACHEINPARENT "fission.bfcacheInParent" + +// Default this to time out unused content viewers after 30 minutes +#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60) + +static const char* kObservedPrefs[] = {PREF_SHISTORY_SIZE, + PREF_SHISTORY_MAX_TOTAL_VIEWERS, + PREF_FISSION_BFCACHEINPARENT, nullptr}; + +static int32_t gHistoryMaxSize = 50; + +// List of all SHistory objects, used for content viewer cache eviction. +// When being destroyed, this helper removes everything from the list to avoid +// assertions when we leak. +struct ListHelper { +#ifdef DEBUG + ~ListHelper() { mList.clear(); } +#endif // DEBUG + + LinkedList mList; +}; + +static ListHelper gSHistoryList; +// Max viewers allowed total, across all SHistory objects - negative default +// means we will calculate how many viewers to cache based on total memory +int32_t nsSHistory::sHistoryMaxTotalViewers = -1; + +// A counter that is used to be able to know the order in which +// entries were touched, so that we can evict older entries first. +static uint32_t gTouchCounter = 0; + +extern mozilla::LazyLogModule gSHLog; + +LazyLogModule gSHistoryLog("nsSHistory"); + +#define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format) + +extern mozilla::LazyLogModule gPageCacheLog; +extern mozilla::LazyLogModule gSHIPBFCacheLog; + +// This macro makes it easier to print a log message which includes a URI's +// spec. Example use: +// +// nsIURI *uri = [...]; +// LOG_SPEC(("The URI is %s.", _spec), uri); +// +#define LOG_SPEC(format, uri) \ + PR_BEGIN_MACRO \ + if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \ + nsAutoCString _specStr("(null)"_ns); \ + if (uri) { \ + _specStr = uri->GetSpecOrDefault(); \ + } \ + const char* _spec = _specStr.get(); \ + LOG(format); \ + } \ + PR_END_MACRO + +// This macro makes it easy to log a message including an SHEntry's URI. +// For example: +// +// nsCOMPtr shentry = [...]; +// LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry); +// +#define LOG_SHENTRY_SPEC(format, shentry) \ + PR_BEGIN_MACRO \ + if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \ + nsCOMPtr uri = shentry->GetURI(); \ + LOG_SPEC(format, uri); \ + } \ + PR_END_MACRO + +// Calls a F on all registered session history listeners. +template +static void NotifyListeners(nsAutoTObserverArray& aListeners, + F&& f) { + for (const nsWeakPtr& weakPtr : aListeners.EndLimitedRange()) { + nsCOMPtr listener = do_QueryReferent(weakPtr); + if (listener) { + f(listener); + } + } +} + +class MOZ_STACK_CLASS SHistoryChangeNotifier { + public: + explicit SHistoryChangeNotifier(nsSHistory* aHistory) { + // If we're already in an update, the outermost change notifier will + // update browsing context in the destructor. + if (!aHistory->HasOngoingUpdate()) { + aHistory->SetHasOngoingUpdate(true); + mSHistory = aHistory; + } + } + + ~SHistoryChangeNotifier() { + if (mSHistory) { + MOZ_ASSERT(mSHistory->HasOngoingUpdate()); + mSHistory->SetHasOngoingUpdate(false); + + RefPtr rootBC = mSHistory->GetBrowsingContext(); + if (mozilla::SessionHistoryInParent() && rootBC) { + rootBC->Canonical()->HistoryCommitIndexAndLength(); + } + } + } + + RefPtr mSHistory; +}; + +enum HistCmd { HIST_CMD_GOTOINDEX, HIST_CMD_RELOAD }; + +class nsSHistoryObserver final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsSHistoryObserver() {} + + static void PrefChanged(const char* aPref, void* aSelf); + void PrefChanged(const char* aPref); + + protected: + ~nsSHistoryObserver() {} +}; + +StaticRefPtr gObserver; + +NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver) + +// static +void nsSHistoryObserver::PrefChanged(const char* aPref, void* aSelf) { + static_cast(aSelf)->PrefChanged(aPref); +} + +void nsSHistoryObserver::PrefChanged(const char* aPref) { + nsSHistory::UpdatePrefs(); + nsSHistory::GloballyEvictContentViewers(); +} + +NS_IMETHODIMP +nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "cacheservice:empty-cache") || + !strcmp(aTopic, "memory-pressure")) { + nsSHistory::GloballyEvictAllContentViewers(); + } + + return NS_OK; +} + +void nsSHistory::EvictContentViewerForEntry(nsISHEntry* aEntry) { + nsCOMPtr viewer = aEntry->GetContentViewer(); + if (viewer) { + LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for " + "owning SHEntry 0x%p at %s.", + viewer.get(), aEntry, _spec), + aEntry); + + // Drop the presentation state before destroying the viewer, so that + // document teardown is able to correctly persist the state. + NotifyListenersContentViewerEvicted(1); + aEntry->SetContentViewer(nullptr); + aEntry->SyncPresentationState(); + viewer->Destroy(); + } else if (nsCOMPtr she = do_QueryInterface(aEntry)) { + if (RefPtr frameLoader = she->GetFrameLoader()) { + nsCOMPtr owner = + do_QueryInterface(frameLoader->GetOwnerContent()); + RefPtr currentFrameLoader; + if (owner) { + currentFrameLoader = owner->GetFrameLoader(); + } + + // Only destroy non-current frameloader when evicting from the bfcache. + if (currentFrameLoader != frameLoader) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("nsSHistory::EvictContentViewerForEntry " + "destroying an nsFrameLoader.")); + NotifyListenersContentViewerEvicted(1); + she->SetFrameLoader(nullptr); + frameLoader->Destroy(); + } + } + } + + // When dropping bfcache, we have to remove associated dynamic entries as + // well. + int32_t index = GetIndexOfEntry(aEntry); + if (index != -1) { + RemoveDynEntries(index, aEntry); + } +} + +nsSHistory::nsSHistory(BrowsingContext* aRootBC) + : mRootBC(aRootBC->Id()), + mHasOngoingUpdate(false), + mIndex(-1), + mRequestedIndex(-1), + mRootDocShellID(aRootBC->GetHistoryID()) { + static bool sCalledStartup = false; + if (!sCalledStartup) { + Startup(); + sCalledStartup = true; + } + + // Add this new SHistory object to the list + gSHistoryList.mList.insertBack(this); + + // Init mHistoryTracker on setting mRootBC so we can bind its event + // target to the tabGroup. + mHistoryTracker = mozilla::MakeUnique( + this, + mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS, + CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT), + GetCurrentSerialEventTarget()); +} + +nsSHistory::~nsSHistory() { + // Clear mEntries explicitly here so that the destructor of the entries + // can still access nsSHistory in a reasonable way. + mEntries.Clear(); +} + +NS_IMPL_ADDREF(nsSHistory) +NS_IMPL_RELEASE(nsSHistory) + +NS_INTERFACE_MAP_BEGIN(nsSHistory) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory) + NS_INTERFACE_MAP_ENTRY(nsISHistory) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +// static +uint32_t nsSHistory::CalcMaxTotalViewers() { +// This value allows tweaking how fast the allowed amount of content viewers +// grows with increasing amounts of memory. Larger values mean slower growth. +#ifdef ANDROID +# define MAX_TOTAL_VIEWERS_BIAS 15.9 +#else +# define MAX_TOTAL_VIEWERS_BIAS 14 +#endif + + // Calculate an estimate of how many ContentViewers we should cache based + // on RAM. This assumes that the average ContentViewer is 4MB (conservative) + // and caps the max at 8 ContentViewers + // + // TODO: Should we split the cache memory betw. ContentViewer caching and + // nsCacheService? + // + // RAM | ContentViewers | on Android + // ------------------------------------- + // 32 Mb 0 0 + // 64 Mb 1 0 + // 128 Mb 2 0 + // 256 Mb 3 1 + // 512 Mb 5 2 + // 768 Mb 6 2 + // 1024 Mb 8 3 + // 2048 Mb 8 5 + // 3072 Mb 8 7 + // 4096 Mb 8 8 + uint64_t bytes = PR_GetPhysicalMemorySize(); + + if (bytes == 0) { + return 0; + } + + // Conversion from unsigned int64_t to double doesn't work on all platforms. + // We need to truncate the value at INT64_MAX to make sure we don't + // overflow. + if (bytes > INT64_MAX) { + bytes = INT64_MAX; + } + + double kBytesD = (double)(bytes >> 10); + + // This is essentially the same calculation as for nsCacheService, + // except that we divide the final memory calculation by 4, since + // we assume each ContentViewer takes on average 4MB + uint32_t viewers = 0; + double x = std::log(kBytesD) / std::log(2.0) - MAX_TOTAL_VIEWERS_BIAS; + if (x > 0) { + viewers = (uint32_t)(x * x - x + 2.001); // add .001 for rounding + viewers /= 4; + } + + // Cap it off at 8 max + if (viewers > 8) { + viewers = 8; + } + return viewers; +} + +// static +void nsSHistory::UpdatePrefs() { + Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize); + if (mozilla::SessionHistoryInParent() && !mozilla::BFCacheInParent()) { + sHistoryMaxTotalViewers = 0; + return; + } + + Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS, + &sHistoryMaxTotalViewers); + // If the pref is negative, that means we calculate how many viewers + // we think we should cache, based on total memory + if (sHistoryMaxTotalViewers < 0) { + sHistoryMaxTotalViewers = CalcMaxTotalViewers(); + } +} + +// static +nsresult nsSHistory::Startup() { + UpdatePrefs(); + + // The goal of this is to unbreak users who have inadvertently set their + // session history size to less than the default value. + int32_t defaultHistoryMaxSize = + Preferences::GetInt(PREF_SHISTORY_SIZE, 50, PrefValueKind::Default); + if (gHistoryMaxSize < defaultHistoryMaxSize) { + gHistoryMaxSize = defaultHistoryMaxSize; + } + + // Allow the user to override the max total number of cached viewers, + // but keep the per SHistory cached viewer limit constant + if (!gObserver) { + gObserver = new nsSHistoryObserver(); + Preferences::RegisterCallbacks(nsSHistoryObserver::PrefChanged, + kObservedPrefs, gObserver.get()); + + nsCOMPtr obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + // Observe empty-cache notifications so tahat clearing the disk/memory + // cache will also evict all content viewers. + obsSvc->AddObserver(gObserver, "cacheservice:empty-cache", false); + + // Same for memory-pressure notifications + obsSvc->AddObserver(gObserver, "memory-pressure", false); + } + } + + return NS_OK; +} + +// static +void nsSHistory::Shutdown() { + if (gObserver) { + Preferences::UnregisterCallbacks(nsSHistoryObserver::PrefChanged, + kObservedPrefs, gObserver.get()); + + nsCOMPtr obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache"); + obsSvc->RemoveObserver(gObserver, "memory-pressure"); + } + gObserver = nullptr; + } +} + +// static +already_AddRefed nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) { + nsCOMPtr rootEntry = aEntry; + nsCOMPtr result = nullptr; + while (rootEntry) { + result = rootEntry; + rootEntry = result->GetParent(); + } + + return result.forget(); +} + +// static +nsresult nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry, + BrowsingContext* aBC, + WalkHistoryEntriesFunc aCallback, + void* aData) { + NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE); + + int32_t childCount = aRootEntry->GetChildCount(); + for (int32_t i = 0; i < childCount; i++) { + nsCOMPtr childEntry; + aRootEntry->GetChildAt(i, getter_AddRefs(childEntry)); + if (!childEntry) { + // childEntry can be null for valid reasons, for example if the + // docshell at index i never loaded anything useful. + // Remember to clone also nulls in the child array (bug 464064). + aCallback(nullptr, nullptr, i, aData); + continue; + } + + BrowsingContext* childBC = nullptr; + if (aBC) { + for (BrowsingContext* child : aBC->Children()) { + // If the SH pref is on and we are in the parent process, update + // canonical BC directly + bool foundChild = false; + if (mozilla::SessionHistoryInParent() && XRE_IsParentProcess()) { + if (child->Canonical()->HasHistoryEntry(childEntry)) { + childBC = child; + foundChild = true; + } + } + + nsDocShell* docshell = static_cast(child->GetDocShell()); + if (docshell && docshell->HasHistoryEntry(childEntry)) { + childBC = docshell->GetBrowsingContext(); + foundChild = true; + } + + // XXX Simplify this once the old and new session history + // implementations don't run at the same time. + if (foundChild) { + break; + } + } + } + + nsresult rv = aCallback(childEntry, childBC, i, aData); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// callback data for WalkHistoryEntries +struct MOZ_STACK_CLASS CloneAndReplaceData { + CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry, + bool aCloneChildren, nsISHEntry* aDestTreeParent) + : cloneID(aCloneID), + cloneChildren(aCloneChildren), + replaceEntry(aReplaceEntry), + destTreeParent(aDestTreeParent) {} + + uint32_t cloneID; + bool cloneChildren; + nsISHEntry* replaceEntry; + nsISHEntry* destTreeParent; + nsCOMPtr resultEntry; +}; + +nsresult nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry, + BrowsingContext* aOwnerBC, + int32_t aChildIndex, void* aData) { + nsCOMPtr dest; + + CloneAndReplaceData* data = static_cast(aData); + uint32_t cloneID = data->cloneID; + nsISHEntry* replaceEntry = data->replaceEntry; + + if (!aEntry) { + if (data->destTreeParent) { + data->destTreeParent->AddChild(nullptr, aChildIndex); + } + return NS_OK; + } + + uint32_t srcID = aEntry->GetID(); + + nsresult rv = NS_OK; + if (srcID == cloneID) { + // Replace the entry + dest = replaceEntry; + } else { + // Clone the SHEntry... + rv = aEntry->Clone(getter_AddRefs(dest)); + NS_ENSURE_SUCCESS(rv, rv); + } + dest->SetIsSubFrame(true); + + if (srcID != cloneID || data->cloneChildren) { + // Walk the children + CloneAndReplaceData childData(cloneID, replaceEntry, data->cloneChildren, + dest); + rv = nsSHistory::WalkHistoryEntries(aEntry, aOwnerBC, CloneAndReplaceChild, + &childData); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (srcID != cloneID && aOwnerBC) { + nsSHistory::HandleEntriesToSwapInDocShell(aOwnerBC, aEntry, dest); + } + + if (data->destTreeParent) { + data->destTreeParent->AddChild(dest, aChildIndex); + } + data->resultEntry = dest; + return rv; +} + +// static +nsresult nsSHistory::CloneAndReplace( + nsISHEntry* aSrcEntry, BrowsingContext* aOwnerBC, uint32_t aCloneID, + nsISHEntry* aReplaceEntry, bool aCloneChildren, nsISHEntry** aDestEntry) { + NS_ENSURE_ARG_POINTER(aDestEntry); + NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE); + CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr); + nsresult rv = CloneAndReplaceChild(aSrcEntry, aOwnerBC, 0, &data); + data.resultEntry.swap(*aDestEntry); + return rv; +} + +// static +void nsSHistory::WalkContiguousEntries( + nsISHEntry* aEntry, const std::function& aCallback) { + MOZ_ASSERT(aEntry); + + nsCOMPtr shistory = aEntry->GetShistory(); + if (!shistory) { + // If there is no session history in the entry, it means this is not a root + // entry. So, we can return from here. + return; + } + + int32_t index = shistory->GetIndexOfEntry(aEntry); + int32_t count = shistory->GetCount(); + + nsCOMPtr targetURI = aEntry->GetURI(); + + // First, call the callback on the input entry. + aCallback(aEntry); + + // Walk backward to find the entries that have the same origin as the + // input entry. + for (int32_t i = index - 1; i >= 0; i--) { + RefPtr entry; + shistory->GetEntryAtIndex(i, getter_AddRefs(entry)); + if (entry) { + nsCOMPtr uri = entry->GetURI(); + if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI( + targetURI, uri, false, false))) { + break; + } + + aCallback(entry); + } + } + + // Then, Walk forward. + for (int32_t i = index + 1; i < count; i++) { + RefPtr entry; + shistory->GetEntryAtIndex(i, getter_AddRefs(entry)); + if (entry) { + nsCOMPtr uri = entry->GetURI(); + if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI( + targetURI, uri, false, false))) { + break; + } + + aCallback(entry); + } + } +} + +NS_IMETHODIMP +nsSHistory::AddChildSHEntryHelper(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry, + BrowsingContext* aRootBC, + bool aCloneChildren) { + MOZ_ASSERT(aRootBC->IsTop()); + + /* You are currently in the rootDocShell. + * You will get here when a subframe has a new url + * to load and you have walked up the tree all the + * way to the top to clone the current SHEntry hierarchy + * and replace the subframe where a new url was loaded with + * a new entry. + */ + nsCOMPtr child; + nsCOMPtr currentHE; + int32_t index = mIndex; + if (index < 0) { + return NS_ERROR_FAILURE; + } + + GetEntryAtIndex(index, getter_AddRefs(currentHE)); + NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE); + + nsresult rv = NS_OK; + uint32_t cloneID = aCloneRef->GetID(); + rv = nsSHistory::CloneAndReplace(currentHE, aRootBC, cloneID, aNewEntry, + aCloneChildren, getter_AddRefs(child)); + + if (NS_SUCCEEDED(rv)) { + rv = AddEntry(child, true); + if (NS_SUCCEEDED(rv)) { + child->SetDocshellID(aRootBC->GetHistoryID()); + } + } + + return rv; +} + +nsresult nsSHistory::SetChildHistoryEntry(nsISHEntry* aEntry, + BrowsingContext* aBC, + int32_t aEntryIndex, void* aData) { + SwapEntriesData* data = static_cast(aData); + if (!aBC || aBC == data->ignoreBC) { + return NS_OK; + } + + nsISHEntry* destTreeRoot = data->destTreeRoot; + + nsCOMPtr destEntry; + + if (data->destTreeParent) { + // aEntry is a clone of some child of destTreeParent, but since the + // trees aren't necessarily in sync, we'll have to locate it. + // Note that we could set aShell's entry to null if we don't find a + // corresponding entry under destTreeParent. + + uint32_t targetID = aEntry->GetID(); + + // First look at the given index, since this is the common case. + nsCOMPtr entry; + data->destTreeParent->GetChildAt(aEntryIndex, getter_AddRefs(entry)); + if (entry && entry->GetID() == targetID) { + destEntry.swap(entry); + } else { + int32_t childCount; + data->destTreeParent->GetChildCount(&childCount); + for (int32_t i = 0; i < childCount; ++i) { + data->destTreeParent->GetChildAt(i, getter_AddRefs(entry)); + if (!entry) { + continue; + } + + if (entry->GetID() == targetID) { + destEntry.swap(entry); + break; + } + } + } + } else { + destEntry = destTreeRoot; + } + + nsSHistory::HandleEntriesToSwapInDocShell(aBC, aEntry, destEntry); + // Now handle the children of aEntry. + SwapEntriesData childData = {data->ignoreBC, destTreeRoot, destEntry}; + return nsSHistory::WalkHistoryEntries(aEntry, aBC, SetChildHistoryEntry, + &childData); +} + +// static +void nsSHistory::HandleEntriesToSwapInDocShell( + mozilla::dom::BrowsingContext* aBC, nsISHEntry* aOldEntry, + nsISHEntry* aNewEntry) { + bool shPref = mozilla::SessionHistoryInParent(); + if (aBC->IsInProcess() || !shPref) { + nsDocShell* docshell = static_cast(aBC->GetDocShell()); + if (docshell) { + docshell->SwapHistoryEntries(aOldEntry, aNewEntry); + } + } else { + // FIXME Bug 1633988: Need to update entries? + } + + // XXX Simplify this once the old and new session history implementations + // don't run at the same time. + if (shPref && XRE_IsParentProcess()) { + aBC->Canonical()->SwapHistoryEntries(aOldEntry, aNewEntry); + } +} + +void nsSHistory::UpdateRootBrowsingContextState(BrowsingContext* aRootBC) { + if (aRootBC && aRootBC->EverAttached()) { + bool sameDocument = IsEmptyOrHasEntriesForSingleTopLevelPage(); + if (sameDocument != aRootBC->GetIsSingleToplevelInHistory()) { + // If the browsing context is discarded then its session history is + // invalid and will go away. + Unused << aRootBC->SetIsSingleToplevelInHistory(sameDocument); + } + } +} + +NS_IMETHODIMP +nsSHistory::AddToRootSessionHistory(bool aCloneChildren, nsISHEntry* aOSHE, + BrowsingContext* aRootBC, + nsISHEntry* aEntry, uint32_t aLoadType, + bool aShouldPersist, + Maybe* aPreviousEntryIndex, + Maybe* aLoadedEntryIndex) { + MOZ_ASSERT(aRootBC->IsTop()); + + nsresult rv = NS_OK; + + // If we need to clone our children onto the new session + // history entry, do so now. + if (aCloneChildren && aOSHE) { + uint32_t cloneID = aOSHE->GetID(); + nsCOMPtr newEntry; + nsSHistory::CloneAndReplace(aOSHE, aRootBC, cloneID, aEntry, true, + getter_AddRefs(newEntry)); + NS_ASSERTION(aEntry == newEntry, + "The new session history should be in the new entry"); + } + // This is the root docshell + bool addToSHistory = !LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY); + if (!addToSHistory) { + // Replace current entry in session history; If the requested index is + // valid, it indicates the loading was triggered by a history load, and + // we should replace the entry at requested index instead. + int32_t index = GetIndexForReplace(); + + // Replace the current entry with the new entry + if (index >= 0) { + rv = ReplaceEntry(index, aEntry); + } else { + // If we're trying to replace an inexistant shistory entry, append. + addToSHistory = true; + } + } + if (addToSHistory) { + // Add to session history + *aPreviousEntryIndex = Some(mIndex); + rv = AddEntry(aEntry, aShouldPersist); + *aLoadedEntryIndex = Some(mIndex); + MOZ_LOG(gPageCacheLog, LogLevel::Verbose, + ("Previous index: %d, Loaded index: %d", + aPreviousEntryIndex->value(), aLoadedEntryIndex->value())); + } + if (NS_SUCCEEDED(rv)) { + aEntry->SetDocshellID(aRootBC->GetHistoryID()); + } + return rv; +} + +/* Add an entry to the History list at mIndex and + * increment the index to point to the new entry + */ +NS_IMETHODIMP +nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist) { + NS_ENSURE_ARG(aSHEntry); + + nsCOMPtr shistoryOfEntry = aSHEntry->GetShistory(); + if (shistoryOfEntry && shistoryOfEntry != this) { + NS_WARNING( + "The entry has been associated to another nsISHistory instance. " + "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() " + "first if you're copying an entry from another nsISHistory."); + return NS_ERROR_FAILURE; + } + + aSHEntry->SetShistory(this); + + // If we have a root docshell, update the docshell id of the root shentry to + // match the id of that docshell + RefPtr rootBC = GetBrowsingContext(); + if (rootBC) { + aSHEntry->SetDocshellID(mRootDocShellID); + } + + if (mIndex >= 0) { + MOZ_ASSERT(mIndex < Length(), "Index out of range!"); + if (mIndex >= Length()) { + return NS_ERROR_FAILURE; + } + + if (mEntries[mIndex] && !mEntries[mIndex]->GetPersist()) { + NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); }); + aSHEntry->SetPersist(aPersist); + mEntries[mIndex] = aSHEntry; + UpdateRootBrowsingContextState(); + return NS_OK; + } + } + SHistoryChangeNotifier change(this); + + int32_t truncating = Length() - 1 - mIndex; + if (truncating > 0) { + NotifyListeners(mListeners, + [truncating](auto l) { l->OnHistoryTruncate(truncating); }); + } + + nsCOMPtr uri = aSHEntry->GetURI(); + NotifyListeners(mListeners, + [&uri, this](auto l) { l->OnHistoryNewEntry(uri, mIndex); }); + + // Remove all entries after the current one, add the new one, and set the + // new one as the current one. + MOZ_ASSERT(mIndex >= -1); + aSHEntry->SetPersist(aPersist); + mEntries.TruncateLength(mIndex + 1); + mEntries.AppendElement(aSHEntry); + mIndex++; + if (mIndex > 0) { + UpdateEntryLength(mEntries[mIndex - 1], mEntries[mIndex], false); + } + + // Purge History list if it is too long + if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) { + PurgeHistory(Length() - gHistoryMaxSize); + } + + UpdateRootBrowsingContextState(); + + return NS_OK; +} + +void nsSHistory::NotifyOnHistoryReplaceEntry() { + NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); }); +} + +/* Get size of the history list */ +NS_IMETHODIMP +nsSHistory::GetCount(int32_t* aResult) { + MOZ_ASSERT(aResult, "null out param?"); + *aResult = Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetIndex(int32_t* aResult) { + MOZ_ASSERT(aResult, "null out param?"); + *aResult = mIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::SetIndex(int32_t aIndex) { + if (aIndex < 0 || aIndex >= Length()) { + return NS_ERROR_FAILURE; + } + + mIndex = aIndex; + return NS_OK; +} + +/* Get the requestedIndex */ +NS_IMETHODIMP +nsSHistory::GetRequestedIndex(int32_t* aResult) { + MOZ_ASSERT(aResult, "null out param?"); + *aResult = mRequestedIndex; + return NS_OK; +} + +NS_IMETHODIMP_(void) +nsSHistory::InternalSetRequestedIndex(int32_t aRequestedIndex) { + MOZ_ASSERT(aRequestedIndex >= -1 && aRequestedIndex < Length()); + mRequestedIndex = aRequestedIndex; +} + +NS_IMETHODIMP +nsSHistory::GetEntryAtIndex(int32_t aIndex, nsISHEntry** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + if (aIndex < 0 || aIndex >= Length()) { + return NS_ERROR_FAILURE; + } + + *aResult = mEntries[aIndex]; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP_(int32_t) +nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry) { + for (int32_t i = 0; i < Length(); i++) { + if (aSHEntry == mEntries[i]) { + return i; + } + } + + return -1; +} + +static void LogEntry(nsISHEntry* aEntry, int32_t aIndex, int32_t aTotal, + const nsCString& aPrefix, bool aIsCurrent) { + if (!aEntry) { + MOZ_LOG(gSHLog, LogLevel::Debug, + (" %s+- %i SH Entry null\n", aPrefix.get(), aIndex)); + return; + } + + nsCOMPtr uri = aEntry->GetURI(); + nsAutoString title, name; + aEntry->GetTitle(title); + aEntry->GetName(name); + + SHEntrySharedParentState* shared; + if (mozilla::SessionHistoryInParent()) { + shared = static_cast(aEntry)->SharedInfo(); + } else { + shared = static_cast(aEntry)->GetState(); + } + + nsID docShellId; + aEntry->GetDocshellID(docShellId); + + int32_t childCount = aEntry->GetChildCount(); + + MOZ_LOG(gSHLog, LogLevel::Debug, + ("%s%s+- %i SH Entry %p %" PRIu64 " %s\n", aIsCurrent ? ">" : " ", + aPrefix.get(), aIndex, aEntry, shared->GetId(), + nsIDToCString(docShellId).get())); + + nsCString prefix(aPrefix); + if (aIndex < aTotal - 1) { + prefix.AppendLiteral("| "); + } else { + prefix.AppendLiteral(" "); + } + + MOZ_LOG(gSHLog, LogLevel::Debug, + (" %s%s URL = %s\n", prefix.get(), childCount > 0 ? "|" : " ", + uri->GetSpecOrDefault().get())); + MOZ_LOG(gSHLog, LogLevel::Debug, + (" %s%s Title = %s\n", prefix.get(), childCount > 0 ? "|" : " ", + NS_LossyConvertUTF16toASCII(title).get())); + MOZ_LOG(gSHLog, LogLevel::Debug, + (" %s%s Name = %s\n", prefix.get(), childCount > 0 ? "|" : " ", + NS_LossyConvertUTF16toASCII(name).get())); + MOZ_LOG( + gSHLog, LogLevel::Debug, + (" %s%s Is in BFCache = %s\n", prefix.get(), childCount > 0 ? "|" : " ", + aEntry->GetIsInBFCache() ? "true" : "false")); + + nsCOMPtr prevChild; + for (int32_t i = 0; i < childCount; ++i) { + nsCOMPtr child; + aEntry->GetChildAt(i, getter_AddRefs(child)); + LogEntry(child, i, childCount, prefix, false); + child.swap(prevChild); + } +} + +void nsSHistory::LogHistory() { + if (!MOZ_LOG_TEST(gSHLog, LogLevel::Debug)) { + return; + } + + MOZ_LOG(gSHLog, LogLevel::Debug, ("nsSHistory %p\n", this)); + int32_t length = Length(); + for (int32_t i = 0; i < length; i++) { + LogEntry(mEntries[i], i, length, EmptyCString(), i == mIndex); + } +} + +void nsSHistory::WindowIndices(int32_t aIndex, int32_t* aOutStartIndex, + int32_t* aOutEndIndex) { + *aOutStartIndex = std::max(0, aIndex - nsSHistory::VIEWER_WINDOW); + *aOutEndIndex = std::min(Length() - 1, aIndex + nsSHistory::VIEWER_WINDOW); +} + +static void MarkAsInitialEntry( + SessionHistoryEntry* aEntry, + nsTHashMap& aHashtable) { + if (!aEntry->BCHistoryLength().Modified()) { + ++(aEntry->BCHistoryLength()); + } + aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry); + for (const RefPtr& entry : aEntry->Children()) { + if (entry) { + MarkAsInitialEntry(entry, aHashtable); + } + } +} + +static void ClearEntries(SessionHistoryEntry* aEntry) { + aEntry->ClearBCHistoryLength(); + for (const RefPtr& entry : aEntry->Children()) { + if (entry) { + ClearEntries(entry); + } + } +} + +NS_IMETHODIMP +nsSHistory::PurgeHistory(int32_t aNumEntries) { + if (Length() <= 0 || aNumEntries <= 0) { + return NS_ERROR_FAILURE; + } + + SHistoryChangeNotifier change(this); + + aNumEntries = std::min(aNumEntries, Length()); + + NotifyListeners(mListeners, + [aNumEntries](auto l) { l->OnHistoryPurge(aNumEntries); }); + + // Set all the entries hanging of the first entry that we keep + // (mEntries[aNumEntries]) as being created as the result of a load + // (so contributing one to their BCHistoryLength). + nsTHashMap docshellIDToEntry; + if (aNumEntries != Length()) { + nsCOMPtr she = + do_QueryInterface(mEntries[aNumEntries]); + if (she) { + MarkAsInitialEntry(she, docshellIDToEntry); + } + } + + // Reset the BCHistoryLength of all the entries that we're removing to a new + // counter with value 0 while decreasing their contribution to a shared + // BCHistoryLength. The end result is that they don't contribute to the + // BCHistoryLength of any other entry anymore. + for (int32_t i = 0; i < aNumEntries; ++i) { + nsCOMPtr she = do_QueryInterface(mEntries[i]); + if (she) { + ClearEntries(she); + } + } + + RefPtr rootBC = GetBrowsingContext(); + if (rootBC) { + rootBC->PreOrderWalk([&docshellIDToEntry](BrowsingContext* aBC) { + SessionHistoryEntry* entry = docshellIDToEntry.Get(aBC->GetHistoryID()); + Unused << aBC->SetHistoryEntryCount( + entry ? uint32_t(entry->BCHistoryLength()) : 0); + }); + } + + // Remove the first `aNumEntries` entries. + mEntries.RemoveElementsAt(0, aNumEntries); + + // Adjust the indices, but don't let them go below -1. + mIndex -= aNumEntries; + mIndex = std::max(mIndex, -1); + mRequestedIndex -= aNumEntries; + mRequestedIndex = std::max(mRequestedIndex, -1); + + if (rootBC && rootBC->GetDocShell()) { + rootBC->GetDocShell()->HistoryPurged(aNumEntries); + } + + UpdateRootBrowsingContextState(rootBC); + + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::AddSHistoryListener(nsISHistoryListener* aListener) { + NS_ENSURE_ARG_POINTER(aListener); + + // Check if the listener supports Weak Reference. This is a must. + // This listener functionality is used by embedders and we want to + // have the right ownership with who ever listens to SHistory + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_FAILURE; + } + + mListeners.AppendElementUnlessExists(listener); + return NS_OK; +} + +void nsSHistory::NotifyListenersContentViewerEvicted(uint32_t aNumEvicted) { + NotifyListeners(mListeners, [aNumEvicted](auto l) { + l->OnContentViewerEvicted(aNumEvicted); + }); +} + +NS_IMETHODIMP +nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener) { + // Make sure the listener that wants to be removed is the + // one we have in store. + nsWeakPtr listener = do_GetWeakReference(aListener); + mListeners.RemoveElement(listener); + return NS_OK; +} + +/* Replace an entry in the History list at a particular index. + * Do not update index or count. + */ +NS_IMETHODIMP +nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry) { + NS_ENSURE_ARG(aReplaceEntry); + + if (aIndex < 0 || aIndex >= Length()) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr shistoryOfEntry = aReplaceEntry->GetShistory(); + if (shistoryOfEntry && shistoryOfEntry != this) { + NS_WARNING( + "The entry has been associated to another nsISHistory instance. " + "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() " + "first if you're copying an entry from another nsISHistory."); + return NS_ERROR_FAILURE; + } + + aReplaceEntry->SetShistory(this); + + NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); }); + + aReplaceEntry->SetPersist(true); + mEntries[aIndex] = aReplaceEntry; + + UpdateRootBrowsingContextState(); + + return NS_OK; +} + +// Calls OnHistoryReload on all registered session history listeners. +// Listeners may return 'false' to cancel an action so make sure that we +// set the return value to 'false' if one of the listeners wants to cancel. +NS_IMETHODIMP +nsSHistory::NotifyOnHistoryReload(bool* aCanReload) { + *aCanReload = true; + + for (const nsWeakPtr& weakPtr : mListeners.EndLimitedRange()) { + nsCOMPtr listener = do_QueryReferent(weakPtr); + if (listener) { + bool retval = true; + + if (NS_SUCCEEDED(listener->OnHistoryReload(&retval)) && !retval) { + *aCanReload = false; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("nsSHistory::EvictOutOfRangeContentViewers %i", aIndex)); + + // Check our per SHistory object limit in the currently navigated SHistory + EvictOutOfRangeWindowContentViewers(aIndex); + // Check our total limit across all SHistory objects + GloballyEvictContentViewers(); + return NS_OK; +} + +NS_IMETHODIMP_(void) +nsSHistory::EvictContentViewersOrReplaceEntry(nsISHEntry* aNewSHEntry, + bool aReplace) { + if (!aReplace) { + int32_t curIndex; + GetIndex(&curIndex); + if (curIndex > -1) { + EvictOutOfRangeContentViewers(curIndex); + } + } else { + nsCOMPtr rootSHEntry = nsSHistory::GetRootSHEntry(aNewSHEntry); + + int32_t index = GetIndexOfEntry(rootSHEntry); + if (index > -1) { + ReplaceEntry(index, rootSHEntry); + } + } +} + +NS_IMETHODIMP +nsSHistory::EvictAllContentViewers() { + // XXXbz we don't actually do a good job of evicting things as we should, so + // we might have viewers quite far from mIndex. So just evict everything. + for (int32_t i = 0; i < Length(); i++) { + EvictContentViewerForEntry(mEntries[i]); + } + + return NS_OK; +} + +static void FinishRestore(CanonicalBrowsingContext* aBrowsingContext, + nsDocShellLoadState* aLoadState, + SessionHistoryEntry* aEntry, + nsFrameLoader* aFrameLoader, bool aCanSave) { + MOZ_ASSERT(aEntry); + MOZ_ASSERT(aFrameLoader); + + aEntry->SetFrameLoader(nullptr); + + nsCOMPtr shistory = aEntry->GetShistory(); + int32_t indexOfHistoryLoad = + shistory ? shistory->GetIndexOfEntry(aEntry) : -1; + + nsCOMPtr frameLoaderOwner = + do_QueryInterface(aBrowsingContext->GetEmbedderElement()); + if (frameLoaderOwner && aFrameLoader->GetMaybePendingBrowsingContext() && + indexOfHistoryLoad >= 0) { + RefPtr webProgress = + aBrowsingContext->GetWebProgress(); + if (webProgress) { + // Synthesize a STATE_START WebProgress state change event from here + // in order to ensure emitting it on the BrowsingContext we navigate + // *from* instead of the BrowsingContext we navigate *to*. This will fire + // before and the next one will be ignored by BrowsingContextWebProgress: + // https://searchfox.org/mozilla-central/rev/77f0b36028b2368e342c982ea47609040b399d89/docshell/base/BrowsingContextWebProgress.cpp#196-203 + nsCOMPtr nextURI = aEntry->GetURI(); + nsCOMPtr nextOriginalURI = aEntry->GetOriginalURI(); + nsCOMPtr request = MakeAndAddRef( + nextURI, nextOriginalURI ? nextOriginalURI : nextURI, + ""_ns /* aMatchedList */); + webProgress->OnStateChange(webProgress, request, + nsIWebProgressListener::STATE_START | + nsIWebProgressListener::STATE_IS_DOCUMENT | + nsIWebProgressListener::STATE_IS_REQUEST | + nsIWebProgressListener::STATE_IS_WINDOW | + nsIWebProgressListener::STATE_IS_NETWORK, + NS_OK); + } + + RefPtr loadingBC = + aFrameLoader->GetMaybePendingBrowsingContext()->Canonical(); + RefPtr currentFrameLoader = + frameLoaderOwner->GetFrameLoader(); + // The current page can be bfcached, store the + // nsFrameLoader in the current SessionHistoryEntry. + RefPtr currentSHEntry = + aBrowsingContext->GetActiveSessionHistoryEntry(); + if (currentSHEntry) { + // Update layout history state now, before we change the IsInBFCache flag + // and the active session history entry. + aBrowsingContext->SynchronizeLayoutHistoryState(); + + if (aCanSave) { + currentSHEntry->SetFrameLoader(currentFrameLoader); + Unused << aBrowsingContext->SetIsInBFCache(true); + } + } + + if (aBrowsingContext->IsActive()) { + loadingBC->PreOrderWalk([&](BrowsingContext* aContext) { + if (BrowserParent* bp = aContext->Canonical()->GetBrowserParent()) { + ProcessPriorityManager::BrowserPriorityChanged(bp, true); + } + }); + } + + if (aEntry) { + aEntry->SetWireframe(Nothing()); + } + + // ReplacedBy will swap the entry back. + aBrowsingContext->SetActiveSessionHistoryEntry(aEntry); + loadingBC->SetActiveSessionHistoryEntry(nullptr); + NavigationIsolationOptions options; + aBrowsingContext->ReplacedBy(loadingBC, options); + + // Assuming we still have the session history, update the index. + if (loadingBC->GetSessionHistory()) { + shistory->InternalSetRequestedIndex(indexOfHistoryLoad); + shistory->UpdateIndex(); + } + loadingBC->HistoryCommitIndexAndLength(); + + // ResetSHEntryHasUserInteractionCache(); ? + // browser.navigation.requireUserInteraction is still + // disabled everywhere. + + frameLoaderOwner->RestoreFrameLoaderFromBFCache(aFrameLoader); + // EvictOutOfRangeContentViewers is called here explicitly to + // possibly evict the now in the bfcache document. + // HistoryCommitIndexAndLength might not have evicted that before the + // FrameLoader swap. + shistory->EvictOutOfRangeContentViewers(indexOfHistoryLoad); + + // The old page can't be stored in the bfcache, + // destroy the nsFrameLoader. + if (!aCanSave && currentFrameLoader) { + currentFrameLoader->Destroy(); + } + + Unused << loadingBC->SetIsInBFCache(false); + + // We need to call this after we've restored the page from BFCache (see + // SetIsInBFCache(false) above), so that the page is not frozen anymore and + // the right focus events are fired. + frameLoaderOwner->UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(); + + return; + } + + aFrameLoader->Destroy(); + + // Fall back to do a normal load. + aBrowsingContext->LoadURI(aLoadState, false); +} + +/* static */ +void nsSHistory::LoadURIOrBFCache(LoadEntryResult& aLoadEntry) { + if (mozilla::BFCacheInParent() && aLoadEntry.mBrowsingContext->IsTop()) { + MOZ_ASSERT(XRE_IsParentProcess()); + RefPtr loadState = aLoadEntry.mLoadState; + RefPtr canonicalBC = + aLoadEntry.mBrowsingContext->Canonical(); + nsCOMPtr she = do_QueryInterface(loadState->SHEntry()); + nsCOMPtr currentShe = + canonicalBC->GetActiveSessionHistoryEntry(); + MOZ_ASSERT(she); + RefPtr frameLoader = she->GetFrameLoader(); + if (frameLoader && + (!currentShe || (she->SharedInfo() != currentShe->SharedInfo() && + !currentShe->GetFrameLoader()))) { + bool canSave = (!currentShe || currentShe->GetSaveLayoutStateFlag()) && + canonicalBC->AllowedInBFCache(Nothing(), nullptr); + + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("nsSHistory::LoadURIOrBFCache " + "saving presentation=%i", + canSave)); + + nsCOMPtr frameLoaderOwner = + do_QueryInterface(canonicalBC->GetEmbedderElement()); + if (frameLoaderOwner) { + RefPtr currentFrameLoader = + frameLoaderOwner->GetFrameLoader(); + if (currentFrameLoader && + currentFrameLoader->GetMaybePendingBrowsingContext()) { + if (WindowGlobalParent* wgp = + currentFrameLoader->GetMaybePendingBrowsingContext() + ->Canonical() + ->GetCurrentWindowGlobal()) { + wgp->PermitUnload([canonicalBC, loadState, she, frameLoader, + currentFrameLoader, canSave](bool aAllow) { + if (aAllow) { + FinishRestore(canonicalBC, loadState, she, frameLoader, + canSave && canonicalBC->AllowedInBFCache( + Nothing(), nullptr)); + } else if (currentFrameLoader->GetMaybePendingBrowsingContext()) { + nsISHistory* shistory = + currentFrameLoader->GetMaybePendingBrowsingContext() + ->Canonical() + ->GetSessionHistory(); + if (shistory) { + shistory->InternalSetRequestedIndex(-1); + } + } + }); + return; + } + } + } + + FinishRestore(canonicalBC, loadState, she, frameLoader, canSave); + return; + } + if (frameLoader) { + she->SetFrameLoader(nullptr); + frameLoader->Destroy(); + } + } + + aLoadEntry.mBrowsingContext->LoadURI(aLoadEntry.mLoadState, false); +} + +/* static */ +void nsSHistory::LoadURIs(nsTArray& aLoadResults) { + for (LoadEntryResult& loadEntry : aLoadResults) { + LoadURIOrBFCache(loadEntry); + } +} + +NS_IMETHODIMP +nsSHistory::Reload(uint32_t aReloadFlags) { + nsTArray loadResults; + nsresult rv = Reload(aReloadFlags, loadResults); + NS_ENSURE_SUCCESS(rv, rv); + + if (loadResults.IsEmpty()) { + return NS_OK; + } + + LoadURIs(loadResults); + return NS_OK; +} + +nsresult nsSHistory::Reload(uint32_t aReloadFlags, + nsTArray& aLoadResults) { + MOZ_ASSERT(aLoadResults.IsEmpty()); + + uint32_t loadType; + if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY && + aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { + loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE; + } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) { + loadType = LOAD_RELOAD_BYPASS_PROXY; + } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { + loadType = LOAD_RELOAD_BYPASS_CACHE; + } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) { + loadType = LOAD_RELOAD_CHARSET_CHANGE; + } else { + loadType = LOAD_RELOAD_NORMAL; + } + + // We are reloading. Send Reload notifications. + // nsDocShellLoadFlagType is not public, where as nsIWebNavigation + // is public. So send the reload notifications with the + // nsIWebNavigation flags. + bool canNavigate = true; + MOZ_ALWAYS_SUCCEEDS(NotifyOnHistoryReload(&canNavigate)); + if (!canNavigate) { + return NS_OK; + } + + nsresult rv = LoadEntry( + mIndex, loadType, HIST_CMD_RELOAD, aLoadResults, /* aSameEpoch */ false, + /* aLoadCurrentEntry */ true, + aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION); + if (NS_FAILED(rv)) { + aLoadResults.Clear(); + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::ReloadCurrentEntry() { + nsTArray loadResults; + nsresult rv = ReloadCurrentEntry(loadResults); + NS_ENSURE_SUCCESS(rv, rv); + + LoadURIs(loadResults); + return NS_OK; +} + +nsresult nsSHistory::ReloadCurrentEntry( + nsTArray& aLoadResults) { + // Notify listeners + NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); }); + + return LoadEntry(mIndex, LOAD_HISTORY, HIST_CMD_RELOAD, aLoadResults, + /* aSameEpoch */ false, /* aLoadCurrentEntry */ true, + /* aUserActivation */ false); +} + +void nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex) { + // XXX rename method to EvictContentViewersExceptAroundIndex, or something. + + // We need to release all content viewers that are no longer in the range + // + // aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW + // + // to ensure that this SHistory object isn't responsible for more than + // VIEWER_WINDOW content viewers. But our job is complicated by the + // fact that two entries which are related by either hash navigations or + // history.pushState will have the same content viewer. + // + // To illustrate the issue, suppose VIEWER_WINDOW = 3 and we have four + // linked entries in our history. Suppose we then add a new content + // viewer and call into this function. So the history looks like: + // + // A A A A B + // + * + // + // where the letters are content viewers and + and * denote the beginning and + // end of the range aIndex +/- VIEWER_WINDOW. + // + // Although one copy of the content viewer A exists outside the range, we + // don't want to evict A, because it has other copies in range! + // + // We therefore adjust our eviction strategy to read: + // + // Evict each content viewer outside the range aIndex -/+ + // VIEWER_WINDOW, unless that content viewer also appears within the + // range. + // + // (Note that it's entirely legal to have two copies of one content viewer + // separated by a different content viewer -- call pushState twice, go back + // once, and refresh -- so we can't rely on identical viewers only appearing + // adjacent to one another.) + + if (aIndex < 0) { + return; + } + NS_ENSURE_TRUE_VOID(aIndex < Length()); + + // Calculate the range that's safe from eviction. + int32_t startSafeIndex, endSafeIndex; + WindowIndices(aIndex, &startSafeIndex, &endSafeIndex); + + LOG( + ("EvictOutOfRangeWindowContentViewers(index=%d), " + "Length()=%d. Safe range [%d, %d]", + aIndex, Length(), startSafeIndex, endSafeIndex)); + + // The content viewers in range aIndex -/+ VIEWER_WINDOW will not be + // evicted. Collect a set of them so we don't accidentally evict one of them + // if it appears outside this range. + nsCOMArray safeViewers; + nsTArray> safeFrameLoaders; + for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) { + nsCOMPtr viewer = mEntries[i]->GetContentViewer(); + if (viewer) { + safeViewers.AppendObject(viewer); + } else if (nsCOMPtr she = + do_QueryInterface(mEntries[i])) { + nsFrameLoader* frameLoader = she->GetFrameLoader(); + if (frameLoader) { + safeFrameLoaders.AppendElement(frameLoader); + } + } + } + + // Walk the SHistory list and evict any content viewers that aren't safe. + // (It's important that the condition checks Length(), rather than a cached + // copy of Length(), because the length might change between iterations.) + for (int32_t i = 0; i < Length(); i++) { + nsCOMPtr entry = mEntries[i]; + nsCOMPtr viewer = entry->GetContentViewer(); + if (viewer) { + if (safeViewers.IndexOf(viewer) == -1) { + EvictContentViewerForEntry(entry); + } + } else if (nsCOMPtr she = + do_QueryInterface(mEntries[i])) { + nsFrameLoader* frameLoader = she->GetFrameLoader(); + if (frameLoader) { + if (!safeFrameLoaders.Contains(frameLoader)) { + EvictContentViewerForEntry(entry); + } + } + } + } +} + +namespace { + +class EntryAndDistance { + public: + EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist) + : mSHistory(aSHistory), + mEntry(aEntry), + mViewer(aEntry->GetContentViewer()), + mLastTouched(mEntry->GetLastTouched()), + mDistance(aDist) { + nsCOMPtr she = do_QueryInterface(aEntry); + if (she) { + mFrameLoader = she->GetFrameLoader(); + } + NS_ASSERTION(mViewer || mFrameLoader, + "Entry should have a content viewer or frame loader."); + } + + bool operator<(const EntryAndDistance& aOther) const { + // Compare distances first, and fall back to last-accessed times. + if (aOther.mDistance != this->mDistance) { + return this->mDistance < aOther.mDistance; + } + + return this->mLastTouched < aOther.mLastTouched; + } + + bool operator==(const EntryAndDistance& aOther) const { + // This is a little silly; we need == so the default comaprator can be + // instantiated, but this function is never actually called when we sort + // the list of EntryAndDistance objects. + return aOther.mDistance == this->mDistance && + aOther.mLastTouched == this->mLastTouched; + } + + RefPtr mSHistory; + nsCOMPtr mEntry; + nsCOMPtr mViewer; + RefPtr mFrameLoader; + uint32_t mLastTouched; + int32_t mDistance; +}; + +} // namespace + +// static +void nsSHistory::GloballyEvictContentViewers() { + // First, collect from each SHistory object the entries which have a cached + // content viewer. Associate with each entry its distance from its SHistory's + // current index. + + nsTArray entries; + + for (auto shist : gSHistoryList.mList) { + // Maintain a list of the entries which have viewers and belong to + // this particular shist object. We'll add this list to the global list, + // |entries|, eventually. + nsTArray shEntries; + + // Content viewers are likely to exist only within shist->mIndex -/+ + // VIEWER_WINDOW, so only search within that range. + // + // A content viewer might exist outside that range due to either: + // + // * history.pushState or hash navigations, in which case a copy of the + // content viewer should exist within the range, or + // + // * bugs which cause us not to call nsSHistory::EvictContentViewers() + // often enough. Once we do call EvictContentViewers() for the + // SHistory object in question, we'll do a full search of its history + // and evict the out-of-range content viewers, so we don't bother here. + // + int32_t startIndex, endIndex; + shist->WindowIndices(shist->mIndex, &startIndex, &endIndex); + for (int32_t i = startIndex; i <= endIndex; i++) { + nsCOMPtr entry = shist->mEntries[i]; + nsCOMPtr contentViewer = entry->GetContentViewer(); + + bool found = false; + bool hasContentViewerOrFrameLoader = false; + if (contentViewer) { + hasContentViewerOrFrameLoader = true; + // Because one content viewer might belong to multiple SHEntries, we + // have to search through shEntries to see if we already know + // about this content viewer. If we find the viewer, update its + // distance from the SHistory's index and continue. + for (uint32_t j = 0; j < shEntries.Length(); j++) { + EntryAndDistance& container = shEntries[j]; + if (container.mViewer == contentViewer) { + container.mDistance = + std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex)); + found = true; + break; + } + } + } else if (nsCOMPtr she = do_QueryInterface(entry)) { + if (RefPtr frameLoader = she->GetFrameLoader()) { + hasContentViewerOrFrameLoader = true; + // Similar search as above but using frameloader. + for (uint32_t j = 0; j < shEntries.Length(); j++) { + EntryAndDistance& container = shEntries[j]; + if (container.mFrameLoader == frameLoader) { + container.mDistance = std::min(container.mDistance, + DeprecatedAbs(i - shist->mIndex)); + found = true; + break; + } + } + } + } + + // If we didn't find a EntryAndDistance for this content viewer / + // frameloader, make a new one. + if (hasContentViewerOrFrameLoader && !found) { + EntryAndDistance container(shist, entry, + DeprecatedAbs(i - shist->mIndex)); + shEntries.AppendElement(container); + } + } + + // We've found all the entries belonging to shist which have viewers. + // Add those entries to our global list and move on. + entries.AppendElements(shEntries); + } + + // We now have collected all cached content viewers. First check that we + // have enough that we actually need to evict some. + if ((int32_t)entries.Length() <= sHistoryMaxTotalViewers) { + return; + } + + // If we need to evict, sort our list of entries and evict the largest + // ones. (We could of course get better algorithmic complexity here by using + // a heap or something more clever. But sHistoryMaxTotalViewers isn't large, + // so let's not worry about it.) + entries.Sort(); + + for (int32_t i = entries.Length() - 1; i >= sHistoryMaxTotalViewers; --i) { + (entries[i].mSHistory)->EvictContentViewerForEntry(entries[i].mEntry); + } +} + +nsresult nsSHistory::FindEntryForBFCache(SHEntrySharedParentState* aEntry, + nsISHEntry** aResult, + int32_t* aResultIndex) { + *aResult = nullptr; + *aResultIndex = -1; + + int32_t startIndex, endIndex; + WindowIndices(mIndex, &startIndex, &endIndex); + + for (int32_t i = startIndex; i <= endIndex; ++i) { + nsCOMPtr shEntry = mEntries[i]; + + // Does shEntry have the same BFCacheEntry as the argument to this method? + if (shEntry->HasBFCacheEntry(aEntry)) { + shEntry.forget(aResult); + *aResultIndex = i; + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP_(void) +nsSHistory::EvictExpiredContentViewerForEntry( + SHEntrySharedParentState* aEntry) { + int32_t index; + nsCOMPtr shEntry; + FindEntryForBFCache(aEntry, getter_AddRefs(shEntry), &index); + + if (index == mIndex) { + NS_WARNING("How did the current SHEntry expire?"); + } + + if (shEntry) { + EvictContentViewerForEntry(shEntry); + } +} + +NS_IMETHODIMP_(void) +nsSHistory::AddToExpirationTracker(SHEntrySharedParentState* aEntry) { + RefPtr entry = aEntry; + if (!mHistoryTracker || !entry) { + return; + } + + mHistoryTracker->AddObject(entry); + return; +} + +NS_IMETHODIMP_(void) +nsSHistory::RemoveFromExpirationTracker(SHEntrySharedParentState* aEntry) { + RefPtr entry = aEntry; + MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty()); + if (!mHistoryTracker || !entry) { + return; + } + + mHistoryTracker->RemoveObject(entry); +} + +// Evicts all content viewers in all history objects. This is very +// inefficient, because it requires a linear search through all SHistory +// objects for each viewer to be evicted. However, this method is called +// infrequently -- only when the disk or memory cache is cleared. + +// static +void nsSHistory::GloballyEvictAllContentViewers() { + int32_t maxViewers = sHistoryMaxTotalViewers; + sHistoryMaxTotalViewers = 0; + GloballyEvictContentViewers(); + sHistoryMaxTotalViewers = maxViewers; +} + +void GetDynamicChildren(nsISHEntry* aEntry, nsTArray& aDocshellIDs) { + int32_t count = aEntry->GetChildCount(); + for (int32_t i = 0; i < count; ++i) { + nsCOMPtr child; + aEntry->GetChildAt(i, getter_AddRefs(child)); + if (child) { + if (child->IsDynamicallyAdded()) { + child->GetDocshellID(*aDocshellIDs.AppendElement()); + } else { + GetDynamicChildren(child, aDocshellIDs); + } + } + } +} + +bool RemoveFromSessionHistoryEntry(nsISHEntry* aRoot, + nsTArray& aDocshellIDs) { + bool didRemove = false; + int32_t childCount = aRoot->GetChildCount(); + for (int32_t i = childCount - 1; i >= 0; --i) { + nsCOMPtr child; + aRoot->GetChildAt(i, getter_AddRefs(child)); + if (child) { + nsID docshelldID; + child->GetDocshellID(docshelldID); + if (aDocshellIDs.Contains(docshelldID)) { + didRemove = true; + aRoot->RemoveChild(child); + } else if (RemoveFromSessionHistoryEntry(child, aDocshellIDs)) { + didRemove = true; + } + } + } + return didRemove; +} + +bool RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex, + nsTArray& aEntryIDs) { + nsCOMPtr root; + aHistory->GetEntryAtIndex(aIndex, getter_AddRefs(root)); + return root ? RemoveFromSessionHistoryEntry(root, aEntryIDs) : false; +} + +bool IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2) { + if (!aEntry1 && !aEntry2) { + return true; + } + if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) { + return false; + } + uint32_t id1 = aEntry1->GetID(); + uint32_t id2 = aEntry2->GetID(); + if (id1 != id2) { + return false; + } + + int32_t count1 = aEntry1->GetChildCount(); + int32_t count2 = aEntry2->GetChildCount(); + // We allow null entries in the end of the child list. + int32_t count = std::max(count1, count2); + for (int32_t i = 0; i < count; ++i) { + nsCOMPtr child1, child2; + aEntry1->GetChildAt(i, getter_AddRefs(child1)); + aEntry2->GetChildAt(i, getter_AddRefs(child2)); + if (!IsSameTree(child1, child2)) { + return false; + } + } + + return true; +} + +bool nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) { + NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!"); + NS_ASSERTION(aIndex != 0 || aKeepNext, + "If we're removing index 0 we must be keeping the next"); + NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!"); + + int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1; + + nsresult rv; + nsCOMPtr root1, root2; + rv = GetEntryAtIndex(aIndex, getter_AddRefs(root1)); + if (NS_FAILED(rv)) { + return false; + } + rv = GetEntryAtIndex(compareIndex, getter_AddRefs(root2)); + if (NS_FAILED(rv)) { + return false; + } + + SHistoryChangeNotifier change(this); + + if (IsSameTree(root1, root2)) { + if (aIndex < compareIndex) { + // If we're removing the entry with the lower index we need to move its + // BCHistoryLength to the entry we're keeping. If we're removing the entry + // with the higher index then it shouldn't have a modified + // BCHistoryLength. + UpdateEntryLength(root1, root2, true); + } + nsCOMPtr she = do_QueryInterface(root1); + if (she) { + ClearEntries(she); + } + mEntries.RemoveElementAt(aIndex); + + // FIXME Bug 1546350: Reimplement history listeners. + // if (mRootBC && mRootBC->GetDocShell()) { + // static_cast(mRootBC->GetDocShell()) + // ->HistoryEntryRemoved(aIndex); + //} + + // Adjust our indices to reflect the removed entry. + if (mIndex > aIndex) { + mIndex = mIndex - 1; + } + + // NB: If the entry we are removing is the entry currently + // being navigated to (mRequestedIndex) then we adjust the index + // only if we're not keeping the next entry (because if we are keeping + // the next entry (because the current is a duplicate of the next), then + // that entry slides into the spot that we're currently pointing to. + // We don't do this adjustment for mIndex because mIndex cannot equal + // aIndex. + + // NB: We don't need to guard on mRequestedIndex being nonzero here, + // because either they're strictly greater than aIndex which is at least + // zero, or they are equal to aIndex in which case aKeepNext must be true + // if aIndex is zero. + if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) { + mRequestedIndex = mRequestedIndex - 1; + } + + return true; + } + return false; +} + +NS_IMETHODIMP_(void) +nsSHistory::RemoveEntries(nsTArray& aIDs, int32_t aStartIndex) { + bool didRemove; + RemoveEntries(aIDs, aStartIndex, &didRemove); + if (didRemove) { + RefPtr rootBC = GetBrowsingContext(); + if (rootBC && rootBC->GetDocShell()) { + rootBC->GetDocShell()->DispatchLocationChangeEvent(); + } + } +} + +void nsSHistory::RemoveEntries(nsTArray& aIDs, int32_t aStartIndex, + bool* aDidRemove) { + SHistoryChangeNotifier change(this); + + int32_t index = aStartIndex; + while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) { + } + int32_t minIndex = index; + index = aStartIndex; + while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) { + } + + // We need to remove duplicate nsSHEntry trees. + *aDidRemove = false; + while (index > minIndex) { + if (index != mIndex && RemoveDuplicate(index, index < mIndex)) { + *aDidRemove = true; + } + --index; + } + + UpdateRootBrowsingContextState(); +} + +void nsSHistory::RemoveFrameEntries(nsISHEntry* aEntry) { + int32_t count = aEntry->GetChildCount(); + AutoTArray ids; + for (int32_t i = 0; i < count; ++i) { + nsCOMPtr child; + aEntry->GetChildAt(i, getter_AddRefs(child)); + if (child) { + child->GetDocshellID(*ids.AppendElement()); + } + } + RemoveEntries(ids, mIndex); +} + +void nsSHistory::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry) { + // Remove dynamic entries which are at the index and belongs to the container. + nsCOMPtr entry(aEntry); + if (!entry) { + GetEntryAtIndex(aIndex, getter_AddRefs(entry)); + } + + if (entry) { + AutoTArray toBeRemovedEntries; + GetDynamicChildren(entry, toBeRemovedEntries); + if (toBeRemovedEntries.Length()) { + RemoveEntries(toBeRemovedEntries, aIndex); + } + } +} + +void nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry) { + int32_t index; + nsCOMPtr shEntry; + FindEntryForBFCache(static_cast(aBFEntry), + getter_AddRefs(shEntry), &index); + if (shEntry) { + RemoveDynEntries(index, shEntry); + } +} + +NS_IMETHODIMP +nsSHistory::UpdateIndex() { + SHistoryChangeNotifier change(this); + + // Update the actual index with the right value. + if (mIndex != mRequestedIndex && mRequestedIndex != -1) { + mIndex = mRequestedIndex; + } + + mRequestedIndex = -1; + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GotoIndex(int32_t aIndex, bool aUserActivation) { + nsTArray loadResults; + nsresult rv = GotoIndex(aIndex, loadResults, /*aSameEpoch*/ false, + aIndex == mIndex, aUserActivation); + NS_ENSURE_SUCCESS(rv, rv); + + LoadURIs(loadResults); + return NS_OK; +} + +NS_IMETHODIMP_(void) +nsSHistory::EnsureCorrectEntryAtCurrIndex(nsISHEntry* aEntry) { + int index = mRequestedIndex == -1 ? mIndex : mRequestedIndex; + if (index > -1 && (mEntries[index] != aEntry)) { + ReplaceEntry(index, aEntry); + } +} + +nsresult nsSHistory::GotoIndex(int32_t aIndex, + nsTArray& aLoadResults, + bool aSameEpoch, bool aLoadCurrentEntry, + bool aUserActivation) { + return LoadEntry(aIndex, LOAD_HISTORY, HIST_CMD_GOTOINDEX, aLoadResults, + aSameEpoch, aLoadCurrentEntry, aUserActivation); +} + +NS_IMETHODIMP_(bool) +nsSHistory::HasUserInteractionAtIndex(int32_t aIndex) { + nsCOMPtr entry; + GetEntryAtIndex(aIndex, getter_AddRefs(entry)); + if (!entry) { + return false; + } + return entry->GetHasUserInteraction(); +} + +nsresult nsSHistory::LoadNextPossibleEntry( + int32_t aNewIndex, long aLoadType, uint32_t aHistCmd, + nsTArray& aLoadResults, bool aLoadCurrentEntry, + bool aUserActivation) { + mRequestedIndex = -1; + if (aNewIndex < mIndex) { + return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd, aLoadResults, + /*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation); + } + if (aNewIndex > mIndex) { + return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd, aLoadResults, + /*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation); + } + return NS_ERROR_FAILURE; +} + +nsresult nsSHistory::LoadEntry(int32_t aIndex, long aLoadType, + uint32_t aHistCmd, + nsTArray& aLoadResults, + bool aSameEpoch, bool aLoadCurrentEntry, + bool aUserActivation) { + MOZ_LOG(gSHistoryLog, LogLevel::Debug, + ("LoadEntry(%d, 0x%lx, %u)", aIndex, aLoadType, aHistCmd)); + RefPtr rootBC = GetBrowsingContext(); + if (!rootBC) { + return NS_ERROR_FAILURE; + } + + if (aIndex < 0 || aIndex >= Length()) { + MOZ_LOG(gSHistoryLog, LogLevel::Debug, ("Index out of range")); + // The index is out of range. + // Clear the requested index in case it had bogus value. This way the next + // load succeeds if the offset is reasonable. + mRequestedIndex = -1; + + return NS_ERROR_FAILURE; + } + + int32_t originalRequestedIndex = mRequestedIndex; + int32_t previousRequest = mRequestedIndex > -1 ? mRequestedIndex : mIndex; + int32_t requestedOffset = aIndex - previousRequest; + + // This is a normal local history navigation. + + nsCOMPtr prevEntry; + nsCOMPtr nextEntry; + GetEntryAtIndex(mIndex, getter_AddRefs(prevEntry)); + GetEntryAtIndex(aIndex, getter_AddRefs(nextEntry)); + if (!nextEntry || !prevEntry) { + mRequestedIndex = -1; + return NS_ERROR_FAILURE; + } + + if (mozilla::SessionHistoryInParent()) { + if (aHistCmd == HIST_CMD_GOTOINDEX) { + // https://html.spec.whatwg.org/#history-traversal: + // To traverse the history + // "If entry has a different Document object than the current entry, then + // run the following substeps: Remove any tasks queued by the history + // traversal task source..." + // + // aSameEpoch is true only if the navigations would have been + // generated in the same spin of the event loop (i.e. history.go(-2); + // history.go(-1)) and from the same content process. + if (aSameEpoch) { + bool same_doc = false; + prevEntry->SharesDocumentWith(nextEntry, &same_doc); + if (!same_doc) { + MOZ_LOG( + gSHistoryLog, LogLevel::Debug, + ("Aborting GotoIndex %d - same epoch and not same doc", aIndex)); + // Ignore this load. Before SessionHistoryInParent, this would + // have been dropped in InternalLoad after we filter out SameDoc + // loads. + return NS_ERROR_FAILURE; + } + } + } + } + // Keep note of requested history index in mRequestedIndex; after all bailouts + mRequestedIndex = aIndex; + + // Remember that this entry is getting loaded at this point in the sequence + + nextEntry->SetLastTouched(++gTouchCounter); + + // Get the uri for the entry we are about to visit + nsCOMPtr nextURI = nextEntry->GetURI(); + + MOZ_ASSERT(nextURI, "nextURI can't be null"); + + // Send appropriate listener notifications. + if (aHistCmd == HIST_CMD_GOTOINDEX) { + // We are going somewhere else. This is not reload either + NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); }); + } + + if (mRequestedIndex == mIndex) { + // Possibly a reload case + InitiateLoad(nextEntry, rootBC, aLoadType, aLoadResults, aLoadCurrentEntry, + aUserActivation, requestedOffset); + return NS_OK; + } + + // Going back or forward. + bool differenceFound = LoadDifferingEntries( + prevEntry, nextEntry, rootBC, aLoadType, aLoadResults, aLoadCurrentEntry, + aUserActivation, requestedOffset); + if (!differenceFound) { + // LoadNextPossibleEntry will change the offset by one, and in order + // to keep track of the requestedOffset, need to reset mRequestedIndex to + // the value it had initially. + mRequestedIndex = originalRequestedIndex; + // We did not find any differences. Go further in the history. + return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd, aLoadResults, + aLoadCurrentEntry, aUserActivation); + } + + return NS_OK; +} + +bool nsSHistory::LoadDifferingEntries(nsISHEntry* aPrevEntry, + nsISHEntry* aNextEntry, + BrowsingContext* aParent, long aLoadType, + nsTArray& aLoadResults, + bool aLoadCurrentEntry, + bool aUserActivation, int32_t aOffset) { + MOZ_ASSERT(aPrevEntry && aNextEntry && aParent); + + uint32_t prevID = aPrevEntry->GetID(); + uint32_t nextID = aNextEntry->GetID(); + + // Check the IDs to verify if the pages are different. + if (prevID != nextID) { + // Set the Subframe flag if not navigating the root docshell. + aNextEntry->SetIsSubFrame(aParent->Id() != mRootBC); + InitiateLoad(aNextEntry, aParent, aLoadType, aLoadResults, + aLoadCurrentEntry, aUserActivation, aOffset); + return true; + } + + // The entries are the same, so compare any child frames + int32_t pcnt = aPrevEntry->GetChildCount(); + int32_t ncnt = aNextEntry->GetChildCount(); + + // Create an array for child browsing contexts. + nsTArray> browsingContexts; + aParent->GetChildren(browsingContexts); + + // Search for something to load next. + bool differenceFound = false; + for (int32_t i = 0; i < ncnt; ++i) { + // First get an entry which may cause a new page to be loaded. + nsCOMPtr nChild; + aNextEntry->GetChildAt(i, getter_AddRefs(nChild)); + if (!nChild) { + continue; + } + nsID docshellID; + nChild->GetDocshellID(docshellID); + + // Then find the associated docshell. + RefPtr bcChild; + for (const RefPtr& bc : browsingContexts) { + if (bc->GetHistoryID() == docshellID) { + bcChild = bc; + break; + } + } + if (!bcChild) { + continue; + } + + // Then look at the previous entries to see if there was + // an entry for the docshell. + nsCOMPtr pChild; + for (int32_t k = 0; k < pcnt; ++k) { + nsCOMPtr child; + aPrevEntry->GetChildAt(k, getter_AddRefs(child)); + if (child) { + nsID dID; + child->GetDocshellID(dID); + if (dID == docshellID) { + pChild = child; + break; + } + } + } + if (!pChild) { + continue; + } + + // Finally recursively call this method. + // This will either load a new page to shell or some subshell or + // do nothing. + if (LoadDifferingEntries(pChild, nChild, bcChild, aLoadType, aLoadResults, + aLoadCurrentEntry, aUserActivation, aOffset)) { + differenceFound = true; + } + } + return differenceFound; +} + +void nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry, + BrowsingContext* aFrameBC, long aLoadType, + nsTArray& aLoadResults, + bool aLoadCurrentEntry, bool aUserActivation, + int32_t aOffset) { + MOZ_ASSERT(aFrameBC && aFrameEntry); + + LoadEntryResult* loadResult = aLoadResults.AppendElement(); + loadResult->mBrowsingContext = aFrameBC; + + nsCOMPtr newURI = aFrameEntry->GetURI(); + RefPtr loadState = new nsDocShellLoadState(newURI); + + loadState->SetHasValidUserGestureActivation(aUserActivation); + + // At the time we initiate a history entry load we already know if https-first + // was able to upgrade the request from http to https. There is no point in + // re-retrying to upgrade. + loadState->SetIsExemptFromHTTPSOnlyMode(true); + + /* Set the loadType in the SHEntry too to what was passed on. + * This will be passed on to child subframes later in nsDocShell, + * so that proper loadType is maintained through out a frameset + */ + aFrameEntry->SetLoadType(aLoadType); + + loadState->SetLoadType(aLoadType); + + loadState->SetSHEntry(aFrameEntry); + + // If we're loading the current entry we want to treat it as not a + // same-document navigation (see nsDocShell::IsSameDocumentNavigation), so + // record that here in the LoadingSessionHistoryEntry. + bool loadingCurrentEntry; + if (mozilla::SessionHistoryInParent()) { + loadingCurrentEntry = aLoadCurrentEntry; + } else { + loadingCurrentEntry = + aFrameBC->GetDocShell() && + nsDocShell::Cast(aFrameBC->GetDocShell())->IsOSHE(aFrameEntry); + } + loadState->SetLoadIsFromSessionHistory(aOffset, loadingCurrentEntry); + + if (mozilla::SessionHistoryInParent()) { + nsCOMPtr she = do_QueryInterface(aFrameEntry); + aFrameBC->Canonical()->AddLoadingSessionHistoryEntry( + loadState->GetLoadingSessionHistoryInfo()->mLoadId, she); + } + + nsCOMPtr originalURI = aFrameEntry->GetOriginalURI(); + loadState->SetOriginalURI(originalURI); + + loadState->SetLoadReplace(aFrameEntry->GetLoadReplace()); + + loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE); + nsCOMPtr triggeringPrincipal = + aFrameEntry->GetTriggeringPrincipal(); + loadState->SetTriggeringPrincipal(triggeringPrincipal); + loadState->SetFirstParty(false); + nsCOMPtr csp = aFrameEntry->GetCsp(); + loadState->SetCsp(csp); + + loadResult->mLoadState = std::move(loadState); +} + +NS_IMETHODIMP +nsSHistory::CreateEntry(nsISHEntry** aEntry) { + nsCOMPtr entry; + if (XRE_IsParentProcess() && mozilla::SessionHistoryInParent()) { + entry = new SessionHistoryEntry(); + } else { + entry = new nsSHEntry(); + } + entry.forget(aEntry); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +nsSHistory::IsEmptyOrHasEntriesForSingleTopLevelPage() { + if (mEntries.IsEmpty()) { + return true; + } + + nsISHEntry* entry = mEntries[0]; + size_t length = mEntries.Length(); + for (size_t i = 1; i < length; ++i) { + bool sharesDocument = false; + mEntries[i]->SharesDocumentWith(entry, &sharesDocument); + if (!sharesDocument) { + return false; + } + } + + return true; +} + +static void CollectEntries( + nsTHashMap& aHashtable, + SessionHistoryEntry* aEntry) { + aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry); + for (const RefPtr& entry : aEntry->Children()) { + if (entry) { + CollectEntries(aHashtable, entry); + } + } +} + +static void UpdateEntryLength( + nsTHashMap& aHashtable, + SessionHistoryEntry* aNewEntry, bool aMove) { + SessionHistoryEntry* oldEntry = aHashtable.Get(aNewEntry->DocshellID()); + if (oldEntry) { + MOZ_ASSERT(oldEntry->GetID() != aNewEntry->GetID() || !aMove || + !aNewEntry->BCHistoryLength().Modified()); + aNewEntry->SetBCHistoryLength(oldEntry->BCHistoryLength()); + if (oldEntry->GetID() != aNewEntry->GetID()) { + MOZ_ASSERT(!aMove); + // If we have a new id then aNewEntry was created for a new load, so + // record that in BCHistoryLength. + ++aNewEntry->BCHistoryLength(); + } else if (aMove) { + // We're moving the BCHistoryLength from the old entry to the new entry, + // so we need to let the old entry know that it's not contributing to its + // BCHistoryLength, and the new one that it does if the old one was + // before. + aNewEntry->BCHistoryLength().SetModified( + oldEntry->BCHistoryLength().Modified()); + oldEntry->BCHistoryLength().SetModified(false); + } + } + + for (const RefPtr& entry : aNewEntry->Children()) { + if (entry) { + UpdateEntryLength(aHashtable, entry, aMove); + } + } +} + +void nsSHistory::UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry, + bool aMove) { + nsCOMPtr oldSHE = do_QueryInterface(aOldEntry); + nsCOMPtr newSHE = do_QueryInterface(aNewEntry); + + if (!oldSHE || !newSHE) { + return; + } + + nsTHashMap docshellIDToEntry; + CollectEntries(docshellIDToEntry, oldSHE); + + ::UpdateEntryLength(docshellIDToEntry, newSHE, aMove); +} diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h new file mode 100644 index 0000000000..d029a55c6c --- /dev/null +++ b/docshell/shistory/nsSHistory.h @@ -0,0 +1,343 @@ +/* -*- 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/. */ + +#ifndef nsSHistory_h +#define nsSHistory_h + +#include "nsCOMPtr.h" +#include "nsDocShellLoadState.h" +#include "nsExpirationTracker.h" +#include "nsISHistory.h" +#include "nsSHEntryShared.h" +#include "nsSimpleEnumerator.h" +#include "nsTObserverArray.h" +#include "nsWeakReference.h" + +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/LinkedList.h" +#include "mozilla/UniquePtr.h" + +class nsIDocShell; +class nsDocShell; +class nsSHistoryObserver; +class nsISHEntry; + +namespace mozilla { +namespace dom { +class LoadSHEntryResult; +} +} // namespace mozilla + +class nsSHistory : public mozilla::LinkedListElement, + public nsISHistory, + public nsSupportsWeakReference { + public: + // The timer based history tracker is used to evict bfcache on expiration. + class HistoryTracker final + : public nsExpirationTracker { + public: + explicit HistoryTracker(nsSHistory* aSHistory, uint32_t aTimeout, + nsIEventTarget* aEventTarget) + : nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker", + aEventTarget) { + MOZ_ASSERT(aSHistory); + mSHistory = aSHistory; + } + + protected: + virtual void NotifyExpired( + mozilla::dom::SHEntrySharedParentState* aObj) override { + RemoveObject(aObj); + mSHistory->EvictExpiredContentViewerForEntry(aObj); + } + + private: + // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker + // so it's safe to use raw pointer here. + nsSHistory* mSHistory; + }; + + // Structure used in SetChildHistoryEntry + struct SwapEntriesData { + mozilla::dom::BrowsingContext* + ignoreBC; // constant; the browsing context to ignore + nsISHEntry* destTreeRoot; // constant; the root of the dest tree + nsISHEntry* destTreeParent; // constant; the node under destTreeRoot + // whose children will correspond to aEntry + }; + + explicit nsSHistory(mozilla::dom::BrowsingContext* aRootBC); + NS_DECL_ISUPPORTS + NS_DECL_NSISHISTORY + + // One time initialization method + static nsresult Startup(); + static void Shutdown(); + static void UpdatePrefs(); + + // Max number of total cached content viewers. If the pref + // browser.sessionhistory.max_total_viewers is negative, then + // this value is calculated based on the total amount of memory. + // Otherwise, it comes straight from the pref. + static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; } + + // Get the root SHEntry from a given entry. + static already_AddRefed GetRootSHEntry(nsISHEntry* aEntry); + + // Callback prototype for WalkHistoryEntries. + // `aEntry` is the child history entry, `aBC` is its corresponding browsing + // context, `aChildIndex` is the child's index in its parent entry, and + // `aData` is the opaque pointer passed to WalkHistoryEntries. Both structs + // that are passed as `aData` to this function have a field + // `aEntriesToUpdate`, which is an array of entries we need to update in + // docshell, if the 'SH in parent' pref is on (which implies that this method + // is executed in the parent) + typedef nsresult (*WalkHistoryEntriesFunc)(nsISHEntry* aEntry, + mozilla::dom::BrowsingContext* aBC, + int32_t aChildIndex, void* aData); + + // Clone a session history tree for subframe navigation. + // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except + // for the entry with id |aCloneID|, which will be replaced with + // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which + // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will + // have that pointer updated to point to the cloned history entry. + // If aCloneChildren is true then the children of the entry with id + // |aCloneID| will be cloned into |aReplaceEntry|. + static nsresult CloneAndReplace(nsISHEntry* aSrcEntry, + mozilla::dom::BrowsingContext* aOwnerBC, + uint32_t aCloneID, nsISHEntry* aReplaceEntry, + bool aCloneChildren, nsISHEntry** aDestEntry); + + // Child-walking callback for CloneAndReplace + static nsresult CloneAndReplaceChild(nsISHEntry* aEntry, + mozilla::dom::BrowsingContext* aOwnerBC, + int32_t aChildIndex, void* aData); + + // Child-walking callback for SetHistoryEntry + static nsresult SetChildHistoryEntry(nsISHEntry* aEntry, + mozilla::dom::BrowsingContext* aBC, + int32_t aEntryIndex, void* aData); + + // For each child of aRootEntry, find the corresponding shell which is + // a child of aBC, and call aCallback. The opaque pointer aData + // is passed to the callback. + static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry, + mozilla::dom::BrowsingContext* aBC, + WalkHistoryEntriesFunc aCallback, + void* aData); + + // This function finds all entries that are contiguous and same-origin with + // the aEntry. And call the aCallback on them, including the aEntry. This only + // works for the root entries. It will do nothing for non-root entries. + static void WalkContiguousEntries( + nsISHEntry* aEntry, const std::function& aCallback); + + nsTArray>& Entries() { return mEntries; } + + void NotifyOnHistoryReplaceEntry(); + + void RemoveEntries(nsTArray& aIDs, int32_t aStartIndex, + bool* aDidRemove); + + // The size of the window of SHEntries which can have alive viewers in the + // bfcache around the currently active SHEntry. + // + // We try to keep viewers for SHEntries between index - VIEWER_WINDOW and + // index + VIEWER_WINDOW alive. + static const int32_t VIEWER_WINDOW = 3; + + struct LoadEntryResult { + RefPtr mBrowsingContext; + RefPtr mLoadState; + }; + + static void LoadURIs(nsTArray& aLoadResults); + static void LoadURIOrBFCache(LoadEntryResult& aLoadEntry); + + // If this doesn't return an error then either aLoadResult is set to nothing, + // in which case the caller should ignore the load, or it returns a valid + // LoadEntryResult in aLoadResult which the caller should use to do the load. + nsresult Reload(uint32_t aReloadFlags, + nsTArray& aLoadResults); + nsresult ReloadCurrentEntry(nsTArray& aLoadResults); + nsresult GotoIndex(int32_t aIndex, nsTArray& aLoadResults, + bool aSameEpoch, bool aLoadCurrentEntry, + bool aUserActivation); + + void WindowIndices(int32_t aIndex, int32_t* aOutStartIndex, + int32_t* aOutEndIndex); + void NotifyListenersContentViewerEvicted(uint32_t aNumEvicted); + + int32_t Length() { return int32_t(mEntries.Length()); } + int32_t Index() { return mIndex; } + already_AddRefed GetBrowsingContext() { + return mozilla::dom::BrowsingContext::Get(mRootBC); + } + bool HasOngoingUpdate() { return mHasOngoingUpdate; } + void SetHasOngoingUpdate(bool aVal) { mHasOngoingUpdate = aVal; } + + void SetBrowsingContext(mozilla::dom::BrowsingContext* aRootBC) { + uint64_t newID = aRootBC ? aRootBC->Id() : 0; + if (mRootBC != newID) { + mRootBC = newID; + UpdateRootBrowsingContextState(aRootBC); + } + } + + int32_t GetIndexForReplace() { + // Replace current entry in session history; If the requested index is + // valid, it indicates the loading was triggered by a history load, and + // we should replace the entry at requested index instead. + return mRequestedIndex == -1 ? mIndex : mRequestedIndex; + } + + // Update the root browsing context state when adding, removing or + // replacing entries. + void UpdateRootBrowsingContextState() { + RefPtr rootBC(GetBrowsingContext()); + UpdateRootBrowsingContextState(rootBC); + } + + void GetEpoch(uint64_t& aEpoch, + mozilla::Maybe& aId) const { + aEpoch = mEpoch; + aId = mEpochParentId; + } + void SetEpoch(uint64_t aEpoch, + mozilla::Maybe aId) { + mEpoch = aEpoch; + mEpochParentId = aId; + } + + void LogHistory(); + + protected: + virtual ~nsSHistory(); + + uint64_t mRootBC; + + private: + friend class nsSHistoryObserver; + + void UpdateRootBrowsingContextState( + mozilla::dom::BrowsingContext* aBrowsingContext); + + bool LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry, + mozilla::dom::BrowsingContext* aParent, + long aLoadType, + nsTArray& aLoadResults, + bool aLoadCurrentEntry, bool aUserActivation, + int32_t aOffset); + void InitiateLoad(nsISHEntry* aFrameEntry, + mozilla::dom::BrowsingContext* aFrameBC, long aLoadType, + nsTArray& aLoadResult, + bool aLoadCurrentEntry, bool aUserActivation, + int32_t aOffset); + + nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd, + nsTArray& aLoadResults, bool aSameEpoch, + bool aLoadCurrentEntry, bool aUserActivation); + + // Find the history entry for a given bfcache entry. It only looks up between + // the range where alive viewers may exist (i.e nsSHistory::VIEWER_WINDOW). + nsresult FindEntryForBFCache(mozilla::dom::SHEntrySharedParentState* aEntry, + nsISHEntry** aResult, int32_t* aResultIndex); + + // Evict content viewers in this window which don't lie in the "safe" range + // around aIndex. + virtual void EvictOutOfRangeWindowContentViewers(int32_t aIndex); + void EvictContentViewerForEntry(nsISHEntry* aEntry); + static void GloballyEvictContentViewers(); + static void GloballyEvictAllContentViewers(); + + // Calculates a max number of total + // content viewers to cache, based on amount of total memory + static uint32_t CalcMaxTotalViewers(); + + nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType, + uint32_t aHistCmd, + nsTArray& aLoadResults, + bool aLoadCurrentEntry, bool aUserActivation); + + // aIndex is the index of the entry which may be removed. + // If aKeepNext is true, aIndex is compared to aIndex + 1, + // otherwise comparison is done to aIndex - 1. + bool RemoveDuplicate(int32_t aIndex, bool aKeepNext); + + // We need to update entries in docshell and browsing context. + // If our docshell is located in parent or 'SH in parent' pref is off we can + // update it directly, Otherwise, we have two choices. If the browsing context + // that owns the docshell is in the same process as the process who called us + // over IPC, then we save entries that need to be updated in a list, and once + // we have returned from the IPC call, we update the docshell in the child + // process. Otherwise, if the browsing context is in a different process, we + // do a nested IPC call to that process to update the docshell in that + // process. + static void HandleEntriesToSwapInDocShell(mozilla::dom::BrowsingContext* aBC, + nsISHEntry* aOldEntry, + nsISHEntry* aNewEntry); + + void UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry, + bool aMove); + + protected: + bool mHasOngoingUpdate; + nsTArray> mEntries; // entries are never null + private: + // Track all bfcache entries and evict on expiration. + mozilla::UniquePtr mHistoryTracker; + + int32_t mIndex; // -1 means "no index" + int32_t mRequestedIndex; // -1 means "no requested index" + + // Session History listeners + nsAutoTObserverArray mListeners; + + nsID mRootDocShellID; + + // Max viewers allowed total, across all SHistory objects + static int32_t sHistoryMaxTotalViewers; + + // The epoch (and id) tell us what navigations occured within the same + // event-loop spin in the child. We need to know this in order to + // implement spec requirements for dropping pending navigations when we + // do a history navigation, if it's not same-document. Content processes + // update the epoch via a runnable on each ::Go (including AsyncGo). + uint64_t mEpoch = 0; + mozilla::Maybe mEpochParentId; +}; + +// CallerWillNotifyHistoryIndexAndLengthChanges is used to prevent +// SHistoryChangeNotifier to send automatic index and length updates. +// When that is done, it is up to the caller to explicitly send those updates. +// This is needed in cases when the update is a reaction to some change in a +// child process and child process passes a changeId to the parent side. +class MOZ_STACK_CLASS CallerWillNotifyHistoryIndexAndLengthChanges { + public: + explicit CallerWillNotifyHistoryIndexAndLengthChanges( + nsISHistory* aSHistory) { + nsSHistory* shistory = static_cast(aSHistory); + if (shistory && !shistory->HasOngoingUpdate()) { + shistory->SetHasOngoingUpdate(true); + mSHistory = shistory; + } + } + + ~CallerWillNotifyHistoryIndexAndLengthChanges() { + if (mSHistory) { + mSHistory->SetHasOngoingUpdate(false); + } + } + + RefPtr mSHistory; +}; + +inline nsISupports* ToSupports(nsSHistory* aObj) { + return static_cast(aObj); +} + +#endif /* nsSHistory */ -- cgit v1.2.3