[Lit Mangler]: Maintain property cases (#30306)
This commit is contained in:
@@ -437,7 +437,7 @@ exports[`mangled files should have up to date snapshots ./chromium_src/chrome/br
|
||||
</div>
|
||||
- <profile-card-menu .profileState=\\"\${this.profileState}\\"
|
||||
- ?hidden=\\"\${isGlicVersion()}\\">
|
||||
+ <profile-card-menu .profilestate=\\"\${this.profileState}\\" ?hidden=\\"\${isGlicVersion()}\\">
|
||||
+ <profile-card-menu .profileState=\\"\${this.profileState}\\" ?hidden=\\"\${isGlicVersion()}\\">
|
||||
</profile-card-menu>
|
||||
</div>
|
||||
-<cr-tooltip id=\\"gaiaNameTooltip\\" for=\\"gaiaName\\" manual-mode offset=\\"0\\"
|
||||
@@ -459,22 +459,19 @@ exports[`mangled files should have up to date snapshots ./chromium_src/chrome/br
|
||||
"===================================================================
|
||||
--- ../chrome/browser/resources/signin/profile_picker/profile_picker_main_view.html.ts
|
||||
+++ gen/chrome/browser/resources/signin/profile_picker/preprocessed/profile_picker_main_view.html.ts
|
||||
@@ -14,49 +14,35 @@
|
||||
@@ -14,10 +14,9 @@
|
||||
}
|
||||
}()}
|
||||
<div class=\\"flex-container\\">
|
||||
<div class=\\"title-container\\">
|
||||
- <img id=\\"picker-logo\\" @click=\\"\${this.onProductLogoClick_}\\"
|
||||
- src=\\"picker_logo.svg\\" role=\\"presentation\\">
|
||||
- <h1 class=\\"title\\" .innerHTML=\\"\${this.getTitle_()}\\"></h1>
|
||||
- <div class=\\"subtitle\\" .innerHTML=\\"\${this.getSubtitle_()}\\"></div>
|
||||
+ <img id=\\"picker-logo\\" @click=\\"\${this.onProductLogoClick_}\\" src=\\"picker_logo.svg\\" role=\\"presentation\\">
|
||||
+ <h1 class=\\"title\\" .innerhtml=\\"\${this.getTitle_()}\\"></h1>
|
||||
+ <div class=\\"subtitle\\" .innerhtml=\\"\${this.getSubtitle_()}\\"></div>
|
||||
<h1 class=\\"title\\" .innerHTML=\\"\${this.getTitle_()}\\"></h1>
|
||||
<div class=\\"subtitle\\" .innerHTML=\\"\${this.getSubtitle_()}\\"></div>
|
||||
</div>
|
||||
<div id=\\"profilesWrapper\\" ?hidden=\\"\${(this.shouldHideProfilesWrapper_())}\\">
|
||||
<div id=\\"profilesContainer\\" class=\\"custom-scrollbar\\">
|
||||
\${this.profilesList_.map((item, index) => html\`
|
||||
@@ -26,37 +25,24 @@
|
||||
<profile-card class=\\"profile-item\\" .profileState=\\"\${item}\\"
|
||||
data-index=\\"\${index}\\">
|
||||
</profile-card>
|
||||
|
||||
@@ -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: '<div ?hidden=${this.foo}>Hello</div>', children: [], id: 0 }
|
||||
@@ -131,7 +146,7 @@ describe('Attribute handling', () => {
|
||||
const template: HTMLTemplateTags = { text: `<meta name="viewport" content="width=device-width, initial-scale=\${this.scale}">`, children: [], id: 0 }
|
||||
utilsForTest.setResult(template)
|
||||
|
||||
utilsForTest.mangle(() => {}, template)
|
||||
utilsForTest.mangle(() => { }, template)
|
||||
expect(template.text).toBe(`<meta name="viewport" content="width=device-width, initial-scale=\${this.scale}">`)
|
||||
})
|
||||
|
||||
@@ -148,6 +163,64 @@ describe('Attribute handling', () => {
|
||||
})
|
||||
});
|
||||
|
||||
describe('Property handling', () => {
|
||||
it('property names should roundtrip through the mangler', () => {
|
||||
const original = `<div .fooBar="\${this.foo}" .baz="1" .hElLo="2">Hello</div>`
|
||||
const template = loadTextForTesting(original)
|
||||
|
||||
utilsForTest.mangleAll(e => {
|
||||
// don't do anything, we're just checking the output is sane
|
||||
}, () => true)
|
||||
|
||||
expect(getOutputForTesting(template)).toBe(`<div .fooBar="\${this.foo}" .baz="1" .hElLo="2">Hello</div>`)
|
||||
})
|
||||
|
||||
it('should still lowercase attributes', () => {
|
||||
const original = `<div .fooBar="\${this.foo}" .baz="1" .hElLo="2" BAZ="7" ?hiDDen="true">Hello</div>`
|
||||
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 = `<div .fooBar="\${this.foo}" .baz="1" .hElLo="2">
|
||||
<span .propertyName="Hello">World</span>
|
||||
</div>`
|
||||
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 = `<div .fooBar="\${this.foo}" .baz="1" .hElLo="2">
|
||||
\${items.map(item => html\`<span .propertyName="\${item.value}">World</span>\`)}
|
||||
</div>`
|
||||
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 = `<div .fooBar="\${this.foo}" .baz="1" .hElLo="2">
|
||||
\${items.map(item => html\`<span .propertyName="\${item.value}">World</span>\`)}
|
||||
</div>`
|
||||
const template = loadTextForTesting(original)
|
||||
expect(getOutputForTesting(template)).toBe(original)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Escaping', () => {
|
||||
it('should escape quotes', () => {
|
||||
const template: HTMLTemplateTags = { text: `<div attr=\${"foo"}>"\${this.foo}"</div>`, children: [], id: 0 }
|
||||
@@ -163,7 +236,7 @@ describe('Escaping', () => {
|
||||
|
||||
utilsForTest.mangle(t => {
|
||||
expect(t.querySelector('script')).toBeNull()
|
||||
}, template)
|
||||
}, template)
|
||||
expect(template.text).toBe(`<div>\${"<script>alert('pwnd')</script>"}</div>`)
|
||||
})
|
||||
|
||||
@@ -173,56 +246,56 @@ describe('Escaping', () => {
|
||||
|
||||
utilsForTest.mangle(t => {
|
||||
expect(t.querySelector('h1')).toBeNull()
|
||||
}, template)
|
||||
}, template)
|
||||
expect(template.text).toBe(`<div>\${"<h1>Jay</h1>"}</div>`)
|
||||
})
|
||||
|
||||
it('should be fine with escaped HTML entities', () => {
|
||||
const template: HTMLTemplateTags = { text: `<div><script>alert('hahaha')<script></div>`, children: [], id: 0 }
|
||||
utilsForTest.setResult(template)
|
||||
utilsForTest.mangle(t => {}, template)
|
||||
utilsForTest.mangle(t => { }, template)
|
||||
expect(template.text).toBe(`<div><script>alert('hahaha')<script></div>`)
|
||||
})
|
||||
|
||||
it('should be fine with escaped HTML entities in interpolated strings', () => {
|
||||
const template: HTMLTemplateTags = { text: `<div>\${"<script>alert('hahaha')<script>"}</div>`, children: [], id: 0 }
|
||||
utilsForTest.setResult(template)
|
||||
utilsForTest.mangle(t => {}, template)
|
||||
utilsForTest.mangle(t => { }, template)
|
||||
expect(template.text).toBe(`<div>\${"<script>alert('hahaha')<script>"}</div>`)
|
||||
})
|
||||
|
||||
it('should be fine with escaped HTML entities in interpolated strings', () => {
|
||||
const template: HTMLTemplateTags = { text: `<div>\${"<script>alert('hahaha')<script>"}</div>`, children: [], id: 0 }
|
||||
utilsForTest.setResult(template)
|
||||
utilsForTest.mangle(t => {}, template)
|
||||
utilsForTest.mangle(t => { }, template)
|
||||
expect(template.text).toBe(`<div>\${"<script>alert('hahaha')<script>"}</div>`)
|
||||
})
|
||||
|
||||
it('should be fine with unescaped HTML entities in interpolated strings', () => {
|
||||
const template: HTMLTemplateTags = { text: `<div>\${"<foo&"}</div>`, children: [], id: 0 }
|
||||
utilsForTest.setResult(template)
|
||||
utilsForTest.mangle(t => {}, template)
|
||||
utilsForTest.mangle(t => { }, template)
|
||||
expect(template.text).toBe(`<div>\${"<foo&"}</div>`)
|
||||
})
|
||||
|
||||
it('should be fine with unescaped tags in interpolated strings', () => {
|
||||
const template: HTMLTemplateTags = { text: `<div>\${"<script>alert('pwnd')</script>"}</div>`, children: [], id: 0 }
|
||||
utilsForTest.setResult(template)
|
||||
utilsForTest.mangle(t => {}, template)
|
||||
utilsForTest.mangle(t => { }, template)
|
||||
expect(template.text).toBe(`<div>\${"<script>alert('pwnd')</script>"}</div>`)
|
||||
})
|
||||
|
||||
it('should be fine with unescaped tags and entities in interpolated strings', () => {
|
||||
const template: HTMLTemplateTags = { text: `<div>\${"$lt;<script$gt;>alert('pwnd')<</script>>"}</div>`, children: [], id: 0 }
|
||||
utilsForTest.setResult(template)
|
||||
utilsForTest.mangle(t => {}, template)
|
||||
utilsForTest.mangle(t => { }, template)
|
||||
expect(template.text).toBe(`<div>\${"$lt;<script$gt;>alert('pwnd')<</script>>"}</div>`)
|
||||
})
|
||||
|
||||
it('should be fine with quotes in attributes', () => {
|
||||
const template: HTMLTemplateTags = { text: `<img src=\${"foo onload='javascript:alert(\`pwnd\`)'"}>`, children: [], id: 0 }
|
||||
utilsForTest.setResult(template)
|
||||
utilsForTest.mangle(t => {}, template)
|
||||
utilsForTest.mangle(t => { }, template)
|
||||
expect(template.text).toBe(`<img src="\${"foo onload='javascript:alert(\`pwnd\`)'"}">`)
|
||||
})
|
||||
})
|
||||
@@ -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: '<div>Hello</div>', children: [] },
|
||||
{ id: 2, text: '<div>Hello</div>', children: [] },
|
||||
] }
|
||||
const result: HTMLTemplateTags = {
|
||||
id: 0, text: exampleHtml, children: [
|
||||
{ id: 1, text: '<div>Hello</div>', children: [] },
|
||||
{ id: 2, text: '<div>Hello</div>', children: [] },
|
||||
]
|
||||
}
|
||||
utilsForTest.resetTemplateId()
|
||||
utilsForTest.getTemplateLiterals(sourceFile, sourceFile, result)
|
||||
utilsForTest.injectPlaceholders(result)
|
||||
|
||||
@@ -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<string, string> = {}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user