Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "mozilla/StaticPrefs_browser.h"
#include "nsIXULRuntime.h"
#include "nsComponentManagerUtils.h"
#include "nsSHistory.h"
#include "nsDocShell.h"
#include "nsXULAppAPI.h"
extern mozilla::LazyLogModule gSHLog;
namespace mozilla {
namespace dom {
ChildSHistory::PendingAsyncHistoryNavigation::PendingAsyncHistoryNavigation(
ChildSHistory* aHistory, int32_t aOffset, bool aRequireUserInteraction,
bool aUserActivation)
: Runnable("PendingAsyncHistoryNavigation"),
mHistory(aHistory),
mRequireUserInteraction(aRequireUserInteraction),
mUserActivation(aUserActivation),
mOffset(aOffset) {}
ChildSHistory::PendingAsyncHistoryNavigation::PendingAsyncHistoryNavigation(
ChildSHistory* aHistory, const nsID& aKey,
BrowsingContext* aBrowsingContext, bool aRequireUserInteraction,
bool aUserActivation, bool aCheckForCancelation,
std::function<void(nsresult)>&& aResolver)
: Runnable("PendingAsyncHistoryNavigation"),
mHistory(aHistory),
mRequireUserInteraction(aRequireUserInteraction),
mUserActivation(aUserActivation),
mCheckForCancelation(aCheckForCancelation),
mOffset(std::numeric_limits<int32_t>::max()),
mKey(Some(aKey)),
mBrowsingContext(aBrowsingContext),
mResolver(Some(aResolver)) {}
nsresult ChildSHistory::PendingAsyncHistoryNavigation::Run() {
if (isInList()) {
remove();
if (mKey) {
RefPtr browsingContext = mBrowsingContext;
mHistory->GotoKey(*mKey, browsingContext, mRequireUserInteraction,
mUserActivation, mCheckForCancelation, *mResolver,
IgnoreErrors());
} else {
mHistory->Go(mOffset, mRequireUserInteraction, mUserActivation,
IgnoreErrors());
}
}
return NS_OK;
}
ChildSHistory::ChildSHistory(BrowsingContext* aBrowsingContext)
: mBrowsingContext(aBrowsingContext) {}
ChildSHistory::~ChildSHistory() {
if (mHistory) {
static_cast<nsSHistory*>(mHistory.get())->SetBrowsingContext(nullptr);
}
}
void ChildSHistory::SetBrowsingContext(BrowsingContext* aBrowsingContext) {
mBrowsingContext = aBrowsingContext;
}
int32_t ChildSHistory::Count() {
int32_t length = mLength;
for (const auto& change : mPendingSHistoryChanges) {
length += change.mLengthDelta;
}
return length;
}
int32_t ChildSHistory::Index() {
int32_t index = mIndex;
for (const auto& change : mPendingSHistoryChanges) {
index += change.mIndexDelta;
}
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 (XRE_IsParentProcess()) {
nsCOMPtr<nsISHistory> shistory =
mBrowsingContext->Canonical()->GetSessionHistory();
if (shistory) {
aRv = shistory->Reload(aReloadFlags);
}
} else {
ContentChild::GetSingleton()->SendHistoryReload(mBrowsingContext,
aReloadFlags);
}
}
bool ChildSHistory::CanGo(int32_t aOffset, bool aRequireUserInteraction) {
CheckedInt<int32_t> index = Index();
index += aOffset;
if (!index.isValid()) {
return false;
}
if (!mHistory || aOffset >= 0) {
return index.value() < Count() && index.value() >= 0;
}
if (!aRequireUserInteraction) {
return index.value() >= 0;
}
bool canGoBack;
mHistory->CanGoBackFromEntryAtIndex(Index(), &canGoBack);
return canGoBack;
}
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 (!StaticPrefs::browser_navigation_requireUserInteraction() ||
!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) {
CheckedInt<int32_t> index = Index();
MOZ_LOG(gSHLog, LogLevel::Debug,
("ChildSHistory::AsyncGo(%d), current index = %d", aOffset,
index.value()));
RefPtr<PendingAsyncHistoryNavigation> asyncNav =
new PendingAsyncHistoryNavigation(this, aOffset, aRequireUserInteraction,
aUserActivation);
mPendingNavigations.insertBack(asyncNav);
NS_DispatchToCurrentThread(asyncNav.forget());
}
void ChildSHistory::AsyncGo(const nsID& aKey, BrowsingContext* aNavigable,
bool aRequireUserInteraction, bool aUserActivation,
bool aCheckForCancelation,
std::function<void(nsresult)>&& aResolver) {
CheckedInt<int32_t> index = Index();
MOZ_LOG_FMT(gSHLog, LogLevel::Debug,
"ChildSHistory::AsyncGo({}), current index = {}",
aKey.ToString().get(), index.value());
RefPtr<PendingAsyncHistoryNavigation> asyncNav =
new PendingAsyncHistoryNavigation(
this, aKey, aNavigable, aRequireUserInteraction, aUserActivation,
aCheckForCancelation, std::move(aResolver));
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 (!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());
}
});
}
void ChildSHistory::GotoKey(const nsID& aKey, BrowsingContext* aNavigable,
bool aRequireUserInteraction, bool aUserActivation,
bool aCheckForCancelation,
const std::function<void(nsresult)>& aResolver,
ErrorResult& aRv) {
if (!mPendingEpoch) {
mPendingEpoch = true;
RefPtr<ChildSHistory> self(this);
NS_DispatchToCurrentThread(
NS_NewRunnableFunction("UpdateEpochRunnable", [self] {
self->mHistoryEpoch++;
self->mPendingEpoch = false;
}));
}
nsCOMPtr<nsISHistory> shistory = mHistory;
aNavigable->NavigationTraverse(
aKey, mHistoryEpoch, aUserActivation,
/* aCheckForCancelation */ aCheckForCancelation,
[shistory, aResolver](nsresult aResult) { aResolver(aResult); });
}
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();
}
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