tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mdom/events/EventTarget.h | 7+++++++
Mdom/events/NavigateEvent.cpp | 4++++
Mdom/events/NavigateEvent.h | 6+++++-
Mdom/navigation/Navigation.cpp | 8++++++++
Mdom/navigation/Navigation.h | 25+++++++++++++++++++++++++
Mdom/navigation/NavigationDestination.cpp | 6++++++
Mdom/navigation/NavigationDestination.h | 2++
Adom/navigation/NavigationPrecommitController.cpp | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adom/navigation/NavigationPrecommitController.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/navigation/NavigationUtils.cpp | 14++++++++++++++
Mdom/navigation/NavigationUtils.h | 3+++
Mdom/navigation/moz.build | 2++
Adom/webidl/NavigationPrecommitController.webidl | 15+++++++++++++++
Mdom/webidl/moz.build | 1+
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",