Commit 140a71fd authored by rbpotter's avatar rbpotter Committed by Commit Bot

Web UI: Allow multiple templates in JS files when replacing expressions

Allow multiple HTML _template strings in JS files when running i18n
template replacements. This is necessary to correctly replace
expressions for bundled JS files generated from Polymer 3 pages, as the
bundled files will generally contain many element definitions, each
including HTML template string. This will be used when optimized
Web UI pages are migrated to use Polymer 3/JS modules (e.g.
chrome://extensions).

Bug: 1004967
Change-Id: I9b5990fc26daf272980ee8d4f5aac688aceffb27
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1834846
Commit-Queue: Rebekah Potter <rbpotter@chromium.org>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#703031}
parent 087a0cd2
......@@ -21,18 +21,18 @@ const char kHtmlTemplateStart[] = "_template: html`";
const size_t kHtmlTemplateStartSize = base::size(kHtmlTemplateStart) - 1;
// Currently only legacy _template: html`...`, syntax is supported.
enum HtmlTemplateType { NONE = 0, LEGACY = 1 };
enum HtmlTemplateType { INVALID = 0, NONE = 1, LEGACY = 2 };
struct TemplatePosition {
HtmlTemplateType type;
base::StringPiece::size_type position;
};
TemplatePosition FindHtmlTemplateStart(const base::StringPiece& source) {
base::StringPiece::size_type found = source.find(kHtmlTemplateStart);
HtmlTemplateType type = found == base::StringPiece::npos ? NONE : LEGACY;
return {type, found + kHtmlTemplateStartSize};
}
struct HtmlTemplate {
base::StringPiece::size_type start;
base::StringPiece::size_type length;
HtmlTemplateType type;
};
TemplatePosition FindHtmlTemplateEnd(const base::StringPiece& source) {
enum State { OPEN, IN_ESCAPE, IN_TICK };
......@@ -62,6 +62,36 @@ TemplatePosition FindHtmlTemplateEnd(const base::StringPiece& source) {
return {NONE, base::StringPiece::npos};
}
HtmlTemplate FindHtmlTemplate(const base::StringPiece& source) {
HtmlTemplate out;
base::StringPiece::size_type found = source.find(kHtmlTemplateStart);
// No template found, return early.
if (found == base::StringPiece::npos) {
out.type = NONE;
return out;
}
out.start = found + kHtmlTemplateStartSize;
TemplatePosition end = FindHtmlTemplateEnd(source.substr(out.start));
// Template is not terminated.
if (end.type == NONE) {
out.type = INVALID;
return out;
}
out.length = end.position;
// Check for a nested template
if (source.substr(out.start, out.length).find(kHtmlTemplateStart) !=
base::StringPiece::npos) {
out.type = INVALID;
return out;
}
out.type = LEGACY;
return out;
}
// Escape quotes and backslashes ('"\).
std::string PolymerParameterEscape(const std::string& in_string) {
std::string out;
......@@ -200,42 +230,45 @@ void TemplateReplacementsFromDictionaryValue(
bool ReplaceTemplateExpressionsInJS(base::StringPiece source,
const TemplateReplacements& replacements,
std::string* formatted) {
// Replacement is only done in JS for the contents of the HTML _template
// string.
TemplatePosition start_result = FindHtmlTemplateStart(source);
if (start_result.type == NONE) {
*formatted = source.as_string();
return true;
}
CHECK(formatted->empty());
base::StringPiece remaining = source;
while (true) {
// Replacement is only done in JS for the contents of HTML _template
// strings.
HtmlTemplate current_template = FindHtmlTemplate(remaining);
// If there was an error finding a template, return false.
if (current_template.type == INVALID)
return false;
// If there are no more templates, copy the remaining JS to the output and
// return true.
if (current_template.type == NONE) {
formatted->append(remaining.as_string());
return true;
}
// Only one template allowed per file.
TemplatePosition second_start_result =
FindHtmlTemplateStart(source.substr(start_result.position));
if (second_start_result.type != NONE)
return false;
// Copy the JS before the template to the output.
formatted->append(remaining.substr(0, current_template.start).as_string());
TemplatePosition end_result =
FindHtmlTemplateEnd(source.substr(start_result.position));
// Retrieve the HTML portion of the source.
base::StringPiece html_template =
remaining.substr(current_template.start, current_template.length);
// Template must be properly terminated.
if (start_result.type != end_result.type)
return false;
// Perform replacements with JS escaping.
std::string formatted_html;
if (!ReplaceTemplateExpressionsInternal(html_template, replacements, true,
&formatted_html)) {
return false;
}
// Retrieve the HTML portion of the source.
base::StringPiece html_template =
source.substr(start_result.position, end_result.position);
// Append the formatted HTML template.
formatted->append(formatted_html);
// Perform replacements with JS escaping.
std::string formatted_html;
if (!ReplaceTemplateExpressionsInternal(html_template, replacements, true,
&formatted_html)) {
return false;
// Increment to the end of the current template.
remaining =
remaining.substr(current_template.start + current_template.length);
}
// Re-assemble the JS file.
*formatted =
source.substr(0, start_result.position).as_string() + formatted_html +
source.substr(start_result.position + end_result.position).as_string();
return true;
}
......
......@@ -275,17 +275,6 @@ TEST(TemplateExpressionsTest, JSReplacementsError) {
// All these cases should fail.
const TestCase kTestCases[] = {
// 2 HTML template strings are not allowed.
{"Polymer({\n"
" _template: html`\n"
" <span>Hello</span>\n"
" `,\n"
" _template: html`\n"
" <div>World</div>\n"
" `,\n"
" is: 'foo-element',\n"
"});",
""},
// Nested templates not allowed.
{"Polymer({\n"
" _template: html`\n"
......@@ -349,4 +338,54 @@ TEST(TemplateExpressionsTest, JSReplacementsError) {
}
}
TEST(TemplateExpressionsTest, JSMultipleTemplates) {
TemplateReplacements substitutions;
substitutions["test"] = "word";
substitutions["5"] = "number";
const TestCase kTestCases[] = {
// Only the second template has substitutions
{"Polymer({\n"
" _template: html`<div>Hello</div>`,\n"
" is: 'foo-element',\n"
"});"
"Polymer({\n"
" _template: html`<div>$i18n{5}$i18n{test}</div>`,\n"
" is: 'bar-element',\n"
"});",
"Polymer({\n"
" _template: html`<div>Hello</div>`,\n"
" is: 'foo-element',\n"
"});"
"Polymer({\n"
" _template: html`<div>numberword</div>`,\n"
" is: 'bar-element',\n"
"});"},
// 2 templates, both with substitutions.
{"Polymer({\n"
" _template: html`<div>$i18n{test}</div>`,\n"
" is: 'foo-element',\n"
"});"
"Polymer({\n"
" _template: html`<div>$i18n{5}</div>`,\n"
" is: 'bar-element',\n"
"});",
"Polymer({\n"
" _template: html`<div>word</div>`,\n"
" is: 'foo-element',\n"
"});"
"Polymer({\n"
" _template: html`<div>number</div>`,\n"
" is: 'bar-element',\n"
"});"}};
std::string formatted;
for (const TestCase test_case : kTestCases) {
ASSERT_TRUE(ReplaceTemplateExpressionsInJS(test_case.js_in, substitutions,
&formatted));
EXPECT_EQ(test_case.expected_out, formatted);
formatted.clear();
}
}
} // namespace ui
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment