fix: restore proper class name escaping (#36258)

This commit is contained in:
Dmitry Gozman 2025-06-10 13:44:45 +01:00 committed by GitHub
parent 93dfdbf123
commit 92994a8f85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 24 additions and 4 deletions

View File

@ -578,6 +578,25 @@ function escapeNodeName(node: Node): string {
}
function escapeClassName(className: string): string {
// We are escaping it for document.querySelectorAll, not for usage in CSS file.
return className.replace(/[:\.]/g, char => '\\' + char);
// We are escaping class names for document.querySelectorAll by following CSS.escape() rules.
let result = '';
for (let i = 0; i < className.length; i++)
result += cssEscapeCharacter(className, i);
return result;
}
function cssEscapeCharacter(s: string, i: number): string {
// https://drafts.csswg.org/cssom/#serialize-an-identifier
const c = s.charCodeAt(i);
if (c === 0x0000)
return '\uFFFD';
if ((c >= 0x0001 && c <= 0x001f) ||
(c >= 0x0030 && c <= 0x0039 && (i === 0 || (i === 1 && s.charCodeAt(0) === 0x002d))))
return '\\' + c.toString(16) + ' ';
if (i === 0 && c === 0x002d && s.length === 1)
return '\\' + s.charAt(i);
if (c >= 0x0080 || c === 0x002d || c === 0x005f || (c >= 0x0030 && c <= 0x0039) ||
(c >= 0x0041 && c <= 0x005a) || (c >= 0x0061 && c <= 0x007a))
return s.charAt(i);
return '\\' + s.charAt(i);
}

View File

@ -311,13 +311,14 @@ it.describe('selector generator', () => {
<c>
</c>
</b>
<b class="foo bar.baz">
<b>
<c mark=1></c>
</b>
</div>
<div><b class="foo"></b></div>
`);
expect(await generate(page, 'c[mark="1"]')).toBe('.foo.bar\\.baz > c');
await page.$eval('[mark="1"]', c => c.parentElement.className = 'foo 12.bar.baz[&x]-_?"\'');
expect(await generate(page, 'c[mark="1"]')).toBe(`.foo.\\31 2\\.bar\\.baz\\[\\&x\\]-_\\?\\"\\' > c`);
});
it('should properly join child selectors under nested ordinals', async ({ page }) => {