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