commit fc022d4a4882b940735c118e8d70d40615513417
parent c2905dccab779c0bb37957234b5291107b3c04cf
Author: Jan-Niklas Jaeschke <jjaschke@mozilla.com>
Date: Tue, 28 Oct 2025 17:18:25 +0000
Bug 1970123, part 1 - Navigation API: Implement `NavigationPrecommitController`. r=dom-core,webidl,smaug
Differential Revision: https://phabricator.services.mozilla.com/D270087
Diffstat:
14 files changed, 303 insertions(+), 1 deletion(-)
diff --git a/dom/events/EventTarget.h b/dom/events/EventTarget.h
@@ -36,6 +36,7 @@ class EventListener;
class EventListenerOptionsOrBoolean;
class EventHandlerNonNull;
class GlobalObject;
+class Navigation;
class WindowProxyHolder;
enum class CallerType : uint32_t;
enum class EventCallbackDebuggerNotificationType : uint8_t;
@@ -137,6 +138,12 @@ class EventTarget : public nsISupports, public nsWrapperCache {
inline nsINode* AsNode();
inline const nsINode* AsNode() const;
+ virtual bool IsNavigation() const { return false; }
+ inline Navigation* GetAsNavigation();
+ inline const Navigation* GetAsNavigation() const;
+ inline Navigation* AsNavigation();
+ inline const Navigation* AsNavigation() const;
+
virtual bool IsInnerWindow() const { return false; }
virtual bool IsOuterWindow() const { return false; }
virtual bool IsRootWindow() const { return false; }
diff --git a/dom/events/NavigateEvent.cpp b/dom/events/NavigateEvent.cpp
@@ -88,6 +88,10 @@ already_AddRefed<NavigateEvent> NavigateEvent::Constructor(
NavigationType NavigateEvent::NavigationType() const { return mNavigationType; }
+void NavigateEvent::SetNavigationType(enum NavigationType aNavigationType) {
+ mNavigationType = aNavigationType;
+}
+
already_AddRefed<NavigationDestination> NavigateEvent::Destination() const {
return do_AddRef(mDestination);
}
diff --git a/dom/events/NavigateEvent.h b/dom/events/NavigateEvent.h
@@ -52,6 +52,8 @@ class NavigateEvent final : public Event {
enum NavigationType NavigationType() const;
+ void SetNavigationType(enum NavigationType aNavigationType);
+
already_AddRefed<NavigationDestination> Destination() const;
bool CanIntercept() const;
@@ -68,6 +70,8 @@ class NavigateEvent final : public Event {
void GetInfo(JSContext* aCx, JS::MutableHandle<JS::Value> aInfo) const;
+ void SetInfo(JS::Value aInfo) { mInfo = aInfo; }
+
bool HasUAVisualTransition() const;
Element* GetSourceElement() const;
@@ -104,9 +108,9 @@ class NavigateEvent final : public Event {
MOZ_CAN_RUN_SCRIPT
void Finish(bool aDidFulfill);
- private:
void PerformSharedChecks(ErrorResult& aRv);
+ private:
void PotentiallyResetFocus();
MOZ_CAN_RUN_SCRIPT
diff --git a/dom/navigation/Navigation.cpp b/dom/navigation/Navigation.cpp
@@ -1956,6 +1956,14 @@ void Navigation::CreateNavigationActivationFrom(
oldEntry, aNavigationType);
}
+// https://html.spec.whatwg.org/#dom-navigationprecommitcontroller-redirect
+void Navigation::SetSerializedStateIntoOngoingAPIMethodTracker(
+ nsIStructuredCloneContainer* aSerializedState) {
+ MOZ_DIAGNOSTIC_ASSERT(mOngoingAPIMethodTracker);
+ // This is step 10.3 of NavigationPrecommitController.redirect()
+ mOngoingAPIMethodTracker->SetSerializedState(aSerializedState);
+}
+
} // namespace mozilla::dom
#undef LOG_FMTV
diff --git a/dom/navigation/Navigation.h b/dom/navigation/Navigation.h
@@ -53,6 +53,9 @@ struct NavigationAPIMethodTracker final : public nsISupports {
void ResolveFinishedPromise();
void RejectFinishedPromise(JS::Handle<JS::Value> aException);
void CreateResult(JSContext* aCx, NavigationResult& aResult);
+ void SetSerializedState(nsIStructuredCloneContainer* aSerializedState) {
+ mSerializedState = aSerializedState;
+ }
Promise* CommittedPromise() { return mCommittedPromise; }
Promise* FinishedPromise() { return mFinishedPromise; }
@@ -78,6 +81,10 @@ class Navigation final : public DOMEventTargetHelper {
explicit Navigation(nsPIDOMWindowInner* aWindow);
+ bool IsNavigation() const override { return true; }
+
+ NS_IMPL_FROMEVENTTARGET_HELPER(Navigation, IsNavigation())
+
using EventTarget::EventListenerAdded;
virtual void EventListenerAdded(nsAtom* aType) override;
using EventTarget::EventListenerRemoved;
@@ -184,6 +191,9 @@ class Navigation final : public DOMEventTargetHelper {
SessionHistoryInfo* aPreviousEntryForActivation,
NavigationType aNavigationType);
+ void SetSerializedStateIntoOngoingAPIMethodTracker(
+ nsIStructuredCloneContainer* aSerializedState);
+
private:
friend struct NavigationAPIMethodTracker;
using UpcomingTraverseAPIMethodTrackers =
@@ -282,6 +292,21 @@ class Navigation final : public DOMEventTargetHelper {
RefPtr<NavigationActivation> mActivation;
};
+inline Navigation* EventTarget::GetAsNavigation() {
+ return IsNavigation() ? AsNavigation() : nullptr;
+}
+inline const Navigation* EventTarget::GetAsNavigation() const {
+ return IsNavigation() ? AsNavigation() : nullptr;
+}
+inline Navigation* EventTarget::AsNavigation() {
+ MOZ_DIAGNOSTIC_ASSERT(IsNavigation());
+ return static_cast<Navigation*>(this);
+}
+inline const Navigation* EventTarget::AsNavigation() const {
+ MOZ_DIAGNOSTIC_ASSERT(IsNavigation());
+ return static_cast<const Navigation*>(this);
+}
+
} // namespace mozilla::dom
template <>
diff --git a/dom/navigation/NavigationDestination.cpp b/dom/navigation/NavigationDestination.cpp
@@ -96,6 +96,10 @@ void NavigationDestination::GetState(JSContext* aCx,
}
}
+void NavigationDestination::SetState(nsIStructuredCloneContainer* aState) {
+ mState = aState;
+}
+
JSObject* NavigationDestination::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return NavigationDestination_Binding::Wrap(aCx, this, aGivenProto);
@@ -109,4 +113,6 @@ NavigationHistoryEntry* NavigationDestination::GetEntry() const {
nsIURI* NavigationDestination::GetURL() const { return mURL; }
+void NavigationDestination::SetURL(nsIURI* aURI) { mURL = aURI; }
+
} // namespace mozilla::dom
diff --git a/dom/navigation/NavigationDestination.h b/dom/navigation/NavigationDestination.h
@@ -41,6 +41,7 @@ class NavigationDestination final : public nsISupports, public nsWrapperCache {
bool SameDocument() const;
void GetState(JSContext* aCx, JS::MutableHandle<JS::Value> aRetVal,
ErrorResult& aRv) const;
+ void SetState(nsIStructuredCloneContainer* aState);
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
@@ -48,6 +49,7 @@ class NavigationDestination final : public nsISupports, public nsWrapperCache {
NavigationHistoryEntry* GetEntry() const;
nsIURI* GetURL() const;
+ void SetURL(nsIURI* aURI);
private:
~NavigationDestination() = default;
diff --git a/dom/navigation/NavigationPrecommitController.cpp b/dom/navigation/NavigationPrecommitController.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NavigationPrecommitController.h"
+
+#include "Navigation.h"
+#include "NavigationUtils.h"
+#include "mozilla/dom/NavigateEvent.h"
+#include "mozilla/dom/NavigationPrecommitControllerBinding.h"
+#include "nsNetUtil.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(NavigationPrecommitController,
+ mGlobalObject, mEvent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(NavigationPrecommitController)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(NavigationPrecommitController)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NavigationPrecommitController)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NavigationPrecommitController::NavigationPrecommitController(
+ NavigateEvent* aEvent, nsIGlobalObject* aGlobalObject)
+ : mGlobalObject(aGlobalObject), mEvent(aEvent) {}
+
+NavigationPrecommitController::~NavigationPrecommitController() {}
+
+JSObject* NavigationPrecommitController::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return NavigationPrecommitController_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsIGlobalObject* NavigationPrecommitController::GetParentObject() const {
+ return mGlobalObject;
+}
+
+// https://html.spec.whatwg.org/#dom-navigationprecommitcontroller-redirect
+void NavigationPrecommitController::Redirect(
+ JSContext* aCx, const nsAString& aUrl,
+ const NavigationNavigateOptions& aOptions, ErrorResult& aRv) {
+ // The redirect(url, options) method steps are:
+
+ // 1. Assert: this's event's interception state is not "none".
+ MOZ_DIAGNOSTIC_ASSERT(mEvent);
+ MOZ_DIAGNOSTIC_ASSERT(mEvent->InterceptionState() !=
+ NavigateEvent::InterceptionState::None);
+
+ // 2. Perform shared checks given this's event.
+ mEvent->PerformSharedChecks(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // 3. If this's event's interception state is not "intercepted", then throw an
+ // "InvalidStateError" DOMException.
+ if (mEvent->InterceptionState() !=
+ NavigateEvent::InterceptionState::Intercepted) {
+ aRv.ThrowInvalidStateError(
+ "Expected interception state to be 'intercepted'");
+ return;
+ }
+
+ // 4. If this's event's navigationType is neither "push" nor "replace", then
+ // throw an "InvalidStateError" DOMException.
+ if (mEvent->NavigationType() != NavigationType::Push &&
+ mEvent->NavigationType() != NavigationType::Replace) {
+ aRv.ThrowInvalidStateError(
+ "Expected navigation type to be 'push' or 'replace'");
+ return;
+ }
+
+ // 5. Let document be this's relevant global object's associated Document.
+ RefPtr<Document> document = mEvent->GetDocument();
+ if (!document) {
+ aRv.ThrowInvalidStateError("Document is not available");
+ return;
+ }
+ // 6. Let destinationURL be the result of parsing url given document.
+ RefPtr<nsIURI> destinationURL;
+ nsresult res = NS_NewURI(getter_AddRefs(destinationURL), aUrl, nullptr,
+ document->GetDocBaseURI());
+
+ // 7. If destinationURL is failure, then throw a "SyntaxError" DOMException.
+ if (NS_FAILED(res)) {
+ aRv.ThrowSyntaxError("URL given to navigate() is invalid");
+ return;
+ }
+
+ // 8. If document cannot have its URL rewritten to destinationURL, then throw
+ // a "SecurityError" DOMException.
+ if (!document->CanRewriteURL(destinationURL)) {
+ aRv.ThrowSecurityError("Cannot rewrite URL to the given destination URL");
+ return;
+ }
+
+ // 9. If options["history"] is "push" or "replace", then set this's event's
+ // navigationType to options["history"].
+ if (aOptions.mHistory == NavigationHistoryBehavior::Push ||
+ aOptions.mHistory == NavigationHistoryBehavior::Replace) {
+ mEvent->SetNavigationType(
+ *NavigationUtils::NavigationTypeFromNavigationHistoryBehavior(
+ aOptions.mHistory));
+ }
+ RefPtr destination = mEvent->Destination();
+
+ // 10. If options["state"] exists, then:
+ if (!aOptions.mState.isUndefined()) {
+ // 10.1 Let serializedState be the result of calling
+ // StructuredSerializeForStorage(options["state"]). This may throw an
+ // exception.
+ RefPtr<nsIStructuredCloneContainer> serializedState =
+ new nsStructuredCloneContainer();
+ JS::Rooted<JS::Value> state(aCx, aOptions.mState);
+
+ 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);
+ aRv.ThrowJSException(aCx, exception);
+ } else {
+ // The spec only mentions that this might throw, but the tests expect
+ // the DataCloneError.
+ aRv.ThrowDataCloneError("Failed to serialize value for redirect().");
+ }
+ return;
+ }
+
+ // 10.2 Set this's event's destination's state to serializedState.
+ destination->SetState(serializedState);
+
+ // 10.3 Set this's event's target's ongoing API method tracker's serialized
+ // state to serializedState.
+ if (Navigation* target =
+ Navigation::FromEventTargetOrNull(mEvent->GetTarget())) {
+ target->SetSerializedStateIntoOngoingAPIMethodTracker(serializedState);
+ }
+ }
+
+ // 11. Set this's event's destination's URL to destinationURL.
+ destination->SetURL(destinationURL);
+ // 12. If options["info"] exists, then set this's event's info to
+ // options["info"].
+ if (!aOptions.mInfo.isUndefined()) {
+ mEvent->SetInfo(aOptions.mInfo);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/navigation/NavigationPrecommitController.h b/dom/navigation/NavigationPrecommitController.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_NAVIGATIONPRECOMMITCONTROLLER_H_
+#define DOM_NAVIGATIONPRECOMMITCONTROLLER_H_
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+struct NavigationNavigateOptions;
+class NavigateEvent;
+} // namespace mozilla::dom
+
+class nsIGlobalObject;
+
+namespace mozilla::dom {
+
+class NavigationPrecommitController final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(NavigationPrecommitController)
+
+ public:
+ NavigationPrecommitController(NavigateEvent* aEvent,
+ nsIGlobalObject* aGlobalObject);
+
+ protected:
+ ~NavigationPrecommitController();
+
+ public:
+ nsIGlobalObject* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // https://html.spec.whatwg.org/#dom-navigationprecommitcontroller-redirect
+ void Redirect(JSContext* aCx, const nsAString& aUrl,
+ const NavigationNavigateOptions& aOptions, ErrorResult& aRv);
+
+ private:
+ nsCOMPtr<nsIGlobalObject> mGlobalObject;
+
+ RefPtr<NavigateEvent> mEvent;
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_NAVIGATIONPRECOMMITCONTROLLER_H_
diff --git a/dom/navigation/NavigationUtils.cpp b/dom/navigation/NavigationUtils.cpp
@@ -24,6 +24,20 @@ NavigationUtils::NavigationHistoryBehavior(NavigationType aNavigationType) {
return Nothing();
}
+/*static*/ Maybe<NavigationType>
+NavigationUtils::NavigationTypeFromNavigationHistoryBehavior(
+ enum NavigationHistoryBehavior aBehavior) {
+ switch (aBehavior) {
+ case NavigationHistoryBehavior::Push:
+ return Some(NavigationType::Push);
+ case NavigationHistoryBehavior::Replace:
+ return Some(NavigationType::Replace);
+ default:
+ break;
+ }
+ return Nothing();
+}
+
/* static */
Maybe<NavigationType> NavigationUtils::NavigationTypeFromLoadType(
uint32_t aLoadType) {
diff --git a/dom/navigation/NavigationUtils.h b/dom/navigation/NavigationUtils.h
@@ -18,6 +18,9 @@ class NavigationUtils {
static Maybe<enum NavigationHistoryBehavior> NavigationHistoryBehavior(
NavigationType aNavigationType);
+ static Maybe<NavigationType> NavigationTypeFromNavigationHistoryBehavior(
+ enum NavigationHistoryBehavior aBehavior);
+
static Maybe<NavigationType> NavigationTypeFromLoadType(uint32_t aLoadType);
};
diff --git a/dom/navigation/moz.build b/dom/navigation/moz.build
@@ -12,6 +12,7 @@ EXPORTS.mozilla.dom += [
"NavigationActivation.h",
"NavigationDestination.h",
"NavigationHistoryEntry.h",
+ "NavigationPrecommitController.h",
"NavigationTransition.h",
"NavigationUtils.h",
"UserNavigationInvolvement.h",
@@ -22,6 +23,7 @@ UNIFIED_SOURCES += [
"NavigationActivation.cpp",
"NavigationDestination.cpp",
"NavigationHistoryEntry.cpp",
+ "NavigationPrecommitController.cpp",
"NavigationTransition.cpp",
"NavigationUtils.cpp",
]
diff --git a/dom/webidl/NavigationPrecommitController.webidl b/dom/webidl/NavigationPrecommitController.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://html.spec.whatwg.org/#the-navigationprecommitcontroller-interface
+ */
+[Func="Navigation::IsAPIEnabled", Exposed=Window]
+interface NavigationPrecommitController {
+ [Throws]
+ undefined redirect(USVString url, optional NavigationNavigateOptions options = {});
+};
+
+callback NavigationPrecommitHandler = Promise<undefined>(NavigationPrecommitController controller);
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
@@ -809,6 +809,7 @@ WEBIDL_FILES = [
"NavigationActivation.webidl",
"NavigationDestination.webidl",
"NavigationHistoryEntry.webidl",
+ "NavigationPrecommitController.webidl",
"NavigationPreloadManager.webidl",
"NavigationTransition.webidl",
"Navigator.webidl",