Commit a11d3c19 authored by Anders Hartvoll Ruud's avatar Anders Hartvoll Ruud Committed by Commit Bot

Handle :visited/:link in RuleSet

Instead of having SelectorChecker::Match match the same selector
twice, this CL proposes a way to deal with visited links via
RuleSet.

Whenever we add a rule to the RuleSet which contains :link or :visited,
we effectively split the rule into two non-overlapping rules;
one which applies to the regular style (kMatchLink) and one which
applies to the visited style (kMatchVisited). The rules marked with
kMatchVisited go in a separate list of visited dependent rules on the
RuleSet. Then, when collecting matching rules, we try to match from
this list only when kInsideVisitedLink.

This way we can avoid paying any extra cost on every call to
SelectorChecker::Match.

Fixed: 1139464
Change-Id: I66f59acfbc0eb5e3225ab2b5be9ae572542bb8ce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2485062Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/master@{#818901}
parent 9b60dd1e
......@@ -629,6 +629,7 @@ blink_core_tests_css = [
"cssom/prepopulated_computed_style_property_map_test.cc",
"cssom/style_property_map_test.cc",
"drag_update_test.cc",
"element_rule_collector_test.cc",
"font_face_cache_test.cc",
"font_size_functions_test.cc",
"font_update_invalidation_test.cc",
......@@ -681,7 +682,6 @@ blink_core_tests_css = [
"resolver/style_resolver_test.cc",
"rule_feature_set_test.cc",
"rule_set_test.cc",
"selector_checker_test.cc",
"selector_query_test.cc",
"style_element_test.cc",
"style_engine_test.cc",
......
......@@ -177,11 +177,10 @@ void ElementRuleCollector::CollectMatchingRulesForList(
SelectorChecker::MatchResult result;
context.selector = &rule_data->Selector();
// If the selector does not contain :link or :visited, we disable
// :visited matching as a performance optimization.
context.is_inside_visited_link =
(inside_link_ == EInsideLink::kInsideVisitedLink) &&
rule_data->HasLinkOrVisited();
rule_data->LinkMatchType() == CSSSelector::kMatchVisited;
DCHECK(!context.is_inside_visited_link ||
(inside_link_ == EInsideLink::kInsideVisitedLink));
if (!checker.Match(context, result)) {
rejected++;
continue;
......@@ -253,6 +252,10 @@ void ElementRuleCollector::CollectMatchingRules(
if (element.IsLink())
CollectMatchingRulesForList(match_request.rule_set->LinkPseudoClassRules(),
cascade_order, match_request);
if (inside_link_ == EInsideLink::kInsideVisitedLink) {
CollectMatchingRulesForList(match_request.rule_set->VisitedDependentRules(),
cascade_order, match_request);
}
if (SelectorChecker::MatchesFocusPseudoClass(element))
CollectMatchingRulesForList(match_request.rule_set->FocusPseudoClassRules(),
cascade_order, match_request);
......@@ -349,7 +352,7 @@ void ElementRuleCollector::SortAndTransferMatchedRules() {
const RuleData* rule_data = matched_rule.GetRuleData();
result_.AddMatchedProperties(
&rule_data->Rule()->Properties(),
AdjustLinkMatchType(inside_link_, matched_rule.GetLinkMatchType()),
AdjustLinkMatchType(inside_link_, rule_data->LinkMatchType()),
rule_data->GetValidPropertyFilter(matching_ua_rules_));
}
}
......@@ -378,7 +381,7 @@ void ElementRuleCollector::DidMatchRule(
style_->SetHasPseudoElementStyle(dynamic_pseudo);
} else {
matched_rules_.push_back(MatchedRule(
rule_data, result.specificity, result.link_match_type, cascade_order,
rule_data, result.specificity, cascade_order,
match_request.style_sheet_index, match_request.style_sheet));
}
}
......
......@@ -54,13 +54,11 @@ class MatchedRule {
public:
MatchedRule(const RuleData* rule_data,
unsigned specificity,
unsigned link_match_type,
ShadowV0CascadeOrder cascade_order,
unsigned style_sheet_index,
const CSSStyleSheet* parent_style_sheet)
: rule_data_(rule_data),
specificity_(specificity),
link_match_type_(link_match_type),
parent_style_sheet_(parent_style_sheet) {
DCHECK(rule_data_);
static const unsigned kBitsForPositionInRuleData = 18;
......@@ -76,7 +74,6 @@ class MatchedRule {
unsigned Specificity() const {
return GetRuleData()->Specificity() + specificity_;
}
unsigned GetLinkMatchType() const { return link_match_type_; }
const CSSStyleSheet* ParentStyleSheet() const { return parent_style_sheet_; }
void Trace(Visitor* visitor) const {
visitor->Trace(parent_style_sheet_);
......@@ -86,7 +83,6 @@ class MatchedRule {
private:
Member<const RuleData> rule_data_;
unsigned specificity_;
unsigned link_match_type_;
uint64_t position_;
Member<const CSSStyleSheet> parent_style_sheet_;
};
......
......@@ -34,7 +34,7 @@ class ContainerNode;
// Encapsulates the context for matching against a single style sheet by
// ElementRuleCollector. Carries the RuleSet, scope (a ContainerNode) and
// CSSStyleSheet.
class MatchRequest {
class CORE_EXPORT MatchRequest {
STACK_ALLOCATED();
public:
......
......@@ -63,6 +63,16 @@ static inline ValidPropertyFilter DetermineValidPropertyFilter(
return ValidPropertyFilter::kNoFilter;
}
static unsigned DetermineLinkMatchType(const AddRuleFlags add_rule_flags,
const CSSSelector& selector) {
if (selector.HasLinkOrVisited()) {
return (add_rule_flags & kRuleIsVisitedDependent)
? CSSSelector::kMatchVisited
: CSSSelector::kMatchLink;
}
return CSSSelector::kMatchAll;
}
RuleData* RuleData::MaybeCreate(StyleRule* rule,
unsigned selector_index,
unsigned position,
......@@ -86,7 +96,7 @@ RuleData::RuleData(StyleRule* rule,
selector_index_(selector_index),
position_(position),
specificity_(Selector().Specificity()),
has_link_or_visited_(Selector().HasLinkOrVisited()),
link_match_type_(DetermineLinkMatchType(add_rule_flags, Selector())),
has_document_security_origin_(add_rule_flags &
kRuleHasDocumentSecurityOrigin),
valid_property_filter_(
......@@ -272,6 +282,19 @@ void RuleSet::AddRule(StyleRule* rule,
// rules.
universal_rules_.push_back(rule_data);
}
// If the rule has CSSSelector::kMatchLink, it means that there is a :visited
// or :link pseudo-class somewhere in the selector. In those cases, we
// effectively split the rule into two: one which covers the situation
// where we are in an unvisited link (kMatchLink), and another which covers
// the visited link case (kMatchVisited).
if (rule_data->LinkMatchType() == CSSSelector::kMatchLink) {
RuleData* visited_dependent = RuleData::MaybeCreate(
rule, rule_data->SelectorIndex(), rule_data->GetPosition(),
add_rule_flags | kRuleIsVisitedDependent);
DCHECK(visited_dependent);
visited_dependent_rules_.push_back(visited_dependent);
}
}
void RuleSet::AddPageRule(StyleRulePage* rule) {
......@@ -467,6 +490,7 @@ void RuleSet::Trace(Visitor* visitor) const {
visitor->Trace(scroll_timeline_rules_);
visitor->Trace(deep_combinator_or_shadow_pseudo_rules_);
visitor->Trace(part_pseudo_rules_);
visitor->Trace(visited_dependent_rules_);
visitor->Trace(content_pseudo_element_rules_);
visitor->Trace(slotted_pseudo_element_rules_);
visitor->Trace(pending_rules_);
......
......@@ -36,9 +36,12 @@
namespace blink {
enum AddRuleFlags {
using AddRuleFlags = unsigned;
enum AddRuleFlag {
kRuleHasNoSpecialState = 0,
kRuleHasDocumentSecurityOrigin = 1,
kRuleHasDocumentSecurityOrigin = 1 << 0,
kRuleIsVisitedDependent = 1 << 1,
};
// Some CSS properties do not apply to certain pseudo-elements, and need to be
......@@ -110,7 +113,7 @@ class CORE_EXPORT RuleData : public GarbageCollected<RuleData> {
return contains_uncommon_attribute_selector_;
}
unsigned Specificity() const { return specificity_; }
bool HasLinkOrVisited() const { return has_link_or_visited_; }
unsigned LinkMatchType() const { return link_match_type_; }
bool HasDocumentSecurityOrigin() const {
return has_document_security_origin_;
}
......@@ -146,10 +149,10 @@ class CORE_EXPORT RuleData : public GarbageCollected<RuleData> {
unsigned contains_uncommon_attribute_selector_ : 1;
// 32 bits above
unsigned specificity_ : 24;
unsigned has_link_or_visited_ : 1;
unsigned link_match_type_ : 2;
unsigned has_document_security_origin_ : 1;
unsigned valid_property_filter_ : 2;
// 28 bits above
// 29 bits above
// Use plain array instead of a Vector to minimize memory overhead.
unsigned descendant_selector_identifier_hashes_[kMaximumIdentifierCount];
};
......@@ -240,6 +243,10 @@ class CORE_EXPORT RuleSet final : public GarbageCollected<RuleSet> {
DCHECK(!pending_rules_);
return &part_pseudo_rules_;
}
const HeapVector<Member<const RuleData>>* VisitedDependentRules() const {
DCHECK(!pending_rules_);
return &visited_dependent_rules_;
}
const HeapVector<Member<StyleRulePage>>& PageRules() const {
DCHECK(!pending_rules_);
return page_rules_;
......@@ -351,6 +358,7 @@ class CORE_EXPORT RuleSet final : public GarbageCollected<RuleSet> {
HeapVector<Member<const RuleData>> universal_rules_;
HeapVector<Member<const RuleData>> shadow_host_rules_;
HeapVector<Member<const RuleData>> part_pseudo_rules_;
HeapVector<Member<const RuleData>> visited_dependent_rules_;
RuleFeatureSet features_;
HeapVector<Member<StyleRulePage>> page_rules_;
HeapVector<Member<StyleRuleFontFace>> font_face_rules_;
......
......@@ -238,8 +238,6 @@ bool SelectorChecker::Match(const SelectorCheckingContext& context,
if (context.selector->IsLastInTagHistory())
return false;
}
if (UNLIKELY(context.is_inside_visited_link))
return MatchForVisitedLink(context, result) == kSelectorMatches;
return MatchSelector(context, result) == kSelectorMatches;
}
......@@ -339,26 +337,6 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForPseudoShadow(
return MatchSelector(context, result);
}
SelectorChecker::MatchStatus SelectorChecker::MatchForVisitedLink(
const SelectorCheckingContext& context,
MatchResult& result) const {
DCHECK(context.is_inside_visited_link);
SelectorCheckingContext unvisited(context);
unvisited.is_inside_visited_link = false;
unsigned link_match_type = 0;
if (MatchSelector(unvisited, result) == kSelectorMatches)
link_match_type |= CSSSelector::kMatchLink;
if (MatchSelector(context, result) == kSelectorMatches)
link_match_type |= CSSSelector::kMatchVisited;
result.link_match_type = link_match_type;
return link_match_type ? kSelectorMatches : kSelectorFailsCompletely;
}
static inline Element* ParentOrV0ShadowHostElement(const Element& element) {
if (auto* shadow_root = DynamicTo<ShadowRoot>(element.parentNode())) {
if (shadow_root->GetType() != ShadowRootType::V0)
......
......@@ -128,7 +128,6 @@ class CORE_EXPORT SelectorChecker {
public:
PseudoId dynamic_pseudo{kPseudoIdNone};
unsigned specificity{0};
unsigned link_match_type{CSSSelector::kMatchAll};
};
bool Match(const SelectorCheckingContext& context, MatchResult& result) const;
......@@ -183,8 +182,6 @@ class CORE_EXPORT SelectorChecker {
MatchStatus MatchForPseudoShadow(const SelectorCheckingContext&,
const ContainerNode*,
MatchResult&) const;
MatchStatus MatchForVisitedLink(const SelectorCheckingContext&,
MatchResult&) const;
bool CheckPseudoClass(const SelectorCheckingContext&, MatchResult&) const;
bool CheckPseudoElement(const SelectorCheckingContext&, MatchResult&) const;
bool CheckScrollbarPseudoClass(const SelectorCheckingContext&,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment