diff --git a/NEWS b/NEWS index facc4a1d0031..c7bcb9a040fd 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,10 @@ PHP NEWS - DOM: . Fixed bug GH-25268 (NULL php_libxml_ref_obj dereference in dom_objects_store_clone_obj when cloning Dom\Notation). (David Carlier) + . Fixed CSS selectors checking only the nearest element for a descendant or + subsequent-sibling combinator after a compound selector that starts with + :is(), :where(), :not(), :has() or :nth-child(An+B of S), by merging + lexbor/lexbor@f6c1d06. (phcorp) - MBString: . Fixed stack overflow in mb_check_encoding() and mb_convert_encoding() with diff --git a/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c b/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c index 3f16ccfa5f5a..19e1c1523c0b 100644 --- a/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c +++ b/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c @@ -189,6 +189,10 @@ static lxb_selectors_entry_t * lxb_selectors_state_find(lxb_selectors_t *selectors, lxb_selectors_entry_t *entry); +static lxb_selectors_entry_t * +lxb_selectors_state_find_forward(lxb_selectors_t *selectors, + lxb_selectors_entry_t *entry); + static lxb_selectors_entry_t * lxb_selectors_state_found_check(lxb_selectors_t *selectors, lxb_selectors_entry_t *entry); @@ -673,6 +677,29 @@ lxb_inline void lxb_selectors_switch_to_not_found(lxb_selectors_t *selectors, lxb_selectors_nested_t *current) { + lxb_css_selector_combinator_t combinator; + + combinator = current->entry->combinator; + + /* + * A nested selector rejected this candidate. Continue the combinator + * before backtracking to another entry. + */ + if (combinator == LXB_CSS_SELECTOR_COMBINATOR_DESCENDANT + || combinator == LXB_CSS_SELECTOR_COMBINATOR_FOLLOWING + || (current->forward + && combinator == LXB_CSS_SELECTOR_COMBINATOR_CHILD)) + { + if (current->forward) { + selectors->state = lxb_selectors_state_find_forward; + } + else { + selectors->state = lxb_selectors_state_find; + } + + return; + } + if (current->forward) { selectors->state = lxb_selectors_state_not_found_forward; } diff --git a/ext/dom/tests/modern/css_selectors/lexbor436.phpt b/ext/dom/tests/modern/css_selectors/lexbor436.phpt new file mode 100644 index 000000000000..95ab6e765f1d --- /dev/null +++ b/ext/dom/tests/modern/css_selectors/lexbor436.phpt @@ -0,0 +1,43 @@ +--TEST-- +Lexbor #436 (combinator after a compound-leading selector-taking pseudo-class checks only one element) +--EXTENSIONS-- +dom +--FILE-- +
' +); +$nested = Dom\HTMLDocument::createFromString( + '' +); + +$cases = [ + [$siblings, 'div > :is(y) ~ *'], + [$siblings, 'div > :where(y) ~ *'], + [$nested, ':is(ul) li'], + [$nested, ':where(ul) li'], + [$nested, ':not(ol) li'], + [$nested, ':has(> li > ol) li'], + [$nested, ':nth-child(1 of ul) li'], + [$nested, 'body :is(li) li'], +]; + +foreach ($cases as [$dom, $selector]) { + $result = []; + foreach ($dom->querySelectorAll($selector) as $node) { + $result[] = $node->localName . '(' . trim($node->firstChild?->textContent ?? '') . ')'; + } + echo $selector, ': ', implode(' ', $result), "\n"; +} + +?> +--EXPECT-- +div > :is(y) ~ *: z() w() +div > :where(y) ~ *: z() w() +:is(ul) li: li(a) li(b) +:where(ul) li: li(a) li(b) +:not(ol) li: li(a) li(b) +:has(> li > ol) li: li(a) li(b) +:nth-child(1 of ul) li: li(a) li(b) +body :is(li) li: li(b)