Commit 4b250f6e authored by Jared Wein's avatar Jared Wein
Browse files

Bug 1549809 - Reduce duplication of reflected Fluent strings. r=MattN,Pike

This patch also fixes a bug where the custom elements wouldn't display their localized text if the attributes were updated before the custom element was defined.

Differential Revision: https://phabricator.services.mozilla.com/D30800

--HG--
extra : moz-landing-system : lando
parent f7081578
......@@ -9,6 +9,7 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';"/>
<title data-l10n-id="about-logins-page-title"></title>
<link rel="localization" href="browser/aboutLogins.ftl">
<script defer="defer" src="chrome://browser/content/aboutlogins/components/reflected-fluent-element.js"></script>
<script defer="defer" src="chrome://browser/content/aboutlogins/components/login-filter.js"></script>
<script defer="defer" src="chrome://browser/content/aboutlogins/components/login-item.js"></script>
<script defer="defer" src="chrome://browser/content/aboutlogins/components/login-list.js"></script>
......
......@@ -2,7 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
class LoginFilter extends HTMLElement {
/* globals ReflectedFluentElement */
class LoginFilter extends ReflectedFluentElement {
connectedCallback() {
if (this.children.length) {
return;
......@@ -12,6 +14,8 @@ class LoginFilter extends HTMLElement {
this.attachShadow({mode: "open"})
.appendChild(loginFilterTemplate.content.cloneNode(true));
this.reflectFluentStrings();
this.addEventListener("input", this);
}
......@@ -28,22 +32,21 @@ class LoginFilter extends HTMLElement {
}
}
static get observedAttributes() {
static get reflectedFluentIDs() {
return ["placeholder"];
}
/* Fluent doesn't handle localizing into Shadow DOM yet so strings
need to get reflected in to their targeted element. */
attributeChangedCallback(attr, oldValue, newValue) {
if (!this.shadowRoot) {
return;
}
static get observedAttributes() {
return this.reflectedFluentIDs;
}
switch (attr) {
case "placeholder":
this.shadowRoot.querySelector("input").placeholder = newValue;
break;
handleSpecialCaseFluentString(attrName) {
if (attrName != "placeholder") {
return false;
}
this.shadowRoot.querySelector("input").placeholder = this.getAttribute(attrName);
return true;
}
}
customElements.define("login-filter", LoginFilter);
......@@ -2,7 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
class LoginItem extends HTMLElement {
/* globals ReflectedFluentElement */
class LoginItem extends ReflectedFluentElement {
constructor() {
super();
this._login = {};
......@@ -18,6 +20,8 @@ class LoginItem extends HTMLElement {
this.attachShadow({mode: "open"})
.appendChild(loginItemTemplate.content.cloneNode(true));
this.reflectFluentStrings();
for (let selector of [
".delete-button",
".save-changes-button",
......@@ -32,7 +36,7 @@ class LoginItem extends HTMLElement {
this.render();
}
static get observedAttributes() {
static get reflectedFluentIDs() {
return [
"cancel-button",
"delete-button",
......@@ -46,17 +50,8 @@ class LoginItem extends HTMLElement {
];
}
/* Fluent doesn't handle localizing into Shadow DOM yet so strings
need to get reflected in to their targeted element. */
attributeChangedCallback(attr, oldValue, newValue) {
if (!this.shadowRoot) {
return;
}
// Strings that are reflected to their shadowed element are assigned
// to an attribute name that matches a className on the element.
let shadowedElement = this.shadowRoot.querySelector("." + attr);
shadowedElement.textContent = newValue;
static get observedAttributes() {
return this.reflectedFluentIDs;
}
render() {
......
......@@ -2,9 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* globals LoginListItem */
/* globals ReflectedFluentElement, LoginListItem */
class LoginList extends HTMLElement {
class LoginList extends ReflectedFluentElement {
constructor() {
super();
this._logins = [];
......@@ -18,6 +18,9 @@ class LoginList extends HTMLElement {
let loginListTemplate = document.querySelector("#login-list-template");
this.attachShadow({mode: "open"})
.appendChild(loginListTemplate.content.cloneNode(true));
this.reflectFluentStrings();
this.render();
window.addEventListener("AboutLoginsLoginSelected", this);
......@@ -71,22 +74,12 @@ class LoginList extends HTMLElement {
}
}
static get observedAttributes() {
static get reflectedFluentIDs() {
return ["count"];
}
/* Fluent doesn't handle localizing into Shadow DOM yet so strings
need to get reflected in to their targeted element. */
attributeChangedCallback(attr, oldValue, newValue) {
if (!this.shadowRoot) {
return;
}
switch (attr) {
case "count":
this.shadowRoot.querySelector(".count").textContent = newValue;
break;
}
static get observedAttributes() {
return this.reflectedFluentIDs;
}
setLogins(logins) {
......
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
class ReflectedFluentElement extends HTMLElement {
_isReflectedAttributePresent(attr) {
return this.constructor.reflectedFluentIDs.includes(attr.name);
}
/* This function should be called to apply any localized strings that Fluent
may have applied to the element before the custom element was defined. */
reflectFluentStrings() {
for (let reflectedFluentID of this.constructor.reflectedFluentIDs) {
if (this.hasAttribute(reflectedFluentID)) {
if (this.handleSpecialCaseFluentString &&
this.handleSpecialCaseFluentString(reflectedFluentID)) {
continue;
}
let attrValue = this.getAttribute(reflectedFluentID);
// Strings that are reflected to their shadowed element are assigned
// to an attribute name that matches a className on the element.
let shadowedElement = this.shadowRoot.querySelector("." + reflectedFluentID);
shadowedElement.textContent = attrValue;
}
}
}
/* Fluent doesn't handle localizing into Shadow DOM yet so strings
need to get reflected in to their targeted element. */
attributeChangedCallback(attr, oldValue, newValue) {
if (!this.shadowRoot) {
return;
}
// Don't respond to attribute changes that aren't related to locale text.
if (!this.constructor.reflectedFluentIDs.includes(attr)) {
return;
}
if (this.handleSpecialCaseFluentString &&
this.handleSpecialCaseFluentString(attr)) {
return;
}
// Strings that are reflected to their shadowed element are assigned
// to an attribute name that matches a className on the element.
let shadowedElement = this.shadowRoot.querySelector("." + attr);
shadowedElement.textContent = newValue;
}
}
customElements.define("reflected-fluent-element", ReflectedFluentElement);
......@@ -11,6 +11,7 @@ browser.jar:
content/browser/aboutlogins/components/login-list.js (content/components/login-list.js)
content/browser/aboutlogins/components/login-list-item.css (content/components/login-list-item.css)
content/browser/aboutlogins/components/login-list-item.js (content/components/login-list-item.js)
content/browser/aboutlogins/components/reflected-fluent-element.js (content/components/reflected-fluent-element.js)
content/browser/aboutlogins/aboutLogins.css (content/aboutLogins.css)
content/browser/aboutlogins/aboutLogins.js (content/aboutLogins.js)
content/browser/aboutlogins/aboutLogins.html (content/aboutLogins.html)
......@@ -5,8 +5,10 @@ support-files =
../../content/components/login-item.js
../../content/components/login-list.js
../../content/components/login-list-item.js
../../content/components/reflected-fluent-element.js
aboutlogins_common.js
[test_login_filter.html]
[test_login_item.html]
[test_login_list.html]
[test_reflected_fluent_element.html]
......@@ -8,6 +8,7 @@ Test the login-filter component
<title>Test the login-filter component</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="reflected-fluent-element.js"></script>
<script src="login-filter.js"></script>
<script src="login-list-item.js"></script>
<script src="login-list.js"></script>
......
......@@ -7,6 +7,7 @@ Test the login-item component
<meta charset="utf-8">
<title>Test the login-item component</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="reflected-fluent-element.js"></script>
<script src="login-item.js"></script>
<script src="aboutlogins_common.js"></script>
......
......@@ -7,6 +7,7 @@ Test the login-list component
<meta charset="utf-8">
<title>Test the login-list component</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="reflected-fluent-element.js"></script>
<script src="login-list-item.js"></script>
<script src="login-list.js"></script>
<script src="aboutlogins_common.js"></script>
......
<!DOCTYPE HTML>
<html>
<!--
Test the reflected-fluent-element component
-->
<head>
<meta charset="utf-8">
<title>Test the reflected-fluent-element component</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="reflected-fluent-element.js"></script>
<script src="aboutlogins_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display">
</p>
<div id="content" style="display: none">
<iframe id="templateFrame" src="aboutLogins.html"
sandbox="allow-same-origin"></iframe>
</div>
<pre id="test">
</pre>
<script>
/** Test the reflected-fluent-element component **/
const TEST_STRINGS = {
loginFilter: {
placeholder: "Sample placeholder",
},
loginItem: {
"cancel-button": "Cancel",
"delete-button": "Delete",
"hostname-label": "Website Address",
"password-label": "Password",
"save-changes-button": "Save Changes",
// See stubFluentL10n for the following three
"time-created": "",
"time-changed": "",
"time-used": "",
"username-label": "Username",
},
};
let gLoginFilter;
let gLoginItem;
add_task(async function setup() {
stubFluentL10n({
"time-created": "timeCreated",
"time-changed": "timeChanged",
"time-used": "timeUsed",
});
let displayEl = document.getElementById("display");
// Create and append the login-filter element before its template
// is cloned the custom element defined.
gLoginFilter = document.createElement("login-filter");
gLoginFilter.setAttribute("placeholder", TEST_STRINGS.loginFilter.placeholder);
displayEl.appendChild(gLoginFilter);
// ... and do the same with the login-item.
gLoginItem = document.createElement("login-item");
for (let attrKey of Object.keys(TEST_STRINGS.loginItem)) {
gLoginItem.setAttribute(attrKey, TEST_STRINGS.loginItem[attrKey]);
}
displayEl.appendChild(gLoginItem);
let templateFrame = document.getElementById("templateFrame");
importDependencies(templateFrame, displayEl);
// The script needs to be inserted after the element and template are appended
// to match the environment of the locale text being applied before the custom
// element is defined.
for (let scriptSrc of ["login-filter.js", "login-item.js", "login-list.js"]) {
let scriptEl = document.createElement("script");
scriptEl.setAttribute("src", scriptSrc);
document.head.appendChild(scriptEl);
}
});
add_task(async function test_placeholder_on_login_filter() {
ok(gLoginFilter, "loginFilter exists");
await SimpleTest.promiseWaitForCondition(() => !!gLoginFilter.shadowRoot, "Wait for shadowRoot");
is(gLoginFilter.shadowRoot.querySelector("input").placeholder,
TEST_STRINGS.loginFilter.placeholder,
"Placeholder text should be present when set before the element is defined");
});
add_task(async function test_login_item() {
ok(gLoginItem, "loginItem exists");
await SimpleTest.promiseWaitForCondition(() => !!gLoginItem.shadowRoot, "Wait for shadowRoot");
for (let attrKey of Object.keys(TEST_STRINGS.loginItem)) {
let selector = "." + attrKey;
is(gLoginItem.shadowRoot.querySelector(selector).textContent,
TEST_STRINGS.loginItem[attrKey],
selector + " textContent should be present when set before the element is defined");
}
});
add_task(async function test_attribute_changed_callback() {
let displayEl = document.getElementById("display");
let loginList = document.createElement("login-list");
displayEl.appendChild(loginList);
await SimpleTest.promiseWaitForCondition(() => !!loginList.shadowRoot, "Wait for element to get templated");
loginList.setAttribute("count", "1234");
await SimpleTest.promiseWaitForCondition(() => loginList.shadowRoot.querySelector(".count").textContent.includes("1234"),
"Wait for text to get localized");
ok(loginList.shadowRoot.querySelector(".count").textContent.includes("1234"),
"The count attribute should be inherited by the .count span");
});
</script>
</body>
</html>
Supports Markdown
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