Skip to content

fix(utils): update isShadowRoot to use spec-compliant custom element regex - #5059

Merged
straker merged 2 commits into
dequelabs:developfrom
bwyard:fix/5030-shadow-root-unicode-regex
May 26, 2026
Merged

fix(utils): update isShadowRoot to use spec-compliant custom element regex#5059
straker merged 2 commits into
dequelabs:developfrom
bwyard:fix/5030-shadow-root-unicode-regex

Conversation

@bwyard

@bwyard bwyard commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

The custom element name regex in isShadowRoot only matches ASCII characters. The HTML spec allows a broader set of Unicode characters in Potential Custom Element Names (PCEN), so valid elements like <café-menu> and <math-π> were being rejected as shadow root candidates.

Two changes:

  • Replace the ASCII-only regex with the spec-compliant PCEN character ranges
  • Add an explicit reserved names check (annotation-xml, color-profile, font-face, etc.): these match the pattern but are explicitly excluded by the spec

Tests added for both cases.

Closes #5030

@bwyard
bwyard requested a review from a team as a code owner April 5, 2026 05:40
@CLAassistant

CLAassistant commented Apr 5, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@straker straker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the pr. A suggestions about the regex.

Comment thread lib/core/utils/is-shadow-root.js Outdated
straker added a commit that referenced this pull request May 7, 2026
…ne if custom-element nodeName is valid (#5097)

Realized that I needed this for
#5041 as when trying to
create the external script and having `get-element-internals` use
`isHTMLElement` would result in the external file having all the the
different standards objects as well as a ton of unnecessary utils
functions. Thus I needed a way to determine if the element is a custom
element to short circuit the lookup in `get-element-internals` without
relying on `isHTMLElement`. This implements what we discussed in
#5059 into it's own function
so it can be used in both places.
@bwyard

bwyard commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

Yes it is. Im starting to turn the corner think I'll be able to do it this coming weekend. Had a Really bad health spat

@straker

straker commented May 21, 2026

Copy link
Copy Markdown
Contributor

@bwyard hope you get better! Just a heads up, I created #5097 as I needed it in another piece of code we've been working on. So you should be able to call that function in place of the regex we have in isShadowRoot.

…alidCustomElementName

isShadowRoot was using an ASCII-only regex to identify custom elements,
rejecting valid names containing Unicode characters permitted by the
HTML spec (e.g. café-menu, math-π) and not excluding the reserved names
defined in the custom element name spec.

Delegates the check to the isValidCustomElementName util introduced in
dequelabs#5097, which implements the full spec-compliant validation. The
reservedNames list and custom regex are removed from this file.

Tests added for reserved name rejection and Unicode name acceptance.

Closes dequelabs#5030
@bwyard
bwyard force-pushed the fix/5030-shadow-root-unicode-regex branch from c7b45df to 73b4818 Compare May 21, 2026 23:46

@straker straker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for updating that!

Reviewed for security.

@straker
straker merged commit edc6ce2 into dequelabs:develop May 26, 2026
22 of 23 checks passed
WilcoFiers added a commit that referenced this pull request Jun 1, 2026
##
[4.12.0](v4.11.4...v4.12.0)
(2026-06-01)

### Features

- add gather-internals.js external script
([#5099](#5099))
([c61d58b](c61d58b)),
closes [#5080](#5080)
- **aria-allowed/prohibited-attr, aria-required-parent/children:**
partially support element internals role
([#5080](#5080))
([417b48a](417b48a)),
closes [#5039](#5039)
[#4259](#4259)
- **axe.externalAPIs:** add public api for setting elementInternal data
([#5105](#5105))
([63bab8f](63bab8f))
- **core:** expose normalizeRunOptions
([#4998](#4998))
([b8e6a59](b8e6a59))
- expose axe.resetLocale() to restore the default locale
([#5108](#5108))
([c2b5292](c2b5292)),
closes [#5107](#5107)
- **getRules:** include rule enabled state in returned objects
([#5118](#5118))
([75bf772](75bf772)),
closes [#5116](#5116)
- **list,listitem:** support element internals role
([#5119](#5119))
([7d9d696](7d9d696))
- **new-rule:** check that aria-tab have an accessible name
([#5001](#5001))
([0d4e4e7](0d4e4e7)),
closes [#4842](#4842)
- **rules:** deprecate landmark-complementary-is-top-level rules
([#4992](#4992))
([9e09139](9e09139)),
closes [#4950](#4950)
- **utils:** add `getElementInternals` function
([#5077](#5077))
([1c15f82](1c15f82))

### Bug Fixes

- **aria-allowed-attr:** restrict br and wbr elements to aria-hidden
only ([#4974](#4974))
([c6245e7](c6245e7))
- **aria-conditional-attr:** add support for radio
([#5100](#5100))
([8223c98](8223c98))
- **aria-valid-attr-value:** handle multiple aria-errormessage IDs
([#4973](#4973))
([0489e30](0489e30))
- **aria:** prevent getOwnedVirtual from returning duplicate nodes
([#4987](#4987))
([48ca955](48ca955)),
closes [#4840](#4840)
- **commons/text:** exclude natively hidden elements from
aria-labelledby accessible name
([#5076](#5076))
([ea7202c](ea7202c)),
closes [#4704](#4704)
- **DqElement:** avoid calling constructors with cloneNode
([#5013](#5013))
([0281fa1](0281fa1))
- **existing-rule:** aria-busy now shows an error message for a use with
unallowed children
([#5017](#5017))
([2067b87](2067b87))
- **helpUrl:** ensure axe.configure always updates the help URLs
([#5114](#5114))
([c4f60ff](c4f60ff))
- **label-content-name-mismatch:** match visible text with aria-label
and exclude invisible text
([#5096](#5096))
([3a012a1](3a012a1))
- **locale:** ensure all subtags are correctly set
([#5112](#5112))
([13005ed](13005ed))
- **scrollable-region-focusable:** clarify the issue is in safari
([#4995](#4995))
([4ec5211](4ec5211)),
closes
[WebKit#190870](https://github.com/dequelabs/WebKit/issues/190870)
[WebKit#277290](https://github.com/dequelabs/WebKit/issues/277290)
- **scrollable-region-focusable:** do not fail scroll areas when all
content is visible without scrolling
([#4993](#4993))
([838707a](838707a))
- **target-size:** determine offset using clientRects if target is
display:inline
([#5012](#5012))
([a4b8091](a4b8091))
- **target-size:** ignore position: fixed elements that are offscreen
when page is scrolled
([#5066](#5066))
([1229a6e](1229a6e)),
closes [#5065](#5065)
- **target-size:** ignore widgets that are inline with other inline
elements ([#5000](#5000))
([a8dd81b](a8dd81b))
- **utils/getAncestry:** escape node name
([#5079](#5079))
([d1fabaa](d1fabaa)),
closes [#5078](#5078)
- **utils:** Add null check to parseCrossOriginStylesheet, closes
[#5074](#5074)
([#5075](#5075))
([f12ef32](f12ef32))
- **utils:** update isShadowRoot to use spec-compliant custom element
regex ([#5059](#5059))
([edc6ce2](edc6ce2)),
closes [#5030](#5030)

This PR was opened by a robot 🤖 🎉
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regex for allowed shadow hosts is inaccurate

4 participants