Commit ec3307c5 authored by dpapad's avatar dpapad Committed by Commit Bot

WebUI: Fix $i18nPolymer{} strings with special characters in Polymer3.

Need to double escape backslashes, since the JS parser parses the string
before the Polymer bindings parser kicks in.

Fixed: 1063530
Change-Id: I3a61824703cd5826920e347c82468d1ddf764cac
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2117162Reviewed-by: default avatarRebekah Potter <rbpotter@chromium.org>
Commit-Queue: dpapad <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#756424}
parent 6741607a
...@@ -12,8 +12,6 @@ ...@@ -12,8 +12,6 @@
// #import {isChildVisible, isVisible} from 'chrome://test/test_util.m.js'; // #import {isChildVisible, isVisible} from 'chrome://test/test_util.m.js';
// clang-format on // clang-format on
// TODO(https://crbug.com/1063530): Remove all ignores in this file, once
// i18nPolymer works correctly in Polymer 3.
cr.define('settings_sync_account_control', function() { cr.define('settings_sync_account_control', function() {
suite('SyncAccountControl', function() { suite('SyncAccountControl', function() {
...@@ -172,10 +170,10 @@ cr.define('settings_sync_account_control', function() { ...@@ -172,10 +170,10 @@ cr.define('settings_sync_account_control', function() {
// Avatar row shows the right account. // Avatar row shows the right account.
assertTrue(test_util.isChildVisible(testElement, '#promo-header')); assertTrue(test_util.isChildVisible(testElement, '#promo-header'));
assertTrue(test_util.isChildVisible(testElement, '#avatar-row')); assertTrue(test_util.isChildVisible(testElement, '#avatar-row'));
/* #ignore */ assertTrue(userInfo.textContent.includes('fooName')); assertTrue(userInfo.textContent.includes('fooName'));
/* #ignore */ assertTrue(userInfo.textContent.includes('foo@foo.com')); assertTrue(userInfo.textContent.includes('foo@foo.com'));
/* #ignore */ assertFalse(userInfo.textContent.includes('barName')); assertFalse(userInfo.textContent.includes('barName'));
/* #ignore */ assertFalse(userInfo.textContent.includes('bar@bar.com')); assertFalse(userInfo.textContent.includes('bar@bar.com'));
// Menu contains the right items. // Menu contains the right items.
assertTrue(!!testElement.$$('#menu')); assertTrue(!!testElement.$$('#menu'));
...@@ -213,13 +211,10 @@ cr.define('settings_sync_account_control', function() { ...@@ -213,13 +211,10 @@ cr.define('settings_sync_account_control', function() {
// email. // email.
items[1].click(); items[1].click();
Polymer.dom.flush(); Polymer.dom.flush();
/* #ignore */ assertFalse( assertFalse(userInfo.textContent.includes('fooName'));
/* #ignore */ userInfo.textContent.includes('fooName')); assertFalse(userInfo.textContent.includes('foo@foo.com'));
/* #ignore */ assertFalse( assertTrue(userInfo.textContent.includes('barName'));
/* #ignore */ userInfo.textContent.includes('foo@foo.com')); assertTrue(userInfo.textContent.includes('bar@bar.com'));
/* #ignore */ assertTrue(userInfo.textContent.includes('barName'));
/* #ignore */ assertTrue(
/* #ignore */ userInfo.textContent.includes('bar@bar.com'));
assertTrue(test_util.isVisible(syncButton)); assertTrue(test_util.isVisible(syncButton));
browserProxy.resetResolver('startSyncingWithEmail'); browserProxy.resetResolver('startSyncingWithEmail');
...@@ -263,10 +258,10 @@ cr.define('settings_sync_account_control', function() { ...@@ -263,10 +258,10 @@ cr.define('settings_sync_account_control', function() {
} }
const userInfo = testElement.$$('#user-info'); const userInfo = testElement.$$('#user-info');
/* #ignore */ assertTrue(userInfo.textContent.includes('barName')); assertTrue(userInfo.textContent.includes('barName'));
/* #ignore */ assertTrue(userInfo.textContent.includes('bar@bar.com')); assertTrue(userInfo.textContent.includes('bar@bar.com'));
/* #ignore */ assertFalse(userInfo.textContent.includes('fooName')); assertFalse(userInfo.textContent.includes('fooName'));
/* #ignore */ assertFalse(userInfo.textContent.includes('foo@foo.com')); assertFalse(userInfo.textContent.includes('foo@foo.com'));
assertFalse(test_util.isChildVisible(testElement, '#sync-button')); assertFalse(test_util.isChildVisible(testElement, '#sync-button'));
assertTrue(test_util.isChildVisible(testElement, '#turn-off')); assertTrue(test_util.isChildVisible(testElement, '#turn-off'));
...@@ -298,9 +293,9 @@ cr.define('settings_sync_account_control', function() { ...@@ -298,9 +293,9 @@ cr.define('settings_sync_account_control', function() {
assertTrue(!!testElement.$$('[icon="settings:sync-problem"]')); assertTrue(!!testElement.$$('[icon="settings:sync-problem"]'));
let displayedText = let displayedText =
userInfo.querySelector('span:not([hidden])').textContent; userInfo.querySelector('span:not([hidden])').textContent;
/* #ignore */ assertFalse(displayedText.includes('barName')); assertFalse(displayedText.includes('barName'));
/* #ignore */ assertFalse(displayedText.includes('fooName')); assertFalse(displayedText.includes('fooName'));
/* #ignore */ assertTrue(displayedText.includes('Sync isn\'t working')); assertTrue(displayedText.includes('Sync isn\'t working'));
// The sync error button is shown to resolve the error. // The sync error button is shown to resolve the error.
assertTrue(test_util.isChildVisible(testElement, '#sync-error-button')); assertTrue(test_util.isChildVisible(testElement, '#sync-error-button'));
...@@ -317,9 +312,9 @@ cr.define('settings_sync_account_control', function() { ...@@ -317,9 +312,9 @@ cr.define('settings_sync_account_control', function() {
.classList.contains('sync-paused')); .classList.contains('sync-paused'));
assertTrue(!!testElement.$$('[icon=\'settings:sync-disabled\']')); assertTrue(!!testElement.$$('[icon=\'settings:sync-disabled\']'));
displayedText = userInfo.querySelector('span:not([hidden])').textContent; displayedText = userInfo.querySelector('span:not([hidden])').textContent;
/* #ignore */ assertFalse(displayedText.includes('barName')); assertFalse(displayedText.includes('barName'));
/* #ignore */ assertFalse(displayedText.includes('fooName')); assertFalse(displayedText.includes('fooName'));
/* #ignore */ assertTrue(displayedText.includes('Sync is paused')); assertTrue(displayedText.includes('Sync is paused'));
// The sync error button is shown to resolve the error. // The sync error button is shown to resolve the error.
assertTrue(test_util.isChildVisible(testElement, '#sync-error-button')); assertTrue(test_util.isChildVisible(testElement, '#sync-error-button'));
...@@ -337,9 +332,9 @@ cr.define('settings_sync_account_control', function() { ...@@ -337,9 +332,9 @@ cr.define('settings_sync_account_control', function() {
.classList.contains('sync-disabled')); .classList.contains('sync-disabled'));
assertTrue(!!testElement.$$('[icon=\'cr:sync\']')); assertTrue(!!testElement.$$('[icon=\'cr:sync\']'));
displayedText = userInfo.querySelector('span:not([hidden])').textContent; displayedText = userInfo.querySelector('span:not([hidden])').textContent;
/* #ignore */ assertFalse(displayedText.includes('barName')); assertFalse(displayedText.includes('barName'));
/* #ignore */ assertFalse(displayedText.includes('fooName')); assertFalse(displayedText.includes('fooName'));
/* #ignore */ assertTrue(displayedText.includes('Sync disabled')); assertTrue(displayedText.includes('Sync disabled'));
assertFalse(test_util.isChildVisible(testElement, '#sync-error-button')); assertFalse(test_util.isChildVisible(testElement, '#sync-error-button'));
testElement.syncStatus = { testElement.syncStatus = {
...@@ -355,9 +350,9 @@ cr.define('settings_sync_account_control', function() { ...@@ -355,9 +350,9 @@ cr.define('settings_sync_account_control', function() {
.classList.contains('sync-problem')); .classList.contains('sync-problem'));
assertTrue(!!testElement.$$('[icon="settings:sync-problem"]')); assertTrue(!!testElement.$$('[icon="settings:sync-problem"]'));
displayedText = userInfo.querySelector('span:not([hidden])').textContent; displayedText = userInfo.querySelector('span:not([hidden])').textContent;
/* #ignore */ assertFalse(displayedText.includes('barName')); assertFalse(displayedText.includes('barName'));
/* #ignore */ assertFalse(displayedText.includes('fooName')); assertFalse(displayedText.includes('fooName'));
/* #ignore */ assertTrue(displayedText.includes('Sync isn\'t working')); assertTrue(displayedText.includes('Sync isn\'t working'));
testElement.syncStatus = { testElement.syncStatus = {
firstSetupInProgress: false, firstSetupInProgress: false,
...@@ -373,11 +368,10 @@ cr.define('settings_sync_account_control', function() { ...@@ -373,11 +368,10 @@ cr.define('settings_sync_account_control', function() {
.classList.contains('sync-problem')); .classList.contains('sync-problem'));
assertTrue(!!testElement.$$('[icon="settings:sync-problem"]')); assertTrue(!!testElement.$$('[icon="settings:sync-problem"]'));
displayedText = userInfo.querySelector('span:not([hidden])').textContent; displayedText = userInfo.querySelector('span:not([hidden])').textContent;
/* #ignore */ assertFalse(displayedText.includes('barName')); assertFalse(displayedText.includes('barName'));
/* #ignore */ assertFalse(displayedText.includes('fooName')); assertFalse(displayedText.includes('fooName'));
/* #ignore */ assertFalse(displayedText.includes('Sync isn\'t working')); assertFalse(displayedText.includes('Sync isn\'t working'));
/* #ignore */ assertTrue( assertTrue(displayedText.includes('Error syncing passwords'));
/* #ignore */ displayedText.includes('Error syncing passwords'));
// The sync error button is shown to resolve the error. // The sync error button is shown to resolve the error.
assertTrue(test_util.isChildVisible(testElement, '#sync-error-button')); assertTrue(test_util.isChildVisible(testElement, '#sync-error-button'));
assertTrue(test_util.isChildVisible(testElement, '#turn-off')); assertTrue(test_util.isChildVisible(testElement, '#turn-off'));
...@@ -398,9 +392,8 @@ cr.define('settings_sync_account_control', function() { ...@@ -398,9 +392,8 @@ cr.define('settings_sync_account_control', function() {
const userInfo = testElement.$$('#user-info'); const userInfo = testElement.$$('#user-info');
const setupButtons = testElement.$$('#setup-buttons'); const setupButtons = testElement.$$('#setup-buttons');
/* #ignore */ assertTrue(userInfo.textContent.includes('barName')); assertTrue(userInfo.textContent.includes('barName'));
/* #ignore */ assertTrue( assertTrue(userInfo.textContent.includes('Setup in progress...'));
/* #ignore */ userInfo.textContent.includes('Setup in progress...'));
assertTrue(test_util.isVisible(setupButtons)); assertTrue(test_util.isVisible(setupButtons));
}); });
......
...@@ -65,22 +65,23 @@ HtmlTemplate FindHtmlTemplate(const base::StringPiece& source) { ...@@ -65,22 +65,23 @@ HtmlTemplate FindHtmlTemplate(const base::StringPiece& source) {
} }
// Escape quotes and backslashes ('"\). // Escape quotes and backslashes ('"\).
std::string PolymerParameterEscape(const std::string& in_string) { std::string PolymerParameterEscape(const std::string& in_string,
bool is_javascript) {
std::string out; std::string out;
out.reserve(in_string.size() * 2); out.reserve(in_string.size() * 2);
for (const char c : in_string) { for (const char c : in_string) {
switch (c) { switch (c) {
case '\\': case '\\':
out.append("\\\\"); out.append(is_javascript ? R"(\\\\)" : R"(\\)");
break; break;
case '\'': case '\'':
out.append("\\'"); out.append(is_javascript ? R"(\\')" : R"(\')");
break; break;
case '"': case '"':
out.append("&quot;"); out.append("&quot;");
break; break;
case ',': case ',':
out.append("\\\\,"); out.append(is_javascript ? R"(\\,)" : R"(\,)");
break; break;
default: default:
out += c; out += c;
...@@ -182,7 +183,7 @@ bool ReplaceTemplateExpressionsInternal( ...@@ -182,7 +183,7 @@ bool ReplaceTemplateExpressionsInternal(
// Pass the replacement through unchanged. // Pass the replacement through unchanged.
} else if (context == "Polymer") { } else if (context == "Polymer") {
// Escape quotes and backslash for '$i18nPolymer{}' use (i.e. quoted). // Escape quotes and backslash for '$i18nPolymer{}' use (i.e. quoted).
replacement = PolymerParameterEscape(replacement); replacement = PolymerParameterEscape(replacement, is_javascript);
} else { } else {
CHECK(false) << "Unknown context " << context; CHECK(false) << "Unknown context " << context;
} }
......
...@@ -76,14 +76,14 @@ TEST(TemplateExpressionsTest, ReplaceTemplateExpressionsPolymerQuoting) { ...@@ -76,14 +76,14 @@ TEST(TemplateExpressionsTest, ReplaceTemplateExpressionsPolymerQuoting) {
TEST(TemplateExpressionsTest, ReplaceTemplateExpressionsPolymerMixed) { TEST(TemplateExpressionsTest, ReplaceTemplateExpressionsPolymerMixed) {
static TemplateReplacements substitutions; static TemplateReplacements substitutions;
substitutions["punctuationSample"] = "a\"b'c<d>e&f,g"; substitutions["punctuationSample"] = R"(a"b'c<d>e&f,g)";
substitutions["htmlSample"] = "<div>hello</div>"; substitutions["htmlSample"] = "<div>hello</div>";
EXPECT_EQ("a&quot;b\\'c<d>e&f\\\\,g", EXPECT_EQ(R"(a&quot;b\'c<d>e&f\,g)",
ReplaceTemplateExpressions("$i18nPolymer{punctuationSample}", ReplaceTemplateExpressions("$i18nPolymer{punctuationSample}",
substitutions)); substitutions));
EXPECT_EQ("<div>hello</div>", ReplaceTemplateExpressions( EXPECT_EQ("<div>hello</div>", ReplaceTemplateExpressions(
"$i18nPolymer{htmlSample}", substitutions)); "$i18nPolymer{htmlSample}", substitutions));
EXPECT_EQ("multiple: <div>hello</div>, a&quot;b\\'c<d>e&f\\\\,g.", EXPECT_EQ(R"(multiple: <div>hello</div>, a&quot;b\'c<d>e&f\,g.)",
ReplaceTemplateExpressions("multiple: $i18nPolymer{htmlSample}, " ReplaceTemplateExpressions("multiple: $i18nPolymer{htmlSample}, "
"$i18nPolymer{punctuationSample}.", "$i18nPolymer{punctuationSample}.",
substitutions)); substitutions));
...@@ -561,4 +561,33 @@ TEST(TemplateExpressionsTest, JSMultipleTemplates) { ...@@ -561,4 +561,33 @@ TEST(TemplateExpressionsTest, JSMultipleTemplates) {
} }
} }
TEST(TemplateExpressionsTest, ReplaceTemplateExpressionsPolymerQuotingJS) {
static TemplateReplacements substitutions;
substitutions["quotesAndCommas"] = R"(don't "moo", they said)";
substitutions["backslashes"] = R"(\ \\ \n \r \t \b \f \0)";
const TestCase kTestCases[] = {
// Case: quotesAndCommas
{R"(<!--_html_template_start_-->
<div>[[Call('$i18nPolymer{quotesAndCommas}')]]</div>
<!--_html_template_end_-->)",
R"(<!--_html_template_start_-->
<div>[[Call('don\\'t &quot;moo&quot;\\, they said')]]</div>
<!--_html_template_end_-->)"},
// Case: backslashes
{R"(<!--_html_template_start_-->
<div>[[Call('$i18nPolymer{backslashes}')]]</div>
<!--_html_template_end_-->)",
R"(<!--_html_template_start_-->
<div>[[Call('\\\\ \\\\\\\\ \\\\n \\\\r \\\\t \\\\b \\\\f \\\\0')]]</div>
<!--_html_template_end_-->)"},
};
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 } // 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