From 0c2f75806728240194db906c6e2119f841ac7438 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Wed, 23 Aug 2023 10:57:12 -0600 Subject: [PATCH] LibWeb: Implement the start of the Navigation API This API is how JavaScript can manipulate the new Navigable concepts directly. We are still missing most of the interesting algorithms on Navigation that do the actual navigation steps, and call into the currently WIP navigable AOs. --- .../Userland/Libraries/LibWeb/HTML/BUILD.gn | 1 + .../Userland/Libraries/LibWeb/idl_files.gni | 1 + Userland/Libraries/LibWeb/CMakeLists.txt | 1 + Userland/Libraries/LibWeb/Forward.h | 1 + Userland/Libraries/LibWeb/HTML/EventNames.h | 3 + Userland/Libraries/LibWeb/HTML/Navigation.cpp | 224 ++++++++++++++++++ Userland/Libraries/LibWeb/HTML/Navigation.h | 90 +++++++ Userland/Libraries/LibWeb/HTML/Navigation.idl | 56 +++++ Userland/Libraries/LibWeb/idl_files.cmake | 1 + 9 files changed, 378 insertions(+) create mode 100644 Userland/Libraries/LibWeb/HTML/Navigation.cpp create mode 100644 Userland/Libraries/LibWeb/HTML/Navigation.h create mode 100644 Userland/Libraries/LibWeb/HTML/Navigation.idl diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn index d3b288175a..03e7f08406 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn @@ -123,6 +123,7 @@ source_set("HTML") { "MimeTypeArray.cpp", "Navigable.cpp", "NavigableContainer.cpp", + "Navigation.cpp", "NavigationCurrentEntryChangeEvent.cpp", "NavigationHistoryEntry.cpp", "Navigator.cpp", diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni b/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni index 160306da21..e4d32263aa 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni @@ -182,6 +182,7 @@ standard_idl_files = [ "//Userland/Libraries/LibWeb/HTML/MessagePort.idl", "//Userland/Libraries/LibWeb/HTML/MimeType.idl", "//Userland/Libraries/LibWeb/HTML/MimeTypeArray.idl", + "//Userland/Libraries/LibWeb/HTML/Navigation.idl", "//Userland/Libraries/LibWeb/HTML/NavigationCurrentEntryChangeEvent.idl", "//Userland/Libraries/LibWeb/HTML/NavigationHistoryEntry.idl", "//Userland/Libraries/LibWeb/HTML/Navigator.idl", diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 8b3750d404..3f1291adce 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -350,6 +350,7 @@ set(SOURCES HTML/MimeTypeArray.cpp HTML/Navigable.cpp HTML/NavigableContainer.cpp + HTML/Navigation.cpp HTML/NavigationCurrentEntryChangeEvent.cpp HTML/NavigationHistoryEntry.cpp HTML/Navigator.cpp diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index dd97b1c2a2..59629827dc 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -414,6 +414,7 @@ class MimeType; class MimeTypeArray; class Navigable; class NavigableContainer; +class Navigation; class NavigationCurrentEntryChangeEvent; class NavigationHistoryEntry; class Navigator; diff --git a/Userland/Libraries/LibWeb/HTML/EventNames.h b/Userland/Libraries/LibWeb/HTML/EventNames.h index 71cfca226c..37563b0f76 100644 --- a/Userland/Libraries/LibWeb/HTML/EventNames.h +++ b/Userland/Libraries/LibWeb/HTML/EventNames.h @@ -62,6 +62,9 @@ namespace Web::HTML::EventNames { __ENUMERATE_HTML_EVENT(loadstart) \ __ENUMERATE_HTML_EVENT(message) \ __ENUMERATE_HTML_EVENT(messageerror) \ + __ENUMERATE_HTML_EVENT(navigate) \ + __ENUMERATE_HTML_EVENT(navigatesuccess) \ + __ENUMERATE_HTML_EVENT(navigateerror) \ __ENUMERATE_HTML_EVENT(offline) \ __ENUMERATE_HTML_EVENT(online) \ __ENUMERATE_HTML_EVENT(open) \ diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.cpp b/Userland/Libraries/LibWeb/HTML/Navigation.cpp new file mode 100644 index 0000000000..ab0b0c8f45 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Navigation.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::HTML { + +JS::NonnullGCPtr Navigation::create(JS::Realm& realm) +{ + return realm.heap().allocate(realm, realm); +} + +Navigation::Navigation(JS::Realm& realm) + : DOM::EventTarget(realm) +{ +} + +Navigation::~Navigation() = default; + +void Navigation::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + set_prototype(&Bindings::ensure_web_prototype(realm, "Navigation")); +} + +void Navigation::visit_edges(JS::Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + for (auto& entry : m_entry_list) + visitor.visit(entry); +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-entries +Vector> Navigation::entries() const +{ + // The entries() method steps are: + + // 1. If this has entries and events disabled, then return the empty list. + if (has_entries_and_events_disabled()) + return {}; + + // 2. Return this's entry list. + // NOTE: Recall that because of Web IDL's sequence type conversion rules, + // this will create a new JavaScript array object on each call. + // That is, navigation.entries() !== navigation.entries(). + return m_entry_list; +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-current-entry +JS::GCPtr Navigation::current_entry() const +{ + // The current entry of a Navigation navigation is the result of running the following steps: + + // 1. If navigation has entries and events disabled, then return null. + if (has_entries_and_events_disabled()) + return nullptr; + + // 2. Assert: navigation's current entry index is not −1. + VERIFY(m_current_entry_index != -1); + + // 3. Return navigation's entry list[navigation's current entry index]. + return m_entry_list[m_current_entry_index]; +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-updatecurrententry +WebIDL::ExceptionOr Navigation::update_current_entry(NavigationUpdateCurrentEntryOptions options) +{ + // The updateCurrentEntry(options) method steps are: + + // 1. Let current be the current entry of this. + auto current = current_entry(); + + // 2. If current is null, then throw an "InvalidStateError" DOMException. + if (current == nullptr) + return WebIDL::InvalidStateError::create(realm(), "Cannot update current NavigationHistoryEntry when there is no current entry"sv); + + // 3. Let serializedState be StructuredSerializeForStorage(options["state"]), rethrowing any exceptions. + auto serialized_state = TRY(structured_serialize_for_storage(vm(), options.state)); + + // 4. Set current's session history entry's navigation API state to serializedState. + current->session_history_entry().navigation_api_state = serialized_state; + + // 5. Fire an event named currententrychange at this using NavigationCurrentEntryChangeEvent, + // with its navigationType attribute initialized to null and its from initialized to current. + NavigationCurrentEntryChangeEventInit event_init = {}; + event_init.navigation_type = {}; + event_init.from = current; + dispatch_event(HTML::NavigationCurrentEntryChangeEvent::create(realm(), HTML::EventNames::currententrychange, event_init)); + + return {}; +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-cangoback +bool Navigation::can_go_back() const +{ + // The canGoBack getter steps are: + + // 1. If this has entries and events disabled, then return false. + if (has_entries_and_events_disabled()) + return false; + + // 2. Assert: this's current entry index is not −1. + VERIFY(m_current_entry_index != -1); + + // 3. If this's current entry index is 0, then return false. + // 4. Return true. + return (m_current_entry_index != 0); +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-cangoforward +bool Navigation::can_go_forward() const +{ + // The canGoForward getter steps are: + + // 1. If this has entries and events disabled, then return false. + if (has_entries_and_events_disabled()) + return false; + + // 2. Assert: this's current entry index is not −1. + VERIFY(m_current_entry_index != -1); + + // 3. If this's current entry index is equal to this's entry list's size, then return false. + // 4. Return true. + return (m_current_entry_index != static_cast(m_entry_list.size())); +} + +void Navigation::set_onnavigate(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::navigate, event_handler); +} + +WebIDL::CallbackType* Navigation::onnavigate() +{ + return event_handler_attribute(HTML::EventNames::navigate); +} + +void Navigation::set_onnavigatesuccess(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::navigatesuccess, event_handler); +} + +WebIDL::CallbackType* Navigation::onnavigatesuccess() +{ + return event_handler_attribute(HTML::EventNames::navigatesuccess); +} + +void Navigation::set_onnavigateerror(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::navigateerror, event_handler); +} + +WebIDL::CallbackType* Navigation::onnavigateerror() +{ + return event_handler_attribute(HTML::EventNames::navigateerror); +} + +void Navigation::set_oncurrententrychange(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::currententrychange, event_handler); +} + +WebIDL::CallbackType* Navigation::oncurrententrychange() +{ + return event_handler_attribute(HTML::EventNames::currententrychange); +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#has-entries-and-events-disabled +bool Navigation::has_entries_and_events_disabled() const +{ + // A Navigation navigation has entries and events disabled if the following steps return true: + + // 1. Let document be navigation's relevant global object's associated Document. + auto const& document = verify_cast(relevant_global_object(*this)).associated_document(); + + // 2. If document is not fully active, then return true. + if (!document.is_fully_active()) + return true; + + // 3. If document's is initial about:blank is true, then return true. + if (document.is_initial_about_blank()) + return true; + + // 4. If document's origin is opaque, then return true. + if (document.origin().is_opaque()) + return true; + + // 5. Return false. + return false; +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#getting-the-navigation-api-entry-index +i64 Navigation::get_the_navigation_api_entry_index(SessionHistoryEntry const& she) const +{ + // To get the navigation API entry index of a session history entry she within a Navigation navigation: + + // 1. Let index be 0. + i64 index = 0; + + // 2. For each nhe of navigation's entry list: + for (auto const& nhe : m_entry_list) { + // 1. If nhe's session history entry is equal to she, then return index. + if (&nhe->session_history_entry() == &she) + return index; + + // 2. Increment index by 1. + ++index; + } + + // 3. Return −1. + return -1; +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.h b/Userland/Libraries/LibWeb/HTML/Navigation.h new file mode 100644 index 0000000000..7c96133db7 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Navigation.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigationupdatecurrententryoptions +struct NavigationUpdateCurrentEntryOptions { + JS::Value state; +}; + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigationoptions +struct NavigationOptions { + JS::Value info; +}; + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigationnavigateoptions +struct NavigationNavigateOptions : public NavigationOptions { + JS::Value state; + Bindings::NavigationHistoryBehavior history = Bindings::NavigationHistoryBehavior::Auto; +}; + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigationreloadoptions +struct NavigationReloadOptions : public NavigationOptions { + JS::Value state; +}; + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigationresult +struct NavigationResult { + JS::NonnullGCPtr committed; + JS::NonnullGCPtr finished; +}; + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-interface +class Navigation : public DOM::EventTarget { + WEB_PLATFORM_OBJECT(Navigation, DOM::EventTarget); + +public: + [[nodiscard]] static JS::NonnullGCPtr create(JS::Realm&); + + // IDL properties and methods + Vector> entries() const; + JS::GCPtr current_entry() const; + WebIDL::ExceptionOr update_current_entry(NavigationUpdateCurrentEntryOptions); + bool can_go_back() const; + bool can_go_forward() const; + + // Event Handlers + void set_onnavigate(WebIDL::CallbackType*); + WebIDL::CallbackType* onnavigate(); + + void set_onnavigatesuccess(WebIDL::CallbackType*); + WebIDL::CallbackType* onnavigatesuccess(); + + void set_onnavigateerror(WebIDL::CallbackType*); + WebIDL::CallbackType* onnavigateerror(); + + void set_oncurrententrychange(WebIDL::CallbackType*); + WebIDL::CallbackType* oncurrententrychange(); + + // Abstract Operations + bool has_entries_and_events_disabled() const; + i64 get_the_navigation_api_entry_index(SessionHistoryEntry const&) const; + + virtual ~Navigation() override; + +private: + explicit Navigation(JS::Realm&); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Visitor&) override; + + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-entry-list + // Each Navigation has an associated entry list, a list of NavigationHistoryEntry objects, initially empty. + Vector> m_entry_list; + + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-current-entry-index + // Each Navigation has an associated current entry index, an integer, initially −1. + i64 m_current_entry_index { -1 }; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.idl b/Userland/Libraries/LibWeb/HTML/Navigation.idl new file mode 100644 index 0000000000..fce3741b1b --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Navigation.idl @@ -0,0 +1,56 @@ +#import +#import +#import + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-interface +[Exposed=Window] +interface Navigation : EventTarget { + sequence entries(); + readonly attribute NavigationHistoryEntry? currentEntry; + undefined updateCurrentEntry(NavigationUpdateCurrentEntryOptions options); + // FIXME: readonly attribute NavigationTransition? transition; + + readonly attribute boolean canGoBack; + readonly attribute boolean canGoForward; + + // TODO: Actually implement navigation algorithms + // NavigationResult navigate(USVString url, optional NavigationNavigateOptions options = {}); + // NavigationResult reload(optional NavigationReloadOptions options = {}); + + // NavigationResult traverseTo(DOMString key, optional NavigationOptions options = {}); + // NavigationResult back(optional NavigationOptions options = {}); + // NavigationResult forward(optional NavigationOptions options = {}); + + attribute EventHandler onnavigate; + attribute EventHandler onnavigatesuccess; + attribute EventHandler onnavigateerror; + attribute EventHandler oncurrententrychange; +}; + +dictionary NavigationUpdateCurrentEntryOptions { + required any state; +}; + +dictionary NavigationOptions { + any info; +}; + +dictionary NavigationNavigateOptions : NavigationOptions { + any state; + NavigationHistoryBehavior history = "auto"; +}; + +dictionary NavigationReloadOptions : NavigationOptions { + any state; +}; + +dictionary NavigationResult { + Promise committed; + Promise finished; +}; + +enum NavigationHistoryBehavior { + "auto", + "push", + "replace" +}; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 6e8cb487f4..1113b8e172 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -168,6 +168,7 @@ libweb_js_bindings(HTML/MessageEvent) libweb_js_bindings(HTML/MessagePort) libweb_js_bindings(HTML/MimeType) libweb_js_bindings(HTML/MimeTypeArray) +libweb_js_bindings(HTML/Navigation) libweb_js_bindings(HTML/NavigationCurrentEntryChangeEvent) libweb_js_bindings(HTML/NavigationHistoryEntry) libweb_js_bindings(HTML/Navigator)