diff --git a/tools/chromium_src/lit_mangler/__snapshots__/mangle.test.ts.snap b/tools/chromium_src/lit_mangler/__snapshots__/mangle.test.ts.snap index 9a80cde43c7..fff07824d2f 100644 --- a/tools/chromium_src/lit_mangler/__snapshots__/mangle.test.ts.snap +++ b/tools/chromium_src/lit_mangler/__snapshots__/mangle.test.ts.snap @@ -437,7 +437,7 @@ exports[`mangled files should have up to date snapshots ./chromium_src/chrome/br - -+ ++ -
- --

--
+ -+

-+
+

+
-
- \${this.profilesList_.map((item, index) => html\` +@@ -26,37 +25,24 @@ diff --git a/tools/chromium_src/lit_mangler/lit_mangler.test.ts b/tools/chromium_src/lit_mangler/lit_mangler.test.ts index e479d17d869..c9b73173544 100644 --- a/tools/chromium_src/lit_mangler/lit_mangler.test.ts +++ b/tools/chromium_src/lit_mangler/lit_mangler.test.ts @@ -6,6 +6,21 @@ import { utilsForTest, HTMLTemplateTags } from './lit_mangler' import ts from 'typescript' +const loadTextForTesting = (text: string) => { + const sourceFile = ts.createSourceFile('test.ts', text, ts.ScriptTarget.Latest) + const template: HTMLTemplateTags = { id: 0, text, children: [] } + utilsForTest.resetTemplateId() + utilsForTest.cachePropertyCasesFromText(text) + utilsForTest.setResult(template) + utilsForTest.getTemplateLiterals(sourceFile, sourceFile, template) + utilsForTest.injectPlaceholders(template) + return template +} + +const getOutputForTesting = (template: HTMLTemplateTags) => { + return utilsForTest.restorePropertyCases(utilsForTest.replacePlaceholders(template)) +} + describe('Attribute handling', () => { it('should not break when attributes are unquoted', () => { const template: HTMLTemplateTags = { text: '
Hello
', children: [], id: 0 } @@ -131,7 +146,7 @@ describe('Attribute handling', () => { const template: HTMLTemplateTags = { text: ``, children: [], id: 0 } utilsForTest.setResult(template) - utilsForTest.mangle(() => {}, template) + utilsForTest.mangle(() => { }, template) expect(template.text).toBe(``) }) @@ -148,6 +163,64 @@ describe('Attribute handling', () => { }) }); +describe('Property handling', () => { + it('property names should roundtrip through the mangler', () => { + const original = `
Hello
` + const template = loadTextForTesting(original) + + utilsForTest.mangleAll(e => { + // don't do anything, we're just checking the output is sane + }, () => true) + + expect(getOutputForTesting(template)).toBe(`
Hello
`) + }) + + it('should still lowercase attributes', () => { + const original = `
Hello
` + const template = loadTextForTesting(original) + + utilsForTest.mangleAll(e => { + // don't do anything, we're just checking the output is sane + }, () => true) + + expect(getOutputForTesting(template)).toBe(original) + }) + + it('should handle camelCase properties in children', () => { + const original = `
+ World +
` + const template = loadTextForTesting(original) + utilsForTest.mangle(e => { + // don't do anything, we're just checking the output is sane + }, template) + + expect(getOutputForTesting(template)).toBe(original) + }) + + it('should handle camelCase properties in nested templates', () => { + const original = `
+ \${items.map(item => html\`World\`)} +
` + const template = loadTextForTesting(original) + + // Run a mangler over all templates + utilsForTest.mangleAll(e => { + // don't do anything, we're just checking the output is sane + }, () => true) + + expect(getOutputForTesting(template)).toBe(original) + }) + + it('not running any manglers should not change the output', () => { + const original = `
+ \${items.map(item => html\`World\`)} +
` + const template = loadTextForTesting(original) + expect(getOutputForTesting(template)).toBe(original) + }) +}) + describe('Escaping', () => { it('should escape quotes', () => { const template: HTMLTemplateTags = { text: `
"\${this.foo}"
`, children: [], id: 0 } @@ -163,7 +236,7 @@ describe('Escaping', () => { utilsForTest.mangle(t => { expect(t.querySelector('script')).toBeNull() - }, template) + }, template) expect(template.text).toBe(`
\${""}
`) }) @@ -173,56 +246,56 @@ describe('Escaping', () => { utilsForTest.mangle(t => { expect(t.querySelector('h1')).toBeNull() - }, template) + }, template) expect(template.text).toBe(`
\${"

Jay

"}
`) }) it('should be fine with escaped HTML entities', () => { const template: HTMLTemplateTags = { text: `
<script>alert('hahaha')<script>
`, children: [], id: 0 } utilsForTest.setResult(template) - utilsForTest.mangle(t => {}, template) + utilsForTest.mangle(t => { }, template) expect(template.text).toBe(`
<script>alert('hahaha')<script>
`) }) it('should be fine with escaped HTML entities in interpolated strings', () => { const template: HTMLTemplateTags = { text: `
\${"<script>alert('hahaha')<script>"}
`, children: [], id: 0 } utilsForTest.setResult(template) - utilsForTest.mangle(t => {}, template) + utilsForTest.mangle(t => { }, template) expect(template.text).toBe(`
\${"<script>alert('hahaha')<script>"}
`) }) it('should be fine with escaped HTML entities in interpolated strings', () => { const template: HTMLTemplateTags = { text: `
\${"<script>alert('hahaha')<script>"}
`, children: [], id: 0 } utilsForTest.setResult(template) - utilsForTest.mangle(t => {}, template) + utilsForTest.mangle(t => { }, template) expect(template.text).toBe(`
\${"<script>alert('hahaha')<script>"}
`) }) it('should be fine with unescaped HTML entities in interpolated strings', () => { const template: HTMLTemplateTags = { text: `
\${"`, children: [], id: 0 } utilsForTest.setResult(template) - utilsForTest.mangle(t => {}, template) + utilsForTest.mangle(t => { }, template) expect(template.text).toBe(`
\${"`) }) it('should be fine with unescaped tags in interpolated strings', () => { const template: HTMLTemplateTags = { text: `
\${""}
`, children: [], id: 0 } utilsForTest.setResult(template) - utilsForTest.mangle(t => {}, template) + utilsForTest.mangle(t => { }, template) expect(template.text).toBe(`
\${""}
`) }) it('should be fine with unescaped tags and entities in interpolated strings', () => { const template: HTMLTemplateTags = { text: `
\${"$lt;alert('pwnd')<"}
`, children: [], id: 0 } utilsForTest.setResult(template) - utilsForTest.mangle(t => {}, template) + utilsForTest.mangle(t => { }, template) expect(template.text).toBe(`
\${"$lt;alert('pwnd')<"}
`) }) it('should be fine with quotes in attributes', () => { const template: HTMLTemplateTags = { text: ``, children: [], id: 0 } utilsForTest.setResult(template) - utilsForTest.mangle(t => {}, template) + utilsForTest.mangle(t => { }, template) expect(template.text).toBe(``) }) }) @@ -407,10 +480,12 @@ export function getList(this: string[]) { }) it('should be possible to mangle all matching templates', () => { - const result: HTMLTemplateTags = { id: 0, text: exampleHtml, children: [ - { id: 1, text: '
Hello
', children: [] }, - { id: 2, text: '
Hello
', children: [] }, - ] } + const result: HTMLTemplateTags = { + id: 0, text: exampleHtml, children: [ + { id: 1, text: '
Hello
', children: [] }, + { id: 2, text: '
Hello
', children: [] }, + ] + } utilsForTest.resetTemplateId() utilsForTest.getTemplateLiterals(sourceFile, sourceFile, result) utilsForTest.injectPlaceholders(result) diff --git a/tools/chromium_src/lit_mangler/lit_mangler.ts b/tools/chromium_src/lit_mangler/lit_mangler.ts index f6c59895270..beb977d3ee6 100644 --- a/tools/chromium_src/lit_mangler/lit_mangler.ts +++ b/tools/chromium_src/lit_mangler/lit_mangler.ts @@ -24,10 +24,17 @@ export interface HTMLTemplateTags { let nextId = 1 let isHTML = false +// Properties are camelCase but will be converted to lowercase by the HTML +// parser. We need to keep track of the proper casing so we don't break setting +// properties. +const propertyRegex = /\s\.(\w+)/gm +const propertyCases: Record = {} + const loadRaw = (filepath: string) => { isHTML = filepath.endsWith('.html') const text = readFileSync(filepath, 'utf-8') + cachePropertyCasesFromText(text) const tsFile = ts.createSourceFile(path.basename(filepath), text, ts.ScriptTarget.Latest) return tsFile @@ -61,6 +68,30 @@ const getTemplateLiterals = (source: ts.SourceFile, node: ts.Node, templateTags: }) } +/** + * Stores the property cases from the source text in |propertyCases|. They need + * to be restored when we save the file. + * @param text The source text of the file + */ +const cachePropertyCasesFromText = (text: string) => { + const matches = text.matchAll(propertyRegex) + for (const match of matches) { + propertyCases[match[0].toLocaleLowerCase()] = match[0] + } +} + +/** + * Restores the property cases from |propertyCases| in the source text. + * @param text The source text of the file + * @returns The source text with the property cases restored + */ +const restorePropertyCases = (text: string) => { + for (const [key, value] of Object.entries(propertyCases)) { + text = text.replaceAll(key, value) + } + return text +} + /** * Loads the HTML template literals from a file. * @@ -186,7 +217,7 @@ export const write = (file: string) => { throw new Error('This should only be called after load') } - const text = replacePlaceholders(result) + const text = restorePropertyCases(replacePlaceholders(result)) writeFileSync(file, text) } @@ -299,5 +330,7 @@ export const utilsForTest = { }, getResult: () => result, resetTemplateId: () => nextId = 1, - load + load, + cachePropertyCasesFromText, + restorePropertyCases }