commit a50088adb2eded22a03c6c60227df8716e8770eb
parent 5a4a19e0909b3c15f73c09f422234fd293fdaa8e
Author: Adam Vandolder <avandolder@mozilla.com>
Date: Fri, 12 Dec 2025 23:46:48 +0000
Bug 2005793 - Check if the Navigation API Key needs to be copied for a replace load again after a redirect. r=dom-core,smaug
Differential Revision: https://phabricator.services.mozilla.com/D276305
Diffstat:
3 files changed, 62 insertions(+), 17 deletions(-)
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
@@ -634,23 +634,8 @@ CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad(
}
MOZ_DIAGNOSTIC_ASSERT(entry);
- // https://html.spec.whatwg.org/#finalize-a-cross-document-navigation
- // 9. If entryToReplace is null, then: ...
- // Otherwise: ...
- // 4. If historyEntry's document state's origin is same origin with
- // entryToReplace's document state's origin, then set
- // historyEntry's navigation API key to entryToReplace's
- // navigation API key.
- if (mActiveEntry &&
- aLoadState->GetNavigationType() == NavigationType::Replace) {
- nsCOMPtr<nsIURI> uri = mActiveEntry->GetURIOrInheritedForAboutBlank();
- nsCOMPtr<nsIURI> targetURI = entry->GetURIOrInheritedForAboutBlank();
- bool sameOrigin =
- NS_SUCCEEDED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
- targetURI, uri, false, false));
- if (sameOrigin) {
- entry->SetNavigationKey(mActiveEntry->Info().NavigationKey());
- }
+ if (aLoadState->GetNavigationType() == NavigationType::Replace) {
+ MaybeReuseNavigationKeyFromActiveEntry(entry);
}
UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
@@ -772,6 +757,11 @@ CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad(
loadingEntry->SetDocshellID(GetHistoryID());
loadingEntry->SetIsDynamicallyAdded(CreatedDynamically());
+ if (aInfo->mTriggeringNavigationType &&
+ *aInfo->mTriggeringNavigationType == NavigationType::Replace) {
+ MaybeReuseNavigationKeyFromActiveEntry(loadingEntry);
+ }
+
auto result = MakeUnique<LoadingSessionHistoryInfo>(loadingEntry, aInfo);
MOZ_LOG_FMT(
gNavigationAPILog, LogLevel::Debug,
@@ -825,6 +815,33 @@ void CanonicalBrowsingContext::GetContiguousEntriesForLoad(
}
}
+void CanonicalBrowsingContext::MaybeReuseNavigationKeyFromActiveEntry(
+ SessionHistoryEntry* aEntry) {
+ MOZ_ASSERT(aEntry);
+
+ // https://html.spec.whatwg.org/#finalize-a-cross-document-navigation
+ // 9. If entryToReplace is null, then: ...
+ // Otherwise: ...
+ // 4. If historyEntry's document state's origin is same origin with
+ // entryToReplace's document state's origin, then set
+ // historyEntry's navigation API key to entryToReplace's
+ // navigation API key.
+ if (!mActiveEntry) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = mActiveEntry->GetURIOrInheritedForAboutBlank();
+ nsCOMPtr<nsIURI> targetURI = aEntry->GetURIOrInheritedForAboutBlank();
+ bool sameOrigin =
+ NS_SUCCEEDED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
+ targetURI, uri, false, false));
+ if (!sameOrigin) {
+ return;
+ }
+
+ aEntry->SetNavigationKey(mActiveEntry->Info().NavigationKey());
+}
+
using PrintPromise = CanonicalBrowsingContext::PrintPromise;
#ifdef NS_PRINTING
// Clients must call StaticCloneForPrintingCreated or
diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h
@@ -587,6 +587,8 @@ class CanonicalBrowsingContext final : public BrowsingContext {
void GetContiguousEntriesForLoad(LoadingSessionHistoryInfo& aLoadingInfo,
const RefPtr<SessionHistoryEntry>& aEntry);
+ void MaybeReuseNavigationKeyFromActiveEntry(SessionHistoryEntry* aEntry);
+
EntryList* GetActiveEntries();
// XXX(farre): Store a ContentParent pointer here rather than mProcessId?
diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-server-redirect-replace.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-server-redirect-replace.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="i" src="/common/blank.html"></iframe>
+<script>
+promise_test(async t => {
+ // Wait for after the load event so that the navigation doesn't get converted
+ // into a replace navigation.
+ await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
+
+ let url = i.contentWindow.navigation.currentEntry.url;
+ let key = i.contentWindow.navigation.currentEntry.key;
+
+ // Do a redirected cross-document replace navigation in the iframe.
+ i.contentWindow.navigation.navigate(
+ "/common/redirect.py?location=/common/blank.html?redirected",
+ {history: "replace"});
+ await new Promise(resolve => i.onload = resolve);
+
+ assert_equals(i.contentWindow.navigation.entries().length, 1);
+ assert_equals(i.contentWindow.navigation.currentEntry.url, url + "?redirected");
+ assert_equals(i.contentWindow.navigation.currentEntry.key, key);
+}, "A navigation to a redirected page ends up on the page redirected to");
+</script>
+</body>