Skip to content

Commit 1ece063

Browse files
committed
initial support for the :has() pseudo selector added
1 parent 65b116c commit 1ece063

File tree

3 files changed

+430
-9
lines changed

3 files changed

+430
-9
lines changed

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
<action type="update" dev="rbri">
1212
Upgrade Apache commons-lang3 to 3.19.0.
1313
</action>
14+
<action type="add" dev="rbri">
15+
Initial support for the :has() pseudo selector added.
16+
</action>
1417
<action type="add" dev="rbri">
1518
Initial support for the :where() pseudo selector added.
1619
</action>

src/main/java/org/htmlunit/css/CssStyleSheet.java

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
import org.htmlunit.cssparser.parser.condition.AttributeCondition;
7171
import org.htmlunit.cssparser.parser.condition.Condition;
7272
import org.htmlunit.cssparser.parser.condition.Condition.ConditionType;
73+
import org.htmlunit.cssparser.parser.condition.HasPseudoClassCondition;
7374
import org.htmlunit.cssparser.parser.condition.IsPseudoClassCondition;
7475
import org.htmlunit.cssparser.parser.condition.NotPseudoClassCondition;
7576
import org.htmlunit.cssparser.parser.condition.WherePseudoClassCondition;
@@ -80,6 +81,7 @@
8081
import org.htmlunit.cssparser.parser.selector.ElementSelector;
8182
import org.htmlunit.cssparser.parser.selector.GeneralAdjacentSelector;
8283
import org.htmlunit.cssparser.parser.selector.PseudoElementSelector;
84+
import org.htmlunit.cssparser.parser.selector.RelativeSelector;
8385
import org.htmlunit.cssparser.parser.selector.Selector;
8486
import org.htmlunit.cssparser.parser.selector.Selector.SelectorType;
8587
import org.htmlunit.cssparser.parser.selector.SelectorList;
@@ -100,7 +102,6 @@
100102
import org.htmlunit.html.HtmlTextArea;
101103
import org.htmlunit.html.ValidatableElement;
102104
import org.htmlunit.javascript.host.css.MediaList;
103-
import org.htmlunit.javascript.host.dom.Document;
104105
import org.htmlunit.util.MimeType;
105106
import org.htmlunit.util.StringUtils;
106107
import org.htmlunit.util.UrlUtils;
@@ -509,6 +510,53 @@ && selects(browserVersion, gas.getSelector(), (DomElement) prev1,
509510
}
510511
return false;
511512

513+
case RELATIVE_SELECTOR:
514+
final RelativeSelector rs = (RelativeSelector) selector;
515+
516+
switch (rs.getCombinator()) {
517+
case DESCENDANT_COMBINATOR:
518+
for (final DomElement descendant : element.getDomElementDescendants()) {
519+
if (selects(browserVersion, rs.getSelector(), descendant, pseudoElement,
520+
fromQuerySelectorAll, throwOnSyntax)) {
521+
return true;
522+
}
523+
}
524+
return false;
525+
526+
case CHILD_COMBINATOR:
527+
for (final DomElement child : element.getChildElements()) {
528+
if (selects(browserVersion, rs.getSelector(), child, pseudoElement,
529+
fromQuerySelectorAll, throwOnSyntax)) {
530+
return true;
531+
}
532+
}
533+
return false;
534+
535+
case NEXT_SIBLING_COMBINATOR:
536+
final DomElement nextSibling = element.getNextElementSibling();
537+
if (selects(browserVersion, rs.getSelector(), nextSibling, pseudoElement,
538+
fromQuerySelectorAll, throwOnSyntax)) {
539+
return true;
540+
}
541+
return false;
542+
543+
case SUBSEQUENT_SIBLING_COMBINATOR:
544+
for (DomNode n = element.getNextSibling(); n != null; n = n.getNextSibling()) {
545+
if (n instanceof DomElement
546+
&& selects(browserVersion, rs.getSelector(), (DomElement) n, pseudoElement,
547+
fromQuerySelectorAll, throwOnSyntax)) {
548+
return true;
549+
}
550+
}
551+
return false;
552+
553+
default:
554+
if (LOG.isErrorEnabled()) {
555+
LOG.error("Unknown CSS combinator '" + rs.getCombinator() + "'.");
556+
}
557+
return false;
558+
}
559+
512560
default:
513561
if (LOG.isErrorEnabled()) {
514562
LOG.error("Unknown CSS selector type '" + selector.getSelectorType() + "'.");
@@ -659,6 +707,16 @@ static boolean selects(final BrowserVersion browserVersion,
659707
}
660708
return false;
661709

710+
case HAS_PSEUDO_CLASS_CONDITION:
711+
final HasPseudoClassCondition hasPseudoCondition = (HasPseudoClassCondition) condition;
712+
final SelectorList hasSelectorList = hasPseudoCondition.getSelectors();
713+
for (final Selector selector : hasSelectorList) {
714+
if (selects(browserVersion, selector, element, null, fromQuerySelectorAll, throwOnSyntax)) {
715+
return true;
716+
}
717+
}
718+
return false;
719+
662720
case PSEUDO_CLASS_CONDITION:
663721
return selectsPseudoClass(browserVersion, condition, element);
664722

@@ -1074,6 +1132,7 @@ private static String toString(final InputSource source) {
10741132
* Validates the list of selectors.
10751133
* @param selectorList the selectors
10761134
* @param domNode the dom node the query should work on
1135+
*
10771136
* @throws CSSException if a selector is invalid
10781137
*/
10791138
public static void validateSelectors(final SelectorList selectorList, final DomNode domNode) throws CSSException {
@@ -1084,9 +1143,6 @@ public static void validateSelectors(final SelectorList selectorList, final DomN
10841143
}
10851144
}
10861145

1087-
/**
1088-
* @param documentMode see {@link Document#getDocumentMode()}
1089-
*/
10901146
private static boolean isValidSelector(final Selector selector, final DomNode domNode) {
10911147
switch (selector.getSelectorType()) {
10921148
case ELEMENT_NODE_SELECTOR:
@@ -1124,9 +1180,6 @@ private static boolean isValidSelector(final Selector selector, final DomNode do
11241180
}
11251181
}
11261182

1127-
/**
1128-
* @param documentMode see {@link Document#getDocumentMode()}
1129-
*/
11301183
private static boolean isValidCondition(final Condition condition, final DomNode domNode) {
11311184
switch (condition.getConditionType()) {
11321185
case ATTRIBUTE_CONDITION:
@@ -1141,8 +1194,35 @@ private static boolean isValidCondition(final Condition condition, final DomNode
11411194
return true;
11421195
case NOT_PSEUDO_CLASS_CONDITION:
11431196
final NotPseudoClassCondition notPseudoCondition = (NotPseudoClassCondition) condition;
1144-
final SelectorList selectorList = notPseudoCondition.getSelectors();
1145-
for (final Selector selector : selectorList) {
1197+
final SelectorList notSelectorList = notPseudoCondition.getSelectors();
1198+
for (final Selector selector : notSelectorList) {
1199+
if (!isValidSelector(selector, domNode)) {
1200+
return false;
1201+
}
1202+
}
1203+
return true;
1204+
case IS_PSEUDO_CLASS_CONDITION:
1205+
final IsPseudoClassCondition isPseudoCondition = (IsPseudoClassCondition) condition;
1206+
final SelectorList isSelectorList = isPseudoCondition.getSelectors();
1207+
for (final Selector selector : isSelectorList) {
1208+
if (!isValidSelector(selector, domNode)) {
1209+
return false;
1210+
}
1211+
}
1212+
return true;
1213+
case WHERE_PSEUDO_CLASS_CONDITION:
1214+
final WherePseudoClassCondition wherePseudoCondition = (WherePseudoClassCondition) condition;
1215+
final SelectorList whereSelectorList = wherePseudoCondition.getSelectors();
1216+
for (final Selector selector : whereSelectorList) {
1217+
if (!isValidSelector(selector, domNode)) {
1218+
return false;
1219+
}
1220+
}
1221+
return true;
1222+
case HAS_PSEUDO_CLASS_CONDITION:
1223+
final HasPseudoClassCondition hasPseudoCondition = (HasPseudoClassCondition) condition;
1224+
final SelectorList hasSelectorList = hasPseudoCondition.getSelectors();
1225+
for (final Selector selector : hasSelectorList) {
11461226
if (!isValidSelector(selector, domNode)) {
11471227
return false;
11481228
}

0 commit comments

Comments
 (0)