From 19276008529d638a504c2453cbddaf7c9936ec99 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Sat, 11 Sep 2021 23:43:34 +0100 Subject: [PATCH] LibWeb: Add the History object and stub pushState and replaceState The spec allows us to optionally return from these for any reason. Our reason is that we don't have all the infrastructure in place yet to implement them. --- .../LibWeb/Bindings/WindowObject.cpp | 10 ++++ .../Libraries/LibWeb/Bindings/WindowObject.h | 1 + .../LibWeb/Bindings/WindowObjectHelper.h | 3 ++ Userland/Libraries/LibWeb/CMakeLists.txt | 2 + Userland/Libraries/LibWeb/DOM/Document.cpp | 1 + Userland/Libraries/LibWeb/DOM/Document.h | 5 ++ Userland/Libraries/LibWeb/Forward.h | 1 + Userland/Libraries/LibWeb/HTML/History.cpp | 52 +++++++++++++++++++ Userland/Libraries/LibWeb/HTML/History.h | 47 +++++++++++++++++ Userland/Libraries/LibWeb/HTML/History.idl | 5 ++ 10 files changed, 127 insertions(+) create mode 100644 Userland/Libraries/LibWeb/HTML/History.cpp create mode 100644 Userland/Libraries/LibWeb/HTML/History.h create mode 100644 Userland/Libraries/LibWeb/HTML/History.idl 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); +};