diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp index c6c764651f..1f9b174e1b 100644 --- a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp +++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ void WindowObject::initialize_global_object() define_native_accessor("top", top_getter, nullptr, JS::Attribute::Enumerable); define_native_accessor("parent", parent_getter, {}, JS::Attribute::Enumerable); define_native_accessor("document", document_getter, {}, JS::Attribute::Enumerable); + define_native_accessor("history", history_getter, {}, JS::Attribute::Enumerable); define_native_accessor("performance", performance_getter, {}, JS::Attribute::Enumerable); define_native_accessor("screen", screen_getter, {}, JS::Attribute::Enumerable); define_native_accessor("innerWidth", inner_width_getter, {}, JS::Attribute::Enumerable); @@ -640,4 +642,12 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::scroll_by) return JS::js_undefined(); } +JS_DEFINE_NATIVE_FUNCTION(WindowObject::history_getter) +{ + auto* impl = impl_from(vm, global_object); + if (!impl) + return {}; + return wrap(global_object, impl->associated_document().history()); +} + } diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.h b/Userland/Libraries/LibWeb/Bindings/WindowObject.h index aa1a2a1d77..d574546740 100644 --- a/Userland/Libraries/LibWeb/Bindings/WindowObject.h +++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.h @@ -64,6 +64,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(document_getter); JS_DECLARE_NATIVE_FUNCTION(performance_getter); + JS_DECLARE_NATIVE_FUNCTION(history_getter); JS_DECLARE_NATIVE_FUNCTION(screen_getter); JS_DECLARE_NATIVE_FUNCTION(event_getter); diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h b/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h index 002008b697..e38e77ba47 100644 --- a/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h +++ b/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h @@ -182,6 +182,8 @@ #include #include #include +#include +#include #include #include #include @@ -259,6 +261,7 @@ ADD_WINDOW_OBJECT_INTERFACE(Element) \ ADD_WINDOW_OBJECT_INTERFACE(Event) \ ADD_WINDOW_OBJECT_INTERFACE(EventTarget) \ + ADD_WINDOW_OBJECT_INTERFACE(History) \ ADD_WINDOW_OBJECT_INTERFACE(HTMLAnchorElement) \ ADD_WINDOW_OBJECT_INTERFACE(HTMLAreaElement) \ ADD_WINDOW_OBJECT_INTERFACE(HTMLAudioElement) \ diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 1568ef5429..8d303dd98b 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -77,6 +77,7 @@ set(SOURCES HTML/EventNames.cpp HTML/FormAssociatedElement.cpp HTML/GlobalEventHandlers.cpp + HTML/History.cpp HTML/HTMLAnchorElement.cpp HTML/HTMLAreaElement.cpp HTML/HTMLAudioElement.cpp @@ -329,6 +330,7 @@ libweb_js_wrapper(DOM/Text) libweb_js_wrapper(HTML/CanvasRenderingContext2D) libweb_js_wrapper(HTML/CloseEvent) libweb_js_wrapper(HTML/DOMParser) +libweb_js_wrapper(HTML/History) libweb_js_wrapper(HTML/HTMLAnchorElement) libweb_js_wrapper(HTML/HTMLAreaElement) libweb_js_wrapper(HTML/HTMLAudioElement) diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 4cdc41a2a6..5c09ab76e6 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -64,6 +64,7 @@ Document::Document(const URL& url) , m_url(url) , m_window(Window::create_with_document(*this)) , m_implementation(DOMImplementation::create(*this)) + , m_history(HTML::History::create(*this)) { m_style_update_timer = Core::Timer::create_single_shot(0, [this] { update_style(); diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index ad39329e95..165a6817a5 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace Web::DOM { @@ -276,6 +277,8 @@ public: bool is_fully_active() const; + NonnullRefPtr history() const { return m_history; } + private: explicit Document(const URL&); @@ -356,6 +359,8 @@ private: // https://html.spec.whatwg.org/multipage/semantics.html#script-blocking-style-sheet-counter u32 m_script_blocking_style_sheet_counter { 0 }; + + NonnullRefPtr m_history; }; } diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 488bb38ab4..061336b494 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -223,6 +223,7 @@ class ElementWrapper; class EventListenerWrapper; class EventTargetWrapper; class EventWrapper; +class HistoryWrapper; class HTMLAnchorElementWrapper; class HTMLAreaElementWrapper; class HTMLAudioElementWrapper; diff --git a/Userland/Libraries/LibWeb/HTML/History.cpp b/Userland/Libraries/LibWeb/HTML/History.cpp new file mode 100644 index 0000000000..8e68f281d2 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/History.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::HTML { + +History::History(DOM::Document& document) + : m_associated_document(document) +{ +} + +History::~History() +{ +} + +// https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate +DOM::ExceptionOr History::push_state(JS::Value data, String const&, String const& url) +{ + // NOTE: The second parameter of this function is intentionally unused. + return shared_history_push_replace_state(data, url, IsPush::Yes); +} + +// https://html.spec.whatwg.org/multipage/history.html#dom-history-replacestate +DOM::ExceptionOr History::replace_state(JS::Value data, String const&, String const& url) +{ + // NOTE: The second parameter of this function is intentionally unused. + return shared_history_push_replace_state(data, url, IsPush::No); +} + +// https://html.spec.whatwg.org/multipage/history.html#shared-history-push/replace-state-steps +DOM::ExceptionOr History::shared_history_push_replace_state(JS::Value, String const&, IsPush) +{ + // 1. Let document be history's associated Document. (NOTE: Not necessary) + + // 2. If document is not fully active, then throw a "SecurityError" DOMException. + if (!m_associated_document.is_fully_active()) + return DOM::SecurityError::create("Cannot perform pushState or replaceState on a document that isn't fully active."); + + // 3. Optionally, return. (For example, the user agent might disallow calls to these methods that are invoked on a timer, + // or from event listeners that are not triggered in response to a clear user action, or that are invoked in rapid succession.) + dbgln("FIXME: Implement shared_history_push_replace_state."); + return {}; + + // FIXME: Add the rest of the spec steps once they're added. +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/History.h b/Userland/Libraries/LibWeb/HTML/History.h new file mode 100644 index 0000000000..5e78cc6bd9 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/History.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Web::HTML { + +class History final + : public RefCounted + , public Weakable + , public Bindings::Wrappable { +public: + using WrapperType = Bindings::HistoryWrapper; + + static NonnullRefPtr create(DOM::Document& document) + { + return adopt_ref(*new History(document)); + } + + virtual ~History() override; + + DOM::ExceptionOr push_state(JS::Value data, String const& unused, String const& url); + DOM::ExceptionOr replace_state(JS::Value data, String const& unused, String const& url); + +private: + explicit History(DOM::Document&); + + enum class IsPush { + No, + Yes, + }; + DOM::ExceptionOr shared_history_push_replace_state(JS::Value data, String const& url, IsPush is_push); + + DOM::Document& m_associated_document; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/History.idl b/Userland/Libraries/LibWeb/HTML/History.idl new file mode 100644 index 0000000000..a44c4c55f8 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/History.idl @@ -0,0 +1,5 @@ +[Exposed=Window] +interface History { + undefined pushState(any data, DOMString unused, optional USVString? url = null); + undefined replaceState(any data, DOMString unused, optional USVString? url = null); +};