blob: e5d5605e6c77355c34ded379c923a1e18597d6b5 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/css/invalidation/invalidation_set.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
using BackingType = InvalidationSet::BackingType;
using BackingFlags = InvalidationSet::BackingFlags;
template <BackingType type>
using Backing = InvalidationSet::Backing<type>;
template <BackingType type>
bool HasAny(const Backing<type>& backing,
const BackingFlags& flags,
std::initializer_list<const char*> args) {
for (const char* str : args) {
if (backing.Contains(flags, str))
return true;
}
return false;
}
template <BackingType type>
bool HasAll(const Backing<type>& backing,
const BackingFlags& flags,
std::initializer_list<const char*> args) {
for (const char* str : args) {
if (!backing.Contains(flags, str))
return false;
}
return true;
}
TEST(InvalidationSetTest, Backing_Create) {
BackingFlags flags;
Backing<BackingType::kClasses> backing;
ASSERT_FALSE(backing.IsHashSet(flags));
}
TEST(InvalidationSetTest, Backing_Add) {
BackingFlags flags;
Backing<BackingType::kClasses> backing;
ASSERT_FALSE(backing.IsHashSet(flags));
backing.Add(flags, AtomicString("test1"));
ASSERT_FALSE(backing.IsHashSet(flags));
backing.Add(flags, AtomicString("test2"));
ASSERT_TRUE(backing.IsHashSet(flags));
backing.Clear(flags);
}
TEST(InvalidationSetTest, Backing_AddSame) {
BackingFlags flags;
Backing<BackingType::kClasses> backing;
ASSERT_FALSE(backing.IsHashSet(flags));
backing.Add(flags, AtomicString("test1"));
ASSERT_FALSE(backing.IsHashSet(flags));
backing.Add(flags, AtomicString("test1"));
// No need to upgrade to HashSet if we're adding the item we already have.
ASSERT_FALSE(backing.IsHashSet(flags));
backing.Clear(flags);
}
TEST(InvalidationSetTest, Backing_Independence) {
BackingFlags flags;
Backing<BackingType::kClasses> classes;
Backing<BackingType::kIds> ids;
Backing<BackingType::kTagNames> tag_names;
Backing<BackingType::kAttributes> attributes;
classes.Add(flags, "test1");
ids.Add(flags, "test2");
tag_names.Add(flags, "test3");
attributes.Add(flags, "test4");
// Adding to set does not affect other backings:
ASSERT_TRUE(classes.Contains(flags, "test1"));
ASSERT_FALSE(HasAny(classes, flags, {"test2", "test3", "test4"}));
ASSERT_TRUE(ids.Contains(flags, "test2"));
ASSERT_FALSE(HasAny(ids, flags, {"test1", "test3", "test4"}));
ASSERT_TRUE(tag_names.Contains(flags, "test3"));
ASSERT_FALSE(HasAny(tag_names, flags, {"test1", "test2", "test4"}));
ASSERT_TRUE(attributes.Contains(flags, "test4"));
ASSERT_FALSE(HasAny(attributes, flags, {"test1", "test2", "test3"}));
// Adding additional items to one set does not affect others:
classes.Add(flags, "test5");
tag_names.Add(flags, "test6");
ASSERT_TRUE(HasAll(classes, flags, {"test1", "test5"}));
ASSERT_FALSE(HasAny(classes, flags, {"test2", "test3", "test4", "test6"}));
ASSERT_TRUE(ids.Contains(flags, "test2"));
ASSERT_FALSE(
HasAny(ids, flags, {"test1", "test3", "test4", "test5", "test6"}));
ASSERT_TRUE(HasAll(tag_names, flags, {"test3", "test6"}));
ASSERT_FALSE(HasAny(tag_names, flags, {"test1", "test2", "test4", "test5"}));
ASSERT_TRUE(attributes.Contains(flags, "test4"));
ASSERT_FALSE(HasAny(attributes, flags, {"test1", "test2", "test3"}));
// Clearing one set does not clear others:
classes.Clear(flags);
ids.Clear(flags);
attributes.Clear(flags);
auto all_test_strings = {"test1", "test2", "test3",
"test4", "test5", "test6"};
ASSERT_FALSE(HasAny(classes, flags, all_test_strings));
ASSERT_FALSE(HasAny(ids, flags, all_test_strings));
ASSERT_FALSE(HasAny(attributes, flags, all_test_strings));
ASSERT_FALSE(classes.IsHashSet(flags));
ASSERT_FALSE(ids.IsHashSet(flags));
ASSERT_FALSE(attributes.IsHashSet(flags));
ASSERT_TRUE(tag_names.IsHashSet(flags));
ASSERT_TRUE(HasAll(tag_names, flags, {"test3", "test6"}));
ASSERT_FALSE(HasAny(tag_names, flags, {"test1", "test2", "test4", "test5"}));
tag_names.Clear(flags);
}
TEST(InvalidationSetTest, Backing_ClearContains) {
BackingFlags flags;
Backing<BackingType::kClasses> backing;
// Clearing an empty set:
ASSERT_FALSE(backing.Contains(flags, "test1"));
ASSERT_FALSE(backing.IsHashSet(flags));
backing.Clear(flags);
ASSERT_FALSE(backing.IsHashSet(flags));
// Add one element to the set, and clear it:
backing.Add(flags, "test1");
ASSERT_FALSE(backing.IsHashSet(flags));
ASSERT_TRUE(backing.Contains(flags, "test1"));
backing.Clear(flags);
ASSERT_FALSE(backing.Contains(flags, "test1"));
ASSERT_FALSE(backing.IsHashSet(flags));
// Add two elements to the set, and clear them:
backing.Add(flags, "test1");
ASSERT_FALSE(backing.IsHashSet(flags));
ASSERT_TRUE(backing.Contains(flags, "test1"));
ASSERT_FALSE(backing.Contains(flags, "test2"));
backing.Add(flags, "test2");
ASSERT_TRUE(backing.IsHashSet(flags));
ASSERT_TRUE(backing.Contains(flags, "test1"));
ASSERT_TRUE(backing.Contains(flags, "test2"));
backing.Clear(flags);
ASSERT_FALSE(backing.Contains(flags, "test1"));
ASSERT_FALSE(backing.Contains(flags, "test2"));
ASSERT_FALSE(backing.IsHashSet(flags));
}
TEST(InvalidationSetTest, Backing_BackingIsEmpty) {
BackingFlags flags;
Backing<BackingType::kClasses> backing;
ASSERT_TRUE(backing.IsEmpty(flags));
backing.Add(flags, "test1");
ASSERT_FALSE(backing.IsEmpty(flags));
backing.Add(flags, "test2");
backing.Clear(flags);
ASSERT_TRUE(backing.IsEmpty(flags));
}
TEST(InvalidationSetTest, Backing_IsEmpty) {
BackingFlags flags;
Backing<BackingType::kClasses> backing;
ASSERT_TRUE(backing.IsEmpty(flags));
backing.Add(flags, "test1");
ASSERT_FALSE(backing.IsEmpty(flags));
backing.Clear(flags);
ASSERT_TRUE(backing.IsEmpty(flags));
}
TEST(InvalidationSetTest, Backing_Iterator) {
// Iterate over empty set.
{
BackingFlags flags;
Backing<BackingType::kClasses> backing;
Vector<AtomicString> strings;
for (const AtomicString& str : backing.Items(flags))
strings.push_back(str);
ASSERT_EQ(0u, strings.size());
}
// Iterate over set with one item.
{
BackingFlags flags;
Backing<BackingType::kClasses> backing;
backing.Add(flags, "test1");
Vector<AtomicString> strings;
for (const AtomicString& str : backing.Items(flags))
strings.push_back(str);
ASSERT_EQ(1u, strings.size());
ASSERT_TRUE(strings.Contains("test1"));
backing.Clear(flags);
}
// Iterate over set with multiple items.
{
BackingFlags flags;
Backing<BackingType::kClasses> backing;
backing.Add(flags, "test1");
backing.Add(flags, "test2");
backing.Add(flags, "test3");
Vector<AtomicString> strings;
for (const AtomicString& str : backing.Items(flags))
strings.push_back(str);
ASSERT_EQ(3u, strings.size());
ASSERT_TRUE(strings.Contains("test1"));
ASSERT_TRUE(strings.Contains("test2"));
ASSERT_TRUE(strings.Contains("test3"));
backing.Clear(flags);
}
}
TEST(InvalidationSetTest, Backing_GetStringImpl) {
BackingFlags flags;
Backing<BackingType::kClasses> backing;
EXPECT_FALSE(backing.GetStringImpl(flags));
backing.Add(flags, "a");
EXPECT_EQ("a", AtomicString(backing.GetStringImpl(flags)));
backing.Add(flags, "b");
EXPECT_FALSE(backing.GetStringImpl(flags));
backing.Clear(flags);
}
TEST(InvalidationSetTest, Backing_GetHashSet) {
BackingFlags flags;
Backing<BackingType::kClasses> backing;
EXPECT_FALSE(backing.GetHashSet(flags));
backing.Add(flags, "a");
EXPECT_FALSE(backing.GetHashSet(flags));
backing.Add(flags, "b");
EXPECT_TRUE(backing.GetHashSet(flags));
backing.Clear(flags);
}
TEST(InvalidationSetTest, ClassInvalidatesElement) {
auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
auto& document = dummy_page_holder->GetDocument();
document.body()->setInnerHTML("<div id=test class='a b'>");
document.View()->UpdateAllLifecyclePhasesForTest();
Element* element = document.getElementById("test");
ASSERT_TRUE(element);
scoped_refptr<InvalidationSet> set = DescendantInvalidationSet::Create();
EXPECT_FALSE(set->InvalidatesElement(*element));
// Adding one string sets the string_impl_ of the classes_ Backing.
set->AddClass("a");
EXPECT_TRUE(set->InvalidatesElement(*element));
// Adding another upgrades to a HashSet.
set->AddClass("c");
EXPECT_TRUE(set->InvalidatesElement(*element));
// These sets should not cause invalidation.
set = DescendantInvalidationSet::Create();
set->AddClass("c");
EXPECT_FALSE(set->InvalidatesElement(*element));
set->AddClass("d");
EXPECT_FALSE(set->InvalidatesElement(*element));
}
TEST(InvalidationSetTest, AttributeInvalidatesElement) {
auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
auto& document = dummy_page_holder->GetDocument();
document.body()->setInnerHTML("<div id=test a b>");
document.View()->UpdateAllLifecyclePhasesForTest();
Element* element = document.getElementById("test");
ASSERT_TRUE(element);
scoped_refptr<InvalidationSet> set = DescendantInvalidationSet::Create();
EXPECT_FALSE(set->InvalidatesElement(*element));
// Adding one string sets the string_impl_ of the classes_ Backing.
set->AddAttribute("a");
EXPECT_TRUE(set->InvalidatesElement(*element));
// Adding another upgrades to a HashSet.
set->AddAttribute("c");
EXPECT_TRUE(set->InvalidatesElement(*element));
// These sets should not cause invalidation.
set = DescendantInvalidationSet::Create();
set->AddAttribute("c");
EXPECT_FALSE(set->InvalidatesElement(*element));
set->AddAttribute("d");
EXPECT_FALSE(set->InvalidatesElement(*element));
}
// Once we setWholeSubtreeInvalid, we should not keep the HashSets.
TEST(InvalidationSetTest, SubtreeInvalid_AddBefore) {
scoped_refptr<InvalidationSet> set = DescendantInvalidationSet::Create();
set->AddClass("a");
set->SetWholeSubtreeInvalid();
ASSERT_TRUE(set->IsEmpty());
}
// Don't (re)create HashSets if we've already setWholeSubtreeInvalid.
TEST(InvalidationSetTest, SubtreeInvalid_AddAfter) {
scoped_refptr<InvalidationSet> set = DescendantInvalidationSet::Create();
set->SetWholeSubtreeInvalid();
set->AddTagName("a");
ASSERT_TRUE(set->IsEmpty());
}
// No need to keep the HashSets when combining with a wholeSubtreeInvalid set.
TEST(InvalidationSetTest, SubtreeInvalid_Combine_1) {
scoped_refptr<DescendantInvalidationSet> set1 =
DescendantInvalidationSet::Create();
scoped_refptr<DescendantInvalidationSet> set2 =
DescendantInvalidationSet::Create();
set1->AddId("a");
set2->SetWholeSubtreeInvalid();
set1->Combine(*set2);
ASSERT_TRUE(set1->WholeSubtreeInvalid());
ASSERT_TRUE(set1->IsEmpty());
}
// No need to add HashSets from combining set when we already have
// wholeSubtreeInvalid.
TEST(InvalidationSetTest, SubtreeInvalid_Combine_2) {
scoped_refptr<DescendantInvalidationSet> set1 =
DescendantInvalidationSet::Create();
scoped_refptr<DescendantInvalidationSet> set2 =
DescendantInvalidationSet::Create();
set1->SetWholeSubtreeInvalid();
set2->AddAttribute("a");
set1->Combine(*set2);
ASSERT_TRUE(set1->WholeSubtreeInvalid());
ASSERT_TRUE(set1->IsEmpty());
}
TEST(InvalidationSetTest, SubtreeInvalid_AddCustomPseudoBefore) {
scoped_refptr<InvalidationSet> set = DescendantInvalidationSet::Create();
set->SetCustomPseudoInvalid();
ASSERT_FALSE(set->IsEmpty());
set->SetWholeSubtreeInvalid();
ASSERT_TRUE(set->IsEmpty());
}
TEST(InvalidationSetTest, SelfInvalidationSet_Combine) {
InvalidationSet* self_set = InvalidationSet::SelfInvalidationSet();
EXPECT_TRUE(self_set->IsSelfInvalidationSet());
self_set->Combine(*self_set);
EXPECT_TRUE(self_set->IsSelfInvalidationSet());
scoped_refptr<InvalidationSet> set = DescendantInvalidationSet::Create();
EXPECT_FALSE(set->InvalidatesSelf());
set->Combine(*self_set);
EXPECT_TRUE(set->InvalidatesSelf());
}
#ifndef NDEBUG
TEST(InvalidationSetTest, ShowDebug) {
scoped_refptr<InvalidationSet> set = DescendantInvalidationSet::Create();
set->Show();
}
#endif // NDEBUG
} // namespace
} // namespace blink