From 92994a8f8539ef12a9315b3eeb51ee70ffc0422d Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 10 Jun 2025 13:44:45 +0100 Subject: [PATCH] fix: restore proper class name escaping (#36258) --- packages/injected/src/selectorGenerator.ts | 23 ++++++++++++++++++++-- tests/library/selector-generator.spec.ts | 5 +++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/injected/src/selectorGenerator.ts b/packages/injected/src/selectorGenerator.ts index 8410d7dc7d..a7dad6644f 100644 --- a/packages/injected/src/selectorGenerator.ts +++ b/packages/injected/src/selectorGenerator.ts @@ -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); } diff --git a/tests/library/selector-generator.spec.ts b/tests/library/selector-generator.spec.ts index 68f0d2fccf..a78fb15a5c 100644 --- a/tests/library/selector-generator.spec.ts +++ b/tests/library/selector-generator.spec.ts @@ -311,13 +311,14 @@ it.describe('selector generator', () => { - +
`); - 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 }) => {