From af9a227ca3254b555a3267a1a9945953f756891e Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 17 Jun 2025 10:43:45 +0100 Subject: [PATCH] LibWeb/HTML: Implement HTMLElement.scrollParent Corresponds to https://github.com/w3c/csswg-drafts/commit/d3effb701c2fd6fe899586cd26a78ff3111f4ef7 What a "fixed position container" is isn't clear to me, and we don't seem to use that elsewhere, so I've left the steps using that as FIXMEs for now. There's no test coverage for this in WPT yet and I'm not confident enough in the specific behaviour to write one myself. So, waiting on https://github.com/web-platform-tests/wpt/issues/53214 --- Libraries/LibWeb/HTML/HTMLElement.cpp | 41 +++++++++++++++++++++++++++ Libraries/LibWeb/HTML/HTMLElement.h | 1 + Libraries/LibWeb/HTML/HTMLElement.idl | 1 + 3 files changed, 43 insertions(+) diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index 95974a4f6e9..af9b3c8828d 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -461,6 +461,47 @@ String HTMLElement::outer_text() return get_the_text_steps(); } +// https://drafts.csswg.org/cssom-view/#dom-htmlelement-scrollparent +GC::Ptr HTMLElement::scroll_parent() const +{ + // 1. If any of the following holds true, return null and terminate this algorithm: + // - The element does not have an associated box. + // - The element is the root element. + // - The element is the body element. + // - FIXME: The element’s computed value of the position property is fixed and no ancestor establishes a fixed position containing block. + if (!layout_node()) + return nullptr; + if (is_document_element()) + return nullptr; + if (is_html_body_element()) + return nullptr; + + // 2. Let ancestor be the containing block of the element in the flat tree and repeat these substeps: + auto ancestor = layout_node()->containing_block(); + while (true) { + // 1. If ancestor is the initial containing block, return the scrollingElement for the element’s document if it + // is not closed-shadow-hidden from the element, otherwise return null. + if (ancestor->is_viewport()) { + auto const scrolling_element = document().scrolling_element(); + if (scrolling_element && !scrolling_element->is_closed_shadow_hidden_from(*this)) + return const_cast(scrolling_element.ptr()); + return nullptr; + } + + // 2. If ancestor is not closed-shadow-hidden from the element, and is a scroll container, terminate this + // algorithm and return ancestor. + if (!ancestor->dom_node()->is_closed_shadow_hidden_from(*this) && ancestor->is_scroll_container()) { + return const_cast(static_cast(ancestor->dom_node())); + } + + // FIXME: 3. If the computed value of the position property of ancestor is fixed, and no ancestor establishes a fixed + // position containing block, terminate this algorithm and return null. + + // 4. Let ancestor be the containing block of ancestor in the flat tree. + ancestor = layout_node()->containing_block(); + } +} + // https://www.w3.org/TR/cssom-view-1/#dom-htmlelement-offsetparent GC::Ptr HTMLElement::offset_parent() const { diff --git a/Libraries/LibWeb/HTML/HTMLElement.h b/Libraries/LibWeb/HTML/HTMLElement.h index bb26b36afd8..405f26faa57 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Libraries/LibWeb/HTML/HTMLElement.h @@ -105,6 +105,7 @@ public: int offset_width() const; int offset_height() const; GC::Ptr offset_parent() const; + GC::Ptr scroll_parent() const; bool cannot_navigate() const; diff --git a/Libraries/LibWeb/HTML/HTMLElement.idl b/Libraries/LibWeb/HTML/HTMLElement.idl index 9df48602a0a..f8f6e8d6f20 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.idl +++ b/Libraries/LibWeb/HTML/HTMLElement.idl @@ -40,6 +40,7 @@ interface HTMLElement : Element { [CEReactions] attribute DOMString? popover; // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface + readonly attribute Element? scrollParent; readonly attribute Element? offsetParent; readonly attribute long offsetTop; readonly attribute long offsetLeft;