blob: 9f164aec8f905a222dfb5075f35fcddf1e44aed3 [file] [log] [blame]
/*
* Copyright (C) 2014 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/css/invalidation/invalidation_set.h"
#include <memory>
#include <utility>
#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/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/wtf/text/string_builder.h"
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;
#define TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART_IF_ENABLED( \
element, reason, invalidationSet, singleSelectorPart) \
if (UNLIKELY(*g_tracing_enabled)) \
TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART( \
element, reason, invalidationSet, singleSelectorPart);
// static
void InvalidationSetDeleter::Destruct(const InvalidationSet* obj) {
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() {
g_tracing_enabled = TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking"));
}
InvalidationSet::InvalidationSet(InvalidationType type)
: type_(static_cast<unsigned>(type)),
invalidates_self_(false),
is_alive_(true) {}
bool InvalidationSet::InvalidatesElement(Element& element) const {
if (invalidation_flags_.WholeSubtreeInvalid())
return true;
if (HasTagNames() && HasTagName(element.LocalNameForSelectorMatching())) {
TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART_IF_ENABLED(
element, kInvalidationSetMatchedTagName, *this,
element.LocalNameForSelectorMatching());
return true;
}
if (element.HasID() && HasIds() && HasId(element.IdForStyleResolution())) {
TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART_IF_ENABLED(
element, kInvalidationSetMatchedId, *this,
element.IdForStyleResolution());
return true;
}
if (element.HasClass() && HasClasses()) {
if (StringImpl* class_name = FindAnyClass(element)) {
TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART_IF_ENABLED(
element, kInvalidationSetMatchedClass, *this, String(class_name));
return true;
}
}
if (element.hasAttributes() && HasAttributes()) {
if (StringImpl* attribute = FindAnyAttribute(element)) {
TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART_IF_ENABLED(
element, kInvalidationSetMatchedAttribute, *this, String(attribute));
return true;
}
}
if (element.HasPart() && invalidation_flags_.InvalidatesParts()) {
TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART_IF_ENABLED(
element, kInvalidationSetMatchedPart, *this, "");
return true;
}
return false;
}
bool InvalidationSet::InvalidatesTagName(Element& element) const {
if (HasTagNames() && HasTagName(element.LocalNameForSelectorMatching())) {
TRACE_STYLE_INVALIDATOR_INVALIDATION_SELECTORPART_IF_ENABLED(
element, kInvalidationSetMatchedTagName, *this,
element.LocalNameForSelectorMatching());
return true;
}
return false;
}
void InvalidationSet::Combine(const InvalidationSet& other) {
CHECK(is_alive_);
CHECK(other.is_alive_);
CHECK_EQ(GetType(), other.GetType());
if (IsSelfInvalidationSet()) {
// We should never modify the SelfInvalidationSet singleton. When
// aggregating the contents from another invalidation set into an
// invalidation set which only invalidates self, we instantiate a new
// DescendantInvalidation set before calling Combine(). We still may end up
// here if we try to combine two references to the singleton set.
DCHECK(other.IsSelfInvalidationSet());
return;
}
CHECK_NE(&other, this);
if (auto* invalidation_set = DynamicTo<SiblingInvalidationSet>(this)) {
SiblingInvalidationSet& siblings = *invalidation_set;
const SiblingInvalidationSet& other_siblings =
To<SiblingInvalidationSet>(other);
siblings.UpdateMaxDirectAdjacentSelectors(
other_siblings.MaxDirectAdjacentSelectors());
if (other_siblings.SiblingDescendants())
siblings.EnsureSiblingDescendants().Combine(
*other_siblings.SiblingDescendants());
if (other_siblings.Descendants())
siblings.EnsureDescendants().Combine(*other_siblings.Descendants());
}
if (other.InvalidatesSelf()) {
SetInvalidatesSelf();
if (other.IsSelfInvalidationSet())
return;
}
// No longer bother combining data structures, since the whole subtree is
// deemed invalid.
if (WholeSubtreeInvalid())
return;
if (other.WholeSubtreeInvalid()) {
SetWholeSubtreeInvalid();
return;
}
if (other.CustomPseudoInvalid())
SetCustomPseudoInvalid();
if (other.TreeBoundaryCrossing())
SetTreeBoundaryCrossing();
if (other.InsertionPointCrossing())
SetInsertionPointCrossing();
if (other.InvalidatesSlotted())
SetInvalidatesSlotted();
if (other.InvalidatesParts())
SetInvalidatesParts();
for (const auto& class_name : other.Classes())
AddClass(class_name);
for (const auto& id : other.Ids())
AddId(id);
for (const auto& tag_name : other.TagNames())
AddTagName(tag_name);
for (const auto& attribute : other.Attributes())
AddAttribute(attribute);
}
void InvalidationSet::Destroy() const {
if (auto* invalidation_set = DynamicTo<DescendantInvalidationSet>(this))
delete invalidation_set;
else
delete To<SiblingInvalidationSet>(this);
}
void InvalidationSet::ClearAllBackings() {
classes_.Clear(backing_flags_);
ids_.Clear(backing_flags_);
tag_names_.Clear(backing_flags_);
attributes_.Clear(backing_flags_);
}
bool InvalidationSet::HasEmptyBackings() const {
return classes_.IsEmpty(backing_flags_) && ids_.IsEmpty(backing_flags_) &&
tag_names_.IsEmpty(backing_flags_) &&
attributes_.IsEmpty(backing_flags_);
}
StringImpl* InvalidationSet::FindAnyClass(Element& element) const {
const SpaceSplitString& class_names = element.ClassNames();
wtf_size_t size = class_names.size();
if (StringImpl* string_impl = classes_.GetStringImpl(backing_flags_)) {
for (wtf_size_t i = 0; i < size; ++i) {
if (Equal(string_impl, class_names[i].Impl()))
return string_impl;
}
}
if (const HashSet<AtomicString>* set = classes_.GetHashSet(backing_flags_)) {
for (wtf_size_t i = 0; i < size; ++i) {
auto item = set->find(class_names[i]);
if (item != set->end())
return item->Impl();
}
}
return nullptr;
}
StringImpl* InvalidationSet::FindAnyAttribute(Element& element) const {
if (StringImpl* string_impl = attributes_.GetStringImpl(backing_flags_)) {
if (element.HasAttributeIgnoringNamespace(AtomicString(string_impl)))
return string_impl;
}
if (const HashSet<AtomicString>* set =
attributes_.GetHashSet(backing_flags_)) {
for (const auto& attribute : *set) {
if (element.HasAttributeIgnoringNamespace(attribute))
return attribute.Impl();
}
}
return nullptr;
}
void InvalidationSet::AddClass(const AtomicString& class_name) {
if (WholeSubtreeInvalid())
return;
CHECK(!class_name.IsEmpty());
classes_.Add(backing_flags_, class_name);
}
void InvalidationSet::AddId(const AtomicString& id) {
if (WholeSubtreeInvalid())
return;
CHECK(!id.IsEmpty());
ids_.Add(backing_flags_, id);
}
void InvalidationSet::AddTagName(const AtomicString& tag_name) {
if (WholeSubtreeInvalid())
return;
CHECK(!tag_name.IsEmpty());
tag_names_.Add(backing_flags_, tag_name);
}
void InvalidationSet::AddAttribute(const AtomicString& attribute) {
if (WholeSubtreeInvalid())
return;
CHECK(!attribute.IsEmpty());
attributes_.Add(backing_flags_, attribute);
}
void InvalidationSet::SetWholeSubtreeInvalid() {
if (invalidation_flags_.WholeSubtreeInvalid())
return;
invalidation_flags_.SetWholeSubtreeInvalid(true);
invalidation_flags_.SetInvalidateCustomPseudo(false);
invalidation_flags_.SetTreeBoundaryCrossing(false);
invalidation_flags_.SetInsertionPointCrossing(false);
invalidation_flags_.SetInvalidatesSlotted(false);
invalidation_flags_.SetInvalidatesParts(false);
ClearAllBackings();
}
namespace {
scoped_refptr<DescendantInvalidationSet> CreateSelfInvalidationSet() {
auto new_set = DescendantInvalidationSet::Create();
new_set->SetInvalidatesSelf();
return new_set;
}
scoped_refptr<DescendantInvalidationSet> CreatePartInvalidationSet() {
auto new_set = DescendantInvalidationSet::Create();
new_set->SetInvalidatesParts();
new_set->SetTreeBoundaryCrossing();
return new_set;
}
} // namespace
InvalidationSet* InvalidationSet::SelfInvalidationSet() {
DEFINE_STATIC_REF(InvalidationSet, singleton_, CreateSelfInvalidationSet());
return singleton_;
}
InvalidationSet* InvalidationSet::PartInvalidationSet() {
DEFINE_STATIC_REF(InvalidationSet, singleton_, CreatePartInvalidationSet());
return singleton_;
}
void InvalidationSet::ToTracedValue(TracedValue* value) const {
value->BeginDictionary();
value->SetString("id", DescendantInvalidationSetToIdString(*this));
if (invalidation_flags_.WholeSubtreeInvalid())
value->SetBoolean("allDescendantsMightBeInvalid", true);
if (invalidation_flags_.InvalidateCustomPseudo())
value->SetBoolean("customPseudoInvalid", true);
if (invalidation_flags_.TreeBoundaryCrossing())
value->SetBoolean("treeBoundaryCrossing", true);
if (invalidation_flags_.InsertionPointCrossing())
value->SetBoolean("insertionPointCrossing", true);
if (invalidation_flags_.InvalidatesSlotted())
value->SetBoolean("invalidatesSlotted", true);
if (invalidation_flags_.InvalidatesParts())
value->SetBoolean("invalidatesParts", true);
if (HasIds()) {
value->BeginArray("ids");
for (const auto& id : Ids())
value->PushString(id);
value->EndArray();
}
if (HasClasses()) {
value->BeginArray("classes");
for (const auto& class_name : Classes())
value->PushString(class_name);
value->EndArray();
}
if (HasTagNames()) {
value->BeginArray("tagNames");
for (const auto& tag_name : TagNames())
value->PushString(tag_name);
value->EndArray();
}
if (HasAttributes()) {
value->BeginArray("attributes");
for (const auto& attribute : Attributes())
value->PushString(attribute);
value->EndArray();
}
value->EndDictionary();
}
#ifndef NDEBUG
void InvalidationSet::Show() const {
TracedValueJSON value;
value.BeginArray("InvalidationSet");
ToTracedValue(&value);
value.EndArray();
LOG(ERROR) << value.ToJSON().Ascii();
}
#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(
scoped_refptr<DescendantInvalidationSet> descendants)
: InvalidationSet(InvalidationType::kInvalidateSiblings),
max_direct_adjacent_selectors_(1),
descendant_invalidation_set_(std::move(descendants)) {}
SiblingInvalidationSet::SiblingInvalidationSet()
: InvalidationSet(InvalidationType::kInvalidateNthSiblings),
max_direct_adjacent_selectors_(kDirectAdjacentMax) {}
DescendantInvalidationSet& SiblingInvalidationSet::EnsureSiblingDescendants() {
if (!sibling_descendant_invalidation_set_)
sibling_descendant_invalidation_set_ = DescendantInvalidationSet::Create();
return *sibling_descendant_invalidation_set_;
}
DescendantInvalidationSet& SiblingInvalidationSet::EnsureDescendants() {
if (!descendant_invalidation_set_)
descendant_invalidation_set_ = DescendantInvalidationSet::Create();
return *descendant_invalidation_set_;
}
std::ostream& operator<<(std::ostream& ostream, const InvalidationSet& set) {
return ostream << set.ToString().Utf8();
}
} // namespace blink