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);
|
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
|
NS_IMETHODIMP
|
||||||
nsDocShell::Reload(uint32_t aReloadFlags) {
|
nsDocShell::Reload(uint32_t aReloadFlags) {
|
||||||
if (!IsNavigationAllowed()) {
|
if (!IsNavigationAllowed()) {
|
||||||
|
|||||||
@@ -1131,6 +1131,12 @@ class nsDocShell final : public nsDocLoader,
|
|||||||
bool IsSameDocumentAsActiveEntry(
|
bool IsSameDocumentAsActiveEntry(
|
||||||
const mozilla::dom::SessionHistoryInfo& aSHInfo);
|
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:
|
private:
|
||||||
void SetCurrentURIInternal(nsIURI* aURI);
|
void SetCurrentURIInternal(nsIURI* aURI);
|
||||||
|
|
||||||
|
|||||||
@@ -4150,12 +4150,15 @@ class Document : public nsINode,
|
|||||||
--mThrowOnDynamicMarkupInsertionCounter;
|
--mThrowOnDynamicMarkupInsertionCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/#unload-counter
|
||||||
bool ShouldIgnoreOpens() const { return mIgnoreOpensDuringUnloadCounter; }
|
bool ShouldIgnoreOpens() const { return mIgnoreOpensDuringUnloadCounter; }
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/#unload-counter
|
||||||
void IncrementIgnoreOpensDuringUnloadCounter() {
|
void IncrementIgnoreOpensDuringUnloadCounter() {
|
||||||
++mIgnoreOpensDuringUnloadCounter;
|
++mIgnoreOpensDuringUnloadCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/#unload-counter
|
||||||
void DecrementIgnoreOpensDuringUnloadCounter() {
|
void DecrementIgnoreOpensDuringUnloadCounter() {
|
||||||
MOZ_ASSERT(mIgnoreOpensDuringUnloadCounter);
|
MOZ_ASSERT(mIgnoreOpensDuringUnloadCounter);
|
||||||
--mIgnoreOpensDuringUnloadCounter;
|
--mIgnoreOpensDuringUnloadCounter;
|
||||||
|
|||||||
@@ -13,11 +13,13 @@
|
|||||||
#include "nsCycleCollectionParticipant.h"
|
#include "nsCycleCollectionParticipant.h"
|
||||||
#include "nsDocShell.h"
|
#include "nsDocShell.h"
|
||||||
#include "nsGlobalWindowInner.h"
|
#include "nsGlobalWindowInner.h"
|
||||||
|
#include "nsIPrincipal.h"
|
||||||
#include "nsIStructuredCloneContainer.h"
|
#include "nsIStructuredCloneContainer.h"
|
||||||
#include "nsIXULRuntime.h"
|
#include "nsIXULRuntime.h"
|
||||||
#include "nsNetUtil.h"
|
#include "nsNetUtil.h"
|
||||||
#include "nsTHashtable.h"
|
#include "nsTHashtable.h"
|
||||||
|
|
||||||
|
#include "jsapi.h"
|
||||||
#include "mozilla/CycleCollectedJSContext.h"
|
#include "mozilla/CycleCollectedJSContext.h"
|
||||||
#include "mozilla/CycleCollectedUniquePtr.h"
|
#include "mozilla/CycleCollectedUniquePtr.h"
|
||||||
#include "mozilla/HoldDropJSObjects.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 {
|
namespace {
|
||||||
|
|
||||||
void LogEntry(NavigationHistoryEntry* aEntry, uint64_t aIndex, uint64_t aTotal,
|
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,
|
void Navigate(JSContext* aCx, const nsAString& aUrl,
|
||||||
const NavigationNavigateOptions& aOptions,
|
const NavigationNavigateOptions& aOptions,
|
||||||
NavigationResult& aResult) {}
|
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,
|
void TraverseTo(JSContext* aCx, const nsAString& aKey,
|
||||||
const NavigationOptions& aOptions,
|
const NavigationOptions& aOptions,
|
||||||
@@ -167,6 +169,18 @@ class Navigation final : public DOMEventTargetHelper {
|
|||||||
RefPtr<NavigationAPIMethodTracker> AddUpcomingTraverseAPIMethodTracker(
|
RefPtr<NavigationAPIMethodTracker> AddUpcomingTraverseAPIMethodTracker(
|
||||||
const nsID& aKey, JS::Handle<JS::Value> aInfo);
|
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);
|
static void CleanUp(NavigationAPIMethodTracker* aNavigationAPIMethodTracker);
|
||||||
|
|
||||||
void AbortOngoingNavigation(
|
void AbortOngoingNavigation(
|
||||||
|
|||||||
Reference in New Issue
Block a user