Bug 1959959 - Navigation API: Implement navigation.reload(). r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D246587
This commit is contained in:
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user