tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 9dd79a9a56ba1447c463a429e4e835a5c9cd345e
parent bfcb80eb8a3ef5117b9ad57246d2ec2cb23eb135
Author: Andreas Farre <farre@mozilla.com>
Date:   Thu,  2 Oct 2025 20:15:43 +0000

Bug 1991265 - Make push to replace load conversion follow spec. r=smaug

Make sure that navigations from Location and Window.open perform correct
conversion of history handling when passed "auto".

Differential Revision: https://phabricator.services.mozilla.com/D266519

Diffstat:
Mdocshell/base/BrowsingContext.cpp | 28++++++++--------------------
Mdocshell/base/BrowsingContext.h | 2+-
Mdocshell/base/nsDocShell.cpp | 47+++++++++++++++++++++++++++++++++++++++++++----
Mdocshell/base/nsDocShellLoadState.cpp | 51++++++++++++++++++++++++++++++++++++++++-----------
Mdocshell/base/nsDocShellLoadState.h | 26++++++++++++++++++++++----
Mdocshell/base/nsDocShellLoadTypes.h | 13+++++++++++++
Mdom/base/Document.cpp | 3+++
Mdom/base/Document.h | 12++++++++++++
Mdom/base/Location.cpp | 14+++++++-------
Mdom/base/LocationBase.cpp | 34++++++++++++++++++++++++++--------
Mdom/base/LocationBase.h | 6++++--
Mdom/base/nsGlobalWindowOuter.cpp | 2+-
Mdom/ipc/DOMTypes.ipdlh | 5+++++
Mdom/ipc/NavigationAPIIPCUtils.h | 5+++++
Mdom/navigation/Navigation.cpp | 2+-
Mdom/security/test/sec-fetch/file_no_cache.sjs | 2+-
Mdom/security/test/sec-fetch/test_iframe_history_manipulation.html | 2+-
Mtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-user-click.html.ini | 2--
Mtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html.ini | 8--------
Mtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html.ini | 8--------
Mtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-load.html.ini | 2--
Mtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-pageshow.html.ini | 2--
Mtesting/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self.html.ini | 2--
Dtesting/web-platform/meta/html/browsers/history/the-history-interface/traverse-during-beforeunload.html.ini | 4----
Dtesting/web-platform/meta/navigation-api/currententrychange-event/location-api.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin-sameorigindomain.sub.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/location-crossdocument-sameorigin.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin-sameorigindomain.sub.html.ini | 12------------
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/location-samedocument-sameorigin.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin-sameorigindomain.sub.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/open-crossdocument-sameorigin.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin-sameorigindomain.sub.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/cross-window/open-samedocument-sameorigin.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/navigate-iframe-location.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/navigate-navigation-navigate.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/navigate-event/navigate-window-open.html.ini | 5-----
Dtesting/web-platform/meta/navigation-api/navigation-history-entry/current-basic.html.ini | 3---
Mtesting/web-platform/meta/navigation-api/navigation-methods/sandboxing-back-parent.html.ini | 3++-
Dtesting/web-platform/meta/navigation-api/state/cross-document-getState-undefined.html.ini | 3---
Dtesting/web-platform/meta/navigation-api/state/cross-document-location-api.html.ini | 15---------------
Mtesting/web-platform/tests/css/cssom-view/scroll-behavior-smooth-navigation.html | 6+++++-
Mtesting/web-platform/tests/html/browsers/the-window-object/open-close/self-close.html | 2+-
Mtesting/web-platform/tests/navigation-api/navigate-event/navigate-navigation-navigate.html | 2+-
Mtoolkit/components/windowwatcher/nsWindowWatcher.cpp | 9++++++++-
Mtoolkit/components/windowwatcher/nsWindowWatcher.h | 3++-
47 files changed, 212 insertions(+), 169 deletions(-)

diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp @@ -2127,6 +2127,7 @@ nsresult BrowsingContext::LoadURI(nsDocShellLoadState* aLoadState, if (mDocShell) { nsCOMPtr<nsIDocShell> docShell = mDocShell; + return docShell->LoadURI(aLoadState, aSetNavigating); } @@ -2437,7 +2438,7 @@ BrowsingContext::CheckURLAndCreateLoadState(nsIURI* aURI, void BrowsingContext::Navigate(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv, NavigationHistoryBehavior aHistoryHandling, - bool aShouldNotForceReplaceInOnLoad) { + bool aNeedsCompletelyLoadedDocument) { MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug, "Navigate to {} as {}", *aURI, aHistoryHandling); CallerType callerType = aSubjectPrincipal.IsSystemPrincipal() @@ -2456,27 +2457,14 @@ void BrowsingContext::Navigate(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, return; } - loadState->SetShouldNotForceReplaceInOnLoad(aShouldNotForceReplaceInOnLoad); + loadState->SetNeedsCompletelyLoadedDocument(aNeedsCompletelyLoadedDocument); - // Step 12 - NavigationHistoryBehavior historyHandling = aHistoryHandling; - if (aHistoryHandling == NavigationHistoryBehavior::Auto) { - if (auto* document = GetExtantDocument()) { - bool equals = false; - aURI->Equals(document->GetDocumentURI(), &equals); - if (equals && document->GetPrincipal()) { - document->GetPrincipal()->Equals(&aSubjectPrincipal, &equals); - } - if (equals) { - historyHandling = NavigationHistoryBehavior::Replace; - } else { - historyHandling = NavigationHistoryBehavior::Push; - } - } - } + loadState->SetShouldNotForceReplaceInOnLoad( + /* aShouldNotForceReplaceInOnLoad */ true); + + loadState->SetHistoryBehavior(aHistoryHandling); - // Step 13 of #navigate are handled later in nsDocShell::InternalLoad(). - if (historyHandling == NavigationHistoryBehavior::Replace) { + if (aHistoryHandling == NavigationHistoryBehavior::Replace) { loadState->SetLoadType(LOAD_STOP_CONTENT_AND_REPLACE); } else { loadState->SetLoadType(LOAD_STOP_CONTENT); diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h @@ -459,7 +459,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { void Navigate(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv, NavigationHistoryBehavior aHistoryHandling = NavigationHistoryBehavior::Auto, - bool aShouldNotForceReplaceInOnLoad = false); + bool aNeedsCompletelyLoadedDocument = false); // Removes the root document for this BrowsingContext tree from the BFCache, // if it is cached, and returns true if it was. diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp @@ -72,6 +72,7 @@ #include "mozilla/dom/Navigation.h" #include "mozilla/dom/NavigationBinding.h" #include "mozilla/dom/NavigationHistoryEntry.h" +#include "mozilla/dom/NavigationUtils.h" #include "mozilla/dom/PerformanceNavigation.h" #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/PolicyContainer.h" @@ -8729,6 +8730,7 @@ struct SameDocumentNavigationState { bool mSameExceptHashes = false; bool mSecureUpgradeURI = false; bool mHistoryNavBetweenSameDoc = false; + bool mIdentical = false; }; bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState, @@ -8834,6 +8836,12 @@ bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState, } } + // Two URIs are identical if they're same except hashes, they both have + // hashes, and their hashes are the same. + aState.mIdentical = aState.mSameExceptHashes && + (aState.mNewURIHasRef == aState.mCurrentURIHasRef) && + aState.mCurrentHash.Equals(aState.mNewHash); + // A same document navigation happens when we navigate between two SHEntries // for the same document. We do a same document navigation under two // circumstances. Either @@ -9381,10 +9389,8 @@ nsresult nsDocShell::HandleSameDocumentNavigation( // 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); + NavigationUtils::NavigationTypeFromLoadType(mLoadType).valueOr( + NavigationType::Push)); } // Fire a hashchange event URIs differ, and only in their hashes. @@ -9465,6 +9471,34 @@ uint32_t nsDocShell::GetLoadTypeForFormSubmission( : LOAD_LINK; } +static void MaybeConvertToReplaceLoad(nsDocShellLoadState* aLoadState, + Document* aExtantDocument, + bool aIdenticalURI) { + if (!aExtantDocument) { + return; + } + + bool convertToReplaceLoad = aLoadState->NeedsCompletelyLoadedDocument() && + !aExtantDocument->IsCompletelyLoaded(); + if (const auto& historyBehavior = aLoadState->HistoryBehavior(); + !convertToReplaceLoad && historyBehavior && + *historyBehavior == NavigationHistoryBehavior::Auto) { + convertToReplaceLoad = aIdenticalURI; + if (convertToReplaceLoad && aExtantDocument->GetPrincipal()) { + aExtantDocument->GetPrincipal()->Equals(aLoadState->TriggeringPrincipal(), + &convertToReplaceLoad); + } + } + + if (convertToReplaceLoad) { + MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug, + "Convert to replace when navigating from {} to {}", + *aExtantDocument->GetDocumentURI(), *aLoadState->URI()); + aLoadState->SetLoadType(MaybeAddLoadFlags( + aLoadState->LoadType(), nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)); + } +} + // InternalLoad performs several of the steps from // https://html.spec.whatwg.org/#navigate. nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, @@ -9525,6 +9559,11 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, IsSameDocumentNavigation(aLoadState, sameDocumentNavigationState) && !aLoadState->GetPendingRedirectedChannel(); + if (mLoadType != LOAD_ERROR_PAGE) { + MaybeConvertToReplaceLoad(aLoadState, GetExtantDocument(), + sameDocumentNavigationState.mIdentical); + } + // Note: We do this check both here and in BrowsingContext:: // LoadURI/InternalLoad, since document-specific sandbox flags are only // available in the process triggering the load, and we don't want the target diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp @@ -71,6 +71,9 @@ nsDocShellLoadState::nsDocShellLoadState( mOriginalFrameSrc = aLoadState.OriginalFrameSrc(); mShouldCheckForRecursion = aLoadState.ShouldCheckForRecursion(); mIsFormSubmission = aLoadState.IsFormSubmission(); + mShouldNotForceReplaceInOnLoad = aLoadState.ShouldNotForceReplaceInOnLoad(); + mNeedsCompletelyLoadedDocument = aLoadState.NeedsCompletelyLoadedDocument(); + mHistoryBehavior = aLoadState.HistoryBehavior(); mLoadType = aLoadState.LoadType(); mTarget = aLoadState.Target(); mTargetBrowsingContext = aLoadState.TargetBrowsingContext(); @@ -172,7 +175,6 @@ nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther) mInheritPrincipal(aOther.mInheritPrincipal), mPrincipalIsExplicit(aOther.mPrincipalIsExplicit), mNotifiedBeforeUnloadListeners(aOther.mNotifiedBeforeUnloadListeners), - mShouldNotForceReplaceInOnLoad(aOther.mShouldNotForceReplaceInOnLoad), mPrincipalToInherit(aOther.mPrincipalToInherit), mPartitionedPrincipalToInherit(aOther.mPartitionedPrincipalToInherit), mForceAllowDataURI(aOther.mForceAllowDataURI), @@ -181,6 +183,9 @@ nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther) mOriginalFrameSrc(aOther.mOriginalFrameSrc), mShouldCheckForRecursion(aOther.mShouldCheckForRecursion), mIsFormSubmission(aOther.mIsFormSubmission), + mShouldNotForceReplaceInOnLoad(aOther.mShouldNotForceReplaceInOnLoad), + mNeedsCompletelyLoadedDocument(aOther.mNeedsCompletelyLoadedDocument), + mHistoryBehavior(aOther.mHistoryBehavior), mLoadType(aOther.mLoadType), mSHEntry(aOther.mSHEntry), mTarget(aOther.mTarget), @@ -235,12 +240,14 @@ nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier) mInheritPrincipal(false), mPrincipalIsExplicit(false), mNotifiedBeforeUnloadListeners(false), - mShouldNotForceReplaceInOnLoad(false), mForceAllowDataURI(false), mIsExemptFromHTTPSFirstMode(false), mOriginalFrameSrc(false), mShouldCheckForRecursion(false), mIsFormSubmission(false), + mShouldNotForceReplaceInOnLoad(false), + mNeedsCompletelyLoadedDocument(false), + mHistoryBehavior(Nothing()), mLoadType(LOAD_NORMAL), mSrcdocData(VoidString()), mLoadFlags(0), @@ -670,15 +677,6 @@ void nsDocShellLoadState::SetNotifiedBeforeUnloadListeners( mNotifiedBeforeUnloadListeners = aNotifiedBeforeUnloadListeners; } -bool nsDocShellLoadState::ShouldNotForceReplaceInOnLoad() const { - return mShouldNotForceReplaceInOnLoad; -} - -void nsDocShellLoadState::SetShouldNotForceReplaceInOnLoad( - bool aShouldNotForceReplaceInOnLoad) { - mShouldNotForceReplaceInOnLoad = aShouldNotForceReplaceInOnLoad; -} - bool nsDocShellLoadState::ForceAllowDataURI() const { return mForceAllowDataURI; } @@ -727,6 +725,34 @@ void nsDocShellLoadState::SetIsFormSubmission(bool aIsFormSubmission) { mIsFormSubmission = aIsFormSubmission; } +bool nsDocShellLoadState::ShouldNotForceReplaceInOnLoad() const { + return mShouldNotForceReplaceInOnLoad; +} + +void nsDocShellLoadState::SetShouldNotForceReplaceInOnLoad( + bool aShouldNotForceReplaceInOnLoad) { + mShouldNotForceReplaceInOnLoad = aShouldNotForceReplaceInOnLoad; +} + +bool nsDocShellLoadState::NeedsCompletelyLoadedDocument() const { + return mNeedsCompletelyLoadedDocument; +} + +void nsDocShellLoadState::SetNeedsCompletelyLoadedDocument( + bool aNeedsCompletelyLoadedDocument) { + mNeedsCompletelyLoadedDocument = aNeedsCompletelyLoadedDocument; +} + +Maybe<mozilla::dom::NavigationHistoryBehavior> +nsDocShellLoadState::HistoryBehavior() const { + return mHistoryBehavior; +} + +void nsDocShellLoadState::SetHistoryBehavior( + mozilla::dom::NavigationHistoryBehavior aHistoryBehavior) { + mHistoryBehavior = Some(aHistoryBehavior); +} + uint32_t nsDocShellLoadState::LoadType() const { return mLoadType; } void nsDocShellLoadState::SetLoadType(uint32_t aLoadType) { @@ -1377,6 +1403,9 @@ DocShellLoadStateInit nsDocShellLoadState::Serialize( loadState.OriginalFrameSrc() = mOriginalFrameSrc; loadState.ShouldCheckForRecursion() = mShouldCheckForRecursion; loadState.IsFormSubmission() = mIsFormSubmission; + loadState.ShouldNotForceReplaceInOnLoad() = mShouldNotForceReplaceInOnLoad; + loadState.NeedsCompletelyLoadedDocument() = mNeedsCompletelyLoadedDocument; + loadState.HistoryBehavior() = mHistoryBehavior; loadState.LoadType() = mLoadType; loadState.userNavigationInvolvement() = mUserNavigationInvolvement; loadState.Target() = mTarget; diff --git a/docshell/base/nsDocShellLoadState.h b/docshell/base/nsDocShellLoadState.h @@ -178,6 +178,16 @@ class nsDocShellLoadState final { void SetIsFormSubmission(bool aIsFormSubmission); + bool NeedsCompletelyLoadedDocument() const; + + void SetNeedsCompletelyLoadedDocument(bool aNeedsCompletelyLoadedDocument); + + mozilla::Maybe<mozilla::dom::NavigationHistoryBehavior> HistoryBehavior() + const; + + void SetHistoryBehavior( + mozilla::dom::NavigationHistoryBehavior aHistoryBehavior); + uint32_t LoadType() const; void SetLoadType(uint32_t aLoadType); @@ -542,10 +552,6 @@ class nsDocShellLoadState final { // notified if applicable. bool mNotifiedBeforeUnloadListeners; - // If this attribute is true, navigations for subframes taking place inside of - // an onload handler will not be changed to replace loads. - bool mShouldNotForceReplaceInOnLoad; - // Principal we're inheriting. If null, this means the principal should be // inherited from the current document. If set to NullPrincipal, the channel // will fill in principal information later in the load. See internal comments @@ -585,6 +591,18 @@ class nsDocShellLoadState final { // form submission. bool mIsFormSubmission; + // If this attribute is true, navigations for subframes taking place inside of + // an onload handler will not be changed to replace loads. + bool mShouldNotForceReplaceInOnLoad; + + // If this attribute is true, we need to check if the current document is + // completely loaded to determine if we should perform a push or replace load. + bool mNeedsCompletelyLoadedDocument; + + // If this attribute is `Auto`, we should determine if this should be a push + // or replace load when actually loading. + mozilla::Maybe<mozilla::dom::NavigationHistoryBehavior> mHistoryBehavior; + // Contains a load type as specified by the nsDocShellLoadTypes::load* // constants uint32_t mLoadType; diff --git a/docshell/base/nsDocShellLoadTypes.h b/docshell/base/nsDocShellLoadTypes.h @@ -21,6 +21,7 @@ # define MAKE_LOAD_TYPE(type, flags) ((type) | ((flags) << 16)) # define LOAD_TYPE_HAS_FLAGS(type, flags) ((type) & ((flags) << 16)) +# define LOAD_TYPE_SET_FLAGS(type, flags) ((type) | ((flags) << 16)) /** * These are flags that confuse ConvertLoadTypeToDocShellLoadInfo and should @@ -201,5 +202,17 @@ inline nsDOMNavigationTiming::Type ConvertLoadTypeToNavigationType( return result; } +static inline uint32_t MaybeAddLoadFlags(uint32_t aLoadType, uint32_t aFlags) { + uint32_t loadType = LOAD_TYPE_SET_FLAGS(aLoadType, aFlags); + if (IsValidLoadType(loadType)) { + return loadType; + } + + NS_WARNING("Adjusting load flags results in an invalid load type."); + return aLoadType; +} + +# undef LOAD_TYPE_SET_FLAGS + #endif // MOZILLA_INTERNAL_API #endif diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -1391,6 +1391,7 @@ Document::Document(const char* aContentType) mHaveFiredTitleChange(false), mIsShowing(false), mVisible(true), + mIsCompletelyLoaded(false), mRemovedFromDocShell(false), // mAllowDNSPrefetch starts true, so that we can always reliably && it // with various values that might disable it. Since we never prefetch @@ -12572,6 +12573,8 @@ void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget, if (auto* wgc = GetWindowGlobalChild()) { wgc->UnblockBFCacheFor(BFCacheStatus::PAGE_LOADING); } + + mIsCompletelyLoaded = true; } static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) { diff --git a/dom/base/Document.h b/dom/base/Document.h @@ -2676,12 +2676,20 @@ class Document : public nsINode, * called yet. */ bool IsShowing() const { return mIsShowing; } + /** * Return whether the document is currently visible (in the sense of * OnPageHide having been called and OnPageShow not yet having been called) */ bool IsVisible() const { return mVisible; } + /** + * Return whether the document has completely finished loading, in the spec + * sense. We only store a bool though, whereas spec stores when loading + * finished. See https://html.spec.whatwg.org/#completely-loaded-time + */ + bool IsCompletelyLoaded() const { return mIsCompletelyLoaded; } + void SetSuppressedEventListener(EventListener* aListener); EventListener* GetSuppressedEventListener() { @@ -4838,6 +4846,10 @@ class Document : public nsINode, // it's false only when we're in bfcache or unloaded. bool mVisible : 1; + // State for IsCompletelyLoaded. Starts off false and becomes true after + // pageshow has fired. Doesn't reset after that. + bool mIsCompletelyLoaded : 1; + // True if our content viewer has been removed from the docshell // (it may still be displayed, but in zombie state). Form control data // has been saved. diff --git a/dom/base/Location.cpp b/dom/base/Location.cpp @@ -173,7 +173,7 @@ void Location::SetHash(const nsACString& aHash, nsIPrincipal& aSubjectPrincipal, return; } - SetURI(uri, aSubjectPrincipal, aRv); + Navigate(uri, aSubjectPrincipal, aRv); } void Location::GetHost(nsACString& aHost, nsIPrincipal& aSubjectPrincipal, @@ -211,7 +211,7 @@ void Location::SetHost(const nsACString& aHost, nsIPrincipal& aSubjectPrincipal, return; } - SetURI(uri, aSubjectPrincipal, aRv); + Navigate(uri, aSubjectPrincipal, aRv); } void Location::GetHostname(nsACString& aHostname, @@ -248,7 +248,7 @@ void Location::SetHostname(const nsACString& aHostname, return; } - SetURI(uri, aSubjectPrincipal, aRv); + Navigate(uri, aSubjectPrincipal, aRv); } nsresult Location::GetHref(nsACString& aHref) { @@ -328,7 +328,7 @@ void Location::SetPathname(const nsACString& aPathname, return; } - SetURI(uri, aSubjectPrincipal, aRv); + Navigate(uri, aSubjectPrincipal, aRv); } void Location::GetPort(nsACString& aPort, nsIPrincipal& aSubjectPrincipal, @@ -387,7 +387,7 @@ void Location::SetPort(const nsACString& aPort, nsIPrincipal& aSubjectPrincipal, return; } - SetURI(uri, aSubjectPrincipal, aRv); + Navigate(uri, aSubjectPrincipal, aRv); } void Location::GetProtocol(nsACString& aProtocol, @@ -462,7 +462,7 @@ void Location::SetProtocol(const nsACString& aProtocol, return; } - SetURI(uri, aSubjectPrincipal, aRv); + Navigate(uri, aSubjectPrincipal, aRv); } void Location::GetSearch(nsACString& aSearch, nsIPrincipal& aSubjectPrincipal, @@ -513,7 +513,7 @@ void Location::SetSearch(const nsACString& aSearch, return; } - SetURI(uri, aSubjectPrincipal, aRv); + Navigate(uri, aSubjectPrincipal, aRv); } void Location::Reload(JSContext* aCx, bool aForceget, diff --git a/dom/base/LocationBase.cpp b/dom/base/LocationBase.cpp @@ -12,6 +12,7 @@ #include "mozilla/dom/WindowContext.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" +#include "nsDocLoader.h" #include "nsDocShellLoadState.h" #include "nsError.h" #include "nsGlobalWindowInner.h" @@ -23,16 +24,28 @@ namespace mozilla::dom { -void LocationBase::SetURI(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, - ErrorResult& aRv, bool aReplace) { - RefPtr<BrowsingContext> bc = GetBrowsingContext(); - if (!bc || bc->IsDiscarded()) { +static bool IncumbentGlobalHasTransientActivation() { + nsGlobalWindowInner* window = nsContentUtils::IncumbentInnerWindow(); + return window && window->GetWindowContext() && window->GetWindowContext() && + window->GetWindowContext()->HasValidTransientUserGestureActivation(); +} + +// https://html.spec.whatwg.org/#location-object-navigate +void LocationBase::Navigate(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv, + NavigationHistoryBehavior aHistoryHandling) { + // Step 1 + RefPtr<BrowsingContext> navigable = GetBrowsingContext(); + if (!navigable || navigable->IsDiscarded()) { return; } - bc->Navigate(aURI, aSubjectPrincipal, aRv, - aReplace ? NavigationHistoryBehavior::Replace - : NavigationHistoryBehavior::Auto); + // Step 2-3, except the check for if document is completely loaded. + bool needsCompletelyLoadedDocument = !IncumbentGlobalHasTransientActivation(); + + // Step 4 + navigable->Navigate(aURI, aSubjectPrincipal, aRv, aHistoryHandling, + needsCompletelyLoadedDocument); } void LocationBase::SetHref(const nsACString& aHref, @@ -94,7 +107,12 @@ void LocationBase::SetHrefWithBase(const nsACString& aHref, nsIURI* aBase, } } - SetURI(newUri, aSubjectPrincipal, aRv, aReplace || inScriptTag); + NavigationHistoryBehavior historyHandling = NavigationHistoryBehavior::Auto; + if (aReplace || inScriptTag) { + historyHandling = NavigationHistoryBehavior::Replace; + } + + Navigate(newUri, aSubjectPrincipal, aRv, historyHandling); } void LocationBase::Replace(const nsACString& aUrl, diff --git a/dom/base/LocationBase.h b/dom/base/LocationBase.h @@ -7,6 +7,7 @@ #ifndef mozilla_dom_LocationBase_h #define mozilla_dom_LocationBase_h +#include "mozilla/dom/NavigationBinding.h" #include "nsStringFwd.h" class nsIDocShell; @@ -37,8 +38,9 @@ class LocationBase { virtual BrowsingContext* GetBrowsingContext() = 0; virtual nsIDocShell* GetDocShell() = 0; - void SetURI(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv, - bool aReplace = false); + void Navigate(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv, + NavigationHistoryBehavior aHistoryHandling = + NavigationHistoryBehavior::Auto); void SetHrefWithBase(const nsACString& aHref, nsIURI* aBase, nsIPrincipal& aSubjectPrincipal, bool aReplace, ErrorResult& aRv); diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp @@ -6770,7 +6770,7 @@ nsresult nsGlobalWindowOuter::OpenInternal( // BrowsingContext::RevisePopupAbuseLevel() below. RefPtr<nsDocShellLoadState> loadState = aLoadState; if (!loadState && aNavigate && uri) { - loadState = nsWindowWatcher::CreateLoadState(uri, this); + loadState = nsWindowWatcher::CreateLoadState(uri, this, aDoJSFixups); } PopupBlocker::PopupControlState abuseLevel = diff --git a/dom/ipc/DOMTypes.ipdlh b/dom/ipc/DOMTypes.ipdlh @@ -64,6 +64,7 @@ using mozilla::dom::NotificationDirection from "mozilla/dom/NotificationBinding. using mozilla::dom::UserNavigationInvolvement from "mozilla/dom/UserNavigationInvolvement.h"; using mozilla::net::ClassificationFlags from "nsIClassifiedChannel.h"; using mozilla::dom::ForceMediaDocument from "mozilla/dom/LoadURIOptionsBinding.h"; +using mozilla::dom::NavigationHistoryBehavior from "mozilla/dom/NavigationBinding.h"; namespace mozilla { namespace dom { @@ -226,6 +227,10 @@ struct DocShellLoadStateInit bool TextDirectiveUserActivation; bool AllowFocusMove; bool IsFromProcessingFrameAttributes; + bool NeedsCompletelyLoadedDocument; + bool ShouldNotForceReplaceInOnLoad; + NavigationHistoryBehavior? HistoryBehavior; + SchemelessInputType SchemelessInput; ForceMediaDocument forceMediaDocument; HTTPSUpgradeTelemetryType HttpsUpgradeTelemetry; diff --git a/dom/ipc/NavigationAPIIPCUtils.h b/dom/ipc/NavigationAPIIPCUtils.h @@ -23,5 +23,10 @@ template <> struct ParamTraits<mozilla::dom::NavigationType> : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::NavigationType> { }; + +template <> +struct ParamTraits<mozilla::dom::NavigationHistoryBehavior> + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::NavigationHistoryBehavior> {}; } // namespace IPC #endif diff --git a/dom/navigation/Navigation.cpp b/dom/navigation/Navigation.cpp @@ -564,7 +564,7 @@ void Navigation::Navigate(JSContext* aCx, const nsAString& aUrl, MOZ_DIAGNOSTIC_ASSERT(bc); bc->Navigate(urlRecord, *document->NodePrincipal(), /* per spec, error handling defaults to false */ IgnoreErrors(), - aOptions.mHistory, /* aShouldNotForceReplaceInOnLoad */ true); + aOptions.mHistory); // 13. If this's upcoming non-traverse API method tracker is apiMethodTracker, // then: diff --git a/dom/security/test/sec-fetch/file_no_cache.sjs b/dom/security/test/sec-fetch/file_no_cache.sjs @@ -2,7 +2,7 @@ const MESSAGE_PAGE = function (msg) { return ` <html> <script type="text/javascript"> -window.parent.postMessage({test : "${msg}"},"*"); +onload = () => window.parent.postMessage({test : "${msg}"},"*"); </script> <script> addEventListener("back", () => { diff --git a/dom/security/test/sec-fetch/test_iframe_history_manipulation.html b/dom/security/test/sec-fetch/test_iframe_history_manipulation.html @@ -78,7 +78,7 @@ SimpleTest.waitForExplicitFinish(); testFrame = document.createElement('iframe'); testFrame.src = `https://example.org/${REQUEST_PATH}?test`; -document.body.appendChild(testFrame); +onload = () => setTimeout(() => document.body.appendChild(testFrame), 0); </script> </body> diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-user-click.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-user-click.html.ini @@ -1,5 +1,3 @@ [location-assign-user-click.html] expected: if (processor == "x86") and (os == "linux"): [OK, ERROR] - [NO replace before load, triggered by location.assign()] - expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html.ini @@ -1,11 +1,3 @@ [location-setter-user-click.html] expected: if (processor == "x86") and (os == "linux"): [OK, CRASH] - [href] - expected: FAIL - - [search] - expected: FAIL - - [hash] - expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html.ini @@ -1,11 +1,3 @@ [location-setter-user-mouseup.html] expected: if (os == "android") and fission: [OK, TIMEOUT] - [href] - expected: FAIL - - [search] - expected: FAIL - - [hash] - expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-load.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-load.html.ini @@ -1,5 +1,3 @@ [window-open-self-during-load.html] expected: if (os == "android") and fission: [OK, TIMEOUT] - [No replace during load, triggered by window.open(_self) on an iframe] - expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-pageshow.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-pageshow.html.ini @@ -1,5 +1,3 @@ [window-open-self-during-pageshow.html] expected: if (os == "android") and fission: [OK, TIMEOUT] - [No replace during pageshow, triggered by window.open(_self) on an iframe] - expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self.html.ini b/testing/web-platform/meta/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self.html.ini @@ -1,5 +1,3 @@ [window-open-self.html] expected: if (os == "android") and fission: [OK, TIMEOUT] - [No replace before load, triggered by window.open(_self) on an iframe] - expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse-during-beforeunload.html.ini b/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse-during-beforeunload.html.ini @@ -1,4 +0,0 @@ -[traverse-during-beforeunload.html] - [Traversing the history during beforeunload] - expected: FAIL - diff --git a/testing/web-platform/meta/navigation-api/currententrychange-event/location-api.html.ini b/testing/web-platform/meta/navigation-api/currententrychange-event/location-api.html.ini @@ -1,3 +0,0 @@ -[location-api.html] - [currententrychange fires for location API navigations] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin-sameorigindomain.sub.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin-sameorigindomain.sub.html.ini @@ -1,3 +0,0 @@ -[location-crossdocument-crossorigin-sameorigindomain.sub.html] - [using location.href to navigate cross-document targeting a same-origin-domain (but cross-origin) window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-crossdocument-sameorigin.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-crossdocument-sameorigin.html.ini @@ -1,3 +0,0 @@ -[location-crossdocument-sameorigin.html] - [using location.href to navigate cross-document targeting a same-origin window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin-sameorigindomain.sub.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin-sameorigindomain.sub.html.ini @@ -1,12 +0,0 @@ -[location-samedocument-crossorigin-sameorigindomain.sub.html] - expected: - if (os == "win") and debug and (processor == "x86_64"): [OK, CRASH, FAIL, ERROR] - if (os == "win") and debug and (processor == "x86"): [OK, FAIL, ERROR, CRASH] - if (os == "linux") and asan and fission: [CRASH, OK, FAIL] - if (os == "linux") and asan and not fission: [OK, CRASH, FAIL] - if (os == "linux") and not asan and (processor == "x86"): [OK, CRASH, FAIL] - if (os == "mac") and not debug: [OK, ERROR, FAIL, CRASH] - if (os == "android") and debug: [CRASH, OK, FAIL] - [OK, FAIL, CRASH] - [using location.href to navigate same-document targeting a same-origin-domain (but cross-origin) window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin.html.ini @@ -1,3 +0,0 @@ -[location-samedocument-crossorigin.html] - [using location.href to navigate same-document targeting a cross-origin window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-samedocument-sameorigin.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/location-samedocument-sameorigin.html.ini @@ -1,3 +0,0 @@ -[location-samedocument-sameorigin.html] - [using location.href to navigate same-document targeting a same-origin window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin-sameorigindomain.sub.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin-sameorigindomain.sub.html.ini @@ -1,3 +0,0 @@ -[open-crossdocument-crossorigin-sameorigindomain.sub.html] - [using window.open() to navigate cross-document targeting a same-origin-domain (but cross-origin) window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-crossdocument-sameorigin.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-crossdocument-sameorigin.html.ini @@ -1,3 +0,0 @@ -[open-crossdocument-sameorigin.html] - [using window.open() to navigate cross-document targeting a same-origin window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin-sameorigindomain.sub.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin-sameorigindomain.sub.html.ini @@ -1,3 +0,0 @@ -[open-samedocument-crossorigin-sameorigindomain.sub.html] - [using window.open() to navigate same-document targeting a same-origin-domain (but cross-origin) window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin.html.ini @@ -1,3 +0,0 @@ -[open-samedocument-crossorigin.html] - [using window.open() to navigate same-document targeting a cross-origin window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-samedocument-sameorigin.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/cross-window/open-samedocument-sameorigin.html.ini @@ -1,3 +0,0 @@ -[open-samedocument-sameorigin.html] - [using window.open() to navigate same-document targeting a same-origin window] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/navigate-iframe-location.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/navigate-iframe-location.html.ini @@ -1,3 +0,0 @@ -[navigate-iframe-location.html] - [location API on another window fires navigate event in the target window only] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/navigate-navigation-navigate.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/navigate-navigation-navigate.html.ini @@ -1,3 +0,0 @@ -[navigate-navigation-navigate.html] - [navigate event for navigation.navigate()] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigate-event/navigate-window-open.html.ini b/testing/web-platform/meta/navigation-api/navigate-event/navigate-window-open.html.ini @@ -1,5 +0,0 @@ -[navigate-window-open.html] - expected: - if os == "mac": [OK, ERROR, CRASH] - [window.open() fires navigate event in target window but not source] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigation-history-entry/current-basic.html.ini b/testing/web-platform/meta/navigation-api/navigation-history-entry/current-basic.html.ini @@ -1,3 +0,0 @@ -[current-basic.html] - [Basic tests for navigation.currentEntry] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/navigation-methods/sandboxing-back-parent.html.ini b/testing/web-platform/meta/navigation-api/navigation-methods/sandboxing-back-parent.html.ini @@ -1,3 +1,4 @@ [sandboxing-back-parent.html] + expected: TIMEOUT [A sandboxed iframe cannot navigate its parent via its own navigation object by using back()] - expected: FAIL + expected: TIMEOUT diff --git a/testing/web-platform/meta/navigation-api/state/cross-document-getState-undefined.html.ini b/testing/web-platform/meta/navigation-api/state/cross-document-getState-undefined.html.ini @@ -1,3 +0,0 @@ -[cross-document-getState-undefined.html] - [Default behavior for entry.getState() for a non-current cross-document entry] - expected: FAIL diff --git a/testing/web-platform/meta/navigation-api/state/cross-document-location-api.html.ini b/testing/web-platform/meta/navigation-api/state/cross-document-location-api.html.ini @@ -1,15 +0,0 @@ -[cross-document-location-api.html?method=navigate] - expected: - if (os == "linux") and not tsan and (processor == "x86_64") and debug and not fission: [CRASH, OK] - if (os == "win") and not debug and (processor == "x86"): [CRASH, OK] - if (os == "mac") and not debug: [ERROR, CRASH, OK] - if (os == "linux") and tsan: [CRASH, OK] - if (os == "android") and not debug: [CRASH, OK] - [OK, CRASH] - [entry.getState() behavior after cross-document location API navigation] - expected: FAIL - - -[cross-document-location-api.html?method=updateCurrentEntry] - [entry.getState() behavior after cross-document location API navigation] - expected: FAIL diff --git a/testing/web-platform/tests/css/cssom-view/scroll-behavior-smooth-navigation.html b/testing/web-platform/tests/css/cssom-view/scroll-behavior-smooth-navigation.html @@ -70,7 +70,6 @@ }); location.hash = "foo"; }; - instant(); function smooth() { document.documentElement.className = ""; @@ -110,4 +109,9 @@ }; testContainer.style.display = "none"; + + // Setting location.hash while loading results in a replace load, which we don't want. + window.onload = () => { + step_timeout(instant, 0); + } </script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/self-close.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/self-close.html @@ -2,7 +2,7 @@ <p>self-close.html?navs=n&channel=name will navigate n times, then close and notify the channel.</p> <script> -window.onload = setTimeout(() => { +window.onload = () => setTimeout(() => { const urlParams = new URLSearchParams(window.location.search); let n = parseInt(urlParams.get('navs')) || 0; diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-navigate.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-navigate.html @@ -4,7 +4,7 @@ <script> async_test(t => { navigation.onnavigate = t.step_func_done(e => { - assert_equals(e.navigationType, "replace"); + assert_equals(e.navigationType, "push"); assert_true(e.cancelable); assert_true(e.canIntercept); assert_false(e.userInitiated); diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp @@ -2064,7 +2064,7 @@ bool nsWindowWatcher::HaveSpecifiedSize(const WindowFeatures& features) { /* static */ already_AddRefed<nsDocShellLoadState> nsWindowWatcher::CreateLoadState( - nsIURI* aUri, nsPIDOMWindowOuter* aParent) { + nsIURI* aUri, nsPIDOMWindowOuter* aParent, bool aIsWindowOpen) { MOZ_ASSERT(aUri); RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aUri); @@ -2094,6 +2094,13 @@ already_AddRefed<nsDocShellLoadState> nsWindowWatcher::CreateLoadState( } } + // If we're called from JS, i.e window.open, we need to set history handling + // behavior here to be able to do push to replace conversion if needed. + if (aIsWindowOpen) { + loadState->SetHistoryBehavior(NavigationHistoryBehavior::Auto); + loadState->SetShouldNotForceReplaceInOnLoad(true); + } + return loadState.forget(); } diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.h b/toolkit/components/windowwatcher/nsWindowWatcher.h @@ -71,6 +71,7 @@ class nsWindowWatcher : public nsIWindowWatcher, * - the user gesture activation flag based on the parent document * - the text directive user activation flag; this will consume the parent * document's flag and OR's it with the user gesture activation flag. + * If `aIsWindowOpen` is true, history handling will be set to "auto". * * Currently, the returned load state is intended to be passed into * `OpenWindowInternal()`. @@ -78,7 +79,7 @@ class nsWindowWatcher : public nsIWindowWatcher, * function. */ static already_AddRefed<nsDocShellLoadState> CreateLoadState( - nsIURI* aUri, nsPIDOMWindowOuter* aParent); + nsIURI* aUri, nsPIDOMWindowOuter* aParent, bool aIsWindowOpen = false); protected: virtual ~nsWindowWatcher();