diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index 6fc9bc461b..a06f40af8d 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -36,48 +36,20 @@ NonnullRefPtr StyleProperties::clone() const void StyleProperties::set_property(CSS::PropertyID id, NonnullRefPtr value) { - m_property_values.set((unsigned)id, move(value)); + m_property_values.set(id, move(value)); } void StyleProperties::set_property(CSS::PropertyID id, const StringView& value) { - m_property_values.set((unsigned)id, StringStyleValue::create(value)); + m_property_values.set(id, StringStyleValue::create(value)); } -Optional> StyleProperties::property(CSS::PropertyID id) const +Optional> StyleProperties::property(CSS::PropertyID property_id) const { - auto fetch_initial = [](CSS::PropertyID id) -> Optional> { - auto initial_value = property_initial_value(id); - if (initial_value) - return initial_value.release_nonnull(); + auto it = m_property_values.find(property_id); + if (it == m_property_values.end()) return {}; - }; - auto fetch_inherited = [](CSS::PropertyID) -> Optional> { - // FIXME: Implement inheritance - return {}; - }; - - auto it = m_property_values.find((unsigned)id); - if (it == m_property_values.end()) { - if (is_inherited_property(id)) - return fetch_inherited(id); - return fetch_initial(id); - } - - auto& value = it->value; - if (value->is_initial()) - return fetch_initial(id); - if (value->is_inherit()) - return fetch_inherited(id); - if (value->is_unset()) { - if (is_inherited_property(id)) { - return fetch_inherited(id); - } else { - return fetch_initial(id); - } - } - - return value; + return it->value; } Length StyleProperties::length_or_fallback(CSS::PropertyID id, const Length& fallback) const diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index 36284f8549..05fcc8cefb 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -82,7 +82,9 @@ public: Optional z_index() const; private: - HashMap> m_property_values; + friend class StyleResolver; + + HashMap> m_property_values; Optional overflow(CSS::PropertyID) const; void load_font(Layout::Node const&) const; diff --git a/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp index be94138111..0e6f283263 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp @@ -52,22 +52,26 @@ static StyleSheet& quirks_mode_stylesheet() } template -void StyleResolver::for_each_stylesheet(Callback callback) const +void StyleResolver::for_each_stylesheet(CascadeOrigin cascade_origin, Callback callback) const { - callback(default_stylesheet()); - if (document().in_quirks_mode()) - callback(quirks_mode_stylesheet()); - for (auto& sheet : document().style_sheets().sheets()) { - callback(sheet); + if (cascade_origin == CascadeOrigin::Any || cascade_origin == CascadeOrigin::UserAgent) { + callback(default_stylesheet()); + if (document().in_quirks_mode()) + callback(quirks_mode_stylesheet()); + } + if (cascade_origin == CascadeOrigin::Any || cascade_origin == CascadeOrigin::Author) { + for (auto& sheet : document().style_sheets().sheets()) { + callback(sheet); + } } } -Vector StyleResolver::collect_matching_rules(DOM::Element const& element) const +Vector StyleResolver::collect_matching_rules(DOM::Element const& element, CascadeOrigin declaration_type) const { Vector matching_rules; size_t style_sheet_index = 0; - for_each_stylesheet([&](auto& sheet) { + for_each_stylesheet(declaration_type, [&](auto& sheet) { size_t rule_index = 0; static_cast(sheet).for_each_effective_style_rule([&](auto& rule) { size_t selector_index = 0; @@ -531,54 +535,129 @@ Optional StyleResolver::resolve_custom_property(DOM::Element& ele return resolved_with_specificity.style; } -NonnullRefPtr StyleResolver::resolve_style(DOM::Element& element) const +struct MatchingDeclarations { + Vector user_agent_rules; + Vector author_rules; +}; + +void StyleResolver::cascade_declarations(StyleProperties& style, DOM::Element& element, Vector const& matching_rules, CascadeOrigin cascade_origin, bool important) const { - auto style = StyleProperties::create(); - auto* parent_style = element.parent_element() ? element.parent_element()->specified_css_values() : nullptr; - if (parent_style) { - parent_style->for_each_property([&](auto property_id, auto& value) { - if (is_inherited_property(property_id)) - set_property_expanding_shorthands(style, property_id, value, m_document); - }); - } - - element.apply_presentational_hints(*style); - - auto matching_rules = collect_matching_rules(element); - sort_matching_rules(matching_rules); - for (auto& match : matching_rules) { for (auto& property : verify_cast(match.rule->declaration()).properties()) { + if (important != property.important) + continue; auto property_value = property.value; if (property.value->is_custom_property()) { - auto prop = reinterpret_cast(property.value.ptr()); - auto custom_prop_name = prop->custom_property_name(); - auto resolved = resolve_custom_property(element, custom_prop_name); + auto custom_property_name = static_cast(*property.value).custom_property_name(); + auto resolved = resolve_custom_property(element, custom_property_name); if (resolved.has_value()) { property_value = resolved.value().value; } } - // FIXME: This also captures shorthands of which we ideally want to resolve the long names separately. - if (property_value->is_inherit()) { - // HACK: Trying to resolve the font property here lead to copious amounts of debug-spam - if (property.property_id == CSS::PropertyID::Font) - continue; - if (parent_style) { - auto maybe_parent_property_value = parent_style->property(property.property_id); - if (maybe_parent_property_value.has_value()) - property_value = maybe_parent_property_value.release_value(); - } - } set_property_expanding_shorthands(style, property.property_id, property_value, m_document); } } - if (auto* inline_style = verify_cast(element.inline_style())) { - for (auto& property : inline_style->properties()) { - set_property_expanding_shorthands(style, property.property_id, property.value, m_document); + if (cascade_origin == CascadeOrigin::Author) { + if (auto* inline_style = verify_cast(element.inline_style())) { + for (auto& property : inline_style->properties()) { + if (important != property.important) + continue; + set_property_expanding_shorthands(style, property.property_id, property.value, m_document); + } } } +} +// https://drafts.csswg.org/css-cascade/#cascading +void StyleResolver::compute_cascaded_values(StyleProperties& style, DOM::Element& 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); + sort_matching_rules(matching_rule_set.user_agent_rules); + matching_rule_set.author_rules = collect_matching_rules(element, CascadeOrigin::Author); + sort_matching_rules(matching_rule_set.author_rules); + + // Then we apply the declarations from the matched rules in cascade order: + + // Normal user agent declarations + cascade_declarations(style, element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, false); + + // FIXME: Normal user declarations + + // Normal author declarations + cascade_declarations(style, element, matching_rule_set.author_rules, CascadeOrigin::Author, false); + + // Author presentational hints (NOTE: The spec doesn't say exactly how to prioritize these.) + element.apply_presentational_hints(style); + + // FIXME: Animation declarations [css-animations-1] + + // Important author declarations + cascade_declarations(style, element, matching_rule_set.author_rules, CascadeOrigin::Author, true); + + // FIXME: Important user declarations + + // Important user agent declarations + cascade_declarations(style, element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, true); + + // FIXME: Transition declarations [css-transitions-1] +} + +// https://drafts.csswg.org/css-cascade/#defaulting +void StyleResolver::compute_defaulted_values(StyleProperties& style, DOM::Element const& element) const +{ + // FIXME: If we don't know the correct initial value for a property, we fall back to InitialStyleValue. + + auto get_initial_value = [&](PropertyID property_id) -> NonnullRefPtr { + auto value = property_initial_value(property_id); + if (!value) + return InitialStyleValue::the(); + return value.release_nonnull(); + }; + + auto get_inherit_value = [&](PropertyID property_id) -> NonnullRefPtr { + if (!element.parent_element() || !element.parent_element()->specified_css_values()) + return get_initial_value(property_id); + auto& map = element.parent_element()->specified_css_values()->m_property_values; + auto it = map.find(property_id); + VERIFY(it != map.end()); + return const_cast(*it->value); + }; + + // 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_property_id); i <= to_underlying(CSS::last_property_id); ++i) { + auto property_id = (CSS::PropertyID)i; + auto it = style.m_property_values.find(property_id); + if (it == style.m_property_values.end()) { + + if (is_inherited_property(property_id)) + style.m_property_values.set(property_id, get_inherit_value(property_id)); + else + style.m_property_values.set(property_id, get_initial_value(property_id)); + continue; + } + + if (it->value->is_initial()) { + it->value = get_initial_value(property_id); + continue; + } + + if (it->value->is_inherit()) { + it->value = get_inherit_value(property_id); + continue; + } + } +} + +NonnullRefPtr StyleResolver::resolve_style(DOM::Element& element) const +{ + auto style = StyleProperties::create(); + compute_cascaded_values(style, element); + compute_defaulted_values(style, element); return style; } diff --git a/Userland/Libraries/LibWeb/CSS/StyleResolver.h b/Userland/Libraries/LibWeb/CSS/StyleResolver.h index f21415706f..04dd65cf0a 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleResolver.h +++ b/Userland/Libraries/LibWeb/CSS/StyleResolver.h @@ -32,7 +32,17 @@ public: NonnullRefPtr resolve_style(DOM::Element&) const; - Vector collect_matching_rules(DOM::Element const&) const; + // https://www.w3.org/TR/css-cascade/#origin + enum class CascadeOrigin { + Any, // FIXME: This is not part of the spec. Get rid of it. + Author, + User, + UserAgent, + Animation, + Transition, + }; + + Vector collect_matching_rules(DOM::Element const&, CascadeOrigin = CascadeOrigin::Any) const; void sort_matching_rules(Vector&) const; struct CustomPropertyResolutionTuple { Optional style {}; @@ -42,8 +52,18 @@ public: Optional resolve_custom_property(DOM::Element&, String const&) const; private: + void compute_cascaded_values(StyleProperties&, DOM::Element&) const; + void compute_defaulted_values(StyleProperties&, DOM::Element const&) const; + template - void for_each_stylesheet(Callback) const; + void for_each_stylesheet(CascadeOrigin, Callback) const; + + struct MatchingRuleSet { + Vector user_agent_rules; + Vector author_rules; + }; + + void cascade_declarations(StyleProperties&, DOM::Element&, Vector const&, CascadeOrigin, bool important) const; DOM::Document& m_document; };