Commit 822ee7c8 authored by Erik Luo's avatar Erik Luo Committed by Commit Bot

DevTools: get matching selectors directly from css engine

Inspector currently relies on Element::matches() to compute
matching selector indices. This does not support all
selectors, for example ":host".

This CL removes StaticCSSRuleList (only used by Inspector),
and gets selector indices from RuleData (computed by CSS
parsing engine) instead of forcing Inspector to compute them.

Old Explainer:
https://docs.google.com/document/d/1rBOWjHqNpkyvMj4pz-nCW-jxKgEqeRVQLsDwvR5b9Kc/edit?usp=sharing

Bug: 952440, 976062
Change-Id: I846fae5e80d30d9b3fee64d10cbbe01e1b80b751
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1703542
Commit-Queue: Erik Luo <luoe@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#682897}
parent 30ecc820
......@@ -159,7 +159,6 @@ blink_core_sources("css") {
"css_resolution_units.h",
"css_rule.cc",
"css_rule.h",
"css_rule_list.cc",
"css_rule_list.h",
"css_segmented_font_face.cc",
"css_segmented_font_face.h",
......
/**
* (C) 1999-2003 Lars Knoll (knoll@kde.org)
* (C) 2002-2003 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2002, 2005, 2006, 2012 Apple Computer, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "third_party/blink/renderer/core/css/css_rule_list.h"
#include "third_party/blink/renderer/core/css/css_rule.h"
namespace blink {
StaticCSSRuleList::StaticCSSRuleList() = default;
void StaticCSSRuleList::Trace(blink::Visitor* visitor) {
visitor->Trace(rules_);
CSSRuleList::Trace(visitor);
}
} // namespace blink
......@@ -33,6 +33,8 @@ namespace blink {
class CSSRule;
class CSSStyleSheet;
using RuleIndexList = HeapVector<std::pair<Member<CSSRule>, int>>;
class CSSRuleList : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
......@@ -49,25 +51,6 @@ class CSSRuleList : public ScriptWrappable {
DISALLOW_COPY_AND_ASSIGN(CSSRuleList);
};
class StaticCSSRuleList final : public CSSRuleList {
public:
StaticCSSRuleList();
HeapVector<Member<CSSRule>>& Rules() { return rules_; }
CSSStyleSheet* GetStyleSheet() const override { return nullptr; }
void Trace(blink::Visitor*) override;
private:
unsigned length() const override { return rules_.size(); }
CSSRule* item(unsigned index) const override {
return index < rules_.size() ? rules_[index].Get() : nullptr;
}
HeapVector<Member<CSSRule>> rules_;
};
template <class Rule>
class LiveCSSRuleList final : public CSSRuleList {
public:
......
......@@ -73,7 +73,7 @@ StyleRuleList* ElementRuleCollector::MatchedStyleRuleList() {
return style_rule_list_.Release();
}
CSSRuleList* ElementRuleCollector::MatchedCSSRuleList() {
RuleIndexList* ElementRuleCollector::MatchedCSSRuleList() {
DCHECK_EQ(mode_, SelectorChecker::kCollectingCSSRules);
return css_rule_list_.Release();
}
......@@ -88,9 +88,9 @@ inline StyleRuleList* ElementRuleCollector::EnsureStyleRuleList() {
return style_rule_list_;
}
inline StaticCSSRuleList* ElementRuleCollector::EnsureRuleList() {
inline RuleIndexList* ElementRuleCollector::EnsureRuleList() {
if (!css_rule_list_)
css_rule_list_ = MakeGarbageCollected<StaticCSSRuleList>();
css_rule_list_ = MakeGarbageCollected<RuleIndexList>();
return css_rule_list_.Get();
}
......@@ -287,18 +287,19 @@ CSSRule* ElementRuleCollector::FindStyleRule(CSSRuleCollection* css_rules,
void ElementRuleCollector::AppendCSSOMWrapperForRule(
CSSStyleSheet* parent_style_sheet,
StyleRule* rule) {
const RuleData* rule_data) {
// |parentStyleSheet| is 0 if and only if the |rule| is coming from User
// Agent. In this case, it is safe to create CSSOM wrappers without
// parentStyleSheets as they will be used only by inspector which will not try
// to edit them.
CSSRule* css_rule = nullptr;
StyleRule* rule = rule_data->Rule();
if (parent_style_sheet)
css_rule = FindStyleRule(parent_style_sheet, rule);
else
css_rule = rule->CreateCSSOMWrapper();
DCHECK(!parent_style_sheet || css_rule);
EnsureRuleList()->Rules().push_back(css_rule);
EnsureRuleList()->emplace_back(css_rule, rule_data->SelectorIndex());
}
void ElementRuleCollector::SortAndTransferMatchedRules() {
......@@ -314,10 +315,11 @@ void ElementRuleCollector::SortAndTransferMatchedRules() {
}
if (mode_ == SelectorChecker::kCollectingCSSRules) {
for (unsigned i = 0; i < matched_rules_.size(); ++i)
for (unsigned i = 0; i < matched_rules_.size(); ++i) {
AppendCSSOMWrapperForRule(
const_cast<CSSStyleSheet*>(matched_rules_[i].ParentStyleSheet()),
matched_rules_[i].GetRuleData()->Rule());
matched_rules_[i].GetRuleData());
}
return;
}
......
......@@ -25,6 +25,7 @@
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/core/css/css_rule_list.h"
#include "third_party/blink/renderer/core/css/pseudo_style_request.h"
#include "third_party/blink/renderer/core/css/resolver/element_resolve_context.h"
#include "third_party/blink/renderer/core/css/resolver/match_request.h"
......@@ -36,11 +37,9 @@
namespace blink {
class CSSStyleSheet;
class CSSRuleList;
class PartNames;
class RuleData;
class SelectorFilter;
class StaticCSSRuleList;
class StyleRuleUsageTracker;
// TODO(kochi): ShadowV0CascadeOrder is used only for Shadow DOM V0
......@@ -125,7 +124,7 @@ class ElementRuleCollector {
const MatchResult& MatchedResult() const;
StyleRuleList* MatchedStyleRuleList();
CSSRuleList* MatchedCSSRuleList();
RuleIndexList* MatchedCSSRuleList();
void CollectMatchingRules(const MatchRequest&,
ShadowV0CascadeOrder = kIgnoreCascadeOrder,
......@@ -170,11 +169,11 @@ class ElementRuleCollector {
template <class CSSRuleCollection>
CSSRule* FindStyleRule(CSSRuleCollection*, StyleRule*);
void AppendCSSOMWrapperForRule(CSSStyleSheet*, StyleRule*);
void AppendCSSOMWrapperForRule(CSSStyleSheet*, const RuleData*);
void SortMatchedRules();
StaticCSSRuleList* EnsureRuleList();
RuleIndexList* EnsureRuleList();
StyleRuleList* EnsureStyleRuleList();
private:
......@@ -193,7 +192,7 @@ class ElementRuleCollector {
HeapVector<MatchedRule, 32> matched_rules_;
// Output.
Member<StaticCSSRuleList> css_rule_list_;
Member<RuleIndexList> css_rule_list_;
Member<StyleRuleList> style_rule_list_;
MatchResult result_;
DISALLOW_COPY_AND_ASSIGN(ElementRuleCollector);
......
......@@ -1105,7 +1105,7 @@ StyleRuleList* StyleResolver::StyleRulesForElement(Element* element,
return collector.MatchedStyleRuleList();
}
CSSRuleList* StyleResolver::PseudoCSSRulesForElement(
RuleIndexList* StyleResolver::PseudoCSSRulesForElement(
Element* element,
PseudoId pseudo_id,
unsigned rules_to_include) {
......@@ -1123,8 +1123,8 @@ CSSRuleList* StyleResolver::PseudoCSSRulesForElement(
return collector.MatchedCSSRuleList();
}
CSSRuleList* StyleResolver::CssRulesForElement(Element* element,
unsigned rules_to_include) {
RuleIndexList* StyleResolver::CssRulesForElement(Element* element,
unsigned rules_to_include) {
return PseudoCSSRulesForElement(element, kPseudoIdNone, rules_to_include);
}
......
......@@ -43,7 +43,6 @@
namespace blink {
class CSSRuleList;
class CSSValue;
class CompositorKeyframeValue;
class Document;
......@@ -113,10 +112,10 @@ class CORE_EXPORT StyleResolver final
kUAAndUserCSSRules | kAuthorCSSRules | kCrossOriginCSSRules,
kAllCSSRules = kAllButEmptyCSSRules | kEmptyCSSRules,
};
CSSRuleList* CssRulesForElement(
RuleIndexList* CssRulesForElement(
Element*,
unsigned rules_to_include = kAllButEmptyCSSRules);
CSSRuleList* PseudoCSSRulesForElement(
RuleIndexList* PseudoCSSRulesForElement(
Element*,
PseudoId,
unsigned rules_to_include = kAllButEmptyCSSRules);
......
......@@ -133,11 +133,12 @@ String CreateShorthandValue(Document* document,
return style->getPropertyValue(shorthand);
}
HeapVector<Member<CSSStyleRule>> FilterDuplicateRules(CSSRuleList* rule_list) {
HeapVector<Member<CSSStyleRule>> FilterDuplicateRules(
RuleIndexList* rule_list) {
HeapVector<Member<CSSStyleRule>> uniq_rules;
HeapHashSet<Member<CSSRule>> uniq_rules_set;
for (unsigned i = rule_list ? rule_list->length() : 0; i > 0; --i) {
CSSRule* rule = rule_list->item(i - 1);
for (unsigned i = rule_list ? rule_list->size() : 0; i > 0; --i) {
CSSRule* rule = rule_list->at(i - 1).first;
auto* style_rule = DynamicTo<CSSStyleRule>(rule);
if (!style_rule || uniq_rules_set.Contains(rule))
continue;
......@@ -950,7 +951,7 @@ Response InspectorCSSAgent::getMatchedStylesForNode(
StyleResolver& style_resolver = owner_document->EnsureStyleResolver();
element->UpdateDistributionForUnknownReasons();
CSSRuleList* matched_rules = style_resolver.PseudoCSSRulesForElement(
RuleIndexList* matched_rules = style_resolver.PseudoCSSRulesForElement(
element, element_pseudo_id, StyleResolver::kAllCSSRules);
*matched_css_rules = BuildArrayForMatchedRuleList(
matched_rules, original_element, kPseudoIdNone);
......@@ -971,10 +972,10 @@ Response InspectorCSSAgent::getMatchedStylesForNode(
for (PseudoId pseudo_id = kFirstPublicPseudoId;
pseudo_id < kAfterLastInternalPseudoId;
pseudo_id = static_cast<PseudoId>(pseudo_id + 1)) {
CSSRuleList* matched_rules = style_resolver.PseudoCSSRulesForElement(
RuleIndexList* matched_rules = style_resolver.PseudoCSSRulesForElement(
element, pseudo_id, StyleResolver::kAllCSSRules);
protocol::DOM::PseudoType pseudo_type;
if (matched_rules && matched_rules->length() &&
if (matched_rules && matched_rules->size() &&
dom_agent_->GetPseudoElementType(pseudo_id, &pseudo_type)) {
pseudo_id_matches->fromJust()->emplace_back(
protocol::CSS::PseudoElementMatches::create()
......@@ -992,7 +993,7 @@ Response InspectorCSSAgent::getMatchedStylesForNode(
while (parent_element) {
StyleResolver& parent_style_resolver =
parent_element->ownerDocument()->EnsureStyleResolver();
CSSRuleList* parent_matched_rules =
RuleIndexList* parent_matched_rules =
parent_style_resolver.CssRulesForElement(parent_element,
StyleResolver::kAllCSSRules);
std::unique_ptr<protocol::CSS::InheritedStyleEntry> entry =
......@@ -1996,58 +1997,65 @@ std::unique_ptr<protocol::CSS::CSSRule> InspectorCSSAgent::BuildObjectForRule(
return result;
}
static inline bool MatchesPseudoElement(const CSSSelector* selector,
PseudoId element_pseudo_id) {
// According to http://www.w3.org/TR/css3-selectors/#pseudo-elements, "Only
// one pseudo-element may appear per selector."
// As such, check the last selector in the tag history.
for (; !selector->IsLastInTagHistory(); ++selector) {
}
PseudoId selector_pseudo_id =
CSSSelector::GetPseudoId(selector->GetPseudoType());
// FIXME: This only covers the case of matching pseudo-element selectors
// against PseudoElements. We should come up with a solution for matching
// pseudo-element selectors against ordinary Elements, too.
return selector_pseudo_id == element_pseudo_id;
}
std::unique_ptr<protocol::Array<protocol::CSS::RuleMatch>>
InspectorCSSAgent::BuildArrayForMatchedRuleList(
CSSRuleList* rule_list,
RuleIndexList* rule_list,
Element* element,
PseudoId matches_for_pseudo_id) {
auto result = std::make_unique<protocol::Array<protocol::CSS::RuleMatch>>();
if (!rule_list)
return result;
HeapVector<Member<CSSStyleRule>> uniq_rules = FilterDuplicateRules(rule_list);
for (unsigned i = 0; i < uniq_rules.size(); ++i) {
CSSStyleRule* rule = uniq_rules.at(i).Get();
// Dedupe matches coming from the same rule source.
HeapVector<Member<CSSStyleRule>> uniq_rules;
HeapHashSet<Member<CSSRule>> uniq_rules_set;
HeapHashMap<Member<CSSStyleRule>, std::unique_ptr<Vector<unsigned>>>
rule_indices;
for (auto it = rule_list->rbegin(); it != rule_list->rend(); ++it) {
CSSRule* rule = it->first;
auto* style_rule = DynamicTo<CSSStyleRule>(rule);
if (!style_rule)
continue;
if (!uniq_rules_set.Contains(rule)) {
uniq_rules_set.insert(rule);
uniq_rules.push_back(style_rule);
rule_indices.Set(style_rule, std::make_unique<Vector<unsigned>>());
}
rule_indices.at(style_rule)->push_back(it->second);
}
for (auto it = uniq_rules.rbegin(); it != uniq_rules.rend(); ++it) {
CSSStyleRule* rule = it->Get();
std::unique_ptr<protocol::CSS::CSSRule> rule_object =
BuildObjectForRule(rule);
if (!rule_object)
continue;
// Transform complex rule_indices into client-friendly, compound-basis for
// matching_selectors.
// e.g. ".foo + .bar, h1, body h1" for <h1>
// (complex): {.foo: 0, .bar: 1, h1: 2, body: 3, h1: 4}, matches: [2, 4]
// (compound): {.foo: 0, .bar: 0, h1: 1, body: 2, h1: 2}, matches: [1, 2]
auto matching_selectors = std::make_unique<protocol::Array<int>>();
const CSSSelectorList& selector_list = rule->GetStyleRule()->SelectorList();
wtf_size_t index = 0;
PseudoId element_pseudo_id =
matches_for_pseudo_id ? matches_for_pseudo_id : element->GetPseudoId();
for (const CSSSelector* selector = selector_list.First(); selector;
selector = CSSSelectorList::Next(*selector)) {
const CSSSelector* first_tag_history_selector = selector;
bool matched = false;
if (element_pseudo_id)
matched = MatchesPseudoElement(
selector, element_pseudo_id); // Modifies |selector|.
else
matched = element->matches(
AtomicString(first_tag_history_selector->SelectorText()),
IGNORE_EXCEPTION_FOR_TESTING);
if (matched)
matching_selectors->emplace_back(index);
++index;
if (rule->GetStyleRule() && rule->GetStyleRule()->SelectorList().First()) {
const CSSSelectorList& list = rule->GetStyleRule()->SelectorList();
// Compound index (0 -> 1 -> 2).
int compound = 0;
// Complex index of the next compound (0 -> 2 -> 3 -> kNotFound).
wtf_size_t next_compound_start = list.IndexOfNextSelectorAfter(0);
std::sort(rule_indices.at(rule)->begin(), rule_indices.at(rule)->end());
for (unsigned complex_match : (*rule_indices.at(rule))) {
while (complex_match >= next_compound_start &&
next_compound_start != kNotFound) {
next_compound_start =
list.IndexOfNextSelectorAfter(next_compound_start);
compound++;
}
matching_selectors->push_back(compound);
}
}
result->emplace_back(
protocol::CSS::RuleMatch::create()
.setRule(std::move(rule_object))
......
......@@ -29,6 +29,7 @@
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/css/css_rule_list.h"
#include "third_party/blink/renderer/core/css/css_selector.h"
#include "third_party/blink/renderer/core/execution_context/security_context.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
......@@ -45,7 +46,6 @@
namespace blink {
class CSSRule;
class CSSRuleList;
class CSSStyleRule;
class CSSStyleSheet;
class Document;
......@@ -288,7 +288,7 @@ class CORE_EXPORT InspectorCSSAgent final
std::unique_ptr<protocol::CSS::RuleUsage> BuildCoverageInfo(CSSStyleRule*,
bool);
std::unique_ptr<protocol::Array<protocol::CSS::RuleMatch>>
BuildArrayForMatchedRuleList(CSSRuleList*, Element*, PseudoId);
BuildArrayForMatchedRuleList(RuleIndexList*, Element*, PseudoId);
std::unique_ptr<protocol::CSS::CSSStyle> BuildObjectForAttributesStyle(
Element*);
......
Test that ::content pseudo selector is reported in matched rules.
Dumping matched rules:
:host ::content * { regular
*:host ::content ** { regular
font-weight: bold;
}
Dumping inherited rules:
......
Tests that rules in shadow host are reported in matched styles
Dumping matched rules:
:host { regular
*:host* { regular
color: red;
}
Dumping inherited rules:
......
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