diff options
Diffstat (limited to 'docshell/shistory/ChildSHistory.cpp')
-rw-r--r-- | docshell/shistory/ChildSHistory.cpp | 294 |
1 files changed, 294 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 |