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

[:is/:where] Add reftest framework for RuleFeatureSets

This CL makes it possible to verify that two selector lists produce
identical RuleFeatureSets. This will be very useful for testing the
feature collection behavior for :is/:where in future CLs.

This also adds code for compact stringification of RuleFeatureSets for
debugging purposes. This is helpful for failing reftests, since
it makes it possible to spot the differences.

Bug: 568705
Change-Id: I1d8e9a3d10bef74c9d56c7406b7812038cad86f2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2435345
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Reviewed-by: default avatarEric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812018}
parent 11e880d9
...@@ -1193,6 +1193,21 @@ bool CSSSelector::NeedsUpdatedDistribution() const { ...@@ -1193,6 +1193,21 @@ bool CSSSelector::NeedsUpdatedDistribution() const {
*this); *this);
} }
String CSSSelector::FormatPseudoTypeForDebugging(PseudoType type) {
for (const auto& s : kPseudoTypeWithoutArgumentsMap) {
if (s.type == type)
return s.string;
}
for (const auto& s : kPseudoTypeWithArgumentsMap) {
if (s.type == type)
return s.string;
}
StringBuilder builder;
builder.Append("pseudo-");
builder.AppendNumber(static_cast<int>(type));
return builder.ToString();
}
CSSSelector::RareData::RareData(const AtomicString& value) CSSSelector::RareData::RareData(const AtomicString& value)
: matching_value_(value), : matching_value_(value),
serializing_value_(value), serializing_value_(value),
......
...@@ -399,6 +399,8 @@ class CORE_EXPORT CSSSelector { ...@@ -399,6 +399,8 @@ class CORE_EXPORT CSSSelector {
bool FollowsPart() const; bool FollowsPart() const;
bool NeedsUpdatedDistribution() const; bool NeedsUpdatedDistribution() const;
static String FormatPseudoTypeForDebugging(PseudoType);
private: private:
unsigned relation_ : 4; // enum RelationType unsigned relation_ : 4; // enum RelationType
unsigned match_ : 4; // enum MatchType unsigned match_ : 4; // enum MatchType
......
...@@ -15,4 +15,13 @@ void InvalidationFlags::Merge(const InvalidationFlags& other) { ...@@ -15,4 +15,13 @@ void InvalidationFlags::Merge(const InvalidationFlags& other) {
invalidates_parts_ |= other.invalidates_parts_; invalidates_parts_ |= other.invalidates_parts_;
} }
bool InvalidationFlags::operator==(const InvalidationFlags& other) const {
return invalidate_custom_pseudo_ == other.invalidate_custom_pseudo_ &&
tree_boundary_crossing_ == other.tree_boundary_crossing_ &&
insertion_point_crossing_ == other.insertion_point_crossing_ &&
whole_subtree_invalid_ == other.whole_subtree_invalid_ &&
invalidates_slotted_ == other.invalidates_slotted_ &&
invalidates_parts_ == other.invalidates_parts_;
}
} // namespace blink } // namespace blink
...@@ -19,6 +19,9 @@ class InvalidationFlags { ...@@ -19,6 +19,9 @@ class InvalidationFlags {
invalidates_slotted_(false), invalidates_slotted_(false),
invalidates_parts_(false) {} invalidates_parts_(false) {}
bool operator==(const InvalidationFlags&) const;
bool operator!=(const InvalidationFlags& o) const { return !(*this == o); }
// Merges two sets of flags together by orring all fields. // Merges two sets of flags together by orring all fields.
void Merge(const InvalidationFlags& other); void Merge(const InvalidationFlags& other);
......
...@@ -36,11 +36,30 @@ ...@@ -36,11 +36,30 @@
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h" #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/style/data_equivalency.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink { namespace blink {
namespace {
template <InvalidationSet::BackingType type>
bool BackingEqual(const InvalidationSet::BackingFlags& a_flags,
const InvalidationSet::Backing<type>& a,
const InvalidationSet::BackingFlags& b_flags,
const InvalidationSet::Backing<type>& b) {
if (a.Size(a_flags) != b.Size(b_flags))
return false;
for (const AtomicString& value : a.Items(a_flags)) {
if (!b.Contains(b_flags, value))
return false;
}
return true;
}
} // namespace
static const unsigned char* g_tracing_enabled = nullptr; static const unsigned char* g_tracing_enabled = nullptr;
#define TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART_IF_ENABLED( \ #define TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART_IF_ENABLED( \
...@@ -54,6 +73,37 @@ void InvalidationSetDeleter::Destruct(const InvalidationSet* obj) { ...@@ -54,6 +73,37 @@ void InvalidationSetDeleter::Destruct(const InvalidationSet* obj) {
obj->Destroy(); obj->Destroy();
} }
bool InvalidationSet::operator==(const InvalidationSet& other) const {
if (GetType() != other.GetType())
return false;
if (GetType() == InvalidationType::kInvalidateSiblings) {
const auto& this_sibling = To<SiblingInvalidationSet>(*this);
const auto& other_sibling = To<SiblingInvalidationSet>(other);
if ((this_sibling.MaxDirectAdjacentSelectors() !=
other_sibling.MaxDirectAdjacentSelectors()) ||
!DataEquivalent(this_sibling.Descendants(),
other_sibling.Descendants()) ||
!DataEquivalent(this_sibling.SiblingDescendants(),
other_sibling.SiblingDescendants())) {
return false;
}
}
if (invalidation_flags_ != other.invalidation_flags_)
return false;
if (invalidates_self_ != other.invalidates_self_)
return false;
return BackingEqual(backing_flags_, classes_, other.backing_flags_,
other.classes_) &&
BackingEqual(backing_flags_, ids_, other.backing_flags_, other.ids_) &&
BackingEqual(backing_flags_, tag_names_, other.backing_flags_,
other.tag_names_) &&
BackingEqual(backing_flags_, attributes_, other.backing_flags_,
other.attributes_);
}
void InvalidationSet::CacheTracingFlag() { void InvalidationSet::CacheTracingFlag() {
g_tracing_enabled = TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( g_tracing_enabled = TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking")); TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking"));
...@@ -374,6 +424,80 @@ void InvalidationSet::Show() const { ...@@ -374,6 +424,80 @@ void InvalidationSet::Show() const {
} }
#endif // NDEBUG #endif // NDEBUG
String InvalidationSet::ToString() const {
auto format_backing = [](auto range, const char* prefix, const char* suffix) {
StringBuilder builder;
Vector<AtomicString> names;
for (const auto& str : range)
names.push_back(str);
std::sort(names.begin(), names.end(), WTF::CodeUnitCompareLessThan);
for (const auto& name : names) {
if (!builder.IsEmpty())
builder.Append(" ");
builder.Append(prefix);
builder.Append(name);
builder.Append(suffix);
}
return builder.ToString();
};
StringBuilder features;
if (HasIds())
features.Append(format_backing(Ids(), "#", ""));
if (HasClasses()) {
features.Append(!features.IsEmpty() ? " " : "");
features.Append(format_backing(Classes(), ".", ""));
}
if (HasTagNames()) {
features.Append(!features.IsEmpty() ? " " : "");
features.Append(format_backing(TagNames(), "", ""));
}
if (HasAttributes()) {
features.Append(!features.IsEmpty() ? " " : "");
features.Append(format_backing(Attributes(), "[", "]"));
}
auto format_max_direct_adjancent = [](const InvalidationSet* set) -> String {
const auto* sibling = DynamicTo<SiblingInvalidationSet>(set);
if (!sibling)
return g_empty_atom;
unsigned max = sibling->MaxDirectAdjacentSelectors();
if (max == SiblingInvalidationSet::kDirectAdjacentMax)
return "~";
if (max != 1)
return String::Number(max);
return g_empty_atom;
};
StringBuilder metadata;
metadata.Append(InvalidatesSelf() ? "$" : "");
metadata.Append(invalidation_flags_.WholeSubtreeInvalid() ? "W" : "");
metadata.Append(invalidation_flags_.InvalidateCustomPseudo() ? "C" : "");
metadata.Append(invalidation_flags_.TreeBoundaryCrossing() ? "T" : "");
metadata.Append(invalidation_flags_.InsertionPointCrossing() ? "I" : "");
metadata.Append(invalidation_flags_.InvalidatesSlotted() ? "S" : "");
metadata.Append(invalidation_flags_.InvalidatesParts() ? "P" : "");
metadata.Append(format_max_direct_adjancent(this));
StringBuilder main;
main.Append("{");
if (!features.IsEmpty()) {
main.Append(" ");
main.Append(features);
}
if (!metadata.IsEmpty()) {
main.Append(" ");
main.Append(metadata);
}
main.Append(" }");
return main.ToString();
}
SiblingInvalidationSet::SiblingInvalidationSet( SiblingInvalidationSet::SiblingInvalidationSet(
scoped_refptr<DescendantInvalidationSet> descendants) scoped_refptr<DescendantInvalidationSet> descendants)
: InvalidationSet(InvalidationType::kInvalidateSiblings), : InvalidationSet(InvalidationType::kInvalidateSiblings),
...@@ -396,4 +520,8 @@ DescendantInvalidationSet& SiblingInvalidationSet::EnsureDescendants() { ...@@ -396,4 +520,8 @@ DescendantInvalidationSet& SiblingInvalidationSet::EnsureDescendants() {
return *descendant_invalidation_set_; return *descendant_invalidation_set_;
} }
std::ostream& operator<<(std::ostream& ostream, const InvalidationSet& set) {
return ostream << set.ToString().Utf8();
}
} // namespace blink } // namespace blink
...@@ -101,6 +101,9 @@ class CORE_EXPORT InvalidationSet ...@@ -101,6 +101,9 @@ class CORE_EXPORT InvalidationSet
InvalidationSet(const InvalidationSet&) = delete; InvalidationSet(const InvalidationSet&) = delete;
InvalidationSet& operator=(const InvalidationSet&) = delete; InvalidationSet& operator=(const InvalidationSet&) = delete;
bool operator==(const InvalidationSet&) const;
bool operator!=(const InvalidationSet& o) const { return !(*this == o); }
InvalidationType GetType() const { InvalidationType GetType() const {
return static_cast<InvalidationType>(type_); return static_cast<InvalidationType>(type_);
} }
...@@ -185,6 +188,35 @@ class CORE_EXPORT InvalidationSet ...@@ -185,6 +188,35 @@ class CORE_EXPORT InvalidationSet
void ToTracedValue(TracedValue*) const; void ToTracedValue(TracedValue*) const;
// Format the InvalidationSet for debugging purposes.
//
// Examples:
//
// { .a } - Invalidates class |a|.
// { #a } - Invalidates id |a|.
// { .a #a } - Invalidates class |a| and id |a|.
// { div } - Invalidates tag name |div|.
// { :hover } - Invalidates pseudo-class :hover.
// { .a [name] } - Invalidates class |a| and attribute |name|.
// { $ } - Invalidates self.
// { .a $ } - Invalidates class |a| and self.
// { .b 4 } - Invalidates class |b|. Max direct siblings = 4.
// { .a .b $4 } - Combination of the two previous examples.
// { W } - Whole subtree invalid.
//
// Flags (omitted if false):
//
// $ - Invalidates self.
// W - Whole subtree invalid.
// C - Invalidates custom pseudo.
// T - Tree boundary crossing.
// I - Insertion point crossing.
// S - Invalidates slotted.
// P - Invalidates parts.
// ~ - Max direct siblings is kDirectAdjacentMax.
// <integer> - Max direct siblings is specified number (omitted if 1).
String ToString() const;
#ifndef NDEBUG #ifndef NDEBUG
void Show() const; void Show() const;
#endif #endif
...@@ -253,6 +285,7 @@ class CORE_EXPORT InvalidationSet ...@@ -253,6 +285,7 @@ class CORE_EXPORT InvalidationSet
void Clear(Flags&); void Clear(Flags&);
bool Contains(const Flags&, const AtomicString&) const; bool Contains(const Flags&, const AtomicString&) const;
bool IsEmpty(const Flags&) const; bool IsEmpty(const Flags&) const;
size_t Size(const Flags&) const;
bool IsHashSet(const Flags& flags) const { return flags.bits_ & GetMask(); } bool IsHashSet(const Flags& flags) const { return flags.bits_ & GetMask(); }
StringImpl* GetStringImpl(const Flags& flags) const { StringImpl* GetStringImpl(const Flags& flags) const {
...@@ -548,6 +581,16 @@ bool InvalidationSet::Backing<type>::IsEmpty( ...@@ -548,6 +581,16 @@ bool InvalidationSet::Backing<type>::IsEmpty(
return !IsHashSet(flags) && !string_impl_; return !IsHashSet(flags) && !string_impl_;
} }
template <typename InvalidationSet::BackingType type>
size_t InvalidationSet::Backing<type>::Size(
const InvalidationSet::BackingFlags& flags) const {
if (const HashSet<AtomicString>* set = GetHashSet(flags))
return set->size();
if (const StringImpl* impl = GetStringImpl(flags))
return 1;
return 0;
}
template <> template <>
struct DowncastTraits<DescendantInvalidationSet> { struct DowncastTraits<DescendantInvalidationSet> {
static bool AllowFrom(const InvalidationSet& value) { static bool AllowFrom(const InvalidationSet& value) {
...@@ -569,6 +612,8 @@ struct DowncastTraits<NthSiblingInvalidationSet> { ...@@ -569,6 +612,8 @@ struct DowncastTraits<NthSiblingInvalidationSet> {
} }
}; };
CORE_EXPORT std::ostream& operator<<(std::ostream&, const InvalidationSet&);
} // namespace blink } // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_INVALIDATION_INVALIDATION_SET_H_ #endif // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_INVALIDATION_INVALIDATION_SET_H_
...@@ -28,13 +28,20 @@ ...@@ -28,13 +28,20 @@
namespace blink { namespace blink {
class MediaQueryResult { class CORE_EXPORT MediaQueryResult {
DISALLOW_NEW(); DISALLOW_NEW();
public: public:
MediaQueryResult(const MediaQueryExp& expr, bool result) MediaQueryResult(const MediaQueryExp& expr, bool result)
: expression_(expr), result_(result) {} : expression_(expr), result_(result) {}
bool operator==(const MediaQueryResult& other) const {
return expression_ == other.expression_ && result_ == other.result_;
}
bool operator!=(const MediaQueryResult& other) const {
return !(*this == other);
}
const MediaQueryExp& Expression() const { return expression_; } const MediaQueryExp& Expression() const { return expression_; }
bool Result() const { return result_; } bool Result() const { return result_; }
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "third_party/blink/renderer/core/css/rule_feature_set.h" #include "third_party/blink/renderer/core/css/rule_feature_set.h"
#include <algorithm>
#include <bitset> #include <bitset>
#include "third_party/blink/renderer/core/css/css_custom_ident_value.h" #include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
#include "third_party/blink/renderer/core/css/css_function_value.h" #include "third_party/blink/renderer/core/css/css_function_value.h"
...@@ -43,7 +44,9 @@ ...@@ -43,7 +44,9 @@
#include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/node.h" #include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/style/data_equivalency.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink { namespace blink {
...@@ -250,6 +253,21 @@ scoped_refptr<InvalidationSet> CopyInvalidationSet( ...@@ -250,6 +253,21 @@ scoped_refptr<InvalidationSet> CopyInvalidationSet(
return copy; return copy;
} }
template <typename KeyType,
typename MapType = HashMap<KeyType, scoped_refptr<InvalidationSet>>>
bool InvalidationSetMapsEqual(const MapType& a, const MapType& b) {
if (a.size() != b.size())
return false;
for (const auto& entry : a) {
auto it = b.find(entry.key);
if (it == b.end())
return false;
if (!DataEquivalent(entry.value, it->value))
return false;
}
return true;
}
} // anonymous namespace } // anonymous namespace
InvalidationSet& RuleFeatureSet::EnsureMutableInvalidationSet( InvalidationSet& RuleFeatureSet::EnsureMutableInvalidationSet(
...@@ -384,6 +402,31 @@ RuleFeatureSet::~RuleFeatureSet() { ...@@ -384,6 +402,31 @@ RuleFeatureSet::~RuleFeatureSet() {
is_alive_ = false; is_alive_ = false;
} }
bool RuleFeatureSet::operator==(const RuleFeatureSet& other) const {
return metadata_ == other.metadata_ &&
InvalidationSetMapsEqual<AtomicString>(
class_invalidation_sets_, other.class_invalidation_sets_) &&
InvalidationSetMapsEqual<AtomicString>(id_invalidation_sets_,
other.id_invalidation_sets_) &&
InvalidationSetMapsEqual<AtomicString>(
attribute_invalidation_sets_,
other.attribute_invalidation_sets_) &&
InvalidationSetMapsEqual<CSSSelector::PseudoType>(
pseudo_invalidation_sets_, other.pseudo_invalidation_sets_) &&
DataEquivalent(universal_sibling_invalidation_set_,
other.universal_sibling_invalidation_set_) &&
DataEquivalent(nth_invalidation_set_, other.nth_invalidation_set_) &&
DataEquivalent(universal_sibling_invalidation_set_,
other.universal_sibling_invalidation_set_) &&
DataEquivalent(type_rule_invalidation_set_,
other.type_rule_invalidation_set_) &&
viewport_dependent_media_query_results_ ==
other.viewport_dependent_media_query_results_ &&
device_dependent_media_query_results_ ==
other.device_dependent_media_query_results_ &&
is_alive_ == other.is_alive_;
}
ALWAYS_INLINE InvalidationSet& RuleFeatureSet::EnsureClassInvalidationSet( ALWAYS_INLINE InvalidationSet& RuleFeatureSet::EnsureClassInvalidationSet(
const AtomicString& class_name, const AtomicString& class_name,
InvalidationType type, InvalidationType type,
...@@ -1014,6 +1057,16 @@ void RuleFeatureSet::FeatureMetadata::Clear() { ...@@ -1014,6 +1057,16 @@ void RuleFeatureSet::FeatureMetadata::Clear() {
invalidates_parts = false; invalidates_parts = false;
} }
bool RuleFeatureSet::FeatureMetadata::operator==(
const FeatureMetadata& other) const {
return uses_first_line_rules == other.uses_first_line_rules &&
uses_window_inactive_selector == other.uses_window_inactive_selector &&
needs_full_recalc_for_rule_set_invalidation ==
other.needs_full_recalc_for_rule_set_invalidation &&
max_direct_adjacent_selectors == other.max_direct_adjacent_selectors &&
invalidates_parts == other.invalidates_parts;
}
void RuleFeatureSet::Add(const RuleFeatureSet& other) { void RuleFeatureSet::Add(const RuleFeatureSet& other) {
CHECK(is_alive_); CHECK(is_alive_);
CHECK(other.is_alive_); CHECK(other.is_alive_);
...@@ -1327,4 +1380,127 @@ bool RuleFeatureSet::InvalidationSetFeatures::HasIdClassOrAttribute() const { ...@@ -1327,4 +1380,127 @@ bool RuleFeatureSet::InvalidationSetFeatures::HasIdClassOrAttribute() const {
return !classes.IsEmpty() || !attributes.IsEmpty() || !ids.IsEmpty(); return !classes.IsEmpty() || !attributes.IsEmpty() || !ids.IsEmpty();
} }
String RuleFeatureSet::ToString() const {
StringBuilder builder;
enum TypeFlags {
kId = 1 << 0,
kClass = 1 << 1,
kAttribute = 1 << 2,
kPseudo = 1 << 3,
kDescendant = 1 << 4,
kSibling = 1 << 5,
kType = 1 << 6,
kUniversal = 1 << 7,
kNth = 1 << 8,
};
struct Entry {
String name;
const InvalidationSet* set;
unsigned flags;
};
Vector<Entry> entries;
auto add_invalidation_sets =
[&entries](const String& base, InvalidationSet* set, unsigned flags,
const char* prefix = "", const char* suffix = "") {
if (!set)
return;
DescendantInvalidationSet* descendants;
SiblingInvalidationSet* siblings;
ExtractInvalidationSets(set, descendants, siblings);
if (descendants)
entries.push_back(Entry{base, descendants, flags | kDescendant});
if (siblings)
entries.push_back(Entry{base, siblings, flags | kSibling});
if (siblings && siblings->SiblingDescendants()) {
entries.push_back(Entry{base, siblings->SiblingDescendants(),
flags | kSibling | kDescendant});
}
};
auto format_name = [](const String& base, unsigned flags) {
StringBuilder builder;
// Prefix:
builder.Append((flags & kId) ? "#" : "");
builder.Append((flags & kClass) ? "." : "");
builder.Append((flags & kAttribute) ? "[" : "");
builder.Append(base);
// Suffix:
builder.Append((flags & kAttribute) ? "]" : "");
builder.Append("[");
if (flags & kSibling)
builder.Append("+");
if (flags & kDescendant)
builder.Append(">");
builder.Append("]");
return builder.ToString();
};
auto format_max_direct_adjancent = [](unsigned max) -> String {
if (max == SiblingInvalidationSet::kDirectAdjacentMax)
return "~";
if (max)
return String::Number(max);
return g_empty_atom;
};
for (auto& i : id_invalidation_sets_)
add_invalidation_sets(i.key, i.value.get(), kId, "#");
for (auto& i : class_invalidation_sets_)
add_invalidation_sets(i.key, i.value.get(), kClass, ".");
for (auto& i : attribute_invalidation_sets_)
add_invalidation_sets(i.key, i.value.get(), kAttribute, "[", "]");
for (auto& i : pseudo_invalidation_sets_) {
String name = CSSSelector::FormatPseudoTypeForDebugging(
static_cast<CSSSelector::PseudoType>(i.key));
add_invalidation_sets(name, i.value.get(), kPseudo, ":", "");
}
add_invalidation_sets("type", type_rule_invalidation_set_.get(), kType);
add_invalidation_sets("*", universal_sibling_invalidation_set_.get(),
kUniversal);
add_invalidation_sets("nth", nth_invalidation_set_.get(), kNth);
std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) {
if (a.flags != b.flags)
return a.flags < b.flags;
return WTF::CodeUnitCompareLessThan(a.name, b.name);
});
for (const Entry& entry : entries) {
builder.Append(format_name(entry.name, entry.flags));
builder.Append(entry.set->ToString());
builder.Append(" ");
}
StringBuilder metadata;
metadata.Append(metadata_.uses_first_line_rules ? "F" : "");
metadata.Append(metadata_.uses_window_inactive_selector ? "W" : "");
metadata.Append(metadata_.needs_full_recalc_for_rule_set_invalidation ? "R"
: "");
metadata.Append(metadata_.invalidates_parts ? "P" : "");
metadata.Append(
format_max_direct_adjancent(metadata_.max_direct_adjacent_selectors));
if (!metadata.IsEmpty()) {
builder.Append("META:");
builder.Append(metadata.ToString());
}
return builder.ToString();
}
std::ostream& operator<<(std::ostream& ostream, const RuleFeatureSet& set) {
return ostream << set.ToString().Utf8();
}
} // namespace blink } // namespace blink
...@@ -53,6 +53,9 @@ class CORE_EXPORT RuleFeatureSet { ...@@ -53,6 +53,9 @@ class CORE_EXPORT RuleFeatureSet {
RuleFeatureSet& operator=(const RuleFeatureSet&) = delete; RuleFeatureSet& operator=(const RuleFeatureSet&) = delete;
~RuleFeatureSet(); ~RuleFeatureSet();
bool operator==(const RuleFeatureSet&) const;
bool operator!=(const RuleFeatureSet& o) const { return !(*this == o); }
// Methods for updating the data in this object. // Methods for updating the data in this object.
void Add(const RuleFeatureSet&); void Add(const RuleFeatureSet&);
void Clear(); void Clear();
...@@ -146,6 +149,34 @@ class CORE_EXPORT RuleFeatureSet { ...@@ -146,6 +149,34 @@ class CORE_EXPORT RuleFeatureSet {
bool IsAlive() const { return is_alive_; } bool IsAlive() const { return is_alive_; }
// Format the RuleFeatureSet for debugging purposes.
//
// [>] Means descendant invalidation set.
// [+] Means sibling invalidation set.
// [>+] Means sibling descendant invalidation set.
//
// Examples:
//
// .a[>] { ... } - Descendant invalidation set class |a|.
// #a[+] { ... } - Sibling invalidation set for id |a|
// [name][>] { ... } - Descendant invalidation set for attribute |name|.
// :hover[>] { ... } - Descendant set for pseudo-class |hover|.
// *[+] { ... } - Universal sibling invalidation set.
// nth[+>] { ... } - Nth sibling descendant invalidation set.
// type[>] { ... } - Type rule invalidation set.
//
// META flags (omitted if false):
//
// F - Uses first line rules.
// W - Uses window inactive selector.
// R - Needs full recalc for ruleset invalidation.
// P - Invalidates parts.
// ~ - Max direct siblings is kDirectAdjacentMax.
// <integer> - Max direct siblings is specified number (omitted if 0).
//
// See InvalidationSet::ToString for more information.
String ToString() const;
protected: protected:
enum PositionType { kSubject, kAncestor }; enum PositionType { kSubject, kAncestor };
InvalidationSet* InvalidationSetForSimpleSelector(const CSSSelector&, InvalidationSet* InvalidationSetForSimpleSelector(const CSSSelector&,
...@@ -169,6 +200,8 @@ class CORE_EXPORT RuleFeatureSet { ...@@ -169,6 +200,8 @@ class CORE_EXPORT RuleFeatureSet {
DISALLOW_NEW(); DISALLOW_NEW();
void Add(const FeatureMetadata& other); void Add(const FeatureMetadata& other);
void Clear(); void Clear();
bool operator==(const FeatureMetadata&) const;
bool operator!=(const FeatureMetadata& o) const { return !(*this == o); }
bool uses_first_line_rules = false; bool uses_first_line_rules = false;
bool uses_window_inactive_selector = false; bool uses_window_inactive_selector = false;
...@@ -350,6 +383,8 @@ class CORE_EXPORT RuleFeatureSet { ...@@ -350,6 +383,8 @@ class CORE_EXPORT RuleFeatureSet {
friend class RuleFeatureSetTest; friend class RuleFeatureSetTest;
}; };
CORE_EXPORT std::ostream& operator<<(std::ostream&, const RuleFeatureSet&);
} // namespace blink } // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RULE_FEATURE_SET_H_ #endif // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RULE_FEATURE_SET_H_
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "third_party/blink/renderer/core/css/css_selector_list.h" #include "third_party/blink/renderer/core/css/css_selector_list.h"
#include "third_party/blink/renderer/core/css/invalidation/invalidation_set.h" #include "third_party/blink/renderer/core/css/invalidation/invalidation_set.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h" #include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/parser/media_query_parser.h"
#include "third_party/blink/renderer/core/css/rule_set.h" #include "third_party/blink/renderer/core/css/rule_set.h"
#include "third_party/blink/renderer/core/css/style_rule.h" #include "third_party/blink/renderer/core/css/style_rule.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h" #include "third_party/blink/renderer/core/dom/element_traversal.h"
...@@ -17,6 +18,7 @@ ...@@ -17,6 +18,7 @@
#include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_html_element.h" #include "third_party/blink/renderer/core/html/html_html_element.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/vector.h" #include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink { namespace blink {
...@@ -36,6 +38,12 @@ class RuleFeatureSetTest : public testing::Test { ...@@ -36,6 +38,12 @@ class RuleFeatureSetTest : public testing::Test {
RuleFeatureSet::SelectorPreMatch CollectFeatures( RuleFeatureSet::SelectorPreMatch CollectFeatures(
const String& selector_text) { const String& selector_text) {
return CollectFeaturesTo(selector_text, rule_feature_set_);
}
static RuleFeatureSet::SelectorPreMatch CollectFeaturesTo(
const String& selector_text,
RuleFeatureSet& set) {
CSSSelectorList selector_list = CSSParser::ParseSelector( CSSSelectorList selector_list = CSSParser::ParseSelector(
StrictCSSParserContext(SecureContextMode::kInsecureContext), nullptr, StrictCSSParserContext(SecureContextMode::kInsecureContext), nullptr,
selector_text); selector_text);
...@@ -56,7 +64,7 @@ class RuleFeatureSetTest : public testing::Test { ...@@ -56,7 +64,7 @@ class RuleFeatureSetTest : public testing::Test {
RuleData* rule_data = RuleData::MaybeCreate(style_rule, indices[i], 0, RuleData* rule_data = RuleData::MaybeCreate(style_rule, indices[i], 0,
kRuleHasNoSpecialState); kRuleHasNoSpecialState);
DCHECK(rule_data); DCHECK(rule_data);
if (rule_feature_set_.CollectFeaturesFromRuleData(rule_data)) if (set.CollectFeaturesFromRuleData(rule_data))
result = RuleFeatureSet::SelectorPreMatch::kSelectorMayMatch; result = RuleFeatureSet::SelectorPreMatch::kSelectorMayMatch;
} }
return result; return result;
...@@ -1388,6 +1396,198 @@ TEST_F(RuleFeatureSetTest, invalidatesParts) { ...@@ -1388,6 +1396,198 @@ TEST_F(RuleFeatureSetTest, invalidatesParts) {
} }
} }
TEST_F(RuleFeatureSetTest, MediaQueryResultListEquality) {
scoped_refptr<MediaQuerySet> min_width1 =
MediaQueryParser::ParseMediaQuerySet("(min-width: 1000px)", nullptr);
scoped_refptr<MediaQuerySet> min_width2 =
MediaQueryParser::ParseMediaQuerySet("(min-width: 2000px)", nullptr);
scoped_refptr<MediaQuerySet> min_resolution1 =
MediaQueryParser::ParseMediaQuerySet("(min-resolution: 72dpi)", nullptr);
scoped_refptr<MediaQuerySet> min_resolution2 =
MediaQueryParser::ParseMediaQuerySet("(min-resolution: 300dpi)", nullptr);
{
RuleFeatureSet set1;
RuleFeatureSet set2;
RuleFeatureSet set3;
for (const auto& query : min_width1->QueryVector()) {
for (const auto& expresssion : query->Expressions()) {
set1.ViewportDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, true));
set2.ViewportDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, true));
set3.ViewportDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, false));
}
}
EXPECT_EQ(set1, set2);
EXPECT_NE(set1, set3);
EXPECT_NE(set3, set2);
}
{
RuleFeatureSet set1;
for (const auto& query : min_width1->QueryVector()) {
for (const auto& expresssion : query->Expressions()) {
set1.ViewportDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, true));
}
}
RuleFeatureSet set2;
for (const auto& query : min_width2->QueryVector()) {
for (const auto& expresssion : query->Expressions()) {
set1.ViewportDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, true));
}
}
EXPECT_NE(set1, set2);
}
{
RuleFeatureSet set1;
RuleFeatureSet set2;
RuleFeatureSet set3;
for (const auto& query : min_resolution1->QueryVector()) {
for (const auto& expresssion : query->Expressions()) {
set1.DeviceDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, true));
set2.DeviceDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, true));
set3.DeviceDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, false));
}
}
EXPECT_EQ(set1, set2);
EXPECT_NE(set1, set3);
EXPECT_NE(set3, set2);
}
{
RuleFeatureSet set1;
for (const auto& query : min_resolution1->QueryVector()) {
for (const auto& expresssion : query->Expressions()) {
set1.DeviceDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, true));
}
}
RuleFeatureSet set2;
for (const auto& query : min_resolution2->QueryVector()) {
for (const auto& expresssion : query->Expressions()) {
set2.DeviceDependentMediaQueryResults().push_back(
MediaQueryResult(expresssion, true));
}
}
EXPECT_NE(set1, set2);
}
}
struct RefTestData {
const char* main;
const char* ref;
};
// The test passes if |main| produces the same RuleFeatureSet as |ref|.
RefTestData ref_equal_test_data[] = {
// clang-format off
{".a", ".a"},
// clang-format on
};
// The test passes if |main| does not produce the same RuleFeatureSet as |ref|.
RefTestData ref_not_equal_test_data[] = {
// clang-format off
{"", ".a"},
{"", "#a"},
{"", "div"},
{"", ":hover"},
{"", "::before"},
{"", ":host"},
{"", ":host(.a)"},
{"", ":host-context(.a)"},
{"", "::content"},
{"", "*"},
{"", ":not(.a)"},
{".a", ".b"},
{".a", ".a, .b"},
{"#a", "#b"},
{"ol", "ul"},
{"[foo]", "[bar]"},
{":link", ":visited"},
{".a::before", ".b::after"},
{"::cue(a)", "::cue(b)"},
{".a .b", ".a .c"},
{".a + .b", ".a + .c"},
{".a + .b .c", ".a + .b .d"},
{"div + .a", "div + .b"},
{".a:nth-child(1)", ".b:nth-child(1)"},
{"div", "span"},
// clang-format on
};
class RuleFeatureSetRefTest : public RuleFeatureSetTest,
private ScopedCSSPseudoIsForTest,
private ScopedCSSPseudoWhereForTest {
public:
RuleFeatureSetRefTest()
: ScopedCSSPseudoIsForTest(true), ScopedCSSPseudoWhereForTest(true) {}
void Run(const RefTestData& data) {
RuleFeatureSet main_set;
RuleFeatureSet ref_set;
SCOPED_TRACE(testing::Message() << "Ref: " << data.ref);
SCOPED_TRACE(testing::Message() << "Main: " << data.main);
SCOPED_TRACE("Please see RuleFeatureSet::ToString for documentation");
CollectFeaturesTo(data.main, main_set);
CollectFeaturesTo(data.ref, ref_set);
Compare(main_set, ref_set);
}
virtual void Compare(const RuleFeatureSet&, const RuleFeatureSet&) const = 0;
};
class RuleFeatureSetRefEqualTest
: public RuleFeatureSetRefTest,
public testing::WithParamInterface<RefTestData> {
public:
void Compare(const RuleFeatureSet& main,
const RuleFeatureSet& ref) const override {
EXPECT_EQ(main, ref);
}
};
INSTANTIATE_TEST_SUITE_P(RuleFeatureSetTest,
RuleFeatureSetRefEqualTest,
testing::ValuesIn(ref_equal_test_data));
TEST_P(RuleFeatureSetRefEqualTest, All) {
Run(GetParam());
}
class RuleFeatureSetRefNotEqualTest
: public RuleFeatureSetRefTest,
public testing::WithParamInterface<RefTestData> {
public:
void Compare(const RuleFeatureSet& main,
const RuleFeatureSet& ref) const override {
EXPECT_NE(main, ref);
}
};
INSTANTIATE_TEST_SUITE_P(RuleFeatureSetTest,
RuleFeatureSetRefNotEqualTest,
testing::ValuesIn(ref_not_equal_test_data));
TEST_P(RuleFeatureSetRefNotEqualTest, All) {
Run(GetParam());
}
TEST_F(RuleFeatureSetTest, CopyOnWrite) { TEST_F(RuleFeatureSetTest, CopyOnWrite) {
// RuleFeatureSet local1 has an entry in each of the class/id/attribute/ // RuleFeatureSet local1 has an entry in each of the class/id/attribute/
// pseudo sets. // pseudo sets.
......
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