/* -*- 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 "nsGlobalWindowInner.h" #include "nsDocShell.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/PresShell.h" #include "mozilla/dom/AbortController.h" #include "mozilla/dom/NavigateEvent.h" #include "mozilla/dom/NavigateEventBinding.h" #include "mozilla/dom/Navigation.h" #include "mozilla/dom/SessionHistoryEntry.h" namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(NavigateEvent, Event, (mDestination, mSignal, mFormData, mSourceElement, mNavigationHandlerList, mAbortController), (mInfo)) NS_IMPL_ADDREF_INHERITED(NavigateEvent, Event) NS_IMPL_RELEASE_INHERITED(NavigateEvent, Event) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NavigateEvent) NS_INTERFACE_MAP_END_INHERITING(Event) JSObject* NavigateEvent::WrapObjectInternal(JSContext* aCx, JS::Handle aGivenProto) { return NavigateEvent_Binding::Wrap(aCx, this, aGivenProto); } /* static */ already_AddRefed NavigateEvent::Constructor( const GlobalObject& aGlobal, const nsAString& aType, const NavigateEventInit& aEventInitDict) { nsCOMPtr eventTarget = do_QueryInterface(aGlobal.GetAsSupports()); return Constructor(eventTarget, aType, aEventInitDict); } /* static */ already_AddRefed NavigateEvent::Constructor( EventTarget* aEventTarget, const nsAString& aType, const NavigateEventInit& aEventInitDict) { RefPtr event = new NavigateEvent(aEventTarget); bool trusted = event->Init(aEventTarget); event->InitEvent( aType, aEventInitDict.mBubbles ? CanBubble::eYes : CanBubble::eNo, aEventInitDict.mCancelable ? Cancelable::eYes : Cancelable::eNo, aEventInitDict.mComposed ? Composed::eYes : Composed::eNo); event->InitNavigateEvent(aEventInitDict); event->SetTrusted(trusted); return event.forget(); } /* static */ already_AddRefed NavigateEvent::Constructor( EventTarget* aEventTarget, const nsAString& aType, const NavigateEventInit& aEventInitDict, nsIStructuredCloneContainer* aClassicHistoryAPIState, class AbortController* aAbortController) { RefPtr event = Constructor(aEventTarget, aType, aEventInitDict); event->mAbortController = aAbortController; MOZ_DIAGNOSTIC_ASSERT(event->mSignal == aAbortController->Signal()); event->mClassicHistoryAPIState = aClassicHistoryAPIState; return event.forget(); } NavigationType NavigateEvent::NavigationType() const { return mNavigationType; } already_AddRefed NavigateEvent::Destination() const { return do_AddRef(mDestination); } bool NavigateEvent::CanIntercept() const { return mCanIntercept; } bool NavigateEvent::UserInitiated() const { return mUserInitiated; } bool NavigateEvent::HashChange() const { return mHashChange; } AbortSignal* NavigateEvent::Signal() const { return mSignal; } already_AddRefed NavigateEvent::GetFormData() const { return do_AddRef(mFormData); } void NavigateEvent::GetDownloadRequest(nsAString& aDownloadRequest) const { aDownloadRequest = mDownloadRequest; } void NavigateEvent::GetInfo(JSContext* aCx, JS::MutableHandle aInfo) const { aInfo.set(mInfo); } bool NavigateEvent::HasUAVisualTransition() const { return mHasUAVisualTransition; } Element* NavigateEvent::GetSourceElement() const { return mSourceElement; } template static void MaybeReportWarningToConsole(Document* aDocument, const nsString& aOption, OptionEnum aPrevious, OptionEnum aNew) { if (!aDocument) { return; } nsTArray params = {aOption, NS_ConvertUTF8toUTF16(GetEnumString(aNew)), NS_ConvertUTF8toUTF16(GetEnumString(aPrevious))}; nsContentUtils::ReportToConsole( nsIScriptError::warningFlag, "DOM"_ns, aDocument, nsContentUtils::eDOM_PROPERTIES, "PreviousInterceptCallOptionOverriddenWarning", params); } // https://html.spec.whatwg.org/#dom-navigateevent-intercept void NavigateEvent::Intercept(const NavigationInterceptOptions& aOptions, ErrorResult& aRv) { // Step 1 if (PerformSharedChecks(aRv); aRv.Failed()) { return; } // Step 2 if (!mCanIntercept) { aRv.ThrowSecurityError("Event's canIntercept was initialized to false"); return; } // Step 3 if (!IsBeingDispatched()) { aRv.ThrowInvalidStateError("Event has never been dispatched"); return; } // Step 4 MOZ_DIAGNOSTIC_ASSERT(mInterceptionState == InterceptionState::None || mInterceptionState == InterceptionState::Intercepted); // Step 5 mInterceptionState = InterceptionState::Intercepted; // Step 6 if (aOptions.mHandler.WasPassed()) { mNavigationHandlerList.AppendElement( aOptions.mHandler.InternalValue().get()); } // Step 7 if (aOptions.mFocusReset.WasPassed()) { // Step 7.1 if (mFocusResetBehavior && *mFocusResetBehavior != aOptions.mFocusReset.Value()) { RefPtr document = GetDocument(); MaybeReportWarningToConsole(document, u"focusReset"_ns, *mFocusResetBehavior, aOptions.mFocusReset.Value()); } // Step 7.2 mFocusResetBehavior.emplace(aOptions.mFocusReset.Value()); } // Step 8 if (aOptions.mScroll.WasPassed()) { // Step 8.1 if (mScrollBehavior && *mScrollBehavior != aOptions.mScroll.Value()) { RefPtr document = GetDocument(); MaybeReportWarningToConsole(document, u"scroll"_ns, *mScrollBehavior, aOptions.mScroll.Value()); } // Step 8.2 mScrollBehavior.emplace(aOptions.mScroll.Value()); } } // https://html.spec.whatwg.org/#dom-navigateevent-scroll void NavigateEvent::Scroll(ErrorResult& aRv) { // Step 1 if (PerformSharedChecks(aRv); aRv.Failed()) { return; } // Step 2 if (mInterceptionState != InterceptionState::Committed) { aRv.ThrowInvalidStateError("NavigateEvent was not committed"); return; } // Step 3 ProcessScrollBehavior(); } NavigateEvent::NavigateEvent(EventTarget* aOwner) : Event(aOwner, nullptr, nullptr) { mozilla::HoldJSObjects(this); } NavigateEvent::~NavigateEvent() { DropJSObjects(this); } void NavigateEvent::InitNavigateEvent(const NavigateEventInit& aEventInitDict) { mNavigationType = aEventInitDict.mNavigationType; mDestination = aEventInitDict.mDestination; mCanIntercept = aEventInitDict.mCanIntercept; mUserInitiated = aEventInitDict.mUserInitiated; mHashChange = aEventInitDict.mHashChange; mSignal = aEventInitDict.mSignal; mFormData = aEventInitDict.mFormData; mDownloadRequest = aEventInitDict.mDownloadRequest; mInfo = aEventInitDict.mInfo; mHasUAVisualTransition = aEventInitDict.mHasUAVisualTransition; mSourceElement = aEventInitDict.mSourceElement; } void NavigateEvent::SetCanIntercept(bool aCanIntercept) { mCanIntercept = aCanIntercept; } enum NavigateEvent::InterceptionState NavigateEvent::InterceptionState() const { return mInterceptionState; } void NavigateEvent::SetInterceptionState( enum InterceptionState aInterceptionState) { mInterceptionState = aInterceptionState; } nsIStructuredCloneContainer* NavigateEvent::ClassicHistoryAPIState() const { return mClassicHistoryAPIState; } nsTArray>& NavigateEvent::NavigationHandlerList() { return mNavigationHandlerList; } AbortController* NavigateEvent::AbortController() const { return mAbortController; } bool NavigateEvent::IsBeingDispatched() const { return mEvent->mFlags.mIsBeingDispatched; } // https://html.spec.whatwg.org/#navigateevent-finish void NavigateEvent::Finish(bool aDidFulfill) { switch (mInterceptionState) { // Step 1 case InterceptionState::Intercepted: case InterceptionState::Finished: MOZ_DIAGNOSTIC_ASSERT(false); break; // Step 2 case InterceptionState::None: return; default: break; } // Step 3 PotentiallyResetFocus(); // Step 4 if (aDidFulfill) { PotentiallyProcessScrollBehavior(); } // Step 5 mInterceptionState = InterceptionState::Finished; } // https://html.spec.whatwg.org/#navigateevent-perform-shared-checks void NavigateEvent::PerformSharedChecks(ErrorResult& aRv) { // Step 1 if (RefPtr document = GetDocument(); !document || !document->IsFullyActive()) { aRv.ThrowInvalidStateError("Document isn't fully active"); return; } // Step 2 if (!IsTrusted()) { aRv.ThrowSecurityError("Event is untrusted"); return; } // Step 3 if (DefaultPrevented()) { aRv.ThrowInvalidStateError("Event was canceled"); } } // https://html.spec.whatwg.org/#potentially-reset-the-focus void NavigateEvent::PotentiallyResetFocus() { // Step 1 MOZ_DIAGNOSTIC_ASSERT(mInterceptionState == InterceptionState::Committed || mInterceptionState == InterceptionState::Scrolled); // Step 2 nsCOMPtr window = do_QueryInterface(GetParentObject()); // If we don't have a window here, there's not much we can do. This could // potentially happen in a chrome context, and in the end it's just better to // be sure and null check. if (NS_WARN_IF(!window)) { return; } Navigation* navigation = window->Navigation(); // Step 3 bool focusChanged = navigation->FocusedChangedDuringOngoingNavigation(); // Step 4 navigation->SetFocusedChangedDuringOngoingNavigation(false); // Step 5 if (focusChanged) { return; } // Step 6 if (mFocusResetBehavior && *mFocusResetBehavior == NavigationFocusReset::Manual) { return; } // Step 7 Document* document = window->GetExtantDoc(); // If we don't have a document here, there's not much we can do. if (NS_WARN_IF(!document)) { return; } // Step 8 Element* focusTarget = document->GetDocumentElement(); if (focusTarget) { focusTarget = focusTarget->GetAutofocusDelegate(mozilla::IsFocusableFlags(0)); } // Step 9 if (!focusTarget) { focusTarget = document->GetBody(); } // Step 10 if (!focusTarget) { focusTarget = document->GetDocumentElement(); } // The remaining steps will be implemented in Bug 1948253. // Step 11: Run the focusing steps for focusTarget, with document's viewport // as the fallback target. // Step 12: Move the sequential focus navigation starting point to // focusTarget. } // https://html.spec.whatwg.org/#potentially-process-scroll-behavior void NavigateEvent::PotentiallyProcessScrollBehavior() { // Step 1 MOZ_DIAGNOSTIC_ASSERT(mInterceptionState == InterceptionState::Committed || mInterceptionState == InterceptionState::Scrolled); // Step 2 if (mInterceptionState == InterceptionState::Scrolled) { return; } // Step 3 if (mScrollBehavior && *mScrollBehavior == NavigationScrollBehavior::Manual) { return; } // Process 4 ProcessScrollBehavior(); } // Here we want to scroll to the beginning of the document, as described in // https://drafts.csswg.org/cssom-view/#scroll-to-the-beginning-of-the-document MOZ_CAN_RUN_SCRIPT static void ScrollToBeginningOfDocument(Document& aDocument) { RefPtr presShell = aDocument.GetPresShell(); if (!presShell) { return; } RefPtr rootElement = aDocument.GetRootElement(); ScrollAxis vertical(WhereToScroll::Start, WhenToScroll::Always); presShell->ScrollContentIntoView(rootElement, vertical, ScrollAxis(), ScrollFlags::TriggeredByScript); } // https://html.spec.whatwg.org/#restore-scroll-position-data static void RestoreScrollPositionData(Document* aDocument) { if (!aDocument || aDocument->HasBeenScrolled()) { return; } // This will be implemented in Bug 1955947. Make sure to move this to // `SessionHistoryEntry`/`SessionHistoryInfo`. } // https://html.spec.whatwg.org/#process-scroll-behavior void NavigateEvent::ProcessScrollBehavior() { // Step 1 MOZ_DIAGNOSTIC_ASSERT(mInterceptionState == InterceptionState::Committed); // Step 2 mInterceptionState = InterceptionState::Scrolled; // Step 3 if (mNavigationType == NavigationType::Traverse || mNavigationType == NavigationType::Reload) { RefPtr document = GetDocument(); RestoreScrollPositionData(document); return; } // Step 4.1 RefPtr document = GetDocument(); // If there is no document there's not much to do. if (!document) { return; } // Step 4.2 nsAutoCString ref; if (nsIURI* uri = document->GetDocumentURI(); NS_SUCCEEDED(uri->GetRef(ref)) && !nsContentUtils::GetTargetElement(document, NS_ConvertUTF8toUTF16(ref))) { ScrollToBeginningOfDocument(*document); return; } // Step 4.3 document->ScrollToRef(); } } // namespace mozilla::dom