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)