зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
f7b833b7ce
Коммит
f5feaeaf1c
|
@ -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,32 +25,48 @@ 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;
|
||||
|
||||
function waitForDelete() {
|
||||
return new Promise(resolve => {
|
||||
browser.messageManager.addMessageListener(
|
||||
"AboutLogins:DeleteLogin",
|
||||
function onMsg() {
|
||||
deleteLoginMessageReceived = true;
|
||||
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]"
|
||||
".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 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");
|
||||
|
@ -67,8 +83,39 @@ add_task(async function test_login_item() {
|
|||
);
|
||||
confirmButton.click();
|
||||
});
|
||||
}
|
||||
|
||||
let onDeletePromise = waitForDelete();
|
||||
|
||||
await deleteFirstLogin();
|
||||
await onDeletePromise;
|
||||
|
||||
onDeletePromise = waitForDelete();
|
||||
|
||||
await ContentTask.spawn(browser, null, async () => {
|
||||
let loginList = content.document.querySelector("login-list");
|
||||
ok(
|
||||
deleteLoginMessageReceived,
|
||||
"Clicking the delete button should send the AboutLogins:DeleteLogin messsage"
|
||||
!content.document.documentElement.classList.contains("no-logins"),
|
||||
"Should not be in no logins view as there is still one login"
|
||||
);
|
||||
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"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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, here’s 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 you’ve 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
|
||||
|
|
Загрузка…
Ссылка в новой задаче