From 2660c80a9837a0e13ddfaf94a8858c744acd69b2 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 6 Aug 2024 12:48:21 +0100 Subject: [PATCH] LibWeb: Implement getComputedStyle() pseudoElement parameter Right now, we deviate from the CSSOM spec regarding our CSSStyleDeclaration classes, so this is not as close to the spec as I'd like. But it works, which means we'll be able to test pseudo-element styling a lot more easily. :^) (cherry picked from commit 14611de362d1d41429688dc02ffaf037a32e2e5d) --- .../css/getComputedStyle-pseudo-element.txt | 6 +++ .../css/getComputedStyle-pseudo-element.html | 32 +++++++++++++ .../CSS/ResolvedCSSStyleDeclaration.cpp | 35 ++++++++++---- .../LibWeb/CSS/ResolvedCSSStyleDeclaration.h | 6 ++- Userland/Libraries/LibWeb/HTML/Window.cpp | 48 +++++++++++++++++-- 5 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/css/getComputedStyle-pseudo-element.txt create mode 100644 Tests/LibWeb/Text/input/css/getComputedStyle-pseudo-element.html diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-pseudo-element.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-pseudo-element.txt new file mode 100644 index 0000000000..521bb53ad9 --- /dev/null +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-pseudo-element.txt @@ -0,0 +1,6 @@ +hi #foo: + color: rgb(128, 128, 128) + background-color: rgb(255, 255, 0) +#foo::before: + color: rgb(128, 128, 128) + background-color: rgb(0, 255, 255) diff --git a/Tests/LibWeb/Text/input/css/getComputedStyle-pseudo-element.html b/Tests/LibWeb/Text/input/css/getComputedStyle-pseudo-element.html new file mode 100644 index 0000000000..caf4a9cf1e --- /dev/null +++ b/Tests/LibWeb/Text/input/css/getComputedStyle-pseudo-element.html @@ -0,0 +1,32 @@ + + + +
+ diff --git a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index 80e6c04a3f..5082f39491 100644 --- a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -46,14 +46,15 @@ namespace Web::CSS { JS_DEFINE_ALLOCATOR(ResolvedCSSStyleDeclaration); -JS::NonnullGCPtr ResolvedCSSStyleDeclaration::create(DOM::Element& element) +JS::NonnullGCPtr ResolvedCSSStyleDeclaration::create(DOM::Element& element, Optional pseudo_element) { - return element.realm().heap().allocate(element.realm(), element); + return element.realm().heap().allocate(element.realm(), element, move(pseudo_element)); } -ResolvedCSSStyleDeclaration::ResolvedCSSStyleDeclaration(DOM::Element& element) +ResolvedCSSStyleDeclaration::ResolvedCSSStyleDeclaration(DOM::Element& element, Optional pseudo_element) : CSSStyleDeclaration(element.realm()) , m_element(element) + , m_pseudo_element(move(pseudo_element)) { } @@ -208,6 +209,12 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_property(L return {}; }; + auto get_computed_value = [this](PropertyID property_id) { + if (m_pseudo_element.has_value()) + return m_element->pseudo_element_computed_css_values(m_pseudo_element.value())->property(property_id); + return m_element->computed_css_values()->property(property_id); + }; + // A limited number of properties have special rules for producing their "resolved value". // We also have to manually construct shorthands from their longhands here. // Everything else uses the computed value. @@ -255,7 +262,7 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_property(L // -> line-height // The resolved value is normal if the computed value is normal, or the used value otherwise. case PropertyID::LineHeight: { - auto line_height = static_cast(*layout_node.dom_node()).computed_css_values()->property(property_id); + auto line_height = get_computed_value(property_id); if (line_height->is_identifier() && line_height->to_identifier() == ValueID::Normal) return line_height; return LengthStyleValue::create(Length::make_px(layout_node.computed_values().line_height())); @@ -515,7 +522,7 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_property(L return nullptr; default: if (!property_is_shorthand(property_id)) - return static_cast(*layout_node.dom_node()).computed_css_values()->property(property_id); + return get_computed_value(property_id); // Handle shorthands in a generic way auto longhand_ids = longhands_for_shorthand(property_id); @@ -534,18 +541,27 @@ Optional ResolvedCSSStyleDeclaration::property(PropertyID propert if (!m_element->is_connected()) return {}; + auto get_layout_node = [&]() { + if (m_pseudo_element.has_value()) + return m_element->get_pseudo_element_node(m_pseudo_element.value()); + return m_element->layout_node(); + }; + + Layout::NodeWithStyle* layout_node = get_layout_node(); + // FIXME: Be smarter about updating layout if there's no layout node. // We may legitimately have no layout node if we're not visible, but this protects against situations // where we're requesting the computed style before layout has happened. - if (!m_element->layout_node() || property_affects_layout(property_id)) { + if (!layout_node || property_affects_layout(property_id)) { const_cast(m_element->document()).update_layout(); + layout_node = get_layout_node(); } else { // FIXME: If we had a way to update style for a single element, this would be a good place to use it. const_cast(m_element->document()).update_style(); } - if (!m_element->layout_node()) { - auto style = m_element->document().style_computer().compute_style(const_cast(*m_element)); + if (!layout_node) { + auto style = m_element->document().style_computer().compute_style(const_cast(*m_element), m_pseudo_element); // FIXME: This is a stopgap until we implement shorthand -> longhand conversion. auto value = style->maybe_null_property(property_id); @@ -559,8 +575,7 @@ Optional ResolvedCSSStyleDeclaration::property(PropertyID propert }; } - auto& layout_node = *m_element->layout_node(); - auto value = style_value_for_property(layout_node, property_id); + auto value = style_value_for_property(*layout_node, property_id); if (!value) return {}; return StyleProperty { diff --git a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h index 422f82d1a3..1c7b04eda0 100644 --- a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h +++ b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h @@ -7,6 +7,7 @@ #pragma once #include +#include namespace Web::CSS { @@ -15,7 +16,7 @@ class ResolvedCSSStyleDeclaration final : public CSSStyleDeclaration { JS_DECLARE_ALLOCATOR(ResolvedCSSStyleDeclaration); public: - [[nodiscard]] static JS::NonnullGCPtr create(DOM::Element&); + [[nodiscard]] static JS::NonnullGCPtr create(DOM::Element&, Optional = {}); virtual ~ResolvedCSSStyleDeclaration() override = default; @@ -30,13 +31,14 @@ public: virtual WebIDL::ExceptionOr set_css_text(StringView) override; private: - explicit ResolvedCSSStyleDeclaration(DOM::Element&); + explicit ResolvedCSSStyleDeclaration(DOM::Element&, Optional); virtual void visit_edges(Cell::Visitor&) override; RefPtr style_value_for_property(Layout::NodeWithStyle const&, PropertyID) const; JS::NonnullGCPtr m_element; + Optional m_pseudo_element; }; } diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index 8889c45c8a..8447fcea23 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -1138,9 +1138,51 @@ Variant, JS::Value> Window::event() const // https://w3c.github.io/csswg-drafts/cssom/#dom-window-getcomputedstyle JS::NonnullGCPtr Window::get_computed_style(DOM::Element& element, Optional const& pseudo_element) const { - // FIXME: Make this fully spec compliant. - (void)pseudo_element; - return heap().allocate(realm(), element); + // 1. Let doc be elt’s node document. + + // 2. Let obj be elt. + Optional obj_pseudo; + + // 3. If pseudoElt is provided, is not the empty string, and starts with a colon, then: + if (pseudo_element.has_value() && pseudo_element.value().starts_with(':')) { + // 1. Parse pseudoElt as a , and let type be the result. + auto type = parse_pseudo_element_selector(CSS::Parser::ParsingContext(associated_document()), pseudo_element.value()); + + // 2. If type is failure, or is an ::slotted() or ::part() pseudo-element, let obj be null. + // FIXME: We can't pass a null element to ResolvedCSSStyleDeclaration + if (!type.has_value()) { + } + // 3. Otherwise let obj be the given pseudo-element of elt. + else { + // TODO: Keep the function arguments of the pseudo-element if there are any. + obj_pseudo = type.value().type(); + } + } + + // AD-HOC: Just return a ResolvedCSSStyleDeclaration because that's what we have for now. + // FIXME: Implement CSSStyleProperties, and then follow the rest of these steps instead. + return heap().allocate(realm(), element, obj_pseudo); + + // 4. Let decls be an empty list of CSS declarations. + + // 5. If obj is not null, and elt is connected, part of the flat tree, and its shadow-including root + // has a browsing context which either doesn’t have a browsing context container, or whose browsing + // context container is being rendered, set decls to a list of all longhand properties that are + // supported CSS properties, in lexicographical order, with the value being the resolved value + // computed for obj using the style rules associated with doc. Additionally, append to decls all + // the custom properties whose computed value for obj is not the guaranteed-invalid value. + + // 6. Return a live CSSStyleProperties object with the following properties: + // computed flag + // Set. + // readonly flag + // Set. + // declarations + // decls. + // parent CSS rule + // Null. + // owner node + // obj. } // https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-matchmedia