|
In some contexts, which includes JavaScript and thus user styles, you can use XPath, which interacts with all nodes, not just element nodes like CSS. Your sample selector would be this, if I have understood your intention correctly¹ (that you’re targeting the <a> element, and don’t care if the text “click here ” is an immediate child, or spread across multiple children): //article//a[contains(., "click here ")]
The equivalent to document.querySelector in JavaScript: document.evaluate(
'//article//a[contains(., "click here ")]',
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null,
).singleNodeValue
And the document.querySelectorAll (which returns a static node list) equivalent would use ORDERED_NODE_SNAPSHOT_TYPE, with .snapshotLength and .snapshotItem(index) on the result object. Yes, this API is written in a very old style, and could do with being wrapped in a more pleasant API.See https://www.w3.org/TR/1999/REC-xpath-19991116/ for the syntax, and https://developer.mozilla.org/en-US/docs/Web/API/Document/ev... for document.evaluate(). You will sadly not find a great deal of other useful documentation. As for shadow DOM: yeah, you can’t target closed shadow DOMs at all in any way, by design. Fortunately most are open so that it is at least possible to get into them, even if it’s a pain since the normal tools can’t pierce the boundary. —⁂— ¹ If you wanted to only match “click here ” in an immediate child text node of the <a> element, these four work and are equivalent: //article//a[./text()[contains(., "click here ")]]
//article//a[text()[contains(., "click here ")]]
//article//a[contains(./text(), "click here ")]
//article//a[contains(text(), "click here ")]
These will be more efficient than the first-used selector. Where CSS is mostly unaffected by selector complexity because it largely evaluates from right to left, XPath is more likely to be doing full traversals, which will benefit from more specific selectors, hence things like prefixing paths with /html/body, or only evaluating in document.body, in order to skip traversing the head altogether, if that will never match; and you can easily imagine that contains(//a/text(), "…"), which selects text nodes that are children of <a> elements and thus has selected nodes that have a trivial string-value, will be faster than contains(//a, "…"), which will need to stringify every <a> element, entailing further traversal and string construction. Mind you, the differences in all this will mostly be very small in practical terms. |