tor-browser

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

Navigation.cpp (82553B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/Navigation.h"
      8 
      9 #include "NavigationPrecommitController.h"
     10 #include "fmt/format.h"
     11 #include "jsapi.h"
     12 #include "mozilla/CycleCollectedJSContext.h"
     13 #include "mozilla/CycleCollectedUniquePtr.h"
     14 #include "mozilla/HoldDropJSObjects.h"
     15 #include "mozilla/Logging.h"
     16 #include "mozilla/StaticPrefs_dom.h"
     17 #include "mozilla/dom/DOMException.h"
     18 #include "mozilla/dom/Document.h"
     19 #include "mozilla/dom/ErrorEvent.h"
     20 #include "mozilla/dom/Event.h"
     21 #include "mozilla/dom/FeaturePolicy.h"
     22 #include "mozilla/dom/NavigationActivation.h"
     23 #include "mozilla/dom/NavigationBinding.h"
     24 #include "mozilla/dom/NavigationCurrentEntryChangeEvent.h"
     25 #include "mozilla/dom/NavigationHistoryEntry.h"
     26 #include "mozilla/dom/NavigationTransition.h"
     27 #include "mozilla/dom/NavigationUtils.h"
     28 #include "mozilla/dom/Promise-inl.h"
     29 #include "mozilla/dom/Promise.h"
     30 #include "mozilla/dom/RootedDictionary.h"
     31 #include "mozilla/dom/SessionHistoryEntry.h"
     32 #include "mozilla/dom/WindowContext.h"
     33 #include "mozilla/dom/WindowGlobalChild.h"
     34 #include "nsContentUtils.h"
     35 #include "nsCycleCollectionParticipant.h"
     36 #include "nsDocShell.h"
     37 #include "nsGlobalWindowInner.h"
     38 #include "nsIMultiPartChannel.h"
     39 #include "nsIPrincipal.h"
     40 #include "nsISHistory.h"
     41 #include "nsIScriptChannel.h"
     42 #include "nsIStructuredCloneContainer.h"
     43 #include "nsIXULRuntime.h"
     44 #include "nsNetUtil.h"
     45 #include "nsTHashtable.h"
     46 
     47 mozilla::LazyLogModule gNavigationAPILog("NavigationAPI");
     48 
     49 #define LOG_FMTE(format, ...) \
     50  MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Error, format, ##__VA_ARGS__);
     51 
     52 #define LOG_FMTW(format, ...) \
     53  MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Warning, format, ##__VA_ARGS__);
     54 
     55 #define LOG_FMTI(format, ...) \
     56  MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Info, format, ##__VA_ARGS__);
     57 
     58 #define LOG_FMTD(format, ...) \
     59  MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug, format, ##__VA_ARGS__);
     60 
     61 #define LOG_FMTV(format, ...) \
     62  MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Verbose, format, ##__VA_ARGS__);
     63 
     64 namespace mozilla::dom {
     65 
     66 static void InitNavigationResult(NavigationResult& aResult,
     67                                 const RefPtr<Promise>& aCommitted,
     68                                 const RefPtr<Promise>& aFinished) {
     69  if (aCommitted) {
     70    aResult.mCommitted.Reset();
     71    aResult.mCommitted.Construct(*aCommitted);
     72  }
     73 
     74  if (aFinished) {
     75    aResult.mFinished.Reset();
     76    aResult.mFinished.Construct(*aFinished);
     77  }
     78 }
     79 
     80 NavigationAPIMethodTracker::NavigationAPIMethodTracker(
     81    Navigation* aNavigationObject, const Maybe<nsID> aKey,
     82    const JS::Value& aInfo, nsIStructuredCloneContainer* aSerializedState,
     83    NavigationHistoryEntry* aCommittedToEntry, Promise* aCommittedPromise,
     84    Promise* aFinishedPromise, bool aPending)
     85    : mNavigationObject(aNavigationObject),
     86      mKey(aKey),
     87      mInfo(aInfo),
     88      mPending(aPending),
     89      mSerializedState(aSerializedState),
     90      mCommittedToEntry(aCommittedToEntry),
     91      mCommittedPromise(aCommittedPromise),
     92      mFinishedPromise(aFinishedPromise) {
     93  mozilla::HoldJSObjects(this);
     94 }
     95 
     96 NavigationAPIMethodTracker::~NavigationAPIMethodTracker() {
     97  mozilla::DropJSObjects(this);
     98 }
     99 
    100 // https://html.spec.whatwg.org/#navigation-api-method-tracker-clean-up
    101 void NavigationAPIMethodTracker::CleanUp() { Navigation::CleanUp(this); }
    102 
    103 // https://html.spec.whatwg.org/#notify-about-the-committed-to-entry
    104 void NavigationAPIMethodTracker::NotifyAboutCommittedToEntry(
    105    NavigationHistoryEntry* aNHE) {
    106  MOZ_DIAGNOSTIC_ASSERT(mCommittedPromise);
    107  // Step 1
    108  mCommittedToEntry = aNHE;
    109  if (mSerializedState) {
    110    // Step 2
    111    aNHE->SetNavigationAPIState(mSerializedState);
    112    // At this point, apiMethodTracker's serialized state is no longer needed.
    113    // We drop it do now for efficiency.
    114    mSerializedState = nullptr;
    115  }
    116  mCommittedPromise->MaybeResolve(aNHE);
    117 }
    118 
    119 // https://html.spec.whatwg.org/#resolve-the-finished-promise
    120 void NavigationAPIMethodTracker::ResolveFinishedPromise() {
    121  MOZ_DIAGNOSTIC_ASSERT(mFinishedPromise);
    122  // Step 1
    123  MOZ_DIAGNOSTIC_ASSERT(mCommittedToEntry);
    124  // Step 2
    125  mFinishedPromise->MaybeResolve(mCommittedToEntry);
    126  // Step 3
    127  CleanUp();
    128 }
    129 
    130 // https://html.spec.whatwg.org/#reject-the-finished-promise
    131 void NavigationAPIMethodTracker::RejectFinishedPromise(
    132    JS::Handle<JS::Value> aException) {
    133  MOZ_DIAGNOSTIC_ASSERT(mFinishedPromise);
    134  MOZ_DIAGNOSTIC_ASSERT(mCommittedPromise);
    135  // Step 1
    136  mCommittedPromise->MaybeReject(aException);
    137  // Step 2
    138  mFinishedPromise->MaybeReject(aException);
    139  // Step 3
    140  CleanUp();
    141 }
    142 
    143 // https://html.spec.whatwg.org/#navigation-api-method-tracker-derived-result
    144 void NavigationAPIMethodTracker::CreateResult(JSContext* aCx,
    145                                              NavigationResult& aResult) {
    146  // A navigation API method tracker-derived result for a navigation API
    147  // method tracker is a NavigationResult dictionary instance given by the
    148  // following steps:
    149  // 1. If apiMethodTracker is pending, then return an early error result for
    150  //    an "AbortError" DOMException.
    151  if (mPending) {
    152    ErrorResult rv;
    153    rv.ThrowAbortError("Navigation aborted");
    154    mNavigationObject->SetEarlyErrorResult(aCx, aResult, std::move(rv));
    155    return;
    156  }
    157  // 2. Return «[ "committed" → apiMethodTracker's committed promise,
    158  //            "finished" → apiMethodTracker's finished promise ]».
    159  InitNavigationResult(aResult, mCommittedPromise, mFinishedPromise);
    160 }
    161 
    162 NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(NavigationAPIMethodTracker,
    163                                         (mNavigationObject, mSerializedState,
    164                                          mCommittedToEntry, mCommittedPromise,
    165                                          mFinishedPromise),
    166                                         (mInfo))
    167 
    168 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NavigationAPIMethodTracker)
    169  NS_INTERFACE_MAP_ENTRY(nsISupports)
    170 NS_INTERFACE_MAP_END
    171 
    172 NS_IMPL_CYCLE_COLLECTING_ADDREF(NavigationAPIMethodTracker)
    173 NS_IMPL_CYCLE_COLLECTING_RELEASE(NavigationAPIMethodTracker)
    174 
    175 NS_IMPL_CYCLE_COLLECTION_INHERITED(Navigation, DOMEventTargetHelper, mEntries,
    176                                   mOngoingNavigateEvent, mTransition,
    177                                   mActivation, mOngoingAPIMethodTracker,
    178                                   mUpcomingTraverseAPIMethodTrackers);
    179 NS_IMPL_ADDREF_INHERITED(Navigation, DOMEventTargetHelper)
    180 NS_IMPL_RELEASE_INHERITED(Navigation, DOMEventTargetHelper)
    181 
    182 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Navigation)
    183 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
    184 
    185 Navigation::Navigation(nsPIDOMWindowInner* aWindow)
    186    : DOMEventTargetHelper(aWindow) {
    187  MOZ_ASSERT(aWindow);
    188 }
    189 
    190 JSObject* Navigation::WrapObject(JSContext* aCx,
    191                                 JS::Handle<JSObject*> aGivenProto) {
    192  return Navigation_Binding::Wrap(aCx, this, aGivenProto);
    193 }
    194 
    195 void Navigation::EventListenerAdded(nsAtom* aType) {
    196  UpdateNeedsTraverse();
    197 
    198  EventTarget::EventListenerAdded(aType);
    199 }
    200 
    201 void Navigation::EventListenerRemoved(nsAtom* aType) {
    202  UpdateNeedsTraverse();
    203  EventTarget::EventListenerRemoved(aType);
    204 }
    205 
    206 /* static */
    207 bool Navigation::IsAPIEnabled(JSContext* /* unused */, JSObject* /* unused */) {
    208  return SessionHistoryInParent() &&
    209         StaticPrefs::dom_navigation_webidl_enabled_DoNotUseDirectly();
    210 }
    211 
    212 void Navigation::Entries(
    213    nsTArray<RefPtr<NavigationHistoryEntry>>& aResult) const {
    214  if (HasEntriesAndEventsDisabled()) {
    215    aResult.Clear();
    216    return;
    217  }
    218  aResult = mEntries.Clone();
    219 }
    220 
    221 already_AddRefed<NavigationHistoryEntry> Navigation::GetCurrentEntry() const {
    222  if (HasEntriesAndEventsDisabled()) {
    223    return nullptr;
    224  }
    225 
    226  if (!mCurrentEntryIndex) {
    227    return nullptr;
    228  }
    229 
    230  MOZ_LOG(gNavigationAPILog, LogLevel::Debug,
    231          ("Current Entry: %d; Amount of Entries: %d", int(*mCurrentEntryIndex),
    232           int(mEntries.Length())));
    233  MOZ_ASSERT(*mCurrentEntryIndex < mEntries.Length());
    234 
    235  RefPtr entry{mEntries[*mCurrentEntryIndex]};
    236  return entry.forget();
    237 }
    238 
    239 // https://html.spec.whatwg.org/#dom-navigation-updatecurrententry
    240 void Navigation::UpdateCurrentEntry(
    241    JSContext* aCx, const NavigationUpdateCurrentEntryOptions& aOptions,
    242    ErrorResult& aRv) {
    243  LOG_FMTI("Called navigation.updateCurrentEntry()");
    244  RefPtr currentEntry(GetCurrentEntry());
    245  if (!currentEntry) {
    246    aRv.ThrowInvalidStateError(
    247        "Can't call updateCurrentEntry without a valid entry.");
    248    return;
    249  }
    250 
    251  JS::Rooted<JS::Value> state(aCx, aOptions.mState);
    252  auto serializedState = MakeRefPtr<nsStructuredCloneContainer>();
    253  nsresult rv = serializedState->InitFromJSVal(state, aCx);
    254  if (NS_FAILED(rv)) {
    255    aRv.ThrowDataCloneError(
    256        "Failed to serialize value for updateCurrentEntry.");
    257    return;
    258  }
    259 
    260  currentEntry->SetNavigationAPIState(serializedState);
    261 
    262  ToMaybeRef(GetOwnerWindow())
    263      .andThen([](auto& aWindow) {
    264        return ToMaybeRef(aWindow.GetBrowsingContext());
    265      })
    266      .apply([serializedState](auto& navigable) {
    267        navigable.SynchronizeNavigationAPIState(serializedState);
    268        ToMaybeRef(nsDocShell::Cast(navigable.GetDocShell()))
    269            .andThen([](auto& docshell) {
    270              return ToMaybeRef(docshell.GetActiveSessionHistoryInfo());
    271            })
    272            .apply([serializedState](auto& activeInfo) {
    273              activeInfo.SetNavigationAPIState(serializedState);
    274            });
    275      });
    276 
    277  NavigationCurrentEntryChangeEventInit init;
    278  init.mFrom = currentEntry;
    279  // Leaving the navigation type unspecified means it will be initialized to
    280  // null.
    281  RefPtr event = NavigationCurrentEntryChangeEvent::Constructor(
    282      this, u"currententrychange"_ns, init);
    283  event->SetTrusted(true);
    284  DispatchEvent(*event);
    285 }
    286 
    287 NavigationTransition* Navigation::GetTransition() const { return mTransition; }
    288 
    289 NavigationActivation* Navigation::GetActivation() const { return mActivation; }
    290 
    291 template <typename I>
    292 bool SupportsInterface(nsISupports* aSupports) {
    293  nsCOMPtr<I> ptr = do_QueryInterface(aSupports);
    294  return ptr;
    295 }
    296 
    297 // https://html.spec.whatwg.org/#has-entries-and-events-disabled
    298 bool Navigation::HasEntriesAndEventsDisabled() const {
    299  Document* doc = GetAssociatedDocument();
    300  return !doc || !doc->IsCurrentActiveDocument() ||
    301         doc->IsEverInitialDocument() ||
    302         doc->GetPrincipal()->GetIsNullPrincipal() ||
    303         // We explicitly disallow documents loaded through multipart and script
    304         // channels from having events or entries. See bug 1996218 and bug
    305         // 1996221
    306         SupportsInterface<nsIMultiPartChannel>(doc->GetChannel()) ||
    307         SupportsInterface<nsIScriptChannel>(doc->GetChannel()) ||
    308         // We also disallow documents embedded using <object>/<embed>. See bug
    309         // 1996215.
    310         !doc->GetBrowsingContext() ||
    311         doc->GetBrowsingContext()->IsEmbedderTypeObjectOrEmbed();
    312 }
    313 
    314 // https://html.spec.whatwg.org/#initialize-the-navigation-api-entries-for-a-new-document
    315 void Navigation::InitializeHistoryEntries(
    316    mozilla::Span<const SessionHistoryInfo> aNewSHInfos,
    317    const SessionHistoryInfo* aInitialSHInfo) {
    318  LOG_FMTD("Attempting to initialize history entries for {}.",
    319           aInitialSHInfo->GetURI()
    320               ? aInitialSHInfo->GetURI()->GetSpecOrDefault()
    321               : "<no uri>"_ns)
    322 
    323  mEntries.Clear();
    324  mCurrentEntryIndex.reset();
    325  if (HasEntriesAndEventsDisabled()) {
    326    return;
    327  }
    328 
    329  for (auto i = 0ul; i < aNewSHInfos.Length(); i++) {
    330    mEntries.AppendElement(MakeRefPtr<NavigationHistoryEntry>(
    331        GetOwnerGlobal(), &aNewSHInfos[i], i));
    332    if (aNewSHInfos[i].NavigationKey() == aInitialSHInfo->NavigationKey()) {
    333      mCurrentEntryIndex = Some(i);
    334    }
    335  }
    336 
    337  LogHistory();
    338 
    339  nsID key = aInitialSHInfo->NavigationKey();
    340  nsID id = aInitialSHInfo->NavigationId();
    341  MOZ_LOG(
    342      gNavigationAPILog, LogLevel::Debug,
    343      ("aInitialSHInfo: %s %s\n", key.ToString().get(), id.ToString().get()));
    344 }
    345 
    346 // https://html.spec.whatwg.org/#update-the-navigation-api-entries-for-a-same-document-navigation
    347 void Navigation::UpdateEntriesForSameDocumentNavigation(
    348    SessionHistoryInfo* aDestinationSHE, NavigationType aNavigationType) {
    349  // Step 1.
    350  if (HasEntriesAndEventsDisabled()) {
    351    return;
    352  }
    353 
    354  MOZ_LOG(gNavigationAPILog, LogLevel::Debug,
    355          ("Updating entries for same-document navigation"));
    356 
    357  // Steps 2-7.
    358  RefPtr<NavigationHistoryEntry> oldCurrentEntry = GetCurrentEntry();
    359  nsTArray<RefPtr<NavigationHistoryEntry>> disposedEntries;
    360  switch (aNavigationType) {
    361    case NavigationType::Traverse:
    362      MOZ_LOG(gNavigationAPILog, LogLevel::Debug, ("Traverse navigation"));
    363      SetCurrentEntryIndex(aDestinationSHE);
    364      MOZ_ASSERT(mCurrentEntryIndex);
    365      break;
    366 
    367    case NavigationType::Push:
    368      MOZ_LOG(gNavigationAPILog, LogLevel::Debug, ("Push navigation"));
    369      mCurrentEntryIndex =
    370          Some(mCurrentEntryIndex ? *mCurrentEntryIndex + 1 : 0);
    371      disposedEntries.AppendElements(Span(mEntries).From(*mCurrentEntryIndex));
    372      mEntries.RemoveElementsAt(*mCurrentEntryIndex,
    373                                mEntries.Length() - *mCurrentEntryIndex);
    374      mEntries.AppendElement(MakeRefPtr<NavigationHistoryEntry>(
    375          GetOwnerGlobal(), aDestinationSHE, *mCurrentEntryIndex));
    376      break;
    377 
    378    case NavigationType::Replace:
    379      MOZ_LOG(gNavigationAPILog, LogLevel::Debug, ("Replace navigation"));
    380      if (!oldCurrentEntry) {
    381        LOG_FMTE("No current entry.");
    382        MOZ_ASSERT(false, "FIXME");
    383        return;
    384      }
    385      disposedEntries.AppendElement(oldCurrentEntry);
    386      MOZ_DIAGNOSTIC_ASSERT(
    387          aDestinationSHE->NavigationKey() ==
    388          oldCurrentEntry->SessionHistoryInfo()->NavigationKey());
    389      mEntries[*mCurrentEntryIndex] = MakeRefPtr<NavigationHistoryEntry>(
    390          GetOwnerGlobal(), aDestinationSHE, *mCurrentEntryIndex);
    391      break;
    392 
    393    case NavigationType::Reload:
    394      break;
    395  }
    396 
    397  // Step 8.
    398  if (mOngoingAPIMethodTracker) {
    399    RefPtr<NavigationHistoryEntry> currentEntry = GetCurrentEntry();
    400    mOngoingAPIMethodTracker->NotifyAboutCommittedToEntry(currentEntry);
    401  }
    402 
    403  for (auto& entry : disposedEntries) {
    404    entry->ResetIndexForDisposal();
    405  }
    406 
    407  // Steps 9-12.
    408  {
    409    nsAutoMicroTask mt;
    410    AutoEntryScript aes(GetOwnerGlobal(),
    411                        "UpdateEntriesForSameDocumentNavigation");
    412 
    413    NavigationCurrentEntryChangeEventInit init;
    414    init.mFrom = oldCurrentEntry;
    415    init.mNavigationType.SetValue(aNavigationType);
    416    RefPtr event = NavigationCurrentEntryChangeEvent::Constructor(
    417        this, u"currententrychange"_ns, init);
    418    event->SetTrusted(true);
    419    DispatchEvent(*event);
    420 
    421    for (const auto& entry : disposedEntries) {
    422      RefPtr<Event> event = NS_NewDOMEvent(entry, nullptr, nullptr);
    423      event->InitEvent(u"dispose"_ns, false, false);
    424      event->SetTrusted(true);
    425      event->SetTarget(entry);
    426      entry->DispatchEvent(*event);
    427    }
    428  }
    429 }
    430 
    431 // https://html.spec.whatwg.org/#update-the-navigation-api-entries-for-reactivation
    432 void Navigation::UpdateForReactivation(SessionHistoryInfo* aReactivatedEntry) {
    433  // NAV-TODO
    434 }
    435 
    436 // https://html.spec.whatwg.org/#navigation-api-early-error-result
    437 void Navigation::SetEarlyErrorResult(JSContext* aCx, NavigationResult& aResult,
    438                                     ErrorResult&& aRv) const {
    439  MOZ_ASSERT(aRv.Failed());
    440  // An early error result for an exception e is a NavigationResult dictionary
    441  // instance given by
    442  // «[ "committed" → a promise rejected with e,
    443  //    "finished" → a promise rejected with e ]».
    444 
    445  // Get the global of the current realm to create the DOMException.
    446  // See https://webidl.spec.whatwg.org/#js-creating-throwing-exceptions
    447  nsIGlobalObject* global = GetCurrentGlobal();
    448  if (!global) {
    449    // Creating a promise should only fail if there is no global.
    450    // In this case, the only solution is to ignore the error.
    451    aRv.SuppressException();
    452    return;
    453  }
    454  JS::Rooted<JS::Value> rootedExceptionValue(aCx);
    455  MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(aRv), &rootedExceptionValue));
    456 
    457  InitNavigationResult(
    458      aResult, Promise::Reject(global, rootedExceptionValue, IgnoreErrors()),
    459      Promise::Reject(global, rootedExceptionValue, IgnoreErrors()));
    460 }
    461 
    462 void Navigation::SetEarlyStateErrorResult(JSContext* aCx,
    463                                          NavigationResult& aResult,
    464                                          const nsACString& aMessage) const {
    465  ErrorResult rv;
    466  rv.ThrowInvalidStateError(aMessage);
    467  SetEarlyErrorResult(aCx, aResult, std::move(rv));
    468 }
    469 
    470 bool Navigation::CheckIfDocumentIsFullyActiveAndMaybeSetEarlyErrorResult(
    471    JSContext* aCx, const Document* aDocument,
    472    NavigationResult& aResult) const {
    473  if (!aDocument || !aDocument->IsFullyActive()) {
    474    ErrorResult rv;
    475    rv.ThrowInvalidStateError("Document is not fully active");
    476    SetEarlyErrorResult(aCx, aResult, std::move(rv));
    477    return false;
    478  }
    479  return true;
    480 }
    481 
    482 bool Navigation::CheckDocumentUnloadCounterAndMaybeSetEarlyErrorResult(
    483    JSContext* aCx, const Document* aDocument,
    484    NavigationResult& aResult) const {
    485  if (!aDocument || aDocument->ShouldIgnoreOpens()) {
    486    ErrorResult rv;
    487    rv.ThrowInvalidStateError("Document is unloading");
    488    SetEarlyErrorResult(aCx, aResult, std::move(rv));
    489    return false;
    490  }
    491  return true;
    492 }
    493 
    494 already_AddRefed<nsIStructuredCloneContainer>
    495 Navigation::CreateSerializedStateAndMaybeSetEarlyErrorResult(
    496    JSContext* aCx, const JS::Value& aState, NavigationResult& aResult) const {
    497  JS::Rooted<JS::Value> state(aCx, aState);
    498  RefPtr global = GetOwnerGlobal();
    499  MOZ_DIAGNOSTIC_ASSERT(global);
    500 
    501  RefPtr<nsIStructuredCloneContainer> serializedState =
    502      new nsStructuredCloneContainer();
    503  const nsresult rv = serializedState->InitFromJSVal(state, aCx);
    504  if (NS_FAILED(rv)) {
    505    JS::Rooted<JS::Value> exception(aCx);
    506    if (JS_GetPendingException(aCx, &exception)) {
    507      JS_ClearPendingException(aCx);
    508      InitNavigationResult(aResult,
    509                           Promise::Reject(global, exception, IgnoreErrors()),
    510                           Promise::Reject(global, exception, IgnoreErrors()));
    511      return nullptr;
    512    }
    513    SetEarlyErrorResult(aCx, aResult, ErrorResult(rv));
    514    return nullptr;
    515  }
    516  return serializedState.forget();
    517 }
    518 
    519 // https://html.spec.whatwg.org/#dom-navigation-navigate
    520 void Navigation::Navigate(JSContext* aCx, const nsAString& aUrl,
    521                          const NavigationNavigateOptions& aOptions,
    522                          NavigationResult& aResult) {
    523  LOG_FMTI("Called navigation.navigate() with url = {}",
    524           NS_ConvertUTF16toUTF8(aUrl));
    525  // 4. Let document be this's relevant global object's associated Document.
    526  const RefPtr<Document> document = GetAssociatedDocument();
    527  if (!document) {
    528    return;
    529  }
    530 
    531  // 1. Let urlRecord be the result of parsing a URL given url, relative to
    532  //    this's relevant settings object.
    533  RefPtr<nsIURI> urlRecord;
    534  nsresult res = NS_NewURI(getter_AddRefs(urlRecord), aUrl, nullptr,
    535                           document->GetDocBaseURI());
    536  if (NS_FAILED(res)) {
    537    // 2. If urlRecord is failure, then return an early error result for a
    538    //    "SyntaxError" DOMException.
    539    ErrorResult rv;
    540    rv.ThrowSyntaxError("URL given to navigate() is invalid");
    541    SetEarlyErrorResult(aCx, aResult, std::move(rv));
    542    return;
    543  }
    544 
    545  // 3. If urlRecord's scheme is "javascript", then return an early error result
    546  //    for a "NotSupportedError" DOMException.
    547  if (urlRecord->SchemeIs("javascript")) {
    548    ErrorResult rv;
    549    rv.ThrowNotSupportedError("The javascript: protocol is not supported");
    550    SetEarlyErrorResult(aCx, aResult, std::move(rv));
    551    return;
    552  }
    553 
    554  // 5. If options["history"] is "push", and the navigation must be a replace
    555  //    given urlRecord and document, then return an early error result for a
    556  //    "NotSupportedError" DOMException.
    557  if (aOptions.mHistory == NavigationHistoryBehavior::Push &&
    558      nsContentUtils::NavigationMustBeAReplace(*urlRecord, *document)) {
    559    ErrorResult rv;
    560    rv.ThrowNotSupportedError("Navigation must be a replace navigation");
    561    SetEarlyErrorResult(aCx, aResult, std::move(rv));
    562    return;
    563  }
    564 
    565  // 6. Let state be options["state"], if it exists; otherwise, undefined.
    566  // 7. Let serializedState be StructuredSerializeForStorage(state). If this
    567  //    throws an exception, then return an early error result for that
    568  //    exception.
    569  nsCOMPtr<nsIStructuredCloneContainer> serializedState =
    570      CreateSerializedStateAndMaybeSetEarlyErrorResult(aCx, aOptions.mState,
    571                                                       aResult);
    572  if (!serializedState) {
    573    return;
    574  }
    575 
    576  // 8. If document is not fully active, then return an early error result for
    577  //    an "InvalidStateError" DOMException.
    578  if (!CheckIfDocumentIsFullyActiveAndMaybeSetEarlyErrorResult(aCx, document,
    579                                                               aResult)) {
    580    return;
    581  }
    582 
    583  // 9. If document's unload counter is greater than 0, then return an early
    584  //    error result for an "InvalidStateError" DOMException.
    585  if (!CheckDocumentUnloadCounterAndMaybeSetEarlyErrorResult(aCx, document,
    586                                                             aResult)) {
    587    return;
    588  }
    589 
    590  // 10. Let info be options["info"], if it exists; otherwise, undefined.
    591  // 11. Let apiMethodTracker be the result of setting up a navigate API method
    592  //     tracker for this given info and serializedState.
    593  JS::Rooted<JS::Value> info(aCx, aOptions.mInfo);
    594  RefPtr<NavigationAPIMethodTracker> apiMethodTracker =
    595      SetUpNavigateReloadAPIMethodTracker(info, serializedState);
    596  MOZ_ASSERT(apiMethodTracker);
    597 
    598  // 12. Navigate document's node navigable to urlRecord using document, with
    599  //     historyHandling set to options["history"], navigationAPIState set to
    600  //     serializedState, and navigationAPIMethodTracker set to
    601  //     apiMethodTracker.
    602 
    603  RefPtr bc = document->GetBrowsingContext();
    604  MOZ_DIAGNOSTIC_ASSERT(bc);
    605  bc->Navigate(urlRecord, document, *document->NodePrincipal(),
    606               /* per spec, error handling defaults to false */ IgnoreErrors(),
    607               aOptions.mHistory, /* aNeedsCompletelyLoadedDocument */ false,
    608               serializedState, apiMethodTracker);
    609 
    610  // 13. Return a navigation API method tracker-derived result for
    611  //     apiMethodTracker.
    612  apiMethodTracker->CreateResult(aCx, aResult);
    613 }
    614 
    615 // https://html.spec.whatwg.org/#performing-a-navigation-api-traversal
    616 void Navigation::PerformNavigationTraversal(JSContext* aCx, const nsID& aKey,
    617                                            const NavigationOptions& aOptions,
    618                                            NavigationResult& aResult) {
    619  LOG_FMTV("traverse navigation to {}", aKey.ToString().get());
    620  // 1. Let document be navigation's relevant global object's associated
    621  //    Document.
    622  const Document* document = GetAssociatedDocument();
    623 
    624  // 2. If document is not fully active, then return an early error result for
    625  //    an "InvalidStateError" DOMException.
    626  if (!document || !document->IsFullyActive()) {
    627    SetEarlyStateErrorResult(aCx, aResult, "Document is not fully active"_ns);
    628    return;
    629  }
    630 
    631  // 3. If document's unload counter is greater than 0, then return an early
    632  //    error result for an "InvalidStateError" DOMException.
    633  if (document->ShouldIgnoreOpens()) {
    634    SetEarlyStateErrorResult(aCx, aResult, "Document is unloading"_ns);
    635    return;
    636  }
    637 
    638  // 4. Let current be the current entry of navigation.
    639  RefPtr<NavigationHistoryEntry> current = GetCurrentEntry();
    640  if (!current) {
    641    SetEarlyStateErrorResult(aCx, aResult,
    642                             "No current navigation history entry"_ns);
    643    return;
    644  }
    645 
    646  // 5. If key equals current's session history entry's navigation API key, then
    647  //    return «[ "committed" → a promise resolved with current, "finished" → a
    648  //    promise resolved with current ]».
    649  RefPtr global = GetOwnerGlobal();
    650  if (!global) {
    651    return;
    652  }
    653 
    654  if (current->Key() == aKey) {
    655    InitNavigationResult(aResult,
    656                         Promise::Resolve(global, current, IgnoreErrors()),
    657                         Promise::Resolve(global, current, IgnoreErrors()));
    658    return;
    659  }
    660 
    661  // 6. If navigation's upcoming traverse API method trackers[key] exists, then
    662  //    return a navigation API method tracker-derived result for navigation's
    663  //    upcoming traverse API method trackers[key].
    664  if (auto maybeTracker =
    665          mUpcomingTraverseAPIMethodTrackers.MaybeGet(aKey).valueOr(nullptr)) {
    666    maybeTracker->CreateResult(aCx, aResult);
    667    return;
    668  }
    669 
    670  // 7. Let info be options["info"], if it exists; otherwise, undefined.
    671  JS::Rooted<JS::Value> info(aCx, aOptions.mInfo);
    672 
    673  // 8. Let apiMethodTracker be the result of adding an upcoming traverse API
    674  //    method tracker for navigation given key and info.
    675  RefPtr apiMethodTracker = AddUpcomingTraverseAPIMethodTracker(aKey, info);
    676 
    677  // 9. Let navigable be document's node navigable.
    678  RefPtr<BrowsingContext> navigable = document->GetBrowsingContext();
    679 
    680  // 10. Let traversable be navigable's traversable navigable.
    681  RefPtr<BrowsingContext> traversable = navigable->Top();
    682  // 11. Let sourceSnapshotParams be the result of snapshotting source snapshot
    683  //     params given document.
    684 
    685  // 13. Return a navigation API method tracker-derived result for
    686  //     apiMethodTracker.
    687  apiMethodTracker->CreateResult(aCx, aResult);
    688 
    689  // 12. Append the following session history traversal steps to traversable:
    690  auto* childSHistory = traversable->GetChildSessionHistory();
    691  auto performNavigationTraversalSteps = [apiMethodTracker](nsresult aResult) {
    692    // 12.3 If targetSHE is navigable's active session history entry,
    693    //      then abort these steps.
    694    if (NS_SUCCEEDED(aResult)) {
    695      return;
    696    }
    697 
    698    AutoJSAPI jsapi;
    699    if (NS_WARN_IF(!jsapi.Init(
    700            apiMethodTracker->mNavigationObject->GetParentObject()))) {
    701      return;
    702    }
    703 
    704    ErrorResult rv;
    705 
    706    switch (aResult) {
    707      case NS_ERROR_DOM_INVALID_STATE_ERR:
    708        // 12.2 Let targetSHE be the session history entry in navigableSHEs
    709        //      whose navigation API key is key. If no such entry exists,
    710        //      then:
    711        rv.ThrowInvalidStateError("No such entry with key found");
    712        break;
    713      case NS_ERROR_DOM_ABORT_ERR:
    714        // 12.5 If result is "canceled-by-beforeunload", then queue a global
    715        //      task on the navigation and traversal task source given
    716        //      navigation's relevant global object to reject the finished
    717        //      promise for apiMethodTracker with a new "AbortError"
    718        //      DOMException
    719        // created in navigation's relevant realm.
    720        rv.ThrowAbortError("Navigation was canceled");
    721        break;
    722      case NS_ERROR_DOM_SECURITY_ERR:
    723        // 12.6 If result is "initiator-disallowed", then queue a global task on
    724        //      the navigation and traversal task source given navigation's
    725        //      relevant global object to reject the finished promise for
    726        //      apiMethodTracker with a new "SecurityError" DOMException
    727        //      created in navigation's relevant realm.
    728        rv.ThrowSecurityError("Navigation was not allowed");
    729        break;
    730      default:
    731        MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected result");
    732        rv.ThrowInvalidStateError("Unexpected result");
    733        break;
    734    }
    735    JS::Rooted<JS::Value> rootedExceptionValue(jsapi.cx());
    736    MOZ_ALWAYS_TRUE(
    737        ToJSValue(jsapi.cx(), std::move(rv), &rootedExceptionValue));
    738    apiMethodTracker->RejectFinishedPromise(rootedExceptionValue);
    739  };
    740 
    741  // 12.4 Let result be the result of applying the traverse history step given
    742  //      by targetSHE's step to traversable, given sourceSnapshotParams,
    743  //      navigable, and "none".
    744  childSHistory->AsyncGo(aKey, navigable, /*aRequireUserInteraction=*/false,
    745                         /*aUserActivation=*/false,
    746                         /*aCheckForCancelation=*/true,
    747                         performNavigationTraversalSteps);
    748 }
    749 
    750 // https://html.spec.whatwg.org/#dom-navigation-reload
    751 void Navigation::Reload(JSContext* aCx, const NavigationReloadOptions& aOptions,
    752                        NavigationResult& aResult) {
    753  LOG_FMTI("Called navigation.reload()");
    754  // 1. Let document be this's relevant global object's associated Document.
    755  const RefPtr<Document> document = GetAssociatedDocument();
    756  if (!document) {
    757    return;
    758  }
    759 
    760  // 2. Let serializedState be StructuredSerializeForStorage(undefined).
    761  RefPtr<nsIStructuredCloneContainer> serializedState;
    762 
    763  // 3. If options["state"] exists, then set serializedState to
    764  //    StructuredSerializeForStorage(options["state"]). If this throws an
    765  //    exception, then return an early error result for that exception.
    766  if (!aOptions.mState.isUndefined()) {
    767    serializedState = CreateSerializedStateAndMaybeSetEarlyErrorResult(
    768        aCx, aOptions.mState, aResult);
    769    if (!serializedState) {
    770      return;
    771    }
    772  } else {
    773    // 4. Otherwise:
    774    // 4.1 Let current be the current entry of this.
    775    // 4.2 If current is not null, then set serializedState to current's
    776    //     session history entry's navigation API state.
    777    if (RefPtr<NavigationHistoryEntry> current = GetCurrentEntry()) {
    778      serializedState = current->GetNavigationAPIState();
    779    }
    780  }
    781  // 5. If document is not fully active, then return an early error result for
    782  //    an "InvalidStateError" DOMException.
    783  if (!CheckIfDocumentIsFullyActiveAndMaybeSetEarlyErrorResult(aCx, document,
    784                                                               aResult)) {
    785    return;
    786  }
    787 
    788  // 6. If document's unload counter is greater than 0, then return an early
    789  //    error result for an "InvalidStateError" DOMException.
    790  if (!CheckDocumentUnloadCounterAndMaybeSetEarlyErrorResult(aCx, document,
    791                                                             aResult)) {
    792    return;
    793  }
    794 
    795  // 7. Let info be options["info"], if it exists; otherwise, undefined.
    796  JS::Rooted<JS::Value> info(aCx, aOptions.mInfo);
    797  // 8. Let apiMethodTracker be the result of setting up a reload API method
    798  //    tracker for this given info and serializedState.
    799  RefPtr<NavigationAPIMethodTracker> apiMethodTracker =
    800      SetUpNavigateReloadAPIMethodTracker(info, serializedState);
    801  MOZ_ASSERT(apiMethodTracker);
    802  // 9. Reload document's node navigable with navigationAPIState set to
    803  //    serializedState and navigationAPIMethodTracker set to apiMethodTracker.
    804  RefPtr docShell = nsDocShell::Cast(document->GetDocShell());
    805  MOZ_ASSERT(docShell);
    806  docShell->ReloadNavigable(Some(WrapNotNullUnchecked(aCx)),
    807                            nsIWebNavigation::LOAD_FLAGS_NONE, serializedState,
    808                            UserNavigationInvolvement::None, apiMethodTracker);
    809 
    810  // 10. Return a navigation API method tracker-derived result for
    811  //     apiMethodTracker.
    812  apiMethodTracker->CreateResult(aCx, aResult);
    813 }
    814 
    815 // https://html.spec.whatwg.org/#dom-navigation-traverseto
    816 void Navigation::TraverseTo(JSContext* aCx, const nsAString& aKey,
    817                            const NavigationOptions& aOptions,
    818                            NavigationResult& aResult) {
    819  LOG_FMTI("Called navigation.traverseTo() with key = {}",
    820           NS_ConvertUTF16toUTF8(aKey).get());
    821 
    822  // 1. If this's current entry index is −1, then return an early error result
    823  //    for an "InvalidStateError" DOMException.
    824  if (mCurrentEntryIndex.isNothing()) {
    825    ErrorResult rv;
    826    rv.ThrowInvalidStateError("Current entry index is unexpectedly -1");
    827    SetEarlyErrorResult(aCx, aResult, std::move(rv));
    828    return;
    829  }
    830 
    831  // 2. If this's entry list does not contain a NavigationHistoryEntry whose
    832  //    session history entry's navigation API key equals key, then return an
    833  //    early error result for an "InvalidStateError" DOMException.
    834  nsID key{};
    835  const bool foundKey =
    836      key.Parse(NS_ConvertUTF16toUTF8(aKey).Data()) &&
    837      std::find_if(mEntries.begin(), mEntries.end(), [&](const auto& aEntry) {
    838        return aEntry->Key() == key;
    839      }) != mEntries.end();
    840  if (!foundKey) {
    841    ErrorResult rv;
    842    rv.ThrowInvalidStateError("Session history entry key does not exist");
    843    SetEarlyErrorResult(aCx, aResult, std::move(rv));
    844    return;
    845  }
    846 
    847  // 3. Return the result of performing a navigation API traversal given this,
    848  //    key, and options.
    849  PerformNavigationTraversal(aCx, key, aOptions, aResult);
    850 }
    851 
    852 // https://html.spec.whatwg.org/#dom-navigation-back
    853 void Navigation::Back(JSContext* aCx, const NavigationOptions& aOptions,
    854                      NavigationResult& aResult) {
    855  LOG_FMTI("Called navigation.back()");
    856  // 1. If this's current entry index is −1 or 0, then return an early error
    857  //    result for an "InvalidStateError" DOMException.
    858  if (mCurrentEntryIndex.isNothing() || *mCurrentEntryIndex == 0 ||
    859      *mCurrentEntryIndex > mEntries.Length() - 1) {
    860    SetEarlyStateErrorResult(aCx, aResult,
    861                             "Current entry index is unexpectedly -1 or 0"_ns);
    862    return;
    863  }
    864 
    865  // 2. Let key be this's entry list[this's current entry index − 1]'s session
    866  //    history entry's navigation API key.
    867  MOZ_DIAGNOSTIC_ASSERT(mEntries[*mCurrentEntryIndex - 1]);
    868  const nsID key = mEntries[*mCurrentEntryIndex - 1]->Key();
    869 
    870  // 3. Return the result of performing a navigation API traversal given this,
    871  //    key, and options.
    872  PerformNavigationTraversal(aCx, key, aOptions, aResult);
    873 }
    874 
    875 // https://html.spec.whatwg.org/#dom-navigation-forward
    876 void Navigation::Forward(JSContext* aCx, const NavigationOptions& aOptions,
    877                         NavigationResult& aResult) {
    878  LOG_FMTI("Called navigation.forward()");
    879 
    880  // 1. If this's current entry index is −1 or is equal to this's entry list's
    881  //    size − 1, then return an early error result for an "InvalidStateError"
    882  //    DOMException.
    883  if (mCurrentEntryIndex.isNothing() ||
    884      *mCurrentEntryIndex >= mEntries.Length() - 1) {
    885    ErrorResult rv;
    886    rv.ThrowInvalidStateError(
    887        "Current entry index is unexpectedly -1 or entry list's size - 1");
    888    SetEarlyErrorResult(aCx, aResult, std::move(rv));
    889    return;
    890  }
    891 
    892  // 2. Let key be this's entry list[this's current entry index + 1]'s session
    893  //    history entry's navigation API key.
    894  MOZ_ASSERT(mEntries[*mCurrentEntryIndex + 1]);
    895  const nsID& key = mEntries[*mCurrentEntryIndex + 1]->Key();
    896 
    897  // 3. Return the result of performing a navigation API traversal given this,
    898  //    key, and options.
    899  PerformNavigationTraversal(aCx, key, aOptions, aResult);
    900 }
    901 
    902 namespace {
    903 
    904 void LogEntry(NavigationHistoryEntry* aEntry, uint64_t aIndex, uint64_t aTotal,
    905              bool aIsCurrent) {
    906  if (!aEntry) {
    907    MOZ_LOG(gNavigationAPILog, LogLevel::Debug,
    908            (" +- %d NHEntry null\n", int(aIndex)));
    909    return;
    910  }
    911 
    912  nsString key, id;
    913  aEntry->GetKey(key);
    914  aEntry->GetId(id);
    915  MOZ_LOG(gNavigationAPILog, LogLevel::Debug,
    916          ("%s+- %d NHEntry %p %s %s\n", aIsCurrent ? ">" : " ", int(aIndex),
    917           aEntry, NS_ConvertUTF16toUTF8(key).get(),
    918           NS_ConvertUTF16toUTF8(id).get()));
    919 
    920  nsAutoString url;
    921  aEntry->GetUrl(url);
    922  MOZ_LOG(gNavigationAPILog, LogLevel::Debug,
    923          ("   URL = %s\n", NS_ConvertUTF16toUTF8(url).get()));
    924 }
    925 
    926 }  // namespace
    927 
    928 // https://html.spec.whatwg.org/#fire-a-traverse-navigate-event
    929 bool Navigation::FireTraverseNavigateEvent(
    930    JSContext* aCx, const SessionHistoryInfo& aDestinationSessionHistoryInfo,
    931    Maybe<UserNavigationInvolvement> aUserInvolvement) {
    932  // aDestinationSessionHistoryInfo corresponds to
    933  // https://html.spec.whatwg.org/#fire-navigate-traverse-destinationshe
    934 
    935  // To not unnecessarily create an event that's never used, step 1 and step 2
    936  // in #fire-a-traverse-navigate-event have been moved to after step 25 in
    937  // #inner-navigate-event-firing-algorithm in our implementation.
    938 
    939  // Work around for https://github.com/whatwg/html/issues/11802
    940  InnerInformAboutAbortingNavigation(aCx);
    941 
    942  // Step 5
    943  RefPtr<NavigationHistoryEntry> destinationNHE =
    944      FindNavigationHistoryEntry(aDestinationSessionHistoryInfo);
    945 
    946  // Step 6.2 and step 7.2
    947  RefPtr<nsIStructuredCloneContainer> state =
    948      destinationNHE ? destinationNHE->GetNavigationAPIState() : nullptr;
    949 
    950  // Step 8
    951  bool isSameDocument =
    952      ToMaybeRef(
    953          nsDocShell::Cast(nsContentUtils::GetDocShellForEventTarget(this)))
    954          .andThen([](auto& aDocShell) {
    955            return ToMaybeRef(aDocShell.GetActiveSessionHistoryInfo());
    956          })
    957          .map([&aDestinationSessionHistoryInfo](auto& aSessionHistoryInfo) {
    958            return aDestinationSessionHistoryInfo.SharesDocumentWith(
    959                aSessionHistoryInfo);
    960          })
    961          .valueOr(false);
    962 
    963  // Step 3, step 4, step 6.1, and step 7.1.
    964  RefPtr<NavigationDestination> destination =
    965      MakeAndAddRef<NavigationDestination>(
    966          GetOwnerGlobal(), aDestinationSessionHistoryInfo.GetURI(),
    967          destinationNHE, state, isSameDocument);
    968 
    969  // Step 9
    970  return InnerFireNavigateEvent(
    971      aCx, NavigationType::Traverse, destination,
    972      aUserInvolvement.valueOr(UserNavigationInvolvement::None),
    973      /* aSourceElement */ nullptr,
    974      /* aFormDataEntryList*/ nullptr,
    975      /* aClassicHistoryAPIState */ nullptr,
    976      /* aDownloadRequestFilename */ VoidString());
    977 }
    978 
    979 // https://html.spec.whatwg.org/#fire-a-push/replace/reload-navigate-event
    980 bool Navigation::FirePushReplaceReloadNavigateEvent(
    981    JSContext* aCx, NavigationType aNavigationType, nsIURI* aDestinationURL,
    982    bool aIsSameDocument, Maybe<UserNavigationInvolvement> aUserInvolvement,
    983    Element* aSourceElement, FormData* aFormDataEntryList,
    984    nsIStructuredCloneContainer* aNavigationAPIState,
    985    nsIStructuredCloneContainer* aClassicHistoryAPIState,
    986    NavigationAPIMethodTracker* aApiMethodTrackerForNavigateOrReload) {
    987  // 1. Let document be navigation's relevant global object's associated
    988  //    Document.
    989  RefPtr document = GetAssociatedDocument();
    990 
    991  // 2. Inform the navigation API about aborting navigation in document's node
    992  //    navigable.
    993  InnerInformAboutAbortingNavigation(aCx);
    994 
    995  // 3. If navigation has entries and events disabled, and
    996  //    apiMethodTrackerForNavigateOrReload is not null:
    997  if (HasEntriesAndEventsDisabled() && aApiMethodTrackerForNavigateOrReload) {
    998    // 3.1. Set apiMethodTrackerForNavigateOrReload's pending to false.
    999    aApiMethodTrackerForNavigateOrReload->MarkAsNotPending();
   1000    aApiMethodTrackerForNavigateOrReload = nullptr;
   1001  }
   1002 
   1003  // 4. If document is not fully active, then return false.
   1004  if (!document || !document->IsFullyActive()) {
   1005    return false;
   1006  }
   1007 
   1008  // To not unnecessarily create an event that's never used, step 5 and step 6
   1009  // in #fire-a-push/replace/reload-navigate-event have been moved to after step
   1010  // 23 in #inner-navigate-event-firing-algorithm in our implementation.
   1011 
   1012  // Step 7 to step 11
   1013  RefPtr<NavigationDestination> destination =
   1014      MakeAndAddRef<NavigationDestination>(GetOwnerGlobal(), aDestinationURL,
   1015                                           /* aEntry */ nullptr,
   1016                                           /* aState */ aNavigationAPIState,
   1017                                           aIsSameDocument);
   1018 
   1019  // Step 8
   1020  return InnerFireNavigateEvent(
   1021      aCx, aNavigationType, destination,
   1022      aUserInvolvement.valueOr(UserNavigationInvolvement::None), aSourceElement,
   1023      aFormDataEntryList, aClassicHistoryAPIState,
   1024      /* aDownloadRequestFilename */ VoidString(),
   1025      aApiMethodTrackerForNavigateOrReload);
   1026 }
   1027 
   1028 // https://html.spec.whatwg.org/#fire-a-download-request-navigate-event
   1029 bool Navigation::FireDownloadRequestNavigateEvent(
   1030    JSContext* aCx, nsIURI* aDestinationURL,
   1031    UserNavigationInvolvement aUserInvolvement, Element* aSourceElement,
   1032    const nsAString& aFilename) {
   1033  // To not unnecessarily create an event that's never used, step 1 and step 2
   1034  // in #fire-a-download-request-navigate-event have been moved to after step
   1035  // 25 in #inner-navigate-event-firing-algorithm in our implementation.
   1036 
   1037  // Work around for https://github.com/whatwg/html/issues/11802
   1038  InnerInformAboutAbortingNavigation(aCx);
   1039 
   1040  // Step 3 to step 7
   1041  RefPtr<NavigationDestination> destination =
   1042      MakeAndAddRef<NavigationDestination>(GetOwnerGlobal(), aDestinationURL,
   1043                                           /* aEntry */ nullptr,
   1044                                           /* aState */ nullptr,
   1045                                           /* aIsSameDocument */ false);
   1046 
   1047  // Step 8
   1048  return InnerFireNavigateEvent(
   1049      aCx, NavigationType::Push, destination, aUserInvolvement, aSourceElement,
   1050      /* aFormDataEntryList */ nullptr,
   1051      /* aClassicHistoryAPIState */ nullptr, aFilename);
   1052 }
   1053 
   1054 static bool HasHistoryActionActivation(
   1055    Maybe<nsGlobalWindowInner&> aRelevantGlobalObject) {
   1056  return aRelevantGlobalObject
   1057      .map([](auto& aRelevantGlobalObject) {
   1058        WindowContext* windowContext = aRelevantGlobalObject.GetWindowContext();
   1059        return windowContext && windowContext->HasValidHistoryActivation();
   1060      })
   1061      .valueOr(false);
   1062 }
   1063 
   1064 static void ConsumeHistoryActionUserActivation(
   1065    Maybe<nsGlobalWindowInner&> aRelevantGlobalObject) {
   1066  aRelevantGlobalObject.apply([](auto& aRelevantGlobalObject) {
   1067    if (WindowContext* windowContext =
   1068            aRelevantGlobalObject.GetWindowContext()) {
   1069      windowContext->ConsumeHistoryActivation();
   1070    }
   1071  });
   1072 }
   1073 
   1074 // Implementation of this will be done in Bug 1948593.
   1075 static bool HasUAVisualTransition(Maybe<Document&>) { return false; }
   1076 
   1077 static bool EqualsExceptRef(nsIURI* aURI, nsIURI* aOtherURI) {
   1078  bool equalsExceptRef = false;
   1079  return aURI && aOtherURI &&
   1080         NS_SUCCEEDED(aURI->EqualsExceptRef(aOtherURI, &equalsExceptRef)) &&
   1081         equalsExceptRef;
   1082 }
   1083 
   1084 static bool Equals(nsIURI* aURI, nsIURI* aOtherURI) {
   1085  bool equals = false;
   1086  return aURI && aOtherURI && NS_SUCCEEDED(aURI->Equals(aOtherURI, &equals)) &&
   1087         equals;
   1088 }
   1089 
   1090 static bool HasRef(nsIURI* aURI) {
   1091  bool hasRef = false;
   1092  aURI->GetHasRef(&hasRef);
   1093  return hasRef;
   1094 }
   1095 
   1096 static bool HasIdenticalFragment(nsIURI* aURI, nsIURI* aOtherURI) {
   1097  nsAutoCString ref;
   1098 
   1099  if (HasRef(aURI) != HasRef(aOtherURI)) {
   1100    return false;
   1101  }
   1102 
   1103  if (NS_FAILED(aURI->GetRef(ref))) {
   1104    return false;
   1105  }
   1106 
   1107  nsAutoCString otherRef;
   1108  if (NS_FAILED(aOtherURI->GetRef(otherRef))) {
   1109    return false;
   1110  }
   1111 
   1112  return ref.Equals(otherRef);
   1113 }
   1114 
   1115 static void LogEvent(Event* aEvent, NavigateEvent* aOngoingEvent,
   1116                     const nsACString& aReason) {
   1117  if (!MOZ_LOG_TEST(gNavigationAPILog, LogLevel::Debug)) {
   1118    return;
   1119  }
   1120 
   1121  nsAutoString eventType;
   1122  aEvent->GetType(eventType);
   1123 
   1124  nsTArray<nsCString> log = {nsCString(aReason),
   1125                             NS_ConvertUTF16toUTF8(eventType)};
   1126 
   1127  if (aEvent->Cancelable()) {
   1128    log.AppendElement("cancelable");
   1129  }
   1130 
   1131  if (aOngoingEvent) {
   1132    log.AppendElement(
   1133        fmt::format(FMT_STRING("{}"), aOngoingEvent->NavigationType()));
   1134 
   1135    if (RefPtr<NavigationDestination> destination =
   1136            aOngoingEvent->Destination()) {
   1137      log.AppendElement(destination->GetURL()->GetSpecOrDefault());
   1138    }
   1139 
   1140    if (aOngoingEvent->HashChange()) {
   1141      log.AppendElement("hashchange"_ns);
   1142    }
   1143  }
   1144 
   1145  LOG_FMTD("{}", fmt::join(log.begin(), log.end(), std::string_view{" "}));
   1146 }
   1147 
   1148 nsresult Navigation::FireEvent(const nsAString& aName) {
   1149  RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
   1150  // it doesn't bubble, and it isn't cancelable
   1151  event->InitEvent(aName, false, false);
   1152  event->SetTrusted(true);
   1153  ErrorResult rv;
   1154  LogEvent(event, mOngoingNavigateEvent, "Fire"_ns);
   1155  DispatchEvent(*event, rv);
   1156  return rv.StealNSResult();
   1157 }
   1158 
   1159 static void ExtractErrorInformation(JSContext* aCx,
   1160                                    JS::Handle<JS::Value> aError,
   1161                                    ErrorEventInit& aErrorEventInitDict,
   1162                                    NavigateEvent* aEvent) {
   1163  nsContentUtils::ExtractErrorValues(
   1164      aCx, aError, aErrorEventInitDict.mFilename, &aErrorEventInitDict.mLineno,
   1165      &aErrorEventInitDict.mColno, aErrorEventInitDict.mMessage);
   1166  aErrorEventInitDict.mError = aError;
   1167  aErrorEventInitDict.mBubbles = false;
   1168  aErrorEventInitDict.mCancelable = false;
   1169 
   1170  if (!aErrorEventInitDict.mFilename.IsEmpty()) {
   1171    return;
   1172  }
   1173 
   1174  RefPtr document = aEvent->GetAssociatedDocument();
   1175  if (!document) {
   1176    return;
   1177  }
   1178 
   1179  if (auto* uri = document->GetDocumentURI()) {
   1180    uri->GetSpec(aErrorEventInitDict.mFilename);
   1181  }
   1182 }
   1183 
   1184 nsresult Navigation::FireErrorEvent(const nsAString& aName,
   1185                                    const ErrorEventInit& aEventInitDict) {
   1186  RefPtr<Event> event = ErrorEvent::Constructor(this, aName, aEventInitDict);
   1187  ErrorResult rv;
   1188 
   1189  LogEvent(event, mOngoingNavigateEvent, "Fire"_ns);
   1190  DispatchEvent(*event, rv);
   1191  return rv.StealNSResult();
   1192 }
   1193 
   1194 // https://html.spec.whatwg.org/#resume-applying-the-traverse-history-step
   1195 static void ResumeApplyTheHistoryStep(
   1196    SessionHistoryInfo* aTarget, BrowsingContext* aTraversable,
   1197    UserNavigationInvolvement aUserInvolvement) {
   1198  MOZ_DIAGNOSTIC_ASSERT(aTraversable->IsTop());
   1199  auto* childSHistory = aTraversable->GetChildSessionHistory();
   1200  // Since we've already called #checking-if-unloading-is-canceled, we here pass
   1201  // checkForCancelation set to false.
   1202  childSHistory->AsyncGo(aTarget->NavigationKey(), aTraversable,
   1203                         /* aRequireUserInteraction */ false,
   1204                         /* aUserActivation */ false,
   1205                         /* aCheckForCancelation */ false, [](auto) {});
   1206 }
   1207 
   1208 struct NavigationWaitForAllScope final : public nsISupports,
   1209                                         public SupportsWeakPtr {
   1210  NavigationWaitForAllScope(Navigation* aNavigation,
   1211                            NavigationAPIMethodTracker* aApiMethodTracker,
   1212                            NavigateEvent* aEvent,
   1213                            NavigationDestination* aDestination)
   1214      : mNavigation(aNavigation),
   1215        mAPIMethodTracker(aApiMethodTracker),
   1216        mEvent(aEvent),
   1217        mDestination(aDestination) {}
   1218  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   1219  NS_DECL_CYCLE_COLLECTION_CLASS(NavigationWaitForAllScope)
   1220  RefPtr<Navigation> mNavigation;
   1221  RefPtr<NavigationAPIMethodTracker> mAPIMethodTracker;
   1222  RefPtr<NavigateEvent> mEvent;
   1223  RefPtr<NavigationDestination> mDestination;
   1224 
   1225 private:
   1226  ~NavigationWaitForAllScope() {}
   1227 
   1228 public:
   1229  // https://html.spec.whatwg.org/#process-navigate-event-handler-failure
   1230  MOZ_CAN_RUN_SCRIPT void ProcessNavigateEventHandlerFailure(
   1231      JS::Handle<JS::Value> aRejectionReason) {
   1232    // To process navigate event handler failure given a NavigateEvent object
   1233    // event and a reason:
   1234    LogEvent(mEvent, mEvent, "Rejected"_ns);
   1235 
   1236    // 1. If event's relevant global object's associated Document is not fully
   1237    //    active, then return.
   1238    if (RefPtr document = mEvent->GetDocument();
   1239        !document || !document->IsFullyActive()) {
   1240      return;
   1241    }
   1242 
   1243    // 2. If event's abort controller's signal is aborted, then return.
   1244    if (AbortSignal* signal = mEvent->Signal(); signal->Aborted()) {
   1245      return;
   1246    }
   1247 
   1248    // 3. Assert: event is event's relevant global object's navigation API's
   1249    //    ongoing navigate event.
   1250    MOZ_DIAGNOSTIC_ASSERT(mEvent == mNavigation->mOngoingNavigateEvent);
   1251 
   1252    // 4. If event's interception state is not "intercepted", then finish event
   1253    //    given false.
   1254    RefPtr event = mEvent;
   1255    if (mEvent->InterceptionState() !=
   1256        NavigateEvent::InterceptionState::Intercepted) {
   1257      event->Finish(false);
   1258    }
   1259 
   1260    // 5. Abort event given reason.
   1261    if (AutoJSAPI jsapi; !NS_WARN_IF(!jsapi.Init(mEvent->GetParentObject()))) {
   1262      RefPtr navigation = mNavigation;
   1263      navigation->AbortNavigateEvent(jsapi.cx(), event, aRejectionReason);
   1264    }
   1265  }
   1266  // https://html.spec.whatwg.org/#commit-a-navigate-event
   1267  MOZ_CAN_RUN_SCRIPT void CommitNavigateEvent(NavigationType aNavigationType) {
   1268    // 1. Let navigation be event's target.
   1269    // Omitted since Navigation is part of this's state.
   1270 
   1271    // 3. If event's relevant global object's associated Document is not fully
   1272    //    active, then return.
   1273    RefPtr document = mEvent->GetDocument();
   1274    if (!document || !document->IsFullyActive()) {
   1275      return;
   1276    }
   1277    // 2. Let navigable be event's relevant global object's navigable.
   1278    nsDocShell* docShell = nsDocShell::Cast(document->GetDocShell());
   1279    Maybe<BrowsingContext&> navigable =
   1280        ToMaybeRef(mNavigation->GetOwnerWindow()).andThen([](auto& aWindow) {
   1281          return ToMaybeRef(aWindow.GetBrowsingContext());
   1282        });
   1283    // 4. If event's abort controller's signal is aborted, then return.
   1284    if (AbortSignal* signal = mEvent->Signal(); signal->Aborted()) {
   1285      return;
   1286    }
   1287 
   1288    // 6. Let endResultIsSameDocument be true if event's interception state is
   1289    //    not "none" or event's destination's is same document is true.
   1290    const bool endResultIsSameDocument =
   1291        mEvent->InterceptionState() != NavigateEvent::InterceptionState::None ||
   1292        mDestination->SameDocument();
   1293 
   1294    // 7. Prepare to run script given navigation's relevant settings object.
   1295    // This runs step 12 when going out of scope.
   1296    nsAutoMicroTask mt;
   1297 
   1298    // 9. If event's interception state is not "none":
   1299    if (mEvent->InterceptionState() != NavigateEvent::InterceptionState::None) {
   1300      // The copy of the active session history info might be stale at this
   1301      // point, so make sure to update that. This is not a spec step, but a side
   1302      // effect of SHIP owning the session history entries making Navigation API
   1303      // keep copies for its purposes. Should navigation get aborted at this
   1304      // point, all we've done is eagerly stored scroll positions.
   1305      if (RefPtr current = mNavigation->GetCurrentEntry()) {
   1306        nsPoint scrollPos = docShell->GetCurScrollPos();
   1307        current->SessionHistoryInfo()->SetScrollPosition(scrollPos.x,
   1308                                                         scrollPos.y);
   1309      }
   1310 
   1311      // 5. Set event's interception state to "committed".
   1312      // See https://github.com/whatwg/html/issues/11830 for this change.
   1313      mEvent->SetInterceptionState(NavigateEvent::InterceptionState::Committed);
   1314      // 9.1 Switch on event's navigationType:
   1315      switch (aNavigationType) {
   1316        case NavigationType::Push:
   1317        case NavigationType::Replace:
   1318          // Run the URL and history update steps given event's relevant
   1319          // global object's associated Document and event's destination's
   1320          // URL, with serializedData set to event's classic history API
   1321          // state and historyHandling set to event's navigationType.
   1322          if (docShell) {
   1323            docShell->UpdateURLAndHistory(
   1324                document, mDestination->GetURL(),
   1325                mEvent->ClassicHistoryAPIState(),
   1326                *NavigationUtils::NavigationHistoryBehavior(aNavigationType),
   1327                document->GetDocumentURI(),
   1328                Equals(mDestination->GetURL(), document->GetDocumentURI()));
   1329          }
   1330          break;
   1331        case NavigationType::Reload:
   1332          // Update the navigation API entries for a same-document navigation
   1333          // given navigation, navigable's active session history entry, and
   1334          // "reload".
   1335          if (docShell) {
   1336            mNavigation->UpdateEntriesForSameDocumentNavigation(
   1337                docShell->GetActiveSessionHistoryInfo(), aNavigationType);
   1338          }
   1339          break;
   1340        case NavigationType::Traverse:
   1341          if (auto* entry = mDestination->GetEntry()) {
   1342            // 1. Set navigation's suppress normal scroll restoration during
   1343            //    ongoing navigation to true.
   1344            mNavigation
   1345                ->mSuppressNormalScrollRestorationDuringOngoingNavigation =
   1346                true;
   1347            // 2. Let userInvolvement be "none".
   1348            // 3. If event's userInitiated is true, then set userInvolvement to
   1349            // "activation".
   1350            UserNavigationInvolvement userInvolvement =
   1351                mEvent->UserInitiated() ? UserNavigationInvolvement::Activation
   1352                                        : UserNavigationInvolvement::None;
   1353            // 4. Append the following session history traversal steps to
   1354            //    navigable's traversable navigable:
   1355            // 4.1 Resume applying the traverse history step given event's
   1356            //     destination's entry's session history entry's step,
   1357            //     navigable's traversable navigable, and userInvolvement.
   1358            ResumeApplyTheHistoryStep(entry->SessionHistoryInfo(),
   1359                                      navigable->Top(), userInvolvement);
   1360 
   1361            // This is not in the spec, but both Chrome and Safari does this or
   1362            // something similar.
   1363            MOZ_ASSERT(entry->Index() >= 0);
   1364            mNavigation->SetCurrentEntryIndex(entry->SessionHistoryInfo());
   1365          }
   1366          break;
   1367        default:
   1368          break;
   1369      }
   1370    }
   1371    // 8. If navigation's transition is not null, then resolve navigation's
   1372    //    transition's committed promise with undefined.
   1373    // Steps 8 and 9 are swapped to have a consistent promise behavior
   1374    // (see https://github.com/whatwg/html/issues/11842)
   1375    if (mNavigation->mTransition) {
   1376      mNavigation->mTransition->Committed()->MaybeResolveWithUndefined();
   1377    }
   1378 
   1379    // 10. If endResultIsSameDocument is true:
   1380    if (endResultIsSameDocument) {
   1381      // 10.1 Let promisesList be an empty list.
   1382      AutoTArray<RefPtr<Promise>, 16> promiseList;
   1383 
   1384      if (StaticPrefs::dom_navigation_api_internal_method_tracker()) {
   1385        promiseList.AppendElement(mAPIMethodTracker->CommittedPromise());
   1386      }
   1387 
   1388      // 10.2 For each handler of event's navigation handler list:
   1389      for (auto& handler : mEvent->NavigationHandlerList().Clone()) {
   1390        // 10.2.1 Append the result of invoking handler with an empty
   1391        //        arguments list to promisesList.
   1392        RefPtr promise = MOZ_KnownLive(handler)->Call();
   1393        if (promise) {
   1394          promiseList.AppendElement(promise);
   1395        }
   1396      }
   1397      // 10.3 If promisesList's size is 0, then set promisesList to « a promise
   1398      //      resolved with undefined ».
   1399      nsCOMPtr globalObject = mNavigation->GetOwnerGlobal();
   1400      if (promiseList.IsEmpty()) {
   1401        RefPtr promise = Promise::CreateResolvedWithUndefined(
   1402            globalObject, IgnoredErrorResult());
   1403        if (promise) {
   1404          promiseList.AppendElement(promise);
   1405        }
   1406      }
   1407 
   1408      // 10.4 Wait for all of promisesList, with the following success steps:
   1409 
   1410      // If the committed promise in the api method tracker hasn't resolved yet,
   1411      // we can't run neither of the success nor failure steps. To handle that
   1412      // we set up a callback for when that resolves. This differs from how spec
   1413      // performs these steps, since spec can perform more of
   1414      // #apply-the-history-steps in a synchronous way.
   1415      auto cancelSteps =
   1416          [weakScope = WeakPtr(this)](JS::Handle<JS::Value> aRejectionReason)
   1417              MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
   1418                // If weakScope is null we've been cycle collected
   1419                if (weakScope) {
   1420                  RefPtr scope = weakScope.get();
   1421                  scope->ProcessNavigateEventHandlerFailure(aRejectionReason);
   1422                }
   1423              };
   1424      auto successSteps =
   1425          [weakScope = WeakPtr(this)](const Span<JS::Heap<JS::Value>>&)
   1426              MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
   1427                // If weakScope is null we've been cycle collected
   1428                if (weakScope) {
   1429                  RefPtr scope = weakScope.get();
   1430                  scope->CommitNavigateEventSuccessSteps();
   1431                }
   1432              };
   1433      if (mAPIMethodTracker &&
   1434          !StaticPrefs::dom_navigation_api_internal_method_tracker()) {
   1435        // Promise::WaitForAll marks all promises as handled, but since we're
   1436        // delaying wait for all one microtask, we need to manually mark them
   1437        // here.
   1438        for (auto& promise : promiseList) {
   1439          (void)promise->SetAnyPromiseIsHandled();
   1440        }
   1441 
   1442        LOG_FMTD("Waiting for committed");
   1443        mAPIMethodTracker->CommittedPromise()
   1444            ->AddCallbacksWithCycleCollectedArgs(
   1445                [successSteps, cancelSteps](
   1446                    JSContext*, JS::Handle<JS::Value>, ErrorResult&,
   1447                    nsIGlobalObject* aGlobalObject,
   1448                    const Span<RefPtr<Promise>>& aPromiseList,
   1449                    NavigationWaitForAllScope* aScope)
   1450                    MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
   1451                      Promise::WaitForAll(aGlobalObject, aPromiseList,
   1452                                          successSteps, cancelSteps, aScope);
   1453                    },
   1454                [](JSContext*, JS::Handle<JS::Value>, ErrorResult&,
   1455                   nsIGlobalObject*, const Span<RefPtr<Promise>>&,
   1456                   NavigationWaitForAllScope*) {},
   1457                nsCOMPtr(globalObject),
   1458                nsTArray<RefPtr<Promise>>(std::move(promiseList)),
   1459                RefPtr<NavigationWaitForAllScope>(this));
   1460      } else {
   1461        LOG_FMTD("No API method tracker, not waiting for committed");
   1462        // If we don't have an apiMethodTracker we can immediately start waiting
   1463        // for the promise list.
   1464        Promise::WaitForAll(globalObject, promiseList, successSteps,
   1465                            cancelSteps, this);
   1466      }
   1467    } else if (mAPIMethodTracker && mNavigation->mOngoingAPIMethodTracker) {
   1468      // In contrast to spec we add a check that we're still the ongoing
   1469      // tracker. If we're not, then we've already been cleaned up.
   1470      MOZ_DIAGNOSTIC_ASSERT(mAPIMethodTracker ==
   1471                            mNavigation->mOngoingAPIMethodTracker);
   1472      // Step 11
   1473      mAPIMethodTracker->CleanUp();
   1474      mNavigation->mOngoingNavigateEvent = nullptr;
   1475    } else {
   1476      // It needs to be ensured that the ongoing navigate event is cleared in
   1477      // every code path (e.g. for download events), so that we don't keep
   1478      // intermediate state around.
   1479      // See also https://github.com/whatwg/html/issues/11802
   1480      mNavigation->mOngoingNavigateEvent = nullptr;
   1481    }
   1482  }
   1483 
   1484  MOZ_CAN_RUN_SCRIPT void CommitNavigateEventSuccessSteps() {
   1485    LogEvent(mEvent, mEvent, "Success"_ns);
   1486 
   1487    // 1. If event's relevant global object is not fully active, then abort
   1488    //    these steps.
   1489    RefPtr document = mEvent->GetDocument();
   1490    if (!document || !document->IsFullyActive()) {
   1491      return;
   1492    }
   1493 
   1494    // 2. If event's abort controller's signal is aborted, then abort these
   1495    //    steps.
   1496    if (AbortSignal* signal = mEvent->Signal(); signal->Aborted()) {
   1497      return;
   1498    }
   1499 
   1500    // 3. Assert: event equals navigation's ongoing navigate event.
   1501    MOZ_DIAGNOSTIC_ASSERT(mEvent == mNavigation->mOngoingNavigateEvent);
   1502 
   1503    // 4. Set navigation's ongoing navigate event to null.
   1504    mNavigation->mOngoingNavigateEvent = nullptr;
   1505 
   1506    // 5. Finish event given true.
   1507    RefPtr event = mEvent;
   1508    event->Finish(true);
   1509 
   1510    // 6. If apiMethodTracker is non-null, then resolve the finished promise for
   1511    // apiMethodTracker.
   1512    if (mAPIMethodTracker) {
   1513      mAPIMethodTracker->ResolveFinishedPromise();
   1514    }
   1515 
   1516    // 7. Fire an event named navigatesuccess at navigation.
   1517    RefPtr navigation = mNavigation;
   1518    navigation->FireEvent(u"navigatesuccess"_ns);
   1519 
   1520    // 8. If navigation's transition is not null, then resolve navigation's
   1521    //    transition's finished promise with undefined.
   1522    if (mNavigation->mTransition) {
   1523      mNavigation->mTransition->Finished()->MaybeResolveWithUndefined();
   1524    }
   1525    // 9. Set navigation's transition to null.
   1526    mNavigation->mTransition = nullptr;
   1527  }
   1528 };
   1529 
   1530 NS_IMPL_CYCLE_COLLECTION_WEAK_PTR(NavigationWaitForAllScope, mNavigation,
   1531                                  mAPIMethodTracker, mEvent, mDestination)
   1532 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NavigationWaitForAllScope)
   1533  NS_INTERFACE_MAP_ENTRY(nsISupports)
   1534 NS_INTERFACE_MAP_END
   1535 
   1536 NS_IMPL_CYCLE_COLLECTING_ADDREF(NavigationWaitForAllScope)
   1537 NS_IMPL_CYCLE_COLLECTING_RELEASE(NavigationWaitForAllScope)
   1538 
   1539 already_AddRefed<NavigationAPIMethodTracker> CreateInternalTracker(
   1540    Navigation* aNavigation) {
   1541  RefPtr committedPromise =
   1542      Promise::CreateInfallible(aNavigation->GetOwnerGlobal());
   1543  (void)committedPromise->SetAnyPromiseIsHandled();
   1544  RefPtr finishedPromise = Promise::CreateResolvedWithUndefined(
   1545      aNavigation->GetOwnerGlobal(), IgnoreErrors());
   1546  return MakeAndAddRef<NavigationAPIMethodTracker>(
   1547      aNavigation, Nothing(), JS::UndefinedHandleValue,
   1548      /* aSerializedState */ nullptr,
   1549      /* aCommittedToEntry */ nullptr, committedPromise, finishedPromise);
   1550 }
   1551 
   1552 // https://html.spec.whatwg.org/#inner-navigate-event-firing-algorithm
   1553 bool Navigation::InnerFireNavigateEvent(
   1554    JSContext* aCx, NavigationType aNavigationType,
   1555    NavigationDestination* aDestination,
   1556    UserNavigationInvolvement aUserInvolvement, Element* aSourceElement,
   1557    FormData* aFormDataEntryList,
   1558    nsIStructuredCloneContainer* aClassicHistoryAPIState,
   1559    const nsAString& aDownloadRequestFilename,
   1560    NavigationAPIMethodTracker* aNavigationAPIMethodTracker) {
   1561  nsCOMPtr<nsIGlobalObject> globalObject = GetOwnerGlobal();
   1562  RefPtr apiMethodTracker = aNavigationAPIMethodTracker;
   1563 
   1564  // Step 1
   1565  if (HasEntriesAndEventsDisabled()) {
   1566    // Step 1.1 to step 1.3
   1567    MOZ_DIAGNOSTIC_ASSERT(!mOngoingAPIMethodTracker);
   1568    MOZ_DIAGNOSTIC_ASSERT(mUpcomingTraverseAPIMethodTrackers.IsEmpty());
   1569    MOZ_DIAGNOSTIC_ASSERT(!aNavigationAPIMethodTracker);
   1570 
   1571    // Step 1.5
   1572    return true;
   1573  }
   1574 
   1575  RootedDictionary<NavigateEventInit> init(RootingCx());
   1576 
   1577  // Step 2
   1578  MOZ_DIAGNOSTIC_ASSERT(!mOngoingAPIMethodTracker);
   1579 
   1580  // Step 3
   1581  Maybe<nsID> destinationKey;
   1582  if (auto* destinationEntry = aDestination->GetEntry()) {
   1583    // Step 3.1
   1584    MOZ_DIAGNOSTIC_ASSERT(!aNavigationAPIMethodTracker);
   1585    // Step 3.2
   1586    destinationKey.emplace(destinationEntry->Key());
   1587    // Step 3.3
   1588    MOZ_DIAGNOSTIC_ASSERT(!destinationKey->Equals(nsID{}));
   1589    // Step 3.4, 3.4.2
   1590    if (auto entry =
   1591            mUpcomingTraverseAPIMethodTrackers.Extract(*destinationKey)) {
   1592      // Step 3.4.1
   1593      apiMethodTracker = std::move(*entry);
   1594    }
   1595  }
   1596  // Step 4
   1597  if (apiMethodTracker) {
   1598    apiMethodTracker->MarkAsNotPending();
   1599  } else if (StaticPrefs::dom_navigation_api_internal_method_tracker()) {
   1600    apiMethodTracker = CreateInternalTracker(this);
   1601  }
   1602 
   1603  // This step is currently missing in the spec. See
   1604  // https://github.com/whatwg/html/issues/11816
   1605  mOngoingAPIMethodTracker = apiMethodTracker;
   1606 
   1607  // Step 5
   1608  Maybe<BrowsingContext&> navigable =
   1609      ToMaybeRef(GetOwnerWindow()).andThen([](auto& aWindow) {
   1610        return ToMaybeRef(aWindow.GetBrowsingContext());
   1611      });
   1612 
   1613  // Step 6
   1614  Document* document =
   1615      navigable.map([](auto& aNavigable) { return aNavigable.GetDocument(); })
   1616          .valueOr(nullptr);
   1617 
   1618  // Step7
   1619  init.mCanIntercept = document &&
   1620                       document->CanRewriteURL(aDestination->GetURL(),
   1621                                               /*aReportErrors*/ false) &&
   1622                       (aDestination->SameDocument() ||
   1623                        aNavigationType != NavigationType::Traverse);
   1624 
   1625  // Step 8
   1626  bool traverseCanBeCanceled =
   1627      navigable->IsTop() && aDestination->SameDocument() &&
   1628      (aUserInvolvement != UserNavigationInvolvement::BrowserUI ||
   1629       HasHistoryActionActivation(ToMaybeRef(GetOwnerWindow())));
   1630 
   1631  // Step 9
   1632  init.mCancelable =
   1633      aNavigationType != NavigationType::Traverse || traverseCanBeCanceled;
   1634 
   1635  // Step 11
   1636  init.mNavigationType = aNavigationType;
   1637 
   1638  // Step 12
   1639  init.mDestination = aDestination;
   1640 
   1641  // Step 13
   1642  init.mDownloadRequest = aDownloadRequestFilename;
   1643 
   1644  // Step 14
   1645  if (apiMethodTracker) {
   1646    init.mInfo = apiMethodTracker->mInfo;
   1647  }
   1648 
   1649  // Step 15
   1650  init.mHasUAVisualTransition =
   1651      HasUAVisualTransition(ToMaybeRef(GetAssociatedDocument()));
   1652 
   1653  // Step 16
   1654  init.mSourceElement = aSourceElement;
   1655 
   1656  // Step 17
   1657  RefPtr<AbortController> abortController = new AbortController(globalObject);
   1658 
   1659  // Step 18
   1660  init.mSignal = abortController->Signal();
   1661 
   1662  // step 19
   1663  nsCOMPtr<nsIURI> currentURL = document->GetDocumentURI();
   1664 
   1665  // step 20
   1666  init.mHashChange = !aClassicHistoryAPIState && aDestination->SameDocument() &&
   1667                     EqualsExceptRef(aDestination->GetURL(), currentURL) &&
   1668                     !HasIdenticalFragment(aDestination->GetURL(), currentURL);
   1669 
   1670  // Step 21
   1671  init.mUserInitiated = aUserInvolvement != UserNavigationInvolvement::None;
   1672 
   1673  // Step 22
   1674  init.mFormData = aFormDataEntryList;
   1675 
   1676  // Step 23
   1677  MOZ_DIAGNOSTIC_ASSERT(!mOngoingNavigateEvent);
   1678 
   1679  // We now have everything we need to fully initialize the NavigateEvent, so
   1680  // we'll go ahead and create it now. This is done by the spec in step 1 and
   1681  // step 2 of #fire-a-traverse-navigate-event,
   1682  // #fire-a-push/replace/reload-navigate-event, or
   1683  // #fire-a-download-request-navigate-event, but there's no reason to not
   1684  // delay it until here. This also performs step 12.
   1685  RefPtr<NavigateEvent> event = NavigateEvent::Constructor(
   1686      this, u"navigate"_ns, init, aClassicHistoryAPIState, abortController);
   1687  // Here we're running #concept-event-create from https://dom.spec.whatwg.org/
   1688  // which explicitly sets event's isTrusted attribute to true.
   1689  event->SetTrusted(true);
   1690 
   1691  // Step 24
   1692  mOngoingNavigateEvent = event;
   1693 
   1694  // Step 25
   1695  mFocusChangedDuringOngoingNavigation = false;
   1696 
   1697  // Step 26
   1698  mSuppressNormalScrollRestorationDuringOngoingNavigation = false;
   1699 
   1700  // Step 27 and step 28
   1701  LogEvent(event, mOngoingNavigateEvent, "Fire"_ns);
   1702  if (!DispatchEvent(*event, CallerType::NonSystem, IgnoreErrors())) {
   1703    // Step 28.1
   1704    if (aNavigationType == NavigationType::Traverse) {
   1705      ConsumeHistoryActionUserActivation(ToMaybeRef(GetOwnerWindow()));
   1706    }
   1707 
   1708    // Step 28.2
   1709    if (!abortController->Signal()->Aborted()) {
   1710      AbortOngoingNavigation(aCx);
   1711    }
   1712 
   1713    // Step 28.3
   1714    return false;
   1715  }
   1716 
   1717  // Step 29
   1718  if (event->InterceptionState() != NavigateEvent::InterceptionState::None) {
   1719    // Step 29.1
   1720    RefPtr<NavigationHistoryEntry> fromNHE = GetCurrentEntry();
   1721 
   1722    // Step 29.2
   1723    MOZ_DIAGNOSTIC_ASSERT(fromNHE);
   1724 
   1725    // Step 29.3
   1726    RefPtr<Promise> committedPromise = Promise::CreateInfallible(globalObject);
   1727    RefPtr<Promise> finishedPromise = Promise::CreateInfallible(globalObject);
   1728    mTransition = MakeAndAddRef<NavigationTransition>(
   1729        globalObject, aNavigationType, fromNHE, committedPromise,
   1730        finishedPromise);
   1731 
   1732    // Step 29.4
   1733    MOZ_ALWAYS_TRUE(committedPromise->SetAnyPromiseIsHandled());
   1734    // Step 29.5
   1735    MOZ_ALWAYS_TRUE(finishedPromise->SetAnyPromiseIsHandled());
   1736  }
   1737 
   1738  RefPtr scope = MakeRefPtr<NavigationWaitForAllScope>(this, apiMethodTracker,
   1739                                                       event, aDestination);
   1740  // Step 30
   1741  if (event->NavigationPrecommitHandlerList().IsEmpty()) {
   1742    LOG_FMTD("No precommit handlers, committing directly");
   1743    scope->CommitNavigateEvent(aNavigationType);
   1744  } else {
   1745    LOG_FMTD("Running {} precommit handlers",
   1746             event->NavigationPrecommitHandlerList().Length());
   1747    // Step 31.1
   1748    RefPtr precommitController =
   1749        new NavigationPrecommitController(event, globalObject);
   1750    // Step 31.2
   1751    nsTArray<RefPtr<Promise>> precommitPromiseList;
   1752    // Step 31.3
   1753    for (auto& handler : event->NavigationPrecommitHandlerList().Clone()) {
   1754      // Step 31.3.1
   1755      RefPtr promise = MOZ_KnownLive(handler)->Call(*precommitController);
   1756      if (promise) {
   1757        precommitPromiseList.AppendElement(promise);
   1758      }
   1759    }
   1760    // Step 31.4
   1761    Promise::WaitForAll(
   1762        globalObject, precommitPromiseList,
   1763        [weakScope = WeakPtr(scope),
   1764         aNavigationType](const Span<JS::Heap<JS::Value>>&)
   1765            MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
   1766              // If weakScope is null we've been cycle collected
   1767              if (!weakScope) {
   1768                return;
   1769              }
   1770              RefPtr scope = weakScope.get();
   1771              scope->CommitNavigateEvent(aNavigationType);
   1772            },
   1773        [weakScope = WeakPtr(scope)](JS::Handle<JS::Value> aRejectionReason)
   1774            MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
   1775              // If weakScope is null we've been cycle collected
   1776              if (!weakScope) {
   1777                return;
   1778              }
   1779              RefPtr scope = weakScope.get();
   1780              scope->ProcessNavigateEventHandlerFailure(aRejectionReason);
   1781            },
   1782        scope);
   1783  }
   1784  // Step 32 and 33
   1785  return event->InterceptionState() == NavigateEvent::InterceptionState::None;
   1786 }
   1787 
   1788 NavigationHistoryEntry* Navigation::FindNavigationHistoryEntry(
   1789    const SessionHistoryInfo& aSessionHistoryInfo) const {
   1790  for (const auto& navigationHistoryEntry : mEntries) {
   1791    if (navigationHistoryEntry->IsSameEntry(&aSessionHistoryInfo)) {
   1792      return navigationHistoryEntry;
   1793    }
   1794  }
   1795 
   1796  return nullptr;
   1797 }
   1798 
   1799 // https://html.spec.whatwg.org/#navigation-api-method-tracker-clean-up
   1800 /* static */ void Navigation::CleanUp(
   1801    NavigationAPIMethodTracker* aNavigationAPIMethodTracker) {
   1802  // Step 1
   1803  RefPtr<Navigation> navigation =
   1804      aNavigationAPIMethodTracker->mNavigationObject;
   1805 
   1806  auto needsTraverse =
   1807      MakeScopeExit([navigation]() { navigation->UpdateNeedsTraverse(); });
   1808 
   1809  // Step 2
   1810  if (navigation->mOngoingAPIMethodTracker == aNavigationAPIMethodTracker) {
   1811    navigation->mOngoingAPIMethodTracker = nullptr;
   1812 
   1813    return;
   1814  }
   1815 
   1816  // Step 3.1
   1817  Maybe<nsID> key = aNavigationAPIMethodTracker->mKey;
   1818 
   1819  // Step 3.2
   1820  MOZ_DIAGNOSTIC_ASSERT(key);
   1821 
   1822  // Step 3.3
   1823  MOZ_DIAGNOSTIC_ASSERT(
   1824      navigation->mUpcomingTraverseAPIMethodTrackers.Contains(*key));
   1825 
   1826  navigation->mUpcomingTraverseAPIMethodTrackers.Remove(*key);
   1827 }
   1828 
   1829 void Navigation::SetCurrentEntryIndex(const SessionHistoryInfo* aTargetInfo) {
   1830  mCurrentEntryIndex.reset();
   1831  if (auto* entry = FindNavigationHistoryEntry(*aTargetInfo)) {
   1832    MOZ_ASSERT(entry->Index() >= 0);
   1833    mCurrentEntryIndex = Some(entry->Index());
   1834    return;
   1835  }
   1836 
   1837  LOG_FMTW("Session history entry did not exist");
   1838 }
   1839 
   1840 // https://html.spec.whatwg.org/#inform-the-navigation-api-about-aborting-navigation
   1841 void Navigation::InnerInformAboutAbortingNavigation(JSContext* aCx) {
   1842  // As per https://github.com/whatwg/html/issues/11579, we should abort all
   1843  // ongoing navigate events within "inform the navigation API about aborting
   1844  // navigation".
   1845 
   1846  while (HasOngoingNavigateEvent()) {
   1847    AbortOngoingNavigation(aCx);
   1848  }
   1849 }
   1850 
   1851 // https://html.spec.whatwg.org/#abort-the-ongoing-navigation
   1852 void Navigation::AbortOngoingNavigation(JSContext* aCx,
   1853                                        JS::Handle<JS::Value> aError) {
   1854  // Step 1
   1855  RefPtr<NavigateEvent> event = mOngoingNavigateEvent;
   1856 
   1857  LogEvent(event, event, "Abort"_ns);
   1858 
   1859  // Step 2
   1860  MOZ_DIAGNOSTIC_ASSERT(event);
   1861 
   1862  // Step 3
   1863  mFocusChangedDuringOngoingNavigation = false;
   1864 
   1865  // Step 4
   1866  mSuppressNormalScrollRestorationDuringOngoingNavigation = false;
   1867 
   1868  JS::Rooted<JS::Value> error(aCx, aError);
   1869 
   1870  // Step 5
   1871  if (aError.isUndefined()) {
   1872    RefPtr<DOMException> exception =
   1873        DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
   1874    // It's OK if this fails, it just means that we'll get an empty error
   1875    // dictionary below.
   1876    GetOrCreateDOMReflector(aCx, exception, &error);
   1877  }
   1878 
   1879  // Step 6
   1880  if (event->IsBeingDispatched()) {
   1881    // Here NonSystem is needed since it needs to be the same as what we
   1882    // dispatch with.
   1883    event->PreventDefault(aCx, CallerType::NonSystem);
   1884  }
   1885 
   1886  // Step 7
   1887  AbortNavigateEvent(aCx, event, error);
   1888 }
   1889 
   1890 // https://html.spec.whatwg.org/#abort-a-navigateevent
   1891 void Navigation::AbortNavigateEvent(JSContext* aCx, NavigateEvent* aEvent,
   1892                                    JS::Handle<JS::Value> aReason) {
   1893  // 1. Let navigation be event's relevant global object's navigation API.
   1894  // Omitted since this is called from a Navigation object.
   1895 
   1896  // 2. Signal abort on event's abort controller given reason.
   1897  aEvent->AbortController()->Abort(aCx, aReason);
   1898 
   1899  // 3. Let errorInfo be the result of extracting error information from reason.
   1900  RootedDictionary<ErrorEventInit> init(aCx);
   1901  ExtractErrorInformation(aCx, aReason, init, aEvent);
   1902 
   1903  // 4. Set navigation's ongoing navigate event to null.
   1904  mOngoingNavigateEvent = nullptr;
   1905 
   1906  // 5. If navigation's ongoing API method tracker is non-null, then reject the
   1907  //    finished promise for apiMethodTracker with error.
   1908  if (mOngoingAPIMethodTracker) {
   1909    mOngoingAPIMethodTracker->RejectFinishedPromise(aReason);
   1910  }
   1911 
   1912  // 6. Fire an event named navigateerror at navigation using ErrorEvent, with
   1913  //    additional attributes initialized according to errorInfo.
   1914  FireErrorEvent(u"navigateerror"_ns, init);
   1915 
   1916  // 7. If navigation's transition is null, then return.
   1917  if (!mTransition) {
   1918    return;
   1919  }
   1920 
   1921  // 8. Reject navigation's transition's committed promise with error.
   1922  mTransition->Committed()->MaybeReject(aReason);
   1923  // 9. Reject navigation's transition's finished promise with error.
   1924  mTransition->Finished()->MaybeReject(aReason);
   1925 
   1926  // 10. Set navigation's transition to null.
   1927  mTransition = nullptr;
   1928 }
   1929 
   1930 // https://html.spec.whatwg.org/#inform-the-navigation-api-about-child-navigable-destruction
   1931 void Navigation::InformAboutChildNavigableDestruction(JSContext* aCx) {
   1932  // Step 3
   1933  auto traversalAPIMethodTrackers = mUpcomingTraverseAPIMethodTrackers.Clone();
   1934 
   1935  // Step 4
   1936  for (auto& apiMethodTracker : traversalAPIMethodTrackers.Values()) {
   1937    ErrorResult rv;
   1938    rv.ThrowAbortError("Navigable removed");
   1939    JS::Rooted<JS::Value> rootedExceptionValue(aCx);
   1940    MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &rootedExceptionValue));
   1941    apiMethodTracker->RejectFinishedPromise(rootedExceptionValue);
   1942  }
   1943 }
   1944 
   1945 bool Navigation::FocusedChangedDuringOngoingNavigation() const {
   1946  return mFocusChangedDuringOngoingNavigation;
   1947 }
   1948 
   1949 void Navigation::SetFocusedChangedDuringOngoingNavigation(
   1950    bool aFocusChangedDUringOngoingNavigation) {
   1951  mFocusChangedDuringOngoingNavigation = aFocusChangedDUringOngoingNavigation;
   1952 }
   1953 
   1954 bool Navigation::HasOngoingNavigateEvent() const {
   1955  return mOngoingNavigateEvent;
   1956 }
   1957 
   1958 // The associated document of navigation's relevant global object.
   1959 Document* Navigation::GetAssociatedDocument() const {
   1960  nsGlobalWindowInner* window = GetOwnerWindow();
   1961  return window ? window->GetDocument() : nullptr;
   1962 }
   1963 
   1964 void Navigation::UpdateNeedsTraverse() {
   1965  nsGlobalWindowInner* innerWindow = GetOwnerWindow();
   1966  if (!innerWindow) {
   1967    return;
   1968  }
   1969 
   1970  WindowContext* windowContext = innerWindow->GetWindowContext();
   1971  if (!windowContext) {
   1972    return;
   1973  }
   1974 
   1975  // Since we only care about optimizing for the traversable, bail if we're not
   1976  // the top-level context.
   1977  if (BrowsingContext* browsingContext = innerWindow->GetBrowsingContext();
   1978      !browsingContext || !browsingContext->IsTop()) {
   1979    return;
   1980  }
   1981 
   1982  // We need traverse if we have any method tracker.
   1983  bool needsTraverse =
   1984      mOngoingAPIMethodTracker || !mUpcomingTraverseAPIMethodTrackers.IsEmpty();
   1985 
   1986  // We need traverse if we have any event handlers.
   1987  if (EventListenerManager* eventListenerManager =
   1988          GetExistingListenerManager()) {
   1989    needsTraverse = needsTraverse || eventListenerManager->HasListeners();
   1990  }
   1991 
   1992  // Don't toggle if nothing's changed.
   1993  if (windowContext->GetNeedsTraverse() == needsTraverse) {
   1994    return;
   1995  }
   1996 
   1997  (void)windowContext->SetNeedsTraverse(needsTraverse);
   1998 }
   1999 
   2000 void Navigation::LogHistory() const {
   2001  if (!MOZ_LOG_TEST(gNavigationAPILog, LogLevel::Debug)) {
   2002    return;
   2003  }
   2004 
   2005  MOZ_LOG(gNavigationAPILog, LogLevel::Debug,
   2006          ("Navigation %p (current entry index: %d)\n", this,
   2007           mCurrentEntryIndex ? int(*mCurrentEntryIndex) : -1));
   2008  auto length = mEntries.Length();
   2009  for (uint64_t i = 0; i < length; i++) {
   2010    LogEntry(mEntries[i], i, length,
   2011             mCurrentEntryIndex && i == *mCurrentEntryIndex);
   2012  }
   2013 }
   2014 
   2015 // https://html.spec.whatwg.org/#set-up-a-navigate/reload-api-method-tracker
   2016 RefPtr<NavigationAPIMethodTracker>
   2017 Navigation::SetUpNavigateReloadAPIMethodTracker(
   2018    JS::Handle<JS::Value> aInfo,
   2019    nsIStructuredCloneContainer* aSerializedState) {
   2020  // To set up a navigate/reload API method tracker given a Navigation
   2021  // navigation, a JavaScript value info, and a serialized state-or-null
   2022  // serializedState:
   2023  // 1. Let committedPromise and finishedPromise be new promises created in
   2024  //    navigation's relevant realm.
   2025  RefPtr committedPromise = Promise::CreateInfallible(GetOwnerGlobal());
   2026  RefPtr finishedPromise = Promise::CreateInfallible(GetOwnerGlobal());
   2027  // 2. Mark as handled finishedPromise.
   2028  MOZ_ALWAYS_TRUE(finishedPromise->SetAnyPromiseIsHandled());
   2029 
   2030  // 3. Return a new navigation API method tracker with:
   2031  RefPtr<NavigationAPIMethodTracker> apiMethodTracker =
   2032      MakeAndAddRef<NavigationAPIMethodTracker>(
   2033          this, /* aKey */ Nothing{}, aInfo, aSerializedState,
   2034          /* aCommittedToEntry */ nullptr, committedPromise, finishedPromise,
   2035          /* aPending */ !HasEntriesAndEventsDisabled());
   2036 
   2037  return apiMethodTracker;
   2038 }
   2039 
   2040 // https://html.spec.whatwg.org/#add-an-upcoming-traverse-api-method-tracker
   2041 RefPtr<NavigationAPIMethodTracker>
   2042 Navigation::AddUpcomingTraverseAPIMethodTracker(const nsID& aKey,
   2043                                                JS::Handle<JS::Value> aInfo) {
   2044  // To add an upcoming traverse API method tracker given a Navigation
   2045  // navigation, a string destinationKey, and a JavaScript value info:
   2046  // 1. Let committedPromise and finishedPromise be new promises created in
   2047  //    navigation's relevant realm.
   2048  RefPtr committedPromise = Promise::CreateInfallible(GetOwnerGlobal());
   2049  RefPtr finishedPromise = Promise::CreateInfallible(GetOwnerGlobal());
   2050 
   2051  // 2. Mark as handled finishedPromise.
   2052  MOZ_ALWAYS_TRUE(finishedPromise->SetAnyPromiseIsHandled());
   2053 
   2054  // 3. Let apiMethodTracker be a new navigation API method tracker with:
   2055  RefPtr<NavigationAPIMethodTracker> apiMethodTracker =
   2056      MakeAndAddRef<NavigationAPIMethodTracker>(
   2057          this, Some(aKey), aInfo,
   2058          /* aSerializedState */ nullptr,
   2059          /* aCommittedToEntry */ nullptr, committedPromise, finishedPromise,
   2060          /* aPending */ false);
   2061 
   2062  // 4. Set navigation's upcoming traverse API method trackers[destinationKey]
   2063  //    to apiMethodTracker.
   2064  RefPtr methodTracker =
   2065      mUpcomingTraverseAPIMethodTrackers.InsertOrUpdate(aKey, apiMethodTracker);
   2066 
   2067  UpdateNeedsTraverse();
   2068 
   2069  // 5. Return apiMethodTracker.
   2070  return methodTracker;
   2071 }
   2072 
   2073 // https://html.spec.whatwg.org/#update-document-for-history-step-application
   2074 void Navigation::CreateNavigationActivationFrom(
   2075    SessionHistoryInfo* aPreviousEntryForActivation,
   2076    NavigationType aNavigationType) {
   2077  // Note: we do Step 7.1 at the end of method so we can both create and
   2078  // initialize the activation at once.
   2079  MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug,
   2080              "Creating NavigationActivation for from={}, type={}",
   2081              fmt::ptr(aPreviousEntryForActivation), aNavigationType);
   2082  RefPtr currentEntry = GetCurrentEntry();
   2083  if (!currentEntry) {
   2084    return;
   2085  }
   2086 
   2087  // Step 7.2. Let previousEntryIndex be the result of getting the navigation
   2088  // API entry index of previousEntryForActivation within navigation.
   2089  auto possiblePreviousEntry =
   2090      std::find_if(mEntries.begin(), mEntries.end(),
   2091                   [aPreviousEntryForActivation](const auto& entry) {
   2092                     return entry->IsSameEntry(aPreviousEntryForActivation);
   2093                   });
   2094 
   2095  // 3. If previousEntryIndex is non-negative, then set activation's old entry
   2096  // to navigation's entry list[previousEntryIndex].
   2097  RefPtr<NavigationHistoryEntry> oldEntry;
   2098  if (possiblePreviousEntry != mEntries.end()) {
   2099    MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug,
   2100                "Found previous entry at {}",
   2101                fmt::ptr(possiblePreviousEntry->get()));
   2102    oldEntry = *possiblePreviousEntry;
   2103  } else if (aNavigationType == NavigationType::Replace &&
   2104             !aPreviousEntryForActivation->IsTransient()) {
   2105    // 4. Otherwise, if all the following are true:
   2106    //     navigationType is "replace";
   2107    //     previousEntryForActivation's document state's origin is same origin
   2108    //     with document's origin; and previousEntryForActivation's document's
   2109    //     initial about:blank is false,
   2110    // then set activation's old entry to a new NavigationHistoryEntry in
   2111    // navigation's relevant realm, whose session history entry is
   2112    // previousEntryForActivation.
   2113 
   2114    nsCOMPtr previousURI =
   2115        aPreviousEntryForActivation->GetURIOrInheritedForAboutBlank();
   2116    nsCOMPtr currentURI =
   2117        currentEntry->SessionHistoryInfo()->GetURIOrInheritedForAboutBlank();
   2118    if (NS_SUCCEEDED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
   2119            currentURI, previousURI, false, false))) {
   2120      oldEntry = MakeRefPtr<NavigationHistoryEntry>(
   2121          GetOwnerGlobal(), aPreviousEntryForActivation, -1);
   2122      MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug,
   2123                  "Created a new entry at {}", fmt::ptr(oldEntry.get()));
   2124    }
   2125  }
   2126 
   2127  // 1. If navigation's activation is null, then set navigation's
   2128  // activation to a new NavigationActivation object in navigation's relevant
   2129  // realm.
   2130  // 5. Set activation's new entry to navigation's current entry.
   2131  // 6. Set activation's navigation type to navigationType.
   2132  mActivation = MakeRefPtr<NavigationActivation>(GetOwnerGlobal(), currentEntry,
   2133                                                 oldEntry, aNavigationType);
   2134 }
   2135 
   2136 // https://html.spec.whatwg.org/#dom-navigationprecommitcontroller-redirect
   2137 void Navigation::SetSerializedStateIntoOngoingAPIMethodTracker(
   2138    nsIStructuredCloneContainer* aSerializedState) {
   2139  MOZ_DIAGNOSTIC_ASSERT(mOngoingAPIMethodTracker);
   2140  // This is step 10.3 of NavigationPrecommitController.redirect()
   2141  mOngoingAPIMethodTracker->SetSerializedState(aSerializedState);
   2142 }
   2143 
   2144 }  // namespace mozilla::dom
   2145 
   2146 #undef LOG_FMTV
   2147 #undef LOG_FMTD
   2148 #undef LOG_FMTI
   2149 #undef LOG_FMTW
   2150 #undef LOG_FMTE