From 162e4179fcea9d4916fdd58c91e7ad492072574d Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 4 Oct 2022 19:52:25 +0200 Subject: [PATCH] LibWeb: Implement a simple version of Element.scrollIntoView() We parse the arguments that come in, but since we don't yet track scrollable overflow, we can't do the full "scroll an element into view" algorithm. For now, we just call out to the PageClient and ask it to bring the nearest principal box into the visible viewport. --- Userland/Libraries/LibWeb/DOM/Element.cpp | 74 +++++++++++++++++++++++ Userland/Libraries/LibWeb/DOM/Element.h | 15 +++++ Userland/Libraries/LibWeb/DOM/Element.idl | 14 +++++ 3 files changed, 103 insertions(+) diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index 3da9d40dd8..90462df78e 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -903,4 +905,76 @@ WebIDL::ExceptionOr Element::insert_adjacent_text(String const& where, Str return {}; } +// https://w3c.github.io/csswg-drafts/cssom-view-1/#scroll-an-element-into-view +static void scroll_an_element_into_view(DOM::Element& element, Bindings::ScrollBehavior behavior, Bindings::ScrollLogicalPosition block, Bindings::ScrollLogicalPosition inline_) +{ + // FIXME: The below is ad-hoc, since we don't yet have scrollable elements. + // Return here and implement this according to spec once all overflow is made scrollable. + + (void)behavior; + (void)block; + (void)inline_; + + if (!element.document().browsing_context()) + return; + + auto* page = element.document().browsing_context()->page(); + if (!page) + return; + + // If this element doesn't have a layout node, we can't scroll it into view. + element.document().update_layout(); + if (!element.layout_node()) + return; + + // Find the nearest layout node that is a box (since we need a box to get a usable rect) + auto* layout_node = element.layout_node(); + while (layout_node && !layout_node->is_box()) + layout_node = layout_node->parent(); + + if (!layout_node) + return; + + page->client().page_did_request_scroll_into_view(verify_cast(*layout_node).paint_box()->absolute_padding_box_rect().to_rounded()); +} + +// https://w3c.github.io/csswg-drafts/cssom-view-1/#dom-element-scrollintoview +void Element::scroll_into_view(Optional> arg) +{ + // 1. Let behavior be "auto". + auto behavior = Bindings::ScrollBehavior::Auto; + + // 2. Let block be "start". + auto block = Bindings::ScrollLogicalPosition::Start; + + // 3. Let inline be "nearest". + auto inline_ = Bindings::ScrollLogicalPosition::Nearest; + + // 4. If arg is a ScrollIntoViewOptions dictionary, then: + if (arg.has_value() && arg->has()) { + // 1. Set behavior to the behavior dictionary member of options. + behavior = arg->get().behavior; + + // 2. Set block to the block dictionary member of options. + block = arg->get().block; + + // 3. Set inline to the inline dictionary member of options. + inline_ = arg->get().inline_; + } + // 5. Otherwise, if arg is false, then set block to "end". + else if (arg.has_value() && arg->has() && arg->get() == false) { + block = Bindings::ScrollLogicalPosition::End; + } + + // 6. If the element does not have any associated box, or is not available to user-agent features, then return. + document().update_layout(); + if (!layout_node()) + return; + + // 7. Scroll the element into view with behavior, block, and inline. + scroll_an_element_into_view(*this, behavior, block, inline_); + + // FIXME: 8. Optionally perform some other action that brings the element to the user’s attention. +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index 63f9176993..f3b8736d77 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,17 @@ namespace Web::DOM { +// https://w3c.github.io/csswg-drafts/cssom-view-1/#dictdef-scrolloptions +struct ScrollOptions { + Bindings::ScrollBehavior behavior { Bindings::ScrollBehavior::Auto }; +}; + +// https://w3c.github.io/csswg-drafts/cssom-view-1/#dictdef-scrollintoviewoptions +struct ScrollIntoViewOptions : public ScrollOptions { + Bindings::ScrollLogicalPosition block { Bindings::ScrollLogicalPosition::Start }; + Bindings::ScrollLogicalPosition inline_ { Bindings::ScrollLogicalPosition::Nearest }; +}; + class Element : public ParentNode , public ChildNode @@ -147,6 +159,9 @@ public: WebIDL::ExceptionOr> insert_adjacent_element(String const& where, JS::NonnullGCPtr element); WebIDL::ExceptionOr insert_adjacent_text(String const& where, String const& data); + // https://w3c.github.io/csswg-drafts/cssom-view-1/#dom-element-scrollintoview + void scroll_into_view(Optional>); + protected: Element(Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; diff --git a/Userland/Libraries/LibWeb/DOM/Element.idl b/Userland/Libraries/LibWeb/DOM/Element.idl index 3bc26ce704..f3617d5fea 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.idl +++ b/Userland/Libraries/LibWeb/DOM/Element.idl @@ -9,6 +9,17 @@ #import #import +enum ScrollBehavior { "auto", "smooth" }; +dictionary ScrollOptions { + ScrollBehavior behavior = "auto"; +}; + +enum ScrollLogicalPosition { "start", "center", "end", "nearest" }; +dictionary ScrollIntoViewOptions : ScrollOptions { + ScrollLogicalPosition block = "start"; + ScrollLogicalPosition inline = "nearest"; +}; + // https://dom.spec.whatwg.org/#element interface Element : Node { readonly attribute DOMString? namespaceURI; @@ -56,6 +67,9 @@ interface Element : Node { [CEReactions] Element? insertAdjacentElement(DOMString where, Element element); undefined insertAdjacentText(DOMString where, DOMString data); [CEReactions] undefined insertAdjacentHTML(DOMString position, DOMString text); + + undefined scrollIntoView(optional (boolean or ScrollIntoViewOptions) arg = {}); + }; Element includes ParentNode;