Bug 1549806 - Implement "No logins" screen for about:logins. r=fluent-reviewers,jaws,flod

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Tim Nguyen 2019-08-02 22:28:02 +00:00
Родитель f7b833b7ce
Коммит f5feaeaf1c
14 изменённых файлов: 361 добавлений и 40 удалений

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

@ -34,8 +34,18 @@ login-list {
grid-area: logins;
}
:root:not(.no-logins) login-intro,
login-item[data-editing="true"] + login-intro,
.no-logins login-item:not([data-editing="true"]) {
display: none;
}
login-intro,
login-item {
grid-area: login;
}
login-item {
max-width: 800px;
}

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

@ -8,11 +8,14 @@
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; img-src data: blob: https://firefoxusercontent.com;"/>
<title data-l10n-id="about-logins-page-title"></title>
<link rel="localization" href="branding/brand.ftl">
<link rel="localization" href="browser/branding/sync-brand.ftl">
<link rel="localization" href="browser/branding/brandings.ftl">
<link rel="localization" href="browser/aboutLogins.ftl">
<script type="module" src="chrome://browser/content/aboutlogins/components/confirmation-dialog.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/fxaccounts-button.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/login-filter.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/login-intro.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/login-item.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/login-list.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/login-list-item.js"></script>
@ -32,6 +35,7 @@
</header>
<login-list></login-list>
<login-item></login-item>
<login-intro></login-intro>
<confirmation-dialog hidden></confirmation-dialog>
<template id="confirmation-dialog-template">
@ -83,8 +87,14 @@
</label>
<span class="count" data-l10n-id="login-list-count" data-l10n-args='{"count": 0}'></span>
</div>
<ol role="listbox" tabindex="0" data-l10n-id="login-list">
</ol>
<!-- This container is to work around bug 1569292 -->
<div class="container">
<ol role="listbox" tabindex="0" data-l10n-id="login-list"></ol>
<div class="intro">
<p data-l10n-id="login-list-intro-title"></p>
<span data-l10n-id="login-list-intro-description"></span>
</div>
</div>
<button class="create-login-button" data-l10n-id="create-login-button"></button>
</template>
@ -95,6 +105,25 @@
</li>
</template>
<template id="login-intro-template">
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-intro.css">
<img class="illustration" src="chrome://browser/content/aboutlogins/icons/intro-illustration.svg"/>
<h1 class="heading" data-l10n-id="login-intro-heading"></h1>
<section>
<p class="description" data-l10n-id="login-intro-description"></p>
<ul>
<li data-l10n-id="login-intro-instruction-fxa"></li>
<li data-l10n-id="login-intro-instruction-fxa-settings"></li>
<li data-l10n-id="login-intro-instruction-faq">
<a data-l10n-name="faq" class="intro-faq-link" href="#"></a>
</li>
</ul>
</section>
</template>
<template id="login-item-template">
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/common.css">
@ -171,8 +200,8 @@
<button role="menuitem" class="menuitem-button menuitem-feedback ghost-button" data-event-name="AboutLoginsOpenFeedback" data-l10n-id="menu-menuitem-feedback"></button>
<button role="menuitem" class="menuitem-button menuitem-faq ghost-button" data-event-name="AboutLoginsOpenFAQ" data-l10n-id="menu-menuitem-faq"></button>
<hr role="separator" class="menuitem-separator"></hr>
<button role="menuitem" class="menuitem-button menuitem-mobile menuitem-mobile-android ghost-button" data-event-name="AboutLoginsOpenMobileAndroid" data-l10n-id="menu-menuitem-download-android"></button>
<button role="menuitem" class="menuitem-button menuitem-mobile menuitem-mobile-ios ghost-button" data-event-name="AboutLoginsOpenMobileIos" data-l10n-id="menu-menuitem-download-iphone"></button>
<button role="menuitem" class="menuitem-button menuitem-mobile menuitem-mobile-android ghost-button" data-event-name="AboutLoginsOpenMobileAndroid" data-l10n-id="menu-menuitem-android-app"></button>
<button role="menuitem" class="menuitem-button menuitem-mobile menuitem-mobile-ios ghost-button" data-event-name="AboutLoginsOpenMobileIos" data-l10n-id="menu-menuitem-iphone-app"></button>
</ul>
</template>
</body>

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

@ -3,12 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let gElements = {};
let numberOfLogins = 0;
document.addEventListener(
"DOMContentLoaded",
() => {
gElements.fxAccountsButton = document.querySelector("fxaccounts-button");
gElements.loginList = document.querySelector("login-list");
gElements.loginIntro = document.querySelector("login-intro");
gElements.loginItem = document.querySelector("login-item");
gElements.loginFilter = document.querySelector("login-filter");
@ -26,15 +28,24 @@ document.addEventListener(
{ once: true }
);
function updateNoLogins() {
document.documentElement.classList.toggle("no-logins", numberOfLogins == 0);
gElements.loginList.classList.toggle("no-logins", numberOfLogins == 0);
}
window.addEventListener("AboutLoginsChromeToContent", event => {
switch (event.detail.messageType) {
case "AllLogins": {
gElements.loginList.setLogins(event.detail.value);
numberOfLogins = event.detail.value.length;
updateNoLogins();
break;
}
case "LoginAdded": {
gElements.loginList.loginAdded(event.detail.value);
gElements.loginItem.loginAdded(event.detail.value);
numberOfLogins++;
updateNoLogins();
break;
}
case "LoginModified": {
@ -45,6 +56,8 @@ window.addEventListener("AboutLoginsChromeToContent", event => {
case "LoginRemoved": {
gElements.loginList.loginRemoved(event.detail.value);
gElements.loginItem.loginRemoved(event.detail.value);
numberOfLogins--;
updateNoLogins();
break;
}
case "SyncState": {

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

@ -0,0 +1,23 @@
/* 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/. */
:host {
padding: 60px;
display: flex;
flex-direction: column;
align-items: center;
}
.heading {
font-size: 1.5em;
}
section {
line-height: 2;
}
.description {
font-weight: 600;
margin-bottom: 0;
}

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

@ -0,0 +1,31 @@
/* 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/. */
export default class LoginIntro extends HTMLElement {
connectedCallback() {
if (this.shadowRoot) {
return;
}
let loginIntroTemplate = document.querySelector("#login-intro-template");
let shadowRoot = this.attachShadow({ mode: "open" });
document.l10n.connectRoot(shadowRoot);
shadowRoot.appendChild(loginIntroTemplate.content.cloneNode(true));
shadowRoot.addEventListener("click", this);
}
handleEvent(event) {
let faqLink = this.shadowRoot.querySelector(".intro-faq-link");
if (event.type == "click" && event.originalTarget == faqLink) {
document.dispatchEvent(
new CustomEvent("AboutLoginsOpenFAQ", {
bubbles: true,
})
);
}
}
}
customElements.define("login-intro", LoginIntro);

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

@ -39,6 +39,27 @@
margin-inline-start: 18px;
}
.container {
display: contents;
}
:host(.no-logins) ol,
:host(:not(.no-logins)) .intro {
display: none;
}
.intro {
text-align: center;
padding: 1em;
max-width: 50ch; /* This should be kept in sync with login-list-item username and title max-width */
flex-grow: 1;
border-bottom: 1px solid var(--in-content-box-border-color);
}
.intro span {
font-size: 0.85em;
}
ol {
margin-top: 0;
margin-bottom: 0;

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

После

Ширина:  |  Высота:  |  Размер: 24 KiB

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

@ -9,6 +9,8 @@ browser.jar:
content/browser/aboutlogins/components/fxaccounts-button.js (content/components/fxaccounts-button.js)
content/browser/aboutlogins/components/login-filter.css (content/components/login-filter.css)
content/browser/aboutlogins/components/login-filter.js (content/components/login-filter.js)
content/browser/aboutlogins/components/login-intro.css (content/components/login-intro.css)
content/browser/aboutlogins/components/login-intro.js (content/components/login-intro.js)
content/browser/aboutlogins/components/login-item.css (content/components/login-item.css)
content/browser/aboutlogins/components/login-item.js (content/components/login-item.js)
content/browser/aboutlogins/components/login-list.css (content/components/login-list.css)
@ -21,6 +23,7 @@ browser.jar:
content/browser/aboutlogins/icons/favicon.svg (content/icons/favicon.svg)
content/browser/aboutlogins/icons/hide-password.svg (content/icons/hide-password.svg)
content/browser/aboutlogins/icons/show-password.svg (content/icons/show-password.svg)
content/browser/aboutlogins/icons/intro-illustration.svg (content/icons/intro-illustration.svg)
content/browser/aboutlogins/aboutLogins.css (content/aboutLogins.css)
content/browser/aboutlogins/aboutLogins.js (content/aboutLogins.js)
content/browser/aboutlogins/aboutLogins.html (content/aboutLogins.html)

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

@ -16,6 +16,7 @@ skip-if = asan || debug
[browser_loginListChanges.js]
[browser_masterPassword.js]
skip-if = (os == 'linux') # bug 1569789
[browser_noLoginsView.js]
[browser_openFiltered.js]
[browser_openImport.js]
skip-if = (os != "win") # import is only available on Windows

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

@ -17,6 +17,14 @@ add_task(async function test_create_login() {
await ContentTask.spawn(browser, null, async () => {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
ok(!loginList._selectedGuid, "should not be a selected guid by default");
ok(
content.document.documentElement.classList.contains("no-logins"),
"Should initially be in no logins view"
);
ok(
loginList.classList.contains("no-logins"),
"login-list should initially be in no logins view"
);
});
let testCases = [
@ -82,9 +90,17 @@ add_task(async function test_create_login() {
browser,
{ expectedCount, originTuple },
async ({ expectedCount: aExpectedCount, originTuple: aOriginTuple }) => {
ok(
!content.document.documentElement.classList.contains("no-logins"),
"Should no longer be in no logins view"
);
let loginList = Cu.waiveXrays(
content.document.querySelector("login-list")
);
ok(
!loginList.classList.contains("no-logins"),
"login-list should no longer be in no logins view"
);
let loginGuid = await ContentTaskUtils.waitForCondition(() => {
return loginList._loginGuidsSortedOrder.find(
guid => loginList._logins[guid].login.origin == aOriginTuple[1]

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

@ -25,50 +25,97 @@ add_task(async function test_show_logins() {
loginList._loginGuidsSortedOrder.includes(logins[1].guid)
);
}, "Waiting for logins to be displayed");
ok(
!content.document.documentElement.classList.contains("no-logins"),
"Should no longer be in no logins view"
);
ok(
!loginList.classList.contains("no-logins"),
"login-list should no longer be in no logins view"
);
ok(loginFound, "Newly added logins should be added to the page");
});
});
add_task(async function test_login_item() {
let browser = gBrowser.selectedBrowser;
let deleteLoginMessageReceived = false;
browser.messageManager.addMessageListener(
"AboutLogins:DeleteLogin",
function onMsg() {
deleteLoginMessageReceived = true;
browser.messageManager.removeMessageListener(
function waitForDelete() {
return new Promise(resolve => {
browser.messageManager.addMessageListener(
"AboutLogins:DeleteLogin",
onMsg
function onMsg() {
resolve(true);
browser.messageManager.removeMessageListener(
"AboutLogins:DeleteLogin",
onMsg
);
}
);
}
);
await ContentTask.spawn(browser, [TEST_LOGIN1, TEST_LOGIN2], async logins => {
});
}
function deleteFirstLogin() {
return ContentTask.spawn(browser, null, async () => {
let loginList = content.document.querySelector("login-list");
let loginListItem = loginList.shadowRoot.querySelector(
".login-list-item[data-guid]:not([hidden])"
);
info("Clicking on the first login");
loginListItem.click();
let loginItem = Cu.waiveXrays(
content.document.querySelector("login-item")
);
let loginItemPopulated = await ContentTaskUtils.waitForCondition(() => {
return loginItem._login.guid == loginListItem.dataset.guid;
}, "Waiting for login item to get populated");
ok(loginItemPopulated, "The login item should get populated");
let deleteButton = loginItem.shadowRoot.querySelector(".delete-button");
deleteButton.click();
let confirmDeleteDialog = Cu.waiveXrays(
content.document.querySelector("confirmation-dialog")
);
let confirmButton = confirmDeleteDialog.shadowRoot.querySelector(
".confirm-button"
);
confirmButton.click();
});
}
let onDeletePromise = waitForDelete();
await deleteFirstLogin();
await onDeletePromise;
onDeletePromise = waitForDelete();
await ContentTask.spawn(browser, null, async () => {
let loginList = content.document.querySelector("login-list");
let loginListItem = loginList.shadowRoot.querySelector(
".login-list-item[data-guid]"
ok(
!content.document.documentElement.classList.contains("no-logins"),
"Should not be in no logins view as there is still one login"
);
info("Clicking on the first login");
loginListItem.click();
let loginItem = Cu.waiveXrays(content.document.querySelector("login-item"));
let loginItemPopulated = await ContentTaskUtils.waitForCondition(() => {
return loginItem._login.guid == loginListItem.dataset.guid;
}, "Waiting for login item to get populated");
ok(loginItemPopulated, "The login item should get populated");
let deleteButton = loginItem.shadowRoot.querySelector(".delete-button");
deleteButton.click();
let confirmDeleteDialog = Cu.waiveXrays(
content.document.querySelector("confirmation-dialog")
ok(
!loginList.classList.contains("no-logins"),
"Should not be in no logins view as there is still one login"
);
});
await deleteFirstLogin();
await onDeletePromise;
await ContentTask.spawn(browser, null, async () => {
let loginList = content.document.querySelector("login-list");
ok(
content.document.documentElement.classList.contains("no-logins"),
"Should be in no logins view as all logins got deleted"
);
ok(
loginList.classList.contains("no-logins"),
"login-list should be in no logins view as all logins got deleted"
);
let confirmButton = confirmDeleteDialog.shadowRoot.querySelector(
".confirm-button"
);
confirmButton.click();
});
ok(
deleteLoginMessageReceived,
"Clicking the delete button should send the AboutLogins:DeleteLogin messsage"
);
});

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

@ -0,0 +1,54 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function setup() {
await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: "about:logins",
});
registerCleanupFunction(() => {
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
});
add_task(async function test_no_logins_class() {
await ContentTask.spawn(gBrowser.selectedBrowser, null, async () => {
function isElementHidden(element) {
return content.getComputedStyle(element).display === "none";
}
let loginList = content.document.querySelector("login-list");
ok(
content.document.documentElement.classList.contains("no-logins"),
"root should be in no logins view"
);
ok(
loginList.classList.contains("no-logins"),
"login-list should be in no logins view"
);
let loginIntro = content.document.querySelector("login-intro");
let loginItem = content.document.querySelector("login-item");
let loginListIntro = loginList.shadowRoot.querySelector(".intro");
let loginListList = loginList.shadowRoot.querySelector("ol");
ok(
!isElementHidden(loginIntro),
"login-intro should be shown in no logins view"
);
ok(
!isElementHidden(loginListIntro),
"login-list intro should be shown in no logins view"
);
ok(
isElementHidden(loginItem),
"login-item should be hidden in no logins view"
);
ok(
isElementHidden(loginListList),
"login-list logins list should be hidden in no logins view"
);
});
});

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

@ -25,8 +25,8 @@ menu-menuitem-preferences =
}
menu-menuitem-feedback = Send Feedback
menu-menuitem-faq = Frequently Asked Questions
menu-menuitem-download-android = Lockwise for Android
menu-menuitem-download-iphone = Lockwise for iPhone and iPad
menu-menuitem-android-app = { -lockwise-brand-short-name } for Android
menu-menuitem-iphone-app = { -lockwise-brand-short-name } for iPhone and iPad
## Login List
@ -41,10 +41,20 @@ login-list-sort-label-text = Sort by:
login-list-name-option = Name (A-Z)
login-list-last-changed-option = Last Modified
login-list-last-used-option = Last Used
login-list-intro-title = No logins found
login-list-intro-description = When you save a password in { -brand-product-name }, it will show up here.
login-list-item-title-new-login = New Login
login-list-item-subtitle-new-login = Enter your login credentials
login-list-item-subtitle-missing-username = (no username)
## Introduction screen
login-intro-heading = Looking for your saved logins? Set up { -sync-brand-short-name }.
login-intro-description = If you saved your logins to { -brand-product-name } on a different device, heres how to get them here:
login-intro-instruction-fxa = Create or sign in to your { -fxaccount-brand-name } on the device where your logins are saved
login-intro-instruction-fxa-settings = Make sure youve selected the Logins checkbox in { -sync-brand-short-name } Settings
login-intro-instruction-faq = Visit { -lockwise-brand-short-name } <a data-l10n-name="faq">frequently asked questions</a> for more help
## Login
login-item-new-login-title = Create New Login

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

@ -10,6 +10,7 @@
-facebook-container-brand-name = Facebook Container
-lockwise-brand-name = Firefox Lockwise
-lockwise-brand-short-name = Lockwise
-monitor-brand-name = Firefox Monitor
-monitor-brand-short-name = Monitor
-pocket-brand-name = Pocket