Bug 1959959 - Navigation API: Implement navigation.reload(). r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D246587
This commit is contained in:
Jan-Niklas Jaeschke
2025-04-28 16:39:03 +00:00
parent 53b9d29111
commit 219b770510
5 changed files with 235 additions and 2 deletions

View File

@@ -3891,6 +3891,61 @@ nsresult nsDocShell::LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI,
return InternalLoad(loadState);
}
// https://html.spec.whatwg.org/#reload
// To reload a navigable navigable given an optional serialized state-or-null
// navigationAPIState (default null) and an optional user navigation
// involvement userInvolvement (default "none"):
nsresult nsDocShell::ReloadNavigable(
JSContext* aCx, uint32_t aReloadFlags,
nsIStructuredCloneContainer* aNavigationAPIState,
UserNavigationInvolvement aUserInvolvement) {
// 1. If userInvolvement is not "browser UI", then:
if (aUserInvolvement != UserNavigationInvolvement::BrowserUI) {
// 1.1 Let navigation be navigable's active window's navigation API.
nsPIDOMWindowOuter* windowOuter = GetWindow();
MOZ_DIAGNOSTIC_ASSERT(windowOuter);
nsPIDOMWindowInner* windowInner = windowOuter->GetCurrentInnerWindow();
MOZ_DIAGNOSTIC_ASSERT(windowInner);
RefPtr navigation = windowInner->Navigation();
MOZ_DIAGNOSTIC_ASSERT(navigation);
// 1.2 Let destinationNavigationAPIState be navigable's active session
// history entry's navigation API state.
// 1.3 If navigationAPIState is not null, then set
// destinationNavigationAPIState to navigationAPIState.
RefPtr<nsIStructuredCloneContainer> destinationNavigationAPIState =
aNavigationAPIState;
if (!destinationNavigationAPIState) {
destinationNavigationAPIState =
mActiveEntry ? mActiveEntry->GetNavigationState() : nullptr;
}
// 1.4 Let continue be the result of firing a push/replace/reload navigate
// event at navigation with navigationType set to "reload",
// isSameDocument set to false, userInvolvement set to userInvolvement,
// destinationURL set to navigable's active session history entry's URL,
// and navigationAPIState set to destinationNavigationAPIState.
// 1.5 If continue is false, then return.
RefPtr destinationURL = mActiveEntry ? mActiveEntry->GetURI() : nullptr;
if (!navigation->FirePushReplaceReloadNavigateEvent(
aCx, NavigationType::Reload, destinationURL,
/* aIsSameDocument */ false, Some(aUserInvolvement),
/* aSourceElement*/ nullptr, /* aFormDataEntryList */ Nothing{},
destinationNavigationAPIState,
/* aClassiCHistoryAPIState */ nullptr)) {
return NS_OK;
}
}
// 2. Set navigable's active session history entry's document state's reload
// pending to true.
// 3. Let traversable be navigable's traversable navigable.
// 4. Append the following session history traversal steps to traversable:
// 4.1 Apply the reload history step to traversable given userInvolvement.
// XXX this is not complete yet. userInvolvement is not yet propagated,
// and the navigate event is not yet fired (https://bugzil.la/1962710)
return Reload(aReloadFlags);
}
NS_IMETHODIMP
nsDocShell::Reload(uint32_t aReloadFlags) {
if (!IsNavigationAllowed()) {

View File

@@ -1131,6 +1131,12 @@ class nsDocShell final : public nsDocLoader,
bool IsSameDocumentAsActiveEntry(
const mozilla::dom::SessionHistoryInfo& aSHInfo);
MOZ_CAN_RUN_SCRIPT nsresult
ReloadNavigable(JSContext* aCx, uint32_t aReloadFlags,
nsIStructuredCloneContainer* aNavigationAPIState = nullptr,
mozilla::dom::UserNavigationInvolvement aUserInvolvement =
mozilla::dom::UserNavigationInvolvement::None);
private:
void SetCurrentURIInternal(nsIURI* aURI);

View File

@@ -4150,12 +4150,15 @@ class Document : public nsINode,
--mThrowOnDynamicMarkupInsertionCounter;
}
// https://html.spec.whatwg.org/#unload-counter
bool ShouldIgnoreOpens() const { return mIgnoreOpensDuringUnloadCounter; }
// https://html.spec.whatwg.org/#unload-counter
void IncrementIgnoreOpensDuringUnloadCounter() {
++mIgnoreOpensDuringUnloadCounter;
}
// https://html.spec.whatwg.org/#unload-counter
void DecrementIgnoreOpensDuringUnloadCounter() {
MOZ_ASSERT(mIgnoreOpensDuringUnloadCounter);
--mIgnoreOpensDuringUnloadCounter;

View File

@@ -13,11 +13,13 @@
#include "nsCycleCollectionParticipant.h"
#include "nsDocShell.h"
#include "nsGlobalWindowInner.h"
#include "nsIPrincipal.h"
#include "nsIStructuredCloneContainer.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
#include "nsTHashtable.h"
#include "jsapi.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/CycleCollectedUniquePtr.h"
#include "mozilla/HoldDropJSObjects.h"
@@ -300,6 +302,159 @@ void Navigation::ScheduleEventsFromNavigation(
}));
}
// https://html.spec.whatwg.org/#navigation-api-early-error-result
void Navigation::SetEarlyErrorResult(NavigationResult& aResult,
ErrorResult&& aRv) const {
MOZ_ASSERT(aRv.Failed());
// An early error result for an exception e is a NavigationResult dictionary
// instance given by
// «[ "committed" → a promise rejected with e,
// "finished" → a promise rejected with e ]».
RefPtr global = GetOwnerGlobal();
if (!global) {
// Creating a promise should only fail if there is no global.
// In this case, the only solution is to ignore the error.
aRv.SuppressException();
return;
}
ErrorResult rv2;
aRv.CloneTo(rv2);
aResult.mCommitted.Reset();
aResult.mCommitted.Construct(
Promise::CreateRejectedWithErrorResult(global, aRv));
aResult.mFinished.Reset();
aResult.mFinished.Construct(
Promise::CreateRejectedWithErrorResult(global, rv2));
}
// https://html.spec.whatwg.org/#navigation-api-method-tracker-derived-result
static void CreateResultFromAPIMethodTracker(
NavigationAPIMethodTracker* aApiMethodTracker, NavigationResult& aResult) {
// A navigation API method tracker-derived result for a navigation API
// method tracker is a NavigationResult dictionary instance given by
// «[ "committed" → apiMethodTracker's committed promise,
// "finished" → apiMethodTracker's finished promise ]».
MOZ_ASSERT(aApiMethodTracker);
aResult.mCommitted.Reset();
aResult.mCommitted.Construct(aApiMethodTracker->mCommittedPromise.forget());
aResult.mFinished.Reset();
aResult.mFinished.Construct(aApiMethodTracker->mFinishedPromise.forget());
}
bool Navigation::CheckIfDocumentIsFullyActiveAndMaybeSetEarlyErrorResult(
const Document* aDocument, NavigationResult& aResult) const {
if (!aDocument || !aDocument->IsFullyActive()) {
ErrorResult rv;
rv.ThrowInvalidStateError("Document is not fully active");
SetEarlyErrorResult(aResult, std::move(rv));
return false;
}
return true;
}
bool Navigation::CheckDocumentUnloadCounterAndMaybeSetEarlyErrorResult(
const Document* aDocument, NavigationResult& aResult) const {
if (!aDocument || aDocument->ShouldIgnoreOpens()) {
ErrorResult rv;
rv.ThrowInvalidStateError("Document is unloading");
SetEarlyErrorResult(aResult, std::move(rv));
return false;
}
return true;
}
already_AddRefed<nsIStructuredCloneContainer>
Navigation::CreateSerializedStateAndMaybeSetEarlyErrorResult(
JSContext* aCx, const JS::Value& aState, NavigationResult& aResult) const {
JS::Rooted<JS::Value> state(aCx, aState);
RefPtr global = GetOwnerGlobal();
MOZ_DIAGNOSTIC_ASSERT(global);
RefPtr<nsIStructuredCloneContainer> serializedState =
new nsStructuredCloneContainer();
const nsresult rv = serializedState->InitFromJSVal(state, aCx);
if (NS_FAILED(rv)) {
JS::Rooted<JS::Value> exception(aCx);
if (JS_GetPendingException(aCx, &exception)) {
JS_ClearPendingException(aCx);
aResult.mCommitted.Reset();
aResult.mCommitted.Construct(
Promise::Reject(global, exception, IgnoreErrors()));
aResult.mFinished.Reset();
aResult.mFinished.Construct(
Promise::Reject(global, exception, IgnoreErrors()));
return nullptr;
}
SetEarlyErrorResult(aResult, ErrorResult(rv));
return nullptr;
}
return serializedState.forget();
}
// https://html.spec.whatwg.org/#dom-navigation-reload
void Navigation::Reload(JSContext* aCx, const NavigationReloadOptions& aOptions,
NavigationResult& aResult) {
// 1. Let document be this's relevant global object's associated Document.
const RefPtr<Document> document = GetDocumentIfCurrent();
if (!document) {
return;
}
// 2. Let serializedState be StructuredSerializeForStorage(undefined).
RefPtr<nsIStructuredCloneContainer> serializedState;
// 3. If options["state"] exists, then set serializedState to
// StructuredSerializeForStorage(options["state"]). If this throws an
// exception, then return an early error result for that exception.
if (!aOptions.mState.isUndefined()) {
serializedState = CreateSerializedStateAndMaybeSetEarlyErrorResult(
aCx, aOptions.mState, aResult);
if (!serializedState) {
return;
}
} else {
// 4. Otherwise:
// 4.1 Let current be the current entry of this.
// 4.2 If current is not null, then set serializedState to current's
// session history entry's navigation API state.
if (RefPtr<NavigationHistoryEntry> current = GetCurrentEntry()) {
serializedState = current->GetNavigationState();
}
}
// 5. If document is not fully active, then return an early error result for
// an "InvalidStateError" DOMException.
if (!CheckIfDocumentIsFullyActiveAndMaybeSetEarlyErrorResult(document,
aResult)) {
return;
}
// 6. If document's unload counter is greater than 0, then return an early
// error result for an "InvalidStateError" DOMException.
if (!CheckDocumentUnloadCounterAndMaybeSetEarlyErrorResult(document,
aResult)) {
return;
}
// 7. Let info be options["info"], if it exists; otherwise, undefined.
JS::Rooted<JS::Value> info(aCx, aOptions.mInfo);
// 8. Let apiMethodTracker be the result of maybe setting the upcoming
// non-traverse API method tracker for this given info and serializedState.
RefPtr<NavigationAPIMethodTracker> apiMethodTracker =
MaybeSetUpcomingNonTraverseAPIMethodTracker(info, serializedState);
MOZ_ASSERT(apiMethodTracker);
// 9. Reload document's node navigable with navigationAPIState set to
// serializedState.
RefPtr docShell = nsDocShell::Cast(document->GetDocShell());
MOZ_ASSERT(docShell);
docShell->ReloadNavigable(aCx, nsIWebNavigation::LOAD_FLAGS_NONE,
serializedState);
// 10. Return a navigation API method tracker-derived result for
// apiMethodTracker.
CreateResultFromAPIMethodTracker(apiMethodTracker, aResult);
}
namespace {
void LogEntry(NavigationHistoryEntry* aEntry, uint64_t aIndex, uint64_t aTotal,

View File

@@ -66,8 +66,10 @@ class Navigation final : public DOMEventTargetHelper {
void Navigate(JSContext* aCx, const nsAString& aUrl,
const NavigationNavigateOptions& aOptions,
NavigationResult& aResult) {}
void Reload(JSContext* aCx, const NavigationReloadOptions& aOptions,
NavigationResult& aResult) {}
MOZ_CAN_RUN_SCRIPT void Reload(JSContext* aCx,
const NavigationReloadOptions& aOptions,
NavigationResult& aResult);
void TraverseTo(JSContext* aCx, const nsAString& aKey,
const NavigationOptions& aOptions,
@@ -167,6 +169,18 @@ class Navigation final : public DOMEventTargetHelper {
RefPtr<NavigationAPIMethodTracker> AddUpcomingTraverseAPIMethodTracker(
const nsID& aKey, JS::Handle<JS::Value> aInfo);
void SetEarlyErrorResult(NavigationResult& aResult, ErrorResult&& aRv) const;
bool CheckIfDocumentIsFullyActiveAndMaybeSetEarlyErrorResult(
const Document* aDocument, NavigationResult& aResult) const;
bool CheckDocumentUnloadCounterAndMaybeSetEarlyErrorResult(
const Document* aDocument, NavigationResult& aResult) const;
already_AddRefed<nsIStructuredCloneContainer>
CreateSerializedStateAndMaybeSetEarlyErrorResult(
JSContext* aCx, const JS::Value& aState, NavigationResult& aResult) const;
static void CleanUp(NavigationAPIMethodTracker* aNavigationAPIMethodTracker);
void AbortOngoingNavigation(