Bug 1549808: Add favicons to login list items. r=jaws,fluent-reviewers,flod

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
lesleynorton 2019-08-05 22:10:10 +00:00
Родитель 31fe00df28
Коммит ce8bda124b
11 изменённых файлов: 182 добавлений и 13 удалений

Просмотреть файл

@ -154,6 +154,7 @@ let LEGACY_ACTORS = {
"AboutLogins:LoginModified",
"AboutLogins:LoginRemoved",
"AboutLogins:MasterPasswordResponse",
"AboutLogins:SendFavicons",
"AboutLogins:SyncState",
"AboutLogins:UpdateBreaches",
],

Просмотреть файл

@ -170,6 +170,10 @@ class AboutLoginsChild extends ActorChild {
if (masterPasswordPromise) {
masterPasswordPromise.resolve(message.data);
}
break;
case "AboutLogins:SendFavicons":
this.sendToContent("SendFavicons", message.data);
break;
case "AboutLogins:SyncState":
this.sendToContent("SyncState", message.data);
break;

Просмотреть файл

@ -35,6 +35,12 @@ ChromeUtils.defineModuleGetter(
"resource://services-sync/UIState.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm"
);
XPCOMUtils.defineLazyGetter(this, "log", () => {
return LoginHelper.createLogger("AboutLoginsParent");
});
@ -262,16 +268,19 @@ var AboutLoginsParent = {
messageManager.sendAsyncMessage("AboutLogins:SyncState", syncState);
this.updatePasswordSyncNotificationState();
if (!BREACH_ALERTS_ENABLED) {
return;
if (BREACH_ALERTS_ENABLED) {
const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
logins
);
messageManager.sendAsyncMessage(
"AboutLogins:UpdateBreaches",
breachesByLoginGUID
);
}
const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
logins
);
messageManager.sendAsyncMessage(
"AboutLogins:UpdateBreaches",
breachesByLoginGUID
"AboutLogins:SendFavicons",
await this.getAllFavicons(logins)
);
} catch (ex) {
if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
@ -374,6 +383,38 @@ var AboutLoginsParent = {
}
},
async getFavicon(login) {
try {
const faviconData = await PlacesUtils.promiseFaviconData(login.hostname);
return {
faviconData,
guid: login.guid,
};
} catch (ex) {
return null;
}
},
async getAllFavicons(logins) {
let favicons = await Promise.all(
logins.map(login => this.getFavicon(login))
);
let vanillaFavicons = {};
for (let favicon of favicons) {
if (!favicon) {
continue;
}
try {
vanillaFavicons[favicon.guid] = {
data: favicon.faviconData.data,
dataLen: favicon.faviconData.dataLen,
mimeType: favicon.faviconData.mimeType,
};
} catch (ex) {}
}
return vanillaFavicons;
},
showMasterPasswordLoginNotifications() {
this.showNotifications({
id: MASTER_PASSWORD_NOTIFICATION_ID,

Просмотреть файл

@ -99,9 +99,14 @@
</template>
<template id="login-list-item-template">
<li class="login-list-item" role="option">
<span class="title"></span>
<span class="username"></span>
<li class="login-list-item">
<div class="favicon-wrapper">
<img class="favicon" src="" alt=""/>
</div>
<div>
<span class="title"></span>
<span class="username"></span>
</div>
</li>
</template>
@ -133,6 +138,9 @@
<a class="breach-alert-link" data-l10n-id="breach-alert-link" href="#" rel="noopener noreferer" target="_blank"></a>
</div>
<div class="header">
<div class="login-item-favicon-wrapper">
<img class="login-item-favicon" src="" alt=""/>
</div>
<h2 class="title">
<span class="login-item-title"></span>
<span class="new-login-title" data-l10n-id="login-item-new-login-title"></span>

Просмотреть файл

@ -60,6 +60,10 @@ window.addEventListener("AboutLoginsChromeToContent", event => {
updateNoLogins();
break;
}
case "SendFavicons": {
gElements.loginList.addFavicons(event.detail.value);
break;
}
case "SyncState": {
gElements.fxAccountsButton.updateState(event.detail.value);
break;

Просмотреть файл

@ -41,6 +41,7 @@
.header {
display: flex;
align-items: center;
border-bottom: 1px solid var(--in-content-box-border-color);
margin-bottom: 40px;
}
@ -190,6 +191,24 @@
box-shadow: 0 0 0 4px var(--in-content-border-active-shadow);
}
.login-item-favicon {
width: 24px;
}
.login-item-favicon-wrapper {
margin-inline-end: 12px;
height: 24px;
width: 24px;
background-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
background-repeat: no-repeat;
background-size: contain;
-moz-context-properties: fill;
}
.login-item-favicon-wrapper.hide-default-favicon {
background-image: none;
}
.breach-alert {
border-radius: 8px;
border: 1px solid var(--in-content-border-color);
@ -232,5 +251,9 @@ a.breach-alert-link {
box-shadow: 0 2px 8px 0 rgba(249,249,250,0.1);
color: #0C0C0D;
}
.login-item-favicon-wrapper {
fill: var(--in-content-border-hover);
}
}
}

Просмотреть файл

@ -54,6 +54,10 @@ export default class LoginItem extends HTMLElement {
this._saveChangesButton = this.shadowRoot.querySelector(
".save-changes-button"
);
this._favicon = this.shadowRoot.querySelector(".login-item-favicon");
this._faviconWrapper = this.shadowRoot.querySelector(
".login-item-favicon-wrapper"
);
this._title = this.shadowRoot.querySelector(".login-item-title");
this._timeCreated = this.shadowRoot.querySelector(".time-created");
this._timeChanged = this.shadowRoot.querySelector(".time-changed");
@ -73,6 +77,7 @@ export default class LoginItem extends HTMLElement {
this._originInput.addEventListener("click", this);
this._revealCheckbox.addEventListener("click", this);
window.addEventListener("AboutLoginsInitialLoginSelected", this);
window.addEventListener("AboutLoginsLoadInitialFavicon", this);
window.addEventListener("AboutLoginsLoginSelected", this);
window.addEventListener("AboutLoginsShowBlankLogin", this);
}
@ -97,6 +102,19 @@ export default class LoginItem extends HTMLElement {
timeUsed: this._login.timeLastUsed || "",
});
if (this._login.faviconDataURI) {
this._faviconWrapper.classList.add("hide-default-favicon");
this._favicon.src = this._login.faviconDataURI;
document.l10n.setAttributes(this._favicon, "login-favicon", {
title: this._login.title,
});
this._favicon.hidden = false;
} else {
// reset the src and alt attributes if the currently selected favicon doesn't have a favicon
this._favicon.hidden = true;
this._faviconWrapper.classList.remove("hide-default-favicon");
}
this._title.textContent = this._login.title;
this._originInput.defaultValue = this._login.origin || "";
this._usernameInput.defaultValue = this._login.username || "";
@ -121,6 +139,10 @@ export default class LoginItem extends HTMLElement {
this.setLogin(event.detail, { skipFocusChange: true });
break;
}
case "AboutLoginsLoadInitialFavicon": {
this.render();
break;
}
case "AboutLoginsLoginSelected": {
let login = event.detail;
if (this.hasPendingChanges()) {

Просмотреть файл

@ -22,6 +22,9 @@ export default class LoginListItemFactory {
static update(listItem, login) {
let title = listItem.querySelector(".title");
let username = listItem.querySelector(".username");
let favicon = listItem.querySelector(".favicon");
let faviconWrapper = listItem.querySelector(".favicon-wrapper");
if (!login.guid) {
listItem.id = "new-login-list-item";
document.l10n.setAttributes(title, "login-list-item-title-new-login");
@ -53,5 +56,13 @@ export default class LoginListItemFactory {
"login-list-item-subtitle-missing-username"
);
}
if (login.faviconDataURI) {
faviconWrapper.classList.add("hide-default-favicon");
favicon.src = login.faviconDataURI;
document.l10n.setAttributes(favicon, "login-favicon", {
title: login.title,
});
}
}
}

Просмотреть файл

@ -13,7 +13,7 @@
.meta {
display: flex;
align-items: center;
padding: 5px 18px;
padding: 5px 16px;
border-bottom: 1px solid var(--in-content-box-border-color);
background-color: var(--in-content-box-background);
color: var(--in-content-deemphasized-text);
@ -74,10 +74,11 @@ ol {
}
.login-list-item {
display: block;
display: flex;
align-items: center;
padding: 10px;
padding-inline-end: 18px;
padding-inline-start: 14px;
padding-inline-start: 12px;
border-inline-start: 4px solid transparent;
border-bottom: 1px solid var(--in-content-box-border-color);
}
@ -112,6 +113,23 @@ ol {
overflow: hidden;
}
.favicon-wrapper {
height: 16px;
width: 16px;
background-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
background-repeat: no-repeat;
margin-inline-end: 12px;
-moz-context-properties: fill;
}
.favicon-wrapper.hide-default-favicon {
background-image: none;
}
.favicon {
width: 16px;
}
.username {
font-size: 0.85em;
color: var(--in-content-deemphasized-text);
@ -135,5 +153,9 @@ ol {
.login-list-item.breached {
fill: var(--red-60);
}
.favicon-wrapper {
fill: var(--in-content-border-hover);
}
}
}

Просмотреть файл

@ -216,6 +216,33 @@ export default class LoginList extends HTMLElement {
}
}
addFavicons(favicons) {
for (let favicon in favicons) {
let { login, listItem } = this._logins[favicon];
favicon = favicons[favicon];
if (favicon && login.title) {
favicon.uri = btoa(
new Uint8Array(favicon.data).reduce(
(data, byte) => data + String.fromCharCode(byte),
""
)
);
let faviconDataURI = `data:${favicon.mimeType};base64,${favicon.uri}`;
login.faviconDataURI = faviconDataURI;
}
LoginListItemFactory.update(listItem, login);
}
let selectedListItem = this._list.querySelector(
".login-list-item.selected"
);
if (selectedListItem) {
window.dispatchEvent(
new CustomEvent("AboutLoginsLoadInitialFavicon", {})
);
}
this.render();
}
/**
* @param {Map} breachesByLoginGUID A Map of breaches by login GUIDs used
* for displaying breached login indicators.

Просмотреть файл

@ -9,6 +9,12 @@ login-filter =
create-login-button = Create New Login
# This string is used as alternative text for favicon images.
# Variables:
# $title (String) - The title of the website associated with the favicon.
login-favicon =
.alt = Favicon for { $title }
fxaccounts-sign-in-text = Get your passwords on your other devices
fxaccounts-sign-in-button = Sign in to { -sync-brand-short-name }