Merge mozilla-central to inbound. a=merge CLOSED TREE
|
@ -118,7 +118,7 @@ media/gmp-clearkey/0.1/openaes/.*
|
|||
media/kiss_fft/.*
|
||||
media/libaom/.*
|
||||
media/libcubeb/.*
|
||||
media/libdav1d/version.h
|
||||
media/libdav1d/.*
|
||||
media/libjpeg/.*
|
||||
media/libmkv/.*
|
||||
media/libnestegg/.*
|
||||
|
|
|
@ -176,6 +176,9 @@ jobs:
|
|||
mozilla-esr60:
|
||||
- {weekday: 'Monday', hour: 10, minute: 0}
|
||||
- {weekday: 'Thursday', hour: 10, minute: 0}
|
||||
mozilla-esr68:
|
||||
- {weekday: 'Monday', hour: 10, minute: 0}
|
||||
- {weekday: 'Thursday', hour: 10, minute: 0}
|
||||
|
||||
- name: pipfile-update
|
||||
job:
|
||||
|
|
|
@ -39,7 +39,13 @@
|
|||
// change list style type
|
||||
var list = getNode("list");
|
||||
list.setAttribute("style", "list-style-type: disc;");
|
||||
getComputedStyle(list, "").color; // make style processing sync
|
||||
|
||||
// Flush both the style change and the resulting layout change.
|
||||
// Flushing style on its own is not sufficient, because that can
|
||||
// leave frames marked with NS_FRAME_IS_DIRTY, which will cause
|
||||
// nsTextFrame::GetRenderedText to report the text of a text
|
||||
// frame is empty.
|
||||
list.offsetWidth; // flush layout (which also flushes style)
|
||||
|
||||
testName("li_start", kDiscBulletText + "list start");
|
||||
testName("li_end", kDiscBulletText + "list end");
|
||||
|
|
|
@ -125,6 +125,7 @@ class ContextMenuChild extends JSWindowActorChild {
|
|||
}
|
||||
break;
|
||||
case "pictureinpicture":
|
||||
Services.telemetry.keyedScalarAdd("pictureinpicture.opened_method", "contextmenu", 1);
|
||||
let event = new this.contentWindow.CustomEvent("MozTogglePictureInPicture", {
|
||||
bubbles: true,
|
||||
}, this.contentWindow);
|
||||
|
|
|
@ -971,19 +971,25 @@ var ContentBlocking = {
|
|||
this.identityPopupMultiView.goBack();
|
||||
},
|
||||
|
||||
submitBreakageReport() {
|
||||
onSubmitBreakageReportClicked() {
|
||||
this.identityPopup.hidePopup();
|
||||
|
||||
let comments = document.getElementById(
|
||||
"identity-popup-breakageReportView-collection-comments");
|
||||
this.submitBreakageReport(this.reportURI, comments);
|
||||
},
|
||||
|
||||
submitBreakageReport(uri, commentsTextarea) {
|
||||
let reportEndpoint = Services.prefs.getStringPref(this.PREF_REPORT_BREAKAGE_URL);
|
||||
if (!reportEndpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
let formData = new FormData();
|
||||
formData.set("title", this.reportURI.host);
|
||||
formData.set("title", uri.host);
|
||||
|
||||
// Leave the ? at the end of the URL to signify that this URL had its query stripped.
|
||||
let urlWithoutQuery = this.reportURI.asciiSpec.replace(this.reportURI.query, "");
|
||||
let urlWithoutQuery = uri.asciiSpec.replace(uri.query, "");
|
||||
let body = `Full URL: ${urlWithoutQuery}\n`;
|
||||
body += `userAgent: ${navigator.userAgent}\n`;
|
||||
|
||||
|
@ -995,12 +1001,12 @@ var ContentBlocking = {
|
|||
body += `network.http.referer.defaultPolicy.pbmode: ${Services.prefs.getIntPref("network.http.referer.defaultPolicy.pbmode")}\n`;
|
||||
body += `${ThirdPartyCookies.PREF_ENABLED}: ${Services.prefs.getIntPref(ThirdPartyCookies.PREF_ENABLED)}\n`;
|
||||
body += `network.cookie.lifetimePolicy: ${Services.prefs.getIntPref("network.cookie.lifetimePolicy")}\n`;
|
||||
body += `privacy.annotate_channels.strict_list.enabled: ${Services.prefs.getBoolPref("privacy.annotate_channels.strict_list.enabled")}\n`;
|
||||
body += `privacy.restrict3rdpartystorage.expiration: ${Services.prefs.getIntPref("privacy.restrict3rdpartystorage.expiration")}\n`;
|
||||
body += `${Fingerprinting.PREF_ENABLED}: ${Services.prefs.getBoolPref(Fingerprinting.PREF_ENABLED)}\n`;
|
||||
body += `${Cryptomining.PREF_ENABLED}: ${Services.prefs.getBoolPref(Cryptomining.PREF_ENABLED)}\n`;
|
||||
|
||||
let comments = document.getElementById("identity-popup-breakageReportView-collection-comments");
|
||||
body += "\n**Comments**\n" + comments.value;
|
||||
body += "\n**Comments**\n" + commentsTextarea.value;
|
||||
|
||||
formData.set("body", body);
|
||||
|
||||
|
@ -1024,7 +1030,7 @@ var ContentBlocking = {
|
|||
Cu.reportError(`Content Blocking report to ${reportEndpoint} failed with status ${response.status}`);
|
||||
} else {
|
||||
// Clear the textarea value when the report is submitted
|
||||
comments.value = "";
|
||||
commentsTextarea.value = "";
|
||||
}
|
||||
}).catch(Cu.reportError);
|
||||
},
|
||||
|
|
|
@ -17,6 +17,11 @@ var gProtectionsHandler = {
|
|||
delete this._protectionsIconBox;
|
||||
return this._protectionsIconBox = document.getElementById("tracking-protection-icon-animatable-box");
|
||||
},
|
||||
get _protectionsPopupMultiView() {
|
||||
delete this._protectionsPopupMultiView;
|
||||
return this._protectionsPopupMultiView =
|
||||
document.getElementById("protections-popup-multiView");
|
||||
},
|
||||
get _protectionsPopupMainView() {
|
||||
delete this._protectionsPopupMainView;
|
||||
return this._protectionsPopupMainView =
|
||||
|
@ -52,6 +57,21 @@ var gProtectionsHandler = {
|
|||
return this._protectionPopupTrackersCounterDescription =
|
||||
document.getElementById("protections-popup-trackers-blocked-counter-description");
|
||||
},
|
||||
get _protectionsPopupSiteNotWorkingTPSwitch() {
|
||||
delete this._protectionsPopupSiteNotWorkingTPSwitch;
|
||||
return this._protectionsPopupSiteNotWorkingTPSwitch =
|
||||
document.getElementById("protections-popup-siteNotWorking-tp-switch");
|
||||
},
|
||||
get _protectionsPopupSendReportLearnMore() {
|
||||
delete this._protectionsPopupSendReportLearnMore;
|
||||
return this._protectionsPopupSendReportLearnMore =
|
||||
document.getElementById("protections-popup-sendReportView-learn-more");
|
||||
},
|
||||
get _protectionsPopupSendReportURL() {
|
||||
delete this._protectionsPopupSendReportURL;
|
||||
return this._protectionsPopupSendReportURL =
|
||||
document.getElementById("protections-popup-sendReportView-collection-url");
|
||||
},
|
||||
get _protectionsPopupToastTimeout() {
|
||||
delete this._protectionsPopupToastTimeout;
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "_protectionsPopupToastTimeout",
|
||||
|
@ -128,10 +148,6 @@ var gProtectionsHandler = {
|
|||
},
|
||||
|
||||
refreshProtectionsPopup() {
|
||||
// Refresh the state of the TP toggle switch.
|
||||
this._protectionsPopupTPSwitch.toggleAttribute("enabled",
|
||||
!this._protectionsPopup.hasAttribute("hasException"));
|
||||
|
||||
let host = gIdentityHandler.getHostForDisplay();
|
||||
|
||||
// Push the appropriate strings out to the UI.
|
||||
|
@ -142,7 +158,10 @@ var gProtectionsHandler = {
|
|||
let currentlyEnabled =
|
||||
!this._protectionsPopup.hasAttribute("hasException");
|
||||
|
||||
this._protectionsPopupTPSwitch.toggleAttribute("enabled", currentlyEnabled);
|
||||
for (let tpSwitch of [this._protectionsPopupTPSwitch,
|
||||
this._protectionsPopupSiteNotWorkingTPSwitch]) {
|
||||
tpSwitch.toggleAttribute("enabled", currentlyEnabled);
|
||||
}
|
||||
|
||||
// Display the breakage link according to the current enable state.
|
||||
// The display state of the breakage link will be fixed once the protections
|
||||
|
@ -171,7 +190,10 @@ var gProtectionsHandler = {
|
|||
// styling after toggling the TP switch.
|
||||
let newExceptionState =
|
||||
this._protectionsPopup.toggleAttribute("hasException");
|
||||
this._protectionsPopupTPSwitch.toggleAttribute("enabled", !newExceptionState);
|
||||
for (let tpSwitch of [this._protectionsPopupTPSwitch,
|
||||
this._protectionsPopupSiteNotWorkingTPSwitch]) {
|
||||
tpSwitch.toggleAttribute("enabled", !newExceptionState);
|
||||
}
|
||||
|
||||
// Indicating that we need to show a toast after refreshing the page.
|
||||
// And caching the current URI and window ID in order to only show the mini
|
||||
|
@ -248,4 +270,27 @@ var gProtectionsHandler = {
|
|||
triggerEvent: event,
|
||||
}).catch(Cu.reportError);
|
||||
},
|
||||
|
||||
showSiteNotWorkingView() {
|
||||
this._protectionsPopupMultiView.showSubView("protections-popup-siteNotWorkingView");
|
||||
},
|
||||
|
||||
showSendReportView() {
|
||||
// Save this URI to make sure that the user really only submits the location
|
||||
// they see in the report breakage dialog.
|
||||
this.reportURI = gBrowser.currentURI;
|
||||
let urlWithoutQuery = this.reportURI.asciiSpec.replace("?" + this.reportURI.query, "");
|
||||
this._protectionsPopupSendReportURL.value = urlWithoutQuery;
|
||||
this._protectionsPopupMultiView.showSubView("protections-popup-sendReportView");
|
||||
},
|
||||
|
||||
onSendReportClicked() {
|
||||
this._protectionsPopup.hidePopup();
|
||||
let comments = document.getElementById(
|
||||
"protections-popup-sendReportView-collection-comments");
|
||||
ContentBlocking.submitBreakageReport(this.reportURI, comments);
|
||||
},
|
||||
};
|
||||
|
||||
let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
|
||||
gProtectionsHandler._protectionsPopupSendReportLearnMore.href = baseURL + "blocking-breakage";
|
||||
|
|
|
@ -40,6 +40,22 @@ add_task(async function testToggleSwitch() {
|
|||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function testSiteNotWorking() {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com");
|
||||
await openProtectionsPanel();
|
||||
let viewShownPromise = BrowserTestUtils.waitForEvent(
|
||||
gProtectionsHandler._protectionsPopupMultiView, "ViewShown");
|
||||
document.getElementById("protections-popup-tp-switch-breakage-link").click();
|
||||
let event = await viewShownPromise;
|
||||
is(event.originalTarget.id, "protections-popup-siteNotWorkingView", "Site Not Working? view should be shown");
|
||||
viewShownPromise = BrowserTestUtils.waitForEvent(
|
||||
gProtectionsHandler._protectionsPopupMultiView, "ViewShown");
|
||||
document.getElementById("protections-popup-siteNotWorkingView-sendReport").click();
|
||||
event = await viewShownPromise;
|
||||
is(event.originalTarget.id, "protections-popup-sendReportView", "Send Report view should be shown");
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* A test for the protection settings button.
|
||||
*/
|
||||
|
|
|
@ -265,6 +265,7 @@ async function testReportBreakage(url, tags) {
|
|||
"network.http.referer.defaultPolicy.pbmode",
|
||||
"network.cookie.cookieBehavior",
|
||||
"network.cookie.lifetimePolicy",
|
||||
"privacy.annotate_channels.strict_list.enabled",
|
||||
"privacy.restrict3rdpartystorage.expiration",
|
||||
"privacy.trackingprotection.fingerprinting.enabled",
|
||||
"privacy.trackingprotection.cryptomining.enabled",
|
||||
|
|
|
@ -220,12 +220,10 @@ var AboutLoginsParent = {
|
|||
},
|
||||
|
||||
async showMasterPasswordLoginNotifications() {
|
||||
if (!this._l10n) {
|
||||
this._l10n = new Localization(["browser/aboutLogins.ftl"]);
|
||||
}
|
||||
|
||||
let messageString = await this._l10n.formatValue("master-password-notification-message");
|
||||
for (let subscriber of this._subscriberIterator()) {
|
||||
let MozXULElement = subscriber.ownerGlobal.MozXULElement;
|
||||
MozXULElement.insertFTLIfNeeded("browser/aboutLogins.ftl");
|
||||
|
||||
// If there's already an existing notification bar, don't do anything.
|
||||
let {gBrowser} = subscriber.ownerGlobal;
|
||||
let browser = subscriber;
|
||||
|
@ -238,17 +236,20 @@ var AboutLoginsParent = {
|
|||
// Configure the notification bar
|
||||
let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
|
||||
let iconURL = "chrome://browser/skin/login.svg";
|
||||
let reloadLabel = await this._l10n.formatValue("master-password-reload-button-label");
|
||||
let reloadKey = await this._l10n.formatValue("master-password-reload-button-accesskey");
|
||||
|
||||
let doc = subscriber.ownerDocument;
|
||||
let messageFragment = doc.createDocumentFragment();
|
||||
let message = doc.createElement("span");
|
||||
doc.l10n.setAttributes(message, "master-password-notification-message");
|
||||
messageFragment.appendChild(message);
|
||||
|
||||
let buttons = [{
|
||||
label: reloadLabel,
|
||||
accessKey: reloadKey,
|
||||
"l10n-id": "master-password-reload-button",
|
||||
popup: null,
|
||||
callback() { browser.reload(); },
|
||||
}];
|
||||
|
||||
notification = notificationBox.appendNotification(messageString, MASTER_PASSWORD_NOTIFICATION_ID,
|
||||
notification = notificationBox.appendNotification(messageFragment, MASTER_PASSWORD_NOTIFICATION_ID,
|
||||
iconURL, priority, buttons);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -6,67 +6,76 @@
|
|||
### being translated as the feature is still in heavy development
|
||||
### and strings are likely to change often.
|
||||
|
||||
### Fluent isn't translating elements in the shadow DOM so the translated strings
|
||||
### need to be applied to the composed node where they can be moved to the proper
|
||||
### descendant after translation.
|
||||
|
||||
about-logins-page-title = Logins & Passwords
|
||||
|
||||
create-login-button = New Login
|
||||
|
||||
login-filter =
|
||||
.placeholder = Search Logins
|
||||
|
||||
create-login-button = New Login
|
||||
|
||||
## The ⋯ menu that is in the top corner of the page
|
||||
menu =
|
||||
.title = Open menu
|
||||
menu-menuitem-faq = Frequently Asked Questions
|
||||
menu-menuitem-feedback = Leave Feedback
|
||||
menu-menuitem-import = Import Passwords…
|
||||
menu-menuitem-preferences =
|
||||
{ PLATFORM() ->
|
||||
[windows] Options
|
||||
*[other] Preferences
|
||||
}
|
||||
|
||||
## Login List
|
||||
login-list =
|
||||
.aria-label = Logins matching search query
|
||||
.count =
|
||||
{ $count ->
|
||||
[one] { $count } login
|
||||
*[other] { $count } logins
|
||||
}
|
||||
.last-changed-option = Last Changed
|
||||
.last-used-option = Last Used
|
||||
.missing-username = (no username)
|
||||
.name-option = Name
|
||||
.new-login-subtitle = Enter your login credentials
|
||||
.new-login-title = New Login
|
||||
.sort-label-text = Sort by:
|
||||
login-list-count =
|
||||
{ $count ->
|
||||
[one] { $count } login
|
||||
*[other] { $count } logins
|
||||
}
|
||||
login-list-last-changed-option = Last Changed
|
||||
login-list-last-used-option = Last Used
|
||||
login-list-name-option = Name
|
||||
login-list-sort-label-text = Sort by:
|
||||
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)
|
||||
|
||||
login-item =
|
||||
.cancel-button = Cancel
|
||||
.copied-password-button = ✓ Copied!
|
||||
.copied-username-button = ✓ Copied!
|
||||
.copy-password-button = Copy
|
||||
.copy-username-button = Copy
|
||||
.delete-button = Delete
|
||||
.edit-button = Edit
|
||||
.new-login-title = Create New Login
|
||||
.open-site-button = Launch
|
||||
.origin-label = Website Address
|
||||
.origin-placeholder = https://www.example.com
|
||||
.password-hide-title = Hide password
|
||||
.password-label = Password
|
||||
.password-show-title = Show password
|
||||
.save-changes-button = Save Changes
|
||||
.time-created = Created: { DATETIME($timeCreated, day: "numeric", month: "long", year: "numeric") }
|
||||
.time-changed = Last modified: { DATETIME($timeChanged, day: "numeric", month: "long", year: "numeric") }
|
||||
.time-used = Last used: { DATETIME($timeUsed, day: "numeric", month: "long", year: "numeric") }
|
||||
.username-label = Username
|
||||
.username-placeholder = name@example.com
|
||||
## Login
|
||||
login-item-new-login-title = Create New Login
|
||||
login-item-edit-button = Edit
|
||||
login-item-delete-button = Delete
|
||||
login-item-origin-label = Website Address
|
||||
login-item-origin =
|
||||
.placeholder = https://www.example.com
|
||||
login-item-open-site-button = Launch
|
||||
login-item-username-label = Username
|
||||
login-item-username =
|
||||
.placeholder = name@example.com
|
||||
login-item-copied-username-button-text = ✔ Copied!
|
||||
login-item-copy-username-button-text = Copy
|
||||
login-item-password-label = Password
|
||||
login-item-password-reveal-checkbox-show =
|
||||
.title = Show password
|
||||
login-item-password-reveal-checkbox-hide =
|
||||
.title = Hide password
|
||||
login-item-copied-password-button-text = ✔ Copied!
|
||||
login-item-copy-password-button-text = Copy
|
||||
login-item-save-changes-button = Save Changes
|
||||
login-item-cancel-button = Cancel
|
||||
login-item-time-changed = Last modified: { DATETIME($timeChanged, day: "numeric", month: "long", year: "numeric") }
|
||||
login-item-time-created = Created: { DATETIME($timeCreated, day: "numeric", month: "long", year: "numeric") }
|
||||
login-item-time-used = Last used: { DATETIME($timeUsed, day: "numeric", month: "long", year: "numeric") }
|
||||
|
||||
## Master Password notification
|
||||
master-password-notification-message = Please enter your master password to view saved logins & passwords
|
||||
# TODO: Not sure how to use formatValue with these as attributes on a single ID
|
||||
master-password-reload-button-label = Log in
|
||||
# TODO: Not sure how to use formatValue with these as attributes on a single ID
|
||||
master-password-reload-button-accesskey = L
|
||||
master-password-reload-button =
|
||||
.label = Log in
|
||||
.accesskey = L
|
||||
|
||||
menu-button =
|
||||
.button-title = Open menu
|
||||
.menuitem-faq = Frequently Asked Questions
|
||||
.menuitem-feedback = Leave Feedback
|
||||
.menuitem-import = Import Passwords…
|
||||
.menuitem-preferences =
|
||||
{ PLATFORM() ->
|
||||
[windows] Options
|
||||
*[other] Preferences
|
||||
}
|
||||
confirm-delete-dialog-title = Confirm Deletion
|
||||
confirm-delete-dialog-message = Are you sure you want to delete this login?
|
||||
confirm-delete-dialog-dismiss-button =
|
||||
.title = Cancel
|
||||
confirm-delete-dialog-cancel-button = Cancel
|
||||
confirm-delete-dialog-confirm-button = Delete login
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; img-src data: blob:;"/>
|
||||
<title data-l10n-id="about-logins-page-title"></title>
|
||||
<link rel="localization" href="browser/aboutLogins.ftl">
|
||||
<script type="module" src="chrome://browser/content/aboutlogins/components/copy-to-clipboard-button.js"></script>
|
||||
<script type="module" src="chrome://browser/content/aboutlogins/components/confirm-delete-dialog.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-item.js"></script>
|
||||
<script type="module" src="chrome://browser/content/aboutlogins/components/login-list.js"></script>
|
||||
|
@ -23,65 +23,49 @@
|
|||
<body>
|
||||
<header>
|
||||
<img id="branding-logo" src="chrome://branding/content/aboutlogins.svg" alt=""/>
|
||||
<login-filter data-l10n-id="login-filter"
|
||||
data-l10n-attrs="placeholder"></login-filter>
|
||||
<login-filter></login-filter>
|
||||
<button id="create-login-button" data-l10n-id="create-login-button"></button>
|
||||
<menu-button data-l10n-id="menu-button"
|
||||
data-l10n-attrs="button-title,
|
||||
menuitem-faq,
|
||||
menuitem-feedback,
|
||||
menuitem-import,
|
||||
menuitem-preferences"></menu-button>
|
||||
<menu-button></menu-button>
|
||||
</header>
|
||||
<login-list data-l10n-id="login-list"
|
||||
data-l10n-args='{"count": 0}'
|
||||
data-l10n-attrs="aria-label,
|
||||
count,
|
||||
last-changed-option,
|
||||
last-used-option,
|
||||
missing-username,
|
||||
name-option,
|
||||
new-login-subtitle,
|
||||
new-login-title,
|
||||
sort-label-text"></login-list>
|
||||
<login-item data-l10n-id="login-item"
|
||||
data-l10n-args='{"timeCreated": 0, "timeChanged": 0, "timeUsed": 0}'
|
||||
data-l10n-attrs="cancel-button,
|
||||
copy-password-button,
|
||||
copy-username-button,
|
||||
copied-password-button,
|
||||
copied-username-button,
|
||||
delete-button,
|
||||
edit-button,
|
||||
new-login-title,
|
||||
open-site-button,
|
||||
origin-label,
|
||||
origin-placeholder,
|
||||
password-hide-title,
|
||||
password-label,
|
||||
password-show-title,
|
||||
save-changes-button,
|
||||
time-created,
|
||||
time-changed,
|
||||
time-used,
|
||||
username-label,
|
||||
username-placeholder"></login-item>
|
||||
<login-list></login-list>
|
||||
<login-item></login-item>
|
||||
<confirm-delete-dialog hidden></confirm-delete-dialog>
|
||||
|
||||
<template id="confirm-delete-dialog-template">
|
||||
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
|
||||
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/confirm-delete-dialog.css">
|
||||
<div class="overlay">
|
||||
<div class="container" role="dialog" aria-labelledby="title" aria-describedby="message">
|
||||
<div class="title-bar">
|
||||
<h1 class="title" id="title" data-l10n-id="confirm-delete-dialog-title"></h1>
|
||||
<button class="dismiss-button" data-l10n-id="confirm-delete-dialog-dismiss-button"></button>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p class="message" id="message" data-l10n-id="confirm-delete-dialog-message"></p>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button class="cancel-button" data-l10n-id="confirm-delete-dialog-cancel-button"></button>
|
||||
<button class="confirm-button" data-l10n-id="confirm-delete-dialog-confirm-button"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="login-list-template">
|
||||
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
|
||||
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-list.css">
|
||||
<div class="meta">
|
||||
<label for="login-sort">
|
||||
<span class="sort-label-text"></span>
|
||||
<span data-l10n-id="login-list-sort-label-text"></span>
|
||||
<select id="login-sort">
|
||||
<option class="name-option" value="name"/>
|
||||
<option class="last-used-option" value="last-used"/>
|
||||
<option class="last-changed-option" value="last-changed"/>
|
||||
<option data-l10n-id="login-list-name-option" value="name"/>
|
||||
<option data-l10n-id="login-list-last-used-option" value="last-used"/>
|
||||
<option data-l10n-id="login-list-last-changed-option" value="last-changed"/>
|
||||
</select>
|
||||
</label>
|
||||
<span class="count"></span>
|
||||
<span class="count" data-l10n-id="login-list-count" data-l10n-args='{"count": 0}'></span>
|
||||
</div>
|
||||
<ol role="listbox" tabindex="0">
|
||||
<ol role="listbox" tabindex="0" data-l10n-id="login-list">
|
||||
</ol>
|
||||
</template>
|
||||
|
||||
|
@ -97,71 +81,71 @@
|
|||
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/common.css">
|
||||
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-item.css">
|
||||
<div class="header">
|
||||
<h2 class="title"></h2>
|
||||
<button class="edit-button alternate-button"></button>
|
||||
<button class="delete-button alternate-button"></button>
|
||||
<h2 class="title">
|
||||
<span class="login-item-title"></span>
|
||||
<span class="new-login-title" data-l10n-id="login-item-new-login-title"></span>
|
||||
</h2>
|
||||
<button class="edit-button alternate-button" data-l10n-id="login-item-edit-button"></button>
|
||||
<button class="delete-button alternate-button" data-l10n-id="login-item-delete-button"></button>
|
||||
</div>
|
||||
<form>
|
||||
<div class="detail-row">
|
||||
<label class="detail-cell">
|
||||
<span class="origin-label field-label"></span>
|
||||
<input type="url" name="origin" required/>
|
||||
<span class="origin-label field-label" data-l10n-id="login-item-origin-label"></span>
|
||||
<input type="url" name="origin" required data-l10n-id="login-item-origin"/>
|
||||
</label>
|
||||
<button class="open-site-button"></button>
|
||||
<button class="open-site-button" data-l10n-id="login-item-open-site-button"></button>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label class="detail-cell">
|
||||
<span class="username-label field-label"></span>
|
||||
<input type="text" name="username"/>
|
||||
<span class="username-label field-label" data-l10n-id="login-item-username-label"></span>
|
||||
<input type="text" name="username" data-l10n-id="login-item-username"/>
|
||||
</label>
|
||||
<copy-to-clipboard-button class="copy-username-button"
|
||||
data-telemetry-object="username"></copy-to-clipboard-button>
|
||||
<button class="copy-button copy-username-button" data-copy-login-property="username" data-telemetry-object="username">
|
||||
<span class="copied-button-text" data-l10n-id="login-item-copied-username-button-text"></span>
|
||||
<span class="copy-button-text" data-l10n-id="login-item-copy-username-button-text"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label class="detail-cell">
|
||||
<span class="password-label field-label"></span>
|
||||
<span class="password-label field-label" data-l10n-id="login-item-password-label"></span>
|
||||
<div class="reveal-password-wrapper">
|
||||
<input type="password" name="password" required/>
|
||||
<input type="checkbox" class="reveal-password-checkbox"/>
|
||||
<input type="checkbox"
|
||||
class="reveal-password-checkbox"
|
||||
data-l10n-id="login-item-password-reveal-checkbox"/>
|
||||
</div>
|
||||
</label>
|
||||
<copy-to-clipboard-button class="copy-password-button"
|
||||
data-telemetry-object="password"></copy-to-clipboard-button>
|
||||
<button class="copy-button copy-password-button" data-copy-login-property="password" data-telemetry-object="password">
|
||||
<span class="copied-button-text" data-l10n-id="login-item-copied-password-button-text"></span>
|
||||
<span class="copy-button-text" data-l10n-id="login-item-copy-password-button-text"></span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="time-created meta-info"></p>
|
||||
<p class="time-changed meta-info"></p>
|
||||
<p class="time-used meta-info"></p>
|
||||
<button class="save-changes-button"></button>
|
||||
<button class="cancel-button"></button>
|
||||
<p class="time-created meta-info" data-l10n-id="login-item-time-created" data-l10n-args='{"timeCreated": 0}'></p>
|
||||
<p class="time-changed meta-info" data-l10n-id="login-item-time-changed" data-l10n-args='{"timeChanged": 0}'></p>
|
||||
<p class="time-used meta-info" data-l10n-id="login-item-time-used" data-l10n-args='{"timeUsed": 0}'></p>
|
||||
<button class="save-changes-button" data-l10n-id="login-item-save-changes-button"></button>
|
||||
<button class="cancel-button" data-l10n-id="login-item-cancel-button"></button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<template id="login-filter-template">
|
||||
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
|
||||
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-filter.css">
|
||||
<input class="filter" type="text"/>
|
||||
<input data-l10n-id="login-filter" class="filter" type="text"/>
|
||||
</template>
|
||||
|
||||
<template id="menu-button-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/menu-button.css">
|
||||
<button class="menu-button alternate-button"></button>
|
||||
<button class="menu-button alternate-button" data-l10n-id="menu"></button>
|
||||
<ul class="menu" role="menu" hidden>
|
||||
<button role="menuitem" class="menuitem-button menuitem-import alternate-button" hidden data-supported-platforms="Win32" data-event-name="AboutLoginsImport"></button>
|
||||
<button role="menuitem" class="menuitem-button menuitem-preferences alternate-button" data-event-name="AboutLoginsOpenPreferences"></button>
|
||||
<button role="menuitem" class="menuitem-button menuitem-feedback alternate-button" data-event-name="AboutLoginsOpenFeedback"></button>
|
||||
<button role="menuitem" class="menuitem-button menuitem-faq alternate-button" data-event-name="AboutLoginsOpenFAQ"></button>
|
||||
<button role="menuitem" class="menuitem-button menuitem-import alternate-button" hidden data-supported-platforms="Win32" data-event-name="AboutLoginsImport" data-l10n-id="menu-menuitem-import"></button>
|
||||
<button role="menuitem" class="menuitem-button menuitem-preferences alternate-button" data-event-name="AboutLoginsOpenPreferences" data-l10n-id="menu-menuitem-preferences"></button>
|
||||
<button role="menuitem" class="menuitem-button menuitem-feedback alternate-button" data-event-name="AboutLoginsOpenFeedback" data-l10n-id="menu-menuitem-feedback"></button>
|
||||
<button role="menuitem" class="menuitem-button menuitem-faq alternate-button" data-event-name="AboutLoginsOpenFAQ" data-l10n-id="menu-menuitem-faq"></button>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template id="copy-to-clipboard-button-template">
|
||||
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
|
||||
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/copy-to-clipboard-button.css">
|
||||
<button class="copy-button">
|
||||
<span class="copied-button-text"></span>
|
||||
<span class="copy-button-text"></span>
|
||||
</button>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
:host {
|
||||
/* these variable values come from about:preferences */
|
||||
--in-content-dialogtitle-background: #f1f1f1;
|
||||
--in-content-dialogtitle-border: #c1c1c1;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
/* TODO: this color is used in the about:preferences overlay, but
|
||||
why isn't it declared as a variable? */
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
z-index: 2;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 50%;
|
||||
min-width: 250px;
|
||||
max-width: 500px;
|
||||
height: 40%;
|
||||
min-height: 200px;
|
||||
margin: auto;
|
||||
background: var(--in-content-page-background);
|
||||
color: var(--in-content-page-color);
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
position: relative;
|
||||
flex: 0 1 auto;
|
||||
text-align: center;
|
||||
background-color: var(--in-content-dialogtitle-background);
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid var(--in-content-dialogtitle-border);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: .9em;
|
||||
line-height: 1.8em;
|
||||
font-weight: 600;
|
||||
-moz-user-select: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button.dismiss-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
margin: 8px 16px;
|
||||
padding: 0;
|
||||
background: url(chrome://global/skin/icons/close.svg) no-repeat center;
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
fill: currentColor;
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
button.dismiss-button:dir(rtl) {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex: 0 1 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.buttons button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content,
|
||||
.buttons {
|
||||
margin: 16px;
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/* 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 ConfirmDeleteDialog extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this._promise = null;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.shadowRoot) {
|
||||
return;
|
||||
}
|
||||
let template = document.querySelector("#confirm-delete-dialog-template");
|
||||
let shadowRoot = this.attachShadow({mode: "open"});
|
||||
document.l10n.connectRoot(shadowRoot);
|
||||
shadowRoot.appendChild(template.content.cloneNode(true));
|
||||
|
||||
this._cancelButton = this.shadowRoot.querySelector(".cancel-button");
|
||||
this._confirmButton = this.shadowRoot.querySelector(".confirm-button");
|
||||
this._dismissButton = this.shadowRoot.querySelector(".dismiss-button");
|
||||
this._message = this.shadowRoot.querySelector(".message");
|
||||
this._overlay = this.shadowRoot.querySelector(".overlay");
|
||||
this._title = this.shadowRoot.querySelector(".title");
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "keydown":
|
||||
if (event.key === "Escape" && !event.defaultPrevented) {
|
||||
this.onCancel();
|
||||
}
|
||||
break;
|
||||
case "click":
|
||||
if (event.target.classList.contains("cancel-button") ||
|
||||
event.target.classList.contains("dismiss-button") ||
|
||||
event.target.classList.contains("overlay")) {
|
||||
this.onCancel();
|
||||
} else if (event.target.classList.contains("confirm-button")) {
|
||||
this.onConfirm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
this._cancelButton.removeEventListener("click", this);
|
||||
this._confirmButton.removeEventListener("click", this);
|
||||
this._dismissButton.removeEventListener("click", this);
|
||||
this._overlay.removeEventListener("click", this);
|
||||
window.removeEventListener("keydown", this);
|
||||
|
||||
this.hidden = true;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.hidden = false;
|
||||
|
||||
this._cancelButton.addEventListener("click", this);
|
||||
this._confirmButton.addEventListener("click", this);
|
||||
this._dismissButton.addEventListener("click", this);
|
||||
this._overlay.addEventListener("click", this);
|
||||
window.addEventListener("keydown", this);
|
||||
|
||||
// For accessibility, focus the least destructive action button when the
|
||||
// dialog loads.
|
||||
this._cancelButton.focus();
|
||||
|
||||
this._promise = new Promise((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
});
|
||||
|
||||
return this._promise;
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this._reject();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
onConfirm() {
|
||||
this._resolve();
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
customElements.define("confirm-delete-dialog", ConfirmDeleteDialog);
|
|
@ -1,29 +0,0 @@
|
|||
/* 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 {
|
||||
--success-color: #00c100;
|
||||
}
|
||||
|
||||
@supports -moz-bool-pref("browser.in-content.dark-mode") {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:host {
|
||||
--success-color: #86DE74;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host(:not([data-copied])) .copied-button-text,
|
||||
:host([data-copied]) .copy-button-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([data-copied]) {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
:host([data-copied]) button {
|
||||
background-color: transparent;
|
||||
opacity: 1; /* override common.css fading out disabled buttons */
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
import {recordTelemetryEvent} from "../aboutLoginsUtils.js";
|
||||
import ReflectedFluentElement from "./reflected-fluent-element.js";
|
||||
|
||||
export default class CopyToClipboardButton extends ReflectedFluentElement {
|
||||
/**
|
||||
* The number of milliseconds to display the "Copied" success message
|
||||
* before reverting to the normal "Copy" button.
|
||||
*/
|
||||
static get BUTTON_RESET_TIMEOUT() {
|
||||
return 5000;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._relatedInput = null;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.shadowRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
let CopyToClipboardButtonTemplate = document.querySelector("#copy-to-clipboard-button-template");
|
||||
this.attachShadow({mode: "open"})
|
||||
.appendChild(CopyToClipboardButtonTemplate.content.cloneNode(true));
|
||||
|
||||
this._copyButton = this.shadowRoot.querySelector(".copy-button");
|
||||
this._copyButton.addEventListener("click", this);
|
||||
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
static get reflectedFluentIDs() {
|
||||
return ["copy-button-text", "copied-button-text"];
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return CopyToClipboardButton.reflectedFluentIDs;
|
||||
}
|
||||
|
||||
handleSpecialCaseFluentString(attrName) {
|
||||
if (attrName != "copied-button-text" &&
|
||||
attrName != "copy-button-text") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let span = this.shadowRoot.querySelector("." + attrName);
|
||||
span.textContent = this.getAttribute(attrName);
|
||||
return true;
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type != "click" || event.currentTarget != this._copyButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._copyButton.disabled = true;
|
||||
navigator.clipboard.writeText(this._relatedInput.value).then(() => {
|
||||
this.dataset.copied = true;
|
||||
setTimeout(() => {
|
||||
this._copyButton.disabled = false;
|
||||
delete this.dataset.copied;
|
||||
}, CopyToClipboardButton.BUTTON_RESET_TIMEOUT);
|
||||
}, () => this._copyButton.disabled = false);
|
||||
|
||||
if (this.dataset.telemetryObject) {
|
||||
recordTelemetryEvent({object: this.dataset.telemetryObject, method: "copy"});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Element} val A reference to the input element whose value will
|
||||
* be placed on the clipboard.
|
||||
*/
|
||||
set relatedInput(val) {
|
||||
this._relatedInput = val;
|
||||
}
|
||||
}
|
||||
customElements.define("copy-to-clipboard-button", CopyToClipboardButton);
|
|
@ -3,23 +3,21 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import {recordTelemetryEvent} from "../aboutLoginsUtils.js";
|
||||
import ReflectedFluentElement from "./reflected-fluent-element.js";
|
||||
|
||||
export default class LoginFilter extends ReflectedFluentElement {
|
||||
export default class LoginFilter extends HTMLElement {
|
||||
connectedCallback() {
|
||||
if (this.shadowRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
let loginFilterTemplate = document.querySelector("#login-filter-template");
|
||||
this.attachShadow({mode: "open"})
|
||||
.appendChild(loginFilterTemplate.content.cloneNode(true));
|
||||
let shadowRoot = this.attachShadow({mode: "open"});
|
||||
document.l10n.connectRoot(shadowRoot);
|
||||
shadowRoot.appendChild(loginFilterTemplate.content.cloneNode(true));
|
||||
|
||||
this._input = this.shadowRoot.querySelector("input");
|
||||
|
||||
this.addEventListener("input", this);
|
||||
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
|
@ -31,14 +29,6 @@ export default class LoginFilter extends ReflectedFluentElement {
|
|||
}
|
||||
}
|
||||
|
||||
static get reflectedFluentIDs() {
|
||||
return ["placeholder"];
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return this.reflectedFluentIDs;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._input.value;
|
||||
}
|
||||
|
@ -48,16 +38,6 @@ export default class LoginFilter extends ReflectedFluentElement {
|
|||
this._dispatchFilterEvent(val);
|
||||
}
|
||||
|
||||
handleSpecialCaseFluentString(attrName) {
|
||||
if (!this.shadowRoot ||
|
||||
attrName != "placeholder") {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._input.placeholder = this.getAttribute(attrName);
|
||||
return true;
|
||||
}
|
||||
|
||||
_dispatchFilterEvent(value) {
|
||||
this.dispatchEvent(new CustomEvent("AboutLoginsFilterLogins", {
|
||||
bubbles: true,
|
||||
|
|
|
@ -8,6 +8,15 @@
|
|||
--reveal-checkbox-opacity: .8;
|
||||
--reveal-checkbox-opacity-hover: .6;
|
||||
--reveal-checkbox-opacity-active: 1;
|
||||
--success-color: #00c100;
|
||||
}
|
||||
|
||||
@supports -moz-bool-pref("browser.in-content.dark-mode") {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:host {
|
||||
--success-color: #86DE74;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host([data-editing]) .edit-button,
|
||||
|
@ -16,6 +25,8 @@
|
|||
:host([data-is-new-login]) copy-to-clipboard-button,
|
||||
:host([data-is-new-login]) .open-site-button,
|
||||
:host([data-is-new-login]) .meta-info,
|
||||
:host([data-is-new-login]) .login-item-title,
|
||||
:host(:not([data-is-new-login])) .new-login-title,
|
||||
:host(:not([data-editing])) .cancel-button,
|
||||
:host(:not([data-editing])) .save-changes-button {
|
||||
display: none;
|
||||
|
@ -90,6 +101,17 @@
|
|||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.copy-button:not([data-copied]) .copied-button-text,
|
||||
.copy-button[data-copied] .copy-button-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.copy-button[data-copied] {
|
||||
color: var(--success-color) !important; /* override common.css */
|
||||
background-color: transparent;
|
||||
opacity: 1; /* override common.css fading out disabled buttons */
|
||||
}
|
||||
|
||||
.meta-info {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
|
|
@ -3,9 +3,16 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import {recordTelemetryEvent} from "../aboutLoginsUtils.js";
|
||||
import ReflectedFluentElement from "./reflected-fluent-element.js";
|
||||
|
||||
export default class LoginItem extends ReflectedFluentElement {
|
||||
export default class LoginItem extends HTMLElement {
|
||||
/**
|
||||
* The number of milliseconds to display the "Copied" success message
|
||||
* before reverting to the normal "Copy" button.
|
||||
*/
|
||||
static get COPY_BUTTON_RESET_TIMEOUT() {
|
||||
return 5000;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._login = {};
|
||||
|
@ -18,13 +25,17 @@ export default class LoginItem extends ReflectedFluentElement {
|
|||
}
|
||||
|
||||
let loginItemTemplate = document.querySelector("#login-item-template");
|
||||
this.attachShadow({mode: "open"})
|
||||
.appendChild(loginItemTemplate.content.cloneNode(true));
|
||||
let shadowRoot = this.attachShadow({mode: "open"});
|
||||
document.l10n.connectRoot(shadowRoot);
|
||||
shadowRoot.appendChild(loginItemTemplate.content.cloneNode(true));
|
||||
|
||||
for (let selector of [
|
||||
".copy-password-button",
|
||||
".copy-username-button",
|
||||
".delete-button",
|
||||
".edit-button",
|
||||
".open-site-button",
|
||||
".reveal-password-checkbox",
|
||||
".save-changes-button",
|
||||
".cancel-button",
|
||||
]) {
|
||||
|
@ -32,6 +43,7 @@ export default class LoginItem extends ReflectedFluentElement {
|
|||
button.addEventListener("click", this);
|
||||
}
|
||||
|
||||
this._confirmDeleteDialog = document.querySelector("confirm-delete-dialog");
|
||||
this._copyPasswordButton = this.shadowRoot.querySelector(".copy-password-button");
|
||||
this._copyUsernameButton = this.shadowRoot.querySelector(".copy-username-button");
|
||||
this._deleteButton = this.shadowRoot.querySelector(".delete-button");
|
||||
|
@ -41,98 +53,23 @@ export default class LoginItem extends ReflectedFluentElement {
|
|||
this._usernameInput = this.shadowRoot.querySelector("input[name='username']");
|
||||
this._passwordInput = this.shadowRoot.querySelector("input[name='password']");
|
||||
this._revealCheckbox = this.shadowRoot.querySelector(".reveal-password-checkbox");
|
||||
this._title = this.shadowRoot.querySelector(".title");
|
||||
|
||||
this._copyUsernameButton.relatedInput = this._usernameInput;
|
||||
this._copyPasswordButton.relatedInput = this._passwordInput;
|
||||
this._title = this.shadowRoot.querySelector(".login-item-title");
|
||||
this._timeCreated = this.shadowRoot.querySelector(".time-created");
|
||||
this._timeChanged = this.shadowRoot.querySelector(".time-changed");
|
||||
this._timeUsed = this.shadowRoot.querySelector(".time-used");
|
||||
|
||||
this.render();
|
||||
|
||||
this._originInput.addEventListener("blur", this);
|
||||
this._revealCheckbox.addEventListener("click", this);
|
||||
window.addEventListener("AboutLoginsLoginSelected", this);
|
||||
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
static get reflectedFluentIDs() {
|
||||
return [
|
||||
"cancel-button",
|
||||
"copied-password-button",
|
||||
"copied-username-button",
|
||||
"copy-password-button",
|
||||
"copy-username-button",
|
||||
"delete-button",
|
||||
"edit-button",
|
||||
"new-login-title",
|
||||
"open-site-button",
|
||||
"origin-label",
|
||||
"origin-placeholder",
|
||||
"password-hide-title",
|
||||
"password-label",
|
||||
"password-show-title",
|
||||
"save-changes-button",
|
||||
"time-created",
|
||||
"time-changed",
|
||||
"time-used",
|
||||
"username-label",
|
||||
"username-placeholder",
|
||||
];
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return this.reflectedFluentIDs;
|
||||
}
|
||||
|
||||
handleSpecialCaseFluentString(attrName) {
|
||||
switch (attrName) {
|
||||
case "copied-password-button":
|
||||
case "copy-password-button": {
|
||||
let newAttrName = attrName.substr(0, attrName.indexOf("-")) + "-button-text";
|
||||
this._copyPasswordButton.setAttribute(newAttrName, this.getAttribute(attrName));
|
||||
break;
|
||||
}
|
||||
case "copied-username-button":
|
||||
case "copy-username-button": {
|
||||
let newAttrName = attrName.substr(0, attrName.indexOf("-")) + "-button-text";
|
||||
this._copyUsernameButton.setAttribute(newAttrName, this.getAttribute(attrName));
|
||||
break;
|
||||
}
|
||||
case "new-login-title": {
|
||||
this._title.setAttribute(attrName, this.getAttribute(attrName));
|
||||
if (!this._login.title) {
|
||||
this._title.textContent = this.getAttribute(attrName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "origin-placeholder": {
|
||||
this._originInput.setAttribute("placeholder", this.getAttribute(attrName));
|
||||
break;
|
||||
}
|
||||
case "password-hide-title":
|
||||
case "password-show-title": {
|
||||
this._updatePasswordRevealState();
|
||||
break;
|
||||
}
|
||||
case "username-placeholder": {
|
||||
this._usernameInput.setAttribute("placeholder", this.getAttribute(attrName));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
let l10nArgs = {
|
||||
timeCreated: this._login.timeCreated || "",
|
||||
timeChanged: this._login.timePasswordChanged || "",
|
||||
timeUsed: this._login.timeLastUsed || "",
|
||||
};
|
||||
document.l10n.setAttributes(this, "login-item", l10nArgs);
|
||||
document.l10n.setAttributes(this._timeCreated, "login-item-time-created", {timeCreated: this._login.timeCreated || ""});
|
||||
document.l10n.setAttributes(this._timeChanged, "login-item-time-changed", {timeChanged: this._login.timePasswordChanged || ""});
|
||||
document.l10n.setAttributes(this._timeUsed, "login-item-time-used", {timeUsed: this._login.timeLastUsed || ""});
|
||||
|
||||
this._title.textContent = this._login.title || this._title.getAttribute("new-login-title");
|
||||
this._title.textContent = this._login.title;
|
||||
this._originInput.defaultValue = this._login.origin || "";
|
||||
this._usernameInput.defaultValue = this._login.username || "";
|
||||
this._passwordInput.defaultValue = this._login.password || "";
|
||||
|
@ -157,7 +94,7 @@ export default class LoginItem extends ReflectedFluentElement {
|
|||
break;
|
||||
}
|
||||
case "click": {
|
||||
let classList = event.target.classList;
|
||||
let classList = event.currentTarget.classList;
|
||||
if (classList.contains("reveal-password-checkbox")) {
|
||||
this._updatePasswordRevealState();
|
||||
|
||||
|
@ -184,13 +121,24 @@ export default class LoginItem extends ReflectedFluentElement {
|
|||
});
|
||||
return;
|
||||
}
|
||||
if (classList.contains("delete-button")) {
|
||||
document.dispatchEvent(new CustomEvent("AboutLoginsDeleteLogin", {
|
||||
bubbles: true,
|
||||
detail: this._login,
|
||||
}));
|
||||
if (classList.contains("copy-password-button") ||
|
||||
classList.contains("copy-username-button")) {
|
||||
let copyButton = event.currentTarget;
|
||||
copyButton.disabled = true;
|
||||
let propertyToCopy = copyButton.dataset.copyLoginProperty;
|
||||
navigator.clipboard.writeText(this._login[propertyToCopy]).then(() => {
|
||||
copyButton.dataset.copied = true;
|
||||
setTimeout(() => {
|
||||
copyButton.disabled = false;
|
||||
delete copyButton.dataset.copied;
|
||||
}, LoginItem.COPY_BUTTON_RESET_TIMEOUT);
|
||||
}, () => copyButton.disabled = false);
|
||||
|
||||
recordTelemetryEvent({object: "existing_login", method: "delete"});
|
||||
recordTelemetryEvent({object: copyButton.dataset.telemetryObject, method: "copy"});
|
||||
return;
|
||||
}
|
||||
if (classList.contains("delete-button")) {
|
||||
this.confirmDelete();
|
||||
return;
|
||||
}
|
||||
if (classList.contains("edit-button")) {
|
||||
|
@ -235,6 +183,21 @@ export default class LoginItem extends ReflectedFluentElement {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the confirm delete dialog, completing the deletion if the user
|
||||
* agrees.
|
||||
*/
|
||||
confirmDelete() {
|
||||
const dialog = document.querySelector("confirm-delete-dialog");
|
||||
dialog.show().then(() => {
|
||||
document.dispatchEvent(new CustomEvent("AboutLoginsDeleteLogin", {
|
||||
bubbles: true,
|
||||
detail: this._login,
|
||||
}));
|
||||
recordTelemetryEvent({object: "existing_login", method: "delete"});
|
||||
}, () => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {login} login The login that should be displayed. The login object is
|
||||
* a plain JS object representation of nsILoginInfo/nsILoginMetaInfo.
|
||||
|
@ -381,13 +344,12 @@ export default class LoginItem extends ReflectedFluentElement {
|
|||
}
|
||||
|
||||
_updatePasswordRevealState() {
|
||||
let labelAttr = this._revealCheckbox.checked ? "password-show-title"
|
||||
: "password-hide-title";
|
||||
this._revealCheckbox.setAttribute("aria-label", this.getAttribute(labelAttr));
|
||||
this._revealCheckbox.setAttribute("title", this.getAttribute(labelAttr));
|
||||
let titleId = this._revealCheckbox.checked ? "login-item-password-reveal-checkbox-hide"
|
||||
: "login-item-password-reveal-checkbox-show";
|
||||
document.l10n.setAttributes(this._revealCheckbox, titleId);
|
||||
|
||||
let {checked} = this._revealCheckbox;
|
||||
let inputType = checked ? "type" : "password";
|
||||
let inputType = checked ? "text" : "password";
|
||||
this._passwordInput.setAttribute("type", inputType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,29 +21,35 @@ export default class LoginListItem extends HTMLElement {
|
|||
}
|
||||
|
||||
let loginListItemTemplate = document.querySelector("#login-list-item-template");
|
||||
this.attachShadow({mode: "open"})
|
||||
.appendChild(loginListItemTemplate.content.cloneNode(true));
|
||||
let shadowRoot = this.attachShadow({mode: "open"});
|
||||
document.l10n.connectRoot(shadowRoot);
|
||||
shadowRoot.appendChild(loginListItemTemplate.content.cloneNode(true));
|
||||
|
||||
this._title = this.shadowRoot.querySelector(".title");
|
||||
this._username = this.shadowRoot.querySelector(".username");
|
||||
this.setAttribute("role", "option");
|
||||
|
||||
this.render();
|
||||
|
||||
this.addEventListener("click", this);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this._login.guid) {
|
||||
delete this.dataset.guid;
|
||||
this._title.textContent = this.getAttribute("new-login-title");
|
||||
this._username.textContent = this.getAttribute("new-login-subtitle");
|
||||
document.l10n.setAttributes(this._title, "login-list-item-title-new-login");
|
||||
document.l10n.setAttributes(this._username, "login-list-item-subtitle-new-login");
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataset.guid = this._login.guid;
|
||||
this._title.textContent = this._login.title;
|
||||
this._username.textContent = this._login.username.trim() || this.getAttribute("missing-username");
|
||||
if (this._login.username.trim()) {
|
||||
this._username.removeAttribute("data-l10n-id");
|
||||
this._username.textContent = this._login.username.trim();
|
||||
} else {
|
||||
document.l10n.setAttributes(this._username, "login-list-item-subtitle-missing-username");
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import LoginListItem from "./login-list-item.js";
|
||||
import ReflectedFluentElement from "./reflected-fluent-element.js";
|
||||
|
||||
const collator = new Intl.Collator();
|
||||
const sortFnOptions = {
|
||||
|
@ -12,7 +11,7 @@ const sortFnOptions = {
|
|||
"last-changed": (a, b) => (a.timePasswordChanged < b.timePasswordChanged),
|
||||
};
|
||||
|
||||
export default class LoginList extends ReflectedFluentElement {
|
||||
export default class LoginList extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this._logins = [];
|
||||
|
@ -26,10 +25,12 @@ export default class LoginList extends ReflectedFluentElement {
|
|||
return;
|
||||
}
|
||||
let loginListTemplate = document.querySelector("#login-list-template");
|
||||
this.attachShadow({mode: "open"})
|
||||
.appendChild(loginListTemplate.content.cloneNode(true));
|
||||
let shadowRoot = this.attachShadow({mode: "open"});
|
||||
document.l10n.connectRoot(shadowRoot);
|
||||
shadowRoot.appendChild(loginListTemplate.content.cloneNode(true));
|
||||
|
||||
this._list = this.shadowRoot.querySelector("ol");
|
||||
this._count = this.shadowRoot.querySelector(".count");
|
||||
|
||||
this.render();
|
||||
|
||||
|
@ -38,15 +39,13 @@ export default class LoginList extends ReflectedFluentElement {
|
|||
window.addEventListener("AboutLoginsLoginSelected", this);
|
||||
window.addEventListener("AboutLoginsFilterLogins", this);
|
||||
this.addEventListener("keydown", this);
|
||||
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
render() {
|
||||
this._list.textContent = "";
|
||||
|
||||
if (!this._logins.length) {
|
||||
document.l10n.setAttributes(this, "login-list", {count: 0});
|
||||
document.l10n.setAttributes(this._count, "login-list-count", {count: 0});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -59,7 +58,6 @@ export default class LoginList extends ReflectedFluentElement {
|
|||
|
||||
for (let login of this._logins) {
|
||||
let listItem = new LoginListItem(login);
|
||||
listItem.setAttribute("missing-username", this.getAttribute("missing-username"));
|
||||
if (login.guid == this._selectedGuid) {
|
||||
listItem.classList.add("selected");
|
||||
listItem.setAttribute("aria-selected", "true");
|
||||
|
@ -69,7 +67,7 @@ export default class LoginList extends ReflectedFluentElement {
|
|||
}
|
||||
|
||||
let visibleLoginCount = this._applyFilter();
|
||||
document.l10n.setAttributes(this, "login-list", {count: visibleLoginCount});
|
||||
document.l10n.setAttributes(this._count, "login-list-count", {count: visibleLoginCount});
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
|
@ -101,42 +99,6 @@ export default class LoginList extends ReflectedFluentElement {
|
|||
}
|
||||
}
|
||||
|
||||
static get reflectedFluentIDs() {
|
||||
return ["aria-label",
|
||||
"count",
|
||||
"last-used-option",
|
||||
"last-changed-option",
|
||||
"missing-username",
|
||||
"name-option",
|
||||
"new-login-subtitle",
|
||||
"new-login-title",
|
||||
"sort-label-text"];
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return this.reflectedFluentIDs;
|
||||
}
|
||||
|
||||
handleSpecialCaseFluentString(attrName) {
|
||||
switch (attrName) {
|
||||
case "aria-label": {
|
||||
this._list.setAttribute("aria-label", this.getAttribute(attrName));
|
||||
break;
|
||||
}
|
||||
case "missing-username": {
|
||||
break;
|
||||
}
|
||||
case "new-login-subtitle":
|
||||
case "new-login-title": {
|
||||
this._blankLoginListItem.setAttribute(attrName, this.getAttribute(attrName));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {login[]} logins An array of logins used for displaying in the list.
|
||||
*/
|
||||
|
|
|
@ -2,17 +2,16 @@
|
|||
* 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/. */
|
||||
|
||||
import ReflectedFluentElement from "chrome://browser/content/aboutlogins/components/reflected-fluent-element.js";
|
||||
|
||||
export default class MenuButton extends ReflectedFluentElement {
|
||||
export default class MenuButton extends HTMLElement {
|
||||
connectedCallback() {
|
||||
if (this.shadowRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
let MenuButtonTemplate = document.querySelector("#menu-button-template");
|
||||
this.attachShadow({mode: "open"})
|
||||
.appendChild(MenuButtonTemplate.content.cloneNode(true));
|
||||
let shadowRoot = this.attachShadow({mode: "open"});
|
||||
document.l10n.connectRoot(shadowRoot);
|
||||
shadowRoot.appendChild(MenuButtonTemplate.content.cloneNode(true));
|
||||
|
||||
for (let menuitem of this.shadowRoot.querySelectorAll(".menuitem-button[data-supported-platforms]")) {
|
||||
let supportedPlatforms = menuitem.dataset.supportedPlatforms.split(",").map(platform => platform.trim());
|
||||
|
@ -27,32 +26,6 @@ export default class MenuButton extends ReflectedFluentElement {
|
|||
this.addEventListener("blur", this);
|
||||
this._menuButton.addEventListener("click", this);
|
||||
this.addEventListener("keydown", this, true);
|
||||
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
static get reflectedFluentIDs() {
|
||||
return [
|
||||
"button-title",
|
||||
"menuitem-faq",
|
||||
"menuitem-import",
|
||||
"menuitem-feedback",
|
||||
"menuitem-preferences",
|
||||
];
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return MenuButton.reflectedFluentIDs;
|
||||
}
|
||||
|
||||
handleSpecialCaseFluentString(attrName) {
|
||||
if (!this.shadowRoot ||
|
||||
attrName != "button-title") {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._menuButton.setAttribute("title", this.getAttribute(attrName));
|
||||
return true;
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
/* 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 ReflectedFluentElement extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this._reflectFluentStrings();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
_isReflectedAttributePresent(attr) {
|
||||
return this.constructor.reflectedFluentIDs.includes(attr.name);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define("reflected-fluent-element", ReflectedFluentElement);
|
|
@ -3,8 +3,8 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
browser.jar:
|
||||
content/browser/aboutlogins/components/copy-to-clipboard-button.css (content/components/copy-to-clipboard-button.css)
|
||||
content/browser/aboutlogins/components/copy-to-clipboard-button.js (content/components/copy-to-clipboard-button.js)
|
||||
content/browser/aboutlogins/components/confirm-delete-dialog.css (content/components/confirm-delete-dialog.css)
|
||||
content/browser/aboutlogins/components/confirm-delete-dialog.js (content/components/confirm-delete-dialog.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-item.css (content/components/login-item.css)
|
||||
|
@ -15,7 +15,6 @@ browser.jar:
|
|||
content/browser/aboutlogins/components/login-list-item.js (content/components/login-list-item.js)
|
||||
content/browser/aboutlogins/components/menu-button.css (content/components/menu-button.css)
|
||||
content/browser/aboutlogins/components/menu-button.js (content/components/menu-button.js)
|
||||
content/browser/aboutlogins/components/reflected-fluent-element.js (content/components/reflected-fluent-element.js)
|
||||
content/browser/aboutlogins/icons/delete.svg (content/icons/delete.svg)
|
||||
content/browser/aboutlogins/icons/edit.svg (content/icons/edit.svg)
|
||||
content/browser/aboutlogins/icons/faq.svg (content/icons/faq.svg)
|
||||
|
|
|
@ -8,6 +8,7 @@ support-files =
|
|||
# Skip ASAN and debug since waiting for content events is already slow.
|
||||
[browser_aaa_eventTelemetry_run_first.js]
|
||||
skip-if = asan || debug
|
||||
[browser_confirmDeleteDialog.js]
|
||||
[browser_copyToClipboardButton.js]
|
||||
[browser_createLogin.js]
|
||||
[browser_deleteLogin.js]
|
||||
|
|
|
@ -43,7 +43,6 @@ add_task(async function test_telemetry_events() {
|
|||
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
|
||||
let loginItem = content.document.querySelector("login-item");
|
||||
let copyButton = loginItem.shadowRoot.querySelector(".copy-username-button");
|
||||
copyButton = copyButton.shadowRoot.querySelector(".copy-button");
|
||||
copyButton.click();
|
||||
});
|
||||
await waitForTelemetryEventCount(2);
|
||||
|
@ -51,7 +50,6 @@ add_task(async function test_telemetry_events() {
|
|||
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
|
||||
let loginItem = content.document.querySelector("login-item");
|
||||
let copyButton = loginItem.shadowRoot.querySelector(".copy-password-button");
|
||||
copyButton = copyButton.shadowRoot.querySelector(".copy-button");
|
||||
copyButton.click();
|
||||
});
|
||||
await waitForTelemetryEventCount(3);
|
||||
|
@ -108,6 +106,9 @@ add_task(async function test_telemetry_events() {
|
|||
let loginItem = content.document.querySelector("login-item");
|
||||
let deleteButton = loginItem.shadowRoot.querySelector(".delete-button");
|
||||
deleteButton.click();
|
||||
let confirmDeleteDialog = content.document.querySelector("confirm-delete-dialog");
|
||||
let confirmDeleteButton = confirmDeleteDialog.shadowRoot.querySelector(".confirm-button");
|
||||
confirmDeleteButton.click();
|
||||
});
|
||||
await waitForTelemetryEventCount(10);
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/* 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() {
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
await ContentTask.spawn(browser, null, async () => {
|
||||
let dialog = Cu.waiveXrays(content.document.querySelector("confirm-delete-dialog"));
|
||||
|
||||
let cancelButton = dialog.shadowRoot.querySelector(".cancel-button");
|
||||
let confirmDeleteButton = dialog.shadowRoot.querySelector(".confirm-button");
|
||||
let dismissButton = dialog.shadowRoot.querySelector(".dismiss-button");
|
||||
let message = dialog.shadowRoot.querySelector(".message");
|
||||
let title = dialog.shadowRoot.querySelector(".title");
|
||||
|
||||
is(title.textContent, "Confirm Deletion",
|
||||
"Title contents should match l10n attribute set on outer element");
|
||||
is(message.textContent, "Are you sure you want to delete this login?",
|
||||
"Message contents should match l10n attribute set on outer element");
|
||||
is(cancelButton.textContent, "Cancel",
|
||||
"Cancel button contents should match l10n attribute set on outer element");
|
||||
is(confirmDeleteButton.textContent, "Delete login",
|
||||
"Delete button contents should match l10n attribute set on outer element");
|
||||
|
||||
let showPromise = dialog.show();
|
||||
cancelButton.click();
|
||||
try {
|
||||
await showPromise;
|
||||
ok(false, "Promise returned by show() should not resolve after clicking cancel button");
|
||||
} catch (ex) {
|
||||
ok(true, "Promise returned by show() should reject after clicking cancel button");
|
||||
}
|
||||
await ContentTaskUtils.waitForCondition(() => dialog.hidden,
|
||||
"Waiting for the dialog to be hidden");
|
||||
ok(dialog.hidden, "Dialog should be hidden after clicking cancel button");
|
||||
|
||||
showPromise = dialog.show();
|
||||
dismissButton.click();
|
||||
try {
|
||||
await showPromise;
|
||||
ok(false, "Promise returned by show() should not resolve after clicking dismiss button");
|
||||
} catch (ex) {
|
||||
ok(true, "Promise returned by show() should reject after clicking dismiss button");
|
||||
}
|
||||
await ContentTaskUtils.waitForCondition(() => dialog.hidden,
|
||||
"Waiting for the dialog to be hidden");
|
||||
ok(dialog.hidden, "Dialog should be hidden after clicking dismiss button");
|
||||
|
||||
showPromise = dialog.show();
|
||||
confirmDeleteButton.click();
|
||||
try {
|
||||
await showPromise;
|
||||
ok(true, "Promise returned by show() should resolve after clicking confirm button");
|
||||
} catch (ex) {
|
||||
ok(false, "Promise returned by show() should not reject after clicking confirm button");
|
||||
}
|
||||
await ContentTaskUtils.waitForCondition(() => dialog.hidden,
|
||||
"Waiting for the dialog to be hidden");
|
||||
ok(dialog.hidden, "Dialog should be hidden after clicking confirm button");
|
||||
});
|
||||
});
|
|
@ -19,8 +19,7 @@ add_task(async function test() {
|
|||
loginItem.setLogin(Cu.cloneInto(login, content));
|
||||
|
||||
// Lower the timeout for the test.
|
||||
let copyButton = loginItem.shadowRoot.querySelector(".copy-username-button");
|
||||
Object.defineProperty(copyButton.constructor, "BUTTON_RESET_TIMEOUT", {
|
||||
Object.defineProperty(loginItem.constructor, "COPY_BUTTON_RESET_TIMEOUT", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: 1000,
|
||||
|
@ -40,9 +39,8 @@ add_task(async function test() {
|
|||
await ContentTask.spawn(browser, testObj, async function(aTestObj) {
|
||||
let loginItem = content.document.querySelector("login-item");
|
||||
let copyButton = loginItem.shadowRoot.querySelector(aTestObj.copyButtonSelector);
|
||||
let innerButton = copyButton.shadowRoot.querySelector("button");
|
||||
info("Clicking 'copy' button");
|
||||
innerButton.click();
|
||||
copyButton.click();
|
||||
});
|
||||
});
|
||||
ok(true, testObj.expectedValue + " is on clipboard now");
|
||||
|
|
|
@ -45,6 +45,10 @@ add_task(async function test_login_item() {
|
|||
|
||||
let deleteButton = loginItem.shadowRoot.querySelector(".delete-button");
|
||||
deleteButton.click();
|
||||
|
||||
let confirmDeleteDialog = Cu.waiveXrays(content.document.querySelector("confirm-delete-dialog"));
|
||||
let confirmButton = confirmDeleteDialog.shadowRoot.querySelector(".confirm-button");
|
||||
confirmButton.click();
|
||||
});
|
||||
ok(deleteLoginMessageReceived,
|
||||
"Clicking the delete button should send the AboutLogins:DeleteLogin messsage");
|
||||
|
|
|
@ -80,6 +80,9 @@ add_task(async function test_login_item() {
|
|||
ok(loginItem.dataset.editing, "LoginItem should be in 'edit' mode");
|
||||
let deleteButton = loginItem.shadowRoot.querySelector(".delete-button");
|
||||
deleteButton.click();
|
||||
let confirmDeleteDialog = Cu.waiveXrays(content.document.querySelector("confirm-delete-dialog"));
|
||||
let confirmDeleteButton = confirmDeleteDialog.shadowRoot.querySelector(".confirm-button");
|
||||
confirmDeleteButton.click();
|
||||
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
loginListItem = Cu.waiveXrays(loginList.shadowRoot.querySelector("login-list-item"));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
/* exported asyncElementRendered, importDependencies, stubFluentL10n */
|
||||
/* exported asyncElementRendered, importDependencies */
|
||||
|
||||
/**
|
||||
* A helper to await on while waiting for an asynchronous rendering of a Custom
|
||||
|
@ -25,18 +25,24 @@ function importDependencies(templateFrame, destinationEl) {
|
|||
}
|
||||
}
|
||||
|
||||
function stubFluentL10n(argsMap) {
|
||||
Object.defineProperty(document, "l10n", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: {
|
||||
setAttributes(element, id, args) {
|
||||
element.setAttribute("data-l10n-id", id);
|
||||
for (let attrName of Object.keys(argsMap)) {
|
||||
let varName = argsMap[attrName];
|
||||
element.setAttribute(attrName, args[varName]);
|
||||
}
|
||||
},
|
||||
Object.defineProperty(document, "l10n", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: {
|
||||
connectRoot() {
|
||||
},
|
||||
});
|
||||
}
|
||||
translateElements() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getAttributes(element) {
|
||||
return {
|
||||
id: element.getAttribute("data-l10n-id"),
|
||||
args: JSON.parse(element.getAttribute("data-l10n-args")),
|
||||
};
|
||||
},
|
||||
setAttributes(element, id, args) {
|
||||
element.setAttribute("data-l10n-id", id);
|
||||
element.setAttribute("data-l10n-args", JSON.stringify(args));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,8 +3,8 @@ scheme = https
|
|||
support-files =
|
||||
aboutlogins_common.js
|
||||
|
||||
[test_confirm_delete_dialog.html]
|
||||
[test_login_filter.html]
|
||||
[test_login_item.html]
|
||||
[test_login_list.html]
|
||||
[test_menu_button.html]
|
||||
[test_reflected_fluent_element.html]
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test the confirm-delete-dialog component
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test the confirm-delete-dialog component</title>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="module" src="chrome://browser/content/aboutlogins/components/confirm-delete-dialog.js"></script>
|
||||
<script src="aboutlogins_common.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display">
|
||||
</p>
|
||||
<div id="content" style="display: none">
|
||||
<iframe id="templateFrame" src="chrome://browser/content/aboutlogins/aboutLogins.html"
|
||||
sandbox="allow-same-origin"></iframe>
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
<script>
|
||||
/** Test the confirm-delete-dialog component **/
|
||||
|
||||
let cancelButton, confirmButton, gConfirmDeleteDialog;
|
||||
add_task(async function setup() {
|
||||
let templateFrame = document.getElementById("templateFrame");
|
||||
let displayEl = document.getElementById("display");
|
||||
importDependencies(templateFrame, displayEl);
|
||||
|
||||
gConfirmDeleteDialog = document.createElement("confirm-delete-dialog");
|
||||
displayEl.appendChild(gConfirmDeleteDialog);
|
||||
ok(gConfirmDeleteDialog, "The dialog should exist");
|
||||
|
||||
cancelButton = gConfirmDeleteDialog.shadowRoot.querySelector(".cancel-button");
|
||||
confirmButton = gConfirmDeleteDialog.shadowRoot.querySelector(".confirm-button");
|
||||
ok(cancelButton, "The cancel button should exist");
|
||||
ok(confirmButton, "The confirm button should exist");
|
||||
});
|
||||
|
||||
add_task(async function test_escape_key_to_cancel() {
|
||||
gConfirmDeleteDialog.show();
|
||||
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
|
||||
sendKey("ESCAPE");
|
||||
ok(gConfirmDeleteDialog.hidden, "The dialog should be hidden after hitting Escape");
|
||||
gConfirmDeleteDialog.hide();
|
||||
});
|
||||
|
||||
add_task(async function test_initial_focus() {
|
||||
gConfirmDeleteDialog.show();
|
||||
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
|
||||
is(gConfirmDeleteDialog.shadowRoot.activeElement, cancelButton,
|
||||
"After initially opening the dialog, the cancel button should be focused");
|
||||
gConfirmDeleteDialog.hide();
|
||||
});
|
||||
|
||||
add_task(async function test_tab_focus() {
|
||||
gConfirmDeleteDialog.show();
|
||||
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
|
||||
sendKey("TAB");
|
||||
is(gConfirmDeleteDialog.shadowRoot.activeElement, confirmButton,
|
||||
"After opening the dialog and tabbing once, the confirm delete button should be focused");
|
||||
gConfirmDeleteDialog.hide();
|
||||
});
|
||||
|
||||
add_task(async function test_enter_key_to_cancel() {
|
||||
let showPromise = gConfirmDeleteDialog.show();
|
||||
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
|
||||
sendKey("RETURN");
|
||||
try {
|
||||
await showPromise;
|
||||
ok(false, "The dialog Promise should not resolve after hitting Return with the cancel button focused");
|
||||
} catch (ex) {
|
||||
ok(true, "The dialog Promise should reject after hitting Return with the cancel button focused");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_enter_key_to_confirm() {
|
||||
let showPromise = gConfirmDeleteDialog.show();
|
||||
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
|
||||
sendKey("TAB");
|
||||
sendKey("RETURN");
|
||||
try {
|
||||
await showPromise;
|
||||
ok(true, "The dialog Promise should resolve after hitting Return with the confirm button focused");
|
||||
} catch (ex) {
|
||||
ok(false, "The dialog Promise should not reject after hitting Return with the confirm button focused");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -29,10 +29,6 @@ Test the login-filter component
|
|||
let gLoginFilter;
|
||||
let gLoginList;
|
||||
add_task(async function setup() {
|
||||
stubFluentL10n({
|
||||
"count": "count",
|
||||
});
|
||||
|
||||
let templateFrame = document.getElementById("templateFrame");
|
||||
let displayEl = document.getElementById("display");
|
||||
importDependencies(templateFrame, displayEl);
|
||||
|
@ -113,12 +109,10 @@ add_task(async function test_list_filtered() {
|
|||
sendString(testObj.query);
|
||||
|
||||
await SimpleTest.promiseWaitForCondition(() => {
|
||||
return gLoginList.hasAttribute("count") &&
|
||||
+gLoginList.getAttribute("count") == testObj.resultExpectedCount;
|
||||
let countElement = gLoginList.shadowRoot.querySelector(".count");
|
||||
return countElement.hasAttribute("data-l10n-args") &&
|
||||
JSON.parse(countElement.getAttribute("data-l10n-args")).count == testObj.resultExpectedCount;
|
||||
}, `Waiting for the search result count to update to ${testObj.resultExpectedCount} (tc#${testObj.testCase})`);
|
||||
let count = +gLoginList.getAttribute("count");
|
||||
is(count, testObj.resultExpectedCount,
|
||||
`The login list count should match the expected result (tc#${testObj.testCase})`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -47,12 +47,6 @@ const TEST_LOGIN_2 = {
|
|||
};
|
||||
|
||||
add_task(async function setup() {
|
||||
stubFluentL10n({
|
||||
"time-created": "timeCreated",
|
||||
"time-changed": "timeChanged",
|
||||
"time-used": "timeUsed",
|
||||
});
|
||||
|
||||
let templateFrame = document.getElementById("templateFrame");
|
||||
let displayEl = document.getElementById("display");
|
||||
importDependencies(templateFrame, displayEl);
|
||||
|
@ -66,9 +60,9 @@ add_task(async function test_empty_item() {
|
|||
is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, "", "origin should be blank");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, "", "username should be blank");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, "", "password should be blank");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-created").textContent, "", "time-created should be blank when undefined");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-changed").textContent, "", "time-changed should be blank when undefined");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-used").textContent, "", "time-used should be blank when undefined");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, "", "time-created should be blank when undefined");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, "", "time-changed should be blank when undefined");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, "", "time-used should be blank when undefined");
|
||||
});
|
||||
|
||||
add_task(async function test_set_login() {
|
||||
|
@ -80,9 +74,9 @@ add_task(async function test_set_login() {
|
|||
is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, TEST_LOGIN_1.origin, "origin should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, TEST_LOGIN_1.username, "username should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, TEST_LOGIN_1.password, "password should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-created").textContent, TEST_LOGIN_1.timeCreated, "time-created should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-changed").textContent, TEST_LOGIN_1.timePasswordChanged, "time-changed should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-used").textContent, TEST_LOGIN_1.timeLastUsed, "time-used should be populated");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, TEST_LOGIN_1.timeCreated, "time-created should be populated");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, TEST_LOGIN_1.timePasswordChanged, "time-changed should be populated");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, TEST_LOGIN_1.timeLastUsed, "time-used should be populated");
|
||||
});
|
||||
|
||||
add_task(async function test_edit_login() {
|
||||
|
@ -98,9 +92,9 @@ add_task(async function test_edit_login() {
|
|||
is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, TEST_LOGIN_1.origin, "origin should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, TEST_LOGIN_1.username, "username should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, TEST_LOGIN_1.password, "password should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-created").textContent, TEST_LOGIN_1.timeCreated, "time-created should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-changed").textContent, TEST_LOGIN_1.timePasswordChanged, "time-changed should be populated");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-used").textContent, TEST_LOGIN_1.timeLastUsed, "time-used should be populated");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, TEST_LOGIN_1.timeCreated, "time-created should be populated");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, TEST_LOGIN_1.timePasswordChanged, "time-changed should be populated");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, TEST_LOGIN_1.timeLastUsed, "time-used should be populated");
|
||||
|
||||
gLoginItem.shadowRoot.querySelector("input[name='username']").value = "newUsername";
|
||||
gLoginItem.shadowRoot.querySelector("input[name='password']").value = "newPassword";
|
||||
|
@ -158,9 +152,9 @@ add_task(async function test_set_login_empty() {
|
|||
is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, "", "origin should be empty");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, "", "username should be empty");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, "", "password should be empty");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-created").textContent, "", "time-created should be blank when undefined");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-changed").textContent, "", "time-changed should be blank when undefined");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-used").textContent, "", "time-used should be blank when undefined");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, "", "time-created should be blank when undefined");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, "", "time-changed should be blank when undefined");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, "", "time-used should be blank when undefined");
|
||||
|
||||
let createEventDispatched = false;
|
||||
document.addEventListener("AboutLoginsCreateLogin", event => {
|
||||
|
@ -211,9 +205,9 @@ add_task(async function test_different_login_modified() {
|
|||
is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, TEST_LOGIN_1.origin, "origin should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, TEST_LOGIN_1.username, "username should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, TEST_LOGIN_1.password, "password should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-created").textContent, TEST_LOGIN_1.timeCreated, "time-created should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-changed").textContent, TEST_LOGIN_1.timePasswordChanged, "time-changed should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-used").textContent, TEST_LOGIN_1.timeLastUsed, "time-used should be unchanged");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, TEST_LOGIN_1.timeCreated, "time-created should be unchanged");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, TEST_LOGIN_1.timePasswordChanged, "time-changed should be unchanged");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, TEST_LOGIN_1.timeLastUsed, "time-used should be unchanged");
|
||||
});
|
||||
|
||||
add_task(async function test_different_login_removed() {
|
||||
|
@ -225,9 +219,9 @@ add_task(async function test_different_login_removed() {
|
|||
is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, TEST_LOGIN_1.origin, "origin should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, TEST_LOGIN_1.username, "username should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, TEST_LOGIN_1.password, "password should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-created").textContent, TEST_LOGIN_1.timeCreated, "time-created should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-changed").textContent, TEST_LOGIN_1.timePasswordChanged, "time-changed should be unchanged");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-used").textContent, TEST_LOGIN_1.timeLastUsed, "time-used should be unchanged");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, TEST_LOGIN_1.timeCreated, "time-created should be unchanged");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, TEST_LOGIN_1.timePasswordChanged, "time-changed should be unchanged");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, TEST_LOGIN_1.timeLastUsed, "time-used should be unchanged");
|
||||
});
|
||||
|
||||
add_task(async function test_login_modified() {
|
||||
|
@ -239,9 +233,9 @@ add_task(async function test_login_modified() {
|
|||
is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, modifiedLogin.origin, "origin should be updated");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, modifiedLogin.username, "username should be updated");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, modifiedLogin.password, "password should be updated");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-created").textContent, modifiedLogin.timeCreated, "time-created should be updated");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-changed").textContent, modifiedLogin.timePasswordChanged, "time-changed should be updated");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-used").textContent, modifiedLogin.timeLastUsed, "time-used should be updated");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, modifiedLogin.timeCreated, "time-created should be updated");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, modifiedLogin.timePasswordChanged, "time-changed should be updated");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, modifiedLogin.timeLastUsed, "time-used should be updated");
|
||||
});
|
||||
|
||||
add_task(async function test_login_removed() {
|
||||
|
@ -252,9 +246,9 @@ add_task(async function test_login_removed() {
|
|||
is(gLoginItem.shadowRoot.querySelector("input[name='origin']").value, "", "origin should be cleared");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='username']").value, "", "username should be cleared");
|
||||
is(gLoginItem.shadowRoot.querySelector("input[name='password']").value, "", "password should be cleared");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-created").textContent, "", "time-created should be blank when undefined");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-changed").textContent, "", "time-changed should be blank when undefined");
|
||||
is(gLoginItem.shadowRoot.querySelector(".time-used").textContent, "", "time-used should be blank when undefined");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, "", "time-created should be blank when undefined");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, "", "time-changed should be blank when undefined");
|
||||
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, "", "time-used should be blank when undefined");
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
@ -58,10 +58,6 @@ const TEST_LOGIN_3 = {
|
|||
};
|
||||
|
||||
add_task(async function setup() {
|
||||
stubFluentL10n({
|
||||
"count": "count",
|
||||
});
|
||||
|
||||
let templateFrame = document.getElementById("templateFrame");
|
||||
let displayEl = document.getElementById("display");
|
||||
importDependencies(templateFrame, displayEl);
|
||||
|
@ -134,10 +130,9 @@ add_task(async function test_empty_login_username_in_list() {
|
|||
ok(!loginListItems[0].dataset.guid, "first login-list-item should be the 'new' item");
|
||||
is(loginListItems[1].dataset.guid, TEST_LOGIN_3.guid, "login-list-item should have correct guid attribute");
|
||||
|
||||
loginListItems[1].setAttribute("missing-username", "(no username)");
|
||||
loginListItems[1].render();
|
||||
let loginUsername = loginListItems[1].shadowRoot.querySelector(".username");
|
||||
is(loginUsername.textContent, "(no username)", "login should show missing username text");
|
||||
is(loginUsername.getAttribute("data-l10n-id"), "login-list-item-subtitle-missing-username", "login should show missing username text");
|
||||
});
|
||||
|
||||
add_task(async function test_populated_list() {
|
||||
|
@ -163,12 +158,12 @@ add_task(async function test_populated_list() {
|
|||
add_task(async function test_filtered_list() {
|
||||
is(gLoginList.shadowRoot.querySelectorAll("login-list-item:not([hidden])").length, 2, "Both logins should be visible");
|
||||
let countSpan = gLoginList.shadowRoot.querySelector(".count");
|
||||
is(countSpan.textContent, "2", "Count should match full list length");
|
||||
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 2, "Count should match full list length");
|
||||
window.dispatchEvent(new CustomEvent("AboutLoginsFilterLogins", {
|
||||
bubbles: true,
|
||||
detail: "user1",
|
||||
}));
|
||||
is(countSpan.textContent, "1", "Count should match result amount");
|
||||
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 1, "Count should match result amount");
|
||||
let loginListItems = gLoginList.shadowRoot.querySelectorAll("login-list-item");
|
||||
is(loginListItems[0].shadowRoot.querySelector(".username").textContent, "user1", "user1 is expected first");
|
||||
ok(!loginListItems[0].hidden, "user1 should remain visible");
|
||||
|
@ -177,7 +172,7 @@ add_task(async function test_filtered_list() {
|
|||
bubbles: true,
|
||||
detail: "user2",
|
||||
}));
|
||||
is(countSpan.textContent, "1", "Count should match result amount");
|
||||
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 1, "Count should match result amount");
|
||||
loginListItems = gLoginList.shadowRoot.querySelectorAll("login-list-item");
|
||||
ok(loginListItems[0].hidden, "user1 should be hidden");
|
||||
ok(!loginListItems[1].hidden, "user2 should be visible");
|
||||
|
@ -185,7 +180,7 @@ add_task(async function test_filtered_list() {
|
|||
bubbles: true,
|
||||
detail: "user",
|
||||
}));
|
||||
is(countSpan.textContent, "2", "Count should match result amount");
|
||||
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 2, "Count should match result amount");
|
||||
loginListItems = gLoginList.shadowRoot.querySelectorAll("login-list-item");
|
||||
ok(!loginListItems[0].hidden, "user1 should be visible");
|
||||
ok(!loginListItems[1].hidden, "user2 should be visible");
|
||||
|
@ -193,7 +188,7 @@ add_task(async function test_filtered_list() {
|
|||
bubbles: true,
|
||||
detail: "foo",
|
||||
}));
|
||||
is(countSpan.textContent, "0", "Count should match result amount");
|
||||
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 0, "Count should match result amount");
|
||||
loginListItems = gLoginList.shadowRoot.querySelectorAll("login-list-item");
|
||||
ok(loginListItems[0].hidden, "user1 should be hidden");
|
||||
ok(loginListItems[1].hidden, "user2 should be hidden");
|
||||
|
@ -201,7 +196,7 @@ add_task(async function test_filtered_list() {
|
|||
bubbles: true,
|
||||
detail: "",
|
||||
}));
|
||||
is(countSpan.textContent, "2", "Count should be reset to full list length");
|
||||
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 2, "Count should be reset to full list length");
|
||||
loginListItems = gLoginList.shadowRoot.querySelectorAll("login-list-item");
|
||||
ok(!loginListItems[0].hidden, "user1 should be visible");
|
||||
ok(!loginListItems[1].hidden, "user2 should be visible");
|
||||
|
@ -248,13 +243,13 @@ add_task(async function test_login_removed() {
|
|||
|
||||
add_task(async function test_login_added_filtered() {
|
||||
let countSpan = gLoginList.shadowRoot.querySelector(".count");
|
||||
is(countSpan.textContent, "2", "Count should match full list length");
|
||||
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 2, "Count should match full list length");
|
||||
window.dispatchEvent(new CustomEvent("AboutLoginsFilterLogins", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: "user1",
|
||||
}));
|
||||
is(countSpan.textContent, "1", "Count should match result amount");
|
||||
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 1, "Count should match result amount");
|
||||
|
||||
let newLogin = Object.assign({}, TEST_LOGIN_1, {username: "user22", guid: "111222"});
|
||||
gLoginList.loginAdded(newLogin);
|
||||
|
@ -267,7 +262,7 @@ add_task(async function test_login_added_filtered() {
|
|||
ok(!loginListItems[0].hidden, "login-list-item1 should be visible");
|
||||
ok(loginListItems[1].hidden, "login-list-item2 should be hidden");
|
||||
ok(loginListItems[2].hidden, "login-list-item3 should be hidden");
|
||||
is(countSpan.textContent, "1", "Count should remain unchanged");
|
||||
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 1, "Count should remain unchanged");
|
||||
});
|
||||
|
||||
add_task(async function test_sorted_list() {
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test the reflected-fluent-element component
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test the reflected-fluent-element component</title>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="aboutlogins_common.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display">
|
||||
</p>
|
||||
<div id="content" style="display: none">
|
||||
<iframe id="templateFrame" src="chrome://browser/content/aboutlogins/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",
|
||||
"origin-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", `chrome://browser/content/aboutlogins/components/${scriptSrc}`);
|
||||
scriptEl.setAttribute("type", "module");
|
||||
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>
|
|
@ -346,7 +346,7 @@
|
|||
<button id="identity-popup-breakageReportView-submit"
|
||||
default="true"
|
||||
label="&contentBlocking.breakageReportView.sendReport.label;"
|
||||
oncommand="ContentBlocking.submitBreakageReport();"/>
|
||||
oncommand="ContentBlocking.onSubmitBreakageReportClicked();"/>
|
||||
</vbox>
|
||||
</panelview>
|
||||
</panelmultiview>
|
||||
|
|
|
@ -25,19 +25,21 @@
|
|||
</hbox>
|
||||
|
||||
<hbox id="protections-popup-tp-switch-section" class="identity-popup-section">
|
||||
<vbox id="protections-popup-tp-switch-label-box" flex="1">
|
||||
<label id="protections-popup-tp-switch-on-header"
|
||||
<vbox class="protections-popup-tp-switch-label-box" flex="1">
|
||||
<label class="protections-popup-tp-switch-on-header"
|
||||
hidden="true">Tracking protection is ON for this site</label>
|
||||
<label id="protections-popup-tp-switch-off-header"
|
||||
<label class="protections-popup-tp-switch-off-header"
|
||||
hidden="true">Tracking protection is OFF for this site</label>
|
||||
<label id="protections-popup-tp-switch-breakage-link"
|
||||
class="text-link"
|
||||
onclick="gProtectionsHandler.showSiteNotWorkingView();"
|
||||
hidden="true">Site not working?</label>
|
||||
</vbox>
|
||||
<vbox id="protections-popup-tp-switch-box">
|
||||
<vbox class="protections-popup-tp-switch-box">
|
||||
<toolbarbutton id="protections-popup-tp-switch"
|
||||
enabled="false"
|
||||
oncommand="gProtectionsHandler.onTPSwitchCommand();" />
|
||||
class="protections-popup-tp-switch"
|
||||
enabled="false"
|
||||
oncommand="gProtectionsHandler.onTPSwitchCommand();" />
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
|
@ -59,5 +61,77 @@
|
|||
href="about:protections">Show Full Report</label>
|
||||
</hbox>
|
||||
</panelview>
|
||||
|
||||
<!-- Site Not Working? SubView -->
|
||||
<panelview id="protections-popup-siteNotWorkingView"
|
||||
title="Site Not Working?"
|
||||
descriptionheightworkaround="true"
|
||||
flex="1">
|
||||
<hbox id="protections-popup-siteNotWorkingView-header">
|
||||
<vbox class="protections-popup-tp-switch-label-box" flex="1">
|
||||
<label class="protections-popup-tp-switch-on-header"
|
||||
hidden="true">Tracking protection is ON for this site</label>
|
||||
<label class="protections-popup-tp-switch-off-header"
|
||||
hidden="true">Tracking protection is OFF for this site</label>
|
||||
</vbox>
|
||||
<vbox class="protections-popup-tp-switch-box">
|
||||
<toolbarbutton id="protections-popup-siteNotWorking-tp-switch"
|
||||
class="protections-popup-tp-switch"
|
||||
enabled="false"
|
||||
oncommand="gProtectionsHandler.onTPSwitchCommand();" />
|
||||
</vbox>
|
||||
</hbox>
|
||||
<vbox id="protections-popup-siteNotWorkingView-body" flex="1">
|
||||
<label>Turn off Tracking Protection if you're having issues with:</label>
|
||||
<label>
|
||||
<html:ul id="protections-popup-siteNotWorkingView-body-issue-list">
|
||||
<html:li>Log in fields</html:li>
|
||||
<html:li>Forms</html:li>
|
||||
<html:li>Payments</html:li>
|
||||
<html:li>Comments</html:li>
|
||||
<html:li>Videos</html:li>
|
||||
</html:ul>
|
||||
</label>
|
||||
</vbox>
|
||||
<hbox id="protections-popup-siteNotWorkingView-footer"
|
||||
class="panel-footer">
|
||||
<label id="protections-popup-siteNotWorkingView-siteStillBroken" flex="1">Site Still Broken?</label>
|
||||
<label id="protections-popup-siteNotWorkingView-sendReport"
|
||||
onclick="gProtectionsHandler.showSendReportView();"
|
||||
class="text-link">Send Report</label>
|
||||
</hbox>
|
||||
</panelview>
|
||||
|
||||
|
||||
<!-- Send Report SubView -->
|
||||
<panelview id="protections-popup-sendReportView"
|
||||
title="Send Report"
|
||||
descriptionheightworkaround="true">
|
||||
<vbox id="protections-popup-sendReportView-heading">
|
||||
<description>&contentBlocking.breakageReportView2.description;</description>
|
||||
<label id="protections-popup-sendReportView-learn-more"
|
||||
is="text-link">&contentBlocking.breakageReportView.learnMore;</label>
|
||||
</vbox>
|
||||
<vbox id="protections-popup-sendReportView-body" class="panel-view-body-unscrollable">
|
||||
<vbox class="protections-popup-sendReportView-collection-section">
|
||||
<label>&contentBlocking.breakageReportView.collection.url.label;</label>
|
||||
<html:input readonly="readonly" id="protections-popup-sendReportView-collection-url"/>
|
||||
</vbox>
|
||||
<vbox class="protections-popup-sendReportView-collection-section">
|
||||
<label>&contentBlocking.breakageReportView.collection.comments.label;</label>
|
||||
<html:textarea id="protections-popup-sendReportView-collection-comments"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox id="protections-popup-sendReportView-footer"
|
||||
class="panel-footer">
|
||||
<button id="protections-popup-sendReportView-cancel"
|
||||
label="&contentBlocking.breakageReportView.cancel.label;"
|
||||
oncommand="gProtectionsHandler._protectionsPopupMultiView.goBack();"/>
|
||||
<button id="protections-popup-sendReportView-submit"
|
||||
default="true"
|
||||
label="&contentBlocking.breakageReportView.sendReport.label;"
|
||||
oncommand="gProtectionsHandler.onSendReportClicked();"/>
|
||||
</vbox>
|
||||
</panelview>
|
||||
</panelmultiview>
|
||||
</panel>
|
||||
|
|
|
@ -198,6 +198,14 @@
|
|||
["tabs"]
|
||||
]
|
||||
},
|
||||
"topSites": {
|
||||
"url": "chrome://extensions/content/parent/ext-topSites.js",
|
||||
"schema": "chrome://extensions/content/schemas/top_sites.json",
|
||||
"scopes": ["addon_parent"],
|
||||
"paths": [
|
||||
["topSites"]
|
||||
]
|
||||
},
|
||||
"urlbar": {
|
||||
"url": "chrome://browser/content/parent/ext-urlbar.js",
|
||||
"schema": "chrome://browser/content/schemas/urlbar.json",
|
||||
|
|
|
@ -19,7 +19,7 @@ const TOPIC_CONTENT_DOCUMENT_INTERACTIVE = "content-document-interactive";
|
|||
|
||||
// Automated tests ensure packaged locales are in this list. Copied output of:
|
||||
// https://github.com/mozilla/activity-stream/blob/master/bin/render-activity-stream-html.js
|
||||
const ACTIVITY_STREAM_BCP47 = "en-US ach an ar ast az be bg bn br bs ca cak crh cs cy da de dsb el en-CA en-GB eo es-AR es-CL es-ES es-MX et eu fa ff fi fr fy-NL ga-IE gd gl gn gu-IN he hi-IN hr hsb hu hy-AM ia id is it ja ja-JP-macos ka kab kk km kn ko lij lo lt ltg lv mk mr ms my nb-NO ne-NP nl nn-NO oc pa-IN pl pt-BR pt-PT rm ro ru si sk sl sq sr sv-SE ta te th tl tr trs uk ur uz vi zh-CN zh-TW".split(" ");
|
||||
const ACTIVITY_STREAM_BCP47 = "en-US ach an ar ast az be bg bn br bs ca cak crh cs cy da de dsb el en-CA en-GB eo es-AR es-CL es-ES es-MX et eu fa ff fi fr fy-NL ga-IE gd gl gn gu-IN he hi-IN hr hsb hu hy-AM ia id is it ja ka kab kk km kn ko lij lo lt ltg lv mk mr ms my nb-NO ne-NP nl nn-NO oc pa-IN pl pt-BR pt-PT rm ro ru si sk sl sq sr sv-SE ta te th tl tr trs uk ur uz vi zh-CN zh-TW".split(" ");
|
||||
|
||||
const ABOUT_URL = "about:newtab";
|
||||
const BASE_URL = "resource://activity-stream/";
|
||||
|
|
|
@ -120,9 +120,11 @@ class UrlbarView {
|
|||
* may be showing stale results.
|
||||
*/
|
||||
get visibleItemCount() {
|
||||
return Array.reduce(this._rows.children, (sum, r) => {
|
||||
return sum + Number(this._isRowVisible(r));
|
||||
}, 0);
|
||||
let sum = 0;
|
||||
for (let row of this._rows.children) {
|
||||
sum += Number(this._isRowVisible(row));
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -289,19 +289,19 @@ add_task(async function test_adaptive_mouse() {
|
|||
await PlacesUtils.history.clear();
|
||||
await bumpScore(url1, "site", {visits: 3, picks: 3}, true);
|
||||
await bumpScore(url2, "site", {visits: 3, picks: 1}, true);
|
||||
await promiseAutocompleteResultPopup("");
|
||||
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
|
||||
await promiseAutocompleteResultPopup("si");
|
||||
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
|
||||
Assert.equal(result.url, url1, "Check first result");
|
||||
result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
|
||||
result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
|
||||
Assert.equal(result.url, url2, "Check second result");
|
||||
|
||||
info("Same visit count, different picks, invert");
|
||||
await PlacesUtils.history.clear();
|
||||
await bumpScore(url1, "site", {visits: 3, picks: 1}, true);
|
||||
await bumpScore(url2, "site", {visits: 3, picks: 3}, true);
|
||||
await promiseAutocompleteResultPopup("");
|
||||
result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
|
||||
Assert.equal(result.url, url2, "Check first result");
|
||||
await promiseAutocompleteResultPopup("si");
|
||||
result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
|
||||
Assert.equal(result.url, url2, "Check first result");
|
||||
result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
|
||||
Assert.equal(result.url, url1, "Check second result");
|
||||
});
|
||||
|
|
|
@ -9,12 +9,9 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
#identity-popup {
|
||||
--identity-popup-width: 33rem;
|
||||
}
|
||||
|
||||
#identity-popup,
|
||||
#protections-popup {
|
||||
--protections-popup-width: 33rem;
|
||||
--popup-width: 33rem;
|
||||
}
|
||||
|
||||
/* This is used by screenshots tests to hide intermittently different
|
||||
|
@ -78,13 +75,13 @@
|
|||
}
|
||||
|
||||
#identity-popup-mainView {
|
||||
min-width: var(--identity-popup-width);
|
||||
max-width: var(--identity-popup-width);
|
||||
min-width: var(--popup-width);
|
||||
max-width: var(--popup-width);
|
||||
}
|
||||
|
||||
#protections-popup-mainView {
|
||||
min-width: var(--protections-popup-width);
|
||||
max-width: var(--protections-popup-width);
|
||||
min-width: var(--popup-width);
|
||||
max-width: var(--popup-width);
|
||||
}
|
||||
|
||||
#protections-popup[toast] #protections-popup-mainView > :not(#protections-popup-mainView-panel-header),
|
||||
|
@ -192,6 +189,7 @@
|
|||
#identity-popup-trackersView > .panel-header,
|
||||
#identity-popup-securityView > .panel-header,
|
||||
#identity-popup-breakageReportView > .panel-header,
|
||||
#protections-popup-sendReportView > .panel-header,
|
||||
#identity-popup-content-blocking-report-breakage,
|
||||
.identity-popup-content-blocking-category-label,
|
||||
.identity-popup-content-blocking-category-state-label,
|
||||
|
@ -210,7 +208,8 @@
|
|||
}
|
||||
|
||||
#identity-popup-mainView-panel-header,
|
||||
#protections-popup-mainView-panel-header {
|
||||
#protections-popup-mainView-panel-header,
|
||||
#protections-popup-siteNotWorkingView-footer {
|
||||
padding: 4px 1em;
|
||||
min-height: 40px;
|
||||
-moz-box-pack: center;
|
||||
|
@ -240,7 +239,13 @@
|
|||
overflow-wrap: break-word;
|
||||
/* This is needed for the overflow-wrap to work correctly.
|
||||
* 33em is the panel width, panel-header has 1em padding on each side. */
|
||||
max-width: calc(var(--identity-popup-width) - 2em);
|
||||
max-width: calc(var(--popup-width) - 2em);
|
||||
}
|
||||
|
||||
#protections-popup-mainView-panel-header-span,
|
||||
#protections-popup-toast-panel-tp-on-desc,
|
||||
#protections-popup-toast-panel-tp-off-desc {
|
||||
max-width: calc(var(--popup-width) - 2em);
|
||||
}
|
||||
|
||||
#identity-popup-permissions-content > description,
|
||||
|
@ -263,7 +268,7 @@
|
|||
/* This is needed for the overflow-wrap to work correctly.
|
||||
* 1em + 2em + 24px is .identity-popup-security-content padding
|
||||
* 33em is the panel width */
|
||||
max-width: calc(var(--identity-popup-width) - 3rem - 24px);
|
||||
max-width: calc(var(--popup-width) - 3rem - 24px);
|
||||
}
|
||||
|
||||
.identity-popup-warning-gray {
|
||||
|
@ -371,33 +376,41 @@ description#identity-popup-content-verifier,
|
|||
|
||||
/* CONTENT BLOCKING / TRACKING PROTECTION */
|
||||
|
||||
#identity-popup-breakageReportView-footer {
|
||||
#identity-popup-breakageReportView-footer,
|
||||
#protections-popup-sendReportView-footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#identity-popup-breakageReportView-footer > button {
|
||||
#identity-popup-breakageReportView-footer > button,
|
||||
#protections-popup-sendReportView-footer > button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#identity-popup-breakageReportView-heading,
|
||||
#identity-popup-breakageReportView-body {
|
||||
#identity-popup-breakageReportView-body,
|
||||
#protections-popup-sendReportView-heading,
|
||||
#protections-popup-sendReportView-body {
|
||||
padding: 16px;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
.identity-popup-breakageReportView-collection-section {
|
||||
.identity-popup-breakageReportView-collection-section,
|
||||
.protections-popup-sendReportView-collection-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
#identity-popup-breakageReportView-body {
|
||||
#identity-popup-breakageReportView-body,
|
||||
#protections-popup-sendReportView-body {
|
||||
border-top: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
||||
#identity-popup-breakageReportView-collection-url {
|
||||
#identity-popup-breakageReportView-collection-url,
|
||||
#protections-popup-sendReportView-collection-url {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
#identity-popup-breakageReportView-collection-comments {
|
||||
#identity-popup-breakageReportView-collection-comments,
|
||||
#protections-popup-sendReportView-collection-comments {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
|
@ -545,8 +558,8 @@ description#identity-popup-content-verifier,
|
|||
#identity-popup-trackersView-strict-info {
|
||||
min-height: 40px;
|
||||
/* Limit to full width - margin */
|
||||
max-width: calc(var(--identity-popup-width) - 12px);
|
||||
min-width: calc(var(--identity-popup-width) - 12px);
|
||||
max-width: calc(var(--popup-width) - 12px);
|
||||
min-width: calc(var(--popup-width) - 12px);
|
||||
background-color: #45a1ff80;
|
||||
margin: 6px;
|
||||
text-align: center;
|
||||
|
@ -566,7 +579,7 @@ description#identity-popup-content-verifier,
|
|||
#identity-popup-trackersView-strict-info > label {
|
||||
overflow-wrap: break-word;
|
||||
/* Limit to full width - container margin - container padding - icon width - icon margin */
|
||||
max-width: calc(var(--identity-popup-width) - 12px - 20px - 16px - 10px);
|
||||
max-width: calc(var(--popup-width) - 12px - 20px - 16px - 10px);
|
||||
}
|
||||
|
||||
/* Content Blocking categories */
|
||||
|
@ -811,8 +824,8 @@ description#identity-popup-content-verifier,
|
|||
display: none;
|
||||
}
|
||||
|
||||
#protections-popup:not([hasException]) #protections-popup-tp-switch-on-header,
|
||||
#protections-popup[hasException] #protections-popup-tp-switch-off-header,
|
||||
#protections-popup:not([hasException]) .protections-popup-tp-switch-on-header,
|
||||
#protections-popup[hasException] .protections-popup-tp-switch-off-header,
|
||||
#protections-popup:not([hasException])[toast] #protections-popup-toast-panel-tp-on-desc,
|
||||
#protections-popup[hasException][toast] #protections-popup-toast-panel-tp-off-desc {
|
||||
display: unset;
|
||||
|
@ -832,8 +845,9 @@ description#identity-popup-content-verifier,
|
|||
);
|
||||
}
|
||||
|
||||
#protections-popup-tp-switch-label-box,
|
||||
#protections-popup-tp-switch-box {
|
||||
#protections-popup-siteNotWorkingView-body,
|
||||
.protections-popup-tp-switch-label-box,
|
||||
.protections-popup-tp-switch-box {
|
||||
padding: 4px 1em;
|
||||
min-height: 40px;
|
||||
-moz-box-pack: center;
|
||||
|
@ -842,12 +856,12 @@ description#identity-popup-content-verifier,
|
|||
position: relative;
|
||||
}
|
||||
|
||||
#protections-popup-tp-switch-on-header,
|
||||
#protections-popup-tp-switch-off-header {
|
||||
.protections-popup-tp-switch-on-header,
|
||||
.protections-popup-tp-switch-off-header {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#protections-popup-tp-switch {
|
||||
.protections-popup-tp-switch {
|
||||
-moz-appearance: none;
|
||||
box-sizing: border-box;
|
||||
min-width: 26px;
|
||||
|
@ -857,12 +871,12 @@ description#identity-popup-content-verifier,
|
|||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
margin-inline-start: 1px;
|
||||
margin-inline-end: 7px;
|
||||
padding: 2px;
|
||||
padding-inline-end: 0;
|
||||
transition: padding .2s ease;
|
||||
}
|
||||
|
||||
#protections-popup-tp-switch::before {
|
||||
.protections-popup-tp-switch::before {
|
||||
position: relative;
|
||||
display: block;
|
||||
content: "";
|
||||
|
@ -872,23 +886,31 @@ description#identity-popup-content-verifier,
|
|||
background: white;
|
||||
}
|
||||
|
||||
#protections-popup-tp-switch[enabled] {
|
||||
.protections-popup-tp-switch[enabled] {
|
||||
background-color: #0a84ff;
|
||||
border: 1px solid #0a84ff;
|
||||
/* Push the toggle to the right. */
|
||||
padding-inline-start: 12px;
|
||||
}
|
||||
|
||||
#protections-popup-tp-switch:hover,
|
||||
#protections-popup-tp-switch:-moz-focusring {
|
||||
.protections-popup-tp-switch:hover,
|
||||
.protections-popup-tp-switch:-moz-focusring {
|
||||
border: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
||||
#protections-popup-tp-switch[enabled=true]:hover,
|
||||
#protections-popup-tp-switch[enabled=true]:-moz-focusring {
|
||||
.protections-popup-tp-switch[enabled=true]:hover,
|
||||
.protections-popup-tp-switch[enabled=true]:-moz-focusring {
|
||||
background-color: #45a1ff;
|
||||
}
|
||||
|
||||
#protections-popup-siteNotWorkingView-body-issue-list {
|
||||
padding-inline-start: 1em;
|
||||
}
|
||||
|
||||
#protections-popup-siteNotWorkingView-footer {
|
||||
border-top: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
||||
#protections-popup-settings-section {
|
||||
padding: 4px;
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
|
|
|
@ -204,7 +204,7 @@ button {
|
|||
height: 12px;
|
||||
vertical-align: 0;
|
||||
margin-inline-end: 8px;
|
||||
background-image: url(chrome://devtools/skin/images/alert.svg);
|
||||
background-image: url(chrome://devtools/skin/images/alert-small.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
|
|
@ -52,8 +52,9 @@ AccessibilityView.prototype = {
|
|||
* @param {JSON} supports a collection of flags indicating which accessibility
|
||||
* panel features are supported by the current serverside
|
||||
* version.
|
||||
* @param {Array} fluentBundles array of FluentBundles elements for localization
|
||||
*/
|
||||
async initialize(accessibility, walker, supports) {
|
||||
async initialize(accessibility, walker, supports, fluentBundles) {
|
||||
// Make sure state is reset every time accessibility panel is initialized.
|
||||
await this.store.dispatch(reset(accessibility, supports));
|
||||
const container = document.getElementById("content");
|
||||
|
@ -63,7 +64,7 @@ AccessibilityView.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
const mainFrame = MainFrame({ accessibility, walker });
|
||||
const mainFrame = MainFrame({ accessibility, walker, fluentBundles });
|
||||
// Render top level component
|
||||
const provider = createElement(Provider, { store: this.store }, mainFrame);
|
||||
this.mainFrame = ReactDOM.render(provider, container);
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
--accessible-label-color: var(--grey-60);
|
||||
/* Similarly to webconsole, add more padding before the toolbar group. */
|
||||
--separator-inline-margin: 5px;
|
||||
--accessibility-code-background: var(--grey-20);
|
||||
}
|
||||
|
||||
:root.theme-dark {
|
||||
|
@ -42,6 +43,7 @@
|
|||
--accessible-label-background-color: var(--grey-80);
|
||||
--accessible-label-border-color: var(--grey-50);
|
||||
--accessible-label-color: var(--grey-40);
|
||||
--accessibility-code-background: var(--grey-70);
|
||||
}
|
||||
|
||||
/* General */
|
||||
|
@ -660,8 +662,31 @@ body {
|
|||
white-space: initial;
|
||||
}
|
||||
|
||||
/* Color Contrast */
|
||||
.accessibility-color-contrast-check,
|
||||
/* Checks */
|
||||
.accessibility-check code {
|
||||
background-color: var(--accessibility-code-background);
|
||||
border-radius: 2px;
|
||||
box-decoration-break: clone;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.accessibility-text-label-check .icon {
|
||||
display: inline;
|
||||
-moz-context-properties: fill;
|
||||
vertical-align: top;
|
||||
margin-block-start: 2px;
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
|
||||
.accessibility-text-label-check .icon.fail {
|
||||
fill: var(--theme-icon-error-color);
|
||||
}
|
||||
|
||||
.accessibility-text-label-check .icon.WARNING {
|
||||
fill: var(--theme-icon-warning-color);
|
||||
}
|
||||
|
||||
.accessibility-check,
|
||||
.accessibility-color-contrast {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
@ -669,7 +694,7 @@ body {
|
|||
height: inherit;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-check {
|
||||
.accessibility-check {
|
||||
flex-direction: column;
|
||||
padding: 4px var(--accessibility-horizontal-indent);
|
||||
line-height: 20px;
|
||||
|
@ -679,20 +704,21 @@ body {
|
|||
align-items: baseline;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-header {
|
||||
.accessibility-check-header {
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
font-size: var(--accessibility-font-size);
|
||||
line-height: var(--accessibility-toolbar-height);
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation {
|
||||
.accessibility-check-annotation {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
white-space: normal;
|
||||
color: var(--accessible-label-color);
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation .link {
|
||||
.accessibility-check-annotation .link {
|
||||
color: var(--accessibility-link-color);
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
|
@ -700,16 +726,16 @@ body {
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation .link:hover:not(:focus) {
|
||||
.accessibility-check-annotation .link:hover:not(:focus) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation .link:focus:not(:active) {
|
||||
.accessibility-check-annotation .link:focus:not(:active) {
|
||||
box-shadow: 0 0 0 2px var(--accessibility-toolbar-focus), 0 0 0 4px var(--accessibility-toolbar-focus-alpha30);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation .link:active {
|
||||
.accessibility-check-annotation .link:active {
|
||||
color: var(--accessibility-link-color-active);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ const { div } = require("devtools/client/shared/vendor/react-dom-factories");
|
|||
const List = createFactory(require("devtools/client/shared/components/List").List);
|
||||
const ColorContrastCheck =
|
||||
createFactory(require("./ColorContrastAccessibility").ColorContrastCheck);
|
||||
const TextLabelCheck = createFactory(require("./TextLabelCheck"));
|
||||
const { L10N } = require("../utils/l10n");
|
||||
|
||||
const { accessibility: { AUDIT_TYPE } } = require("devtools/shared/constants");
|
||||
|
@ -39,6 +40,10 @@ class Checks extends Component {
|
|||
return ColorContrastCheck(contrastRatio);
|
||||
}
|
||||
|
||||
[AUDIT_TYPE.TEXT_LABEL](textLabelCheck) {
|
||||
return TextLabelCheck(textLabelCheck);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { audit, labelledby } = this.props;
|
||||
if (!audit) {
|
||||
|
|
|
@ -152,7 +152,7 @@ class ContrastAnnotationClass extends Component {
|
|||
return (
|
||||
LearnMoreLink(
|
||||
{
|
||||
className: "accessibility-color-contrast-annotation",
|
||||
className: "accessibility-check-annotation",
|
||||
href: A11Y_CONTRAST_LEARN_MORE_LINK,
|
||||
learnMoreStringKey: "accessibility.learnMore",
|
||||
l10n: L10N,
|
||||
|
@ -178,10 +178,10 @@ class ColorContrastCheck extends Component {
|
|||
return (
|
||||
div({
|
||||
role: "presentation",
|
||||
className: "accessibility-color-contrast-check",
|
||||
className: "accessibility-check",
|
||||
},
|
||||
h3({
|
||||
className: "accessibility-color-contrast-header",
|
||||
className: "accessibility-check-header",
|
||||
}, L10N.getStr("accessibility.contrast.header")),
|
||||
ColorContrastAccessibility(this.props),
|
||||
!error && ContrastAnnotation(this.props)
|
||||
|
|
|
@ -12,6 +12,10 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
|||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { reset } = require("../actions/ui");
|
||||
|
||||
// Localization
|
||||
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
||||
const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
|
||||
|
||||
// Constants
|
||||
const { SIDEBAR_WIDTH, PORTRAIT_MODE_WIDTH } = require("../constants");
|
||||
|
||||
|
@ -31,6 +35,7 @@ class MainFrame extends Component {
|
|||
static get propTypes() {
|
||||
return {
|
||||
accessibility: PropTypes.object.isRequired,
|
||||
fluentBundles: PropTypes.array.isRequired,
|
||||
walker: PropTypes.object.isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
|
@ -91,7 +96,7 @@ class MainFrame extends Component {
|
|||
* Render Accessibility panel content
|
||||
*/
|
||||
render() {
|
||||
const { accessibility, walker, enabled, auditing } = this.props;
|
||||
const { accessibility, walker, fluentBundles, enabled, auditing } = this.props;
|
||||
|
||||
if (!enabled) {
|
||||
return Description({ accessibility });
|
||||
|
@ -100,7 +105,7 @@ class MainFrame extends Component {
|
|||
// Audit is currently running.
|
||||
const isAuditing = auditing.length > 0;
|
||||
|
||||
return (
|
||||
return LocalizationProvider({ messages: fluentBundles },
|
||||
div({ className: "mainFrame", role: "presentation" },
|
||||
Toolbar({ accessibility, walker }),
|
||||
isAuditing && AuditProgressOverlay(),
|
||||
|
@ -122,8 +127,10 @@ class MainFrame extends Component {
|
|||
}, AccessibilityTree({ walker })),
|
||||
endPanel: RightSidebar({ walker }),
|
||||
vert: this.useLandscapeMode,
|
||||
})),
|
||||
));
|
||||
})
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,335 @@
|
|||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
// React
|
||||
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
||||
const Localized = createFactory(FluentReact.Localized);
|
||||
|
||||
const { openDocLink } = require("devtools/client/shared/link");
|
||||
|
||||
const { A11Y_TEXT_LABEL_LINKS } = require("../constants");
|
||||
|
||||
const {
|
||||
accessibility: {
|
||||
AUDIT_TYPE: { TEXT_LABEL },
|
||||
ISSUE_TYPE: {
|
||||
[TEXT_LABEL]: {
|
||||
AREA_NO_NAME_FROM_ALT,
|
||||
DIALOG_NO_NAME,
|
||||
DOCUMENT_NO_TITLE,
|
||||
EMBED_NO_NAME,
|
||||
FIGURE_NO_NAME,
|
||||
FORM_FIELDSET_NO_NAME,
|
||||
FORM_FIELDSET_NO_NAME_FROM_LEGEND,
|
||||
FORM_NO_NAME,
|
||||
FORM_NO_VISIBLE_NAME,
|
||||
FORM_OPTGROUP_NO_NAME,
|
||||
FORM_OPTGROUP_NO_NAME_FROM_LABEL,
|
||||
FRAME_NO_NAME,
|
||||
HEADING_NO_CONTENT,
|
||||
HEADING_NO_NAME,
|
||||
IFRAME_NO_NAME_FROM_TITLE,
|
||||
IMAGE_NO_NAME,
|
||||
INTERACTIVE_NO_NAME,
|
||||
MATHML_GLYPH_NO_NAME,
|
||||
TOOLBAR_NO_NAME,
|
||||
},
|
||||
},
|
||||
SCORES: { BEST_PRACTICES, FAIL, WARNING },
|
||||
},
|
||||
} = require("devtools/shared/constants");
|
||||
|
||||
/**
|
||||
* A map from text label issues to annotation component properties.
|
||||
*/
|
||||
const ISSUE_TO_ANNOTATION_MAP = {
|
||||
[AREA_NO_NAME_FROM_ALT]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.AREA_NO_NAME_FROM_ALT,
|
||||
l10nId: "accessibility-text-label-issue-area",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "alt");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <div> since we can't provide
|
||||
// three args with the same name.
|
||||
get div() {
|
||||
return ReactDOM.code({}, "area");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// three args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "href");
|
||||
},
|
||||
},
|
||||
},
|
||||
[DIALOG_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.DIALOG_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-dialog",
|
||||
},
|
||||
[DOCUMENT_NO_TITLE]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.DOCUMENT_NO_TITLE,
|
||||
l10nId: "accessibility-text-label-issue-document-title",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "title");
|
||||
},
|
||||
},
|
||||
},
|
||||
[EMBED_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.EMBED_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-embed",
|
||||
},
|
||||
[FIGURE_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FIGURE_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-figure",
|
||||
},
|
||||
[FORM_FIELDSET_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_FIELDSET_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-fieldset",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "fieldset");
|
||||
},
|
||||
},
|
||||
},
|
||||
[FORM_FIELDSET_NO_NAME_FROM_LEGEND]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_FIELDSET_NO_NAME_FROM_LEGEND,
|
||||
l10nId: "accessibility-text-label-issue-fieldset-legend",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "legend");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// two args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "fieldset");
|
||||
},
|
||||
},
|
||||
},
|
||||
[FORM_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-form",
|
||||
},
|
||||
[FORM_NO_VISIBLE_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_NO_VISIBLE_NAME,
|
||||
l10nId: "accessibility-text-label-issue-form-visible",
|
||||
},
|
||||
[FORM_OPTGROUP_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_OPTGROUP_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-optgroup",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "optgroup");
|
||||
},
|
||||
},
|
||||
},
|
||||
[FORM_OPTGROUP_NO_NAME_FROM_LABEL]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_OPTGROUP_NO_NAME_FROM_LABEL,
|
||||
l10nId: "accessibility-text-label-issue-optgroup-label",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "label");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// two args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "optgroup");
|
||||
},
|
||||
},
|
||||
},
|
||||
[FRAME_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FRAME_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-frame",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "frame");
|
||||
},
|
||||
},
|
||||
},
|
||||
[HEADING_NO_CONTENT]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.HEADING_NO_CONTENT,
|
||||
l10nId: "accessibility-text-label-issue-heading-content",
|
||||
},
|
||||
[HEADING_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.HEADING_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-heading",
|
||||
},
|
||||
[IFRAME_NO_NAME_FROM_TITLE]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.IFRAME_NO_NAME_FROM_TITLE,
|
||||
l10nId: "accessibility-text-label-issue-iframe",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "title");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// two args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "iframe");
|
||||
},
|
||||
},
|
||||
},
|
||||
[IMAGE_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.IMAGE_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-image",
|
||||
},
|
||||
[INTERACTIVE_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.INTERACTIVE_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-interactive",
|
||||
},
|
||||
[MATHML_GLYPH_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.MATHML_GLYPH_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-glyph",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "alt");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// two args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "mglyph");
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOOLBAR_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.TOOLBAR_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-toolbar",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A map of accessibility scores to the text descriptions of check icons.
|
||||
*/
|
||||
const SCORE_TO_ICON_MAP = {
|
||||
[BEST_PRACTICES]: {
|
||||
l10nId: "accessibility-best-practices",
|
||||
src: "chrome://devtools/skin/images/info.svg",
|
||||
|
||||
},
|
||||
[FAIL]: {
|
||||
l10nId: "accessibility-fail",
|
||||
src: "chrome://devtools/skin/images/error.svg",
|
||||
},
|
||||
[WARNING]: {
|
||||
l10nId: "accessibility-warning",
|
||||
src: "chrome://devtools/skin/images/alert.svg",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Localized "Learn more" link that opens a new tab with relevant documentation.
|
||||
*/
|
||||
class LearnMoreClass extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
href: PropTypes.string,
|
||||
l10nId: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
static get defaultProps() {
|
||||
return {
|
||||
href: "#",
|
||||
l10nId: null,
|
||||
onClick: LearnMoreClass.openDocOnClick,
|
||||
};
|
||||
}
|
||||
|
||||
static openDocOnClick(event) {
|
||||
event.preventDefault();
|
||||
openDocLink(event.target.href);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { href, l10nId, onClick } = this.props;
|
||||
const className = "link";
|
||||
|
||||
return Localized({ id: l10nId }, ReactDOM.a({ className, href, onClick }));
|
||||
}
|
||||
}
|
||||
|
||||
const LearnMore = createFactory(LearnMoreClass);
|
||||
|
||||
/**
|
||||
* Renders icon with text description for the text label accessibility check.
|
||||
*
|
||||
* @param {Object}
|
||||
* Options:
|
||||
* - score: value from SCORES from "devtools/shared/constants"
|
||||
*/
|
||||
function Icon({ score }) {
|
||||
const { l10nId, src } = SCORE_TO_ICON_MAP[score];
|
||||
|
||||
return Localized({ id: l10nId, attrs: { alt: true } },
|
||||
ReactDOM.img({ src, className: `icon ${score}` })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders text description of the text label accessibility check.
|
||||
*
|
||||
* @param {Object}
|
||||
* Options:
|
||||
* - issue: value from ISSUE_TYPE[AUDIT_TYPE.TEXT_LABEL] from
|
||||
* "devtools/shared/constants"
|
||||
*/
|
||||
function Annotation({ issue }) {
|
||||
const { args, href, l10nId } = ISSUE_TO_ANNOTATION_MAP[issue];
|
||||
|
||||
return Localized({
|
||||
id: l10nId,
|
||||
a: LearnMore({ l10nId: "accessibility-learn-more", href }),
|
||||
...args,
|
||||
},
|
||||
ReactDOM.p({ className: "accessibility-check-annotation" })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for rendering a check for text label accessibliity check failures,
|
||||
* warnings and best practices suggestions association with a given
|
||||
* accessibility object in the accessibility tree.
|
||||
*/
|
||||
class TextLabelCheck extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
issue: PropTypes.string.isRequired,
|
||||
score: PropTypes.string.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { issue, score } = this.props;
|
||||
|
||||
return ReactDOM.div({
|
||||
role: "presentation",
|
||||
className: "accessibility-check",
|
||||
},
|
||||
Localized({
|
||||
id: "accessibility-text-label-header",
|
||||
},
|
||||
ReactDOM.h3({ className: "accessibility-check-header" })),
|
||||
ReactDOM.div({
|
||||
role: "presentation",
|
||||
className: "accessibility-text-label-check",
|
||||
},
|
||||
Icon({ score }),
|
||||
Annotation({ issue })
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextLabelCheck;
|
|
@ -22,5 +22,6 @@ DevToolsModules(
|
|||
'MainFrame.js',
|
||||
'RightSidebar.js',
|
||||
'TextLabelBadge.js',
|
||||
'TextLabelCheck.js',
|
||||
'Toolbar.js'
|
||||
)
|
||||
|
|
|
@ -3,7 +3,34 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { accessibility: { AUDIT_TYPE } } = require("devtools/shared/constants");
|
||||
const {
|
||||
accessibility: {
|
||||
AUDIT_TYPE,
|
||||
ISSUE_TYPE: {
|
||||
[AUDIT_TYPE.TEXT_LABEL]: {
|
||||
AREA_NO_NAME_FROM_ALT,
|
||||
DIALOG_NO_NAME,
|
||||
DOCUMENT_NO_TITLE,
|
||||
EMBED_NO_NAME,
|
||||
FIGURE_NO_NAME,
|
||||
FORM_FIELDSET_NO_NAME,
|
||||
FORM_FIELDSET_NO_NAME_FROM_LEGEND,
|
||||
FORM_NO_NAME,
|
||||
FORM_NO_VISIBLE_NAME,
|
||||
FORM_OPTGROUP_NO_NAME,
|
||||
FORM_OPTGROUP_NO_NAME_FROM_LABEL,
|
||||
FRAME_NO_NAME,
|
||||
HEADING_NO_CONTENT,
|
||||
HEADING_NO_NAME,
|
||||
IFRAME_NO_NAME_FROM_TITLE,
|
||||
IMAGE_NO_NAME,
|
||||
INTERACTIVE_NO_NAME,
|
||||
MATHML_GLYPH_NO_NAME,
|
||||
TOOLBAR_NO_NAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
} = require("devtools/shared/constants");
|
||||
|
||||
// Used in accessible component for properties tree rendering.
|
||||
exports.TREE_ROW_HEIGHT = 21;
|
||||
|
@ -84,3 +111,41 @@ exports.A11Y_LEARN_MORE_LINK =
|
|||
exports.A11Y_CONTRAST_LEARN_MORE_LINK =
|
||||
"https://developer.mozilla.org/docs/Web/Accessibility/Understanding_WCAG/Perceivable/" +
|
||||
"Color_contrast?utm_source=devtools&utm_medium=a11y-panel-checks-color-contrast";
|
||||
|
||||
const A11Y_TEXT_LABEL_LINK_BASE =
|
||||
"https://developer.mozilla.org/docs/Web/Accessibility/Understanding_WCAG/Text_labels_and_names" +
|
||||
"?utm_source=devtools&utm_medium=a11y-panel-checks-text-label";
|
||||
|
||||
const A11Y_TEXT_LABEL_LINK_IDS = {
|
||||
[AREA_NO_NAME_FROM_ALT]:
|
||||
"Use_alt_attribute_to_provide_a_name_for_areas_that_have_the_href_attribute",
|
||||
[DIALOG_NO_NAME]: "Dialogs_should_have_a_name",
|
||||
[DOCUMENT_NO_TITLE]: "Documents_must_have_a_title",
|
||||
[EMBED_NO_NAME]: "Embedded_content_must_have_a_name",
|
||||
[FIGURE_NO_NAME]: "Figures_with_optional_captions_should_have_a_name",
|
||||
[FORM_FIELDSET_NO_NAME]: "Form_element_groups_must_have_a_name",
|
||||
[FORM_FIELDSET_NO_NAME_FROM_LEGEND]:
|
||||
"Use_legend_element_to_provide_a_name_for_form_element_groups",
|
||||
[FORM_NO_NAME]: "Form_elements_must_have_a_name",
|
||||
[FORM_NO_VISIBLE_NAME]: "Form_elements_should_have_a_visible_text_label",
|
||||
[FORM_OPTGROUP_NO_NAME]: "Groupings_of_options_must_have_a_name",
|
||||
[FORM_OPTGROUP_NO_NAME_FROM_LABEL]:
|
||||
"Use_label_attribute_to_provide_a_name_for_groupings_of_options",
|
||||
[FRAME_NO_NAME]: "Frames_must_have_a_name",
|
||||
[HEADING_NO_NAME]: "Headings_must_have_a_name",
|
||||
[HEADING_NO_CONTENT]: "Headings_must_have_visible_text_content",
|
||||
[IFRAME_NO_NAME_FROM_TITLE]: "Use_title_attribute_to_describe_iframe_content",
|
||||
[IMAGE_NO_NAME]: "Content_with_images_must_have_a_name",
|
||||
[INTERACTIVE_NO_NAME]: "Interactive_elements_must_have_a_name",
|
||||
[MATHML_GLYPH_NO_NAME]:
|
||||
"Use_alt_attribute_to_provide_a_name_for_MathML_glyphs",
|
||||
[TOOLBAR_NO_NAME]:
|
||||
"Toolbars_must_have_a_name_when_there_is_more_than_one_toolbar",
|
||||
};
|
||||
|
||||
const A11Y_TEXT_LABEL_LINKS = {};
|
||||
for (const key in A11Y_TEXT_LABEL_LINK_IDS) {
|
||||
A11Y_TEXT_LABEL_LINKS[key] =
|
||||
`${A11Y_TEXT_LABEL_LINK_BASE}#${A11Y_TEXT_LABEL_LINK_IDS[key]}`;
|
||||
}
|
||||
exports.A11Y_TEXT_LABEL_LINKS = A11Y_TEXT_LABEL_LINKS;
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const { L10nRegistry } = require("resource://gre/modules/L10nRegistry.jsm");
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const Telemetry = require("devtools/client/shared/telemetry");
|
||||
|
@ -81,6 +84,8 @@ AccessibilityPanel.prototype = {
|
|||
this.picker = new Picker(this);
|
||||
}
|
||||
|
||||
this.fluentBundles = await this.createFluentBundles();
|
||||
|
||||
this.updateA11YServiceDurationTimer();
|
||||
this.front.on("init", this.updateA11YServiceDurationTimer);
|
||||
this.front.on("shutdown", this.updateA11YServiceDurationTimer);
|
||||
|
@ -94,6 +99,25 @@ AccessibilityPanel.prototype = {
|
|||
return this._opening;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve message contexts for the current locales, and return them as an
|
||||
* array of FluentBundles elements.
|
||||
*/
|
||||
async createFluentBundles() {
|
||||
const locales = Services.locale.appLocalesAsBCP47;
|
||||
const generator =
|
||||
L10nRegistry.generateBundles(locales, ["devtools/accessibility.ftl"]);
|
||||
|
||||
// Return value of generateBundles is a generator and should be converted to
|
||||
// a sync iterable before using it with React.
|
||||
const contexts = [];
|
||||
for await (const message of generator) {
|
||||
contexts.push(message);
|
||||
}
|
||||
|
||||
return contexts;
|
||||
},
|
||||
|
||||
onNewAccessibleFrontSelected(selected) {
|
||||
this.emit("new-accessible-front-selected", selected);
|
||||
},
|
||||
|
@ -133,7 +157,8 @@ AccessibilityPanel.prototype = {
|
|||
}
|
||||
// Alright reset the flag we are about to refresh the panel.
|
||||
this.shouldRefresh = false;
|
||||
this.postContentMessage("initialize", this.front, this.walker, this.supports);
|
||||
this.postContentMessage("initialize", this.front, this.walker, this.supports,
|
||||
this.fluentBundles);
|
||||
},
|
||||
|
||||
updateA11YServiceDurationTimer() {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"plugins": ["@babel/plugin-proposal-async-generator-functions"],
|
||||
};
|
|
@ -4,10 +4,10 @@ exports[`AuditController component: audit filter filtered contrast checks fail 1
|
|||
|
||||
exports[`AuditController component: audit filter filtered contrast checks fail range 1`] = `"<span></span>"`;
|
||||
|
||||
exports[`AuditController component: audit filter filtered contrast checks success 1`] = `null`;
|
||||
exports[`AuditController component: audit filter filtered contrast checks success 1`] = `""`;
|
||||
|
||||
exports[`AuditController component: audit filter filtered no checks 1`] = `null`;
|
||||
exports[`AuditController component: audit filter filtered no checks 1`] = `""`;
|
||||
|
||||
exports[`AuditController component: audit filter filtered unknown checks 1`] = `null`;
|
||||
exports[`AuditController component: audit filter filtered unknown checks 1`] = `""`;
|
||||
|
||||
exports[`AuditController component: audit filter not filtered 1`] = `"<span></span>"`;
|
||||
|
|
|
@ -10,4 +10,4 @@ exports[`AuditProgressOverlay component: render auditing progress 2`] = `"<span
|
|||
|
||||
exports[`AuditProgressOverlay component: render auditing progress 3`] = `"<span id=\\"audit-progress-container\\">accessibility.progress.progressbar<progress max=\\"100\\" value=\\"75\\" class=\\"audit-progress-progressbar\\" aria-labelledby=\\"audit-progress-container\\"></progress></span>"`;
|
||||
|
||||
exports[`AuditProgressOverlay component: render not auditing 1`] = `null`;
|
||||
exports[`AuditProgressOverlay component: render not auditing 1`] = `""`;
|
||||
|
|
|
@ -6,10 +6,10 @@ exports[`Badges component: contrast ratio fail render 1`] = `"<span class=\\"bad
|
|||
|
||||
exports[`Badges component: contrast ratio success render 1`] = `"<span class=\\"badges\\" role=\\"group\\" aria-label=\\"accessibility.badges\\"></span>"`;
|
||||
|
||||
exports[`Badges component: empty checks render 1`] = `null`;
|
||||
exports[`Badges component: empty checks render 1`] = `""`;
|
||||
|
||||
exports[`Badges component: no props render 1`] = `null`;
|
||||
exports[`Badges component: no props render 1`] = `""`;
|
||||
|
||||
exports[`Badges component: null checks render 1`] = `null`;
|
||||
exports[`Badges component: null checks render 1`] = `""`;
|
||||
|
||||
exports[`Badges component: unsupported checks render 1`] = `null`;
|
||||
exports[`Badges component: unsupported checks render 1`] = `""`;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TextLabelCheck component: BEST_PRACTICES render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\" class=\\"accessibility-text-label-check\\"><img src=\\"chrome://devtools/skin/images/info.svg\\" class=\\"icon BEST_PRACTICES\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
|
||||
|
||||
exports[`TextLabelCheck component: WARNING render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\" class=\\"accessibility-text-label-check\\"><img src=\\"chrome://devtools/skin/images/alert.svg\\" class=\\"icon WARNING\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
|
||||
|
||||
exports[`TextLabelCheck component: fail render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\" class=\\"accessibility-text-label-check\\"><img src=\\"chrome://devtools/skin/images/error.svg\\" class=\\"icon fail\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
|
|
@ -0,0 +1,69 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { mount } = require("enzyme");
|
||||
const { createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const TextLabelCheckClass = require("devtools/client/accessibility/components/TextLabelCheck");
|
||||
const TextLabelCheck = createFactory(TextLabelCheckClass);
|
||||
|
||||
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
||||
const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
|
||||
|
||||
const {
|
||||
accessibility: {
|
||||
AUDIT_TYPE: { TEXT_LABEL },
|
||||
ISSUE_TYPE: {
|
||||
[TEXT_LABEL]: {
|
||||
AREA_NO_NAME_FROM_ALT,
|
||||
DIALOG_NO_NAME,
|
||||
FORM_NO_VISIBLE_NAME,
|
||||
},
|
||||
},
|
||||
SCORES: { BEST_PRACTICES, FAIL, WARNING },
|
||||
},
|
||||
} = require("devtools/shared/constants");
|
||||
|
||||
function testTextLabelCheck(wrapper, props) {
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
expect(wrapper.children().length).toBe(1);
|
||||
const container = wrapper.childAt(0);
|
||||
expect(container.hasClass("accessibility-check")).toBe(true);
|
||||
expect(container.prop("role")).toBe("presentation");
|
||||
expect(wrapper.props()).toMatchObject(props);
|
||||
|
||||
const localized = wrapper.find(FluentReact.Localized);
|
||||
expect(localized.length).toBe(3);
|
||||
|
||||
const heading = localized.at(0).childAt(0);
|
||||
expect(heading.type()).toBe("h3");
|
||||
expect(heading.hasClass("accessibility-check-header")).toBe(true);
|
||||
|
||||
const icon = localized.at(1).childAt(0);
|
||||
expect(icon.type()).toBe("img");
|
||||
expect(icon.hasClass(props.score === FAIL ? "fail" : props.score)).toBe(true);
|
||||
|
||||
const annotation = localized.at(2).childAt(0);
|
||||
expect(annotation.type()).toBe("p");
|
||||
expect(annotation.hasClass("accessibility-check-annotation")).toBe(true);
|
||||
}
|
||||
|
||||
describe("TextLabelCheck component:", () => {
|
||||
const testProps = [
|
||||
{ issue: AREA_NO_NAME_FROM_ALT, score: FAIL },
|
||||
{ issue: FORM_NO_VISIBLE_NAME, score: WARNING },
|
||||
{ issue: DIALOG_NO_NAME, score: BEST_PRACTICES },
|
||||
];
|
||||
|
||||
for (const props of testProps) {
|
||||
it(`${props.score} render`, () => {
|
||||
const wrapper = mount(LocalizationProvider({ messages: []},
|
||||
TextLabelCheck(props)));
|
||||
|
||||
const textLabelCheck = wrapper.find(TextLabelCheckClass);
|
||||
testTextLabelCheck(textLabelCheck, props);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -10,12 +10,13 @@
|
|||
"test-ci": "jest --json"
|
||||
},
|
||||
"dependencies": {
|
||||
"jest": "^23.0.0",
|
||||
"@babel/plugin-proposal-async-generator-functions": "^7.2.0",
|
||||
"jest": "^24.6.0",
|
||||
"react-test-renderer": "16.4.1",
|
||||
"react": "16.4.1",
|
||||
"react-dom": "16.4.1",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-to-json": "3.3.4",
|
||||
"enzyme-adapter-react-16": "^1.1.1"
|
||||
"enzyme": "^3.9.0",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"enzyme-adapter-react-16": "^1.13.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,13 @@ window._snapshots = {
|
|||
"type": "div",
|
||||
"props": {
|
||||
"role": "presentation",
|
||||
"className": "accessibility-color-contrast-check",
|
||||
"className": "accessibility-check",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "h3",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-header",
|
||||
"className": "accessibility-check-header",
|
||||
},
|
||||
"children": [
|
||||
"Color and Contrast",
|
||||
|
@ -45,13 +45,13 @@ window._snapshots = {
|
|||
"type": "div",
|
||||
"props": {
|
||||
"role": "presentation",
|
||||
"className": "accessibility-color-contrast-check",
|
||||
"className": "accessibility-check",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "h3",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-header",
|
||||
"className": "accessibility-check-header",
|
||||
},
|
||||
"children": [
|
||||
"Color and Contrast",
|
||||
|
@ -83,7 +83,7 @@ window._snapshots = {
|
|||
{
|
||||
"type": "p",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-annotation",
|
||||
"className": "accessibility-check-annotation",
|
||||
},
|
||||
"children": [
|
||||
"Does not meet WCAG standards for accessible text. ",
|
||||
|
@ -110,13 +110,13 @@ window._snapshots = {
|
|||
"type": "div",
|
||||
"props": {
|
||||
"role": "presentation",
|
||||
"className": "accessibility-color-contrast-check",
|
||||
"className": "accessibility-check",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "h3",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-header",
|
||||
"className": "accessibility-check-header",
|
||||
},
|
||||
"children": [
|
||||
"Color and Contrast",
|
||||
|
@ -170,7 +170,7 @@ window._snapshots = {
|
|||
{
|
||||
"type": "p",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-annotation",
|
||||
"className": "accessibility-check-annotation",
|
||||
},
|
||||
"children": [
|
||||
"Does not meet WCAG standards for accessible text. ",
|
||||
|
@ -197,13 +197,13 @@ window._snapshots = {
|
|||
"type": "div",
|
||||
"props": {
|
||||
"role": "presentation",
|
||||
"className": "accessibility-color-contrast-check",
|
||||
"className": "accessibility-check",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "h3",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-header",
|
||||
"className": "accessibility-check-header",
|
||||
},
|
||||
"children": [
|
||||
"Color and Contrast",
|
||||
|
@ -246,7 +246,7 @@ window._snapshots = {
|
|||
{
|
||||
"type": "p",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-annotation",
|
||||
"className": "accessibility-check-annotation",
|
||||
},
|
||||
"children": [
|
||||
"Meets WCAG AA standards for accessible text. ",
|
||||
|
|
|
@ -69,7 +69,12 @@ devtools.jar:
|
|||
skin/images/accessibility.svg (themes/images/accessibility.svg)
|
||||
skin/images/add.svg (themes/images/add.svg)
|
||||
skin/images/alert.svg (themes/images/alert.svg)
|
||||
skin/images/alert-small.svg (themes/images/alert-small.svg)
|
||||
skin/images/alert-tiny.svg (themes/images/alert-tiny.svg)
|
||||
skin/images/error.svg (themes/images/error.svg)
|
||||
skin/images/error-small.svg (themes/images/error-small.svg)
|
||||
skin/images/info.svg (themes/images/info.svg)
|
||||
skin/images/info-small.svg (themes/images/info-small.svg)
|
||||
skin/images/arrow.svg (themes/images/arrow.svg)
|
||||
skin/images/arrow-big.svg (themes/images/arrow-big.svg)
|
||||
skin/images/arrowhead-left.svg (themes/images/arrowhead-left.svg)
|
||||
|
@ -122,8 +127,6 @@ devtools.jar:
|
|||
skin/images/rules-view-print-simulation.svg (themes/images/rules-view-print-simulation.svg)
|
||||
skin/markup.css (themes/markup.css)
|
||||
skin/webconsole.css (themes/webconsole.css)
|
||||
skin/images/webconsole/error.svg (themes/images/webconsole/error.svg)
|
||||
skin/images/webconsole/info.svg (themes/images/webconsole/info.svg)
|
||||
skin/images/webconsole/input.svg (themes/images/webconsole/input.svg)
|
||||
skin/images/webconsole/navigation.svg (themes/images/webconsole/navigation.svg)
|
||||
skin/images/webconsole/return.svg (themes/images/webconsole/return.svg)
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# 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/.
|
||||
|
||||
### These strings are used inside the Accessibility panel.
|
||||
|
||||
accessibility-learn-more = Learn more
|
||||
|
||||
accessibility-text-label-header = Text Labels and Names
|
||||
|
||||
## Text entries that are used as text alternative for icons that depict accessibility isses.
|
||||
|
||||
accessibility-warning =
|
||||
.alt = Warning
|
||||
|
||||
accessibility-fail =
|
||||
.alt = Error
|
||||
|
||||
accessibility-best-practices =
|
||||
.alt = Best Practices
|
||||
|
||||
## Text entries for a paragraph used in the accessibility panel sidebar's checks section
|
||||
## that describe that currently selected accessible object has an accessibility issue
|
||||
## with its text label or accessible name.
|
||||
|
||||
accessibility-text-label-issue-area = Use <code>alt</code> attribute to label <div>area</div> elements that have the <span>href</span> attribute. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-dialog = Dialogs should be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-document-title = Documents must have a <code>title</code>. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-embed = Embedded content must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-figure = Figures with optional captions should be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-fieldset = <code>fieldset</code> elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-fieldset-legend = Use <code>legend</code> element to label <span>fieldset</span> elements. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-form = Form elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-form-visible = Form elements should have a visible text label. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-frame = <code>frame</code> elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-glyph = Use <code>alt</code> attribute to label <span>mglyph</span> elements. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-heading = Headings must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-heading-content = Headings should have visible text content. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-iframe = Use <code>title</code> attribute to describe <span>iframe</span> content. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-image = Content with images must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-interactive = Interactive elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-optgroup = <code>optgroup</code> elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-optgroup-label = Use <code>label</code> attribute to label <span>optgroup</span> elements. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-toolbar = Toolbars must be labeled when there is more than one toolbar. <a>Learn more</a>
|
|
@ -475,7 +475,7 @@
|
|||
height: 12px;
|
||||
vertical-align: -1px;
|
||||
margin-inline-start: 5px;
|
||||
background-image: url(chrome://devtools/skin/images/alert.svg);
|
||||
background-image: url(chrome://devtools/skin/images/alert-small.svg);
|
||||
background-size: cover;
|
||||
-moz-context-properties: fill;
|
||||
fill: var(--yellow-60);
|
||||
|
|
|
@ -282,7 +282,14 @@ pref("devtools.webconsole.sidebarToggle", false);
|
|||
// Enable CodeMirror in the JsTerm
|
||||
pref("devtools.webconsole.jsterm.codeMirror", true);
|
||||
|
||||
// Enable editor mode in the console.
|
||||
// Enable editor mode in the console in Nightly builds.
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
pref("devtools.webconsole.features.editor", true);
|
||||
#else
|
||||
pref("devtools.webconsole.features.editor", false);
|
||||
#endif
|
||||
|
||||
// Saved editor mode state in the console.
|
||||
pref("devtools.webconsole.input.editor", false);
|
||||
|
||||
// Disable the new performance recording panel by default
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12">
|
||||
<path fill="context-fill" d="M6 0a1 1 0 0 1 .89.54l5 9.6A1 1 0 0 1 11 11.6H1a1 1 0 0 1-.89-1.46l5-9.6A1 1 0 0 1 6 0zm-.25 8a.75.75 0 0 0-.75.75v.5c0 .41.34.75.75.75h.5c.41 0 .75-.34.75-.75v-.5A.75.75 0 0 0 6.25 8h-.5zM7 3.7a1 1 0 1 0-2 0v2.6a1 1 0 1 0 2 0V3.7z" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 570 B |
|
@ -1,6 +1,6 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12">
|
||||
<path fill="context-fill" d="M6 0a1 1 0 0 1 .89.54l5 9.6A1 1 0 0 1 11 11.6H1a1 1 0 0 1-.89-1.46l5-9.6A1 1 0 0 1 6 0zm-.25 8a.75.75 0 0 0-.75.75v.5c0 .41.34.75.75.75h.5c.41 0 .75-.34.75-.75v-.5A.75.75 0 0 0 6.25 8h-.5zM7 3.7a1 1 0 1 0-2 0v2.6a1 1 0 1 0 2 0V3.7z" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M14.742 12.106L9.789 2.2a2 2 0 0 0-3.578 0l-4.953 9.91A2 2 0 0 0 3.047 15h9.905a2 2 0 0 0 1.79-2.894zM7 5a1 1 0 0 1 2 0v4a1 1 0 0 1-2 0zm1 8.25A1.25 1.25 0 1 1 9.25 12 1.25 1.25 0 0 1 8 13.25z" />
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 570 B После Ширина: | Высота: | Размер: 531 B |
До Ширина: | Высота: | Размер: 538 B После Ширина: | Высота: | Размер: 538 B |
|
@ -0,0 +1,6 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||
<path fill="context-fill" fill-rule="evenodd" d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm0-3.429a1.143 1.143 0 1 0 0-2.285 1.143 1.143 0 0 0 0 2.285zm0-3.428c.631 0 1.143-.512 1.143-1.143V4.571a1.143 1.143 0 0 0-2.286 0V8c0 .631.512 1.143 1.143 1.143z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 556 B |
До Ширина: | Высота: | Размер: 538 B После Ширина: | Высота: | Размер: 538 B |
|
@ -0,0 +1,6 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M8 1a7 7 0 1 0 7 7 7.008 7.008 0 0 0-7-7zm0 13a6 6 0 1 1 6-6 6.007 6.007 0 0 1-6 6zm0-7a1 1 0 0 0-1 1v3a1 1 0 1 0 2 0V8a1 1 0 0 0-1-1zm0-3.188A1.188 1.188 0 1 0 9.188 5 1.188 1.188 0 0 0 8 3.812z" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 534 B |
|
@ -83,7 +83,7 @@
|
|||
height: 12px;
|
||||
max-height: 12px;
|
||||
margin-inline-end: 5px;
|
||||
background-image: url(chrome://devtools/skin/images/webconsole/info.svg);
|
||||
background-image: url(chrome://devtools/skin/images/info-small.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
-moz-context-properties: fill;
|
||||
|
@ -91,7 +91,7 @@
|
|||
}
|
||||
|
||||
.opt-icon.warning::before {
|
||||
background-image: url(chrome://devtools/skin/images/alert.svg);
|
||||
background-image: url(chrome://devtools/skin/images/alert-small.svg);
|
||||
fill: var(--yellow-60);
|
||||
}
|
||||
|
||||
|
|
|
@ -580,7 +580,7 @@ html, body, #app, #memory-tool {
|
|||
height: 12px;
|
||||
max-height: 12px;
|
||||
margin-inline-end: 5px;
|
||||
background-image: url(chrome://devtools/skin/images/alert.svg);
|
||||
background-image: url(chrome://devtools/skin/images/alert-small.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
-moz-context-properties: fill;
|
||||
|
|
|
@ -775,7 +775,7 @@ menuitem.experimental-option::before {
|
|||
max-height: 12px;
|
||||
margin-top: 2px;
|
||||
margin-inline-end: 5px;
|
||||
background-image: url(chrome://devtools/skin/images/alert.svg);
|
||||
background-image: url(chrome://devtools/skin/images/alert-small.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
-moz-context-properties: fill;
|
||||
|
|
|
@ -371,14 +371,14 @@
|
|||
}
|
||||
|
||||
.ruleview-warning {
|
||||
background-image: url(chrome://devtools/skin/images/alert.svg);
|
||||
background-image: url(chrome://devtools/skin/images/alert-small.svg);
|
||||
fill: var(--yellow-60);
|
||||
}
|
||||
|
||||
.ruleview-unused-warning {
|
||||
background-image: url(chrome://devtools/skin/images/webconsole/info.svg);
|
||||
background-image: url(chrome://devtools/skin/images/info-small.svg);
|
||||
background-color: var(--theme-sidebar-background);
|
||||
fill: var(--theme-icon-dimmed-color);
|
||||
fill: var(--theme-icon-dimmed-color);
|
||||
}
|
||||
|
||||
.ruleview-unused-warning:hover {
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
--theme-icon-color: rgba(12, 12, 13, 0.8);
|
||||
--theme-icon-dimmed-color: rgba(135, 135, 137, 0.9);
|
||||
--theme-icon-checked-color: var(--blue-60);
|
||||
--theme-icon-error-color: var(--red-60);
|
||||
--theme-icon-warning-color: var(--yellow-65);
|
||||
|
||||
/* Text color */
|
||||
--theme-comment: var(--grey-50);
|
||||
|
@ -151,6 +153,8 @@
|
|||
--theme-icon-color: rgba(249, 249, 250, 0.7);
|
||||
--theme-icon-dimmed-color: rgba(147, 147, 149, 0.9);
|
||||
--theme-icon-checked-color: var(--blue-30);
|
||||
--theme-icon-error-color: var(--red-40);
|
||||
--theme-icon-warning-color: var(--yellow-60);
|
||||
|
||||
/* Text color */
|
||||
--theme-comment: var(--grey-45);
|
||||
|
|
|
@ -21,11 +21,9 @@
|
|||
--console-error-background: hsl(345, 23%, 24%);
|
||||
--console-error-border: hsl(345, 30%, 35%);
|
||||
--console-error-color: var(--red-20);
|
||||
--console-error-icon-color: var(--red-40);
|
||||
--console-warning-background: hsl(42, 37%, 19%);
|
||||
--console-warning-border: hsl(60, 30%, 26%);
|
||||
--console-warning-color: hsl(43, 94%, 81%);
|
||||
--console-warning-icon-color: var(--yellow-60);
|
||||
--console-navigation-color: var(--theme-highlight-blue);
|
||||
--console-navigation-border: var(--blue-60);
|
||||
--console-indent-border-color: var(--theme-highlight-blue);
|
||||
|
@ -45,11 +43,9 @@
|
|||
--console-error-background: hsl(344, 73%, 97%);
|
||||
--console-error-border: rgba(215, 0, 34, 0.12); /* Red 60 + opacity */
|
||||
--console-error-color: var(--red-70);
|
||||
--console-error-icon-color: var(--red-60);
|
||||
--console-warning-background: hsl(54, 100%, 92%);
|
||||
--console-warning-border: rgba(215, 182, 0, 0.28); /* Yellow 60 + opacity */
|
||||
--console-warning-color: var(--yellow-80);
|
||||
--console-warning-icon-color: var(--yellow-65);
|
||||
--console-navigation-color: var(--theme-highlight-blue);
|
||||
--console-navigation-border: var(--blue-30);
|
||||
--console-indent-border-color: var(--theme-highlight-blue);
|
||||
|
@ -280,17 +276,17 @@ a {
|
|||
|
||||
.message.info > .icon {
|
||||
color: var(--theme-icon-color);
|
||||
background-image: url(chrome://devtools/skin/images/webconsole/info.svg);
|
||||
background-image: url(chrome://devtools/skin/images/info-small.svg);
|
||||
}
|
||||
|
||||
.message.error > .icon {
|
||||
color: var(--console-error-icon-color);
|
||||
background-image: url(chrome://devtools/skin/images/webconsole/error.svg);
|
||||
color: var(--theme-icon-error-color);
|
||||
background-image: url(chrome://devtools/skin/images/error-small.svg);
|
||||
}
|
||||
|
||||
.message.warn > .icon {
|
||||
color: var(--console-warning-icon-color);
|
||||
background-image: url(chrome://devtools/skin/images/alert.svg);
|
||||
color: var(--theme-icon-warning-color);
|
||||
background-image: url(chrome://devtools/skin/images/alert-small.svg);
|
||||
}
|
||||
|
||||
.message.navigationMarker > .icon {
|
||||
|
|
|
@ -59,6 +59,7 @@ class App extends Component {
|
|||
sidebarVisible: PropTypes.bool.isRequired,
|
||||
filterBarDisplayMode:
|
||||
PropTypes.oneOf([...Object.values(FILTERBAR_DISPLAY_MODES)]).isRequired,
|
||||
editorFeatureEnabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -74,6 +75,7 @@ class App extends Component {
|
|||
const {
|
||||
dispatch,
|
||||
webConsoleUI,
|
||||
editorFeatureEnabled,
|
||||
} = this.props;
|
||||
|
||||
if (
|
||||
|
@ -85,10 +87,13 @@ class App extends Component {
|
|||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (event.key.toLowerCase() === "b" && (
|
||||
isMacOS && event.metaKey ||
|
||||
!isMacOS && event.ctrlKey
|
||||
)) {
|
||||
if (
|
||||
editorFeatureEnabled &&
|
||||
event.key.toLowerCase() === "b" && (
|
||||
isMacOS && event.metaKey ||
|
||||
!isMacOS && event.ctrlKey
|
||||
)
|
||||
) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
dispatch(actions.editorToggle());
|
||||
|
|
|
@ -328,9 +328,11 @@ class FilterBar extends Component {
|
|||
children.push(dom.div(
|
||||
{
|
||||
className: "devtools-toolbar split-console-close-button-wrapper",
|
||||
key: "wrapper",
|
||||
},
|
||||
dom.button({
|
||||
id: "split-console-close-button",
|
||||
key: "split-console-close-button",
|
||||
className: "devtools-button",
|
||||
title: l10n.getStr("webconsole.closeSplitConsoleButton.tooltip"),
|
||||
onClick: () => {
|
||||
|
|
|
@ -81,6 +81,7 @@ const prefs = {
|
|||
JSTERM_CODE_MIRROR: "devtools.webconsole.jsterm.codeMirror",
|
||||
AUTOCOMPLETE: "devtools.webconsole.input.autocomplete",
|
||||
GROUP_WARNINGS: "devtools.webconsole.groupWarningMessages",
|
||||
EDITOR: "devtools.webconsole.features.editor",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ const PrefState = (overrides) => Object.freeze(Object.assign({
|
|||
jstermCodeMirror: false,
|
||||
groupWarnings: false,
|
||||
historyCount: 50,
|
||||
editor: false,
|
||||
}, overrides));
|
||||
|
||||
function prefs(state = PrefState(), action) {
|
||||
|
|
|
@ -51,6 +51,7 @@ function configureStore(webConsoleUI, options = {}) {
|
|||
const jstermCodeMirror = getBoolPref(PREFS.FEATURES.JSTERM_CODE_MIRROR);
|
||||
const autocomplete = getBoolPref(PREFS.FEATURES.AUTOCOMPLETE);
|
||||
const groupWarnings = getBoolPref(PREFS.FEATURES.GROUP_WARNINGS);
|
||||
const editor = getBoolPref(PREFS.FEATURES.EDITOR);
|
||||
const historyCount = getIntPref(PREFS.UI.INPUT_HISTORY_COUNT);
|
||||
|
||||
const initialState = {
|
||||
|
@ -61,6 +62,7 @@ function configureStore(webConsoleUI, options = {}) {
|
|||
autocomplete,
|
||||
historyCount,
|
||||
groupWarnings,
|
||||
editor,
|
||||
}),
|
||||
filters: FilterState({
|
||||
error: getBoolPref(PREFS.FILTER.ERROR),
|
||||
|
|
|
@ -28,6 +28,7 @@ pref("devtools.webconsole.groupWarningMessages", false);
|
|||
pref("devtools.webconsole.input.editor", false);
|
||||
pref("devtools.webconsole.input.autocomplete", true);
|
||||
pref("devtools.browserconsole.contentMessages", true);
|
||||
pref("devtools.webconsole.features.editor", true);
|
||||
|
||||
global.loader = {
|
||||
lazyServiceGetter: () => {},
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
const TEST_URI = "data:text/html;charset=utf8,<p>Test editor";
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.webconsole.features.editor", true);
|
||||
// Run test with legacy JsTerm
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", false);
|
||||
await performTests();
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 1519314";
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.webconsole.features.editor", true);
|
||||
await pushPref("devtools.webconsole.input.editor", true);
|
||||
// Run test with legacy JsTerm
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", false);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 1519313";
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.webconsole.features.editor", true);
|
||||
await pushPref("devtools.webconsole.input.editor", true);
|
||||
// Run test with legacy JsTerm
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", false);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
const TEST_URI = "data:text/html;charset=utf-8,Test JsTerm editor line gutters";
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.webconsole.features.editor", true);
|
||||
await pushPref("devtools.webconsole.input.editor", true);
|
||||
|
||||
const hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
|
|
@ -10,6 +10,7 @@ const TEST_URI = "data:text/html;charset=utf-8,Test editor mode toggle keyboard
|
|||
const EDITOR_PREF = "devtools.webconsole.input.editor";
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.webconsole.features.editor", true);
|
||||
await pushPref("devtools.webconsole.input.editor", true);
|
||||
// Run test with legacy JsTerm
|
||||
info("Test legacy JsTerm");
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
const TEST_URI = "data:text/html;charset=utf8,<p>Test editor toolbar";
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.webconsole.features.editor", true);
|
||||
// Run test with legacy JsTerm
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", false);
|
||||
await performTests();
|
||||
|
|
|
@ -358,6 +358,7 @@ class WebConsoleWrapper {
|
|||
const jstermCodeMirror = prefs.jstermCodeMirror
|
||||
&& !Services.appinfo.accessibilityEnabled;
|
||||
const autocomplete = prefs.autocomplete;
|
||||
const editorFeatureEnabled = prefs.editor;
|
||||
|
||||
this.prefsObservers = new Map();
|
||||
this.prefsObservers.set(PREFS.UI.MESSAGE_TIMESTAMP, () => {
|
||||
|
@ -381,6 +382,7 @@ class WebConsoleWrapper {
|
|||
closeSplitConsole: this.closeSplitConsole.bind(this),
|
||||
jstermCodeMirror,
|
||||
autocomplete,
|
||||
editorFeatureEnabled,
|
||||
hideShowContentMessagesCheckbox: !webConsoleUI.isBrowserConsole,
|
||||
});
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
--highlighter-marker-color: #000;
|
||||
|
||||
--grey-40: #b1b1b3;
|
||||
--red-40: #ff3b6b;
|
||||
--yellow-60: #d7b600;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -737,6 +739,34 @@
|
|||
margin-inline-start: 3px;
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .accessible-infobar-audit .accessible-text-label:before {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
content: "";
|
||||
margin-inline-end: 4px;
|
||||
vertical-align: -2px;
|
||||
background-image: none;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .accessible-infobar-audit .accessible-text-label.fail:before {
|
||||
background-image: url(chrome://devtools/skin/images/error-small.svg);
|
||||
fill: var(--red-40);
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .accessible-infobar-audit .accessible-text-label.WARNING:before {
|
||||
background-image: url(chrome://devtools/skin/images/alert-small.svg);
|
||||
fill: var(--yellow-60);
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .accessible-infobar-audit .accessible-text-label.BEST_PRACTICES:before {
|
||||
background-image: url(chrome://devtools/skin/images/info-small.svg);
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .accessible-infobar-name:not(:empty) {
|
||||
border-inline-start: 1px solid #5a6169;
|
||||
margin-inline-start: 6px;
|
||||
|
|
|
@ -9,13 +9,39 @@ const { getCurrentZoom, getViewportDimensions } = require("devtools/shared/layou
|
|||
const { moveInfobar, createNode } = require("./markup");
|
||||
const { truncateString } = require("devtools/shared/inspector/utils");
|
||||
|
||||
const { accessibility: { SCORES } } = require("devtools/shared/constants");
|
||||
|
||||
const STRINGS_URI = "devtools/shared/locales/accessibility.properties";
|
||||
loader.lazyRequireGetter(this, "LocalizationHelper", "devtools/shared/l10n", true);
|
||||
DevToolsUtils.defineLazyGetter(this, "L10N", () => new LocalizationHelper(STRINGS_URI));
|
||||
|
||||
const { accessibility: { AUDIT_TYPE } } = require("devtools/shared/constants");
|
||||
const {
|
||||
accessibility: {
|
||||
AUDIT_TYPE,
|
||||
ISSUE_TYPE: {
|
||||
[AUDIT_TYPE.TEXT_LABEL]: {
|
||||
AREA_NO_NAME_FROM_ALT,
|
||||
DIALOG_NO_NAME,
|
||||
DOCUMENT_NO_TITLE,
|
||||
EMBED_NO_NAME,
|
||||
FIGURE_NO_NAME,
|
||||
FORM_FIELDSET_NO_NAME,
|
||||
FORM_FIELDSET_NO_NAME_FROM_LEGEND,
|
||||
FORM_NO_NAME,
|
||||
FORM_NO_VISIBLE_NAME,
|
||||
FORM_OPTGROUP_NO_NAME,
|
||||
FORM_OPTGROUP_NO_NAME_FROM_LABEL,
|
||||
FRAME_NO_NAME,
|
||||
HEADING_NO_CONTENT,
|
||||
HEADING_NO_NAME,
|
||||
IFRAME_NO_NAME_FROM_TITLE,
|
||||
IMAGE_NO_NAME,
|
||||
INTERACTIVE_NO_NAME,
|
||||
MATHML_GLYPH_NO_NAME,
|
||||
TOOLBAR_NO_NAME,
|
||||
},
|
||||
},
|
||||
SCORES,
|
||||
},
|
||||
} = require("devtools/shared/constants");
|
||||
|
||||
// Max string length for truncating accessible name values.
|
||||
const MAX_STRING_LENGTH = 50;
|
||||
|
@ -383,6 +409,7 @@ class Audit {
|
|||
// object.
|
||||
this.reports = [
|
||||
new ContrastRatio(this),
|
||||
new TextLabel(this),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -539,13 +566,13 @@ class ContrastRatio extends AuditReport {
|
|||
|
||||
/**
|
||||
* Update contrast ratio score infobar markup.
|
||||
* @param {Number}
|
||||
* Contrast ratio for an accessible object being highlighted.
|
||||
* @param {Object}
|
||||
* Audit report for a given highlighted accessible.
|
||||
* @return {Boolean}
|
||||
* True if the contrast ratio markup was updated correctly and infobar audit
|
||||
* block should be visible.
|
||||
*/
|
||||
update({ [AUDIT_TYPE.CONTRAST]: contrastRatio }) {
|
||||
update(audit) {
|
||||
const els = {};
|
||||
for (const key of ["label", "min", "max", "error", "separator"]) {
|
||||
const el = els[key] = this.getElement(`contrast-ratio-${key}`);
|
||||
|
@ -559,6 +586,11 @@ class ContrastRatio extends AuditReport {
|
|||
el.removeAttribute("style");
|
||||
}
|
||||
|
||||
if (!audit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contrastRatio = audit[AUDIT_TYPE.CONTRAST];
|
||||
if (!contrastRatio) {
|
||||
return false;
|
||||
}
|
||||
|
@ -592,6 +624,82 @@ class ContrastRatio extends AuditReport {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Text label audit report that is used to display a problem with text alternatives
|
||||
* as part of the inforbar.
|
||||
*/
|
||||
class TextLabel extends AuditReport {
|
||||
/**
|
||||
* A map from text label issues to annotation component properties.
|
||||
*/
|
||||
static get ISSUE_TO_INFOBAR_LABEL_MAP() {
|
||||
return {
|
||||
[AREA_NO_NAME_FROM_ALT]: "accessibility.text.label.issue.area",
|
||||
[DIALOG_NO_NAME]: "accessibility.text.label.issue.dialog",
|
||||
[DOCUMENT_NO_TITLE]: "accessibility.text.label.issue.document.title",
|
||||
[EMBED_NO_NAME]: "accessibility.text.label.issue.embed",
|
||||
[FIGURE_NO_NAME]: "accessibility.text.label.issue.figure",
|
||||
[FORM_FIELDSET_NO_NAME]: "accessibility.text.label.issue.fieldset",
|
||||
[FORM_FIELDSET_NO_NAME_FROM_LEGEND]:
|
||||
"accessibility.text.label.issue.fieldset.legend",
|
||||
[FORM_NO_NAME]: "accessibility.text.label.issue.form",
|
||||
[FORM_NO_VISIBLE_NAME]: "accessibility.text.label.issue.form.visible",
|
||||
[FORM_OPTGROUP_NO_NAME]: "accessibility.text.label.issue.optgroup",
|
||||
[FORM_OPTGROUP_NO_NAME_FROM_LABEL]: "accessibility.text.label.issue.optgroup.label",
|
||||
[FRAME_NO_NAME]: "accessibility.text.label.issue.frame",
|
||||
[HEADING_NO_CONTENT]: "accessibility.text.label.issue.heading.content",
|
||||
[HEADING_NO_NAME]: "accessibility.text.label.issue.heading",
|
||||
[IFRAME_NO_NAME_FROM_TITLE]: "accessibility.text.label.issue.iframe",
|
||||
[IMAGE_NO_NAME]: "accessibility.text.label.issue.image",
|
||||
[INTERACTIVE_NO_NAME]: "accessibility.text.label.issue.interactive",
|
||||
[MATHML_GLYPH_NO_NAME]: "accessibility.text.label.issue.glyph",
|
||||
[TOOLBAR_NO_NAME]: "accessibility.text.label.issue.toolbar",
|
||||
};
|
||||
}
|
||||
|
||||
buildMarkup(root) {
|
||||
createNode(this.win, {
|
||||
nodeType: "span",
|
||||
parent: root,
|
||||
attributes: {
|
||||
"class": "text-label",
|
||||
"id": "text-label",
|
||||
},
|
||||
prefix: this.prefix,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update text label audit infobar markup.
|
||||
* @param {Object}
|
||||
* Audit report for a given highlighted accessible.
|
||||
* @return {Boolean}
|
||||
* True if the text label markup was updated correctly and infobar
|
||||
* audit block should be visible.
|
||||
*/
|
||||
update(audit) {
|
||||
const el = this.getElement("text-label");
|
||||
el.setAttribute("hidden", true);
|
||||
Object.values(SCORES).forEach(className => el.classList.remove(className));
|
||||
|
||||
if (!audit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const textLabelAudit = audit[AUDIT_TYPE.TEXT_LABEL];
|
||||
if (!textLabelAudit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { issue, score } = textLabelAudit;
|
||||
this.setTextContent(el, L10N.getStr(TextLabel.ISSUE_TO_INFOBAR_LABEL_MAP[issue]));
|
||||
el.classList.add(score);
|
||||
el.removeAttribute("hidden");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function that calculate accessible object bounds and positioning to
|
||||
* be used for highlighting.
|
||||
|
|
|
@ -20,6 +20,8 @@ const ACCESSIBLE_BOUNDS_SHEET = "data:text/css;charset=utf-8," + encodeURICompon
|
|||
--highlighter-bubble-arrow-size: 8px;
|
||||
|
||||
--grey-40: #b1b1b3;
|
||||
--red-40: #ff3b6b;
|
||||
--yellow-60: #d7b600;
|
||||
}
|
||||
|
||||
.accessible-bounds {
|
||||
|
@ -147,6 +149,34 @@ const ACCESSIBLE_BOUNDS_SHEET = "data:text/css;charset=utf-8," + encodeURICompon
|
|||
border-inline-start: 1px solid #5a6169;
|
||||
margin-inline-start: 6px;
|
||||
padding-inline-start: 6px;
|
||||
}
|
||||
|
||||
.accessible-infobar-audit .accessible-text-label:before {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
content: "";
|
||||
margin-inline-end: 4px;
|
||||
vertical-align: -2px;
|
||||
background-image: none;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.accessible-infobar-audit .accessible-text-label.fail:before {
|
||||
background-image: url(chrome://devtools/skin/images/error-small.svg);
|
||||
fill: var(--red-40);
|
||||
}
|
||||
|
||||
.accessible-infobar-audit .accessible-text-label.WARNING:before {
|
||||
background-image: url(chrome://devtools/skin/images/alert-small.svg);
|
||||
fill: var(--yellow-60);
|
||||
}
|
||||
|
||||
.accessible-infobar-audit .accessible-text-label.BEST_PRACTICES:before {
|
||||
background-image: url(chrome://devtools/skin/images/info-small.svg);
|
||||
}`);
|
||||
|
||||
/**
|
||||
|
|
|
@ -43,6 +43,7 @@ support-files =
|
|||
[browser_accessibility_highlighter_infobar.js]
|
||||
skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184
|
||||
[browser_accessibility_infobar_show.js]
|
||||
[browser_accessibility_infobar_audit_text_label.js]
|
||||
[browser_accessibility_node.js]
|
||||
skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184
|
||||
[browser_accessibility_node_audit.js]
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Checks for the AccessibleHighlighter's infobar component and its text label
|
||||
// audit.
|
||||
|
||||
add_task(async function() {
|
||||
await BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: MAIN_DOMAIN + "doc_accessibility_infobar.html",
|
||||
}, async function(browser) {
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
|
||||
const { HighlighterEnvironment } = require("devtools/server/actors/highlighters");
|
||||
const { AccessibleHighlighter } = require("devtools/server/actors/highlighters/accessible");
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const L10N = new LocalizationHelper(
|
||||
"devtools/shared/locales/accessibility.properties");
|
||||
|
||||
const {
|
||||
accessibility: {
|
||||
AUDIT_TYPE,
|
||||
ISSUE_TYPE: {
|
||||
[AUDIT_TYPE.TEXT_LABEL]: {
|
||||
DIALOG_NO_NAME,
|
||||
FORM_NO_VISIBLE_NAME,
|
||||
TOOLBAR_NO_NAME,
|
||||
},
|
||||
},
|
||||
SCORES: { BEST_PRACTICES, FAIL, WARNING },
|
||||
},
|
||||
} = require("devtools/shared/constants");
|
||||
|
||||
/**
|
||||
* Checks for updated content for an infobar.
|
||||
*
|
||||
* @param {Object} infobar
|
||||
* Accessible highlighter's infobar component.
|
||||
* @param {Object} audit
|
||||
* Audit information that is passed on highlighter show.
|
||||
*/
|
||||
function checkTextLabel(infobar, audit) {
|
||||
const { issue, score } = audit || {};
|
||||
let expected = "";
|
||||
if (issue) {
|
||||
const { ISSUE_TO_INFOBAR_LABEL_MAP } = infobar.audit.reports[1].constructor;
|
||||
expected = L10N.getStr(ISSUE_TO_INFOBAR_LABEL_MAP[issue]);
|
||||
}
|
||||
|
||||
is(infobar.getTextContent("text-label"), expected,
|
||||
"infobar text label audit text content is correct");
|
||||
if (score) {
|
||||
ok(infobar.getElement("text-label").classList.contains(
|
||||
score === FAIL ? "fail" : score));
|
||||
}
|
||||
}
|
||||
|
||||
// Start testing. First, create highlighter environment and initialize.
|
||||
const env = new HighlighterEnvironment();
|
||||
env.initFromWindow(content.window);
|
||||
|
||||
// Wait for loading highlighter environment content to complete before creating the
|
||||
// highlighter.
|
||||
await new Promise(resolve => {
|
||||
const doc = env.document;
|
||||
|
||||
function onContentLoaded() {
|
||||
if (doc.readyState === "interactive" || doc.readyState === "complete") {
|
||||
resolve();
|
||||
} else {
|
||||
doc.addEventListener("DOMContentLoaded", onContentLoaded, { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
onContentLoaded();
|
||||
});
|
||||
|
||||
// Now, we can test the Infobar's audit content.
|
||||
const node = content.document.createElement("div");
|
||||
content.document.body.append(node);
|
||||
const highlighter = new AccessibleHighlighter(env);
|
||||
const infobar = highlighter.accessibleInfobar;
|
||||
const bounds = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 250,
|
||||
h: 100,
|
||||
};
|
||||
|
||||
const tests = [{
|
||||
desc: "Infobar is shown with no text label audit content when no audit.",
|
||||
}, {
|
||||
desc: "Infobar is shown with no text label audit content when audit is null.",
|
||||
audit: null,
|
||||
}, {
|
||||
desc: "Infobar is shown with no text label audit content when empty " +
|
||||
"text label audit.",
|
||||
audit: { [AUDIT_TYPE.TEXT_LABEL]: null },
|
||||
}, {
|
||||
desc: "Infobar is shown with text label audit content for an error.",
|
||||
audit: { [AUDIT_TYPE.TEXT_LABEL]: { score: FAIL, issue: TOOLBAR_NO_NAME } },
|
||||
}, {
|
||||
desc: "Infobar is shown with text label audit content for a warning.",
|
||||
audit: { [AUDIT_TYPE.TEXT_LABEL]: {
|
||||
score: WARNING, issue: FORM_NO_VISIBLE_NAME,
|
||||
}},
|
||||
}, {
|
||||
desc: "Infobar is shown with text label audit content for best practices.",
|
||||
audit: { [AUDIT_TYPE.TEXT_LABEL]: {
|
||||
score: BEST_PRACTICES, issue: DIALOG_NO_NAME,
|
||||
}},
|
||||
}];
|
||||
|
||||
for (const test of tests) {
|
||||
const { desc, audit } = test;
|
||||
|
||||
info(desc);
|
||||
highlighter.show(node, { ...bounds, audit });
|
||||
checkTextLabel(infobar, audit && audit[AUDIT_TYPE.TEXT_LABEL]);
|
||||
highlighter.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -19,3 +19,98 @@ accessibility.contrast.ratio.label=Contrast:
|
|||
# contrast ratio description that also specifies that the color contrast criteria used is
|
||||
# if for large text.
|
||||
accessibility.contrast.ratio.label.large=Contrast (large text):
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.area): A title text that
|
||||
# describes that currently selected accessible object for an <area> element must have
|
||||
# its name provided via the alt attribute.
|
||||
accessibility.text.label.issue.area = Use “alt” attribute to label “area” elements that have the “href” attribute.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.dialog): A title text that
|
||||
# describes that currently selected accessible object for a dialog should have a name
|
||||
# provided.
|
||||
accessibility.text.label.issue.dialog = Dialogs should be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.document.title): A title text that
|
||||
# describes that currently selected accessible object for a document must have a name
|
||||
# provided via title.
|
||||
accessibility.text.label.issue.document.title = Documents must have a title.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.embed): A title text that
|
||||
# describes that currently selected accessible object for an <embed> must have a name
|
||||
# provided.
|
||||
accessibility.text.label.issue.embed = Embedded content must be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.figure): A title text that
|
||||
# describes that currently selected accessible object for a figure should have a name
|
||||
# provided.
|
||||
accessibility.text.label.issue.figure = Figures with optional captions should be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.fieldset): A title text that
|
||||
# describes that currently selected accessible object for a <fieldset> must have a name
|
||||
# provided.
|
||||
accessibility.text.label.issue.fieldset = “fieldset” elements must be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.fieldset.legend): A title text that
|
||||
# describes that currently selected accessible object for a <fieldset> must have a name
|
||||
# provided via <legend> element.
|
||||
accessibility.text.label.issue.fieldset.legend = Use “legend” element to label “fieldset” elements.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.form): A title text that
|
||||
# describes that currently selected accessible object for a form element must have a name
|
||||
# provided.
|
||||
accessibility.text.label.issue.form = Form elements must be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.form.visible): A title text that
|
||||
# describes that currently selected accessible object for a form element should have a name
|
||||
# provided via a visible label/element.
|
||||
accessibility.text.label.issue.form.visible = Form elements should have a visible text label.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.frame): A title text that
|
||||
# describes that currently selected accessible object for a <frame> must have a name
|
||||
# provided.
|
||||
accessibility.text.label.issue.frame = “frame” elements must be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.glyph): A title text that
|
||||
# describes that currently selected accessible object for a <mglyph> must have a name
|
||||
# provided via alt attribute.
|
||||
accessibility.text.label.issue.glyph = Use “alt” attribute to label “mglyph” elements.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.heading): A title text that
|
||||
# describes that currently selected accessible object for a heading must have a name
|
||||
# provided.
|
||||
accessibility.text.label.issue.heading = Headings must be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.heading.content): A title text that
|
||||
# describes that currently selected accessible object for a heading must have visible
|
||||
# content.
|
||||
accessibility.text.label.issue.heading.content = Headings should have visible text content.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.iframe): A title text that
|
||||
# describes that currently selected accessible object for an <iframe> have a name
|
||||
# provided via title attribute.
|
||||
accessibility.text.label.issue.iframe = Use “title” attribute to describe “iframe” content.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.image): A title text that
|
||||
# describes that currently selected accessible object for graphical content must have a
|
||||
# name provided.
|
||||
accessibility.text.label.issue.image = Content with images must be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.interactive): A title text that
|
||||
# describes that currently selected accessible object for interactive element must have a
|
||||
# name provided.
|
||||
accessibility.text.label.issue.interactive = Interactive elements must be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.optgroup): A title text that
|
||||
# describes that currently selected accessible object for an <optgroup> must have a
|
||||
# name provided.
|
||||
accessibility.text.label.issue.optgroup = “optgroup” elements must be labeled.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.optgroup.label): A title text that
|
||||
# describes that currently selected accessible object for an <optgroup> must have a
|
||||
# name provided via label attribute.
|
||||
accessibility.text.label.issue.optgroup.label = Use “label” attribute to label “optgroup” elements.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.text.label.issue.toolbar): A title text that
|
||||
# describes that currently selected accessible object for a toolbar must have a
|
||||
# name provided when there is more than one toolbar in the document.
|
||||
accessibility.text.label.issue.toolbar = Toolbars must be labeled when there is more than one toolbar.
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { extend } = require("devtools/shared/extend");
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var { Request } = require("../Request");
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var { settleAll } = require("devtools/shared/DevToolsUtils");
|
||||
|
|