summaryrefslogtreecommitdiffstats
path: root/docshell/shistory
diff options
context:
space:
mode:
Diffstat (limited to 'docshell/shistory')
-rw-r--r--docshell/shistory/ChildSHistory.cpp294
-rw-r--r--docshell/shistory/ChildSHistory.h149
-rw-r--r--docshell/shistory/SessionHistoryEntry.cpp1831
-rw-r--r--docshell/shistory/SessionHistoryEntry.h510
-rw-r--r--docshell/shistory/moz.build42
-rw-r--r--docshell/shistory/nsIBFCacheEntry.idl16
-rw-r--r--docshell/shistory/nsISHEntry.idl476
-rw-r--r--docshell/shistory/nsISHistory.idl291
-rw-r--r--docshell/shistory/nsISHistoryListener.idl88
-rw-r--r--docshell/shistory/nsSHEntry.cpp1131
-rw-r--r--docshell/shistory/nsSHEntry.h72
-rw-r--r--docshell/shistory/nsSHEntryShared.cpp365
-rw-r--r--docshell/shistory/nsSHEntryShared.h219
-rw-r--r--docshell/shistory/nsSHistory.cpp2410
-rw-r--r--docshell/shistory/nsSHistory.h343
15 files changed, 8237 insertions, 0 deletions
diff --git a/docshell/shistory/ChildSHistory.cpp b/docshell/shistory/ChildSHistory.cpp
new file mode 100644
index 0000000000..9148491718
--- /dev/null
+++ b/docshell/shistory/ChildSHistory.cpp
@@ -0,0 +1,294 @@
+/* -*- 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<nsSHistory*>(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<nsSHistory*>(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()) {
+ nsCOMPtr<nsISHistory> shistory =
+ mBrowsingContext->Canonical()->GetSessionHistory();
+ if (shistory) {
+ aRv = shistory->Reload(aReloadFlags);
+ }
+ } else {
+ ContentChild::GetSingleton()->SendHistoryReload(mBrowsingContext,
+ aReloadFlags);
+ }
+
+ return;
+ }
+ nsCOMPtr<nsISHistory> shistory = mHistory;
+ aRv = shistory->Reload(aReloadFlags);
+}
+
+bool ChildSHistory::CanGo(int32_t aOffset) {
+ CheckedInt<int32_t> 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<int32_t> 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<int32_t> 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<PendingAsyncHistoryNavigation> 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<ChildSHistory> self(this);
+ NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction("UpdateEpochRunnable", [self] {
+ self->mHistoryEpoch++;
+ self->mPendingEpoch = false;
+ }));
+ }
+
+ nsCOMPtr<nsISHistory> shistory = mHistory;
+ RefPtr<BrowsingContext> bc = mBrowsingContext;
+ bc->HistoryGo(
+ aOffset, mHistoryEpoch, aRequireUserInteraction, aUserActivation,
+ [shistory](Maybe<int32_t>&& aRequestedIndex) {
+ // FIXME Should probably only do this for non-fission.
+ if (aRequestedIndex.isSome() && shistory) {
+ shistory->InternalSetRequestedIndex(aRequestedIndex.value());
+ }
+ });
+ } else {
+ nsCOMPtr<nsISHistory> shistory = mHistory;
+ aRv = shistory->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::EvictLocalDocumentViewers() {
+ if (!mozilla::SessionHistoryInParent()) {
+ mHistory->EvictAllDocumentViewers();
+ }
+}
+
+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<nsSHistory*>(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<JSObject*> 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..7187240471
--- /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<JSObject*> 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 EvictLocalDocumentViewers();
+
+ // 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<PendingAsyncHistoryNavigation> {
+ 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<ChildSHistory> mHistory;
+ bool mRequireUserInteraction;
+ bool mUserActivation;
+ int32_t mOffset;
+ };
+
+ RefPtr<BrowsingContext> mBrowsingContext;
+ nsCOMPtr<nsISHistory> mHistory;
+ // Can be removed once history-in-parent is the only way
+ mozilla::LinkedList<PendingAsyncHistoryNavigation> mPendingNavigations;
+ int32_t mIndex = -1;
+ int32_t mLength = 0;
+
+ struct PendingSHistoryChange {
+ nsID mChangeID;
+ int32_t mIndexDelta;
+ int32_t mLengthDelta;
+ };
+ AutoTArray<PendingSHistoryChange, 2> 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..692ab4fe44
--- /dev/null
+++ b/docshell/shistory/SessionHistoryEntry.cpp
@@ -0,0 +1,1831 @@
+/* -*- 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/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<nsIUploadChannel2> 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<nsIHttpChannel> 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<nsILoadInfo> 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<nsIHttpChannel> 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<nsIInputStream> 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<nsIInputStream> 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<SHEntrySharedParentState*>(mSharedState.Get())->mSaveLayoutState =
+ aSaveLayoutStateFlag;
+}
+
+void SessionHistoryInfo::FillLoadInfo(nsDocShellLoadState& aLoadState) const {
+ aLoadState.SetOriginalURI(mOriginalURI);
+ aLoadState.SetMaybeResultPrincipalURI(Some(mResultPrincipalURI));
+ aLoadState.SetUnstrippedURI(mUnstrippedURI);
+ aLoadState.SetLoadReplace(mLoadReplace);
+ nsCOMPtr<nsIInputStream> 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<nsIURI> 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. On a reload we still want to check,
+ // because the exemptions set by the user could have changed.
+ if ((mLoadType & nsIDocShell::LOAD_CMD_RELOAD) == 0) {
+ aLoadState.SetIsExemptFromHTTPSFirstMode(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<SHEntrySharedState>(
+ aTriggeringPrincipal, aPrincipalToInherit, aPartitionedPrincipalToInherit,
+ aCsp, aContentType));
+}
+
+SessionHistoryInfo::SharedState::SharedState() { Init(); }
+
+SessionHistoryInfo::SharedState::SharedState(
+ const SessionHistoryInfo::SharedState& aOther) {
+ Init(aOther);
+}
+
+SessionHistoryInfo::SharedState::SharedState(
+ const Maybe<const SessionHistoryInfo::SharedState&>& aOther) {
+ if (aOther.isSome()) {
+ Init(aOther.ref());
+ } else {
+ Init();
+ }
+}
+
+SessionHistoryInfo::SharedState::~SharedState() {
+ if (XRE_IsParentProcess()) {
+ mParent
+ .RefPtr<SHEntrySharedParentState>::~RefPtr<SHEntrySharedParentState>();
+ } else {
+ mChild.UniquePtr<SHEntrySharedState>::~UniquePtr<SHEntrySharedState>();
+ }
+}
+
+SessionHistoryInfo::SharedState& SessionHistoryInfo::SharedState::operator=(
+ const SessionHistoryInfo::SharedState& aOther) {
+ if (this != &aOther) {
+ if (XRE_IsParentProcess()) {
+ mParent = aOther.mParent;
+ } else {
+ mChild = MakeUnique<SHEntrySharedState>(*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<SHEntrySharedParentState>(new SHEntrySharedParentState());
+ } else {
+ new (&mChild)
+ UniquePtr<SHEntrySharedState>(MakeUnique<SHEntrySharedState>());
+ }
+}
+
+void SessionHistoryInfo::SharedState::Init(
+ const SessionHistoryInfo::SharedState& aOther) {
+ if (XRE_IsParentProcess()) {
+ new (&mParent) RefPtr<SHEntrySharedParentState>(aOther.mParent);
+ } else {
+ new (&mChild) UniquePtr<SHEntrySharedState>(
+ MakeUnique<SHEntrySharedState>(*aOther.mChild));
+ }
+}
+
+static uint64_t gLoadingSessionHistoryInfoLoadId = 0;
+
+nsTHashMap<nsUint64HashKey, SessionHistoryEntry::LoadingEntry>*
+ 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)->mEntry == aEntry);
+}
+
+LoadingSessionHistoryInfo::LoadingSessionHistoryInfo(
+ const SessionHistoryInfo& aInfo)
+ : mInfo(aInfo), mLoadId(UINT64_MAX) {}
+
+already_AddRefed<nsDocShellLoadState>
+LoadingSessionHistoryInfo::CreateLoadInfo() const {
+ RefPtr<nsDocShellLoadState> loadState(
+ new nsDocShellLoadState(mInfo.GetURI()));
+
+ mInfo.FillLoadInfo(*loadState);
+
+ loadState->SetLoadingSessionHistoryInfo(*this);
+
+ return loadState.forget();
+}
+
+static uint32_t gEntryID;
+
+SessionHistoryEntry::LoadingEntry* SessionHistoryEntry::GetByLoadId(
+ uint64_t aLoadId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!sLoadIdToEntry) {
+ return nullptr;
+ }
+
+ return sLoadIdToEntry->Lookup(aLoadId).DataPtrOrNull();
+}
+
+void SessionHistoryEntry::SetByLoadId(uint64_t aLoadId,
+ SessionHistoryEntry* aEntry) {
+ if (!sLoadIdToEntry) {
+ sLoadIdToEntry = new nsTHashMap<nsUint64HashKey, LoadingEntry>();
+ }
+
+ MOZ_LOG(
+ gSHLog, LogLevel::Verbose,
+ ("SessionHistoryEntry::SetByLoadId(%" PRIu64 " - %p)", aLoadId, aEntry));
+ sLoadIdToEntry->InsertOrUpdate(
+ aLoadId, LoadingEntry{
+ .mEntry = aEntry,
+ .mInfoSnapshotForValidation =
+ MakeUnique<SessionHistoryInfo>(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<SessionHistoryInfo>(*aInfo)), mID(++gEntryID) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+}
+
+SessionHistoryEntry::SessionHistoryEntry(const SessionHistoryEntry& aEntry)
+ : mInfo(MakeUnique<SessionHistoryInfo>(*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,
+ nsISupportsWeakReference)
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> 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<nsIURI> 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<nsIURI> 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<nsIURI> 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<nsISHEntry> 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<nsISHEntry> 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<nsIReferrerInfo> referrerInfo = mInfo->mReferrerInfo;
+ referrerInfo.forget(aReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mInfo->mReferrerInfo = aReferrerInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetDocumentViewer(nsIDocumentViewer** aDocumentViewer) {
+ *aDocumentViewer = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetDocumentViewer(nsIDocumentViewer* aDocumentViewer) {
+ 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::GetHasPostData(bool* aResult) {
+ *aResult = mInfo->HasPostData();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLayoutHistoryState(
+ nsILayoutHistoryState** aLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> 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<nsISHEntry> parent = do_QueryReferent(mParent);
+ parent.forget(aParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetParent(nsISHEntry* aParent) {
+ mParent = do_GetWeakReference(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<nsIPrincipal> 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<nsIPrincipal> 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<nsIPrincipal> 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<nsIContentSecurityPolicy> 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<nsStructuredCloneContainer> stateData = mInfo->mStateData;
+ stateData.forget(aStateData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetStateData(nsIStructuredCloneContainer* aStateData) {
+ mInfo->mStateData = static_cast<nsStructuredCloneContainer*>(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<nsIURI> 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<nsISHistory> 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<nsILayoutHistoryState> 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<SessionHistoryEntry> 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<Wireframe>& 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<SessionHistoryEntry> she = do_QueryInterface(aEntry);
+ NS_ENSURE_STATE(she && she->mInfo->mSharedState.Get());
+
+ mInfo->mSharedState =
+ static_cast<SessionHistoryEntry*>(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<SessionHistoryEntry*>(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<SessionHistoryEntry> 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<SessionHistoryEntry*> 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<SessionHistoryEntry> 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<nsISHEntry> 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<SessionHistoryEntry> 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<nsISHEntry> 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<JS::Value> 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<JS::Value> 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<nsISHEntry> 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<nsISHEntry> 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<SessionHistoryInfo>(*aSource.mInfo);
+ mChildren.Clear();
+}
+
+SHEntrySharedParentState* SessionHistoryEntry::SharedInfo() const {
+ return static_cast<SHEntrySharedParentState*>(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<nsISHistory> shistory;
+ GetShistory(getter_AddRefs(shistory));
+ if (shistory) {
+ int32_t index = 0;
+ shistory->GetIndex(&index);
+ shistory->EvictOutOfRangeDocumentViewers(index);
+ }
+ }
+}
+
+nsFrameLoader* SessionHistoryEntry::GetFrameLoader() {
+ return SharedInfo()->mFrameLoader;
+}
+
+void SessionHistoryEntry::SetInfo(SessionHistoryInfo* aInfo) {
+ // FIXME Assert that we're not changing shared state!
+ mInfo = MakeUnique<SessionHistoryInfo>(*aInfo);
+}
+
+} // namespace dom
+
+namespace ipc {
+
+void IPDLParamTraits<dom::SessionHistoryInfo>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::SessionHistoryInfo& aParam) {
+ nsCOMPtr<nsIInputStream> postData = aParam.GetPostData();
+
+ Maybe<std::tuple<uint32_t, dom::ClonedMessageData>> 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(&std::get<0>(*stateData)));
+ NS_ENSURE_TRUE_VOID(
+ aParam.mStateData->BuildClonedMessageData(std::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<dom::SessionHistoryInfo>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::SessionHistoryInfo* aResult) {
+ Maybe<std::tuple<uint32_t, dom::ClonedMessageData>> 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<nsIPrincipal> triggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> principalToInherit;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> 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 = std::get<0>(*stateData);
+ aResult->mStateData = new nsStructuredCloneContainer(version);
+ aResult->mStateData->StealFromClonedMessageData(std::get<1>(*stateData));
+ }
+ MOZ_ASSERT_IF(stateData.isNothing(), !aResult->mStateData);
+ return true;
+}
+
+void IPDLParamTraits<dom::LoadingSessionHistoryInfo>::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<dom::LoadingSessionHistoryInfo>::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<nsILayoutHistoryState*>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ nsILayoutHistoryState* aParam) {
+ if (aParam) {
+ WriteIPDLParam(aWriter, aActor, true);
+ bool scrollPositionOnly = false;
+ nsTArray<nsCString> keys;
+ nsTArray<mozilla::PresState> 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<nsILayoutHistoryState*>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ RefPtr<nsILayoutHistoryState>* aResult) {
+ bool hasLayoutHistoryState = false;
+ if (!ReadIPDLParam(aReader, aActor, &hasLayoutHistoryState)) {
+ aActor->FatalError("Error reading fields for nsILayoutHistoryState");
+ return false;
+ }
+
+ if (hasLayoutHistoryState) {
+ bool scrollPositionOnly = false;
+ nsTArray<nsCString> keys;
+ nsTArray<mozilla::PresState> 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<PresState> newState = MakeUnique<PresState>(state);
+ (*aResult)->AddState(keys[i], std::move(newState));
+ }
+ }
+ return true;
+}
+
+void IPDLParamTraits<mozilla::dom::Wireframe>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const mozilla::dom::Wireframe& aParam) {
+ WriteParam(aWriter, aParam.mCanvasBackground);
+ WriteParam(aWriter, aParam.mRects);
+}
+
+bool IPDLParamTraits<mozilla::dom::Wireframe>::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<mozilla::dom::WireframeRectType>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::WireframeRectType,
+ mozilla::dom::WireframeRectType::Image,
+ mozilla::dom::WireframeRectType::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::WireframeTaggedRect> {
+ static void Write(MessageWriter* aWriter,
+ const mozilla::dom::WireframeTaggedRect& aParam);
+ static bool Read(MessageReader* aReader,
+ mozilla::dom::WireframeTaggedRect* aResult);
+};
+
+void ParamTraits<mozilla::dom::WireframeTaggedRect>::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<mozilla::dom::WireframeTaggedRect>::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..c7f1fea5e9
--- /dev/null
+++ b/docshell/shistory/SessionHistoryEntry.h
@@ -0,0 +1,510 @@
+/* -*- 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"
+#include "nsWeakReference.h"
+
+class nsDocShellLoadState;
+class nsIChannel;
+class nsIInputStream;
+class nsIReferrerInfo;
+class nsISHistory;
+class nsIURI;
+
+namespace mozilla::ipc {
+template <typename P>
+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;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> GetReferrerInfo() { return mReferrerInfo; }
+ void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo;
+ }
+
+ bool HasPostData() const { return mPostData; }
+ already_AddRefed<nsIInputStream> 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);
+
+ bool GetPersist() const { return mPersist; }
+
+ private:
+ friend class SessionHistoryEntry;
+ friend struct mozilla::ipc::IPDLParamTraits<SessionHistoryInfo>;
+
+ void MaybeUpdateTitleFromURI();
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsCOMPtr<nsIURI> mUnstrippedURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsString mTitle;
+ nsString mName;
+ nsCOMPtr<nsIInputStream> mPostData;
+ uint32_t mLoadType = 0;
+ int32_t mScrollPositionX = 0;
+ int32_t mScrollPositionY = 0;
+ RefPtr<nsStructuredCloneContainer> mStateData;
+ Maybe<nsString> mSrcdocData;
+ nsCOMPtr<nsIURI> 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<const SharedState&>& 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<SHEntrySharedState>&& 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<SHEntrySharedParentState> mParent;
+ UniquePtr<SHEntrySharedState> 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<nsDocShellLoadState> 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<bool> 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<RefCountedCounter> 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 nsSupportsWeakReference {
+ 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)
+
+ bool IsInSessionHistory() {
+ SessionHistoryEntry* entry = this;
+ while (nsCOMPtr<SessionHistoryEntry> parent =
+ do_QueryReferent(entry->mParent)) {
+ entry = parent;
+ }
+ return entry->SharedInfo()->mSHistory &&
+ entry->SharedInfo()->mSHistory->IsAlive();
+ }
+
+ 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<Wireframe>& aWireframe);
+
+ 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<SessionHistoryInfo> mInfoSnapshotForValidation;
+ };
+
+ // Get an entry based on LoadingSessionHistoryInfo's mLoadId. Parent process
+ // only.
+ static LoadingEntry* GetByLoadId(uint64_t aLoadId);
+ static void SetByLoadId(uint64_t aLoadId, SessionHistoryEntry* aEntry);
+ static void RemoveLoadId(uint64_t aLoadId);
+
+ const nsTArray<RefPtr<SessionHistoryEntry>>& Children() { return mChildren; }
+
+ private:
+ friend struct LoadingSessionHistoryInfo;
+ virtual ~SessionHistoryEntry();
+
+ UniquePtr<SessionHistoryInfo> mInfo;
+ nsWeakPtr mParent;
+ uint32_t mID;
+ nsTArray<RefPtr<SessionHistoryEntry>> mChildren;
+ Maybe<Wireframe> mWireframe;
+
+ bool mForInitialLoad = false;
+
+ HistoryEntryCounterForBrowsingContext mBCHistoryLength;
+
+ static nsTHashMap<nsUint64HashKey, LoadingEntry>* 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<dom::SessionHistoryInfo> {
+ 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<dom::LoadingSessionHistoryInfo> {
+ 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<nsILayoutHistoryState*> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ nsILayoutHistoryState* aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ RefPtr<nsILayoutHistoryState>* aResult);
+};
+
+// Allow sending dom::Wireframe objects over IPC.
+template <>
+struct IPDLParamTraits<mozilla::dom::Wireframe> {
+ 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
+
+inline nsISupports* ToSupports(mozilla::dom::SessionHistoryEntry* aEntry) {
+ return static_cast<nsISHEntry*>(aEntry);
+}
+
+#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..1d7427f581
--- /dev/null
+++ b/docshell/shistory/nsISHEntry.idl
@@ -0,0 +1,476 @@
+/* -*- 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 nsIDocumentViewer;
+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;
+
+ /** Document viewer, for fast restoration of presentation */
+ [infallible] attribute nsIDocumentViewer documentViewer;
+
+ [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;
+ [infallible] readonly attribute boolean hasPostData;
+
+ /** 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 documentViewer. 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 |documentViewer| or |windowState| are null, then all of the
+ * following members are cleared/reset:
+ * documentViewer, 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..9ca237c670
--- /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<nsID>);
+native MaybeInt32(mozilla::Maybe<int32_t>);
+[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 <code>NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA</code>
+ * Purge was vetod.
+ * @throws <code>NS_ERROR_FAILURE</code> 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 <code>NS_OK</code> index for the history entry
+ * is obtained successfully.
+ * <code>NS_ERROR_FAILURE</code> 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 evictOutOfRangeDocumentViewers(in long aIndex);
+
+ /**
+ * Evict the content viewer associated with a bfcache entry that has timed
+ * out.
+ */
+ [noscript, notxpcom]
+ void evictExpiredDocumentViewerForEntry(in SHEntrySharedParentStatePtr aEntry);
+
+ /**
+ * Evict all the content viewers in this session history
+ */
+ void evictAllDocumentViewers();
+
+ /**
+ * 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 EvictDocumentViewersOrReplaceEntry(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..81fc444009
--- /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 OnDocumentViewerEvicted(in unsigned long aNumEvicted);
+};
diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp
new file mode 100644
index 0000000000..8fc4f74844
--- /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 <algorithm>
+
+#include "nsDocShell.h"
+#include "nsDocShellEditorData.h"
+#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocumentViewer.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),
+ 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, nsISupportsWeakReference)
+
+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::GetHasPostData(bool* aResult) {
+ *aResult = !!mPostData;
+ 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<nsILayoutHistoryState> historyState;
+ historyState = NS_NewLayoutHistoryState();
+ SetLayoutHistoryState(historyState);
+ }
+
+ nsCOMPtr<nsILayoutHistoryState> 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<nsISHEntry> 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<nsISHEntry> 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) {
+ nsCOMPtr<nsISHEntry> parent = do_QueryReferent(mParent);
+ parent.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetParent(nsISHEntry* aParent) {
+ mParent = do_GetWeakReference(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<nsSHEntry*>(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<nsSHEntry*>(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<nsISHEntry> 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<nsISHEntry> 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<nsISHistory> 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<nsIURI> uri = GetURI();
+ RefPtr<nsDocShellLoadState> loadState(new nsDocShellLoadState(uri));
+
+ nsCOMPtr<nsIURI> originalURI = GetOriginalURI();
+ loadState->SetOriginalURI(originalURI);
+
+ mozilla::Maybe<nsCOMPtr<nsIURI>> emplacedResultPrincipalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI = GetResultPrincipalURI();
+ emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI));
+ loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI);
+
+ nsCOMPtr<nsIURI> unstrippedURI = GetUnstrippedURI();
+ loadState->SetUnstrippedURI(unstrippedURI);
+
+ loadState->SetLoadReplace(GetLoadReplace());
+ nsCOMPtr<nsIInputStream> postData = GetPostData();
+ loadState->SetPostDataStream(postData);
+
+ nsAutoCString contentType;
+ GetContentType(contentType);
+ loadState->SetTypeHint(contentType);
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = GetTriggeringPrincipal();
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+ nsCOMPtr<nsIPrincipal> principalToInherit = GetPrincipalToInherit();
+ loadState->SetPrincipalToInherit(principalToInherit);
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit =
+ GetPartitionedPrincipalToInherit();
+ loadState->SetPartitionedPrincipalToInherit(partitionedPrincipalToInherit);
+ nsCOMPtr<nsIContentSecurityPolicy> csp = GetCsp();
+ loadState->SetCsp(csp);
+ nsCOMPtr<nsIReferrerInfo> 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<nsIURI> 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->SetIsExemptFromHTTPSFirstMode(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<nsISHEntry> 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<nsISHEntry> oldRootEntry = nsSHistory::GetRootSHEntry(this);
+
+ if (oldRootEntry) {
+ nsSHistory::SwapEntriesData data = {aIgnoreBC, newRootEntry, nullptr};
+ nsSHistory::SetChildHistoryEntry(oldRootEntry, aTopBC, 0, &data);
+ }
+ }
+}
+
+void nsSHEntry::EvictDocumentViewer() {
+ nsCOMPtr<nsIDocumentViewer> viewer = GetDocumentViewer();
+ if (viewer) {
+ mShared->NotifyListenersDocumentViewerEvicted();
+ // Drop the presentation state before destroying the viewer, so that
+ // document teardown is able to correctly persist the state.
+ SetDocumentViewer(nullptr);
+ SyncPresentationState();
+ viewer->Destroy();
+ }
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetDocumentViewer(nsIDocumentViewer* aViewer) {
+ return GetState()->SetDocumentViewer(aViewer);
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetDocumentViewer(nsIDocumentViewer** aResult) {
+ *aResult = GetState()->mDocumentViewer;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetIsInBFCache(bool* aResult) {
+ *aResult = !!GetState()->mDocumentViewer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::Clone(nsISHEntry** aResult) {
+ nsCOMPtr<nsISHEntry> 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<JS::Value> aOut) {
+ aOut.set(JS::NullValue());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetWireframe(JSContext* aCx, JS::Handle<JS::Value> aArg) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h
new file mode 100644
index 0000000000..1a018575bd
--- /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 "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+class nsSHEntryShared;
+class nsIInputStream;
+class nsIURI;
+class nsIReferrerInfo;
+
+class nsSHEntry : public nsISHEntry, public nsSupportsWeakReference {
+ public:
+ nsSHEntry();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHENTRY
+
+ virtual void EvictDocumentViewer();
+
+ 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<nsSHEntryShared> mShared;
+
+ // See nsSHEntry.idl for comments on these members.
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsCOMPtr<nsIURI> mUnstrippedURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsString mTitle;
+ nsString mName;
+ nsCOMPtr<nsIInputStream> mPostData;
+ uint32_t mLoadType;
+ uint32_t mID;
+ int32_t mScrollPositionX;
+ int32_t mScrollPositionY;
+ nsWeakPtr mParent;
+ nsCOMArray<nsISHEntry> mChildren;
+ nsCOMPtr<nsIStructuredCloneContainer> mStateData;
+ nsString mSrcdocData;
+ nsCOMPtr<nsIURI> 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..2c0e33ef62
--- /dev/null
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -0,0 +1,365 @@
+/* -*- 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 "nsIDocumentViewer.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<nsUint64HashKey, mozilla::dom::SHEntrySharedParentState*>*
+ 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<nsUint64HashKey, SHEntrySharedParentState*>();
+ }
+ 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<nsFrameLoader> 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::NotifyListenersDocumentViewerEvicted() {
+ if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
+ RefPtr<nsSHistory> nsshistory = static_cast<nsSHistory*>(shistory.get());
+ nsshistory->NotifyListenersDocumentViewerEvicted(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<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
+ shistory->RemoveFromExpirationTracker(this);
+ }
+ }
+
+ mFrameLoader = aFrameLoader;
+
+ if (mFrameLoader) {
+ if (nsCOMPtr<nsISHistory> 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 (mDocumentViewer) {
+ 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> nsSHEntryShared::Duplicate() {
+ RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared();
+
+ newEntry->dom::SHEntrySharedParentState::CopyFrom(this);
+ newEntry->dom::SHEntrySharedChildState::CopyFrom(this);
+
+ return newEntry.forget();
+}
+
+void nsSHEntryShared::RemoveFromExpirationTracker() {
+ nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
+ if (shistory && GetExpirationState()->IsTracked()) {
+ shistory->RemoveFromExpirationTracker(this);
+ }
+}
+
+void nsSHEntryShared::SyncPresentationState() {
+ if (mDocumentViewer && mWindowState) {
+ // If we have a content viewer and a window state, we should be ok.
+ return;
+ }
+
+ DropPresentationState();
+}
+
+void nsSHEntryShared::DropPresentationState() {
+ RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
+
+ if (mDocument) {
+ mDocument->SetBFCacheEntry(nullptr);
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nullptr;
+ }
+ if (mDocumentViewer) {
+ mDocumentViewer->ClearHistoryEntry();
+ }
+
+ RemoveFromExpirationTracker();
+ mDocumentViewer = nullptr;
+ mSticky = true;
+ mWindowState = nullptr;
+ mViewerBounds.SetRect(0, 0, 0, 0);
+ mChildShells.Clear();
+ mRefreshURIList = nullptr;
+ mEditorData = nullptr;
+}
+
+nsresult nsSHEntryShared::SetDocumentViewer(nsIDocumentViewer* aViewer) {
+ MOZ_ASSERT(!aViewer || !mDocumentViewer,
+ "SHEntryShared already contains viewer");
+
+ if (mDocumentViewer || !aViewer) {
+ DropPresentationState();
+ }
+
+ // If we're setting mDocumentViewer 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());
+ mDocumentViewer = aViewer;
+
+ if (mDocumentViewer) {
+ // 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<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
+ shistory->AddToExpirationTracker(this);
+ }
+
+ // Store observed document in strong pointer in case it is removed from
+ // the contentviewer
+ mDocument = mDocumentViewer->GetDocument();
+ if (mDocument) {
+ mDocument->SetBFCacheEntry(this);
+ mDocument->AddMutationObserver(this);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSHEntryShared::RemoveFromBFCacheSync() {
+ MOZ_ASSERT(mDocumentViewer && mDocument, "we're not in the bfcache!");
+
+ // The call to DropPresentationState could drop the last reference, so hold
+ // |this| until RemoveDynEntriesForBFCacheEntry finishes.
+ RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
+
+ // DropPresentationState would clear mDocumentViewer.
+ nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
+ DropPresentationState();
+
+ if (viewer) {
+ viewer->Destroy();
+ }
+
+ // Now that we've dropped the viewer, we have to clear associated dynamic
+ // subframe entries.
+ nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
+ if (shistory) {
+ shistory->RemoveDynEntriesForBFCacheEntry(this);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSHEntryShared::RemoveFromBFCacheAsync() {
+ MOZ_ASSERT(mDocumentViewer && 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 mDocumentViewer & mDocument. Capture and
+ // release the references asynchronously so that the document doesn't get
+ // nuked mid-mutation.
+ nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
+ RefPtr<dom::Document> document = mDocument;
+ RefPtr<nsSHEntryShared> self = this;
+ nsresult rv = mDocument->Dispatch(NS_NewRunnableFunction(
+ "nsSHEntryShared::RemoveFromBFCacheAsync", [self, viewer, document]() {
+ if (viewer) {
+ viewer->Destroy();
+ }
+
+ nsCOMPtr<nsISHistory> 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;
+}
+
+// Don't evict a page from bfcache for attribute mutations on NAC subtrees like
+// scrollbars.
+static bool IgnoreMutationForBfCache(const nsINode& aNode) {
+ for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
+ if (!node->ChromeOnlyAccess()) {
+ break;
+ }
+ // Make sure we find a scrollbar in the ancestor chain, to be safe.
+ if (node->IsXULElement(nsGkAtoms::scrollbar)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsSHEntryShared::CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo&) {
+ if (!IgnoreMutationForBfCache(*aContent)) {
+ RemoveFromBFCacheAsync();
+ }
+}
+
+void nsSHEntryShared::AttributeChanged(dom::Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ if (!IgnoreMutationForBfCache(*aElement)) {
+ RemoveFromBFCacheAsync();
+ }
+}
+
+void nsSHEntryShared::ContentAppended(nsIContent* aFirstNewContent) {
+ if (!IgnoreMutationForBfCache(*aFirstNewContent)) {
+ RemoveFromBFCacheAsync();
+ }
+}
+
+void nsSHEntryShared::ContentInserted(nsIContent* aChild) {
+ if (!IgnoreMutationForBfCache(*aChild)) {
+ RemoveFromBFCacheAsync();
+ }
+}
+
+void nsSHEntryShared::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ if (!IgnoreMutationForBfCache(*aChild)) {
+ RemoveFromBFCacheAsync();
+ }
+}
diff --git a/docshell/shistory/nsSHEntryShared.h b/docshell/shistory/nsSHEntryShared.h
new file mode 100644
index 0000000000..5ff855ead4
--- /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 nsIDocShellTreeItem;
+class nsIDocumentViewer;
+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<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+ nsCOMPtr<nsIPrincipal> mPartitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+ nsCString mContentType;
+ // Child side updates layout history state when page is being unloaded or
+ // moved to bfcache.
+ nsCOMPtr<nsILayoutHistoryState> 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 NotifyListenersDocumentViewerEvicted();
+
+ 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<nsFrameLoader> 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<nsIDocShellTreeItem> mChildShells;
+
+ // These members aren't copied by SHEntrySharedChildState::CopyFrom() because
+ // they're specific to a particular content viewer.
+ nsCOMPtr<nsIDocumentViewer> mDocumentViewer;
+ RefPtr<mozilla::dom::Document> mDocument;
+ nsCOMPtr<nsISupports> mWindowState;
+ // FIXME Move to parent?
+ nsCOMPtr<nsIMutableArray> mRefreshURIList;
+ UniquePtr<nsDocShellEditorData> 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<nsSHEntryShared> 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 SetDocumentViewer(nsIDocumentViewer* aViewer);
+};
+
+#endif
diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp
new file mode 100644
index 0000000000..d69d78a9ec
--- /dev/null
+++ b/docshell/shistory/nsSHistory.cpp
@@ -0,0 +1,2410 @@
+/* -*- 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 <algorithm>
+
+#include "nsContentUtils.h"
+#include "nsCOMArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDocShell.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsHashKeys.h"
+#include "nsIDocShell.h"
+#include "nsIDocumentViewer.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 constexpr 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<nsSHistory> 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<nsISHEntry> 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<nsIURI> uri = shentry->GetURI(); \
+ LOG_SPEC(format, uri); \
+ } \
+ PR_END_MACRO
+
+// Calls a F on all registered session history listeners.
+template <typename F>
+static void NotifyListeners(nsAutoTObserverArray<nsWeakPtr, 2>& aListeners,
+ F&& f) {
+ for (const nsWeakPtr& weakPtr : aListeners.EndLimitedRange()) {
+ nsCOMPtr<nsISHistoryListener> 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<BrowsingContext> rootBC = mSHistory->GetBrowsingContext();
+ if (mozilla::SessionHistoryInParent() && rootBC) {
+ rootBC->Canonical()->HistoryCommitIndexAndLength();
+ }
+ }
+ }
+
+ RefPtr<nsSHistory> 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<nsSHistoryObserver> gObserver;
+
+NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver)
+
+// static
+void nsSHistoryObserver::PrefChanged(const char* aPref, void* aSelf) {
+ static_cast<nsSHistoryObserver*>(aSelf)->PrefChanged(aPref);
+}
+
+void nsSHistoryObserver::PrefChanged(const char* aPref) {
+ nsSHistory::UpdatePrefs();
+ nsSHistory::GloballyEvictDocumentViewers();
+}
+
+NS_IMETHODIMP
+nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "cacheservice:empty-cache") ||
+ !strcmp(aTopic, "memory-pressure")) {
+ nsSHistory::GloballyEvictAllDocumentViewers();
+ }
+
+ return NS_OK;
+}
+
+void nsSHistory::EvictDocumentViewerForEntry(nsISHEntry* aEntry) {
+ nsCOMPtr<nsIDocumentViewer> viewer = aEntry->GetDocumentViewer();
+ 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.
+ NotifyListenersDocumentViewerEvicted(1);
+ aEntry->SetDocumentViewer(nullptr);
+ aEntry->SyncPresentationState();
+ viewer->Destroy();
+ } else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry)) {
+ if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
+ nsCOMPtr<nsFrameLoaderOwner> owner =
+ do_QueryInterface(frameLoader->GetOwnerContent());
+ RefPtr<nsFrameLoader> 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::EvictDocumentViewerForEntry "
+ "destroying an nsFrameLoader."));
+ NotifyListenersDocumentViewerEvicted(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<HistoryTracker>(
+ 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<nsIObserverService> 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<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
+ obsSvc->RemoveObserver(gObserver, "memory-pressure");
+ }
+ gObserver = nullptr;
+ }
+}
+
+// static
+already_AddRefed<nsISHEntry> nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) {
+ nsCOMPtr<nsISHEntry> rootEntry = aEntry;
+ nsCOMPtr<nsISHEntry> 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<nsISHEntry> 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<nsDocShell*>(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<nsISHEntry> resultEntry;
+};
+
+nsresult nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry,
+ BrowsingContext* aOwnerBC,
+ int32_t aChildIndex, void* aData) {
+ nsCOMPtr<nsISHEntry> dest;
+
+ CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(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<void(nsISHEntry*)>& aCallback) {
+ MOZ_ASSERT(aEntry);
+
+ nsCOMPtr<nsISHistory> 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<nsIURI> 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<nsISHEntry> entry;
+ shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
+ if (entry) {
+ nsCOMPtr<nsIURI> 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<nsISHEntry> entry;
+ shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
+ if (entry) {
+ nsCOMPtr<nsIURI> 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<nsISHEntry> child;
+ nsCOMPtr<nsISHEntry> 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<SwapEntriesData*>(aData);
+ if (!aBC || aBC == data->ignoreBC) {
+ return NS_OK;
+ }
+
+ nsISHEntry* destTreeRoot = data->destTreeRoot;
+
+ nsCOMPtr<nsISHEntry> 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<nsISHEntry> 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<nsDocShell*>(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<int32_t>* aPreviousEntryIndex,
+ Maybe<int32_t>* 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<nsISHEntry> 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<nsISHistory> 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<BrowsingContext> 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<nsIURI> 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<nsIURI> uri = aEntry->GetURI();
+ nsAutoString title, name;
+ aEntry->GetTitle(title);
+ aEntry->GetName(name);
+
+ SHEntrySharedParentState* shared;
+ if (mozilla::SessionHistoryInParent()) {
+ shared = static_cast<SessionHistoryEntry*>(aEntry)->SharedInfo();
+ } else {
+ shared = static_cast<nsSHEntry*>(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<nsISHEntry> prevChild;
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsISHEntry> 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<nsIDHashKey, SessionHistoryEntry*>& aHashtable) {
+ if (!aEntry->BCHistoryLength().Modified()) {
+ ++(aEntry->BCHistoryLength());
+ }
+ aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
+ for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
+ if (entry) {
+ MarkAsInitialEntry(entry, aHashtable);
+ }
+ }
+}
+
+static void ClearEntries(SessionHistoryEntry* aEntry) {
+ aEntry->ClearBCHistoryLength();
+ for (const RefPtr<SessionHistoryEntry>& 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<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
+ if (aNumEntries != Length()) {
+ nsCOMPtr<SessionHistoryEntry> 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<SessionHistoryEntry> she = do_QueryInterface(mEntries[i]);
+ if (she) {
+ ClearEntries(she);
+ }
+ }
+
+ RefPtr<BrowsingContext> 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::NotifyListenersDocumentViewerEvicted(uint32_t aNumEvicted) {
+ NotifyListeners(mListeners, [aNumEvicted](auto l) {
+ l->OnDocumentViewerEvicted(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<nsISHistory> 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<nsISHistoryListener> listener = do_QueryReferent(weakPtr);
+ if (listener) {
+ bool retval = true;
+
+ if (NS_SUCCEEDED(listener->OnHistoryReload(&retval)) && !retval) {
+ *aCanReload = false;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::EvictOutOfRangeDocumentViewers(int32_t aIndex) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("nsSHistory::EvictOutOfRangeDocumentViewers %i", aIndex));
+
+ // Check our per SHistory object limit in the currently navigated SHistory
+ EvictOutOfRangeWindowDocumentViewers(aIndex);
+ // Check our total limit across all SHistory objects
+ GloballyEvictDocumentViewers();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::EvictDocumentViewersOrReplaceEntry(nsISHEntry* aNewSHEntry,
+ bool aReplace) {
+ if (!aReplace) {
+ int32_t curIndex;
+ GetIndex(&curIndex);
+ if (curIndex > -1) {
+ EvictOutOfRangeDocumentViewers(curIndex);
+ }
+ } else {
+ nsCOMPtr<nsISHEntry> rootSHEntry = nsSHistory::GetRootSHEntry(aNewSHEntry);
+
+ int32_t index = GetIndexOfEntry(rootSHEntry);
+ if (index > -1) {
+ ReplaceEntry(index, rootSHEntry);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::EvictAllDocumentViewers() {
+ // 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++) {
+ EvictDocumentViewerForEntry(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<nsISHistory> shistory = aEntry->GetShistory();
+ int32_t indexOfHistoryLoad =
+ shistory ? shistory->GetIndexOfEntry(aEntry) : -1;
+
+ nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
+ do_QueryInterface(aBrowsingContext->GetEmbedderElement());
+ if (frameLoaderOwner && aFrameLoader->GetMaybePendingBrowsingContext() &&
+ indexOfHistoryLoad >= 0) {
+ RefPtr<BrowsingContextWebProgress> 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<nsIURI> nextURI = aEntry->GetURI();
+ nsCOMPtr<nsIURI> nextOriginalURI = aEntry->GetOriginalURI();
+ nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>(
+ 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<CanonicalBrowsingContext> loadingBC =
+ aFrameLoader->GetMaybePendingBrowsingContext()->Canonical();
+ RefPtr<nsFrameLoader> currentFrameLoader =
+ frameLoaderOwner->GetFrameLoader();
+ // The current page can be bfcached, store the
+ // nsFrameLoader in the current SessionHistoryEntry.
+ RefPtr<SessionHistoryEntry> 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);
+ // EvictOutOfRangeDocumentViewers is called here explicitly to
+ // possibly evict the now in the bfcache document.
+ // HistoryCommitIndexAndLength might not have evicted that before the
+ // FrameLoader swap.
+ shistory->EvictOutOfRangeDocumentViewers(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<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
+ RefPtr<CanonicalBrowsingContext> canonicalBC =
+ aLoadEntry.mBrowsingContext->Canonical();
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(loadState->SHEntry());
+ nsCOMPtr<SessionHistoryEntry> currentShe =
+ canonicalBC->GetActiveSessionHistoryEntry();
+ MOZ_ASSERT(she);
+ RefPtr<nsFrameLoader> 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<nsFrameLoaderOwner> frameLoaderOwner =
+ do_QueryInterface(canonicalBC->GetEmbedderElement());
+ if (frameLoaderOwner) {
+ RefPtr<nsFrameLoader> 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 && !canonicalBC->IsReplaced()) {
+ 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();
+ }
+ }
+
+ RefPtr<BrowsingContext> bc = aLoadEntry.mBrowsingContext;
+ RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
+ bc->LoadURI(loadState, false);
+}
+
+/* static */
+void nsSHistory::LoadURIs(nsTArray<LoadEntryResult>& aLoadResults) {
+ for (LoadEntryResult& loadEntry : aLoadResults) {
+ LoadURIOrBFCache(loadEntry);
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::Reload(uint32_t aReloadFlags) {
+ nsTArray<LoadEntryResult> 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<LoadEntryResult>& 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<LoadEntryResult> loadResults;
+ nsresult rv = ReloadCurrentEntry(loadResults);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LoadURIs(loadResults);
+ return NS_OK;
+}
+
+nsresult nsSHistory::ReloadCurrentEntry(
+ nsTArray<LoadEntryResult>& 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::EvictOutOfRangeWindowDocumentViewers(int32_t aIndex) {
+ // XXX rename method to EvictDocumentViewersExceptAroundIndex, 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(
+ ("EvictOutOfRangeWindowDocumentViewers(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<nsIDocumentViewer> safeViewers;
+ nsTArray<RefPtr<nsFrameLoader>> safeFrameLoaders;
+ for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) {
+ nsCOMPtr<nsIDocumentViewer> viewer = mEntries[i]->GetDocumentViewer();
+ if (viewer) {
+ safeViewers.AppendObject(viewer);
+ } else if (nsCOMPtr<SessionHistoryEntry> 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<nsISHEntry> entry = mEntries[i];
+ nsCOMPtr<nsIDocumentViewer> viewer = entry->GetDocumentViewer();
+ if (viewer) {
+ if (safeViewers.IndexOf(viewer) == -1) {
+ EvictDocumentViewerForEntry(entry);
+ }
+ } else if (nsCOMPtr<SessionHistoryEntry> she =
+ do_QueryInterface(mEntries[i])) {
+ nsFrameLoader* frameLoader = she->GetFrameLoader();
+ if (frameLoader) {
+ if (!safeFrameLoaders.Contains(frameLoader)) {
+ EvictDocumentViewerForEntry(entry);
+ }
+ }
+ }
+ }
+}
+
+namespace {
+
+class EntryAndDistance {
+ public:
+ EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist)
+ : mSHistory(aSHistory),
+ mEntry(aEntry),
+ mViewer(aEntry->GetDocumentViewer()),
+ mLastTouched(mEntry->GetLastTouched()),
+ mDistance(aDist) {
+ nsCOMPtr<SessionHistoryEntry> 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<nsSHistory> mSHistory;
+ nsCOMPtr<nsISHEntry> mEntry;
+ nsCOMPtr<nsIDocumentViewer> mViewer;
+ RefPtr<nsFrameLoader> mFrameLoader;
+ uint32_t mLastTouched;
+ int32_t mDistance;
+};
+
+} // namespace
+
+// static
+void nsSHistory::GloballyEvictDocumentViewers() {
+ // 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<EntryAndDistance> 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<EntryAndDistance> 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::EvictDocumentViewers()
+ // often enough. Once we do call EvictDocumentViewers() 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<nsISHEntry> entry = shist->mEntries[i];
+ nsCOMPtr<nsIDocumentViewer> viewer = entry->GetDocumentViewer();
+
+ bool found = false;
+ bool hasDocumentViewerOrFrameLoader = false;
+ if (viewer) {
+ hasDocumentViewerOrFrameLoader = 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 == viewer) {
+ container.mDistance =
+ std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex));
+ found = true;
+ break;
+ }
+ }
+ } else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(entry)) {
+ if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
+ hasDocumentViewerOrFrameLoader = 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 (hasDocumentViewerOrFrameLoader && !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)->EvictDocumentViewerForEntry(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<nsISHEntry> 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::EvictExpiredDocumentViewerForEntry(
+ SHEntrySharedParentState* aEntry) {
+ int32_t index;
+ nsCOMPtr<nsISHEntry> shEntry;
+ FindEntryForBFCache(aEntry, getter_AddRefs(shEntry), &index);
+
+ if (index == mIndex) {
+ NS_WARNING("How did the current SHEntry expire?");
+ }
+
+ if (shEntry) {
+ EvictDocumentViewerForEntry(shEntry);
+ }
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::AddToExpirationTracker(SHEntrySharedParentState* aEntry) {
+ RefPtr<SHEntrySharedParentState> entry = aEntry;
+ if (!mHistoryTracker || !entry) {
+ return;
+ }
+
+ mHistoryTracker->AddObject(entry);
+ return;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::RemoveFromExpirationTracker(SHEntrySharedParentState* aEntry) {
+ RefPtr<SHEntrySharedParentState> 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::GloballyEvictAllDocumentViewers() {
+ int32_t maxViewers = sHistoryMaxTotalViewers;
+ sHistoryMaxTotalViewers = 0;
+ GloballyEvictDocumentViewers();
+ sHistoryMaxTotalViewers = maxViewers;
+}
+
+void GetDynamicChildren(nsISHEntry* aEntry, nsTArray<nsID>& aDocshellIDs) {
+ int32_t count = aEntry->GetChildCount();
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> 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<nsID>& aDocshellIDs) {
+ bool didRemove = false;
+ int32_t childCount = aRoot->GetChildCount();
+ for (int32_t i = childCount - 1; i >= 0; --i) {
+ nsCOMPtr<nsISHEntry> 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<nsID>& aEntryIDs) {
+ nsCOMPtr<nsISHEntry> 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<nsISHEntry> 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<nsISHEntry> 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<SessionHistoryEntry> she = do_QueryInterface(root1);
+ if (she) {
+ ClearEntries(she);
+ }
+ mEntries.RemoveElementAt(aIndex);
+
+ // FIXME Bug 1546350: Reimplement history listeners.
+ // if (mRootBC && mRootBC->GetDocShell()) {
+ // static_cast<nsDocShell*>(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<nsID>& aIDs, int32_t aStartIndex) {
+ bool didRemove;
+ RemoveEntries(aIDs, aStartIndex, &didRemove);
+ if (didRemove) {
+ RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
+ if (rootBC && rootBC->GetDocShell()) {
+ rootBC->GetDocShell()->DispatchLocationChangeEvent();
+ }
+ }
+}
+
+void nsSHistory::RemoveEntries(nsTArray<nsID>& 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<nsID, 16> ids;
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> 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<nsISHEntry> entry(aEntry);
+ if (!entry) {
+ GetEntryAtIndex(aIndex, getter_AddRefs(entry));
+ }
+
+ if (entry) {
+ AutoTArray<nsID, 16> toBeRemovedEntries;
+ GetDynamicChildren(entry, toBeRemovedEntries);
+ if (toBeRemovedEntries.Length()) {
+ RemoveEntries(toBeRemovedEntries, aIndex);
+ }
+ }
+}
+
+void nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry) {
+ int32_t index;
+ nsCOMPtr<nsISHEntry> shEntry;
+ FindEntryForBFCache(static_cast<nsSHEntryShared*>(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<LoadEntryResult> 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<LoadEntryResult>& 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<nsISHEntry> 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<LoadEntryResult>& 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<LoadEntryResult>& aLoadResults,
+ bool aSameEpoch, bool aLoadCurrentEntry,
+ bool aUserActivation) {
+ MOZ_LOG(gSHistoryLog, LogLevel::Debug,
+ ("LoadEntry(%d, 0x%lx, %u)", aIndex, aLoadType, aHistCmd));
+ RefPtr<BrowsingContext> 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<nsISHEntry> prevEntry;
+ nsCOMPtr<nsISHEntry> 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<nsIURI> 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<LoadEntryResult>& 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<RefPtr<BrowsingContext>> 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<nsISHEntry> nChild;
+ aNextEntry->GetChildAt(i, getter_AddRefs(nChild));
+ if (!nChild) {
+ continue;
+ }
+ nsID docshellID;
+ nChild->GetDocshellID(docshellID);
+
+ // Then find the associated docshell.
+ RefPtr<BrowsingContext> bcChild;
+ for (const RefPtr<BrowsingContext>& 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<nsISHEntry> pChild;
+ for (int32_t k = 0; k < pcnt; ++k) {
+ nsCOMPtr<nsISHEntry> 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<LoadEntryResult>& aLoadResults,
+ bool aLoadCurrentEntry, bool aUserActivation,
+ int32_t aOffset) {
+ MOZ_ASSERT(aFrameBC && aFrameEntry);
+
+ LoadEntryResult* loadResult = aLoadResults.AppendElement();
+ loadResult->mBrowsingContext = aFrameBC;
+
+ nsCOMPtr<nsIURI> newURI = aFrameEntry->GetURI();
+ RefPtr<nsDocShellLoadState> 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->SetIsExemptFromHTTPSFirstMode(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<SessionHistoryEntry> she = do_QueryInterface(aFrameEntry);
+ aFrameBC->Canonical()->AddLoadingSessionHistoryEntry(
+ loadState->GetLoadingSessionHistoryInfo()->mLoadId, she);
+ }
+
+ nsCOMPtr<nsIURI> originalURI = aFrameEntry->GetOriginalURI();
+ loadState->SetOriginalURI(originalURI);
+
+ loadState->SetLoadReplace(aFrameEntry->GetLoadReplace());
+
+ loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ aFrameEntry->GetTriggeringPrincipal();
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+ loadState->SetFirstParty(false);
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aFrameEntry->GetCsp();
+ loadState->SetCsp(csp);
+
+ loadResult->mLoadState = std::move(loadState);
+}
+
+NS_IMETHODIMP
+nsSHistory::CreateEntry(nsISHEntry** aEntry) {
+ nsCOMPtr<nsISHEntry> 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<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
+ SessionHistoryEntry* aEntry) {
+ aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
+ for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
+ if (entry) {
+ CollectEntries(aHashtable, entry);
+ }
+ }
+}
+
+static void UpdateEntryLength(
+ nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& 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<SessionHistoryEntry>& entry : aNewEntry->Children()) {
+ if (entry) {
+ UpdateEntryLength(aHashtable, entry, aMove);
+ }
+ }
+}
+
+void nsSHistory::UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry,
+ bool aMove) {
+ nsCOMPtr<SessionHistoryEntry> oldSHE = do_QueryInterface(aOldEntry);
+ nsCOMPtr<SessionHistoryEntry> newSHE = do_QueryInterface(aNewEntry);
+
+ if (!oldSHE || !newSHE) {
+ return;
+ }
+
+ nsTHashMap<nsIDHashKey, SessionHistoryEntry*> 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..97bbe7f0a9
--- /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<nsSHistory>,
+ public nsISHistory,
+ public nsSupportsWeakReference {
+ public:
+ // The timer based history tracker is used to evict bfcache on expiration.
+ class HistoryTracker final
+ : public nsExpirationTracker<mozilla::dom::SHEntrySharedParentState, 3> {
+ 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->EvictExpiredDocumentViewerForEntry(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<nsISHEntry> 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<void(nsISHEntry*)>& aCallback);
+
+ nsTArray<nsCOMPtr<nsISHEntry>>& Entries() { return mEntries; }
+
+ void NotifyOnHistoryReplaceEntry();
+
+ void RemoveEntries(nsTArray<nsID>& 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<mozilla::dom::BrowsingContext> mBrowsingContext;
+ RefPtr<nsDocShellLoadState> mLoadState;
+ };
+
+ static void LoadURIs(nsTArray<LoadEntryResult>& 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<LoadEntryResult>& aLoadResults);
+ nsresult ReloadCurrentEntry(nsTArray<LoadEntryResult>& aLoadResults);
+ nsresult GotoIndex(int32_t aIndex, nsTArray<LoadEntryResult>& aLoadResults,
+ bool aSameEpoch, bool aLoadCurrentEntry,
+ bool aUserActivation);
+
+ void WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
+ int32_t* aOutEndIndex);
+ void NotifyListenersDocumentViewerEvicted(uint32_t aNumEvicted);
+
+ int32_t Length() { return int32_t(mEntries.Length()); }
+ int32_t Index() { return mIndex; }
+ already_AddRefed<mozilla::dom::BrowsingContext> 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<mozilla::dom::BrowsingContext> rootBC(GetBrowsingContext());
+ UpdateRootBrowsingContextState(rootBC);
+ }
+
+ void GetEpoch(uint64_t& aEpoch,
+ mozilla::Maybe<mozilla::dom::ContentParentId>& aId) const {
+ aEpoch = mEpoch;
+ aId = mEpochParentId;
+ }
+ void SetEpoch(uint64_t aEpoch,
+ mozilla::Maybe<mozilla::dom::ContentParentId> 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<LoadEntryResult>& aLoadResults,
+ bool aLoadCurrentEntry, bool aUserActivation,
+ int32_t aOffset);
+ void InitiateLoad(nsISHEntry* aFrameEntry,
+ mozilla::dom::BrowsingContext* aFrameBC, long aLoadType,
+ nsTArray<LoadEntryResult>& aLoadResult,
+ bool aLoadCurrentEntry, bool aUserActivation,
+ int32_t aOffset);
+
+ nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd,
+ nsTArray<LoadEntryResult>& 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 EvictOutOfRangeWindowDocumentViewers(int32_t aIndex);
+ void EvictDocumentViewerForEntry(nsISHEntry* aEntry);
+ static void GloballyEvictDocumentViewers();
+ static void GloballyEvictAllDocumentViewers();
+
+ // 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<LoadEntryResult>& 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<nsCOMPtr<nsISHEntry>> mEntries; // entries are never null
+ private:
+ // Track all bfcache entries and evict on expiration.
+ mozilla::UniquePtr<HistoryTracker> mHistoryTracker;
+
+ int32_t mIndex; // -1 means "no index"
+ int32_t mRequestedIndex; // -1 means "no requested index"
+
+ // Session History listeners
+ nsAutoTObserverArray<nsWeakPtr, 2> 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<mozilla::dom::ContentParentId> 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<nsSHistory*>(aSHistory);
+ if (shistory && !shistory->HasOngoingUpdate()) {
+ shistory->SetHasOngoingUpdate(true);
+ mSHistory = shistory;
+ }
+ }
+
+ ~CallerWillNotifyHistoryIndexAndLengthChanges() {
+ if (mSHistory) {
+ mSHistory->SetHasOngoingUpdate(false);
+ }
+ }
+
+ RefPtr<nsSHistory> mSHistory;
+};
+
+inline nsISupports* ToSupports(nsSHistory* aObj) {
+ return static_cast<nsISHistory*>(aObj);
+}
+
+#endif /* nsSHistory */