perf: probe subtrees directly in Has / HasMatcher

Has(selector) and HasMatcher(m) used to call
s.document.Find(selector)/FindMatcher(m), materializing every matching
descendant in the entire document, then iterate s.Nodes x matchedNodes x
treeDepth via nodeContains to keep the elements that contain one.

This commit probes each selection node's subtree directly with SingleMatcher, which
short-circuits on the first descendant match.

BenchmarkHas (DocW().Find("h2").Has(".editsection")), -count=10:

               sec/op       B/op
    before     61.97µs       744 B
    after       3.33µs       360 B
                -94.6%      -51.6%
This commit is contained in:
jvoisin
2026-05-29 17:19:03 +02:00
parent 7a46fb40e9
commit 9082a116aa
+12 -2
View File
@@ -79,14 +79,24 @@ func (s *Selection) Intersection(sel *Selection) *Selection {
// that matches the selector.
// It returns a new Selection object with the matching elements.
func (s *Selection) Has(selector string) *Selection {
return s.HasSelection(s.document.Find(selector))
return s.HasMatcher(compileMatcher(selector))
}
// HasMatcher reduces the set of matched elements to those that have a descendant
// that matches the matcher.
// It returns a new Selection object with the matching elements.
func (s *Selection) HasMatcher(m Matcher) *Selection {
return s.HasSelection(s.document.FindMatcher(m))
result := make([]*html.Node, 0, len(s.Nodes))
m = SingleMatcher(m)
for _, n := range s.Nodes {
for c := n.FirstChild; c != nil; c = c.NextSibling {
if c.Type == html.ElementNode && len(m.MatchAll(c)) > 0 {
result = append(result, n)
break
}
}
}
return pushStack(s, result)
}
// HasNodes reduces the set of matched elements to those that have a