diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp index 73392a585bf9..ab8ec9436c5e 100644 --- a/docshell/base/BrowsingContext.cpp +++ b/docshell/base/BrowsingContext.cpp @@ -35,6 +35,7 @@ #include "mozilla/dom/Location.h" #include "mozilla/dom/LocationBinding.h" #include "mozilla/dom/MediaDevices.h" +#include "mozilla/dom/Navigation.h" #include "mozilla/dom/PopupBlocker.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/SessionStoreChild.h" @@ -88,6 +89,7 @@ #include "GVAutoplayRequestStatusIPC.h" extern mozilla::LazyLogModule gAutoplayPermissionLog; +extern mozilla::LazyLogModule gNavigationLog; extern mozilla::LazyLogModule gTimeoutDeferralLog; #define AUTOPLAY_LOG(msg, ...) \ @@ -3924,6 +3926,25 @@ void BrowsingContext::ClearCachedValuesOfLocations() { } } +void BrowsingContext::GetContiguousHistoryEntries( + SessionHistoryInfo& aActiveEntry, Navigation* aNavigation) { + if (!aNavigation) { + return; + } + if (XRE_IsContentProcess()) { + MOZ_ASSERT(ContentChild::GetSingleton()); + ContentChild::GetSingleton()->SendGetContiguousSessionHistoryInfos( + this, aActiveEntry, + [aActiveEntry, navigation = RefPtr(aNavigation)](auto aInfos) mutable { + navigation->InitializeHistoryEntries(aInfos, &aActiveEntry); + }, + [](auto aReason) { MOZ_ASSERT(false, "How did this happen?"); }); + } else { + auto infos = Canonical()->GetContiguousSessionHistoryInfos(aActiveEntry); + aNavigation->InitializeHistoryEntries(infos, &aActiveEntry); + } +} + } // namespace dom namespace ipc { diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h index 13815ddf7e13..2d27484a40fb 100644 --- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h @@ -975,6 +975,9 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { void LocationCreated(dom::Location* aLocation); void ClearCachedValuesOfLocations(); + void GetContiguousHistoryEntries(SessionHistoryInfo& aActiveEntry, + Navigation* aNavigation); + protected: virtual ~BrowsingContext(); BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup, diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp index 1753814e7d74..32f3d2c2d287 100644 --- a/docshell/base/CanonicalBrowsingContext.cpp +++ b/docshell/base/CanonicalBrowsingContext.cpp @@ -18,6 +18,7 @@ #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/Navigation.h" #include "mozilla/dom/PBrowserParent.h" #include "mozilla/dom/PBackgroundSessionStorageCache.h" #include "mozilla/dom/PWindowGlobalParent.h" @@ -671,6 +672,26 @@ CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad( return nullptr; } +mozilla::Span +CanonicalBrowsingContext::GetContiguousSessionHistoryInfos( + SessionHistoryInfo& aInfo) { + MOZ_ASSERT(Navigation::IsAPIEnabled()); + + nsISHistory* history = GetSessionHistory(); + if (!history) { + return {}; + } + + mActiveContiguousEntries.ClearAndRetainStorage(); + nsSHistory::WalkContiguousEntriesInOrder(mActiveEntry, [&](auto* aEntry) { + if (nsCOMPtr entry = do_QueryObject(aEntry)) { + mActiveContiguousEntries.AppendElement(entry->Info()); + } + }); + + return mActiveContiguousEntries; +} + using PrintPromise = CanonicalBrowsingContext::PrintPromise; #ifdef NS_PRINTING // Clients must call StaticCloneForPrintingCreated or diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h index 0283e203a375..3daa36db5723 100644 --- a/docshell/base/CanonicalBrowsingContext.h +++ b/docshell/base/CanonicalBrowsingContext.h @@ -325,6 +325,9 @@ class CanonicalBrowsingContext final : public BrowsingContext { void GetLoadingSessionHistoryInfoFromParent( Maybe& aLoadingInfo); + mozilla::Span GetContiguousSessionHistoryInfos( + SessionHistoryInfo& aInfo); + void HistoryCommitIndexAndLength(); void SynchronizeLayoutHistoryState(); @@ -637,6 +640,8 @@ class CanonicalBrowsingContext final : public BrowsingContext { bool mFullyDiscarded = false; nsTArray> mFullyDiscardedListeners; + + nsTArray mActiveContiguousEntries; }; } // namespace dom diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 9663b96e0d9b..4dc2023d04b3 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -68,6 +68,9 @@ #include "mozilla/dom/FragmentDirective.h" #include "mozilla/dom/HTMLAnchorElement.h" #include "mozilla/dom/HTMLIFrameElement.h" +#include "mozilla/dom/Navigation.h" +#include "mozilla/dom/NavigationBinding.h" +#include "mozilla/dom/NavigationHistoryEntry.h" #include "mozilla/dom/PerformanceNavigation.h" #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/PopupBlocker.h" @@ -280,6 +283,7 @@ static mozilla::LazyLogModule gDocShellAndDOMWindowLeakLogging( #endif static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak"); extern mozilla::LazyLogModule gPageCacheLog; +extern mozilla::LazyLogModule gNavigationLog; mozilla::LazyLogModule gSHLog("SessionHistory"); extern mozilla::LazyLogModule gSHIPBFCacheLog; @@ -9059,6 +9063,21 @@ nsresult nsDocShell::HandleSameDocumentNavigation( // destroy the docshell, nulling out mScriptGlobal. Hold a stack // reference to avoid null derefs. See bug 914521. if (win) { + if (RefPtr navigation = win->Navigation()) { + MOZ_LOG(gNavigationLog, LogLevel::Debug, + ("nsDocShell %p triggering a navigation event from " + "HandleSameDocumentNavigation", + this)); + // Corresponds to step 6.4.2 from the Updating the document algorithm: + // https://html.spec.whatwg.org/multipage/browsing-the-web.html#updating-the-document + navigation->UpdateEntriesForSameDocumentNavigation( + mActiveEntry.get(), + LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY) + ? NavigationType::Replace + : aLoadState->LoadIsFromSessionHistory() ? NavigationType::Traverse + : NavigationType::Push); + } + // Fire a hashchange event URIs differ, and only in their hashes. // If the fragment contains a directive, compare hasRef. bool doHashchange = aState.mSameExceptHashes && @@ -11323,19 +11342,23 @@ nsDocShell::AddState(JS::Handle aData, const nsAString& aTitle, } // end of same-origin check // Step 8: call "URL and history update steps" - rv = UpdateURLAndHistory(document, newURI, scContainer, aTitle, aReplace, + rv = UpdateURLAndHistory(document, newURI, scContainer, + aReplace ? NavigationHistoryBehavior::Replace + : NavigationHistoryBehavior::Push, currentURI, equalURIs); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } -nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI, - nsIStructuredCloneContainer* aData, - const nsAString& aTitle, bool aReplace, - nsIURI* aCurrentURI, bool aEqualURIs) { +nsresult nsDocShell::UpdateURLAndHistory( + Document* aDocument, nsIURI* aNewURI, nsIStructuredCloneContainer* aData, + NavigationHistoryBehavior aHistoryHandling, nsIURI* aCurrentURI, + bool aEqualURIs) { // Implements // https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps + MOZ_ASSERT(aHistoryHandling != NavigationHistoryBehavior::Auto); + bool isReplace = aHistoryHandling == NavigationHistoryBehavior::Replace; // If we have a pending title change, handle it before creating a new entry. aDocument->DoNotifyPossibleTitleChange(); @@ -11344,7 +11367,7 @@ nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI, // history. This will erase all SHEntries after the new entry and make this // entry the current one. This operation may modify mOSHE, which we need // later, so we keep a reference here. - NS_ENSURE_TRUE(mOSHE || mActiveEntry || aReplace, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(mOSHE || mActiveEntry || isReplace, NS_ERROR_FAILURE); nsCOMPtr oldOSHE = mOSHE; // If this push/replaceState changed the document's current URI and the new @@ -11367,7 +11390,7 @@ nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI, mLoadType = LOAD_PUSHSTATE; nsCOMPtr newSHEntry; - if (!aReplace) { + if (!isReplace) { // Step 2. // Step 2.2, "Remove any tasks queued by the history traversal task @@ -11520,7 +11543,7 @@ nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI, RefPtr rootSH = GetRootSessionHistory(); if (rootSH) { rootSH->LegacySHistory()->EvictDocumentViewersOrReplaceEntry(newSHEntry, - aReplace); + isReplace); } } @@ -11560,6 +11583,19 @@ nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI, } aDocument->SetStateObject(aData); + if (RefPtr navigation = aDocument->GetInnerWindow()->Navigation()) { + MOZ_LOG(gNavigationLog, LogLevel::Debug, + ("nsDocShell %p triggering a navigation event for a same-document " + "navigation from UpdateURLAndHistory -> isReplace: %s", + this, isReplace ? "true" : "false")); + // Step 11: Update the navigation API entries for a same-document + // navigation given document's relevant global object's navigation API, + // newEntry, and historyHandling. + navigation->UpdateEntriesForSameDocumentNavigation( + mActiveEntry.get(), + isReplace ? NavigationType::Replace : NavigationType::Push); + } + return NS_OK; } @@ -13659,6 +13695,16 @@ void nsDocShell::MoveLoadingToActiveEntry(bool aPersist, bool aExpired, *loadingEntry, loadType, aPreviousURI, previousActiveEntry.get(), aPersist, false, aExpired, aCacheKey); } + + // Only update navigation if the new entry will be persisted (i.e., is not + // an about: page). + if (aPersist && GetWindow() && GetWindow()->GetCurrentInnerWindow()) { + if (RefPtr navigation = + GetWindow()->GetCurrentInnerWindow()->Navigation()) { + mBrowsingContext->GetContiguousHistoryEntries(*mActiveEntry, + navigation); + } + } } } @@ -13802,3 +13848,8 @@ void nsDocShell::MaybeDisconnectChildListenersOnPageHide() { mChannelToDisconnectOnPageHide = 0; } } + +bool nsDocShell::IsSameDocumentAsActiveEntry( + const mozilla::dom::SessionHistoryInfo& aSHInfo) { + return mActiveEntry ? mActiveEntry->SharesDocumentWith(aSHInfo) : false; +} diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 888741f8490d..f22a33373332 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -48,6 +48,7 @@ namespace dom { class ClientInfo; class ClientSource; class EventTarget; +enum class NavigationHistoryBehavior : uint8_t; class SessionHistoryInfo; struct LoadingSessionHistoryInfo; struct Wireframe; @@ -1111,8 +1112,7 @@ class nsDocShell final : public nsDocLoader, * URI. * @param aNewURI the new URI. * @param aData The serialized state data. May be null. - * @param aTitle The new title. May be empty. - * @param aReplace whether this should replace the exising SHEntry. + * @param aHistoryHandling how to handle updating the history entries. * * Arguments we need internally because deriving them from the * others is a bit complicated: @@ -1120,11 +1120,14 @@ class nsDocShell final : public nsDocLoader, * @param aCurrentURI the current URI we're working with. Might be null. * @param aEqualURIs whether the two URIs involved are equal. */ - nsresult UpdateURLAndHistory(mozilla::dom::Document* aDocument, - nsIURI* aNewURI, - nsIStructuredCloneContainer* aData, - const nsAString& aTitle, bool aReplace, - nsIURI* aCurrentURI, bool aEqualURIs); + nsresult UpdateURLAndHistory( + mozilla::dom::Document* aDocument, nsIURI* aNewURI, + nsIStructuredCloneContainer* aData, + mozilla::dom::NavigationHistoryBehavior aHistoryHandling, + nsIURI* aCurrentURI, bool aEqualURIs); + + bool IsSameDocumentAsActiveEntry( + const mozilla::dom::SessionHistoryInfo& aSHInfo); private: void SetCurrentURIInternal(nsIURI* aURI); diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp index 629e00bf15b5..c5d5375ea8ba 100644 --- a/docshell/shistory/SessionHistoryEntry.cpp +++ b/docshell/shistory/SessionHistoryEntry.cpp @@ -242,6 +242,10 @@ bool SessionHistoryInfo::IsSubFrame() const { return mSharedState.Get()->mIsFrameNavigation; } +nsStructuredCloneContainer* SessionHistoryInfo::GetNavigationState() const { + return mSharedState.Get()->mNavigationState.get(); +} + void SessionHistoryInfo::SetSaveLayoutStateFlag(bool aSaveLayoutStateFlag) { MOZ_ASSERT(XRE_IsParentProcess()); static_cast(mSharedState.Get())->mSaveLayoutState = @@ -1557,6 +1561,8 @@ void IPDLParamTraits::Write( WriteIPDLParam(aWriter, aActor, stateData); WriteIPDLParam(aWriter, aActor, aParam.mSrcdocData); WriteIPDLParam(aWriter, aActor, aParam.mBaseURI); + WriteIPDLParam(aWriter, aActor, aParam.mNavigationKey); + WriteIPDLParam(aWriter, aActor, aParam.mNavigationId); WriteIPDLParam(aWriter, aActor, aParam.mLoadReplace); WriteIPDLParam(aWriter, aActor, aParam.mURIWasModified); WriteIPDLParam(aWriter, aActor, aParam.mScrollRestorationIsManual); @@ -1599,6 +1605,8 @@ bool IPDLParamTraits::Read( !ReadIPDLParam(aReader, aActor, &stateData) || !ReadIPDLParam(aReader, aActor, &aResult->mSrcdocData) || !ReadIPDLParam(aReader, aActor, &aResult->mBaseURI) || + !ReadIPDLParam(aReader, aActor, &aResult->mNavigationKey) || + !ReadIPDLParam(aReader, aActor, &aResult->mNavigationId) || !ReadIPDLParam(aReader, aActor, &aResult->mLoadReplace) || !ReadIPDLParam(aReader, aActor, &aResult->mURIWasModified) || !ReadIPDLParam(aReader, aActor, &aResult->mScrollRestorationIsManual) || diff --git a/docshell/shistory/SessionHistoryEntry.h b/docshell/shistory/SessionHistoryEntry.h index c7f1fea5e954..7ea6c6fcc3a3 100644 --- a/docshell/shistory/SessionHistoryEntry.h +++ b/docshell/shistory/SessionHistoryEntry.h @@ -161,6 +161,12 @@ class SessionHistoryInfo { bool GetPersist() const { return mPersist; } + nsID& NavigationKey() { return mNavigationKey; } + const nsID& NavigationKey() const { return mNavigationKey; } + const nsID& NavigationId() const { return mNavigationId; } + + nsStructuredCloneContainer* GetNavigationState() const; + private: friend class SessionHistoryEntry; friend struct mozilla::ipc::IPDLParamTraits; @@ -182,6 +188,10 @@ class SessionHistoryInfo { Maybe mSrcdocData; nsCOMPtr mBaseURI; + // Fields needed for NavigationHistoryEntry. + nsID mNavigationKey = nsID::GenerateUUID(); + nsID mNavigationId = nsID::GenerateUUID(); + bool mLoadReplace = false; bool mURIWasModified = false; bool mScrollRestorationIsManual = false; @@ -353,12 +363,8 @@ class HistoryEntryCounterForBrowsingContext { // 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 \ - } \ - } +#define NS_SESSIONHISTORYENTRY_IID \ + {0x5b66a244, 0x8cec, 0x4caa, {0xaa, 0x0a, 0x78, 0x92, 0xfd, 0x17, 0xa6, 0x67}} class SessionHistoryEntry : public nsISHEntry, public nsSupportsWeakReference { public: diff --git a/docshell/shistory/nsSHEntryShared.cpp b/docshell/shistory/nsSHEntryShared.cpp index 850e559b387f..480996b06b89 100644 --- a/docshell/shistory/nsSHEntryShared.cpp +++ b/docshell/shistory/nsSHEntryShared.cpp @@ -109,6 +109,7 @@ void SHEntrySharedParentState::CopyFrom(SHEntrySharedParentState* aEntry) { mDynamicallyCreated = aEntry->mDynamicallyCreated; mCacheKey = aEntry->mCacheKey; mLastTouched = aEntry->mLastTouched; + mNavigationState = aEntry->mNavigationState; } void dom::SHEntrySharedParentState::NotifyListenersDocumentViewerEvicted() { diff --git a/docshell/shistory/nsSHEntryShared.h b/docshell/shistory/nsSHEntryShared.h index 5ff855ead4be..75c84ed08579 100644 --- a/docshell/shistory/nsSHEntryShared.h +++ b/docshell/shistory/nsSHEntryShared.h @@ -14,6 +14,7 @@ #include "nsIWeakReferenceUtils.h" #include "nsRect.h" #include "nsString.h" +#include "nsStructuredCloneContainer.h" #include "nsStubMutationObserver.h" #include "mozilla/Attributes.h" @@ -63,7 +64,8 @@ struct SHEntrySharedState { mPrincipalToInherit(aPrincipalToInherit), mPartitionedPrincipalToInherit(aPartitionedPrincipalToInherit), mCsp(aCsp), - mContentType(aContentType) {} + mContentType(aContentType), + mNavigationState(MakeRefPtr()) {} // These members aren't copied by SHEntrySharedParentState::CopyFrom() because // they're specific to a particular content viewer. @@ -83,6 +85,8 @@ struct SHEntrySharedState { bool mIsFrameNavigation = false; bool mSaveLayoutState = true; + RefPtr mNavigationState; + protected: static uint64_t GenerateId(); }; diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp index ffa249d487e5..659d3b3eaf5b 100644 --- a/docshell/shistory/nsSHistory.cpp +++ b/docshell/shistory/nsSHistory.cpp @@ -99,6 +99,7 @@ LazyLogModule gSHistoryLog("nsSHistory"); #define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format) extern mozilla::LazyLogModule gPageCacheLog; +extern mozilla::LazyLogModule gNavigationLog; extern mozilla::LazyLogModule gSHIPBFCacheLog; // This macro makes it easier to print a log message which includes a URI's @@ -616,6 +617,63 @@ void nsSHistory::WalkContiguousEntries( } } +// static +void nsSHistory::WalkContiguousEntriesInOrder( + nsISHEntry* aEntry, const std::function& aCallback) { + MOZ_ASSERT(aEntry); + + nsCOMPtr shistory = aEntry->GetShistory(); + if (!shistory) { + return; + } + + int32_t index = shistory->GetIndexOfEntry(aEntry); + int32_t count = shistory->GetCount(); + + nsCOMPtr targetURI = aEntry->GetURI(); + + // Walk backward to find the entries that have the same origin as the + // input entry. + int32_t lowerBound = index; + for (int32_t i = index - 1; i >= 0; i--) { + RefPtr entry; + shistory->GetEntryAtIndex(i, getter_AddRefs(entry)); + if (!entry) { + continue; + } + nsCOMPtr uri = entry->GetURI(); + if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI( + targetURI, uri, false, false))) { + break; + } + lowerBound = i; + } + for (int32_t i = lowerBound; i < index; i++) { + RefPtr entry; + shistory->GetEntryAtIndex(i, getter_AddRefs(entry)); + MOZ_ASSERT(entry); + aCallback(entry); + } + + // Then, call the callback on the input entry. + aCallback(aEntry); + + // Then, Walk forward. + for (int32_t i = index + 1; i < count; i++) { + RefPtr entry; + shistory->GetEntryAtIndex(i, getter_AddRefs(entry)); + if (!entry) { + continue; + } + nsCOMPtr uri = entry->GetURI(); + if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI( + targetURI, uri, false, false))) { + break; + } + aCallback(entry); + } +} + NS_IMETHODIMP nsSHistory::AddChildSHEntryHelper(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry, BrowsingContext* aRootBC, diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h index ec6b27667697..fac7fb0ad48d 100644 --- a/docshell/shistory/nsSHistory.h +++ b/docshell/shistory/nsSHistory.h @@ -135,6 +135,9 @@ class nsSHistory : public mozilla::LinkedListElement, // works for the root entries. It will do nothing for non-root entries. static void WalkContiguousEntries( nsISHEntry* aEntry, const std::function& aCallback); + // Same as above, but calls aCallback on the entries in their history order. + static void WalkContiguousEntriesInOrder( + nsISHEntry* aEntry, const std::function& aCallback); nsTArray>& Entries() { return mEntries; } diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index accde07b809c..36aa9282f3e3 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -175,6 +175,7 @@ #include "mozilla/dom/FeaturePolicyUtils.h" #include "mozilla/dom/FontFaceSet.h" #include "mozilla/dom/FragmentDirective.h" +#include "mozilla/dom/NavigationBinding.h" #include "mozilla/dom/fragmentdirectives_ffi_generated.h" #include "mozilla/dom/FromParser.h" #include "mozilla/dom/HighlightRegistry.h" @@ -10148,9 +10149,9 @@ Document* Document::Open(const Optional& /* unused */, return nullptr; } nsCOMPtr stateContainer(mStateObjectContainer); - rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns, - /* aReplace = */ true, currentURI, - equalURIs); + rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, + NavigationHistoryBehavior::Replace, + currentURI, equalURIs); if (NS_WARN_IF(NS_FAILED(rv))) { aError.Throw(rv); return nullptr; diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp index 708a64a42557..967b3be18d90 100644 --- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -1170,6 +1170,8 @@ void nsGlobalWindowInner::FreeInnerObjects() { mHistory = nullptr; + mNavigation = nullptr; + if (mNavigator) { mNavigator->OnNavigation(); mNavigator->Invalidate(); @@ -1390,6 +1392,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner) NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindowInner, tmp->mRefCnt.get()) } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigation) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance) @@ -1496,6 +1500,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner) JS::SetRealmNonLive(js::GetNonCCWObjectRealm(wrapper)); } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigation) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance) @@ -2412,6 +2418,14 @@ WindowProxyHolder nsGlobalWindowInner::Window() { return WindowProxyHolder(GetBrowsingContext()); } +Navigation* nsPIDOMWindowInner::Navigation() { + if (!mNavigation && Navigation::IsAPIEnabled()) { + mNavigation = new mozilla::dom::Navigation(this); + } + + return mNavigation; +} + Navigator* nsPIDOMWindowInner::Navigator() { if (!mNavigator) { mNavigator = new mozilla::dom::Navigator(this); @@ -2445,14 +2459,6 @@ nsHistory* nsGlobalWindowInner::GetHistory(ErrorResult& aError) { return mHistory; } -Navigation* nsGlobalWindowInner::Navigation() { - if (!mNavigation && Navigation::IsAPIEnabled(nullptr, nullptr)) { - mNavigation = new mozilla::dom::Navigation(); - } - - return mNavigation; -} - CustomElementRegistry* nsGlobalWindowInner::CustomElements() { if (!mCustomElements) { mCustomElements = new CustomElementRegistry(this); @@ -6663,6 +6669,10 @@ void nsGlobalWindowInner::AddSizeOfIncludingThis( } } + if (mNavigation) { + aWindowSizes.mDOMSizes.mDOMOtherSize += + aWindowSizes.mState.mMallocSizeOf(mNavigation.get()); + } if (mNavigator) { aWindowSizes.mDOMSizes.mDOMOtherSize += mNavigator->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf); diff --git a/dom/base/nsGlobalWindowInner.h b/dom/base/nsGlobalWindowInner.h index 416f295f2644..936e14cabdd6 100644 --- a/dom/base/nsGlobalWindowInner.h +++ b/dom/base/nsGlobalWindowInner.h @@ -120,7 +120,6 @@ class IdleRequestCallback; class InstallTriggerImpl; class IntlUtils; class MediaQueryList; -class Navigation; class OwningExternalOrWindowProxy; class Promise; class PostMessageEvent; @@ -618,7 +617,6 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget, void SetName(const nsAString& aName, mozilla::ErrorResult& aError); mozilla::dom::Location* Location() override; nsHistory* GetHistory(mozilla::ErrorResult& aError); - mozilla::dom::Navigation* Navigation(); mozilla::dom::CustomElementRegistry* CustomElements() override; mozilla::dom::CustomElementRegistry* GetExistingCustomElements(); mozilla::dom::BarProp* GetLocationbar(mozilla::ErrorResult& aError); @@ -1388,7 +1386,6 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget, RefPtr mListenerManager; RefPtr mLocation; RefPtr mHistory; - RefPtr mNavigation; RefPtr mCustomElements; nsTObserverArray> mSharedWorkers; diff --git a/dom/base/nsGlobalWindowOuter.h b/dom/base/nsGlobalWindowOuter.h index 0dca616cae4f..0453a18ec10c 100644 --- a/dom/base/nsGlobalWindowOuter.h +++ b/dom/base/nsGlobalWindowOuter.h @@ -308,6 +308,9 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget, void DetachFromDocShell(bool aIsBeingDiscarded); + // aState is only non-null if we are restoring from the bfcache. + // aForceReuseInnerWindow is only true if we are being triggered via XSLT. + // aActor is only non-null if the new document is about:blank. virtual nsresult SetNewDocument( Document* aDocument, nsISupports* aState, bool aForceReuseInnerWindow, mozilla::dom::WindowGlobalChild* aActor = nullptr) override; diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index a75741697eec..17c8cd006e57 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -58,6 +58,7 @@ class Element; class Location; class MediaDevices; class MediaKeys; +class Navigation; class Navigator; class Performance; class Selection; @@ -590,6 +591,7 @@ class nsPIDOMWindowInner : public mozIDOMWindow { uint32_t GetMarkedCCGeneration() { return mMarkedCCGeneration; } + mozilla::dom::Navigation* Navigation(); mozilla::dom::Navigator* Navigator(); mozilla::dom::MediaDevices* GetExtantMediaDevices() const; virtual mozilla::dom::Location* Location() = 0; @@ -661,6 +663,8 @@ class nsPIDOMWindowInner : public mozIDOMWindow { RefPtr mPerformance; mozilla::UniquePtr mTimeoutManager; + RefPtr mNavigation; + RefPtr mNavigator; // These variables are only used on inner windows. diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 898dee3b6319..10fe608fd73c 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -7654,6 +7654,18 @@ ContentParent::RecvGetLoadingSessionHistoryInfoFromParent( return IPC_OK(); } +mozilla::ipc::IPCResult ContentParent::RecvGetContiguousSessionHistoryInfos( + const MaybeDiscarded& aContext, SessionHistoryInfo&& aInfo, + GetContiguousSessionHistoryInfosResolver&& aResolver) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + aResolver(aContext.get_canonical()->GetContiguousSessionHistoryInfos(aInfo)); + + return IPC_OK(); +} + mozilla::ipc::IPCResult ContentParent::RecvRemoveFromBFCache( const MaybeDiscarded& aContext) { if (aContext.IsNullOrDiscarded()) { diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 50c8b7310ac2..ff09c4432ecd 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -1335,6 +1335,11 @@ class ContentParent final : public PContentParent, const MaybeDiscarded& aContext, GetLoadingSessionHistoryInfoFromParentResolver&& aResolver); + mozilla::ipc::IPCResult RecvGetContiguousSessionHistoryInfos( + const MaybeDiscarded& aContext, + SessionHistoryInfo&& aInfo, + GetContiguousSessionHistoryInfosResolver&& aResolver); + mozilla::ipc::IPCResult RecvRemoveFromBFCache( const MaybeDiscarded& aContext); diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 74b227111975..21e928979743 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -1061,6 +1061,10 @@ parent: async GetLoadingSessionHistoryInfoFromParent(MaybeDiscardedBrowsingContext aContext) returns (LoadingSessionHistoryInfo? aLoadingInfo); + async GetContiguousSessionHistoryInfos(MaybeDiscardedBrowsingContext aContext, + SessionHistoryInfo aInfo) + returns (SessionHistoryInfo[] infos); + async RemoveFromBFCache(MaybeDiscardedBrowsingContext aContext); async InitBackground(Endpoint aEndpoint); diff --git a/dom/navigation/Navigation.cpp b/dom/navigation/Navigation.cpp index c1f13114bc10..4db24c4f4fa3 100644 --- a/dom/navigation/Navigation.cpp +++ b/dom/navigation/Navigation.cpp @@ -5,20 +5,39 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/Navigation.h" -#include "mozilla/dom/NavigationBinding.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/Logging.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/FeaturePolicy.h" +#include "mozilla/dom/NavigationCurrentEntryChangeEvent.h" +#include "mozilla/dom/NavigationHistoryEntry.h" +#include "mozilla/dom/SessionHistoryEntry.h" #include "mozilla/StaticPrefs_dom.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/NavigationCurrentEntryChangeEvent.h" +#include "mozilla/dom/NavigationHistoryEntry.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "nsContentUtils.h" #include "nsIXULRuntime.h" +#include "nsNetUtil.h" + +mozilla::LazyLogModule gNavigationLog("Navigation"); namespace mozilla::dom { -NS_IMPL_CYCLE_COLLECTION_INHERITED(Navigation, DOMEventTargetHelper); +NS_IMPL_CYCLE_COLLECTION_INHERITED(Navigation, DOMEventTargetHelper, mEntries); NS_IMPL_ADDREF_INHERITED(Navigation, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(Navigation, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Navigation) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) +Navigation::Navigation(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) { + MOZ_ASSERT(aWindow); +} + JSObject* Navigation::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return Navigation_Binding::Wrap(aCx, this, aGivenProto); @@ -30,4 +49,228 @@ bool Navigation::IsAPIEnabled(JSContext* /* unused */, JSObject* /* unused */) { StaticPrefs::dom_navigation_webidl_enabled_DoNotUseDirectly(); } +void Navigation::Entries( + nsTArray>& aResult) const { + aResult = mEntries.Clone(); +} + +already_AddRefed Navigation::GetCurrentEntry() const { + if (HasEntriesAndEventsDisabled()) { + return nullptr; + } + + if (!mCurrentEntryIndex) { + return nullptr; + } + + MOZ_LOG(gNavigationLog, LogLevel::Debug, + ("Current Entry: %d; Amount of Entries: %d", int(*mCurrentEntryIndex), + int(mEntries.Length()))); + MOZ_ASSERT(*mCurrentEntryIndex < mEntries.Length()); + + RefPtr entry{mEntries[*mCurrentEntryIndex]}; + return entry.forget(); +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-updatecurrententry +void Navigation::UpdateCurrentEntry( + JSContext* aCx, const NavigationUpdateCurrentEntryOptions& aOptions, + ErrorResult& aRv) { + RefPtr currentEntry(GetCurrentEntry()); + if (!currentEntry) { + aRv.ThrowInvalidStateError( + "Can't call updateCurrentEntry without a valid entry."); + return; + } + + JS::Rooted state(aCx, aOptions.mState); + auto serializedState = MakeRefPtr(); + nsresult rv = serializedState->InitFromJSVal(state, aCx); + if (NS_FAILED(rv)) { + aRv.ThrowDataCloneError( + "Failed to serialize value for updateCurrentEntry."); + return; + } + + currentEntry->SetState(serializedState); + + NavigationCurrentEntryChangeEventInit init; + init.mFrom = currentEntry; + // Leaving the navigation type unspecified means it will be initialized to + // null. + RefPtr event = NavigationCurrentEntryChangeEvent::Constructor( + this, u"currententrychange"_ns, init); + DispatchEvent(*event); +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#has-entries-and-events-disabled +bool Navigation::HasEntriesAndEventsDisabled() const { + Document* doc = mWindow->GetDoc(); + return !doc->IsCurrentActiveDocument() || + (NS_IsAboutBlank(doc->GetDocumentURI()) && doc->IsInitialDocument()) || + doc->GetPrincipal()->GetIsNullPrincipal(); +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#initialize-the-navigation-api-entries-for-a-new-document +void Navigation::InitializeHistoryEntries( + mozilla::Span aNewSHInfos, + const SessionHistoryInfo* aInitialSHInfo) { + mEntries.Clear(); + mCurrentEntryIndex.reset(); + if (HasEntriesAndEventsDisabled()) { + return; + } + + for (auto i = 0ul; i < aNewSHInfos.Length(); i++) { + mEntries.AppendElement( + MakeRefPtr(mWindow, &aNewSHInfos[i], i)); + if (aNewSHInfos[i].NavigationKey() == aInitialSHInfo->NavigationKey()) { + mCurrentEntryIndex = Some(i); + } + } + + LogHistory(); + + nsID key = aInitialSHInfo->NavigationKey(); + nsID id = aInitialSHInfo->NavigationId(); + MOZ_LOG( + gNavigationLog, LogLevel::Debug, + ("aInitialSHInfo: %s %s\n", key.ToString().get(), id.ToString().get())); +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation +void Navigation::UpdateEntriesForSameDocumentNavigation( + SessionHistoryInfo* aDestinationSHE, NavigationType aNavigationType) { + // Step 1. + if (HasEntriesAndEventsDisabled()) { + return; + } + + MOZ_LOG(gNavigationLog, LogLevel::Debug, + ("Updating entries for same-document navigation")); + + // Steps 2-7. + RefPtr oldCurrentEntry = GetCurrentEntry(); + nsTArray> disposedEntries; + switch (aNavigationType) { + case NavigationType::Traverse: + MOZ_LOG(gNavigationLog, LogLevel::Debug, ("Traverse navigation")); + mCurrentEntryIndex.reset(); + for (auto i = 0ul; i < mEntries.Length(); i++) { + if (mEntries[i]->IsSameEntry(aDestinationSHE)) { + mCurrentEntryIndex = Some(i); + break; + } + } + MOZ_ASSERT(mCurrentEntryIndex); + break; + + case NavigationType::Push: + MOZ_LOG(gNavigationLog, LogLevel::Debug, ("Push navigation")); + mCurrentEntryIndex = + Some(mCurrentEntryIndex ? *mCurrentEntryIndex + 1 : 0); + while (*mCurrentEntryIndex < mEntries.Length()) { + disposedEntries.AppendElement(mEntries.PopLastElement()); + } + mEntries.AppendElement(MakeRefPtr( + mWindow, aDestinationSHE, *mCurrentEntryIndex)); + break; + + case NavigationType::Replace: + MOZ_LOG(gNavigationLog, LogLevel::Debug, ("Replace navigation")); + disposedEntries.AppendElement(oldCurrentEntry); + aDestinationSHE->NavigationKey() = oldCurrentEntry->Key(); + mEntries[*mCurrentEntryIndex] = MakeRefPtr( + mWindow, aDestinationSHE, *mCurrentEntryIndex); + break; + + case NavigationType::Reload: + break; + } + + // TODO: Step 8. + + // Steps 9-12. + { + nsAutoMicroTask mt; + AutoEntryScript aes(mWindow->AsGlobal(), + "UpdateEntriesForSameDocumentNavigation"); + + ScheduleEventsFromNavigation(aNavigationType, oldCurrentEntry, + std::move(disposedEntries)); + } +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#update-the-navigation-api-entries-for-reactivation +void Navigation::UpdateForReactivation(SessionHistoryInfo* aReactivatedEntry) { + // NAV-TODO +} + +void Navigation::ScheduleEventsFromNavigation( + NavigationType aType, const RefPtr& aPreviousEntry, + nsTArray>&& aDisposedEntries) { + nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( + "mozilla::dom::Navigation::ScheduleEventsFromNavigation", + [self = RefPtr(this), previousEntry = RefPtr(aPreviousEntry), + disposedEntries = std::move(aDisposedEntries), aType]() { + if (previousEntry) { + NavigationCurrentEntryChangeEventInit init; + init.mFrom = previousEntry; + init.mNavigationType.SetValue(aType); + RefPtr event = NavigationCurrentEntryChangeEvent::Constructor( + self, u"currententrychange"_ns, init); + self->DispatchEvent(*event); + } + + for (const auto& entry : disposedEntries) { + RefPtr event = NS_NewDOMEvent(entry, nullptr, nullptr); + event->InitEvent(u"dispose"_ns, false, false); + event->SetTrusted(true); + event->SetTarget(entry); + entry->DispatchEvent(*event); + } + })); +} + +namespace { + +void LogEntry(NavigationHistoryEntry* aEntry, uint64_t aIndex, uint64_t aTotal, + bool aIsCurrent) { + if (!aEntry) { + MOZ_LOG(gNavigationLog, LogLevel::Debug, + (" +- %d NHEntry null\n", int(aIndex))); + return; + } + + nsString key, id; + aEntry->GetKey(key); + aEntry->GetId(id); + MOZ_LOG(gNavigationLog, LogLevel::Debug, + ("%s+- %d NHEntry %p %s %s\n", aIsCurrent ? ">" : " ", int(aIndex), + aEntry, NS_ConvertUTF16toUTF8(key).get(), + NS_ConvertUTF16toUTF8(id).get())); + + nsAutoString url; + aEntry->GetUrl(url); + MOZ_LOG(gNavigationLog, LogLevel::Debug, + (" URL = %s\n", NS_ConvertUTF16toUTF8(url).get())); +} + +} // namespace + +void Navigation::LogHistory() const { + if (!MOZ_LOG_TEST(gNavigationLog, LogLevel::Debug)) { + return; + } + + MOZ_LOG(gNavigationLog, LogLevel::Debug, + ("Navigation %p (current entry index: %d)\n", this, + mCurrentEntryIndex ? int(*mCurrentEntryIndex) : -1)); + auto length = mEntries.Length(); + for (uint64_t i = 0; i < length; i++) { + LogEntry(mEntries[i], i, length, + mCurrentEntryIndex && i == *mCurrentEntryIndex); + } +} + } // namespace mozilla::dom diff --git a/dom/navigation/Navigation.h b/dom/navigation/Navigation.h index 0120564529a1..3d23e48bcf20 100644 --- a/dom/navigation/Navigation.h +++ b/dom/navigation/Navigation.h @@ -8,6 +8,7 @@ #define mozilla_dom_Navigation_h___ #include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/dom/NavigationBinding.h" namespace mozilla::dom { @@ -20,21 +21,33 @@ struct NavigationUpdateCurrentEntryOptions; struct NavigationReloadOptions; struct NavigationResult; +class SessionHistoryInfo; + class Navigation final : public DOMEventTargetHelper { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Navigation, DOMEventTargetHelper) - void Entries(nsTArray>& aResult) {} - already_AddRefed GetCurrentEntry() { return {}; } + explicit Navigation(nsPIDOMWindowInner* aWindow); + + // Navigation.webidl + void Entries(nsTArray>& aResult) const; + already_AddRefed GetCurrentEntry() const; + MOZ_CAN_RUN_SCRIPT void UpdateCurrentEntry(JSContext* aCx, const NavigationUpdateCurrentEntryOptions& aOptions, - ErrorResult& aRv) {} + ErrorResult& aRv); already_AddRefed GetTransition() { return {}; } already_AddRefed GetActivation() { return {}; } - bool CanGoBack() { return {}; } - bool CanGoForward() { return {}; } + bool CanGoBack() { + return !HasEntriesAndEventsDisabled() && mCurrentEntryIndex && + *mCurrentEntryIndex != 0; + } + bool CanGoForward() { + return !HasEntriesAndEventsDisabled() && mCurrentEntryIndex && + *mCurrentEntryIndex != mEntries.Length() - 1; + } void Navigate(JSContext* aCx, const nsAString& aUrl, const NavigationNavigateOptions& aOptions, @@ -55,15 +68,45 @@ class Navigation final : public DOMEventTargetHelper { IMPL_EVENT_HANDLER(navigateerror); IMPL_EVENT_HANDLER(currententrychange); + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#initialize-the-navigation-api-entries-for-a-new-document + void InitializeHistoryEntries( + mozilla::Span aNewSHInfos, + const SessionHistoryInfo* aInitialSHInfo); + + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#update-the-navigation-api-entries-for-reactivation + MOZ_CAN_RUN_SCRIPT + void UpdateForReactivation(SessionHistoryInfo* aReactivatedEntry); + + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation + void UpdateEntriesForSameDocumentNavigation( + SessionHistoryInfo* aDestinationSHE, NavigationType aNavigationType); + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; // The Navigation API is only enabled if both SessionHistoryInParent and // the dom.navigation.webidl.enabled pref are set. - static bool IsAPIEnabled(JSContext* /* unused */, JSObject* /* unused */); + static bool IsAPIEnabled(JSContext* /* unused */ = nullptr, + JSObject* /* unused */ = nullptr); private: ~Navigation() = default; + + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#has-entries-and-events-disabled + bool HasEntriesAndEventsDisabled() const; + + void ScheduleEventsFromNavigation( + NavigationType aType, + const RefPtr& aPreviousEntry, + nsTArray>&& aDisposedEntries); + + void LogHistory() const; + + nsCOMPtr mWindow; + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-entry-list + nsTArray> mEntries; + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-current-entry + Maybe mCurrentEntryIndex; }; } // namespace mozilla::dom diff --git a/dom/navigation/NavigationHistoryEntry.cpp b/dom/navigation/NavigationHistoryEntry.cpp index 2c6fbeb0367d..004a4be28205 100644 --- a/dom/navigation/NavigationHistoryEntry.cpp +++ b/dom/navigation/NavigationHistoryEntry.cpp @@ -7,19 +7,131 @@ #include "mozilla/dom/NavigationHistoryEntry.h" #include "mozilla/dom/NavigationHistoryEntryBinding.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "nsDocShell.h" + +extern mozilla::LazyLogModule gNavigationLog; + namespace mozilla::dom { -NS_IMPL_CYCLE_COLLECTION_INHERITED(NavigationHistoryEntry, - DOMEventTargetHelper); +NS_IMPL_CYCLE_COLLECTION_INHERITED(NavigationHistoryEntry, DOMEventTargetHelper, + mWindow); NS_IMPL_ADDREF_INHERITED(NavigationHistoryEntry, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(NavigationHistoryEntry, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NavigationHistoryEntry) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) +NavigationHistoryEntry::NavigationHistoryEntry( + nsPIDOMWindowInner* aWindow, const SessionHistoryInfo* aSHInfo, + int64_t aIndex) + : mWindow(aWindow), + mSHInfo(MakeUnique(*aSHInfo)), + mIndex(aIndex) {} + +NavigationHistoryEntry::~NavigationHistoryEntry() = default; + +void NavigationHistoryEntry::GetUrl(nsAString& aResult) const { + if (!GetCurrentDocument()->IsCurrentActiveDocument()) { + return; + } + + if (!SameDocument()) { + auto referrerPolicy = GetCurrentDocument()->ReferrerPolicy(); + if (referrerPolicy == ReferrerPolicy::No_referrer || + referrerPolicy == ReferrerPolicy::Origin) { + return; + } + } + + MOZ_ASSERT(mSHInfo); + nsCOMPtr uri = mSHInfo->GetURI(); + MOZ_ASSERT(uri); + nsCString uriSpec; + uri->GetSpec(uriSpec); + CopyUTF8toUTF16(uriSpec, aResult); +} + +void NavigationHistoryEntry::GetKey(nsAString& aResult) const { + if (!GetCurrentDocument()->IsCurrentActiveDocument()) { + return; + } + + nsIDToCString keyString(mSHInfo->NavigationKey()); + // Omit the curly braces and NUL. + CopyUTF8toUTF16(Substring(keyString.get() + 1, NSID_LENGTH - 3), aResult); +} + +void NavigationHistoryEntry::GetId(nsAString& aResult) const { + if (!GetCurrentDocument()->IsCurrentActiveDocument()) { + return; + } + + nsIDToCString idString(mSHInfo->NavigationId()); + // Omit the curly braces and NUL. + CopyUTF8toUTF16(Substring(idString.get() + 1, NSID_LENGTH - 3), aResult); +} + +int64_t NavigationHistoryEntry::Index() const { + MOZ_ASSERT(mSHInfo); + if (!GetCurrentDocument()->IsCurrentActiveDocument()) { + return -1; + } + return mIndex; +} + +bool NavigationHistoryEntry::SameDocument() const { + MOZ_ASSERT(mSHInfo); + auto* docShell = static_cast(mWindow->GetDocShell()); + return docShell->IsSameDocumentAsActiveEntry(*mSHInfo); +} + +void NavigationHistoryEntry::GetState(JSContext* aCx, + JS::MutableHandle aResult, + ErrorResult& aRv) const { + if (!mSHInfo) { + return; + } + RefPtr state = mSHInfo->GetNavigationState(); + if (!state) { + aResult.setUndefined(); + return; + } + + nsresult rv = state->DeserializeToJsval(aCx, aResult); + if (NS_FAILED(rv)) { + // TODO change this to specific exception + aRv.Throw(rv); + } +} + +void NavigationHistoryEntry::SetState(nsStructuredCloneContainer* aState) { + RefPtr state = mSHInfo->GetNavigationState(); + state->Copy(*aState); +} + +bool NavigationHistoryEntry::IsSameEntry( + const SessionHistoryInfo* aSHInfo) const { + return mSHInfo->NavigationId() == aSHInfo->NavigationId(); +} + +bool NavigationHistoryEntry::SharesDocumentWith( + const SessionHistoryInfo& aSHInfo) const { + return mSHInfo->SharesDocumentWith(aSHInfo); +} + JSObject* NavigationHistoryEntry::WrapObject( JSContext* aCx, JS::Handle aGivenProto) { return NavigationHistoryEntry_Binding::Wrap(aCx, this, aGivenProto); } +Document* NavigationHistoryEntry::GetCurrentDocument() const { + return mWindow->GetDoc(); +} + +const nsID& NavigationHistoryEntry::Key() const { + return mSHInfo->NavigationKey(); +} + } // namespace mozilla::dom diff --git a/dom/navigation/NavigationHistoryEntry.h b/dom/navigation/NavigationHistoryEntry.h index a8529b52536c..8606d0e85ab5 100644 --- a/dom/navigation/NavigationHistoryEntry.h +++ b/dom/navigation/NavigationHistoryEntry.h @@ -9,30 +9,50 @@ #include "mozilla/DOMEventTargetHelper.h" +class nsStructuredCloneContainer; + namespace mozilla::dom { +class SessionHistoryInfo; + class NavigationHistoryEntry final : public DOMEventTargetHelper { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NavigationHistoryEntry, DOMEventTargetHelper) - void GetUrl(nsAString& aResult) const {} - void GetKey(nsAString& aResult) const {} - void GetId(nsAString& aResult) const {} - int64_t Index() const { return {}; } - bool SameDocument() const { return {}; } + NavigationHistoryEntry(nsPIDOMWindowInner* aWindow, + const SessionHistoryInfo* aSHInfo, int64_t aIndex); + + void GetUrl(nsAString& aResult) const; + void GetKey(nsAString& aResult) const; + void GetId(nsAString& aResult) const; + int64_t Index() const; + bool SameDocument() const; void GetState(JSContext* aCx, JS::MutableHandle aResult, - ErrorResult& aRv) {} + ErrorResult& aRv) const; + void SetState(nsStructuredCloneContainer* aState); IMPL_EVENT_HANDLER(dispose); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + bool IsSameEntry(const SessionHistoryInfo* aSHInfo) const; + + bool SharesDocumentWith(const SessionHistoryInfo& aSHInfo) const; + + const nsID& Key() const; + private: - ~NavigationHistoryEntry() = default; + ~NavigationHistoryEntry(); + + Document* GetCurrentDocument() const; + + nsCOMPtr mWindow; + UniquePtr mSHInfo; + int64_t mIndex; }; } // namespace mozilla::dom diff --git a/testing/web-platform/meta/navigation-api/navigation-history-entry/entries-array-equality.html.ini b/testing/web-platform/meta/navigation-api/navigation-history-entry/entries-array-equality.html.ini index 32a032007618..b46b6374cdb6 100644 --- a/testing/web-platform/meta/navigation-api/navigation-history-entry/entries-array-equality.html.ini +++ b/testing/web-platform/meta/navigation-api/navigation-history-entry/entries-array-equality.html.ini @@ -1,8 +1,4 @@ [entries-array-equality.html] + prefs: [dom.navigation.webidl.enabled:true] expected: if (os == "android") and fission: [OK, TIMEOUT] - [navigation.entries() should not return an identical object on repeated invocations] - expected: FAIL - - [navigation.entries() should not return an identical object on repeated invocations] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigation-history-entry/entries-when-inactive.html.ini b/testing/web-platform/meta/navigation-api/navigation-history-entry/entries-when-inactive.html.ini index 7acdc55b84df..aafc09ba772c 100644 --- a/testing/web-platform/meta/navigation-api/navigation-history-entry/entries-when-inactive.html.ini +++ b/testing/web-platform/meta/navigation-api/navigation-history-entry/entries-when-inactive.html.ini @@ -1,8 +1,4 @@ [entries-when-inactive.html] + prefs: [dom.navigation.webidl.enabled:true] expected: if (os == "android") and fission: [OK, TIMEOUT] - [A non-active entry in navigation.entries() should not be modified when a different entry is modified] - expected: FAIL - - [A non-active entry in navigation.entries() should not be modified when a different entry is modified] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigation-history-entry/key-id-back-same-document.html.ini b/testing/web-platform/meta/navigation-api/navigation-history-entry/key-id-back-same-document.html.ini index a1b673e745e4..f7edc9044e99 100644 --- a/testing/web-platform/meta/navigation-api/navigation-history-entry/key-id-back-same-document.html.ini +++ b/testing/web-platform/meta/navigation-api/navigation-history-entry/key-id-back-same-document.html.ini @@ -1,3 +1,2 @@ [key-id-back-same-document.html] - [NavigationHistoryEntry's key and id on same-document back navigation] - expected: FAIL + prefs: [dom.navigation.webidl.enabled:true] diff --git a/testing/web-platform/meta/navigation-api/navigation-history-entry/opaque-origin-data-url.html.ini b/testing/web-platform/meta/navigation-api/navigation-history-entry/opaque-origin-data-url.html.ini index 7fdee45cfa15..f7a6c8f957a3 100644 --- a/testing/web-platform/meta/navigation-api/navigation-history-entry/opaque-origin-data-url.html.ini +++ b/testing/web-platform/meta/navigation-api/navigation-history-entry/opaque-origin-data-url.html.ini @@ -1,4 +1,2 @@ [opaque-origin-data-url.html] - expected: TIMEOUT - [entries() and currentEntry after navigation to a data: URL (which has an opaque origin)] - expected: TIMEOUT + prefs: [dom.navigation.webidl.enabled:true] diff --git a/testing/web-platform/meta/navigation-api/navigation-history-entry/opaque-origin.html.ini b/testing/web-platform/meta/navigation-api/navigation-history-entry/opaque-origin.html.ini index 09028e1f914c..2238f6b96f6b 100644 --- a/testing/web-platform/meta/navigation-api/navigation-history-entry/opaque-origin.html.ini +++ b/testing/web-platform/meta/navigation-api/navigation-history-entry/opaque-origin.html.ini @@ -1,5 +1,4 @@ [opaque-origin.html] + prefs: [dom.navigation.webidl.enabled:true] expected: if (os == "android") and fission: [OK, TIMEOUT] - [navigation.currentEntry/entries()/canGoBack/canGoForward in an opaque origin iframe] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigation-history-entry/sameDocument-after-fragment-navigate.html.ini b/testing/web-platform/meta/navigation-api/navigation-history-entry/sameDocument-after-fragment-navigate.html.ini index 1d449a8186e6..8b2c33d2fd2c 100644 --- a/testing/web-platform/meta/navigation-api/navigation-history-entry/sameDocument-after-fragment-navigate.html.ini +++ b/testing/web-platform/meta/navigation-api/navigation-history-entry/sameDocument-after-fragment-navigate.html.ini @@ -1,5 +1,4 @@ [sameDocument-after-fragment-navigate.html] + prefs: [dom.navigation.webidl.enabled:true] expected: if (os == "android") and fission: [OK, TIMEOUT] - [entry.sameDocument after same-document navigations] - expected: FAIL