/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/Navigation.h" #include "NavigationPrecommitController.h" #include "fmt/format.h" #include "jsapi.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/CycleCollectedUniquePtr.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/Logging.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/ErrorEvent.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/FeaturePolicy.h" #include "mozilla/dom/NavigationActivation.h" #include "mozilla/dom/NavigationBinding.h" #include "mozilla/dom/NavigationCurrentEntryChangeEvent.h" #include "mozilla/dom/NavigationHistoryEntry.h" #include "mozilla/dom/NavigationTransition.h" #include "mozilla/dom/NavigationUtils.h" #include "mozilla/dom/Promise-inl.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/SessionHistoryEntry.h" #include "mozilla/dom/WindowContext.h" #include "mozilla/dom/WindowGlobalChild.h" #include "nsContentUtils.h" #include "nsCycleCollectionParticipant.h" #include "nsDocShell.h" #include "nsGlobalWindowInner.h" #include "nsIMultiPartChannel.h" #include "nsIPrincipal.h" #include "nsISHistory.h" #include "nsIScriptChannel.h" #include "nsIStructuredCloneContainer.h" #include "nsIXULRuntime.h" #include "nsNetUtil.h" #include "nsTHashtable.h" mozilla::LazyLogModule gNavigationAPILog("NavigationAPI"); #define LOG_FMTE(format, ...) \ MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Error, format, ##__VA_ARGS__); #define LOG_FMTW(format, ...) \ MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Warning, format, ##__VA_ARGS__); #define LOG_FMTI(format, ...) \ MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Info, format, ##__VA_ARGS__); #define LOG_FMTD(format, ...) \ MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug, format, ##__VA_ARGS__); #define LOG_FMTV(format, ...) \ MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Verbose, format, ##__VA_ARGS__); namespace mozilla::dom { static void InitNavigationResult(NavigationResult& aResult, const RefPtr& aCommitted, const RefPtr& aFinished) { if (aCommitted) { aResult.mCommitted.Reset(); aResult.mCommitted.Construct(*aCommitted); } if (aFinished) { aResult.mFinished.Reset(); aResult.mFinished.Construct(*aFinished); } } NavigationAPIMethodTracker::NavigationAPIMethodTracker( Navigation* aNavigationObject, const Maybe aKey, const JS::Value& aInfo, nsIStructuredCloneContainer* aSerializedState, NavigationHistoryEntry* aCommittedToEntry, Promise* aCommittedPromise, Promise* aFinishedPromise, bool aPending) : mNavigationObject(aNavigationObject), mKey(aKey), mInfo(aInfo), mPending(aPending), mSerializedState(aSerializedState), mCommittedToEntry(aCommittedToEntry), mCommittedPromise(aCommittedPromise), mFinishedPromise(aFinishedPromise) { mozilla::HoldJSObjects(this); } NavigationAPIMethodTracker::~NavigationAPIMethodTracker() { mozilla::DropJSObjects(this); } // https://html.spec.whatwg.org/#navigation-api-method-tracker-clean-up void NavigationAPIMethodTracker::CleanUp() { Navigation::CleanUp(this); } // https://html.spec.whatwg.org/#notify-about-the-committed-to-entry void NavigationAPIMethodTracker::NotifyAboutCommittedToEntry( NavigationHistoryEntry* aNHE) { MOZ_DIAGNOSTIC_ASSERT(mCommittedPromise); // Step 1 mCommittedToEntry = aNHE; if (mSerializedState) { // Step 2 aNHE->SetNavigationAPIState(mSerializedState); // At this point, apiMethodTracker's serialized state is no longer needed. // We drop it do now for efficiency. mSerializedState = nullptr; } mCommittedPromise->MaybeResolve(aNHE); } // https://html.spec.whatwg.org/#resolve-the-finished-promise void NavigationAPIMethodTracker::ResolveFinishedPromise() { MOZ_DIAGNOSTIC_ASSERT(mFinishedPromise); // Step 1 MOZ_DIAGNOSTIC_ASSERT(mCommittedToEntry); // Step 2 mFinishedPromise->MaybeResolve(mCommittedToEntry); // Step 3 CleanUp(); } // https://html.spec.whatwg.org/#reject-the-finished-promise void NavigationAPIMethodTracker::RejectFinishedPromise( JS::Handle aException) { MOZ_DIAGNOSTIC_ASSERT(mFinishedPromise); MOZ_DIAGNOSTIC_ASSERT(mCommittedPromise); // Step 1 mCommittedPromise->MaybeReject(aException); // Step 2 mFinishedPromise->MaybeReject(aException); // Step 3 CleanUp(); } // https://html.spec.whatwg.org/#navigation-api-method-tracker-derived-result void NavigationAPIMethodTracker::CreateResult(JSContext* aCx, NavigationResult& aResult) { // A navigation API method tracker-derived result for a navigation API // method tracker is a NavigationResult dictionary instance given by the // following steps: // 1. If apiMethodTracker is pending, then return an early error result for // an "AbortError" DOMException. if (mPending) { ErrorResult rv; rv.ThrowAbortError("Navigation aborted"); mNavigationObject->SetEarlyErrorResult(aCx, aResult, std::move(rv)); return; } // 2. Return «[ "committed" → apiMethodTracker's committed promise, // "finished" → apiMethodTracker's finished promise ]». InitNavigationResult(aResult, mCommittedPromise, mFinishedPromise); } NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(NavigationAPIMethodTracker, (mNavigationObject, mSerializedState, mCommittedToEntry, mCommittedPromise, mFinishedPromise), (mInfo)) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NavigationAPIMethodTracker) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(NavigationAPIMethodTracker) NS_IMPL_CYCLE_COLLECTING_RELEASE(NavigationAPIMethodTracker) NS_IMPL_CYCLE_COLLECTION_INHERITED(Navigation, DOMEventTargetHelper, mEntries, mOngoingNavigateEvent, mTransition, mActivation, mOngoingAPIMethodTracker, mUpcomingTraverseAPIMethodTrackers); 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) : DOMEventTargetHelper(aWindow) { MOZ_ASSERT(aWindow); } JSObject* Navigation::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return Navigation_Binding::Wrap(aCx, this, aGivenProto); } void Navigation::EventListenerAdded(nsAtom* aType) { UpdateNeedsTraverse(); EventTarget::EventListenerAdded(aType); } void Navigation::EventListenerRemoved(nsAtom* aType) { UpdateNeedsTraverse(); EventTarget::EventListenerRemoved(aType); } /* static */ bool Navigation::IsAPIEnabled(JSContext* /* unused */, JSObject* /* unused */) { return SessionHistoryInParent() && StaticPrefs::dom_navigation_webidl_enabled_DoNotUseDirectly(); } void Navigation::Entries( nsTArray>& aResult) const { if (HasEntriesAndEventsDisabled()) { aResult.Clear(); return; } aResult = mEntries.Clone(); } already_AddRefed Navigation::GetCurrentEntry() const { if (HasEntriesAndEventsDisabled()) { return nullptr; } if (!mCurrentEntryIndex) { return nullptr; } MOZ_LOG(gNavigationAPILog, 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/#dom-navigation-updatecurrententry void Navigation::UpdateCurrentEntry( JSContext* aCx, const NavigationUpdateCurrentEntryOptions& aOptions, ErrorResult& aRv) { LOG_FMTI("Called navigation.updateCurrentEntry()"); 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->SetNavigationAPIState(serializedState); ToMaybeRef(GetOwnerWindow()) .andThen([](auto& aWindow) { return ToMaybeRef(aWindow.GetBrowsingContext()); }) .apply([serializedState](auto& navigable) { navigable.SynchronizeNavigationAPIState(serializedState); ToMaybeRef(nsDocShell::Cast(navigable.GetDocShell())) .andThen([](auto& docshell) { return ToMaybeRef(docshell.GetActiveSessionHistoryInfo()); }) .apply([serializedState](auto& activeInfo) { activeInfo.SetNavigationAPIState(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); event->SetTrusted(true); DispatchEvent(*event); } NavigationTransition* Navigation::GetTransition() const { return mTransition; } NavigationActivation* Navigation::GetActivation() const { return mActivation; } template bool SupportsInterface(nsISupports* aSupports) { nsCOMPtr ptr = do_QueryInterface(aSupports); return ptr; } // https://html.spec.whatwg.org/#has-entries-and-events-disabled bool Navigation::HasEntriesAndEventsDisabled() const { Document* doc = GetAssociatedDocument(); return !doc || !doc->IsCurrentActiveDocument() || doc->GetInitialStatus() == Document::InitialStatus::IsInitial || doc->GetInitialStatus() == Document::InitialStatus::IsInitialButExplicitlyOpened || doc->GetPrincipal()->GetIsNullPrincipal() || // We explicitly disallow documents loaded through multipart and script // channels from having events or entries. See bug 1996218 and bug // 1996221 SupportsInterface(doc->GetChannel()) || SupportsInterface(doc->GetChannel()) || // We also disallow documents embedded using /. See bug // 1996215. !doc->GetBrowsingContext() || doc->GetBrowsingContext()->IsEmbedderTypeObjectOrEmbed(); } // https://html.spec.whatwg.org/#initialize-the-navigation-api-entries-for-a-new-document void Navigation::InitializeHistoryEntries( mozilla::Span aNewSHInfos, const SessionHistoryInfo* aInitialSHInfo) { LOG_FMTD("Attempting to initialize history entries for {}.", aInitialSHInfo->GetURI() ? aInitialSHInfo->GetURI()->GetSpecOrDefault() : ""_ns) mEntries.Clear(); mCurrentEntryIndex.reset(); if (HasEntriesAndEventsDisabled()) { return; } for (auto i = 0ul; i < aNewSHInfos.Length(); i++) { mEntries.AppendElement(MakeRefPtr( GetOwnerGlobal(), &aNewSHInfos[i], i)); if (aNewSHInfos[i].NavigationKey() == aInitialSHInfo->NavigationKey()) { mCurrentEntryIndex = Some(i); } } LogHistory(); nsID key = aInitialSHInfo->NavigationKey(); nsID id = aInitialSHInfo->NavigationId(); MOZ_LOG( gNavigationAPILog, LogLevel::Debug, ("aInitialSHInfo: %s %s\n", key.ToString().get(), id.ToString().get())); } // https://html.spec.whatwg.org/#update-the-navigation-api-entries-for-a-same-document-navigation void Navigation::UpdateEntriesForSameDocumentNavigation( SessionHistoryInfo* aDestinationSHE, NavigationType aNavigationType) { // Step 1. if (HasEntriesAndEventsDisabled()) { return; } MOZ_LOG(gNavigationAPILog, LogLevel::Debug, ("Updating entries for same-document navigation")); // Steps 2-7. RefPtr oldCurrentEntry = GetCurrentEntry(); nsTArray> disposedEntries; switch (aNavigationType) { case NavigationType::Traverse: MOZ_LOG(gNavigationAPILog, LogLevel::Debug, ("Traverse navigation")); SetCurrentEntryIndex(aDestinationSHE); MOZ_ASSERT(mCurrentEntryIndex); break; case NavigationType::Push: MOZ_LOG(gNavigationAPILog, LogLevel::Debug, ("Push navigation")); mCurrentEntryIndex = Some(mCurrentEntryIndex ? *mCurrentEntryIndex + 1 : 0); disposedEntries.AppendElements(Span(mEntries).From(*mCurrentEntryIndex)); mEntries.RemoveElementsAt(*mCurrentEntryIndex, mEntries.Length() - *mCurrentEntryIndex); mEntries.AppendElement(MakeRefPtr( GetOwnerGlobal(), aDestinationSHE, *mCurrentEntryIndex)); break; case NavigationType::Replace: MOZ_LOG(gNavigationAPILog, LogLevel::Debug, ("Replace navigation")); if (!oldCurrentEntry) { LOG_FMTE("No current entry."); MOZ_ASSERT(false, "FIXME"); return; } disposedEntries.AppendElement(oldCurrentEntry); MOZ_DIAGNOSTIC_ASSERT( aDestinationSHE->NavigationKey() == oldCurrentEntry->SessionHistoryInfo()->NavigationKey()); mEntries[*mCurrentEntryIndex] = MakeRefPtr( GetOwnerGlobal(), aDestinationSHE, *mCurrentEntryIndex); break; case NavigationType::Reload: break; } // Step 8. if (mOngoingAPIMethodTracker) { RefPtr currentEntry = GetCurrentEntry(); mOngoingAPIMethodTracker->NotifyAboutCommittedToEntry(currentEntry); } for (auto& entry : disposedEntries) { entry->ResetIndexForDisposal(); } // Steps 9-12. { nsAutoMicroTask mt; AutoEntryScript aes(GetOwnerGlobal(), "UpdateEntriesForSameDocumentNavigation"); NavigationCurrentEntryChangeEventInit init; init.mFrom = oldCurrentEntry; init.mNavigationType.SetValue(aNavigationType); RefPtr event = NavigationCurrentEntryChangeEvent::Constructor( this, u"currententrychange"_ns, init); event->SetTrusted(true); 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); } } } // https://html.spec.whatwg.org/#update-the-navigation-api-entries-for-reactivation void Navigation::UpdateForReactivation(SessionHistoryInfo* aReactivatedEntry) { // NAV-TODO } // https://html.spec.whatwg.org/#navigation-api-early-error-result void Navigation::SetEarlyErrorResult(JSContext* aCx, NavigationResult& aResult, ErrorResult&& aRv) const { MOZ_ASSERT(aRv.Failed()); // An early error result for an exception e is a NavigationResult dictionary // instance given by // «[ "committed" → a promise rejected with e, // "finished" → a promise rejected with e ]». // Get the global of the current realm to create the DOMException. // See https://webidl.spec.whatwg.org/#js-creating-throwing-exceptions nsIGlobalObject* global = GetCurrentGlobal(); if (!global) { // Creating a promise should only fail if there is no global. // In this case, the only solution is to ignore the error. aRv.SuppressException(); return; } JS::Rooted rootedExceptionValue(aCx); MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(aRv), &rootedExceptionValue)); InitNavigationResult( aResult, Promise::Reject(global, rootedExceptionValue, IgnoreErrors()), Promise::Reject(global, rootedExceptionValue, IgnoreErrors())); } void Navigation::SetEarlyStateErrorResult(JSContext* aCx, NavigationResult& aResult, const nsACString& aMessage) const { ErrorResult rv; rv.ThrowInvalidStateError(aMessage); SetEarlyErrorResult(aCx, aResult, std::move(rv)); } bool Navigation::CheckIfDocumentIsFullyActiveAndMaybeSetEarlyErrorResult( JSContext* aCx, const Document* aDocument, NavigationResult& aResult) const { if (!aDocument || !aDocument->IsFullyActive()) { ErrorResult rv; rv.ThrowInvalidStateError("Document is not fully active"); SetEarlyErrorResult(aCx, aResult, std::move(rv)); return false; } return true; } bool Navigation::CheckDocumentUnloadCounterAndMaybeSetEarlyErrorResult( JSContext* aCx, const Document* aDocument, NavigationResult& aResult) const { if (!aDocument || aDocument->ShouldIgnoreOpens()) { ErrorResult rv; rv.ThrowInvalidStateError("Document is unloading"); SetEarlyErrorResult(aCx, aResult, std::move(rv)); return false; } return true; } already_AddRefed Navigation::CreateSerializedStateAndMaybeSetEarlyErrorResult( JSContext* aCx, const JS::Value& aState, NavigationResult& aResult) const { JS::Rooted state(aCx, aState); RefPtr global = GetOwnerGlobal(); MOZ_DIAGNOSTIC_ASSERT(global); RefPtr serializedState = new nsStructuredCloneContainer(); const nsresult rv = serializedState->InitFromJSVal(state, aCx); if (NS_FAILED(rv)) { JS::Rooted exception(aCx); if (JS_GetPendingException(aCx, &exception)) { JS_ClearPendingException(aCx); InitNavigationResult(aResult, Promise::Reject(global, exception, IgnoreErrors()), Promise::Reject(global, exception, IgnoreErrors())); return nullptr; } SetEarlyErrorResult(aCx, aResult, ErrorResult(rv)); return nullptr; } return serializedState.forget(); } // https://html.spec.whatwg.org/#dom-navigation-navigate void Navigation::Navigate(JSContext* aCx, const nsAString& aUrl, const NavigationNavigateOptions& aOptions, NavigationResult& aResult) { LOG_FMTI("Called navigation.navigate() with url = {}", NS_ConvertUTF16toUTF8(aUrl)); // 4. Let document be this's relevant global object's associated Document. const RefPtr document = GetAssociatedDocument(); if (!document) { return; } // 1. Let urlRecord be the result of parsing a URL given url, relative to // this's relevant settings object. RefPtr urlRecord; nsresult res = NS_NewURI(getter_AddRefs(urlRecord), aUrl, nullptr, document->GetDocBaseURI()); if (NS_FAILED(res)) { // 2. If urlRecord is failure, then return an early error result for a // "SyntaxError" DOMException. ErrorResult rv; rv.ThrowSyntaxError("URL given to navigate() is invalid"); SetEarlyErrorResult(aCx, aResult, std::move(rv)); return; } // 3. If urlRecord's scheme is "javascript", then return an early error result // for a "NotSupportedError" DOMException. if (urlRecord->SchemeIs("javascript")) { ErrorResult rv; rv.ThrowNotSupportedError("The javascript: protocol is not supported"); SetEarlyErrorResult(aCx, aResult, std::move(rv)); return; } // 5. If options["history"] is "push", and the navigation must be a replace // given urlRecord and document, then return an early error result for a // "NotSupportedError" DOMException. if (aOptions.mHistory == NavigationHistoryBehavior::Push && nsContentUtils::NavigationMustBeAReplace(*urlRecord, *document)) { ErrorResult rv; rv.ThrowNotSupportedError("Navigation must be a replace navigation"); SetEarlyErrorResult(aCx, aResult, std::move(rv)); return; } // 6. Let state be options["state"], if it exists; otherwise, undefined. // 7. Let serializedState be StructuredSerializeForStorage(state). If this // throws an exception, then return an early error result for that // exception. nsCOMPtr serializedState = CreateSerializedStateAndMaybeSetEarlyErrorResult(aCx, aOptions.mState, aResult); if (!serializedState) { return; } // 8. If document is not fully active, then return an early error result for // an "InvalidStateError" DOMException. if (!CheckIfDocumentIsFullyActiveAndMaybeSetEarlyErrorResult(aCx, document, aResult)) { return; } // 9. If document's unload counter is greater than 0, then return an early // error result for an "InvalidStateError" DOMException. if (!CheckDocumentUnloadCounterAndMaybeSetEarlyErrorResult(aCx, document, aResult)) { return; } // 10. Let info be options["info"], if it exists; otherwise, undefined. // 11. Let apiMethodTracker be the result of setting up a navigate API method // tracker for this given info and serializedState. JS::Rooted info(aCx, aOptions.mInfo); RefPtr apiMethodTracker = SetUpNavigateReloadAPIMethodTracker(info, serializedState); MOZ_ASSERT(apiMethodTracker); // 12. Navigate document's node navigable to urlRecord using document, with // historyHandling set to options["history"], navigationAPIState set to // serializedState, and navigationAPIMethodTracker set to // apiMethodTracker. RefPtr bc = document->GetBrowsingContext(); MOZ_DIAGNOSTIC_ASSERT(bc); bc->Navigate(urlRecord, document, *document->NodePrincipal(), /* per spec, error handling defaults to false */ IgnoreErrors(), aOptions.mHistory, /* aNeedsCompletelyLoadedDocument */ false, serializedState, apiMethodTracker); // 13. Return a navigation API method tracker-derived result for // apiMethodTracker. apiMethodTracker->CreateResult(aCx, aResult); } // https://html.spec.whatwg.org/#performing-a-navigation-api-traversal void Navigation::PerformNavigationTraversal(JSContext* aCx, const nsID& aKey, const NavigationOptions& aOptions, NavigationResult& aResult) { LOG_FMTV("traverse navigation to {}", aKey.ToString().get()); // 1. Let document be navigation's relevant global object's associated // Document. const Document* document = GetAssociatedDocument(); // 2. If document is not fully active, then return an early error result for // an "InvalidStateError" DOMException. if (!document || !document->IsFullyActive()) { SetEarlyStateErrorResult(aCx, aResult, "Document is not fully active"_ns); return; } // 3. If document's unload counter is greater than 0, then return an early // error result for an "InvalidStateError" DOMException. if (document->ShouldIgnoreOpens()) { SetEarlyStateErrorResult(aCx, aResult, "Document is unloading"_ns); return; } // 4. Let current be the current entry of navigation. RefPtr current = GetCurrentEntry(); if (!current) { SetEarlyStateErrorResult(aCx, aResult, "No current navigation history entry"_ns); return; } // 5. If key equals current's session history entry's navigation API key, then // return «[ "committed" → a promise resolved with current, "finished" → a // promise resolved with current ]». RefPtr global = GetOwnerGlobal(); if (!global) { return; } if (current->Key() == aKey) { InitNavigationResult(aResult, Promise::Resolve(global, current, IgnoreErrors()), Promise::Resolve(global, current, IgnoreErrors())); return; } // 6. If navigation's upcoming traverse API method trackers[key] exists, then // return a navigation API method tracker-derived result for navigation's // upcoming traverse API method trackers[key]. if (auto maybeTracker = mUpcomingTraverseAPIMethodTrackers.MaybeGet(aKey).valueOr(nullptr)) { maybeTracker->CreateResult(aCx, aResult); return; } // 7. Let info be options["info"], if it exists; otherwise, undefined. JS::Rooted info(aCx, aOptions.mInfo); // 8. Let apiMethodTracker be the result of adding an upcoming traverse API // method tracker for navigation given key and info. RefPtr apiMethodTracker = AddUpcomingTraverseAPIMethodTracker(aKey, info); // 9. Let navigable be document's node navigable. RefPtr navigable = document->GetBrowsingContext(); // 10. Let traversable be navigable's traversable navigable. RefPtr traversable = navigable->Top(); // 11. Let sourceSnapshotParams be the result of snapshotting source snapshot // params given document. // 13. Return a navigation API method tracker-derived result for // apiMethodTracker. apiMethodTracker->CreateResult(aCx, aResult); // 12. Append the following session history traversal steps to traversable: auto* childSHistory = traversable->GetChildSessionHistory(); auto performNavigationTraversalSteps = [apiMethodTracker](nsresult aResult) { // 12.3 If targetSHE is navigable's active session history entry, // then abort these steps. if (NS_SUCCEEDED(aResult)) { return; } AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init( apiMethodTracker->mNavigationObject->GetParentObject()))) { return; } ErrorResult rv; switch (aResult) { case NS_ERROR_DOM_INVALID_STATE_ERR: // 12.2 Let targetSHE be the session history entry in navigableSHEs // whose navigation API key is key. If no such entry exists, // then: rv.ThrowInvalidStateError("No such entry with key found"); break; case NS_ERROR_DOM_ABORT_ERR: // 12.5 If result is "canceled-by-beforeunload", then queue a global // task on the navigation and traversal task source given // navigation's relevant global object to reject the finished // promise for apiMethodTracker with a new "AbortError" // DOMException // created in navigation's relevant realm. rv.ThrowAbortError("Navigation was canceled"); break; case NS_ERROR_DOM_SECURITY_ERR: // 12.6 If result is "initiator-disallowed", then queue a global task on // the navigation and traversal task source given navigation's // relevant global object to reject the finished promise for // apiMethodTracker with a new "SecurityError" DOMException // created in navigation's relevant realm. rv.ThrowSecurityError("Navigation was not allowed"); break; default: MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected result"); rv.ThrowInvalidStateError("Unexpected result"); break; } JS::Rooted rootedExceptionValue(jsapi.cx()); MOZ_ALWAYS_TRUE( ToJSValue(jsapi.cx(), std::move(rv), &rootedExceptionValue)); apiMethodTracker->RejectFinishedPromise(rootedExceptionValue); }; // 12.4 Let result be the result of applying the traverse history step given // by targetSHE's step to traversable, given sourceSnapshotParams, // navigable, and "none". childSHistory->AsyncGo(aKey, navigable, /*aRequireUserInteraction=*/false, /*aUserActivation=*/false, /*aCheckForCancelation=*/true, performNavigationTraversalSteps); } // https://html.spec.whatwg.org/#dom-navigation-reload void Navigation::Reload(JSContext* aCx, const NavigationReloadOptions& aOptions, NavigationResult& aResult) { LOG_FMTI("Called navigation.reload()"); // 1. Let document be this's relevant global object's associated Document. const RefPtr document = GetAssociatedDocument(); if (!document) { return; } // 2. Let serializedState be StructuredSerializeForStorage(undefined). RefPtr serializedState; // 3. If options["state"] exists, then set serializedState to // StructuredSerializeForStorage(options["state"]). If this throws an // exception, then return an early error result for that exception. if (!aOptions.mState.isUndefined()) { serializedState = CreateSerializedStateAndMaybeSetEarlyErrorResult( aCx, aOptions.mState, aResult); if (!serializedState) { return; } } else { // 4. Otherwise: // 4.1 Let current be the current entry of this. // 4.2 If current is not null, then set serializedState to current's // session history entry's navigation API state. if (RefPtr current = GetCurrentEntry()) { serializedState = current->GetNavigationAPIState(); } } // 5. If document is not fully active, then return an early error result for // an "InvalidStateError" DOMException. if (!CheckIfDocumentIsFullyActiveAndMaybeSetEarlyErrorResult(aCx, document, aResult)) { return; } // 6. If document's unload counter is greater than 0, then return an early // error result for an "InvalidStateError" DOMException. if (!CheckDocumentUnloadCounterAndMaybeSetEarlyErrorResult(aCx, document, aResult)) { return; } // 7. Let info be options["info"], if it exists; otherwise, undefined. JS::Rooted info(aCx, aOptions.mInfo); // 8. Let apiMethodTracker be the result of setting up a reload API method // tracker for this given info and serializedState. RefPtr apiMethodTracker = SetUpNavigateReloadAPIMethodTracker(info, serializedState); MOZ_ASSERT(apiMethodTracker); // 9. Reload document's node navigable with navigationAPIState set to // serializedState and navigationAPIMethodTracker set to apiMethodTracker. RefPtr docShell = nsDocShell::Cast(document->GetDocShell()); MOZ_ASSERT(docShell); docShell->ReloadNavigable(Some(WrapNotNullUnchecked(aCx)), nsIWebNavigation::LOAD_FLAGS_NONE, serializedState, UserNavigationInvolvement::None, apiMethodTracker); // 10. Return a navigation API method tracker-derived result for // apiMethodTracker. apiMethodTracker->CreateResult(aCx, aResult); } // https://html.spec.whatwg.org/#dom-navigation-traverseto void Navigation::TraverseTo(JSContext* aCx, const nsAString& aKey, const NavigationOptions& aOptions, NavigationResult& aResult) { LOG_FMTI("Called navigation.traverseTo() with key = {}", NS_ConvertUTF16toUTF8(aKey).get()); // 1. If this's current entry index is −1, then return an early error result // for an "InvalidStateError" DOMException. if (mCurrentEntryIndex.isNothing()) { ErrorResult rv; rv.ThrowInvalidStateError("Current entry index is unexpectedly -1"); SetEarlyErrorResult(aCx, aResult, std::move(rv)); return; } // 2. If this's entry list does not contain a NavigationHistoryEntry whose // session history entry's navigation API key equals key, then return an // early error result for an "InvalidStateError" DOMException. nsID key{}; const bool foundKey = key.Parse(NS_ConvertUTF16toUTF8(aKey).Data()) && std::find_if(mEntries.begin(), mEntries.end(), [&](const auto& aEntry) { return aEntry->Key() == key; }) != mEntries.end(); if (!foundKey) { ErrorResult rv; rv.ThrowInvalidStateError("Session history entry key does not exist"); SetEarlyErrorResult(aCx, aResult, std::move(rv)); return; } // 3. Return the result of performing a navigation API traversal given this, // key, and options. PerformNavigationTraversal(aCx, key, aOptions, aResult); } // https://html.spec.whatwg.org/#dom-navigation-back void Navigation::Back(JSContext* aCx, const NavigationOptions& aOptions, NavigationResult& aResult) { LOG_FMTI("Called navigation.back()"); // 1. If this's current entry index is −1 or 0, then return an early error // result for an "InvalidStateError" DOMException. if (mCurrentEntryIndex.isNothing() || *mCurrentEntryIndex == 0 || *mCurrentEntryIndex > mEntries.Length() - 1) { SetEarlyStateErrorResult(aCx, aResult, "Current entry index is unexpectedly -1 or 0"_ns); return; } // 2. Let key be this's entry list[this's current entry index − 1]'s session // history entry's navigation API key. MOZ_DIAGNOSTIC_ASSERT(mEntries[*mCurrentEntryIndex - 1]); const nsID key = mEntries[*mCurrentEntryIndex - 1]->Key(); // 3. Return the result of performing a navigation API traversal given this, // key, and options. PerformNavigationTraversal(aCx, key, aOptions, aResult); } // https://html.spec.whatwg.org/#dom-navigation-forward void Navigation::Forward(JSContext* aCx, const NavigationOptions& aOptions, NavigationResult& aResult) { LOG_FMTI("Called navigation.forward()"); // 1. If this's current entry index is −1 or is equal to this's entry list's // size − 1, then return an early error result for an "InvalidStateError" // DOMException. if (mCurrentEntryIndex.isNothing() || *mCurrentEntryIndex >= mEntries.Length() - 1) { ErrorResult rv; rv.ThrowInvalidStateError( "Current entry index is unexpectedly -1 or entry list's size - 1"); SetEarlyErrorResult(aCx, aResult, std::move(rv)); return; } // 2. Let key be this's entry list[this's current entry index + 1]'s session // history entry's navigation API key. MOZ_ASSERT(mEntries[*mCurrentEntryIndex + 1]); const nsID& key = mEntries[*mCurrentEntryIndex + 1]->Key(); // 3. Return the result of performing a navigation API traversal given this, // key, and options. PerformNavigationTraversal(aCx, key, aOptions, aResult); } namespace { void LogEntry(NavigationHistoryEntry* aEntry, uint64_t aIndex, uint64_t aTotal, bool aIsCurrent) { if (!aEntry) { MOZ_LOG(gNavigationAPILog, LogLevel::Debug, (" +- %d NHEntry null\n", int(aIndex))); return; } nsString key, id; aEntry->GetKey(key); aEntry->GetId(id); MOZ_LOG(gNavigationAPILog, 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(gNavigationAPILog, LogLevel::Debug, (" URL = %s\n", NS_ConvertUTF16toUTF8(url).get())); } } // namespace // https://html.spec.whatwg.org/#fire-a-traverse-navigate-event bool Navigation::FireTraverseNavigateEvent( JSContext* aCx, const SessionHistoryInfo& aDestinationSessionHistoryInfo, Maybe aUserInvolvement) { // aDestinationSessionHistoryInfo corresponds to // https://html.spec.whatwg.org/#fire-navigate-traverse-destinationshe // To not unnecessarily create an event that's never used, step 1 and step 2 // in #fire-a-traverse-navigate-event have been moved to after step 25 in // #inner-navigate-event-firing-algorithm in our implementation. // Work around for https://github.com/whatwg/html/issues/11802 InnerInformAboutAbortingNavigation(aCx); // Step 5 RefPtr destinationNHE = FindNavigationHistoryEntry(aDestinationSessionHistoryInfo); // Step 6.2 and step 7.2 RefPtr state = destinationNHE ? destinationNHE->GetNavigationAPIState() : nullptr; // Step 8 bool isSameDocument = ToMaybeRef( nsDocShell::Cast(nsContentUtils::GetDocShellForEventTarget(this))) .andThen([](auto& aDocShell) { return ToMaybeRef(aDocShell.GetActiveSessionHistoryInfo()); }) .map([&aDestinationSessionHistoryInfo](auto& aSessionHistoryInfo) { return aDestinationSessionHistoryInfo.SharesDocumentWith( aSessionHistoryInfo); }) .valueOr(false); // Step 3, step 4, step 6.1, and step 7.1. RefPtr destination = MakeAndAddRef( GetOwnerGlobal(), aDestinationSessionHistoryInfo.GetURI(), destinationNHE, state, isSameDocument); // Step 9 return InnerFireNavigateEvent( aCx, NavigationType::Traverse, destination, aUserInvolvement.valueOr(UserNavigationInvolvement::None), /* aSourceElement */ nullptr, /* aFormDataEntryList*/ nullptr, /* aClassicHistoryAPIState */ nullptr, /* aDownloadRequestFilename */ VoidString()); } // https://html.spec.whatwg.org/#fire-a-push/replace/reload-navigate-event bool Navigation::FirePushReplaceReloadNavigateEvent( JSContext* aCx, NavigationType aNavigationType, nsIURI* aDestinationURL, bool aIsSameDocument, Maybe aUserInvolvement, Element* aSourceElement, FormData* aFormDataEntryList, nsIStructuredCloneContainer* aNavigationAPIState, nsIStructuredCloneContainer* aClassicHistoryAPIState, NavigationAPIMethodTracker* aApiMethodTrackerForNavigateOrReload) { // 1. Let document be navigation's relevant global object's associated // Document. RefPtr document = GetAssociatedDocument(); // 2. Inform the navigation API about aborting navigation in document's node // navigable. InnerInformAboutAbortingNavigation(aCx); // 3. If navigation has entries and events disabled, and // apiMethodTrackerForNavigateOrReload is not null: if (HasEntriesAndEventsDisabled() && aApiMethodTrackerForNavigateOrReload) { // 3.1. Set apiMethodTrackerForNavigateOrReload's pending to false. aApiMethodTrackerForNavigateOrReload->MarkAsNotPending(); aApiMethodTrackerForNavigateOrReload = nullptr; } // 4. If document is not fully active, then return false. if (!document || !document->IsFullyActive()) { return false; } // To not unnecessarily create an event that's never used, step 5 and step 6 // in #fire-a-push/replace/reload-navigate-event have been moved to after step // 23 in #inner-navigate-event-firing-algorithm in our implementation. // Step 7 to step 11 RefPtr destination = MakeAndAddRef(GetOwnerGlobal(), aDestinationURL, /* aEntry */ nullptr, /* aState */ aNavigationAPIState, aIsSameDocument); // Step 8 return InnerFireNavigateEvent( aCx, aNavigationType, destination, aUserInvolvement.valueOr(UserNavigationInvolvement::None), aSourceElement, aFormDataEntryList, aClassicHistoryAPIState, /* aDownloadRequestFilename */ VoidString(), aApiMethodTrackerForNavigateOrReload); } // https://html.spec.whatwg.org/#fire-a-download-request-navigate-event bool Navigation::FireDownloadRequestNavigateEvent( JSContext* aCx, nsIURI* aDestinationURL, UserNavigationInvolvement aUserInvolvement, Element* aSourceElement, const nsAString& aFilename) { // To not unnecessarily create an event that's never used, step 1 and step 2 // in #fire-a-download-request-navigate-event have been moved to after step // 25 in #inner-navigate-event-firing-algorithm in our implementation. // Work around for https://github.com/whatwg/html/issues/11802 InnerInformAboutAbortingNavigation(aCx); // Step 3 to step 7 RefPtr destination = MakeAndAddRef(GetOwnerGlobal(), aDestinationURL, /* aEntry */ nullptr, /* aState */ nullptr, /* aIsSameDocument */ false); // Step 8 return InnerFireNavigateEvent( aCx, NavigationType::Push, destination, aUserInvolvement, aSourceElement, /* aFormDataEntryList */ nullptr, /* aClassicHistoryAPIState */ nullptr, aFilename); } static bool HasHistoryActionActivation( Maybe aRelevantGlobalObject) { return aRelevantGlobalObject .map([](auto& aRelevantGlobalObject) { WindowContext* windowContext = aRelevantGlobalObject.GetWindowContext(); return windowContext && windowContext->HasValidHistoryActivation(); }) .valueOr(false); } static void ConsumeHistoryActionUserActivation( Maybe aRelevantGlobalObject) { aRelevantGlobalObject.apply([](auto& aRelevantGlobalObject) { if (WindowContext* windowContext = aRelevantGlobalObject.GetWindowContext()) { windowContext->ConsumeHistoryActivation(); } }); } // Implementation of this will be done in Bug 1948593. static bool HasUAVisualTransition(Maybe) { return false; } static bool EqualsExceptRef(nsIURI* aURI, nsIURI* aOtherURI) { bool equalsExceptRef = false; return aURI && aOtherURI && NS_SUCCEEDED(aURI->EqualsExceptRef(aOtherURI, &equalsExceptRef)) && equalsExceptRef; } static bool Equals(nsIURI* aURI, nsIURI* aOtherURI) { bool equals = false; return aURI && aOtherURI && NS_SUCCEEDED(aURI->Equals(aOtherURI, &equals)) && equals; } static bool HasRef(nsIURI* aURI) { bool hasRef = false; aURI->GetHasRef(&hasRef); return hasRef; } static bool HasIdenticalFragment(nsIURI* aURI, nsIURI* aOtherURI) { nsAutoCString ref; if (HasRef(aURI) != HasRef(aOtherURI)) { return false; } if (NS_FAILED(aURI->GetRef(ref))) { return false; } nsAutoCString otherRef; if (NS_FAILED(aOtherURI->GetRef(otherRef))) { return false; } return ref.Equals(otherRef); } static void LogEvent(Event* aEvent, NavigateEvent* aOngoingEvent, const nsACString& aReason) { if (!MOZ_LOG_TEST(gNavigationAPILog, LogLevel::Debug)) { return; } nsAutoString eventType; aEvent->GetType(eventType); nsTArray log = {nsCString(aReason), NS_ConvertUTF16toUTF8(eventType)}; if (aEvent->Cancelable()) { log.AppendElement("cancelable"); } if (aOngoingEvent) { log.AppendElement( fmt::format(FMT_STRING("{}"), aOngoingEvent->NavigationType())); if (RefPtr destination = aOngoingEvent->Destination()) { log.AppendElement(destination->GetURL()->GetSpecOrDefault()); } if (aOngoingEvent->HashChange()) { log.AppendElement("hashchange"_ns); } } LOG_FMTD("{}", fmt::join(log.begin(), log.end(), std::string_view{" "})); } nsresult Navigation::FireEvent(const nsAString& aName) { RefPtr event = NS_NewDOMEvent(this, nullptr, nullptr); // it doesn't bubble, and it isn't cancelable event->InitEvent(aName, false, false); event->SetTrusted(true); ErrorResult rv; LogEvent(event, mOngoingNavigateEvent, "Fire"_ns); DispatchEvent(*event, rv); return rv.StealNSResult(); } static void ExtractErrorInformation(JSContext* aCx, JS::Handle aError, ErrorEventInit& aErrorEventInitDict, NavigateEvent* aEvent) { nsContentUtils::ExtractErrorValues( aCx, aError, aErrorEventInitDict.mFilename, &aErrorEventInitDict.mLineno, &aErrorEventInitDict.mColno, aErrorEventInitDict.mMessage); aErrorEventInitDict.mError = aError; aErrorEventInitDict.mBubbles = false; aErrorEventInitDict.mCancelable = false; if (!aErrorEventInitDict.mFilename.IsEmpty()) { return; } RefPtr document = aEvent->GetAssociatedDocument(); if (!document) { return; } if (auto* uri = document->GetDocumentURI()) { uri->GetSpec(aErrorEventInitDict.mFilename); } } nsresult Navigation::FireErrorEvent(const nsAString& aName, const ErrorEventInit& aEventInitDict) { RefPtr event = ErrorEvent::Constructor(this, aName, aEventInitDict); ErrorResult rv; LogEvent(event, mOngoingNavigateEvent, "Fire"_ns); DispatchEvent(*event, rv); return rv.StealNSResult(); } // https://html.spec.whatwg.org/#resume-applying-the-traverse-history-step static void ResumeApplyTheHistoryStep( SessionHistoryInfo* aTarget, BrowsingContext* aTraversable, UserNavigationInvolvement aUserInvolvement) { MOZ_DIAGNOSTIC_ASSERT(aTraversable->IsTop()); auto* childSHistory = aTraversable->GetChildSessionHistory(); // Since we've already called #checking-if-unloading-is-canceled, we here pass // checkForCancelation set to false. childSHistory->AsyncGo(aTarget->NavigationKey(), aTraversable, /* aRequireUserInteraction */ false, /* aUserActivation */ false, /* aCheckForCancelation */ false, [](auto) {}); } struct NavigationWaitForAllScope final : public nsISupports, public SupportsWeakPtr { NavigationWaitForAllScope(Navigation* aNavigation, NavigationAPIMethodTracker* aApiMethodTracker, NavigateEvent* aEvent, NavigationDestination* aDestination) : mNavigation(aNavigation), mAPIMethodTracker(aApiMethodTracker), mEvent(aEvent), mDestination(aDestination) {} NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(NavigationWaitForAllScope) RefPtr mNavigation; RefPtr mAPIMethodTracker; RefPtr mEvent; RefPtr mDestination; private: ~NavigationWaitForAllScope() {} public: // https://html.spec.whatwg.org/#process-navigate-event-handler-failure MOZ_CAN_RUN_SCRIPT void ProcessNavigateEventHandlerFailure( JS::Handle aRejectionReason) { // To process navigate event handler failure given a NavigateEvent object // event and a reason: LogEvent(mEvent, mEvent, "Rejected"_ns); // 1. If event's relevant global object's associated Document is not fully // active, then return. if (RefPtr document = mEvent->GetDocument(); !document || !document->IsFullyActive()) { return; } // 2. If event's abort controller's signal is aborted, then return. if (AbortSignal* signal = mEvent->Signal(); signal->Aborted()) { return; } // 3. Assert: event is event's relevant global object's navigation API's // ongoing navigate event. MOZ_DIAGNOSTIC_ASSERT(mEvent == mNavigation->mOngoingNavigateEvent); // 4. If event's interception state is not "intercepted", then finish event // given false. RefPtr event = mEvent; if (mEvent->InterceptionState() != NavigateEvent::InterceptionState::Intercepted) { event->Finish(false); } // 5. Abort event given reason. if (AutoJSAPI jsapi; !NS_WARN_IF(!jsapi.Init(mEvent->GetParentObject()))) { RefPtr navigation = mNavigation; navigation->AbortNavigateEvent(jsapi.cx(), event, aRejectionReason); } } // https://html.spec.whatwg.org/#commit-a-navigate-event MOZ_CAN_RUN_SCRIPT void CommitNavigateEvent(NavigationType aNavigationType) { // 1. Let navigation be event's target. // Omitted since Navigation is part of this's state. // 3. If event's relevant global object's associated Document is not fully // active, then return. RefPtr document = mEvent->GetDocument(); if (!document || !document->IsFullyActive()) { return; } // 2. Let navigable be event's relevant global object's navigable. nsDocShell* docShell = nsDocShell::Cast(document->GetDocShell()); Maybe navigable = ToMaybeRef(mNavigation->GetOwnerWindow()).andThen([](auto& aWindow) { return ToMaybeRef(aWindow.GetBrowsingContext()); }); // 4. If event's abort controller's signal is aborted, then return. if (AbortSignal* signal = mEvent->Signal(); signal->Aborted()) { return; } // 6. Let endResultIsSameDocument be true if event's interception state is // not "none" or event's destination's is same document is true. const bool endResultIsSameDocument = mEvent->InterceptionState() != NavigateEvent::InterceptionState::None || mDestination->SameDocument(); // 7. Prepare to run script given navigation's relevant settings object. // This runs step 12 when going out of scope. nsAutoMicroTask mt; // 9. If event's interception state is not "none": if (mEvent->InterceptionState() != NavigateEvent::InterceptionState::None) { // The copy of the active session history info might be stale at this // point, so make sure to update that. This is not a spec step, but a side // effect of SHIP owning the session history entries making Navigation API // keep copies for its purposes. Should navigation get aborted at this // point, all we've done is eagerly stored scroll positions. if (RefPtr current = mNavigation->GetCurrentEntry()) { nsPoint scrollPos = docShell->GetCurScrollPos(); current->SessionHistoryInfo()->SetScrollPosition(scrollPos.x, scrollPos.y); } // 5. Set event's interception state to "committed". // See https://github.com/whatwg/html/issues/11830 for this change. mEvent->SetInterceptionState(NavigateEvent::InterceptionState::Committed); // 9.1 Switch on event's navigationType: switch (aNavigationType) { case NavigationType::Push: case NavigationType::Replace: // Run the URL and history update steps given event's relevant // global object's associated Document and event's destination's // URL, with serializedData set to event's classic history API // state and historyHandling set to event's navigationType. if (docShell) { docShell->UpdateURLAndHistory( document, mDestination->GetURL(), mEvent->ClassicHistoryAPIState(), *NavigationUtils::NavigationHistoryBehavior(aNavigationType), document->GetDocumentURI(), Equals(mDestination->GetURL(), document->GetDocumentURI())); } break; case NavigationType::Reload: // Update the navigation API entries for a same-document navigation // given navigation, navigable's active session history entry, and // "reload". if (docShell) { mNavigation->UpdateEntriesForSameDocumentNavigation( docShell->GetActiveSessionHistoryInfo(), aNavigationType); } break; case NavigationType::Traverse: if (auto* entry = mDestination->GetEntry()) { // 1. Set navigation's suppress normal scroll restoration during // ongoing navigation to true. mNavigation ->mSuppressNormalScrollRestorationDuringOngoingNavigation = true; // 2. Let userInvolvement be "none". // 3. If event's userInitiated is true, then set userInvolvement to // "activation". UserNavigationInvolvement userInvolvement = mEvent->UserInitiated() ? UserNavigationInvolvement::Activation : UserNavigationInvolvement::None; // 4. Append the following session history traversal steps to // navigable's traversable navigable: // 4.1 Resume applying the traverse history step given event's // destination's entry's session history entry's step, // navigable's traversable navigable, and userInvolvement. ResumeApplyTheHistoryStep(entry->SessionHistoryInfo(), navigable->Top(), userInvolvement); // This is not in the spec, but both Chrome and Safari does this or // something similar. MOZ_ASSERT(entry->Index() >= 0); mNavigation->SetCurrentEntryIndex(entry->SessionHistoryInfo()); } break; default: break; } } // 8. If navigation's transition is not null, then resolve navigation's // transition's committed promise with undefined. // Steps 8 and 9 are swapped to have a consistent promise behavior // (see https://github.com/whatwg/html/issues/11842) if (mNavigation->mTransition) { mNavigation->mTransition->Committed()->MaybeResolveWithUndefined(); } // 10. If endResultIsSameDocument is true: if (endResultIsSameDocument) { // 10.1 Let promisesList be an empty list. AutoTArray, 16> promiseList; if (StaticPrefs::dom_navigation_api_internal_method_tracker()) { promiseList.AppendElement(mAPIMethodTracker->CommittedPromise()); } // 10.2 For each handler of event's navigation handler list: for (auto& handler : mEvent->NavigationHandlerList().Clone()) { // 10.2.1 Append the result of invoking handler with an empty // arguments list to promisesList. RefPtr promise = MOZ_KnownLive(handler)->Call(); if (promise) { promiseList.AppendElement(promise); } } // 10.3 If promisesList's size is 0, then set promisesList to « a promise // resolved with undefined ». nsCOMPtr globalObject = mNavigation->GetOwnerGlobal(); if (promiseList.IsEmpty()) { RefPtr promise = Promise::CreateResolvedWithUndefined( globalObject, IgnoredErrorResult()); if (promise) { promiseList.AppendElement(promise); } } // 10.4 Wait for all of promisesList, with the following success steps: // If the committed promise in the api method tracker hasn't resolved yet, // we can't run neither of the success nor failure steps. To handle that // we set up a callback for when that resolves. This differs from how spec // performs these steps, since spec can perform more of // #apply-the-history-steps in a synchronous way. auto cancelSteps = [weakScope = WeakPtr(this)](JS::Handle aRejectionReason) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { // If weakScope is null we've been cycle collected if (weakScope) { RefPtr scope = weakScope.get(); scope->ProcessNavigateEventHandlerFailure(aRejectionReason); } }; auto successSteps = [weakScope = WeakPtr(this)](const Span>&) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { // If weakScope is null we've been cycle collected if (weakScope) { RefPtr scope = weakScope.get(); scope->CommitNavigateEventSuccessSteps(); } }; if (mAPIMethodTracker && !StaticPrefs::dom_navigation_api_internal_method_tracker()) { // Promise::WaitForAll marks all promises as handled, but since we're // delaying wait for all one microtask, we need to manually mark them // here. for (auto& promise : promiseList) { (void)promise->SetAnyPromiseIsHandled(); } LOG_FMTD("Waiting for committed"); mAPIMethodTracker->CommittedPromise() ->AddCallbacksWithCycleCollectedArgs( [successSteps, cancelSteps]( JSContext*, JS::Handle, ErrorResult&, nsIGlobalObject* aGlobalObject, const Span>& aPromiseList, NavigationWaitForAllScope* aScope) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { Promise::WaitForAll(aGlobalObject, aPromiseList, successSteps, cancelSteps, aScope); }, [](JSContext*, JS::Handle, ErrorResult&, nsIGlobalObject*, const Span>&, NavigationWaitForAllScope*) {}, nsCOMPtr(globalObject), nsTArray>(std::move(promiseList)), RefPtr(this)); } else { LOG_FMTD("No API method tracker, not waiting for committed"); // If we don't have an apiMethodTracker we can immediately start waiting // for the promise list. Promise::WaitForAll(globalObject, promiseList, successSteps, cancelSteps, this); } } else if (mAPIMethodTracker && mNavigation->mOngoingAPIMethodTracker) { // In contrast to spec we add a check that we're still the ongoing // tracker. If we're not, then we've already been cleaned up. MOZ_DIAGNOSTIC_ASSERT(mAPIMethodTracker == mNavigation->mOngoingAPIMethodTracker); // Step 11 mAPIMethodTracker->CleanUp(); mNavigation->mOngoingNavigateEvent = nullptr; } else { // It needs to be ensured that the ongoing navigate event is cleared in // every code path (e.g. for download events), so that we don't keep // intermediate state around. // See also https://github.com/whatwg/html/issues/11802 mNavigation->mOngoingNavigateEvent = nullptr; } } MOZ_CAN_RUN_SCRIPT void CommitNavigateEventSuccessSteps() { LogEvent(mEvent, mEvent, "Success"_ns); // 1. If event's relevant global object is not fully active, then abort // these steps. RefPtr document = mEvent->GetDocument(); if (!document || !document->IsFullyActive()) { return; } // 2. If event's abort controller's signal is aborted, then abort these // steps. if (AbortSignal* signal = mEvent->Signal(); signal->Aborted()) { return; } // 3. Assert: event equals navigation's ongoing navigate event. MOZ_DIAGNOSTIC_ASSERT(mEvent == mNavigation->mOngoingNavigateEvent); // 4. Set navigation's ongoing navigate event to null. mNavigation->mOngoingNavigateEvent = nullptr; // 5. Finish event given true. RefPtr event = mEvent; event->Finish(true); // 6. If apiMethodTracker is non-null, then resolve the finished promise for // apiMethodTracker. if (mAPIMethodTracker) { mAPIMethodTracker->ResolveFinishedPromise(); } // 7. Fire an event named navigatesuccess at navigation. RefPtr navigation = mNavigation; navigation->FireEvent(u"navigatesuccess"_ns); // 8. If navigation's transition is not null, then resolve navigation's // transition's finished promise with undefined. if (mNavigation->mTransition) { mNavigation->mTransition->Finished()->MaybeResolveWithUndefined(); } // 9. Set navigation's transition to null. mNavigation->mTransition = nullptr; } }; NS_IMPL_CYCLE_COLLECTION_WEAK_PTR(NavigationWaitForAllScope, mNavigation, mAPIMethodTracker, mEvent, mDestination) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NavigationWaitForAllScope) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(NavigationWaitForAllScope) NS_IMPL_CYCLE_COLLECTING_RELEASE(NavigationWaitForAllScope) already_AddRefed CreateInternalTracker( Navigation* aNavigation) { RefPtr committedPromise = Promise::CreateInfallible(aNavigation->GetOwnerGlobal()); (void)committedPromise->SetAnyPromiseIsHandled(); RefPtr finishedPromise = Promise::CreateResolvedWithUndefined( aNavigation->GetOwnerGlobal(), IgnoreErrors()); return MakeAndAddRef( aNavigation, Nothing(), JS::UndefinedHandleValue, /* aSerializedState */ nullptr, /* aCommittedToEntry */ nullptr, committedPromise, finishedPromise); } // https://html.spec.whatwg.org/#inner-navigate-event-firing-algorithm bool Navigation::InnerFireNavigateEvent( JSContext* aCx, NavigationType aNavigationType, NavigationDestination* aDestination, UserNavigationInvolvement aUserInvolvement, Element* aSourceElement, FormData* aFormDataEntryList, nsIStructuredCloneContainer* aClassicHistoryAPIState, const nsAString& aDownloadRequestFilename, NavigationAPIMethodTracker* aNavigationAPIMethodTracker) { nsCOMPtr globalObject = GetOwnerGlobal(); RefPtr apiMethodTracker = aNavigationAPIMethodTracker; // Step 1 if (HasEntriesAndEventsDisabled()) { // Step 1.1 to step 1.3 MOZ_DIAGNOSTIC_ASSERT(!mOngoingAPIMethodTracker); MOZ_DIAGNOSTIC_ASSERT(mUpcomingTraverseAPIMethodTrackers.IsEmpty()); MOZ_DIAGNOSTIC_ASSERT(!aNavigationAPIMethodTracker); // Step 1.5 return true; } RootedDictionary init(RootingCx()); // Step 2 MOZ_DIAGNOSTIC_ASSERT(!mOngoingAPIMethodTracker); // Step 3 Maybe destinationKey; if (auto* destinationEntry = aDestination->GetEntry()) { // Step 3.1 MOZ_DIAGNOSTIC_ASSERT(!aNavigationAPIMethodTracker); // Step 3.2 destinationKey.emplace(destinationEntry->Key()); // Step 3.3 MOZ_DIAGNOSTIC_ASSERT(!destinationKey->Equals(nsID{})); // Step 3.4, 3.4.2 if (auto entry = mUpcomingTraverseAPIMethodTrackers.Extract(*destinationKey)) { // Step 3.4.1 apiMethodTracker = std::move(*entry); } } // Step 4 if (apiMethodTracker) { apiMethodTracker->MarkAsNotPending(); } else if (StaticPrefs::dom_navigation_api_internal_method_tracker()) { apiMethodTracker = CreateInternalTracker(this); } // This step is currently missing in the spec. See // https://github.com/whatwg/html/issues/11816 mOngoingAPIMethodTracker = apiMethodTracker; // Step 5 Maybe navigable = ToMaybeRef(GetOwnerWindow()).andThen([](auto& aWindow) { return ToMaybeRef(aWindow.GetBrowsingContext()); }); // Step 6 Document* document = navigable.map([](auto& aNavigable) { return aNavigable.GetDocument(); }) .valueOr(nullptr); // Step7 init.mCanIntercept = document && document->CanRewriteURL(aDestination->GetURL(), /*aReportErrors*/ false) && (aDestination->SameDocument() || aNavigationType != NavigationType::Traverse); // Step 8 bool traverseCanBeCanceled = navigable->IsTop() && aDestination->SameDocument() && (aUserInvolvement != UserNavigationInvolvement::BrowserUI || HasHistoryActionActivation(ToMaybeRef(GetOwnerWindow()))); // Step 9 init.mCancelable = aNavigationType != NavigationType::Traverse || traverseCanBeCanceled; // Step 11 init.mNavigationType = aNavigationType; // Step 12 init.mDestination = aDestination; // Step 13 init.mDownloadRequest = aDownloadRequestFilename; // Step 14 if (apiMethodTracker) { init.mInfo = apiMethodTracker->mInfo; } // Step 15 init.mHasUAVisualTransition = HasUAVisualTransition(ToMaybeRef(GetAssociatedDocument())); // Step 16 init.mSourceElement = aSourceElement; // Step 17 RefPtr abortController = new AbortController(globalObject); // Step 18 init.mSignal = abortController->Signal(); // step 19 nsCOMPtr currentURL = document->GetDocumentURI(); // step 20 init.mHashChange = !aClassicHistoryAPIState && aDestination->SameDocument() && EqualsExceptRef(aDestination->GetURL(), currentURL) && !HasIdenticalFragment(aDestination->GetURL(), currentURL); // Step 21 init.mUserInitiated = aUserInvolvement != UserNavigationInvolvement::None; // Step 22 init.mFormData = aFormDataEntryList; // Step 23 MOZ_DIAGNOSTIC_ASSERT(!mOngoingNavigateEvent); // We now have everything we need to fully initialize the NavigateEvent, so // we'll go ahead and create it now. This is done by the spec in step 1 and // step 2 of #fire-a-traverse-navigate-event, // #fire-a-push/replace/reload-navigate-event, or // #fire-a-download-request-navigate-event, but there's no reason to not // delay it until here. This also performs step 12. RefPtr event = NavigateEvent::Constructor( this, u"navigate"_ns, init, aClassicHistoryAPIState, abortController); // Here we're running #concept-event-create from https://dom.spec.whatwg.org/ // which explicitly sets event's isTrusted attribute to true. event->SetTrusted(true); // Step 24 mOngoingNavigateEvent = event; // Step 25 mFocusChangedDuringOngoingNavigation = false; // Step 26 mSuppressNormalScrollRestorationDuringOngoingNavigation = false; // Step 27 and step 28 LogEvent(event, mOngoingNavigateEvent, "Fire"_ns); if (!DispatchEvent(*event, CallerType::NonSystem, IgnoreErrors())) { // Step 28.1 if (aNavigationType == NavigationType::Traverse) { ConsumeHistoryActionUserActivation(ToMaybeRef(GetOwnerWindow())); } // Step 28.2 if (!abortController->Signal()->Aborted()) { AbortOngoingNavigation(aCx); } // Step 28.3 return false; } // Step 29 if (event->InterceptionState() != NavigateEvent::InterceptionState::None) { // Step 29.1 RefPtr fromNHE = GetCurrentEntry(); // Step 29.2 MOZ_DIAGNOSTIC_ASSERT(fromNHE); // Step 29.3 RefPtr committedPromise = Promise::CreateInfallible(globalObject); RefPtr finishedPromise = Promise::CreateInfallible(globalObject); mTransition = MakeAndAddRef( globalObject, aNavigationType, fromNHE, committedPromise, finishedPromise); // Step 29.4 MOZ_ALWAYS_TRUE(committedPromise->SetAnyPromiseIsHandled()); // Step 29.5 MOZ_ALWAYS_TRUE(finishedPromise->SetAnyPromiseIsHandled()); } RefPtr scope = MakeRefPtr(this, apiMethodTracker, event, aDestination); // Step 30 if (event->NavigationPrecommitHandlerList().IsEmpty()) { LOG_FMTD("No precommit handlers, committing directly"); scope->CommitNavigateEvent(aNavigationType); } else { LOG_FMTD("Running {} precommit handlers", event->NavigationPrecommitHandlerList().Length()); // Step 31.1 RefPtr precommitController = new NavigationPrecommitController(event, globalObject); // Step 31.2 nsTArray> precommitPromiseList; // Step 31.3 for (auto& handler : event->NavigationPrecommitHandlerList().Clone()) { // Step 31.3.1 RefPtr promise = MOZ_KnownLive(handler)->Call(*precommitController); if (promise) { precommitPromiseList.AppendElement(promise); } } // Step 31.4 Promise::WaitForAll( globalObject, precommitPromiseList, [weakScope = WeakPtr(scope), aNavigationType](const Span>&) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { // If weakScope is null we've been cycle collected if (!weakScope) { return; } RefPtr scope = weakScope.get(); scope->CommitNavigateEvent(aNavigationType); }, [weakScope = WeakPtr(scope)](JS::Handle aRejectionReason) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { // If weakScope is null we've been cycle collected if (!weakScope) { return; } RefPtr scope = weakScope.get(); scope->ProcessNavigateEventHandlerFailure(aRejectionReason); }, scope); } // Step 32 and 33 return event->InterceptionState() == NavigateEvent::InterceptionState::None; } NavigationHistoryEntry* Navigation::FindNavigationHistoryEntry( const SessionHistoryInfo& aSessionHistoryInfo) const { for (const auto& navigationHistoryEntry : mEntries) { if (navigationHistoryEntry->IsSameEntry(&aSessionHistoryInfo)) { return navigationHistoryEntry; } } return nullptr; } // https://html.spec.whatwg.org/#navigation-api-method-tracker-clean-up /* static */ void Navigation::CleanUp( NavigationAPIMethodTracker* aNavigationAPIMethodTracker) { // Step 1 RefPtr navigation = aNavigationAPIMethodTracker->mNavigationObject; auto needsTraverse = MakeScopeExit([navigation]() { navigation->UpdateNeedsTraverse(); }); // Step 2 if (navigation->mOngoingAPIMethodTracker == aNavigationAPIMethodTracker) { navigation->mOngoingAPIMethodTracker = nullptr; return; } // Step 3.1 Maybe key = aNavigationAPIMethodTracker->mKey; // Step 3.2 MOZ_DIAGNOSTIC_ASSERT(key); // Step 3.3 MOZ_DIAGNOSTIC_ASSERT( navigation->mUpcomingTraverseAPIMethodTrackers.Contains(*key)); navigation->mUpcomingTraverseAPIMethodTrackers.Remove(*key); } void Navigation::SetCurrentEntryIndex(const SessionHistoryInfo* aTargetInfo) { mCurrentEntryIndex.reset(); if (auto* entry = FindNavigationHistoryEntry(*aTargetInfo)) { MOZ_ASSERT(entry->Index() >= 0); mCurrentEntryIndex = Some(entry->Index()); return; } LOG_FMTW("Session history entry did not exist"); } // https://html.spec.whatwg.org/#inform-the-navigation-api-about-aborting-navigation void Navigation::InnerInformAboutAbortingNavigation(JSContext* aCx) { // As per https://github.com/whatwg/html/issues/11579, we should abort all // ongoing navigate events within "inform the navigation API about aborting // navigation". while (HasOngoingNavigateEvent()) { AbortOngoingNavigation(aCx); } } // https://html.spec.whatwg.org/#abort-the-ongoing-navigation void Navigation::AbortOngoingNavigation(JSContext* aCx, JS::Handle aError) { // Step 1 RefPtr event = mOngoingNavigateEvent; LogEvent(event, event, "Abort"_ns); // Step 2 MOZ_DIAGNOSTIC_ASSERT(event); // Step 3 mFocusChangedDuringOngoingNavigation = false; // Step 4 mSuppressNormalScrollRestorationDuringOngoingNavigation = false; JS::Rooted error(aCx, aError); // Step 5 if (aError.isUndefined()) { RefPtr exception = DOMException::Create(NS_ERROR_DOM_ABORT_ERR); // It's OK if this fails, it just means that we'll get an empty error // dictionary below. GetOrCreateDOMReflector(aCx, exception, &error); } // Step 6 if (event->IsBeingDispatched()) { // Here NonSystem is needed since it needs to be the same as what we // dispatch with. event->PreventDefault(aCx, CallerType::NonSystem); } // Step 7 AbortNavigateEvent(aCx, event, error); } // https://html.spec.whatwg.org/#abort-a-navigateevent void Navigation::AbortNavigateEvent(JSContext* aCx, NavigateEvent* aEvent, JS::Handle aReason) { // 1. Let navigation be event's relevant global object's navigation API. // Omitted since this is called from a Navigation object. // 2. Signal abort on event's abort controller given reason. aEvent->AbortController()->Abort(aCx, aReason); // 3. Let errorInfo be the result of extracting error information from reason. RootedDictionary init(aCx); ExtractErrorInformation(aCx, aReason, init, aEvent); // 4. Set navigation's ongoing navigate event to null. mOngoingNavigateEvent = nullptr; // 5. If navigation's ongoing API method tracker is non-null, then reject the // finished promise for apiMethodTracker with error. if (mOngoingAPIMethodTracker) { mOngoingAPIMethodTracker->RejectFinishedPromise(aReason); } // 6. Fire an event named navigateerror at navigation using ErrorEvent, with // additional attributes initialized according to errorInfo. FireErrorEvent(u"navigateerror"_ns, init); // 7. If navigation's transition is null, then return. if (!mTransition) { return; } // 8. Reject navigation's transition's committed promise with error. mTransition->Committed()->MaybeReject(aReason); // 9. Reject navigation's transition's finished promise with error. mTransition->Finished()->MaybeReject(aReason); // 10. Set navigation's transition to null. mTransition = nullptr; } // https://html.spec.whatwg.org/#inform-the-navigation-api-about-child-navigable-destruction void Navigation::InformAboutChildNavigableDestruction(JSContext* aCx) { // Step 3 auto traversalAPIMethodTrackers = mUpcomingTraverseAPIMethodTrackers.Clone(); // Step 4 for (auto& apiMethodTracker : traversalAPIMethodTrackers.Values()) { ErrorResult rv; rv.ThrowAbortError("Navigable removed"); JS::Rooted rootedExceptionValue(aCx); MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &rootedExceptionValue)); apiMethodTracker->RejectFinishedPromise(rootedExceptionValue); } } bool Navigation::FocusedChangedDuringOngoingNavigation() const { return mFocusChangedDuringOngoingNavigation; } void Navigation::SetFocusedChangedDuringOngoingNavigation( bool aFocusChangedDUringOngoingNavigation) { mFocusChangedDuringOngoingNavigation = aFocusChangedDUringOngoingNavigation; } bool Navigation::HasOngoingNavigateEvent() const { return mOngoingNavigateEvent; } // The associated document of navigation's relevant global object. Document* Navigation::GetAssociatedDocument() const { nsGlobalWindowInner* window = GetOwnerWindow(); return window ? window->GetDocument() : nullptr; } void Navigation::UpdateNeedsTraverse() { nsGlobalWindowInner* innerWindow = GetOwnerWindow(); if (!innerWindow) { return; } WindowContext* windowContext = innerWindow->GetWindowContext(); if (!windowContext) { return; } // Since we only care about optimizing for the traversable, bail if we're not // the top-level context. if (BrowsingContext* browsingContext = innerWindow->GetBrowsingContext(); !browsingContext || !browsingContext->IsTop()) { return; } // We need traverse if we have any method tracker. bool needsTraverse = mOngoingAPIMethodTracker || !mUpcomingTraverseAPIMethodTrackers.IsEmpty(); // We need traverse if we have any event handlers. if (EventListenerManager* eventListenerManager = GetExistingListenerManager()) { needsTraverse = needsTraverse || eventListenerManager->HasListeners(); } // Don't toggle if nothing's changed. if (windowContext->GetNeedsTraverse() == needsTraverse) { return; } (void)windowContext->SetNeedsTraverse(needsTraverse); } void Navigation::LogHistory() const { if (!MOZ_LOG_TEST(gNavigationAPILog, LogLevel::Debug)) { return; } MOZ_LOG(gNavigationAPILog, 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); } } // https://html.spec.whatwg.org/#set-up-a-navigate/reload-api-method-tracker RefPtr Navigation::SetUpNavigateReloadAPIMethodTracker( JS::Handle aInfo, nsIStructuredCloneContainer* aSerializedState) { // To set up a navigate/reload API method tracker given a Navigation // navigation, a JavaScript value info, and a serialized state-or-null // serializedState: // 1. Let committedPromise and finishedPromise be new promises created in // navigation's relevant realm. RefPtr committedPromise = Promise::CreateInfallible(GetOwnerGlobal()); RefPtr finishedPromise = Promise::CreateInfallible(GetOwnerGlobal()); // 2. Mark as handled finishedPromise. MOZ_ALWAYS_TRUE(finishedPromise->SetAnyPromiseIsHandled()); // 3. Return a new navigation API method tracker with: RefPtr apiMethodTracker = MakeAndAddRef( this, /* aKey */ Nothing{}, aInfo, aSerializedState, /* aCommittedToEntry */ nullptr, committedPromise, finishedPromise, /* aPending */ !HasEntriesAndEventsDisabled()); return apiMethodTracker; } // https://html.spec.whatwg.org/#add-an-upcoming-traverse-api-method-tracker RefPtr Navigation::AddUpcomingTraverseAPIMethodTracker(const nsID& aKey, JS::Handle aInfo) { // To add an upcoming traverse API method tracker given a Navigation // navigation, a string destinationKey, and a JavaScript value info: // 1. Let committedPromise and finishedPromise be new promises created in // navigation's relevant realm. RefPtr committedPromise = Promise::CreateInfallible(GetOwnerGlobal()); RefPtr finishedPromise = Promise::CreateInfallible(GetOwnerGlobal()); // 2. Mark as handled finishedPromise. MOZ_ALWAYS_TRUE(finishedPromise->SetAnyPromiseIsHandled()); // 3. Let apiMethodTracker be a new navigation API method tracker with: RefPtr apiMethodTracker = MakeAndAddRef( this, Some(aKey), aInfo, /* aSerializedState */ nullptr, /* aCommittedToEntry */ nullptr, committedPromise, finishedPromise, /* aPending */ false); // 4. Set navigation's upcoming traverse API method trackers[destinationKey] // to apiMethodTracker. RefPtr methodTracker = mUpcomingTraverseAPIMethodTrackers.InsertOrUpdate(aKey, apiMethodTracker); UpdateNeedsTraverse(); // 5. Return apiMethodTracker. return methodTracker; } // https://html.spec.whatwg.org/#update-document-for-history-step-application void Navigation::CreateNavigationActivationFrom( SessionHistoryInfo* aPreviousEntryForActivation, NavigationType aNavigationType) { // Note: we do Step 7.1 at the end of method so we can both create and // initialize the activation at once. MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug, "Creating NavigationActivation for from={}, type={}", fmt::ptr(aPreviousEntryForActivation), aNavigationType); RefPtr currentEntry = GetCurrentEntry(); if (!currentEntry) { return; } // Step 7.2. Let previousEntryIndex be the result of getting the navigation // API entry index of previousEntryForActivation within navigation. auto possiblePreviousEntry = std::find_if(mEntries.begin(), mEntries.end(), [aPreviousEntryForActivation](const auto& entry) { return entry->IsSameEntry(aPreviousEntryForActivation); }); // 3. If previousEntryIndex is non-negative, then set activation's old entry // to navigation's entry list[previousEntryIndex]. RefPtr oldEntry; if (possiblePreviousEntry != mEntries.end()) { MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug, "Found previous entry at {}", fmt::ptr(possiblePreviousEntry->get())); oldEntry = *possiblePreviousEntry; } else if (aNavigationType == NavigationType::Replace && !aPreviousEntryForActivation->IsTransient()) { // 4. Otherwise, if all the following are true: // navigationType is "replace"; // previousEntryForActivation's document state's origin is same origin // with document's origin; and previousEntryForActivation's document's // initial about:blank is false, // then set activation's old entry to a new NavigationHistoryEntry in // navigation's relevant realm, whose session history entry is // previousEntryForActivation. nsCOMPtr previousURI = aPreviousEntryForActivation->GetURIOrInheritedForAboutBlank(); nsCOMPtr currentURI = currentEntry->SessionHistoryInfo()->GetURIOrInheritedForAboutBlank(); if (NS_SUCCEEDED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI( currentURI, previousURI, false, false))) { oldEntry = MakeRefPtr( GetOwnerGlobal(), aPreviousEntryForActivation, -1); MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug, "Created a new entry at {}", fmt::ptr(oldEntry.get())); } } // 1. If navigation's activation is null, then set navigation's // activation to a new NavigationActivation object in navigation's relevant // realm. // 5. Set activation's new entry to navigation's current entry. // 6. Set activation's navigation type to navigationType. mActivation = MakeRefPtr(GetOwnerGlobal(), currentEntry, oldEntry, aNavigationType); } // https://html.spec.whatwg.org/#dom-navigationprecommitcontroller-redirect void Navigation::SetSerializedStateIntoOngoingAPIMethodTracker( nsIStructuredCloneContainer* aSerializedState) { MOZ_DIAGNOSTIC_ASSERT(mOngoingAPIMethodTracker); // This is step 10.3 of NavigationPrecommitController.redirect() mOngoingAPIMethodTracker->SetSerializedState(aSerializedState); } } // namespace mozilla::dom #undef LOG_FMTV #undef LOG_FMTD #undef LOG_FMTI #undef LOG_FMTW #undef LOG_FMTE