commit a59aa956ea3465e55a123e96729e1fdbb6b8bf8a
parent 6f16921de968c5da990eeabdd43fb1eb2c4d0339
Author: David Shin <dshin@mozilla.com>
Date: Mon, 15 Dec 2025 20:14:05 +0000
Bug 2002747: Early-reject relative selector invalidation that doesn't match local name. r=firefox-style-system-reviewers,perftest-reviewers,emilio,sparky
Differential Revision: https://phabricator.services.mozilla.com/D276266
Diffstat:
4 files changed, 73 insertions(+), 3 deletions(-)
diff --git a/servo/components/selectors/matching.rs b/servo/components/selectors/matching.rs
@@ -318,6 +318,37 @@ pub enum CompoundSelectorMatchingResult {
NotMatched,
}
+fn complex_selector_early_reject_by_local_name<E: Element>(
+ list: &SelectorList<E::Impl>,
+ element: &E,
+) -> bool {
+ list.slice()
+ .iter()
+ .all(|s| early_reject_by_local_name(s, 0, element))
+}
+
+/// Returns true if this compound would not match the given element by due
+/// to a local name selector (If one exists).
+pub fn early_reject_by_local_name<E: Element>(
+ selector: &Selector<E::Impl>,
+ from_offset: usize,
+ element: &E,
+) -> bool {
+ let iter = selector.iter_from(from_offset);
+ for component in iter {
+ if match component {
+ Component::LocalName(name) => !matches_local_name(element, name),
+ Component::Is(list) | Component::Where(list) => {
+ complex_selector_early_reject_by_local_name(list, element)
+ },
+ _ => continue,
+ } {
+ return true;
+ }
+ }
+ false
+}
+
/// Matches a compound selector belonging to `selector`, starting at offset
/// `from_offset`, matching left to right.
///
diff --git a/servo/components/style/invalidation/element/relative_selector.rs b/servo/components/style/invalidation/element/relative_selector.rs
@@ -30,9 +30,9 @@ use crate::stylist::{CascadeData, Stylist};
use dom::ElementState;
use rustc_hash::FxHashMap;
use selectors::matching::{
- matches_selector, ElementSelectorFlags, IncludeStartingStyle, MatchingContext,
- MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, QuirksMode, SelectorCaches,
- VisitedHandlingMode,
+ early_reject_by_local_name, matches_selector, ElementSelectorFlags,
+ IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode,
+ NeedsSelectorFlags, QuirksMode, SelectorCaches, VisitedHandlingMode,
};
use selectors::parser::SelectorKey;
use selectors::OpaqueElement;
@@ -497,6 +497,13 @@ where
{
return;
}
+ if early_reject_by_local_name(
+ &dependency.selector,
+ dependency.selector_offset,
+ &element,
+ ) {
+ return;
+ }
self.insert_invalidation(element, dependency, host);
},
};
diff --git a/testing/talos/talos/tests/perf-reftest-singletons/has-reject-by-local-name.html b/testing/talos/talos/tests/perf-reftest-singletons/has-reject-by-local-name.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<style>
+#anchor:has(section[class*=nonexistent].bar .item) {
+ color: green;
+}
+</style>
+<script src="util.js"></script>
+<script>
+window.onload = function() {
+ function create_tree(depth, width) {
+ const d = document.createElement("div");
+ if (depth != 0) {
+ for (let i = 0; i < width; i++) {
+ d.appendChild(create_tree(depth - 1, width));
+ }
+ } else {
+ d.classList.add("item");
+ }
+ return d;
+ }
+ anchor.appendChild(create_tree(4, 30));
+ flush_layout();
+ perf_start();
+ for (const d of anchor.children[0].children) {
+ d.classList.add("bar");
+ }
+ flush_layout();
+ perf_finish();
+};
+</script>
+<div id=anchor></div>
diff --git a/testing/talos/talos/tests/perf-reftest-singletons/perf_reftest_singletons.manifest b/testing/talos/talos/tests/perf-reftest-singletons/perf_reftest_singletons.manifest
@@ -49,4 +49,5 @@
% http://localhost/tests/perf-reftest-singletons/deeply-nested-grid-2.html
% http://localhost/tests/perf-reftest-singletons/insert-subtree-not-nth-edge-has-pseudo.html
% http://localhost/tests/perf-reftest-singletons/remove-tons-of-table-rows.html
+% http://localhost/tests/perf-reftest-singletons/has-reject-by-local-name.html
# When modifying this list, please also update build/pgo/index.html (If relevant to profile-guided optimzation).