Skip to content

Anchor relative :has() arguments to their subject - #201

Merged
jdalton merged 2 commits into
dperini:masterfrom
jdalton:fix/relative-has-anchor
Sep 7, 2026
Merged

Anchor relative :has() arguments to their subject#201
jdalton merged 2 commits into
dperini:masterfrom
jdalton:fix/relative-has-anchor

Conversation

@jdalton

@jdalton jdalton commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Change

Anchor every relative :has() argument to its subject, including later sibling arguments. Keep explicit :scope tied to the outer query and restore the previous anchor after success or exceptions.

The private-anchor review identified a real regression. The fix removes the private pseudo-selector entirely: anchors become generated code, and relative compilation uses separate cache keys. Unit and WPT tests reject the reported public and nested queries, preserve quoted attribute values, and cover repeated cached queries and forgiving lists.

Validation

  • 416 Node tests pass, with two inherited expected failures and five skips.
  • All 69 relative-selector Chromium comparisons pass across document, element, and detached contexts.
  • All 44 WPT pages pass. Removed 16 expected failures that this fix resolves.
  • Checks, packed-package tests, coverage, browser tests, and minified WPT pass in CI. Master CI also passes after the squash merge.

The two inherited expected failures cover nested :has() and pseudo-element validation, not private-anchor isolation. See test/relative-has-testing.md for commands.

@jdalton
jdalton marked this pull request as ready for review September 5, 2026 19:42
@jdalton jdalton added the bug label Sep 5, 2026
@jdalton
jdalton force-pushed the fix/relative-has-anchor branch from 8b63327 to 36f19c8 Compare September 6, 2026 02:30

@nrps9909 nrps9909 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 36f19c8ab2b0dc230a7e5194a91507a55d0da3ad against eaf4233b2a7b4f950af7ad56ee0457be8e3d38d6.

The relative anchoring improvement holds in an independent Chromium 151.0.7922.34 comparison: 1,012 observations covering document/scoped/detached selection, repeated cached queries, and element matching. Base has 122 mismatches; head has zero. Cases include later sibling arguments, nested :is / :where, escaped commas, quoted comma values and outer :scope.

For the documented private-anchor isolation TODO, these public queries demonstrate the remaining regression:

nw.select(':-nwsapi-anchor', document);          // []
nw.select('div:has(:-nwsapi-anchor)', document); // []
nw.select('div:not(:-nwsapi-anchor)', document); // all four divs in the fixture

Base and native Chromium throw SyntaxError for all three. Please keep the internal anchor out of public selector compilation, including nested logical selectors and cached queries, and add regressions for those paths before merge. Preserve the valid quoted-value control div:has([data-value=":-nwsapi-anchor"]): it matches the expected parent in both engines, so a blanket substring rejection would introduce another bug.

Validation: full Node suite 34 passed / two expected failures / one browser-only skip; the browser-enabled relative suite passes all 69 reference comparisons (three tests passed / two expected failures). Lint, formatting and packed-package checks pass; the package check runs 11 tests. The two inherited expected failures for nested :has() / pseudo-elements remain as documented. This review confirms the existing isolation TODO with concrete regressions; it does not claim those inherited validation gaps are new.

Validation performed with Codex assistance.

@jdalton
jdalton force-pushed the fix/relative-has-anchor branch from 36f19c8 to 2a3fbae Compare September 6, 2026 11:40
@jdalton

jdalton commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

Confirmed: the private pseudo was accepted by public compilation. Fixed in 8227098. Relative anchors now become generated code rather than selector syntax, with separate cache keys for relative and public compilation. Unit and WPT-harness tests cover all three reported queries, nested logical selectors, repeated cached queries, forgiving lists, and the valid quoted attribute control. Chromium agrees with the new results. The existing 69 relative-selector browser comparisons also pass. Removed 16 WPT expected failures that now pass. Full checks, package tests, and coverage pass locally; CI is running. The two inherited expected failures for nested :has() and pseudo-elements remain separate from this review fix.

@jdalton
jdalton merged commit 3eeafc2 into dperini:master Sep 7, 2026
1 check passed
@jdalton
jdalton deleted the fix/relative-has-anchor branch September 7, 2026 04:16
@nrps9909

nrps9909 commented Sep 7, 2026

Copy link
Copy Markdown

Verified the merged 8227098ca8bf636ba8331bf0717c357bfe7c0b8a: all three public selectors from my original review now throw SyntaxError, while the quoted-attribute control still matches. The original 1,012 relative-selector observations continue to agree with Chromium 151.0.7922.34 on both the previously reviewed and merged versions.

I expanded the isolation checks to document/scoped/detached contexts, three cache-reuse rounds, select and match, nested logical selectors and forgiving lists. Of 504 observations, the earlier reviewed head had 216 mismatches; the merged head has nine, all from one remaining path: matching div:has(:not(:-nwsapi-anchor)) against a childless div returns false instead of throwing. The new regression fixture matches a parent containing a child, so it does not cover this case.

This appears to be the broader deferred argument-validation gap, rather than continued recognition of the private pseudo. With an unrelated :unknown, the same childless-element behavior also reproduces on the previously reviewed head:

const empty = document.createElement('div');
empty.matches('div:has(:not(:unknown))'); // native: SyntaxError
nw.match('div:has(:not(:unknown))', empty); // false on both versions

That broader gap needs validation independent of candidate availability; valid :is() / :where() forgiving lists must remain supported. The original private-selector leak is fixed, with this remaining boundary disclosed separately.

On the documented Node 26 toolchain (26.8.1, pnpm 12.3.4), full Node tests pass 416 cases with two expected failures and five skips; browser tests pass 15 with the same two inherited expected failures; packed-package checks pass 22; all 44 configured WPT/fixture pages pass their expected-result checks. pnpm run check passes. This is the configured manifest, not the complete WPT project, and does not assert a new npm release. AI-assisted follow-up with independently executed checks.

Reproduction artifact: standalone probe, instructions and results. The expanded corpus has 486 observations per version, including empty containers and detached elements: 174 mismatches on the previously reviewed head, 84 on the merged head, 90 resolved observations and zero new failing observations. All 270 valid/forgiving/quoted controls pass on the merged head. The 84 remaining mismatches represent 28 paths repeated across three cache rounds, including select('div:not(:unknown)', emptyContainer) returning [] rather than throwing. These counts use a different corpus from the 504 checks above; they are not additional unique bugs or a proposed parser fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants