From db21c69744a5a7e9343b134e2ae8b5b6387c4bb6 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Thu, 30 Jan 2025 14:14:13 +0000 Subject: [PATCH] LibWeb: Add NavigateEvent.sourceElement Corresponds to https://github.com/whatwg/html/pull/10898 This is with https://github.com/whatwg/html/pull/10971 also applied - the original PR missed `sourceElement` from the `NavigateEventInit` dictionary. I've also updated the imported WPT test as it's been recently changed to account for 10898 being merged. --- Libraries/LibWeb/HTML/Navigable.cpp | 22 +++++++++++-------- Libraries/LibWeb/HTML/Navigable.h | 3 ++- Libraries/LibWeb/HTML/NavigateEvent.cpp | 1 + Libraries/LibWeb/HTML/NavigateEvent.h | 10 ++++++--- Libraries/LibWeb/HTML/NavigateEvent.idl | 2 ++ Libraries/LibWeb/HTML/Navigation.cpp | 20 +++++++++++------ Libraries/LibWeb/HTML/Navigation.h | 4 +++- .../navigate-event/event-constructor.html | 6 ++--- 8 files changed, 43 insertions(+), 25 deletions(-) diff --git a/Libraries/LibWeb/HTML/Navigable.cpp b/Libraries/LibWeb/HTML/Navigable.cpp index b979f238cec2a..0f4b6805117ce 100644 --- a/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Libraries/LibWeb/HTML/Navigable.cpp @@ -1327,7 +1327,8 @@ WebIDL::ExceptionOr Navigable::populate_session_history_entry_document( // an optional serialized state-or-null navigationAPIState (default null), // an optional entry list or null formDataEntryList (default null), // an optional referrer policy referrerPolicy (default the empty string), -// and an optional user navigation involvement userInvolvement (default "none"): +// an optional user navigation involvement userInvolvement (default "none"), +// and an optional Element sourceElement (default null): // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate WebIDL::ExceptionOr Navigable::navigate(NavigateParams params) @@ -1346,6 +1347,7 @@ WebIDL::ExceptionOr Navigable::navigate(NavigateParams params) auto const& form_data_entry_list = params.form_data_entry_list; auto referrer_policy = params.referrer_policy; auto user_involvement = params.user_involvement; + auto source_element = params.source_element; auto& active_document = *this->active_document(); auto& realm = active_document.realm(); auto& vm = this->vm(); @@ -1429,8 +1431,8 @@ WebIDL::ExceptionOr Navigable::navigate(NavigateParams params) && !response && url.equals(active_session_history_entry()->url(), URL::ExcludeFragment::Yes) && url.fragment().has_value()) { - // 1. Navigate to a fragment given navigable, url, historyHandling, userInvolvement, navigationAPIState, and navigationId. - TRY(navigate_to_a_fragment(url, to_history_handling_behavior(history_handling), user_involvement, navigation_api_state, navigation_id)); + // 1. Navigate to a fragment given navigable, url, historyHandling, userInvolvement, sourceElement, navigationAPIState, and navigationId. + TRY(navigate_to_a_fragment(url, to_history_handling_behavior(history_handling), user_involvement, source_element, navigation_api_state, navigation_id)); // 2. Return. return {}; @@ -1492,7 +1494,8 @@ WebIDL::ExceptionOr Navigable::navigate(NavigateParams params) // 4. Let continue be the result of firing a push/replace/reload navigate event at navigation // with navigationType set to historyHandling, isSameDocument set to false, userInvolvement set to userInvolvement, - // formDataEntryList set to entryListForFiring, destinationURL set to url, and navigationAPIState set to navigationAPIStateForFiring. + // sourceElement set to sourceElement, formDataEntryList set to entryListForFiring, destinationURL set to url, + // and navigationAPIState set to navigationAPIStateForFiring. auto navigation_type = [](Bindings::NavigationHistoryBehavior history_handling) { switch (history_handling) { case Bindings::NavigationHistoryBehavior::Push: @@ -1504,7 +1507,7 @@ WebIDL::ExceptionOr Navigable::navigate(NavigateParams params) VERIFY_NOT_REACHED(); } }(history_handling); - auto continue_ = navigation->fire_a_push_replace_reload_navigate_event(navigation_type, url, false, user_involvement, entry_list_for_firing, navigation_api_state_for_firing); + auto continue_ = navigation->fire_a_push_replace_reload_navigate_event(navigation_type, url, false, user_involvement, source_element, entry_list_for_firing, navigation_api_state_for_firing); // 5. If continue is false, then return. if (!continue_) @@ -1611,7 +1614,7 @@ WebIDL::ExceptionOr Navigable::navigate(NavigateParams params) } // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-fragid -WebIDL::ExceptionOr Navigable::navigate_to_a_fragment(URL::URL const& url, HistoryHandlingBehavior history_handling, UserNavigationInvolvement user_involvement, Optional navigation_api_state, String navigation_id) +WebIDL::ExceptionOr Navigable::navigate_to_a_fragment(URL::URL const& url, HistoryHandlingBehavior history_handling, UserNavigationInvolvement user_involvement, GC::Ptr source_element, Optional navigation_api_state, String navigation_id) { (void)navigation_id; @@ -1623,10 +1626,11 @@ WebIDL::ExceptionOr Navigable::navigate_to_a_fragment(URL::URL const& url, // 3. If navigationAPIState is not null, then set destinationNavigationAPIState to navigationAPIState. auto destination_navigation_api_state = navigation_api_state.has_value() ? *navigation_api_state : active_session_history_entry()->navigation_api_state(); - // 4. Let continue be the result of firing a push/replace/reload navigate event at navigation with navigationType set to historyHandling, isSameDocument set to true, - // userInvolvement set to userInvolvement, and destinationURL set to url, and navigationAPIState set to destinationNavigationAPIState. + // 4. Let continue be the result of firing a push/replace/reload navigate event at navigation with navigationType + // set to historyHandling, isSameDocument set to true, userInvolvement set to userInvolvement, sourceElement set + // to sourceElement, destinationURL set to url, and navigationAPIState set to destinationNavigationAPIState. auto navigation_type = history_handling == HistoryHandlingBehavior::Push ? Bindings::NavigationType::Push : Bindings::NavigationType::Replace; - bool const continue_ = navigation->fire_a_push_replace_reload_navigate_event(navigation_type, url, true, user_involvement, {}, destination_navigation_api_state); + bool const continue_ = navigation->fire_a_push_replace_reload_navigate_event(navigation_type, url, true, user_involvement, source_element, {}, destination_navigation_api_state); // 5. If continue is false, then return. if (!continue_) diff --git a/Libraries/LibWeb/HTML/Navigable.h b/Libraries/LibWeb/HTML/Navigable.h index da9ccfb0fe20a..60fd2f26a799a 100644 --- a/Libraries/LibWeb/HTML/Navigable.h +++ b/Libraries/LibWeb/HTML/Navigable.h @@ -146,11 +146,12 @@ class Navigable : public JS::Cell { Optional&> form_data_entry_list = {}; ReferrerPolicy::ReferrerPolicy referrer_policy = ReferrerPolicy::ReferrerPolicy::EmptyString; UserNavigationInvolvement user_involvement = UserNavigationInvolvement::None; + GC::Ptr source_element = nullptr; }; WebIDL::ExceptionOr navigate(NavigateParams); - WebIDL::ExceptionOr navigate_to_a_fragment(URL::URL const&, HistoryHandlingBehavior, UserNavigationInvolvement, Optional navigation_api_state, String navigation_id); + WebIDL::ExceptionOr navigate_to_a_fragment(URL::URL const&, HistoryHandlingBehavior, UserNavigationInvolvement, GC::Ptr source_element, Optional navigation_api_state, String navigation_id); GC::Ptr evaluate_javascript_url(URL::URL const&, URL::Origin const& new_document_origin, UserNavigationInvolvement, String navigation_id); void navigate_to_a_javascript_url(URL::URL const&, HistoryHandlingBehavior, URL::Origin const& initiator_origin, UserNavigationInvolvement, CSPNavigationType csp_navigation_type, String navigation_id); diff --git a/Libraries/LibWeb/HTML/NavigateEvent.cpp b/Libraries/LibWeb/HTML/NavigateEvent.cpp index 05f9ee1b52415..dd7c84e437f46 100644 --- a/Libraries/LibWeb/HTML/NavigateEvent.cpp +++ b/Libraries/LibWeb/HTML/NavigateEvent.cpp @@ -42,6 +42,7 @@ NavigateEvent::NavigateEvent(JS::Realm& realm, FlyString const& event_name, Navi , m_download_request(event_init.download_request) , m_info(event_init.info.value_or(JS::js_undefined())) , m_has_ua_visual_transition(event_init.has_ua_visual_transition) + , m_source_element(event_init.source_element) { } diff --git a/Libraries/LibWeb/HTML/NavigateEvent.h b/Libraries/LibWeb/HTML/NavigateEvent.h index 3b23533bbdb4c..2894c58a1c0f1 100644 --- a/Libraries/LibWeb/HTML/NavigateEvent.h +++ b/Libraries/LibWeb/HTML/NavigateEvent.h @@ -9,7 +9,6 @@ #include #include #include -#include namespace Web::HTML { @@ -25,6 +24,7 @@ struct NavigateEventInit : public DOM::EventInit { Optional download_request = {}; Optional info; bool has_ua_visual_transition = false; + GC::Ptr source_element = nullptr; }; // https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigationintercepthandler @@ -54,8 +54,8 @@ class NavigateEvent : public DOM::Event { [[nodiscard]] static GC::Ref construct_impl(JS::Realm&, FlyString const& event_name, NavigateEventInit const&); - // The navigationType, destination, canIntercept, userInitiated, hashChange, signal, formData, - // downloadRequest, info, and hasUAVisualTransition attributes must return the values they are initialized to. + // The navigationType, destination, canIntercept, userInitiated, hashChange, signal, formData, downloadRequest, + // info, hasUAVisualTransition, and sourceElement attributes must return the values they are initialized to. Bindings::NavigationType navigation_type() const { return m_navigation_type; } GC::Ref destination() const { return m_destination; } bool can_intercept() const { return m_can_intercept; } @@ -66,6 +66,7 @@ class NavigateEvent : public DOM::Event { Optional download_request() const { return m_download_request; } JS::Value info() const { return m_info; } bool has_ua_visual_transition() const { return m_has_ua_visual_transition; } + GC::Ptr source_element() const { return m_source_element; } WebIDL::ExceptionOr intercept(NavigationInterceptOptions const&); WebIDL::ExceptionOr scroll(); @@ -141,6 +142,9 @@ class NavigateEvent : public DOM::Event { // https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigateevent-hasuavisualtransition bool m_has_ua_visual_transition { false }; + + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigateevent-sourceelement + GC::Ptr m_source_element { nullptr }; }; } diff --git a/Libraries/LibWeb/HTML/NavigateEvent.idl b/Libraries/LibWeb/HTML/NavigateEvent.idl index 600d18fbe8190..b1f96425fd32e 100644 --- a/Libraries/LibWeb/HTML/NavigateEvent.idl +++ b/Libraries/LibWeb/HTML/NavigateEvent.idl @@ -19,6 +19,7 @@ interface NavigateEvent : Event { readonly attribute DOMString? downloadRequest; readonly attribute any info; readonly attribute boolean hasUAVisualTransition; + readonly attribute Element? sourceElement; undefined intercept(optional NavigationInterceptOptions options = {}); undefined scroll(); @@ -35,6 +36,7 @@ dictionary NavigateEventInit : EventInit { DOMString? downloadRequest = null; any info; boolean hasUAVisualTransition = false; + Element? sourceElement = null; }; dictionary NavigationInterceptOptions { diff --git a/Libraries/LibWeb/HTML/Navigation.cpp b/Libraries/LibWeb/HTML/Navigation.cpp index 0d171f121d8f7..a391f1c6967fc 100644 --- a/Libraries/LibWeb/HTML/Navigation.cpp +++ b/Libraries/LibWeb/HTML/Navigation.cpp @@ -911,6 +911,7 @@ bool Navigation::inner_navigate_event_firing_algorithm( Bindings::NavigationType navigation_type, GC::Ref destination, UserNavigationInvolvement user_involvement, + GC::Ptr source_element, Optional&> form_data_entry_list, Optional download_request_filename, Optional classic_history_api_state) @@ -1011,6 +1012,9 @@ bool Navigation::inner_navigate_event_firing_algorithm( // of the document's latest entry, was done by the user agent. Otherwise, initialize it to false. event_init.has_ua_visual_transition = false; + // 18. Initialize event's sourceElement to sourceElement. + event_init.source_element = source_element; + // 18. Set event's abort controller to a new AbortController created in navigation's relevant realm. // AD-HOC: Set on the NavigateEvent later after construction auto abort_controller = MUST(DOM::AbortController::construct_impl(realm)); @@ -1291,9 +1295,10 @@ bool Navigation::fire_a_traverse_navigate_event(GC::Ref des // navigation's relevant global object's associated Document; otherwise false. destination->set_is_same_document(destination_she->document() == &as(relevant_global_object(*this)).associated_document()); - // 9. Return the result of performing the inner navigate event firing algorithm given navigation, "traverse", event, destination, userInvolvement, null, and null. + // 9. Return the result of performing the inner navigate event firing algorithm given navigation, "traverse", + // event, destination, userInvolvement, sourceElement, null, and null. // AD-HOC: We don't pass the event, but we do pass the classic_history_api state at the end to be set later - return inner_navigate_event_firing_algorithm(Bindings::NavigationType::Traverse, destination, user_involvement, {}, {}, {}); + return inner_navigate_event_firing_algorithm(Bindings::NavigationType::Traverse, destination, user_involvement, {}, {}, {}, {}); } // https://html.spec.whatwg.org/multipage/nav-history-apis.html#fire-a-push/replace/reload-navigate-event @@ -1302,6 +1307,7 @@ bool Navigation::fire_a_push_replace_reload_navigate_event( URL::URL destination_url, bool is_same_document, UserNavigationInvolvement user_involvement, + GC::Ptr source_element, Optional&> form_data_entry_list, Optional navigation_api_state, Optional classic_history_api_state) @@ -1333,13 +1339,13 @@ bool Navigation::fire_a_push_replace_reload_navigate_event( destination->set_is_same_document(is_same_document); // 8. Return the result of performing the inner navigate event firing algorithm given navigation, - // navigationType, event, destination, userInvolvement, formDataEntryList, and null. + // navigationType, event, destination, userInvolvement, sourceElement, formDataEntryList, and null. // AD-HOC: We don't pass the event, but we do pass the classic_history_api state at the end to be set later - return inner_navigate_event_firing_algorithm(navigation_type, destination, user_involvement, move(form_data_entry_list), {}, move(classic_history_api_state)); + return inner_navigate_event_firing_algorithm(navigation_type, destination, user_involvement, source_element, move(form_data_entry_list), {}, move(classic_history_api_state)); } // https://html.spec.whatwg.org/multipage/nav-history-apis.html#fire-a-download-request-navigate-event -bool Navigation::fire_a_download_request_navigate_event(URL::URL destination_url, UserNavigationInvolvement user_involvement, String filename) +bool Navigation::fire_a_download_request_navigate_event(URL::URL destination_url, UserNavigationInvolvement user_involvement, GC::Ptr source_element, String filename) { auto& realm = relevant_realm(*this); auto& vm = this->vm(); @@ -1364,9 +1370,9 @@ bool Navigation::fire_a_download_request_navigate_event(URL::URL destination_url destination->set_is_same_document(false); // 8. Return the result of performing the inner navigate event firing algorithm given navigation, - // "push", event, destination, userInvolvement, null, and filename. + // "push", event, destination, userInvolvement, sourceElement, null, and filename. // AD-HOC: We don't pass the event, but we do pass the classic_history_api state at the end to be set later - return inner_navigate_event_firing_algorithm(Bindings::NavigationType::Push, destination, user_involvement, {}, move(filename), {}); + return inner_navigate_event_firing_algorithm(Bindings::NavigationType::Push, destination, user_involvement, source_element, {}, move(filename), {}); } // https://html.spec.whatwg.org/multipage/nav-history-apis.html#initialize-the-navigation-api-entries-for-a-new-document diff --git a/Libraries/LibWeb/HTML/Navigation.h b/Libraries/LibWeb/HTML/Navigation.h index 342b962b7c80e..767679714ac4e 100644 --- a/Libraries/LibWeb/HTML/Navigation.h +++ b/Libraries/LibWeb/HTML/Navigation.h @@ -116,10 +116,11 @@ class Navigation : public DOM::EventTarget { URL::URL destination_url, bool is_same_document, UserNavigationInvolvement = UserNavigationInvolvement::None, + GC::Ptr source_element = {}, Optional&> form_data_entry_list = {}, Optional navigation_api_state = {}, Optional classic_history_api_state = {}); - bool fire_a_download_request_navigate_event(URL::URL destination_url, UserNavigationInvolvement user_involvement, String filename); + bool fire_a_download_request_navigate_event(URL::URL destination_url, UserNavigationInvolvement user_involvement, GC::Ptr source_element, String filename); void initialize_the_navigation_api_entries_for_a_new_document(Vector> const& new_shes, GC::Ref initial_she); void update_the_navigation_api_entries_for_a_same_document_navigation(GC::Ref destination_she, Bindings::NavigationType); @@ -154,6 +155,7 @@ class Navigation : public DOM::EventTarget { Bindings::NavigationType, GC::Ref, UserNavigationInvolvement, + GC::Ptr source_element, Optional&> form_data_entry_list, Optional download_request_filename, Optional classic_history_api_state); diff --git a/Tests/LibWeb/Text/input/wpt-import/navigation-api/navigate-event/event-constructor.html b/Tests/LibWeb/Text/input/wpt-import/navigation-api/navigate-event/event-constructor.html index a7d86b9a75fc5..d83c3cdf283a2 100644 --- a/Tests/LibWeb/Text/input/wpt-import/navigation-api/navigate-event/event-constructor.html +++ b/Tests/LibWeb/Text/input/wpt-import/navigation-api/navigate-event/event-constructor.html @@ -78,8 +78,7 @@ assert_equals(event.downloadRequest, downloadRequest); assert_equals(event.info, info); assert_equals(event.hasUAVisualTransition, hasUAVisualTransition); - // NavigateEvent sourceElement is still in development, so test whether it is available. - if ("sourceElement" in e) assert_equals(event.sourceElement, sourceElement); + assert_equals(event.sourceElement, sourceElement); }); history.pushState(2, null, "#2"); }, "all properties are reflected back"); @@ -99,8 +98,7 @@ assert_equals(event.formData, null); assert_equals(event.downloadRequest, null); assert_equals(event.info, undefined); - // NavigateEvent sourceElement is still in development, so test whether it is available. - if ("sourceElement" in e) assert_equals(event.sourceElement, null); + assert_equals(event.sourceElement, null); }); history.pushState(3, null, "#3"); }, "defaults are as expected");