diff options
Diffstat (limited to 'Userland/Libraries/LibWeb/CSS')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Selector.cpp | 14 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Selector.h | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp | 10 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/SelectorEngine.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleComputer.cpp | 87 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleComputer.h | 18 |
6 files changed, 86 insertions, 49 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp index 6b8e495370..83a4feaed3 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.cpp +++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -18,6 +19,19 @@ Selector::~Selector() { } +Optional<Selector::PseudoElement> Selector::pseudo_element() const +{ + // Note: This assumes that only one pseudo-element is allowed in a selector, and that it appears at the end. + // This is true currently, and there are no current proposals to change this, but you never know! + if (compound_selectors().is_empty()) + return {}; + for (auto const& simple_selector : compound_selectors().last().simple_selectors) { + if (simple_selector.type == SimpleSelector::Type::PseudoElement) + return simple_selector.pseudo_element; + } + return {}; +} + u32 Selector::specificity() const { if (m_specificity.has_value()) diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index 29112dfed2..6310ad1ba6 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> - * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org> + * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -131,7 +131,7 @@ public: ~Selector(); Vector<CompoundSelector> const& compound_selectors() const { return m_compound_selectors; } - + Optional<PseudoElement> pseudo_element() const; u32 specificity() const; String serialize() const; diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index 40be51417f..267b719467 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -192,8 +192,8 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, DOM:: case CSS::Selector::SimpleSelector::Type::PseudoClass: return matches_pseudo_class(component.pseudo_class, element); case CSS::Selector::SimpleSelector::Type::PseudoElement: - // FIXME: Implement pseudo-elements. - return false; + // Pseudo-element matching/not-matching is handled in the top level matches(). + return true; default: VERIFY_NOT_REACHED(); } @@ -241,9 +241,13 @@ static inline bool matches(CSS::Selector const& selector, int component_list_ind VERIFY_NOT_REACHED(); } -bool matches(CSS::Selector const& selector, DOM::Element const& element) +bool matches(CSS::Selector const& selector, DOM::Element const& element, Optional<CSS::Selector::PseudoElement> pseudo_element) { VERIFY(!selector.compound_selectors().is_empty()); + if (pseudo_element.has_value() && selector.pseudo_element() != pseudo_element) + return false; + if (!pseudo_element.has_value() && selector.pseudo_element().has_value()) + return false; return matches(selector, selector.compound_selectors().size() - 1, element); } diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.h b/Userland/Libraries/LibWeb/CSS/SelectorEngine.h index 42e13855a5..e39fa9cb8a 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.h +++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.h @@ -11,6 +11,6 @@ namespace Web::SelectorEngine { -bool matches(CSS::Selector const&, DOM::Element const&); +bool matches(CSS::Selector const&, DOM::Element const&, Optional<CSS::Selector::PseudoElement> = {}); } diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index 9780c02e67..413687dfcf 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> * Copyright (c) 2021, the SerenityOS developers. - * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org> + * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -70,9 +70,10 @@ void StyleComputer::for_each_stylesheet(CascadeOrigin cascade_origin, Callback c } } -Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin) const +Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::Selector::PseudoElement> pseudo_element) const { if (cascade_origin == CascadeOrigin::Author) { + // FIXME: Cache pseudo-element rules and look at only those if pseudo_element is set. Vector<MatchingRule> rules_to_run; for (auto const& class_name : element.class_names()) { if (auto it = m_rule_cache->rules_by_class.find(class_name); it != m_rule_cache->rules_by_class.end()) @@ -89,7 +90,7 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e Vector<MatchingRule> matching_rules; for (auto const& rule_to_run : rules_to_run) { auto const& selector = rule_to_run.rule->selectors()[rule_to_run.selector_index]; - if (SelectorEngine::matches(selector, element)) + if (SelectorEngine::matches(selector, element, pseudo_element)) matching_rules.append(rule_to_run); } return matching_rules; @@ -102,7 +103,7 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e static_cast<CSSStyleSheet const&>(sheet).for_each_effective_style_rule([&](auto const& rule) { size_t selector_index = 0; for (auto& selector : rule.selectors()) { - if (SelectorEngine::matches(selector, element)) { + if (SelectorEngine::matches(selector, element, pseudo_element)) { matching_rules.append({ rule, style_sheet_index, rule_index, selector_index, selector.specificity() }); break; } @@ -593,13 +594,13 @@ static HashMap<String, StyleProperty const*> cascade_custom_properties(DOM::Elem } // https://www.w3.org/TR/css-cascade/#cascading -void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element& element) const +void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element& element, Optional<CSS::Selector::PseudoElement> pseudo_element) const { // First, we collect all the CSS rules whose selectors match `element`: MatchingRuleSet matching_rule_set; - matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent); + matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element); sort_matching_rules(matching_rule_set.user_agent_rules); - matching_rule_set.author_rules = collect_matching_rules(element, CascadeOrigin::Author); + matching_rule_set.author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element); sort_matching_rules(matching_rule_set.author_rules); // Then we resolve all the CSS custom properties ("variables") for this element: @@ -631,21 +632,35 @@ void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element // FIXME: Transition declarations [css-transitions-1] } -static NonnullRefPtr<StyleValue> get_inherit_value(CSS::PropertyID property_id, DOM::Element const* element) +static DOM::Element const* get_parent_element(DOM::Element const* element, Optional<CSS::Selector::PseudoElement> pseudo_element) { - if (!element || !element->parent_element() || !element->parent_element()->specified_css_values()) + // Pseudo-elements treat their originating element as their parent. + DOM::Element const* parent_element = nullptr; + if (pseudo_element.has_value()) { + parent_element = element; + } else if (element) { + parent_element = element->parent_element(); + } + return parent_element; +} + +static NonnullRefPtr<StyleValue> get_inherit_value(CSS::PropertyID property_id, DOM::Element const* element, Optional<CSS::Selector::PseudoElement> pseudo_element) +{ + auto* parent_element = get_parent_element(element, pseudo_element); + + if (!parent_element || !parent_element->specified_css_values()) return property_initial_value(property_id); - return element->parent_element()->specified_css_values()->property(property_id).release_value(); + return parent_element->specified_css_values()->property(property_id).release_value(); }; -void StyleComputer::compute_defaulted_property_value(StyleProperties& style, DOM::Element const* element, CSS::PropertyID property_id) const +void StyleComputer::compute_defaulted_property_value(StyleProperties& style, DOM::Element const* element, CSS::PropertyID property_id, Optional<CSS::Selector::PseudoElement> pseudo_element) const { // FIXME: If we don't know the correct initial value for a property, we fall back to InitialStyleValue. auto& value_slot = style.m_property_values[to_underlying(property_id)]; if (!value_slot) { if (is_inherited_property(property_id)) - style.m_property_values[to_underlying(property_id)] = get_inherit_value(property_id, element); + style.m_property_values[to_underlying(property_id)] = get_inherit_value(property_id, element, pseudo_element); else style.m_property_values[to_underlying(property_id)] = property_initial_value(property_id); return; @@ -657,31 +672,33 @@ void StyleComputer::compute_defaulted_property_value(StyleProperties& style, DOM } if (value_slot->is_inherit()) { - value_slot = get_inherit_value(property_id, element); + value_slot = get_inherit_value(property_id, element, pseudo_element); return; } } // https://www.w3.org/TR/css-cascade/#defaulting -void StyleComputer::compute_defaulted_values(StyleProperties& style, DOM::Element const* element) const +void StyleComputer::compute_defaulted_values(StyleProperties& style, DOM::Element const* element, Optional<CSS::Selector::PseudoElement> pseudo_element) const { // Walk the list of all known CSS properties and: // - Add them to `style` if they are missing. // - Resolve `inherit` and `initial` as needed. for (auto i = to_underlying(CSS::first_longhand_property_id); i <= to_underlying(CSS::last_longhand_property_id); ++i) { auto property_id = (CSS::PropertyID)i; - compute_defaulted_property_value(style, element, property_id); + compute_defaulted_property_value(style, element, property_id, pseudo_element); } } -void StyleComputer::compute_font(StyleProperties& style, DOM::Element const* element) const +void StyleComputer::compute_font(StyleProperties& style, DOM::Element const* element, Optional<CSS::Selector::PseudoElement> pseudo_element) const { // To compute the font, first ensure that we've defaulted the relevant CSS font properties. // FIXME: This should be more sophisticated. - compute_defaulted_property_value(style, element, CSS::PropertyID::FontFamily); - compute_defaulted_property_value(style, element, CSS::PropertyID::FontSize); - compute_defaulted_property_value(style, element, CSS::PropertyID::FontStyle); - compute_defaulted_property_value(style, element, CSS::PropertyID::FontWeight); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontFamily, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontSize, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontStyle, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontWeight, pseudo_element); + + auto* parent_element = get_parent_element(element, pseudo_element); auto viewport_rect = document().browsing_context()->viewport_rect(); @@ -755,8 +772,8 @@ void StyleComputer::compute_font(StyleProperties& style, DOM::Element const* ele float root_font_size = 10; Gfx::FontMetrics font_metrics; - if (element && element->parent_element() && element->parent_element()->specified_css_values()) - font_metrics = element->parent_element()->specified_css_values()->computed_font().metrics('M'); + if (parent_element && parent_element->specified_css_values()) + font_metrics = parent_element->specified_css_values()->computed_font().metrics('M'); else font_metrics = Gfx::FontDatabase::default_font().metrics('M'); @@ -765,8 +782,8 @@ void StyleComputer::compute_font(StyleProperties& style, DOM::Element const* ele // Percentages refer to parent element's font size auto percentage = font_size->as_percentage().percentage(); auto parent_font_size = size; - if (element && element->parent_element() && element->parent_element()->layout_node() && element->parent_element()->specified_css_values()) { - auto value = element->parent_element()->specified_css_values()->property(CSS::PropertyID::FontSize).value(); + if (parent_element && parent_element->layout_node() && parent_element->specified_css_values()) { + auto value = parent_element->specified_css_values()->property(CSS::PropertyID::FontSize).value(); if (value->is_length()) { auto length = static_cast<LengthStyleValue const&>(*value).to_length(); if (length.is_absolute() || length.is_relative()) @@ -880,7 +897,7 @@ void StyleComputer::compute_font(StyleProperties& style, DOM::Element const* ele style.set_computed_font(found_font.release_nonnull()); } -void StyleComputer::absolutize_values(StyleProperties& style, DOM::Element const*) const +void StyleComputer::absolutize_values(StyleProperties& style, DOM::Element const*, Optional<CSS::Selector::PseudoElement>) const { auto viewport_rect = document().browsing_context()->viewport_rect(); auto font_metrics = style.computed_font().metrics('M'); @@ -903,7 +920,7 @@ void StyleComputer::absolutize_values(StyleProperties& style, DOM::Element const } // https://drafts.csswg.org/css-display/#transformations -void StyleComputer::transform_box_type_if_needed(StyleProperties& style, DOM::Element const&) const +void StyleComputer::transform_box_type_if_needed(StyleProperties& style, DOM::Element const&, Optional<CSS::Selector::PseudoElement>) const { // 2.7. Automatic Box Type Transformations @@ -940,9 +957,9 @@ void StyleComputer::transform_box_type_if_needed(StyleProperties& style, DOM::El NonnullRefPtr<StyleProperties> StyleComputer::create_document_style() const { auto style = StyleProperties::create(); - compute_font(style, nullptr); - compute_defaulted_values(style, nullptr); - absolutize_values(style, nullptr); + compute_font(style, nullptr, {}); + compute_defaulted_values(style, nullptr, {}); + absolutize_values(style, nullptr, {}); if (auto* browsing_context = m_document.browsing_context()) { auto viewport_rect = browsing_context->viewport_rect(); style->set_property(CSS::PropertyID::Width, CSS::LengthStyleValue::create(CSS::Length::make_px(viewport_rect.width()))); @@ -951,25 +968,25 @@ NonnullRefPtr<StyleProperties> StyleComputer::create_document_style() const return style; } -NonnullRefPtr<StyleProperties> StyleComputer::compute_style(DOM::Element& element) const +NonnullRefPtr<StyleProperties> StyleComputer::compute_style(DOM::Element& element, Optional<CSS::Selector::PseudoElement> pseudo_element) const { build_rule_cache_if_needed(); auto style = StyleProperties::create(); // 1. Perform the cascade. This produces the "specified style" - compute_cascaded_values(style, element); + compute_cascaded_values(style, element, pseudo_element); // 2. Compute the font, since that may be needed for font-relative CSS units - compute_font(style, &element); + compute_font(style, &element, pseudo_element); // 3. Absolutize values, turning font/viewport relative lengths into absolute lengths - absolutize_values(style, &element); + absolutize_values(style, &element, pseudo_element); // 4. Default the values, applying inheritance and 'initial' as needed - compute_defaulted_values(style, &element); + compute_defaulted_values(style, &element, pseudo_element); // 5. Run automatic box type transformations - transform_box_type_if_needed(style, element); + transform_box_type_if_needed(style, element, pseudo_element); return style; } diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.h b/Userland/Libraries/LibWeb/CSS/StyleComputer.h index f7bfcddd6f..3fecd1ce0c 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.h @@ -9,9 +9,11 @@ #include <AK/HashMap.h> #include <AK/NonnullRefPtrVector.h> +#include <AK/Optional.h> #include <AK/OwnPtr.h> #include <LibWeb/CSS/CSSStyleDeclaration.h> #include <LibWeb/CSS/Parser/StyleComponentValueRule.h> +#include <LibWeb/CSS/Selector.h> #include <LibWeb/CSS/StyleProperties.h> #include <LibWeb/Forward.h> @@ -52,7 +54,7 @@ public: DOM::Document const& document() const { return m_document; } NonnullRefPtr<StyleProperties> create_document_style() const; - NonnullRefPtr<StyleProperties> compute_style(DOM::Element&) const; + NonnullRefPtr<StyleProperties> compute_style(DOM::Element&, Optional<CSS::Selector::PseudoElement> = {}) const; // https://www.w3.org/TR/css-cascade/#origin enum class CascadeOrigin { @@ -63,18 +65,18 @@ public: Transition, }; - Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin) const; + Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::Selector::PseudoElement>) const; void invalidate_rule_cache(); private: - void compute_cascaded_values(StyleProperties&, DOM::Element&) const; - void compute_font(StyleProperties&, DOM::Element const*) const; - void compute_defaulted_values(StyleProperties&, DOM::Element const*) const; - void absolutize_values(StyleProperties&, DOM::Element const*) const; - void transform_box_type_if_needed(StyleProperties&, DOM::Element const&) const; + void compute_cascaded_values(StyleProperties&, DOM::Element&, Optional<CSS::Selector::PseudoElement>) const; + void compute_font(StyleProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement>) const; + void compute_defaulted_values(StyleProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement>) const; + void absolutize_values(StyleProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement>) const; + void transform_box_type_if_needed(StyleProperties&, DOM::Element const&, Optional<CSS::Selector::PseudoElement>) const; - void compute_defaulted_property_value(StyleProperties&, DOM::Element const*, CSS::PropertyID) const; + void compute_defaulted_property_value(StyleProperties&, DOM::Element const*, CSS::PropertyID, Optional<CSS::Selector::PseudoElement>) const; RefPtr<StyleValue> resolve_unresolved_style_value(DOM::Element&, PropertyID, UnresolvedStyleValue const&, HashMap<String, StyleProperty const*> const&) const; bool expand_unresolved_values(DOM::Element&, StringView property_name, HashMap<String, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest, size_t source_start_index, HashMap<String, StyleProperty const*> const& custom_properties) const; |