Browse Source

LibWeb: Implement basic version of CSSOM View's VisualViewport

We got some errors while loading https://twinings.co.uk/ about this
interface missing, and it looked fairly simple so I sketched it out.
Note that I did leave some FIXMEs where it's not clear exactly which
metrics we should be returning.
Andreas Kling 2 years ago
parent
commit
9f6ceff7cf

+ 1 - 0
Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/BUILD.gn

@@ -57,5 +57,6 @@ source_set("CSS") {
     "StyleValue.cpp",
     "StyleValue.cpp",
     "Supports.cpp",
     "Supports.cpp",
     "Time.cpp",
     "Time.cpp",
+    "VisualViewport.cpp",
   ]
   ]
 }
 }

+ 1 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -123,6 +123,7 @@ set(SOURCES
     CSS/Supports.cpp
     CSS/Supports.cpp
     CSS/SyntaxHighlighter/SyntaxHighlighter.cpp
     CSS/SyntaxHighlighter/SyntaxHighlighter.cpp
     CSS/Time.cpp
     CSS/Time.cpp
+    CSS/VisualViewport.cpp
     Cookie/Cookie.cpp
     Cookie/Cookie.cpp
     Cookie/ParsedCookie.cpp
     Cookie/ParsedCookie.cpp
     DOM/AbortController.cpp
     DOM/AbortController.cpp

+ 154 - 0
Userland/Libraries/LibWeb/CSS/VisualViewport.cpp

@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/Bindings/Intrinsics.h>
+#include <LibWeb/Bindings/VisualViewportPrototype.h>
+#include <LibWeb/CSS/VisualViewport.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/EventDispatcher.h>
+#include <LibWeb/DOM/IDLEventListener.h>
+#include <LibWeb/HTML/EventHandler.h>
+#include <LibWeb/HTML/EventNames.h>
+
+namespace Web::CSS {
+
+WebIDL::ExceptionOr<JS::NonnullGCPtr<VisualViewport>> VisualViewport::create(DOM::Document& document)
+{
+    return MUST_OR_THROW_OOM(document.heap().allocate<VisualViewport>(document.realm(), document));
+}
+
+VisualViewport::VisualViewport(DOM::Document& document)
+    : DOM::EventTarget(document.realm())
+    , m_document(document)
+{
+}
+
+JS::ThrowCompletionOr<void> VisualViewport::initialize(JS::Realm& realm)
+{
+    MUST_OR_THROW_OOM(Base::initialize(realm));
+    set_prototype(&Bindings::ensure_web_prototype<Bindings::VisualViewportPrototype>(realm, "VisualViewport"));
+
+    return {};
+}
+
+void VisualViewport::visit_edges(Cell::Visitor& visitor)
+{
+    Base::visit_edges(visitor);
+    visitor.visit(m_document);
+}
+
+// https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsetleft
+double VisualViewport::offset_left() const
+{
+    // 1. If the visual viewport’s associated document is not fully active, return 0.
+    if (!m_document->is_fully_active())
+        return 0;
+
+    // 2. Otherwise, return the offset of the left edge of the visual viewport from the left edge of the layout viewport.
+    VERIFY(m_document->browsing_context());
+    return m_document->browsing_context()->viewport_rect().left().to_double();
+}
+
+// https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsettop
+double VisualViewport::offset_top() const
+{
+    // 1. If the visual viewport’s associated document is not fully active, return 0.
+    if (!m_document->is_fully_active())
+        return 0;
+
+    // 2. Otherwise, return the offset of the top edge of the visual viewport from the top edge of the layout viewport.
+    VERIFY(m_document->browsing_context());
+    return m_document->browsing_context()->viewport_rect().top().to_double();
+}
+
+// https://drafts.csswg.org/cssom-view/#dom-visualviewport-pageleft
+double VisualViewport::page_left() const
+{
+    // 1. If the visual viewport’s associated document is not fully active, return 0.
+    if (!m_document->is_fully_active())
+        return 0;
+
+    // FIXME: 2. Otherwise, return the offset of the left edge of the visual viewport
+    //           from the left edge of the initial containing block of the layout viewport’s document.
+    return offset_left();
+}
+
+// https://drafts.csswg.org/cssom-view/#dom-visualviewport-pagetop
+double VisualViewport::page_top() const
+{
+    // 1. If the visual viewport’s associated document is not fully active, return 0.
+    if (!m_document->is_fully_active())
+        return 0;
+
+    // FIXME: 2. Otherwise, return the offset of the top edge of the visual viewport
+    //           from the top edge of the initial containing block of the layout viewport’s document.
+    return offset_top();
+}
+
+// https://drafts.csswg.org/cssom-view/#dom-visualviewport-width
+double VisualViewport::width() const
+{
+    // 1. If the visual viewport’s associated document is not fully active, return 0.
+    if (!m_document->is_fully_active())
+        return 0;
+
+    // 2. Otherwise, return the width of the visual viewport
+    //    FIXME: excluding the width of any rendered vertical classic scrollbar that is fixed to the visual viewport.
+    VERIFY(m_document->browsing_context());
+    return m_document->browsing_context()->viewport_rect().width().to_double();
+}
+
+// https://drafts.csswg.org/cssom-view/#dom-visualviewport-height
+double VisualViewport::height() const
+{
+    // 1. If the visual viewport’s associated document is not fully active, return 0.
+    if (!m_document->is_fully_active())
+        return 0;
+
+    // 2. Otherwise, return the height of the visual viewport
+    //    FIXME: excluding the height of any rendered vertical classic scrollbar that is fixed to the visual viewport.
+    VERIFY(m_document->browsing_context());
+    return m_document->browsing_context()->viewport_rect().height().to_double();
+}
+
+// https://drafts.csswg.org/cssom-view/#dom-visualviewport-scale
+double VisualViewport::scale() const
+{
+    // FIXME: Implement.
+    return 1;
+}
+
+void VisualViewport::set_onresize(WebIDL::CallbackType* event_handler)
+{
+    set_event_handler_attribute(HTML::EventNames::resize, event_handler);
+}
+
+WebIDL::CallbackType* VisualViewport::onresize()
+{
+    return event_handler_attribute(HTML::EventNames::resize);
+}
+
+void VisualViewport::set_onscroll(WebIDL::CallbackType* event_handler)
+{
+    set_event_handler_attribute(HTML::EventNames::scroll, event_handler);
+}
+
+WebIDL::CallbackType* VisualViewport::onscroll()
+{
+    return event_handler_attribute(HTML::EventNames::scroll);
+}
+
+void VisualViewport::set_onscrollend(WebIDL::CallbackType* event_handler)
+{
+    set_event_handler_attribute(HTML::EventNames::scrollend, event_handler);
+}
+
+WebIDL::CallbackType* VisualViewport::onscrollend()
+{
+    return event_handler_attribute(HTML::EventNames::scrollend);
+}
+
+}

+ 49 - 0
Userland/Libraries/LibWeb/CSS/VisualViewport.h

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/EventTarget.h>
+
+namespace Web::CSS {
+
+// https://drafts.csswg.org/cssom-view/#visualviewport
+class VisualViewport final : public DOM::EventTarget {
+    WEB_PLATFORM_OBJECT(VisualViewport, DOM::EventTarget);
+
+public:
+    static WebIDL::ExceptionOr<JS::NonnullGCPtr<VisualViewport>> create(DOM::Document&);
+
+    virtual ~VisualViewport() override = default;
+
+    [[nodiscard]] double offset_left() const;
+    [[nodiscard]] double offset_top() const;
+
+    [[nodiscard]] double page_left() const;
+    [[nodiscard]] double page_top() const;
+
+    [[nodiscard]] double width() const;
+    [[nodiscard]] double height() const;
+
+    [[nodiscard]] double scale() const;
+
+    void set_onresize(WebIDL::CallbackType*);
+    WebIDL::CallbackType* onresize();
+    void set_onscroll(WebIDL::CallbackType*);
+    WebIDL::CallbackType* onscroll();
+    void set_onscrollend(WebIDL::CallbackType*);
+    WebIDL::CallbackType* onscrollend();
+
+private:
+    explicit VisualViewport(DOM::Document&);
+
+    virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
+    virtual void visit_edges(Cell::Visitor&) override;
+
+    JS::NonnullGCPtr<DOM::Document> m_document;
+};
+
+}

+ 21 - 0
Userland/Libraries/LibWeb/CSS/VisualViewport.idl

@@ -0,0 +1,21 @@
+#import <DOM/EventTarget.idl>
+
+[Exposed=Window]
+interface VisualViewport : EventTarget {
+
+    readonly attribute double offsetLeft;
+    readonly attribute double offsetTop;
+
+    readonly attribute double pageLeft;
+    readonly attribute double pageTop;
+
+    readonly attribute double width;
+    readonly attribute double height;
+
+    readonly attribute double scale;
+
+    attribute EventHandler onresize;
+    attribute EventHandler onscroll;
+    attribute EventHandler onscrollend;
+
+};

+ 8 - 0
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -21,6 +21,7 @@
 #include <LibWeb/CSS/MediaQueryList.h>
 #include <LibWeb/CSS/MediaQueryList.h>
 #include <LibWeb/CSS/MediaQueryListEvent.h>
 #include <LibWeb/CSS/MediaQueryListEvent.h>
 #include <LibWeb/CSS/StyleComputer.h>
 #include <LibWeb/CSS/StyleComputer.h>
+#include <LibWeb/CSS/VisualViewport.h>
 #include <LibWeb/Cookie/ParsedCookie.h>
 #include <LibWeb/Cookie/ParsedCookie.h>
 #include <LibWeb/DOM/Attr.h>
 #include <LibWeb/DOM/Attr.h>
 #include <LibWeb/DOM/Comment.h>
 #include <LibWeb/DOM/Comment.h>
@@ -2807,6 +2808,13 @@ HTML::ListOfAvailableImages const& Document::list_of_available_images() const
     return *m_list_of_available_images;
     return *m_list_of_available_images;
 }
 }
 
 
+JS::NonnullGCPtr<CSS::VisualViewport> Document::visual_viewport()
+{
+    if (!m_visual_viewport)
+        m_visual_viewport = CSS::VisualViewport::create(*this).release_value_but_fixme_should_propagate_errors();
+    return *m_visual_viewport;
+}
+
 void Document::register_intersection_observer(Badge<IntersectionObserver::IntersectionObserver>, IntersectionObserver::IntersectionObserver& observer)
 void Document::register_intersection_observer(Badge<IntersectionObserver::IntersectionObserver>, IntersectionObserver::IntersectionObserver& observer)
 {
 {
     auto result = m_intersection_observers.set(observer);
     auto result = m_intersection_observers.set(observer);

+ 4 - 0
Userland/Libraries/LibWeb/DOM/Document.h

@@ -380,6 +380,8 @@ public:
     void evaluate_media_queries_and_report_changes();
     void evaluate_media_queries_and_report_changes();
     void add_media_query_list(JS::NonnullGCPtr<CSS::MediaQueryList>);
     void add_media_query_list(JS::NonnullGCPtr<CSS::MediaQueryList>);
 
 
+    JS::NonnullGCPtr<CSS::VisualViewport> visual_viewport();
+
     bool has_focus() const;
     bool has_focus() const;
 
 
     void set_parser(Badge<HTML::HTMLParser>, HTML::HTMLParser&);
     void set_parser(Badge<HTML::HTMLParser>, HTML::HTMLParser&);
@@ -668,6 +670,8 @@ private:
     // https://html.spec.whatwg.org/multipage/images.html#list-of-available-images
     // https://html.spec.whatwg.org/multipage/images.html#list-of-available-images
     OwnPtr<HTML::ListOfAvailableImages> m_list_of_available_images;
     OwnPtr<HTML::ListOfAvailableImages> m_list_of_available_images;
 
 
+    JS::GCPtr<CSS::VisualViewport> m_visual_viewport;
+
     // NOTE: Not in the spec per say, but Document must be able to access all IntersectionObservers whose root is in the document.
     // NOTE: Not in the spec per say, but Document must be able to access all IntersectionObservers whose root is in the document.
     OrderedHashTable<JS::NonnullGCPtr<IntersectionObserver::IntersectionObserver>> m_intersection_observers;
     OrderedHashTable<JS::NonnullGCPtr<IntersectionObserver::IntersectionObserver>> m_intersection_observers;
 
 

+ 2 - 1
Userland/Libraries/LibWeb/Forward.h

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020-2023, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2021, the SerenityOS developers.
  * Copyright (c) 2021, the SerenityOS developers.
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
@@ -181,6 +181,7 @@ class URLStyleValue;
 class UnicodeRange;
 class UnicodeRange;
 class UnresolvedStyleValue;
 class UnresolvedStyleValue;
 class UnsetStyleValue;
 class UnsetStyleValue;
+class VisualViewport;
 
 
 enum class MediaFeatureID;
 enum class MediaFeatureID;
 enum class PropertyID;
 enum class PropertyID;

+ 1 - 0
Userland/Libraries/LibWeb/HTML/EventNames.h

@@ -78,6 +78,7 @@ namespace Web::HTML::EventNames {
     __ENUMERATE_HTML_EVENT(reset)                    \
     __ENUMERATE_HTML_EVENT(reset)                    \
     __ENUMERATE_HTML_EVENT(resize)                   \
     __ENUMERATE_HTML_EVENT(resize)                   \
     __ENUMERATE_HTML_EVENT(scroll)                   \
     __ENUMERATE_HTML_EVENT(scroll)                   \
+    __ENUMERATE_HTML_EVENT(scrollend)                \
     __ENUMERATE_HTML_EVENT(securitypolicyviolation)  \
     __ENUMERATE_HTML_EVENT(securitypolicyviolation)  \
     __ENUMERATE_HTML_EVENT(seeked)                   \
     __ENUMERATE_HTML_EVENT(seeked)                   \
     __ENUMERATE_HTML_EVENT(seeking)                  \
     __ENUMERATE_HTML_EVENT(seeking)                  \

+ 11 - 0
Userland/Libraries/LibWeb/HTML/Window.cpp

@@ -1085,6 +1085,17 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<CSS::Screen>> Window::screen()
     return JS::NonnullGCPtr { *m_screen };
     return JS::NonnullGCPtr { *m_screen };
 }
 }
 
 
+WebIDL::ExceptionOr<JS::GCPtr<CSS::VisualViewport>> Window::visual_viewport()
+{
+    // If the associated document is fully active, the visualViewport attribute must return
+    // the VisualViewport object associated with the Window object’s associated document.
+    if (associated_document().is_fully_active())
+        return associated_document().visual_viewport();
+
+    // Otherwise, it must return null.
+    return nullptr;
+}
+
 // https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-innerwidth
 // https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-innerwidth
 i32 Window::inner_width() const
 i32 Window::inner_width() const
 {
 {

+ 2 - 1
Userland/Libraries/LibWeb/HTML/Window.h

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020-2023, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
  * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
@@ -156,6 +156,7 @@ public:
 
 
     WebIDL::ExceptionOr<JS::NonnullGCPtr<CSS::MediaQueryList>> match_media(String const& query);
     WebIDL::ExceptionOr<JS::NonnullGCPtr<CSS::MediaQueryList>> match_media(String const& query);
     WebIDL::ExceptionOr<JS::NonnullGCPtr<CSS::Screen>> screen();
     WebIDL::ExceptionOr<JS::NonnullGCPtr<CSS::Screen>> screen();
+    WebIDL::ExceptionOr<JS::GCPtr<CSS::VisualViewport>> visual_viewport();
 
 
     i32 inner_width() const;
     i32 inner_width() const;
     i32 inner_height() const;
     i32 inner_height() const;

+ 2 - 0
Userland/Libraries/LibWeb/HTML/Window.idl

@@ -1,6 +1,7 @@
 #import <Crypto/Crypto.idl>
 #import <Crypto/Crypto.idl>
 #import <CSS/MediaQueryList.idl>
 #import <CSS/MediaQueryList.idl>
 #import <CSS/Screen.idl>
 #import <CSS/Screen.idl>
+#import <CSS/VisualViewport.idl>
 #import <DOM/Document.idl>
 #import <DOM/Document.idl>
 #import <DOM/EventHandler.idl>
 #import <DOM/EventHandler.idl>
 #import <DOM/EventTarget.idl>
 #import <DOM/EventTarget.idl>
@@ -57,6 +58,7 @@ interface Window : EventTarget {
     // https://w3c.github.io/csswg-drafts/cssom-view/#extensions-to-the-window-interface
     // https://w3c.github.io/csswg-drafts/cssom-view/#extensions-to-the-window-interface
     [NewObject] MediaQueryList matchMedia(CSSOMString query);
     [NewObject] MediaQueryList matchMedia(CSSOMString query);
     [SameObject, Replaceable] readonly attribute Screen screen;
     [SameObject, Replaceable] readonly attribute Screen screen;
+    [SameObject, Replaceable] readonly attribute VisualViewport? visualViewport;
 
 
     // viewport
     // viewport
     [Replaceable] readonly attribute long innerWidth;
     [Replaceable] readonly attribute long innerWidth;

+ 1 - 0
Userland/Libraries/LibWeb/idl_files.cmake

@@ -24,6 +24,7 @@ libweb_js_bindings(CSS/MediaQueryListEvent)
 libweb_js_bindings(CSS/Screen)
 libweb_js_bindings(CSS/Screen)
 libweb_js_bindings(CSS/StyleSheet)
 libweb_js_bindings(CSS/StyleSheet)
 libweb_js_bindings(CSS/StyleSheetList)
 libweb_js_bindings(CSS/StyleSheetList)
+libweb_js_bindings(CSS/VisualViewport)
 libweb_js_bindings(DOM/AbstractRange)
 libweb_js_bindings(DOM/AbstractRange)
 libweb_js_bindings(DOM/Attr)
 libweb_js_bindings(DOM/Attr)
 libweb_js_bindings(DOM/AbortController)
 libweb_js_bindings(DOM/AbortController)