Schedule a type selector invalidation set for RuleSet invalidations.

We marked all elements which had a selector in the tagRules bucket for
style recalc for RuleSet invalidations. That means we would recalculate
style for all spans if we added a stylesheet containing a rule with an
"#id span" selector (but not for "#id span.class" as that ends up in
the classRules bucket).

Instead, use an invalidation set containing only tag names for the
selectors where there are no ids, classes, or attribute selectors, and
which have a type selector in the rightmost compound. This means that
"#id span" will not add "span" to that set, but "span" and "div span"
will. "div span" will not add "div", and "div *" will cause a full
scope recalc. In order to support invalidation for those, we would have
had to have one invalidation set for each tag name instead of a single
descendant invalidation set for all.

RuleSet invalidations schedule this typeRuleInvalidationSet on the root
of the TreeScope When doing ruleset invalidations.

BUG=680549

Review-Url: https://codereview.chromium.org/2703643003
Cr-Commit-Position: refs/heads/master@{#451488}
diff --git a/third_party/WebKit/LayoutTests/fast/css/invalidation/sheet-ruleset-invalidation.html b/third_party/WebKit/LayoutTests/fast/css/invalidation/sheet-ruleset-invalidation.html
index e8a7c78..d513ff27 100644
--- a/third_party/WebKit/LayoutTests/fast/css/invalidation/sheet-ruleset-invalidation.html
+++ b/third_party/WebKit/LayoutTests/fast/css/invalidation/sheet-ruleset-invalidation.html
@@ -8,13 +8,39 @@
     <div></div>
     <div></div>
     <div></div>
+    <div id="found">
+        <div></div>
+    </div>
     <span></span>
 </div>
 <script>
     test(() => {
-        assert_true(!!window.internals, "Test requires window.internals.");
+        assert_true(!!window.internals, "Tests require window.internals.");
+    }, "Test for prerequisites.");
+
+    function applyRuleAndReturnAffectedElementCount(ruleString) {
         document.body.offsetTop;
-        document.styleSheets[0].insertRule("span{background:green}", 0);
-        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 1, "Check that only the span is affected.");
-    }, "Inserting a style rule with a type selector should only invalidate elements with that type.");
+        document.styleSheets[0].insertRule(ruleString, 0);
+        var recalcCount = internals.updateStyleAndReturnAffectedElementCount();
+        document.styleSheets[0].removeRule();
+        return recalcCount;
+    }
+
+    test(() => {
+        assert_equals(applyRuleAndReturnAffectedElementCount(
+            "span { background: green }"), 1,
+            "Check that only the span is affected.");
+    }, "A style rule with a type selector should only invalidate elements with that type.");
+
+    test(() => {
+        assert_equals(applyRuleAndReturnAffectedElementCount(
+            "#notfound div { background: red }"), 0,
+            "Check that none of divs are recalculated.");
+    }, "A type selector scoped in an unknown id should not invalidate any elements.");
+
+    test(() => {
+        assert_equals(applyRuleAndReturnAffectedElementCount(
+            "#found div { background: red }"), 1,
+            "Check that only one of the divs is recalculated.");
+    }, "A type selector scoped by a known id should only invalidate descendants of the element with that id.");
 </script>
diff --git a/third_party/WebKit/Source/core/css/RuleFeature.cpp b/third_party/WebKit/Source/core/css/RuleFeature.cpp
index 080193259..ec4bba5 100644
--- a/third_party/WebKit/Source/core/css/RuleFeature.cpp
+++ b/third_party/WebKit/Source/core/css/RuleFeature.cpp
@@ -467,8 +467,7 @@
   const CSSSelector* nextCompound =
       lastInCompound ? lastInCompound->tagHistory() : &ruleData.selector();
   if (!nextCompound) {
-    if (!features.hasFeaturesForRuleSetInvalidation)
-      m_metadata.needsFullRecalcForRuleSetInvalidation = true;
+    updateRuleSetInvalidation(features);
     return;
   }
   if (lastInCompound)
@@ -476,9 +475,17 @@
                                  siblingFeatures, features);
 
   addFeaturesToInvalidationSets(*nextCompound, siblingFeatures, features);
+  updateRuleSetInvalidation(features);
+}
 
-  if (!features.hasFeaturesForRuleSetInvalidation)
-    m_metadata.needsFullRecalcForRuleSetInvalidation = true;
+void RuleFeatureSet::updateRuleSetInvalidation(
+    const InvalidationSetFeatures& features) {
+  if (!features.hasFeaturesForRuleSetInvalidation) {
+    if (features.forceSubtree || features.tagNames.isEmpty())
+      m_metadata.needsFullRecalcForRuleSetInvalidation = true;
+    else
+      addTagNamesToTypeRuleInvalidationSet(features.tagNames);
+  }
 }
 
 void RuleFeatureSet::updateInvalidationSetsForContentAttribute(
@@ -608,7 +615,7 @@
     if (!simpleSelector->tagHistory() ||
         simpleSelector->relation() != CSSSelector::SubSelector) {
       features.hasFeaturesForRuleSetInvalidation =
-          features.hasTagIdClassOrAttribute();
+          features.hasIdClassOrAttribute();
       return simpleSelector;
     }
   }
@@ -1160,6 +1167,14 @@
                                  descendantFeatures);
 }
 
+void RuleFeatureSet::addTagNamesToTypeRuleInvalidationSet(
+    const Vector<AtomicString>& tagNames) {
+  DCHECK(!tagNames.isEmpty());
+  ensureTypeRuleInvalidationSet();
+  for (auto tagName : tagNames)
+    m_typeRuleInvalidationSet->addTagName(tagName);
+}
+
 DEFINE_TRACE(RuleFeatureSet) {
   visitor->trace(m_siblingRules);
   visitor->trace(m_uncommonAttributeRules);
@@ -1190,9 +1205,8 @@
          !tagNames.isEmpty() || customPseudoElement;
 }
 
-bool RuleFeatureSet::InvalidationSetFeatures::hasTagIdClassOrAttribute() const {
-  return !classes.isEmpty() || !attributes.isEmpty() || !ids.isEmpty() ||
-         !tagNames.isEmpty();
+bool RuleFeatureSet::InvalidationSetFeatures::hasIdClassOrAttribute() const {
+  return !classes.isEmpty() || !attributes.isEmpty() || !ids.isEmpty();
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/RuleFeature.h b/third_party/WebKit/Source/core/css/RuleFeature.h
index 4a894eb..40bd9bd 100644
--- a/third_party/WebKit/Source/core/css/RuleFeature.h
+++ b/third_party/WebKit/Source/core/css/RuleFeature.h
@@ -212,7 +212,7 @@
 
     void add(const InvalidationSetFeatures& other);
     bool hasFeatures() const;
-    bool hasTagIdClassOrAttribute() const;
+    bool hasIdClassOrAttribute() const;
 
     Vector<AtomicString> classes;
     Vector<AtomicString> attributes;
@@ -280,6 +280,9 @@
       const InvalidationSetFeatures& siblingFeatures,
       const InvalidationSetFeatures& descendantFeatures);
 
+  void updateRuleSetInvalidation(const InvalidationSetFeatures&);
+  void addTagNamesToTypeRuleInvalidationSet(const Vector<AtomicString>&);
+
   FeatureMetadata m_metadata;
   InvalidationSetMap m_classInvalidationSets;
   InvalidationSetMap m_attributeInvalidationSets;
diff --git a/third_party/WebKit/Source/core/dom/StyleEngine.cpp b/third_party/WebKit/Source/core/dom/StyleEngine.cpp
index 19826f4..75ef643 100644
--- a/third_party/WebKit/Source/core/dom/StyleEngine.cpp
+++ b/third_party/WebKit/Source/core/dom/StyleEngine.cpp
@@ -832,15 +832,21 @@
     for (const Attribute& attribute : element.attributes())
       ruleSet->features().collectInvalidationSetsForAttribute(
           invalidationLists, element, attribute.name());
-    if (ruleSet->tagRules(element.localNameForSelectorMatching()))
-      element.setNeedsStyleRecalc(LocalStyleChange,
-                                  StyleChangeReasonForTracing::create(
-                                      StyleChangeReason::StyleSheetChange));
   }
   m_styleInvalidator.scheduleInvalidationSetsForNode(invalidationLists,
                                                      element);
 }
 
+void StyleEngine::scheduleTypeRuleSetInvalidations(
+    ContainerNode& node,
+    const HeapHashSet<Member<RuleSet>>& ruleSets) {
+  InvalidationLists invalidationLists;
+  for (const auto& ruleSet : ruleSets)
+    ruleSet->features().collectTypeRuleInvalidationSet(invalidationLists, node);
+  DCHECK(invalidationLists.siblings.isEmpty());
+  m_styleInvalidator.scheduleInvalidationSetsForNode(invalidationLists, node);
+}
+
 void StyleEngine::invalidateSlottedElements(HTMLSlotElement& slot) {
   for (auto& node : slot.getDistributedNodes()) {
     if (node->isElementNode())
@@ -863,6 +869,8 @@
   TRACE_EVENT0("blink,blink_style",
                "StyleEngine::scheduleInvalidationsForRuleSets");
 
+  scheduleTypeRuleSetInvalidations(treeScope.rootNode(), ruleSets);
+
   bool invalidateSlotted = false;
   if (treeScope.rootNode().isShadowRoot()) {
     Element& host = toShadowRoot(treeScope.rootNode()).host();
diff --git a/third_party/WebKit/Source/core/dom/StyleEngine.h b/third_party/WebKit/Source/core/dom/StyleEngine.h
index 20e3d9a..0df63e6 100644
--- a/third_party/WebKit/Source/core/dom/StyleEngine.h
+++ b/third_party/WebKit/Source/core/dom/StyleEngine.h
@@ -315,6 +315,8 @@
   void scheduleRuleSetInvalidationsForElement(
       Element&,
       const HeapHashSet<Member<RuleSet>>&);
+  void scheduleTypeRuleSetInvalidations(ContainerNode&,
+                                        const HeapHashSet<Member<RuleSet>>&);
   void invalidateSlottedElements(HTMLSlotElement&);
 
   void updateViewport();
diff --git a/third_party/WebKit/Source/core/dom/StyleEngineTest.cpp b/third_party/WebKit/Source/core/dom/StyleEngineTest.cpp
index e62026e..7859ffbc 100644
--- a/third_party/WebKit/Source/core/dom/StyleEngineTest.cpp
+++ b/third_party/WebKit/Source/core/dom/StyleEngineTest.cpp
@@ -144,29 +144,44 @@
       "<div>"
       "  <span></span>"
       "  <div></div>"
-      "</div>");
+      "</div>"
+      "<b></b><b></b><b></b><b></b>"
+      "<i id=i>"
+      "  <i>"
+      "    <b></b>"
+      "  </i>"
+      "</i>");
 
   document().view()->updateAllLifecyclePhases();
 
   unsigned beforeCount = styleEngine().styleForElementCount();
   EXPECT_EQ(
-      scheduleInvalidationsForRules(document(), "span { background: green}"),
-      RuleSetInvalidationsScheduled);
+      RuleSetInvalidationsScheduled,
+      scheduleInvalidationsForRules(document(), "span { background: green}"));
   document().view()->updateAllLifecyclePhases();
   unsigned afterCount = styleEngine().styleForElementCount();
   EXPECT_EQ(1u, afterCount - beforeCount);
 
   beforeCount = afterCount;
-  EXPECT_EQ(scheduleInvalidationsForRules(document(),
-                                          "body div { background: green}"),
-            RuleSetInvalidationsScheduled);
+  EXPECT_EQ(RuleSetInvalidationsScheduled,
+            scheduleInvalidationsForRules(document(),
+                                          "body div { background: green}"));
   document().view()->updateAllLifecyclePhases();
   afterCount = styleEngine().styleForElementCount();
   EXPECT_EQ(2u, afterCount - beforeCount);
 
   EXPECT_EQ(
-      scheduleInvalidationsForRules(document(), "div * { background: green}"),
-      RuleSetInvalidationFullRecalc);
+      RuleSetInvalidationFullRecalc,
+      scheduleInvalidationsForRules(document(), "div * { background: green}"));
+  document().view()->updateAllLifecyclePhases();
+
+  beforeCount = styleEngine().styleForElementCount();
+  EXPECT_EQ(
+      RuleSetInvalidationsScheduled,
+      scheduleInvalidationsForRules(document(), "#i b { background: green}"));
+  document().view()->updateAllLifecyclePhases();
+  afterCount = styleEngine().styleForElementCount();
+  EXPECT_EQ(1u, afterCount - beforeCount);
 }
 
 TEST_F(StyleEngineTest, RuleSetInvalidationHost) {