mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
LibWeb: Add StyleScope to keep style caches per Document/ShadowRoot
Before this change, we've been maintaining various StyleComputer caches at the document level. This made sense for old-school documents without shadow trees, since all the style information was document-wide anyway. However, documents with many shadow trees ended up suffering since any time you mutated a style sheet inside a shadow tree, *all* style caches for the entire document would get invalidated. This was particularly expensive on Reddit, which has tons of shadow trees with their own style elements. Every time we'd create one of their custom elements, we'd invalidate the document-level "rule cache" and have to rebuild it, taking about ~60ms each time (ouch). This commit introduces a new object called StyleScope. Every Document and ShadowRoot has its own StyleScope. Rule caches etc are moved from StyleComputer to StyleScope. Rule cache invalidation now happens at StyleScope level. As an example, rule cache rebuilds now take ~1ms on Reddit instead of ~60ms. This is largely a mechanical change, moving things around, but there's one key detail to be aware of: due to the :host selector, which works across the shadow DOM boundary and reaches from inside a shadow tree out into the light tree, there are various places where we have to check both the shadow tree's StyleScope *and* the document-level StyleScope in order to get all rules that may apply.
This commit is contained in:
parent
70b5496ecd
commit
66263f142b
Notes:
github-actions[bot]
2025-11-14 21:06:49 +00:00
Author: https://github.com/awesomekling
Commit: 66263f142b
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6827
21 changed files with 734 additions and 561 deletions
|
|
@ -215,6 +215,7 @@ set(SOURCES
|
|||
CSS/StyleProperty.cpp
|
||||
CSS/StylePropertyMapReadOnly.cpp
|
||||
CSS/StylePropertyMap.cpp
|
||||
CSS/StyleScope.cpp
|
||||
CSS/StyleSheet.cpp
|
||||
CSS/StyleSheetIdentifier.cpp
|
||||
CSS/StyleSheetList.cpp
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ String CSSLayerBlockRule::serialized() const
|
|||
return builder.to_string_without_validation();
|
||||
}
|
||||
|
||||
FlyString CSSLayerBlockRule::internal_qualified_name(Badge<StyleComputer>) const
|
||||
FlyString CSSLayerBlockRule::internal_qualified_name(Badge<StyleScope>) const
|
||||
{
|
||||
auto const& parent_name = parent_layer_internal_qualified_name();
|
||||
if (parent_name.is_empty())
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public:
|
|||
|
||||
FlyString const& name() const { return m_name; }
|
||||
FlyString const& internal_name() const { return m_name_internal; }
|
||||
FlyString internal_qualified_name(Badge<StyleComputer>) const;
|
||||
FlyString internal_qualified_name(Badge<StyleScope>) const;
|
||||
|
||||
private:
|
||||
CSSLayerBlockRule(JS::Realm&, FlyString name, CSSRuleList&);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ String CSSLayerStatementRule::serialized() const
|
|||
return builder.to_string_without_validation();
|
||||
}
|
||||
|
||||
Vector<FlyString> CSSLayerStatementRule::internal_qualified_name_list(Badge<StyleComputer>) const
|
||||
Vector<FlyString> CSSLayerStatementRule::internal_qualified_name_list(Badge<StyleScope>) const
|
||||
{
|
||||
Vector<FlyString> qualified_layer_names;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public:
|
|||
|
||||
// FIXME: Should be FrozenArray
|
||||
ReadonlySpan<FlyString> name_list() const { return m_name_list; }
|
||||
Vector<FlyString> internal_qualified_name_list(Badge<StyleComputer>) const;
|
||||
Vector<FlyString> internal_qualified_name_list(Badge<StyleScope>) const;
|
||||
|
||||
private:
|
||||
CSSLayerStatementRule(JS::Realm&, Vector<FlyString> name_list);
|
||||
|
|
|
|||
|
|
@ -353,7 +353,8 @@ void CSSStyleSheet::invalidate_owners(DOM::StyleInvalidationReason reason)
|
|||
m_did_match = {};
|
||||
for (auto& document_or_shadow_root : m_owning_documents_or_shadow_roots) {
|
||||
document_or_shadow_root->invalidate_style(reason);
|
||||
document_or_shadow_root->document().style_computer().invalidate_rule_cache();
|
||||
auto& style_scope = document_or_shadow_root->is_shadow_root() ? as<DOM::ShadowRoot>(*document_or_shadow_root).style_scope() : document_or_shadow_root->document().style_scope();
|
||||
style_scope.invalidate_rule_cache();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -204,7 +204,6 @@ StyleComputer::StyleComputer(DOM::Document& document)
|
|||
, m_root_element_font_metrics(m_default_font_metrics)
|
||||
{
|
||||
m_ancestor_filter = make<CountingBloomFilter<u8, 14>>();
|
||||
m_qualified_layer_names_in_order.append({});
|
||||
}
|
||||
|
||||
StyleComputer::~StyleComputer() = default;
|
||||
|
|
@ -214,7 +213,6 @@ void StyleComputer::visit_edges(Visitor& visitor)
|
|||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_document);
|
||||
visitor.visit(m_loaded_fonts);
|
||||
visitor.visit(m_user_style_sheet);
|
||||
}
|
||||
|
||||
FontLoader::FontLoader(StyleComputer& style_computer, GC::Ptr<CSSStyleSheet> parent_style_sheet, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<URL> urls, Function<void(RefPtr<Gfx::Typeface const>)> on_load)
|
||||
|
|
@ -356,46 +354,6 @@ struct StyleComputer::MatchingFontCandidate {
|
|||
}
|
||||
};
|
||||
|
||||
static CSSStyleSheet& default_stylesheet()
|
||||
{
|
||||
static GC::Root<CSSStyleSheet> sheet;
|
||||
if (!sheet.cell()) {
|
||||
extern String default_stylesheet_source;
|
||||
sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), default_stylesheet_source));
|
||||
}
|
||||
return *sheet;
|
||||
}
|
||||
|
||||
static CSSStyleSheet& quirks_mode_stylesheet()
|
||||
{
|
||||
static GC::Root<CSSStyleSheet> sheet;
|
||||
if (!sheet.cell()) {
|
||||
extern String quirks_mode_stylesheet_source;
|
||||
sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), quirks_mode_stylesheet_source));
|
||||
}
|
||||
return *sheet;
|
||||
}
|
||||
|
||||
static CSSStyleSheet& mathml_stylesheet()
|
||||
{
|
||||
static GC::Root<CSSStyleSheet> sheet;
|
||||
if (!sheet.cell()) {
|
||||
extern String mathml_stylesheet_source;
|
||||
sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), mathml_stylesheet_source));
|
||||
}
|
||||
return *sheet;
|
||||
}
|
||||
|
||||
static CSSStyleSheet& svg_stylesheet()
|
||||
{
|
||||
static GC::Root<CSSStyleSheet> sheet;
|
||||
if (!sheet.cell()) {
|
||||
extern String svg_stylesheet_source;
|
||||
sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), svg_stylesheet_source));
|
||||
}
|
||||
return *sheet;
|
||||
}
|
||||
|
||||
Optional<String> StyleComputer::user_agent_style_sheet_source(StringView name)
|
||||
{
|
||||
extern String default_stylesheet_source;
|
||||
|
|
@ -414,46 +372,23 @@ Optional<String> StyleComputer::user_agent_style_sheet_source(StringView name)
|
|||
return {};
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void StyleComputer::for_each_stylesheet(CascadeOrigin cascade_origin, Callback callback) const
|
||||
{
|
||||
if (cascade_origin == CascadeOrigin::UserAgent) {
|
||||
callback(default_stylesheet(), {});
|
||||
if (document().in_quirks_mode())
|
||||
callback(quirks_mode_stylesheet(), {});
|
||||
callback(mathml_stylesheet(), {});
|
||||
callback(svg_stylesheet(), {});
|
||||
}
|
||||
if (cascade_origin == CascadeOrigin::User) {
|
||||
if (m_user_style_sheet)
|
||||
callback(*m_user_style_sheet, {});
|
||||
}
|
||||
if (cascade_origin == CascadeOrigin::Author) {
|
||||
document().for_each_active_css_style_sheet([&](auto& sheet, auto shadow_root) {
|
||||
callback(sheet, shadow_root);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RuleCache const* StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin cascade_origin, Optional<FlyString const> qualified_layer_name, GC::Ptr<DOM::ShadowRoot const> shadow_root) const
|
||||
{
|
||||
auto const* rule_caches_for_document_and_shadow_roots = [&]() -> RuleCachesForDocumentAndShadowRoots const* {
|
||||
auto& style_scope = shadow_root ? shadow_root->style_scope() : document().style_scope();
|
||||
style_scope.build_rule_cache_if_needed();
|
||||
|
||||
auto const* rule_caches_by_layer = [&]() -> RuleCaches const* {
|
||||
switch (cascade_origin) {
|
||||
case CascadeOrigin::Author:
|
||||
return m_author_rule_cache;
|
||||
return style_scope.m_author_rule_cache;
|
||||
case CascadeOrigin::User:
|
||||
return m_user_rule_cache;
|
||||
return style_scope.m_user_rule_cache;
|
||||
case CascadeOrigin::UserAgent:
|
||||
return m_user_agent_rule_cache;
|
||||
return style_scope.m_user_agent_rule_cache;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}();
|
||||
auto const* rule_caches_by_layer = [&]() -> RuleCaches const* {
|
||||
if (shadow_root)
|
||||
return rule_caches_for_document_and_shadow_roots->for_shadow_roots.get(*shadow_root).value_or(nullptr);
|
||||
return &rule_caches_for_document_and_shadow_roots->for_document;
|
||||
}();
|
||||
if (!rule_caches_by_layer)
|
||||
return nullptr;
|
||||
if (!qualified_layer_name.has_value())
|
||||
|
|
@ -469,17 +404,11 @@ RuleCache const* StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin casc
|
|||
return true;
|
||||
}
|
||||
|
||||
RuleCache const& StyleComputer::get_pseudo_class_rule_cache(PseudoClass pseudo_class) const
|
||||
InvalidationSet StyleComputer::invalidation_set_for_properties(Vector<InvalidationSet::Property> const& properties, StyleScope const& style_scope) const
|
||||
{
|
||||
build_rule_cache_if_needed();
|
||||
return *m_pseudo_class_rule_cache[to_underlying(pseudo_class)];
|
||||
}
|
||||
|
||||
InvalidationSet StyleComputer::invalidation_set_for_properties(Vector<InvalidationSet::Property> const& properties) const
|
||||
{
|
||||
if (!m_style_invalidation_data)
|
||||
if (!style_scope.m_style_invalidation_data)
|
||||
return {};
|
||||
auto const& descendant_invalidation_sets = m_style_invalidation_data->descendant_invalidation_sets;
|
||||
auto const& descendant_invalidation_sets = style_scope.m_style_invalidation_data->descendant_invalidation_sets;
|
||||
InvalidationSet result;
|
||||
for (auto const& property : properties) {
|
||||
if (auto it = descendant_invalidation_sets.find(property); it != descendant_invalidation_sets.end())
|
||||
|
|
@ -488,29 +417,29 @@ InvalidationSet StyleComputer::invalidation_set_for_properties(Vector<Invalidati
|
|||
return result;
|
||||
}
|
||||
|
||||
bool StyleComputer::invalidation_property_used_in_has_selector(InvalidationSet::Property const& property) const
|
||||
bool StyleComputer::invalidation_property_used_in_has_selector(InvalidationSet::Property const& property, StyleScope const& style_scope) const
|
||||
{
|
||||
if (!m_style_invalidation_data)
|
||||
if (!style_scope.m_style_invalidation_data)
|
||||
return true;
|
||||
switch (property.type) {
|
||||
case InvalidationSet::Property::Type::Id:
|
||||
if (m_style_invalidation_data->ids_used_in_has_selectors.contains(property.name()))
|
||||
if (style_scope.m_style_invalidation_data->ids_used_in_has_selectors.contains(property.name()))
|
||||
return true;
|
||||
break;
|
||||
case InvalidationSet::Property::Type::Class:
|
||||
if (m_style_invalidation_data->class_names_used_in_has_selectors.contains(property.name()))
|
||||
if (style_scope.m_style_invalidation_data->class_names_used_in_has_selectors.contains(property.name()))
|
||||
return true;
|
||||
break;
|
||||
case InvalidationSet::Property::Type::Attribute:
|
||||
if (m_style_invalidation_data->attribute_names_used_in_has_selectors.contains(property.name()))
|
||||
if (style_scope.m_style_invalidation_data->attribute_names_used_in_has_selectors.contains(property.name()))
|
||||
return true;
|
||||
break;
|
||||
case InvalidationSet::Property::Type::TagName:
|
||||
if (m_style_invalidation_data->tag_names_used_in_has_selectors.contains(property.name()))
|
||||
if (style_scope.m_style_invalidation_data->tag_names_used_in_has_selectors.contains(property.name()))
|
||||
return true;
|
||||
break;
|
||||
case InvalidationSet::Property::Type::PseudoClass:
|
||||
if (m_style_invalidation_data->pseudo_classes_used_in_has_selectors.contains(property.value.get<PseudoClass>()))
|
||||
if (style_scope.m_style_invalidation_data->pseudo_classes_used_in_has_selectors.contains(property.value.get<PseudoClass>()))
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
|
|
@ -1565,7 +1494,7 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
|
|||
}
|
||||
}
|
||||
|
||||
StyleComputer::MatchingRuleSet StyleComputer::build_matching_rule_set(DOM::AbstractElement abstract_element, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode) const
|
||||
StyleComputer::MatchingRuleSet StyleComputer::build_matching_rule_set(DOM::AbstractElement abstract_element, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode, StyleScope const& style_scope) const
|
||||
{
|
||||
// First, we collect all the CSS rules whose selectors match `element`:
|
||||
MatchingRuleSet matching_rule_set;
|
||||
|
|
@ -1575,7 +1504,7 @@ StyleComputer::MatchingRuleSet StyleComputer::build_matching_rule_set(DOM::Abstr
|
|||
sort_matching_rules(matching_rule_set.user_rules);
|
||||
|
||||
// @layer-ed author rules
|
||||
for (auto const& layer_name : m_qualified_layer_names_in_order) {
|
||||
for (auto const& layer_name : style_scope.m_qualified_layer_names_in_order) {
|
||||
auto layer_rules = collect_matching_rules(abstract_element, CascadeOrigin::Author, attempted_pseudo_class_matches, layer_name);
|
||||
sort_matching_rules(layer_rules);
|
||||
matching_rule_set.author_rules.append({ layer_name, layer_rules });
|
||||
|
|
@ -2377,17 +2306,19 @@ GC::Ref<ComputedProperties> StyleComputer::create_document_style() const
|
|||
|
||||
GC::Ref<ComputedProperties> StyleComputer::compute_style(DOM::AbstractElement abstract_element, Optional<bool&> did_change_custom_properties) const
|
||||
{
|
||||
return *compute_style_impl(abstract_element, ComputeStyleMode::Normal, did_change_custom_properties);
|
||||
auto& style_scope = abstract_element.style_scope();
|
||||
return *compute_style_impl(abstract_element, ComputeStyleMode::Normal, did_change_custom_properties, style_scope);
|
||||
}
|
||||
|
||||
GC::Ptr<ComputedProperties> StyleComputer::compute_pseudo_element_style_if_needed(DOM::AbstractElement abstract_element, Optional<bool&> did_change_custom_properties) const
|
||||
{
|
||||
return compute_style_impl(abstract_element, ComputeStyleMode::CreatePseudoElementStyleIfNeeded, did_change_custom_properties);
|
||||
auto& style_scope = abstract_element.style_scope();
|
||||
return compute_style_impl(abstract_element, ComputeStyleMode::CreatePseudoElementStyleIfNeeded, did_change_custom_properties, style_scope);
|
||||
}
|
||||
|
||||
GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::AbstractElement abstract_element, ComputeStyleMode mode, Optional<bool&> did_change_custom_properties) const
|
||||
GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::AbstractElement abstract_element, ComputeStyleMode mode, Optional<bool&> did_change_custom_properties, StyleScope const& style_scope) const
|
||||
{
|
||||
build_rule_cache_if_needed();
|
||||
style_scope.build_rule_cache_if_needed();
|
||||
|
||||
// Special path for elements that represent a pseudo-element in some element's internal shadow tree.
|
||||
if (abstract_element.element().use_pseudo_element().has_value()) {
|
||||
|
|
@ -2418,7 +2349,7 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::AbstractEleme
|
|||
// 1. Perform the cascade. This produces the "specified style"
|
||||
bool did_match_any_pseudo_element_rules = false;
|
||||
PseudoClassBitmap attempted_pseudo_class_matches;
|
||||
auto matching_rule_set = build_matching_rule_set(abstract_element, attempted_pseudo_class_matches, did_match_any_pseudo_element_rules, mode);
|
||||
auto matching_rule_set = build_matching_rule_set(abstract_element, attempted_pseudo_class_matches, did_match_any_pseudo_element_rules, mode, style_scope);
|
||||
|
||||
auto old_custom_properties = abstract_element.custom_properties();
|
||||
|
||||
|
|
@ -2665,13 +2596,6 @@ GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::AbstractEleme
|
|||
return computed_style;
|
||||
}
|
||||
|
||||
void StyleComputer::build_rule_cache_if_needed() const
|
||||
{
|
||||
if (has_valid_rule_cache())
|
||||
return;
|
||||
const_cast<StyleComputer&>(*this).build_rule_cache();
|
||||
}
|
||||
|
||||
struct SimplifiedSelectorForBucketing {
|
||||
CSS::Selector::SimpleSelector::Type type;
|
||||
FlyString name;
|
||||
|
|
@ -2708,272 +2632,6 @@ static Optional<SimplifiedSelectorForBucketing> is_roundabout_selector_bucketabl
|
|||
return {};
|
||||
}
|
||||
|
||||
void StyleComputer::collect_selector_insights(Selector const& selector, SelectorInsights& insights)
|
||||
{
|
||||
for (auto const& compound_selector : selector.compound_selectors()) {
|
||||
for (auto const& simple_selector : compound_selector.simple_selectors) {
|
||||
if (simple_selector.type == Selector::SimpleSelector::Type::PseudoClass) {
|
||||
if (simple_selector.pseudo_class().type == PseudoClass::Has) {
|
||||
insights.has_has_selectors = true;
|
||||
}
|
||||
for (auto const& argument_selector : simple_selector.pseudo_class().argument_selector_list) {
|
||||
collect_selector_insights(*argument_selector, insights);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin, SelectorInsights& insights)
|
||||
{
|
||||
Vector<MatchingRule> matching_rules;
|
||||
size_t style_sheet_index = 0;
|
||||
for_each_stylesheet(cascade_origin, [&](auto& sheet, GC::Ptr<DOM::ShadowRoot> shadow_root) {
|
||||
auto& rule_caches = [&] -> RuleCaches& {
|
||||
RuleCachesForDocumentAndShadowRoots* rule_caches_for_document_or_shadow_root = nullptr;
|
||||
switch (cascade_origin) {
|
||||
case CascadeOrigin::Author:
|
||||
rule_caches_for_document_or_shadow_root = m_author_rule_cache;
|
||||
break;
|
||||
case CascadeOrigin::User:
|
||||
rule_caches_for_document_or_shadow_root = m_user_rule_cache;
|
||||
break;
|
||||
case CascadeOrigin::UserAgent:
|
||||
rule_caches_for_document_or_shadow_root = m_user_agent_rule_cache;
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
if (!shadow_root)
|
||||
return rule_caches_for_document_or_shadow_root->for_document;
|
||||
return *rule_caches_for_document_or_shadow_root->for_shadow_roots.ensure(*shadow_root, [] { return make<RuleCaches>(); });
|
||||
}();
|
||||
|
||||
size_t rule_index = 0;
|
||||
sheet.for_each_effective_style_producing_rule([&](auto const& rule) {
|
||||
SelectorList const& absolutized_selectors = [&]() {
|
||||
if (rule.type() == CSSRule::Type::Style)
|
||||
return static_cast<CSSStyleRule const&>(rule).absolutized_selectors();
|
||||
if (rule.type() == CSSRule::Type::NestedDeclarations)
|
||||
return static_cast<CSSNestedDeclarations const&>(rule).parent_style_rule().absolutized_selectors();
|
||||
VERIFY_NOT_REACHED();
|
||||
}();
|
||||
|
||||
for (auto const& selector : absolutized_selectors) {
|
||||
m_style_invalidation_data->build_invalidation_sets_for_selector(selector);
|
||||
}
|
||||
|
||||
for (CSS::Selector const& selector : absolutized_selectors) {
|
||||
MatchingRule matching_rule {
|
||||
shadow_root,
|
||||
&rule,
|
||||
sheet,
|
||||
sheet.default_namespace(),
|
||||
selector,
|
||||
style_sheet_index,
|
||||
rule_index,
|
||||
selector.specificity(),
|
||||
cascade_origin,
|
||||
false,
|
||||
};
|
||||
|
||||
auto const& qualified_layer_name = matching_rule.qualified_layer_name();
|
||||
auto& rule_cache = qualified_layer_name.is_empty() ? rule_caches.main : *rule_caches.by_layer.ensure(qualified_layer_name, [] { return make<RuleCache>(); });
|
||||
|
||||
bool contains_root_pseudo_class = false;
|
||||
Optional<CSS::PseudoElement> pseudo_element;
|
||||
|
||||
collect_selector_insights(selector, insights);
|
||||
|
||||
for (auto const& simple_selector : selector.compound_selectors().last().simple_selectors) {
|
||||
if (!matching_rule.contains_pseudo_element) {
|
||||
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoElement) {
|
||||
matching_rule.contains_pseudo_element = true;
|
||||
pseudo_element = simple_selector.pseudo_element().type();
|
||||
matching_rule.slotted = pseudo_element == PseudoElement::Slotted;
|
||||
}
|
||||
}
|
||||
if (!contains_root_pseudo_class) {
|
||||
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass
|
||||
&& simple_selector.pseudo_class().type == CSS::PseudoClass::Root) {
|
||||
contains_root_pseudo_class = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < to_underlying(PseudoClass::__Count); ++i) {
|
||||
auto pseudo_class = static_cast<PseudoClass>(i);
|
||||
// If we're not building a rule cache for this pseudo class, just ignore it.
|
||||
if (!m_pseudo_class_rule_cache[i])
|
||||
continue;
|
||||
if (selector.contains_pseudo_class(pseudo_class)) {
|
||||
// For pseudo class rule caches we intentionally pass no pseudo-element, because we don't want to bucket pseudo class rules by pseudo-element type.
|
||||
m_pseudo_class_rule_cache[i]->add_rule(matching_rule, {}, contains_root_pseudo_class);
|
||||
}
|
||||
}
|
||||
|
||||
rule_cache.add_rule(matching_rule, pseudo_element, contains_root_pseudo_class);
|
||||
}
|
||||
++rule_index;
|
||||
});
|
||||
|
||||
// Loosely based on https://drafts.csswg.org/css-animations-2/#keyframe-processing
|
||||
sheet.for_each_effective_keyframes_at_rule([&](CSSKeyframesRule const& rule) {
|
||||
auto keyframe_set = adopt_ref(*new Animations::KeyframeEffect::KeyFrameSet);
|
||||
HashTable<PropertyID> animated_properties;
|
||||
|
||||
// Forwards pass, resolve all the user-specified keyframe properties.
|
||||
for (auto const& keyframe_rule : *rule.css_rules()) {
|
||||
auto const& keyframe = as<CSSKeyframeRule>(*keyframe_rule);
|
||||
Animations::KeyframeEffect::KeyFrameSet::ResolvedKeyFrame resolved_keyframe;
|
||||
|
||||
auto key = static_cast<u64>(keyframe.key().value() * Animations::KeyframeEffect::AnimationKeyFrameKeyScaleFactor);
|
||||
auto const& keyframe_style = *keyframe.style();
|
||||
for (auto const& it : keyframe_style.properties()) {
|
||||
if (!is_animatable_property(it.property_id))
|
||||
continue;
|
||||
|
||||
// Unresolved properties will be resolved in collect_animation_into()
|
||||
for_each_property_expanding_shorthands(it.property_id, it.value, [&](PropertyID shorthand_id, StyleValue const& shorthand_value) {
|
||||
animated_properties.set(shorthand_id);
|
||||
resolved_keyframe.properties.set(shorthand_id, NonnullRefPtr<StyleValue const> { shorthand_value });
|
||||
});
|
||||
}
|
||||
|
||||
keyframe_set->keyframes_by_key.insert(key, resolved_keyframe);
|
||||
}
|
||||
|
||||
Animations::KeyframeEffect::generate_initial_and_final_frames(keyframe_set, animated_properties);
|
||||
|
||||
if constexpr (LIBWEB_CSS_DEBUG) {
|
||||
dbgln("Resolved keyframe set '{}' into {} keyframes:", rule.name(), keyframe_set->keyframes_by_key.size());
|
||||
for (auto it = keyframe_set->keyframes_by_key.begin(); it != keyframe_set->keyframes_by_key.end(); ++it)
|
||||
dbgln(" - keyframe {}: {} properties", it.key(), it->properties.size());
|
||||
}
|
||||
|
||||
rule_caches.main.rules_by_animation_keyframes.set(rule.name(), move(keyframe_set));
|
||||
});
|
||||
++style_sheet_index;
|
||||
});
|
||||
}
|
||||
|
||||
struct LayerNode {
|
||||
OrderedHashMap<FlyString, LayerNode> children {};
|
||||
};
|
||||
|
||||
static void flatten_layer_names_tree(Vector<FlyString>& layer_names, StringView const& parent_qualified_name, FlyString const& name, LayerNode const& node)
|
||||
{
|
||||
FlyString qualified_name = parent_qualified_name.is_empty() ? name : MUST(String::formatted("{}.{}", parent_qualified_name, name));
|
||||
|
||||
for (auto const& item : node.children)
|
||||
flatten_layer_names_tree(layer_names, qualified_name, item.key, item.value);
|
||||
|
||||
layer_names.append(qualified_name);
|
||||
}
|
||||
|
||||
void StyleComputer::build_qualified_layer_names_cache()
|
||||
{
|
||||
LayerNode root;
|
||||
|
||||
auto insert_layer_name = [&](FlyString const& internal_qualified_name) {
|
||||
auto* node = &root;
|
||||
internal_qualified_name.bytes_as_string_view()
|
||||
.for_each_split_view('.', SplitBehavior::Nothing, [&](StringView part) {
|
||||
auto local_name = MUST(FlyString::from_utf8(part));
|
||||
node = &node->children.ensure(local_name);
|
||||
});
|
||||
};
|
||||
|
||||
// Walk all style sheets, identifying when we first see a @layer name, and add its qualified name to the list.
|
||||
// TODO: Separate the light and shadow-dom layers.
|
||||
for_each_stylesheet(CascadeOrigin::Author, [&](auto& sheet, GC::Ptr<DOM::ShadowRoot>) {
|
||||
// NOTE: Postorder so that a @layer block is iterated after its children,
|
||||
// because we want those children to occur before it in the list.
|
||||
sheet.for_each_effective_rule(TraversalOrder::Postorder, [&](auto& rule) {
|
||||
switch (rule.type()) {
|
||||
case CSSRule::Type::Import:
|
||||
// TODO: Handle `layer(foo)` in import rules once we implement that.
|
||||
break;
|
||||
case CSSRule::Type::LayerBlock: {
|
||||
auto& layer_block = static_cast<CSSLayerBlockRule const&>(rule);
|
||||
insert_layer_name(layer_block.internal_qualified_name({}));
|
||||
break;
|
||||
}
|
||||
case CSSRule::Type::LayerStatement: {
|
||||
auto& layer_statement = static_cast<CSSLayerStatementRule const&>(rule);
|
||||
auto qualified_names = layer_statement.internal_qualified_name_list({});
|
||||
for (auto& name : qualified_names)
|
||||
insert_layer_name(name);
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore everything else
|
||||
case CSSRule::Type::Style:
|
||||
case CSSRule::Type::Media:
|
||||
case CSSRule::Type::FontFace:
|
||||
case CSSRule::Type::Keyframes:
|
||||
case CSSRule::Type::Keyframe:
|
||||
case CSSRule::Type::Margin:
|
||||
case CSSRule::Type::Namespace:
|
||||
case CSSRule::Type::NestedDeclarations:
|
||||
case CSSRule::Type::Page:
|
||||
case CSSRule::Type::Property:
|
||||
case CSSRule::Type::Supports:
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Now, produce a flat list of qualified names to use later
|
||||
m_qualified_layer_names_in_order.clear();
|
||||
flatten_layer_names_tree(m_qualified_layer_names_in_order, ""sv, {}, root);
|
||||
}
|
||||
|
||||
void StyleComputer::build_rule_cache()
|
||||
{
|
||||
m_author_rule_cache = make<RuleCachesForDocumentAndShadowRoots>();
|
||||
m_user_rule_cache = make<RuleCachesForDocumentAndShadowRoots>();
|
||||
m_user_agent_rule_cache = make<RuleCachesForDocumentAndShadowRoots>();
|
||||
|
||||
m_selector_insights = make<SelectorInsights>();
|
||||
m_style_invalidation_data = make<StyleInvalidationData>();
|
||||
|
||||
if (auto user_style_source = document().page().user_style(); user_style_source.has_value()) {
|
||||
m_user_style_sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(document()), user_style_source.value()));
|
||||
}
|
||||
|
||||
build_qualified_layer_names_cache();
|
||||
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Hover)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Active)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Focus)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusWithin)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusVisible)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Target)] = make<RuleCache>();
|
||||
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::Author, *m_selector_insights);
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::User, *m_selector_insights);
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent, *m_selector_insights);
|
||||
}
|
||||
|
||||
void StyleComputer::invalidate_rule_cache()
|
||||
{
|
||||
m_author_rule_cache = nullptr;
|
||||
|
||||
// NOTE: We could be smarter about keeping the user rule cache, and style sheet.
|
||||
// Currently we are re-parsing the user style sheet every time we build the caches,
|
||||
// as it may have changed.
|
||||
m_user_rule_cache = nullptr;
|
||||
m_user_style_sheet = nullptr;
|
||||
|
||||
// NOTE: It might not be necessary to throw away the UA rule cache.
|
||||
// If we are sure that it's safe, we could keep it as an optimization.
|
||||
m_user_agent_rule_cache = nullptr;
|
||||
|
||||
m_pseudo_class_rule_cache = {};
|
||||
m_style_invalidation_data = nullptr;
|
||||
}
|
||||
|
||||
void StyleComputer::did_load_font(FlyString const&)
|
||||
{
|
||||
m_font_matching_algorithm_cache = {};
|
||||
|
|
@ -3782,21 +3440,6 @@ size_t StyleComputer::number_of_css_font_faces_with_loading_in_progress() const
|
|||
return count;
|
||||
}
|
||||
|
||||
bool StyleComputer::may_have_has_selectors() const
|
||||
{
|
||||
if (!has_valid_rule_cache())
|
||||
return true;
|
||||
|
||||
build_rule_cache_if_needed();
|
||||
return m_selector_insights->has_has_selectors;
|
||||
}
|
||||
|
||||
bool StyleComputer::have_has_selectors() const
|
||||
{
|
||||
build_rule_cache_if_needed();
|
||||
return m_selector_insights->has_has_selectors;
|
||||
}
|
||||
|
||||
void RuleCache::add_rule(MatchingRule const& matching_rule, Optional<PseudoElement> pseudo_element, bool contains_root_pseudo_class)
|
||||
{
|
||||
if (matching_rule.slotted) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <LibWeb/CSS/CascadedProperties.h>
|
||||
#include <LibWeb/CSS/Selector.h>
|
||||
#include <LibWeb/CSS/StyleInvalidationData.h>
|
||||
#include <LibWeb/CSS/StyleScope.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/Loader/ResourceLoader.h>
|
||||
|
|
@ -75,26 +76,6 @@ private:
|
|||
CounterType m_buckets[bucket_count];
|
||||
};
|
||||
|
||||
struct MatchingRule {
|
||||
GC::Ptr<DOM::ShadowRoot const> shadow_root;
|
||||
GC::Ptr<CSSRule const> rule; // Either CSSStyleRule or CSSNestedDeclarations
|
||||
GC::Ptr<CSSStyleSheet const> sheet;
|
||||
Optional<FlyString> default_namespace;
|
||||
Selector const& selector;
|
||||
size_t style_sheet_index { 0 };
|
||||
size_t rule_index { 0 };
|
||||
|
||||
u32 specificity { 0 };
|
||||
CascadeOrigin cascade_origin;
|
||||
bool contains_pseudo_element { false };
|
||||
bool slotted { false };
|
||||
|
||||
// Helpers to deal with the fact that `rule` might be a CSSStyleRule or a CSSNestedDeclarations
|
||||
CSSStyleProperties const& declaration() const;
|
||||
SelectorList const& absolutized_selectors() const;
|
||||
FlyString const& qualified_layer_name() const;
|
||||
};
|
||||
|
||||
struct FontFaceKey;
|
||||
|
||||
struct OwnFontFaceKey {
|
||||
|
|
@ -120,22 +101,6 @@ struct FontMatchingAlgorithmCacheKey {
|
|||
[[nodiscard]] bool operator==(FontMatchingAlgorithmCacheKey const& other) const = default;
|
||||
};
|
||||
|
||||
struct RuleCache {
|
||||
HashMap<FlyString, Vector<MatchingRule>> rules_by_id;
|
||||
HashMap<FlyString, Vector<MatchingRule>> rules_by_class;
|
||||
HashMap<FlyString, Vector<MatchingRule>> rules_by_tag_name;
|
||||
HashMap<FlyString, Vector<MatchingRule>, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name;
|
||||
Array<Vector<MatchingRule>, to_underlying(CSS::PseudoElement::KnownPseudoElementCount)> rules_by_pseudo_element;
|
||||
Vector<MatchingRule> root_rules;
|
||||
Vector<MatchingRule> slotted_rules;
|
||||
Vector<MatchingRule> other_rules;
|
||||
|
||||
HashMap<FlyString, NonnullRefPtr<Animations::KeyframeEffect::KeyFrameSet>> rules_by_animation_keyframes;
|
||||
|
||||
void add_rule(MatchingRule const&, Optional<PseudoElement>, bool contains_root_pseudo_class);
|
||||
void for_each_matching_rules(DOM::AbstractElement, Function<IterationDecision(Vector<MatchingRule> const&)> callback) const;
|
||||
};
|
||||
|
||||
class FontLoader;
|
||||
|
||||
class WEB_API StyleComputer final : public GC::Cell {
|
||||
|
|
@ -164,15 +129,10 @@ public:
|
|||
[[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::AbstractElement, Optional<bool&> did_change_custom_properties = {}) const;
|
||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::AbstractElement, Optional<bool&> did_change_custom_properties) const;
|
||||
|
||||
[[nodiscard]] RuleCache const& get_pseudo_class_rule_cache(PseudoClass) const;
|
||||
|
||||
[[nodiscard]] Vector<MatchingRule const*> collect_matching_rules(DOM::AbstractElement, CascadeOrigin, PseudoClassBitmap& attempted_pseudo_class_matches, Optional<FlyString const> qualified_layer_name = {}) const;
|
||||
|
||||
InvalidationSet invalidation_set_for_properties(Vector<InvalidationSet::Property> const&) const;
|
||||
bool invalidation_property_used_in_has_selector(InvalidationSet::Property const&) const;
|
||||
|
||||
[[nodiscard]] bool has_valid_rule_cache() const { return m_author_rule_cache; }
|
||||
void invalidate_rule_cache();
|
||||
InvalidationSet invalidation_set_for_properties(Vector<InvalidationSet::Property> const&, StyleScope const&) const;
|
||||
bool invalidation_property_used_in_has_selector(InvalidationSet::Property const&, StyleScope const&) const;
|
||||
|
||||
Gfx::Font const& initial_font() const;
|
||||
|
||||
|
|
@ -193,9 +153,6 @@ public:
|
|||
|
||||
void collect_animation_into(DOM::AbstractElement, GC::Ref<Animations::KeyframeEffect> animation, ComputedProperties&) const;
|
||||
|
||||
[[nodiscard]] bool may_have_has_selectors() const;
|
||||
[[nodiscard]] bool have_has_selectors() const;
|
||||
|
||||
size_t number_of_css_font_faces_with_loading_in_progress() const;
|
||||
|
||||
[[nodiscard]] GC::Ref<ComputedProperties> compute_properties(DOM::AbstractElement, CascadedProperties&) const;
|
||||
|
|
@ -242,10 +199,10 @@ private:
|
|||
Vector<LayerMatchingRules> author_rules;
|
||||
};
|
||||
|
||||
[[nodiscard]] MatchingRuleSet build_matching_rule_set(DOM::AbstractElement, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode) const;
|
||||
[[nodiscard]] MatchingRuleSet build_matching_rule_set(DOM::AbstractElement, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode, StyleScope const&) const;
|
||||
|
||||
LogicalAliasMappingContext compute_logical_alias_mapping_context(DOM::AbstractElement, ComputeStyleMode, MatchingRuleSet const&) const;
|
||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::AbstractElement, ComputeStyleMode, Optional<bool&> did_change_custom_properties) const;
|
||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::AbstractElement, ComputeStyleMode, Optional<bool&> did_change_custom_properties, StyleScope const&) const;
|
||||
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::AbstractElement, bool did_match_any_pseudo_element_rules, ComputeStyleMode, MatchingRuleSet const&, Optional<LogicalAliasMappingContext>, ReadonlySpan<PropertyID> properties_to_cascade) const;
|
||||
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive);
|
||||
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive);
|
||||
|
|
@ -257,16 +214,10 @@ private:
|
|||
void resolve_effective_overflow_values(ComputedProperties&) const;
|
||||
void transform_box_type_if_needed(ComputedProperties&, DOM::AbstractElement) const;
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_stylesheet(CascadeOrigin, Callback) const;
|
||||
|
||||
[[nodiscard]] CSSPixelRect viewport_rect() const { return m_viewport_rect; }
|
||||
|
||||
[[nodiscard]] Length::FontMetrics calculate_root_element_font_metrics(ComputedProperties const&) const;
|
||||
|
||||
Vector<FlyString> m_qualified_layer_names_in_order;
|
||||
void build_qualified_layer_names_cache();
|
||||
|
||||
void cascade_declarations(
|
||||
CascadedProperties&,
|
||||
DOM::AbstractElement,
|
||||
|
|
@ -277,39 +228,10 @@ private:
|
|||
Optional<LogicalAliasMappingContext>,
|
||||
ReadonlySpan<PropertyID> properties_to_cascade) const;
|
||||
|
||||
void build_rule_cache();
|
||||
void build_rule_cache_if_needed() const;
|
||||
|
||||
GC::Ref<DOM::Document> m_document;
|
||||
|
||||
struct SelectorInsights {
|
||||
bool has_has_selectors { false };
|
||||
};
|
||||
|
||||
struct RuleCaches {
|
||||
RuleCache main;
|
||||
HashMap<FlyString, NonnullOwnPtr<RuleCache>> by_layer;
|
||||
};
|
||||
|
||||
struct RuleCachesForDocumentAndShadowRoots {
|
||||
RuleCaches for_document;
|
||||
HashMap<GC::Ref<DOM::ShadowRoot const>, NonnullOwnPtr<RuleCaches>> for_shadow_roots;
|
||||
};
|
||||
|
||||
void make_rule_cache_for_cascade_origin(CascadeOrigin, SelectorInsights&);
|
||||
|
||||
[[nodiscard]] RuleCache const* rule_cache_for_cascade_origin(CascadeOrigin, Optional<FlyString const> qualified_layer_name, GC::Ptr<DOM::ShadowRoot const>) const;
|
||||
|
||||
static void collect_selector_insights(Selector const&, SelectorInsights&);
|
||||
|
||||
OwnPtr<SelectorInsights> m_selector_insights;
|
||||
Array<OwnPtr<RuleCache>, to_underlying(PseudoClass::__Count)> m_pseudo_class_rule_cache;
|
||||
OwnPtr<StyleInvalidationData> m_style_invalidation_data;
|
||||
OwnPtr<RuleCachesForDocumentAndShadowRoots> m_author_rule_cache;
|
||||
OwnPtr<RuleCachesForDocumentAndShadowRoots> m_user_rule_cache;
|
||||
OwnPtr<RuleCachesForDocumentAndShadowRoots> m_user_agent_rule_cache;
|
||||
GC::Ptr<CSSStyleSheet> m_user_style_sheet;
|
||||
|
||||
using FontLoaderList = Vector<GC::Ref<FontLoader>>;
|
||||
HashMap<OwnFontFaceKey, FontLoaderList> m_loaded_fonts;
|
||||
|
||||
|
|
|
|||
441
Libraries/LibWeb/CSS/StyleScope.cpp
Normal file
441
Libraries/LibWeb/CSS/StyleScope.cpp
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2022-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCore/ReportTime.h>
|
||||
#include <LibWeb/CSS/CSSKeyframesRule.h>
|
||||
#include <LibWeb/CSS/CSSLayerBlockRule.h>
|
||||
#include <LibWeb/CSS/CSSLayerStatementRule.h>
|
||||
#include <LibWeb/CSS/CSSNestedDeclarations.h>
|
||||
#include <LibWeb/CSS/CSSStyleRule.h>
|
||||
#include <LibWeb/CSS/CSSStyleSheet.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/CSS/PropertyID.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/CSS/StyleScope.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
void StyleScope::visit_edges(GC::Cell::Visitor& visitor)
|
||||
{
|
||||
visitor.visit(m_user_style_sheet);
|
||||
}
|
||||
|
||||
StyleScope::StyleScope(GC::Ref<DOM::Node> node)
|
||||
: m_node(node)
|
||||
{
|
||||
m_qualified_layer_names_in_order.append({});
|
||||
}
|
||||
|
||||
void StyleScope::build_rule_cache()
|
||||
{
|
||||
m_author_rule_cache = make<RuleCaches>();
|
||||
m_user_rule_cache = make<RuleCaches>();
|
||||
m_user_agent_rule_cache = make<RuleCaches>();
|
||||
|
||||
m_selector_insights = make<SelectorInsights>();
|
||||
m_style_invalidation_data = make<StyleInvalidationData>();
|
||||
|
||||
if (auto user_style_source = document().page().user_style(); user_style_source.has_value()) {
|
||||
m_user_style_sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(document()), user_style_source.value()));
|
||||
}
|
||||
|
||||
build_qualified_layer_names_cache();
|
||||
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Hover)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Active)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Focus)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusWithin)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusVisible)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Target)] = make<RuleCache>();
|
||||
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::Author, *m_selector_insights);
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::User, *m_selector_insights);
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent, *m_selector_insights);
|
||||
}
|
||||
|
||||
void StyleScope::invalidate_rule_cache()
|
||||
{
|
||||
m_author_rule_cache = nullptr;
|
||||
|
||||
// NOTE: We could be smarter about keeping the user rule cache, and style sheet.
|
||||
// Currently we are re-parsing the user style sheet every time we build the caches,
|
||||
// as it may have changed.
|
||||
m_user_rule_cache = nullptr;
|
||||
m_user_style_sheet = nullptr;
|
||||
|
||||
// NOTE: It might not be necessary to throw away the UA rule cache.
|
||||
// If we are sure that it's safe, we could keep it as an optimization.
|
||||
m_user_agent_rule_cache = nullptr;
|
||||
|
||||
m_pseudo_class_rule_cache = {};
|
||||
m_style_invalidation_data = nullptr;
|
||||
}
|
||||
|
||||
void StyleScope::build_rule_cache_if_needed() const
|
||||
{
|
||||
if (has_valid_rule_cache())
|
||||
return;
|
||||
const_cast<StyleScope&>(*this).build_rule_cache();
|
||||
}
|
||||
|
||||
static CSSStyleSheet& default_stylesheet()
|
||||
{
|
||||
static GC::Root<CSSStyleSheet> sheet;
|
||||
if (!sheet.cell()) {
|
||||
extern String default_stylesheet_source;
|
||||
sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), default_stylesheet_source));
|
||||
}
|
||||
return *sheet;
|
||||
}
|
||||
|
||||
static CSSStyleSheet& quirks_mode_stylesheet()
|
||||
{
|
||||
static GC::Root<CSSStyleSheet> sheet;
|
||||
if (!sheet.cell()) {
|
||||
extern String quirks_mode_stylesheet_source;
|
||||
sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), quirks_mode_stylesheet_source));
|
||||
}
|
||||
return *sheet;
|
||||
}
|
||||
|
||||
static CSSStyleSheet& mathml_stylesheet()
|
||||
{
|
||||
static GC::Root<CSSStyleSheet> sheet;
|
||||
if (!sheet.cell()) {
|
||||
extern String mathml_stylesheet_source;
|
||||
sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), mathml_stylesheet_source));
|
||||
}
|
||||
return *sheet;
|
||||
}
|
||||
|
||||
static CSSStyleSheet& svg_stylesheet()
|
||||
{
|
||||
static GC::Root<CSSStyleSheet> sheet;
|
||||
if (!sheet.cell()) {
|
||||
extern String svg_stylesheet_source;
|
||||
sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), svg_stylesheet_source));
|
||||
}
|
||||
return *sheet;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void StyleScope::for_each_stylesheet(CascadeOrigin cascade_origin, Callback callback) const
|
||||
{
|
||||
if (cascade_origin == CascadeOrigin::UserAgent) {
|
||||
callback(default_stylesheet());
|
||||
if (document().in_quirks_mode())
|
||||
callback(quirks_mode_stylesheet());
|
||||
callback(mathml_stylesheet());
|
||||
callback(svg_stylesheet());
|
||||
}
|
||||
if (cascade_origin == CascadeOrigin::User) {
|
||||
if (m_user_style_sheet)
|
||||
callback(*m_user_style_sheet);
|
||||
}
|
||||
if (cascade_origin == CascadeOrigin::Author) {
|
||||
for_each_active_css_style_sheet(move(callback));
|
||||
}
|
||||
}
|
||||
|
||||
void StyleScope::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin, SelectorInsights& insights)
|
||||
{
|
||||
GC::Ptr<DOM::ShadowRoot const> scope_shadow_root;
|
||||
if (m_node->is_shadow_root())
|
||||
scope_shadow_root = as<DOM::ShadowRoot>(*m_node);
|
||||
|
||||
Vector<MatchingRule> matching_rules;
|
||||
size_t style_sheet_index = 0;
|
||||
for_each_stylesheet(cascade_origin, [&](auto& sheet) {
|
||||
auto& rule_caches = [&] -> RuleCaches& {
|
||||
switch (cascade_origin) {
|
||||
case CascadeOrigin::Author:
|
||||
return *m_author_rule_cache;
|
||||
case CascadeOrigin::User:
|
||||
return *m_user_rule_cache;
|
||||
case CascadeOrigin::UserAgent:
|
||||
return *m_user_agent_rule_cache;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}();
|
||||
|
||||
size_t rule_index = 0;
|
||||
sheet.for_each_effective_style_producing_rule([&](auto const& rule) {
|
||||
SelectorList const& absolutized_selectors = [&]() {
|
||||
if (rule.type() == CSSRule::Type::Style)
|
||||
return static_cast<CSSStyleRule const&>(rule).absolutized_selectors();
|
||||
if (rule.type() == CSSRule::Type::NestedDeclarations)
|
||||
return static_cast<CSSNestedDeclarations const&>(rule).parent_style_rule().absolutized_selectors();
|
||||
VERIFY_NOT_REACHED();
|
||||
}();
|
||||
|
||||
for (auto const& selector : absolutized_selectors) {
|
||||
m_style_invalidation_data->build_invalidation_sets_for_selector(selector);
|
||||
}
|
||||
|
||||
for (CSS::Selector const& selector : absolutized_selectors) {
|
||||
MatchingRule matching_rule {
|
||||
scope_shadow_root,
|
||||
&rule,
|
||||
sheet,
|
||||
sheet.default_namespace(),
|
||||
selector,
|
||||
style_sheet_index,
|
||||
rule_index,
|
||||
selector.specificity(),
|
||||
cascade_origin,
|
||||
false,
|
||||
};
|
||||
|
||||
auto const& qualified_layer_name = matching_rule.qualified_layer_name();
|
||||
auto& rule_cache = qualified_layer_name.is_empty() ? rule_caches.main : *rule_caches.by_layer.ensure(qualified_layer_name, [] { return make<RuleCache>(); });
|
||||
|
||||
bool contains_root_pseudo_class = false;
|
||||
Optional<CSS::PseudoElement> pseudo_element;
|
||||
|
||||
collect_selector_insights(selector, insights);
|
||||
|
||||
for (auto const& simple_selector : selector.compound_selectors().last().simple_selectors) {
|
||||
if (!matching_rule.contains_pseudo_element) {
|
||||
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoElement) {
|
||||
matching_rule.contains_pseudo_element = true;
|
||||
pseudo_element = simple_selector.pseudo_element().type();
|
||||
matching_rule.slotted = pseudo_element == PseudoElement::Slotted;
|
||||
}
|
||||
}
|
||||
if (!contains_root_pseudo_class) {
|
||||
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass
|
||||
&& simple_selector.pseudo_class().type == CSS::PseudoClass::Root) {
|
||||
contains_root_pseudo_class = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < to_underlying(PseudoClass::__Count); ++i) {
|
||||
auto pseudo_class = static_cast<PseudoClass>(i);
|
||||
// If we're not building a rule cache for this pseudo class, just ignore it.
|
||||
if (!m_pseudo_class_rule_cache[i])
|
||||
continue;
|
||||
if (selector.contains_pseudo_class(pseudo_class)) {
|
||||
// For pseudo class rule caches we intentionally pass no pseudo-element, because we don't want to bucket pseudo class rules by pseudo-element type.
|
||||
m_pseudo_class_rule_cache[i]->add_rule(matching_rule, {}, contains_root_pseudo_class);
|
||||
}
|
||||
}
|
||||
|
||||
rule_cache.add_rule(matching_rule, pseudo_element, contains_root_pseudo_class);
|
||||
}
|
||||
++rule_index;
|
||||
});
|
||||
|
||||
// Loosely based on https://drafts.csswg.org/css-animations-2/#keyframe-processing
|
||||
sheet.for_each_effective_keyframes_at_rule([&](CSSKeyframesRule const& rule) {
|
||||
auto keyframe_set = adopt_ref(*new Animations::KeyframeEffect::KeyFrameSet);
|
||||
HashTable<PropertyID> animated_properties;
|
||||
|
||||
// Forwards pass, resolve all the user-specified keyframe properties.
|
||||
for (auto const& keyframe_rule : *rule.css_rules()) {
|
||||
auto const& keyframe = as<CSSKeyframeRule>(*keyframe_rule);
|
||||
Animations::KeyframeEffect::KeyFrameSet::ResolvedKeyFrame resolved_keyframe;
|
||||
|
||||
auto key = static_cast<u64>(keyframe.key().value() * Animations::KeyframeEffect::AnimationKeyFrameKeyScaleFactor);
|
||||
auto const& keyframe_style = *keyframe.style();
|
||||
for (auto const& it : keyframe_style.properties()) {
|
||||
if (!is_animatable_property(it.property_id))
|
||||
continue;
|
||||
|
||||
// Unresolved properties will be resolved in collect_animation_into()
|
||||
StyleComputer::for_each_property_expanding_shorthands(it.property_id, it.value, [&](PropertyID shorthand_id, StyleValue const& shorthand_value) {
|
||||
animated_properties.set(shorthand_id);
|
||||
resolved_keyframe.properties.set(shorthand_id, NonnullRefPtr<StyleValue const> { shorthand_value });
|
||||
});
|
||||
}
|
||||
|
||||
keyframe_set->keyframes_by_key.insert(key, resolved_keyframe);
|
||||
}
|
||||
|
||||
Animations::KeyframeEffect::generate_initial_and_final_frames(keyframe_set, animated_properties);
|
||||
|
||||
if constexpr (LIBWEB_CSS_DEBUG) {
|
||||
dbgln("Resolved keyframe set '{}' into {} keyframes:", rule.name(), keyframe_set->keyframes_by_key.size());
|
||||
for (auto it = keyframe_set->keyframes_by_key.begin(); it != keyframe_set->keyframes_by_key.end(); ++it)
|
||||
dbgln(" - keyframe {}: {} properties", it.key(), it->properties.size());
|
||||
}
|
||||
|
||||
rule_caches.main.rules_by_animation_keyframes.set(rule.name(), move(keyframe_set));
|
||||
});
|
||||
++style_sheet_index;
|
||||
});
|
||||
}
|
||||
|
||||
void StyleScope::collect_selector_insights(Selector const& selector, SelectorInsights& insights)
|
||||
{
|
||||
for (auto const& compound_selector : selector.compound_selectors()) {
|
||||
for (auto const& simple_selector : compound_selector.simple_selectors) {
|
||||
if (simple_selector.type == Selector::SimpleSelector::Type::PseudoClass) {
|
||||
if (simple_selector.pseudo_class().type == PseudoClass::Has) {
|
||||
insights.has_has_selectors = true;
|
||||
}
|
||||
for (auto const& argument_selector : simple_selector.pseudo_class().argument_selector_list) {
|
||||
collect_selector_insights(*argument_selector, insights);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LayerNode {
|
||||
OrderedHashMap<FlyString, LayerNode> children {};
|
||||
};
|
||||
|
||||
static void flatten_layer_names_tree(Vector<FlyString>& layer_names, StringView const& parent_qualified_name, FlyString const& name, LayerNode const& node)
|
||||
{
|
||||
FlyString qualified_name = parent_qualified_name.is_empty() ? name : MUST(String::formatted("{}.{}", parent_qualified_name, name));
|
||||
|
||||
for (auto const& item : node.children)
|
||||
flatten_layer_names_tree(layer_names, qualified_name, item.key, item.value);
|
||||
|
||||
layer_names.append(qualified_name);
|
||||
}
|
||||
|
||||
void StyleScope::build_qualified_layer_names_cache()
|
||||
{
|
||||
LayerNode root;
|
||||
|
||||
auto insert_layer_name = [&](FlyString const& internal_qualified_name) {
|
||||
auto* node = &root;
|
||||
internal_qualified_name.bytes_as_string_view()
|
||||
.for_each_split_view('.', SplitBehavior::Nothing, [&](StringView part) {
|
||||
auto local_name = MUST(FlyString::from_utf8(part));
|
||||
node = &node->children.ensure(local_name);
|
||||
});
|
||||
};
|
||||
|
||||
// Walk all style sheets, identifying when we first see a @layer name, and add its qualified name to the list.
|
||||
// TODO: Separate the light and shadow-dom layers.
|
||||
for_each_stylesheet(CascadeOrigin::Author, [&](auto& sheet) {
|
||||
// NOTE: Postorder so that a @layer block is iterated after its children,
|
||||
// because we want those children to occur before it in the list.
|
||||
sheet.for_each_effective_rule(TraversalOrder::Postorder, [&](auto& rule) {
|
||||
switch (rule.type()) {
|
||||
case CSSRule::Type::Import:
|
||||
// TODO: Handle `layer(foo)` in import rules once we implement that.
|
||||
break;
|
||||
case CSSRule::Type::LayerBlock: {
|
||||
auto& layer_block = static_cast<CSSLayerBlockRule const&>(rule);
|
||||
insert_layer_name(layer_block.internal_qualified_name({}));
|
||||
break;
|
||||
}
|
||||
case CSSRule::Type::LayerStatement: {
|
||||
auto& layer_statement = static_cast<CSSLayerStatementRule const&>(rule);
|
||||
auto qualified_names = layer_statement.internal_qualified_name_list({});
|
||||
for (auto& name : qualified_names)
|
||||
insert_layer_name(name);
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore everything else
|
||||
case CSSRule::Type::Style:
|
||||
case CSSRule::Type::Media:
|
||||
case CSSRule::Type::FontFace:
|
||||
case CSSRule::Type::Keyframes:
|
||||
case CSSRule::Type::Keyframe:
|
||||
case CSSRule::Type::Margin:
|
||||
case CSSRule::Type::Namespace:
|
||||
case CSSRule::Type::NestedDeclarations:
|
||||
case CSSRule::Type::Page:
|
||||
case CSSRule::Type::Property:
|
||||
case CSSRule::Type::Supports:
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Now, produce a flat list of qualified names to use later
|
||||
m_qualified_layer_names_in_order.clear();
|
||||
flatten_layer_names_tree(m_qualified_layer_names_in_order, ""sv, {}, root);
|
||||
}
|
||||
|
||||
bool StyleScope::may_have_has_selectors() const
|
||||
{
|
||||
if (!has_valid_rule_cache())
|
||||
return true;
|
||||
|
||||
build_rule_cache_if_needed();
|
||||
return m_selector_insights->has_has_selectors;
|
||||
}
|
||||
|
||||
bool StyleScope::have_has_selectors() const
|
||||
{
|
||||
build_rule_cache_if_needed();
|
||||
return m_selector_insights->has_has_selectors;
|
||||
}
|
||||
|
||||
DOM::Document& StyleScope::document() const
|
||||
{
|
||||
return m_node->document();
|
||||
}
|
||||
|
||||
RuleCache const& StyleScope::get_pseudo_class_rule_cache(PseudoClass pseudo_class) const
|
||||
{
|
||||
build_rule_cache_if_needed();
|
||||
return *m_pseudo_class_rule_cache[to_underlying(pseudo_class)];
|
||||
}
|
||||
|
||||
void StyleScope::for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&& callback) const
|
||||
{
|
||||
if (auto* shadow_root = as_if<DOM::ShadowRoot>(*m_node)) {
|
||||
shadow_root->for_each_active_css_style_sheet(move(callback));
|
||||
} else {
|
||||
m_node->document().for_each_active_css_style_sheet(move(callback));
|
||||
}
|
||||
}
|
||||
|
||||
void StyleScope::invalidate_style_of_elements_affected_by_has()
|
||||
{
|
||||
if (m_pending_nodes_for_style_invalidation_due_to_presence_of_has.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScopeGuard clear_pending_nodes_guard = [&] {
|
||||
m_pending_nodes_for_style_invalidation_due_to_presence_of_has.clear();
|
||||
};
|
||||
|
||||
// It's ok to call have_has_selectors() instead of may_have_has_selectors() here and force
|
||||
// rule cache build, because it's going to be built soon anyway, since we could get here
|
||||
// only from update_style().
|
||||
if (!have_has_selectors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto nodes = move(m_pending_nodes_for_style_invalidation_due_to_presence_of_has);
|
||||
for (auto const& node : nodes) {
|
||||
if (!node)
|
||||
continue;
|
||||
for (auto ancestor = node.ptr(); ancestor; ancestor = ancestor->parent_or_shadow_host()) {
|
||||
if (!ancestor->is_element())
|
||||
continue;
|
||||
auto& element = static_cast<DOM::Element&>(*ancestor);
|
||||
element.invalidate_style_if_affected_by_has();
|
||||
|
||||
auto* parent = ancestor->parent_or_shadow_host();
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
// If any ancestor's sibling was tested against selectors like ".a:has(+ .b)" or ".a:has(~ .b)"
|
||||
// its style might be affected by the change in descendant node.
|
||||
parent->for_each_child_of_type<DOM::Element>([&](auto& ancestor_sibling_element) {
|
||||
if (ancestor_sibling_element.affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator())
|
||||
ancestor_sibling_element.invalidate_style_if_affected_by_has();
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
121
Libraries/LibWeb/CSS/StyleScope.h
Normal file
121
Libraries/LibWeb/CSS/StyleScope.h
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/FlyString.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGC/Ptr.h>
|
||||
#include <LibWeb/Animations/KeyframeEffect.h>
|
||||
#include <LibWeb/CSS/CascadeOrigin.h>
|
||||
#include <LibWeb/CSS/Selector.h>
|
||||
#include <LibWeb/CSS/StyleInvalidationData.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
struct MatchingRule {
|
||||
GC::Ptr<DOM::ShadowRoot const> shadow_root;
|
||||
GC::Ptr<CSSRule const> rule; // Either CSSStyleRule or CSSNestedDeclarations
|
||||
GC::Ptr<CSSStyleSheet const> sheet;
|
||||
Optional<FlyString> default_namespace;
|
||||
Selector const& selector;
|
||||
size_t style_sheet_index { 0 };
|
||||
size_t rule_index { 0 };
|
||||
|
||||
u32 specificity { 0 };
|
||||
CascadeOrigin cascade_origin;
|
||||
bool contains_pseudo_element { false };
|
||||
bool slotted { false };
|
||||
|
||||
// Helpers to deal with the fact that `rule` might be a CSSStyleRule or a CSSNestedDeclarations
|
||||
CSSStyleProperties const& declaration() const;
|
||||
SelectorList const& absolutized_selectors() const;
|
||||
FlyString const& qualified_layer_name() const;
|
||||
};
|
||||
|
||||
struct RuleCache {
|
||||
HashMap<FlyString, Vector<MatchingRule>> rules_by_id;
|
||||
HashMap<FlyString, Vector<MatchingRule>> rules_by_class;
|
||||
HashMap<FlyString, Vector<MatchingRule>> rules_by_tag_name;
|
||||
HashMap<FlyString, Vector<MatchingRule>, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name;
|
||||
Array<Vector<MatchingRule>, to_underlying(CSS::PseudoElement::KnownPseudoElementCount)> rules_by_pseudo_element;
|
||||
Vector<MatchingRule> root_rules;
|
||||
Vector<MatchingRule> slotted_rules;
|
||||
Vector<MatchingRule> other_rules;
|
||||
|
||||
HashMap<FlyString, NonnullRefPtr<Animations::KeyframeEffect::KeyFrameSet>> rules_by_animation_keyframes;
|
||||
|
||||
void add_rule(MatchingRule const&, Optional<PseudoElement>, bool contains_root_pseudo_class);
|
||||
void for_each_matching_rules(DOM::AbstractElement, Function<IterationDecision(Vector<MatchingRule> const&)> callback) const;
|
||||
};
|
||||
|
||||
struct RuleCaches {
|
||||
RuleCache main;
|
||||
HashMap<FlyString, NonnullOwnPtr<RuleCache>> by_layer;
|
||||
};
|
||||
|
||||
struct SelectorInsights {
|
||||
bool has_has_selectors { false };
|
||||
};
|
||||
|
||||
class StyleScope {
|
||||
public:
|
||||
explicit StyleScope(GC::Ref<DOM::Node>);
|
||||
|
||||
DOM::Node& node() const { return m_node; }
|
||||
DOM::Document& document() const;
|
||||
|
||||
RuleCaches const& author_rule_cache() const { return *m_author_rule_cache; }
|
||||
RuleCaches const& user_rule_cache() const { return *m_user_rule_cache; }
|
||||
RuleCaches const& user_agent_rule_cache() const { return *m_user_agent_rule_cache; }
|
||||
|
||||
[[nodiscard]] bool has_valid_rule_cache() const { return m_author_rule_cache; }
|
||||
void invalidate_rule_cache();
|
||||
|
||||
[[nodiscard]] RuleCache const& get_pseudo_class_rule_cache(PseudoClass) const;
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_stylesheet(CascadeOrigin, Callback) const;
|
||||
|
||||
void make_rule_cache_for_cascade_origin(CascadeOrigin, SelectorInsights&);
|
||||
|
||||
void build_rule_cache();
|
||||
void build_rule_cache_if_needed() const;
|
||||
|
||||
static void collect_selector_insights(Selector const&, SelectorInsights&);
|
||||
|
||||
void build_qualified_layer_names_cache();
|
||||
|
||||
[[nodiscard]] bool may_have_has_selectors() const;
|
||||
[[nodiscard]] bool have_has_selectors() const;
|
||||
|
||||
void for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&& callback) const;
|
||||
|
||||
void invalidate_style_of_elements_affected_by_has();
|
||||
|
||||
void schedule_ancestors_style_invalidation_due_to_presence_of_has(DOM::Node& node) { m_pending_nodes_for_style_invalidation_due_to_presence_of_has.set(node); }
|
||||
|
||||
void visit_edges(GC::Cell::Visitor&);
|
||||
|
||||
Vector<FlyString> m_qualified_layer_names_in_order;
|
||||
OwnPtr<SelectorInsights> m_selector_insights;
|
||||
Array<OwnPtr<RuleCache>, to_underlying(PseudoClass::__Count)> m_pseudo_class_rule_cache;
|
||||
OwnPtr<StyleInvalidationData> m_style_invalidation_data;
|
||||
OwnPtr<RuleCaches> m_author_rule_cache;
|
||||
OwnPtr<RuleCaches> m_user_rule_cache;
|
||||
OwnPtr<RuleCaches> m_user_agent_rule_cache;
|
||||
|
||||
GC::Ptr<CSSStyleSheet> m_user_style_sheet;
|
||||
|
||||
HashTable<GC::Weak<DOM::Node>> m_pending_nodes_for_style_invalidation_due_to_presence_of_has;
|
||||
|
||||
GC::Ref<DOM::Node> m_node;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -116,8 +116,15 @@ void StyleSheetList::add_sheet(CSSStyleSheet& sheet)
|
|||
return;
|
||||
}
|
||||
|
||||
document().style_computer().invalidate_rule_cache();
|
||||
if (auto* shadow_root = as_if<DOM::ShadowRoot>(document_or_shadow_root())) {
|
||||
if (auto* host = shadow_root->host()) {
|
||||
host->invalidate_style(DOM::StyleInvalidationReason::StyleSheetListAddSheet);
|
||||
}
|
||||
shadow_root->style_scope().invalidate_rule_cache();
|
||||
} else {
|
||||
document_or_shadow_root().invalidate_style(DOM::StyleInvalidationReason::StyleSheetListAddSheet);
|
||||
document_or_shadow_root().document().style_scope().invalidate_rule_cache();
|
||||
}
|
||||
}
|
||||
|
||||
void StyleSheetList::remove_sheet(CSSStyleSheet& sheet)
|
||||
|
|
@ -131,8 +138,15 @@ void StyleSheetList::remove_sheet(CSSStyleSheet& sheet)
|
|||
return;
|
||||
}
|
||||
|
||||
m_document_or_shadow_root->document().style_computer().invalidate_rule_cache();
|
||||
if (auto* shadow_root = as_if<DOM::ShadowRoot>(document_or_shadow_root())) {
|
||||
if (auto* host = shadow_root->host()) {
|
||||
host->invalidate_style(DOM::StyleInvalidationReason::StyleSheetListRemoveSheet);
|
||||
}
|
||||
shadow_root->style_scope().invalidate_rule_cache();
|
||||
} else {
|
||||
document_or_shadow_root().invalidate_style(DOM::StyleInvalidationReason::StyleSheetListRemoveSheet);
|
||||
document_or_shadow_root().document().style_scope().invalidate_rule_cache();
|
||||
}
|
||||
}
|
||||
|
||||
GC::Ref<StyleSheetList> StyleSheetList::create(GC::Ref<DOM::Node> document_or_shadow_root)
|
||||
|
|
|
|||
|
|
@ -209,4 +209,12 @@ String AbstractElement::debug_description() const
|
|||
return m_element->debug_description();
|
||||
}
|
||||
|
||||
CSS::StyleScope const& AbstractElement::style_scope() const
|
||||
{
|
||||
auto& root = m_element->root();
|
||||
if (root.is_shadow_root())
|
||||
return as<DOM::ShadowRoot>(root).style_scope();
|
||||
return root.document().style_scope();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ public:
|
|||
String debug_description() const;
|
||||
bool operator==(AbstractElement const&) const = default;
|
||||
|
||||
CSS::StyleScope const& style_scope() const;
|
||||
|
||||
private:
|
||||
enum class WalkMethod : u8 {
|
||||
Previous,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ GC::Ref<WebIDL::ObservableArray> create_adopted_style_sheets_list(Node& document
|
|||
return WebIDL::NotAllowedError::create(document_or_shadow_root.realm(), "Sharing a StyleSheet between documents is not allowed."_utf16);
|
||||
|
||||
style_sheet.add_owning_document_or_shadow_root(document_or_shadow_root);
|
||||
document_or_shadow_root.document().style_computer().invalidate_rule_cache();
|
||||
auto& style_scope = document_or_shadow_root.is_shadow_root() ? as<DOM::ShadowRoot>(document_or_shadow_root).style_scope() : document_or_shadow_root.document().style_scope();
|
||||
style_scope.invalidate_rule_cache();
|
||||
document_or_shadow_root.invalidate_style(DOM::StyleInvalidationReason::AdoptedStyleSheetsList);
|
||||
return {};
|
||||
});
|
||||
|
|
@ -41,8 +42,9 @@ GC::Ref<WebIDL::ObservableArray> create_adopted_style_sheets_list(Node& document
|
|||
VERIFY(is<CSS::CSSStyleSheet>(object));
|
||||
auto& style_sheet = static_cast<CSS::CSSStyleSheet&>(object);
|
||||
|
||||
auto& style_scope = document_or_shadow_root.is_shadow_root() ? as<DOM::ShadowRoot>(document_or_shadow_root).style_scope() : document_or_shadow_root.document().style_scope();
|
||||
style_sheet.remove_owning_document_or_shadow_root(document_or_shadow_root);
|
||||
document_or_shadow_root.document().style_computer().invalidate_rule_cache();
|
||||
style_scope.invalidate_rule_cache();
|
||||
document_or_shadow_root.invalidate_style(DOM::StyleInvalidationReason::AdoptedStyleSheetsList);
|
||||
return {};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -469,6 +469,7 @@ Document::Document(JS::Realm& realm, URL::URL const& url, TemporaryDocumentForFr
|
|||
, m_editing_host_manager(EditingHostManager::create(realm, *this))
|
||||
, m_dynamic_view_transition_style_sheet(parse_css_stylesheet(CSS::Parser::ParsingParams(realm), ""sv, {}))
|
||||
, m_style_invalidator(realm.heap().allocate<StyleInvalidator>())
|
||||
, m_style_scope(*this)
|
||||
{
|
||||
m_legacy_platform_object_flags = PlatformObject::LegacyPlatformObjectFlags {
|
||||
.supports_named_properties = true,
|
||||
|
|
@ -544,6 +545,7 @@ WebIDL::ExceptionOr<void> Document::populate_with_html_head_and_body()
|
|||
void Document::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
m_style_scope.visit_edges(visitor);
|
||||
visitor.visit(m_page);
|
||||
visitor.visit(m_window);
|
||||
visitor.visit(m_layout_root);
|
||||
|
|
@ -1566,7 +1568,10 @@ void Document::update_style()
|
|||
// style change event. [CSS-Transitions-2]
|
||||
m_transition_generation++;
|
||||
|
||||
invalidate_style_of_elements_affected_by_has();
|
||||
style_scope().invalidate_style_of_elements_affected_by_has();
|
||||
for_each_shadow_root([&](auto& shadow_root) {
|
||||
shadow_root.style_scope().invalidate_style_of_elements_affected_by_has();
|
||||
});
|
||||
|
||||
if (!m_style_invalidator->has_pending_invalidations() && !needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
|
||||
return;
|
||||
|
|
@ -1786,54 +1791,13 @@ static Node* find_common_ancestor(Node* a, Node* b)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void Document::invalidate_style_of_elements_affected_by_has()
|
||||
{
|
||||
if (m_pending_nodes_for_style_invalidation_due_to_presence_of_has.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScopeGuard clear_pending_nodes_guard = [&] {
|
||||
m_pending_nodes_for_style_invalidation_due_to_presence_of_has.clear();
|
||||
};
|
||||
|
||||
// It's ok to call have_has_selectors() instead of may_have_has_selectors() here and force
|
||||
// rule cache build, because it's going to be build soon anyway, since we could get here
|
||||
// only from update_style().
|
||||
if (!style_computer().have_has_selectors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto nodes = move(m_pending_nodes_for_style_invalidation_due_to_presence_of_has);
|
||||
for (auto const& node : nodes) {
|
||||
if (!node)
|
||||
continue;
|
||||
for (auto ancestor = node.ptr(); ancestor; ancestor = ancestor->parent_or_shadow_host()) {
|
||||
if (!ancestor->is_element())
|
||||
continue;
|
||||
auto& element = static_cast<Element&>(*ancestor);
|
||||
element.invalidate_style_if_affected_by_has();
|
||||
|
||||
auto* parent = ancestor->parent_or_shadow_host();
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
// If any ancestor's sibling was tested against selectors like ".a:has(+ .b)" or ".a:has(~ .b)"
|
||||
// its style might be affected by the change in descendant node.
|
||||
parent->for_each_child_of_type<Element>([&](auto& ancestor_sibling_element) {
|
||||
if (ancestor_sibling_element.affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator())
|
||||
ancestor_sibling_element.invalidate_style_if_affected_by_has();
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Document::invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass pseudo_class, auto& element_slot, Node& old_new_common_ancestor, auto node)
|
||||
{
|
||||
auto const& rules = style_computer().get_pseudo_class_rule_cache(pseudo_class);
|
||||
|
||||
auto& root = old_new_common_ancestor.root();
|
||||
auto shadow_root = is<ShadowRoot>(root) ? static_cast<ShadowRoot const*>(&root) : nullptr;
|
||||
auto& style_scope = shadow_root ? shadow_root->style_scope() : this->style_scope();
|
||||
|
||||
auto const& rules = style_scope.get_pseudo_class_rule_cache(pseudo_class);
|
||||
|
||||
auto& style_computer = this->style_computer();
|
||||
auto does_rule_match_on_element = [&](Element const& element, CSS::MatchingRule const& rule) {
|
||||
|
|
@ -3486,13 +3450,25 @@ void Document::evaluate_media_queries_and_report_changes()
|
|||
void Document::evaluate_media_rules()
|
||||
{
|
||||
bool any_media_queries_changed_match_state = false;
|
||||
for_each_active_css_style_sheet([&](CSS::CSSStyleSheet& style_sheet, auto) {
|
||||
style_scope().for_each_active_css_style_sheet([&](CSS::CSSStyleSheet& style_sheet) {
|
||||
if (style_sheet.evaluate_media_queries(*this))
|
||||
any_media_queries_changed_match_state = true;
|
||||
});
|
||||
|
||||
for_each_shadow_root([&](auto& shadow_root) {
|
||||
shadow_root.style_scope().for_each_active_css_style_sheet([&](CSS::CSSStyleSheet& style_sheet) {
|
||||
if (style_sheet.evaluate_media_queries(*this))
|
||||
any_media_queries_changed_match_state = true;
|
||||
});
|
||||
});
|
||||
|
||||
if (any_media_queries_changed_match_state) {
|
||||
style_computer().invalidate_rule_cache();
|
||||
// FIXME: Make this more efficient
|
||||
style_scope().invalidate_rule_cache();
|
||||
for_each_shadow_root([&](auto& shadow_root) {
|
||||
shadow_root.style_scope().invalidate_rule_cache();
|
||||
});
|
||||
|
||||
invalidate_style(StyleInvalidationReason::MediaQueryChangedMatchState);
|
||||
}
|
||||
}
|
||||
|
|
@ -6005,32 +5981,25 @@ WebIDL::ExceptionOr<void> Document::set_adopted_style_sheets(JS::Value new_value
|
|||
return {};
|
||||
}
|
||||
|
||||
void Document::for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&, GC::Ptr<DOM::ShadowRoot>)>&& callback) const
|
||||
void Document::for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&& callback) const
|
||||
{
|
||||
if (m_style_sheets) {
|
||||
for (auto& style_sheet : m_style_sheets->sheets()) {
|
||||
if (!(style_sheet->is_alternate() && style_sheet->disabled()))
|
||||
callback(*style_sheet, {});
|
||||
callback(*style_sheet);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_adopted_style_sheets) {
|
||||
m_adopted_style_sheets->for_each<CSS::CSSStyleSheet>([&](auto& style_sheet) {
|
||||
if (!style_sheet.disabled())
|
||||
callback(style_sheet, {});
|
||||
callback(style_sheet);
|
||||
});
|
||||
}
|
||||
|
||||
if (m_dynamic_view_transition_style_sheet) {
|
||||
callback(*m_dynamic_view_transition_style_sheet, {});
|
||||
callback(*m_dynamic_view_transition_style_sheet);
|
||||
}
|
||||
|
||||
for_each_shadow_root([&](auto& shadow_root) {
|
||||
shadow_root.for_each_css_style_sheet([&](auto& style_sheet) {
|
||||
if (!style_sheet.disabled())
|
||||
callback(style_sheet, &shadow_root);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static Optional<CSS::CSSStyleSheet&> find_style_sheet_with_url(String const& url, CSS::CSSStyleSheet& style_sheet)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include <LibWeb/CSS/CSSPropertyRule.h>
|
||||
#include <LibWeb/CSS/CSSStyleSheet.h>
|
||||
#include <LibWeb/CSS/EnvironmentVariable.h>
|
||||
#include <LibWeb/CSS/StyleScope.h>
|
||||
#include <LibWeb/CSS/StyleSheetList.h>
|
||||
#include <LibWeb/Cookie/Cookie.h>
|
||||
#include <LibWeb/DOM/ParentNode.h>
|
||||
|
|
@ -262,7 +263,7 @@ public:
|
|||
CSS::StyleSheetList& style_sheets();
|
||||
CSS::StyleSheetList const& style_sheets() const;
|
||||
|
||||
void for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&, GC::Ptr<DOM::ShadowRoot>)>&& callback) const;
|
||||
void for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&& callback) const;
|
||||
|
||||
CSS::StyleSheetList* style_sheets_for_bindings() { return &style_sheets(); }
|
||||
|
||||
|
|
@ -930,11 +931,6 @@ public:
|
|||
void add_render_blocking_element(GC::Ref<Element>);
|
||||
void remove_render_blocking_element(GC::Ref<Element>);
|
||||
|
||||
void schedule_ancestors_style_invalidation_due_to_presence_of_has(Node& node)
|
||||
{
|
||||
m_pending_nodes_for_style_invalidation_due_to_presence_of_has.set(node);
|
||||
}
|
||||
|
||||
ElementByIdMap& element_by_id() const;
|
||||
|
||||
auto& script_blocking_style_sheet_set() { return m_script_blocking_style_sheet_set; }
|
||||
|
|
@ -951,6 +947,9 @@ public:
|
|||
|
||||
NonnullRefPtr<CSS::StyleValue const> custom_property_initial_value(FlyString const& name) const;
|
||||
|
||||
CSS::StyleScope const& style_scope() const { return m_style_scope; }
|
||||
CSS::StyleScope& style_scope() { return m_style_scope; }
|
||||
|
||||
protected:
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
|
@ -1341,12 +1340,12 @@ private:
|
|||
// https://drafts.csswg.org/css-view-transitions-1/#document-update-callback-queue
|
||||
Vector<GC::Ptr<ViewTransition::ViewTransition>> m_update_callback_queue = {};
|
||||
|
||||
HashTable<GC::Weak<Node>> m_pending_nodes_for_style_invalidation_due_to_presence_of_has;
|
||||
|
||||
GC::Ref<StyleInvalidator> m_style_invalidator;
|
||||
|
||||
// https://www.w3.org/TR/css-properties-values-api-1/#dom-window-registeredpropertyset-slot
|
||||
HashMap<FlyString, GC::Ref<Web::CSS::CSSPropertyRule>> m_registered_custom_properties;
|
||||
|
||||
CSS::StyleScope m_style_scope;
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
|||
|
|
@ -420,10 +420,12 @@ void Node::invalidate_style(StyleInvalidationReason reason)
|
|||
if (is_character_data())
|
||||
return;
|
||||
|
||||
if (document().style_computer().may_have_has_selectors()) {
|
||||
auto& style_scope = root().is_shadow_root() ? static_cast<ShadowRoot&>(root()).style_scope() : document().style_scope();
|
||||
|
||||
if (style_scope.may_have_has_selectors()) {
|
||||
if (reason == StyleInvalidationReason::NodeRemove) {
|
||||
if (auto* parent = parent_or_shadow_host(); parent) {
|
||||
document().schedule_ancestors_style_invalidation_due_to_presence_of_has(*parent);
|
||||
style_scope.schedule_ancestors_style_invalidation_due_to_presence_of_has(*parent);
|
||||
parent->for_each_child_of_type<Element>([&](auto& element) {
|
||||
if (element.affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator())
|
||||
element.invalidate_style_if_affected_by_has();
|
||||
|
|
@ -431,7 +433,7 @@ void Node::invalidate_style(StyleInvalidationReason reason)
|
|||
});
|
||||
}
|
||||
} else {
|
||||
document().schedule_ancestors_style_invalidation_due_to_presence_of_has(*this);
|
||||
style_scope.schedule_ancestors_style_invalidation_due_to_presence_of_has(*this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -499,15 +501,29 @@ void Node::invalidate_style(StyleInvalidationReason reason, Vector<CSS::Invalida
|
|||
if (is_character_data())
|
||||
return;
|
||||
|
||||
bool properties_used_in_has_selectors = false;
|
||||
for (auto const& property : properties) {
|
||||
properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property);
|
||||
}
|
||||
if (properties_used_in_has_selectors) {
|
||||
document().schedule_ancestors_style_invalidation_due_to_presence_of_has(*this);
|
||||
auto& root = this->root();
|
||||
auto& style_scope = root.is_shadow_root() ? static_cast<ShadowRoot&>(root).style_scope() : document().style_scope();
|
||||
CSS::StyleScope* shadow_style_scope = nullptr;
|
||||
if (auto* element = as_if<Element>(this); element && element->is_shadow_host()) {
|
||||
if (auto element_shadow_root = element->shadow_root())
|
||||
shadow_style_scope = &element_shadow_root->style_scope();
|
||||
}
|
||||
|
||||
auto invalidation_set = document().style_computer().invalidation_set_for_properties(properties);
|
||||
bool properties_used_in_has_selectors = false;
|
||||
for (auto const& property : properties) {
|
||||
properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property, style_scope);
|
||||
if (shadow_style_scope)
|
||||
properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property, *shadow_style_scope);
|
||||
}
|
||||
if (properties_used_in_has_selectors) {
|
||||
style_scope.schedule_ancestors_style_invalidation_due_to_presence_of_has(*this);
|
||||
if (shadow_style_scope)
|
||||
shadow_style_scope->schedule_ancestors_style_invalidation_due_to_presence_of_has(*this);
|
||||
}
|
||||
|
||||
auto invalidate_for_style_scope = [this, reason, &properties, &options](CSS::StyleScope& style_scope) {
|
||||
auto invalidation_set = document().style_computer().invalidation_set_for_properties(properties, style_scope);
|
||||
|
||||
if (invalidation_set.needs_invalidate_whole_subtree()) {
|
||||
invalidate_style(reason);
|
||||
return;
|
||||
|
|
@ -522,6 +538,11 @@ void Node::invalidate_style(StyleInvalidationReason reason, Vector<CSS::Invalida
|
|||
}
|
||||
|
||||
document().style_invalidator().add_pending_invalidation(*this, move(invalidation_set));
|
||||
};
|
||||
|
||||
invalidate_for_style_scope(style_scope);
|
||||
if (shadow_style_scope)
|
||||
invalidate_for_style_scope(*shadow_style_scope);
|
||||
}
|
||||
|
||||
Utf16String Node::child_text_content() const
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ GC_DEFINE_ALLOCATOR(ShadowRoot);
|
|||
ShadowRoot::ShadowRoot(Document& document, Element& host, Bindings::ShadowRootMode mode)
|
||||
: DocumentFragment(document)
|
||||
, m_mode(mode)
|
||||
, m_style_scope(*this)
|
||||
{
|
||||
document.register_shadow_root({}, *this);
|
||||
set_host(&host);
|
||||
|
|
@ -156,6 +157,7 @@ CSS::StyleSheetList const& ShadowRoot::style_sheets() const
|
|||
void ShadowRoot::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
m_style_scope.visit_edges(visitor);
|
||||
visitor.visit(m_style_sheets);
|
||||
visitor.visit(m_adopted_style_sheets);
|
||||
}
|
||||
|
|
@ -196,6 +198,21 @@ void ShadowRoot::for_each_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&&
|
|||
}
|
||||
}
|
||||
|
||||
void ShadowRoot::for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&& callback) const
|
||||
{
|
||||
for (auto& style_sheet : style_sheets().sheets()) {
|
||||
if (!style_sheet->disabled())
|
||||
callback(*style_sheet);
|
||||
}
|
||||
|
||||
if (m_adopted_style_sheets) {
|
||||
m_adopted_style_sheets->for_each<CSS::CSSStyleSheet>([&](auto& style_sheet) {
|
||||
if (!style_sheet.disabled())
|
||||
callback(style_sheet);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<Vector<GC::Ref<Animations::Animation>>> ShadowRoot::get_animations()
|
||||
{
|
||||
return calculate_get_animations(*this);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibWeb/Bindings/ShadowRootPrototype.h>
|
||||
#include <LibWeb/CSS/StyleScope.h>
|
||||
#include <LibWeb/DOM/DocumentFragment.h>
|
||||
#include <LibWeb/DOM/ElementByIdMap.h>
|
||||
#include <LibWeb/Export.h>
|
||||
|
|
@ -63,11 +64,15 @@ public:
|
|||
WebIDL::ExceptionOr<void> set_adopted_style_sheets(JS::Value);
|
||||
|
||||
void for_each_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&& callback) const;
|
||||
void for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&)>&& callback) const;
|
||||
|
||||
WebIDL::ExceptionOr<Vector<GC::Ref<Animations::Animation>>> get_animations();
|
||||
|
||||
ElementByIdMap& element_by_id() const;
|
||||
|
||||
CSS::StyleScope const& style_scope() const { return m_style_scope; }
|
||||
CSS::StyleScope& style_scope() { return m_style_scope; }
|
||||
|
||||
virtual void finalize() override;
|
||||
|
||||
protected:
|
||||
|
|
@ -103,6 +108,8 @@ private:
|
|||
|
||||
IntrusiveListNode<ShadowRoot> m_list_node;
|
||||
|
||||
CSS::StyleScope m_style_scope;
|
||||
|
||||
public:
|
||||
using DocumentShadowRootList = IntrusiveList<&ShadowRoot::m_list_node>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -370,6 +370,7 @@ class StringStyleValue;
|
|||
class StyleComputer;
|
||||
class StylePropertyMap;
|
||||
class StylePropertyMapReadOnly;
|
||||
class StyleScope;
|
||||
class StyleSheet;
|
||||
class StyleSheetList;
|
||||
class StyleValue;
|
||||
|
|
|
|||
|
|
@ -599,7 +599,11 @@ void Page::set_user_style(String source)
|
|||
{
|
||||
m_user_style_sheet_source = source;
|
||||
if (top_level_traversable_is_initialized() && top_level_traversable()->active_document()) {
|
||||
top_level_traversable()->active_document()->style_computer().invalidate_rule_cache();
|
||||
auto& document = *top_level_traversable()->active_document();
|
||||
document.style_scope().invalidate_rule_cache();
|
||||
document.for_each_shadow_root([](auto& shadow_root) {
|
||||
shadow_root.style_scope().invalidate_rule_cache();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue