diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc
index 0a3ffa2a386c..e80cd0b0e32e 100644
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -172,26 +172,32 @@
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
+
diff --git a/browser/components/credentialmanager/identityCredentialNotification.ftl b/browser/components/credentialmanager/identityCredentialNotification.ftl
index ef45dddf02ea..fede689ace64 100644
--- a/browser/components/credentialmanager/identityCredentialNotification.ftl
+++ b/browser/components/credentialmanager/identityCredentialNotification.ftl
@@ -6,20 +6,16 @@
## $host (String): the hostname of the site that is being displayed.
## $provider (String): the hostname of another website you are using to log in to the site being displayed
-identity-credential-header-providers = Sign in to { $host }
-identity-credential-header-accounts = Pick a { $host } account
+identity-credential-header-providers = Sign in with a login provider
+identity-credential-header-accounts = Sign in with { $provider }
# Identity providers are websites you use to log into another website, for example: Google when you Log in with Google.
-identity-credential-description-provider-explanation = These are the identity providers that would like to help you log in.
-identity-credential-description-account-explanation = Picking an account here shares that identity with { $host }.
identity-credential-urlbar-anchor =
- .tooltiptext = Open federated login panel
+ .tooltiptext = Open login panel
identity-credential-cancel-button =
.label = Cancel
- .accesskey = C
+ .accesskey = n
identity-credential-accept-button =
- .label = Okay
- .accesskey = O
-identity-credential-policy-title = Legal information
-identity-credential-policy-description = Logging into { $host } with an account from { $provider } is controlled by these legal policies. This is optional and you can cancel this and try to log in again using another method.
-identity-credential-privacy-policy = Privacy Policy
-identity-credential-terms-of-service = Terms of Service
+ .label = Continue
+ .accesskey = C
+identity-credential-policy-title = Use { $provider } as a login provider
+identity-credential-policy-description = Logging in to { $host } with a { $provider } account is subject to { $provider }’s and .
diff --git a/browser/themes/shared/customizableui/panelUI-shared.css b/browser/themes/shared/customizableui/panelUI-shared.css
index 7aa9cb09d0ae..9ac41373bd2c 100644
--- a/browser/themes/shared/customizableui/panelUI-shared.css
+++ b/browser/themes/shared/customizableui/panelUI-shared.css
@@ -1106,7 +1106,6 @@ panelview .toolbarbutton-1,
}
#protections-popup-mainView .subviewbutton-nav:not(.notFound)::after,
-#identity-credential-notification .subviewbutton-nav::after,
.widget-overflow-list .subviewbutton-nav::after,
.PanelUI-subView .subviewbutton-nav::after {
-moz-context-properties: fill, fill-opacity;
@@ -1117,7 +1116,6 @@ panelview .toolbarbutton-1,
}
#protections-popup-mainView .subviewbutton-nav:not(.notFound):-moz-locale-dir(rtl)::after,
-#identity-credential-notification .subviewbutton-nav:-moz-locale-dir(rtl)::after,
.widget-overflow-list .subviewbutton-nav:-moz-locale-dir(rtl)::after,
.PanelUI-subView .subviewbutton-nav:-moz-locale-dir(rtl)::after {
content: url(chrome://global/skin/icons/arrow-left.svg);
diff --git a/browser/themes/shared/identity-credential-notification.css b/browser/themes/shared/identity-credential-notification.css
index 61ebdd2db476..1b5407366f08 100644
--- a/browser/themes/shared/identity-credential-notification.css
+++ b/browser/themes/shared/identity-credential-notification.css
@@ -2,12 +2,103 @@
* 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/. */
-#identity-credential-notification .subviewbutton {
- width: 100%;
- margin-inline: 0;
+#identity-credential-notification {
+ --list-item-border: color-mix(in srgb, currentColor 10%, transparent);
+ --list-item-checked-bgcolor: color-mix(in srgb, var(--button-primary-bgcolor) 6%, transparent);
+ --list-item-checked-border: color-mix(in srgb, var(--button-primary-bgcolor) 20%, transparent);
}
-#credential-provider-explanation,
-#credential-account-explanation {
- margin-top: 1em;
+@media (prefers-contrast) {
+ #identity-credential-notification {
+ --list-item-border: ThreeDShadow;
+ --list-item-checked-bgcolor: transparent;
+ --list-item-checked-border: AccentColor;
+ }
+}
+
+#identity-credential-provider-selector-container,
+#identity-credential-account-selector-container {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.identity-credential-list-item {
+ display: flex;
+ gap: 10px;
+ padding-block: max(calc(var(--arrowpanel-menuitem-padding-block) * 2), 4px);
+ padding-inline: calc(var(--arrowpanel-menuitem-padding-inline) * 2);
+ border: 2px solid var(--list-item-border);
+ border-radius: 4px;
+}
+
+.identity-credential-list-item.checked {
+ background-color: var(--list-item-checked-bgcolor);
+ border-color: var(--list-item-checked-border);
+}
+
+.identity-credential-list-item > .identity-credential-list-item-radio {
+ appearance: none;
+ background-color: var(--checkbox-unchecked-bgcolor);
+ background-image: url("chrome://global/skin/icons/radio.svg");
+ border: 1px solid var(--checkbox-border-color);
+ border-radius: 100%;
+ align-self: center;
+ outline: none;
+ -moz-context-properties: fill;
+ fill: transparent;
+}
+
+.identity-credential-list-item > .identity-credential-list-item-radio:focus-visible {
+ outline-offset: var(--focus-outline-offset);
+}
+
+.identity-credential-list-item > .identity-credential-list-item-radio:hover {
+ background-color: var(--checkbox-unchecked-hover-bgcolor);
+}
+
+.identity-credential-list-item > .identity-credential-list-item-radio:hover:active {
+ background-color: var(--checkbox-unchecked-active-bgcolor);
+}
+
+.identity-credential-list-item > .identity-credential-list-item-radio:checked {
+ fill: var(--checkbox-checked-color);
+ background-color: var(--checkbox-checked-bgcolor);
+ border-color: var(--checkbox-checked-border-color);
+}
+
+.identity-credential-list-item > .identity-credential-list-item-radio:checked:hover {
+ background-color: var(--checkbox-checked-hover-bgcolor);
+}
+
+.identity-credential-list-item > .identity-credential-list-item-radio:checked:hover:active {
+ background-color: var(--checkbox-checked-active-bgcolor);
+}
+
+.identity-credential-list-item-icon {
+ -moz-context-properties: fill, fill-opacity;
+ fill: currentColor;
+ fill-opacity: 0.6;
+ clip-path: circle(50%);
+ width: 32px;
+ height: 32px;
+}
+
+.identity-credential-list-item > .identity-credential-list-item-label {
+ align-self: center;
+ font-weight: 600;
+}
+
+.identity-credential-list-item-label-stack > .identity-credential-list-item-label-name {
+ font-weight: 600;
+}
+
+.identity-credential-list-item-label-stack > .identity-credential-list-item-label-email {
+ font-size: 80%;
+}
+
+.identity-credential-list-item > .identity-credential-list-item-label-stack {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
}
diff --git a/browser/themes/shared/notification-icons.css b/browser/themes/shared/notification-icons.css
index bd6271f4b9b9..b99b89d8a9b2 100644
--- a/browser/themes/shared/notification-icons.css
+++ b/browser/themes/shared/notification-icons.css
@@ -199,7 +199,7 @@
}
#identity-credential-notification-icon {
- list-style-image: url(chrome://browser/skin/fingerprint.svg);
+ list-style-image: url(chrome://browser/skin/login.svg);
}
#permission-popup-menulist {
diff --git a/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs b/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs
index 905770a6b1a1..85954bce1c2a 100644
--- a/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs
+++ b/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs
@@ -68,17 +68,17 @@ export class IdentityCredentialPromptService {
true
);
let headerMessage = localization.formatValueSync(
- "identity-credential-header-providers",
- {
- host: "<>",
- }
+ "identity-credential-header-providers"
);
- let [cancel] = localization.formatMessagesSync([
+ let [accept, cancel] = localization.formatMessagesSync([
+ { id: "identity-credential-accept-button" },
{ id: "identity-credential-cancel-button" },
]);
let cancelLabel = cancel.attributes.find(x => x.name == "label").value;
let cancelKey = cancel.attributes.find(x => x.name == "accesskey").value;
+ let acceptLabel = accept.attributes.find(x => x.name == "label").value;
+ let acceptKey = accept.attributes.find(x => x.name == "accesskey").value;
// Build the choices into the panel
let listBox = browser.ownerDocument.getElementById(
@@ -91,39 +91,62 @@ export class IdentityCredentialPromptService {
"template-credential-provider-list-item"
);
for (const [providerIndex, provider] of identityProviders.entries()) {
- let providerURI = new URL(provider.configURL);
+ let providerURL = new URL(provider.configURL);
let displayDomain = lazy.IDNService.convertToDisplayIDN(
- providerURI.host,
+ providerURL.host,
{}
);
let newItem = itemTemplate.content.firstElementChild.cloneNode(true);
- newItem.firstElementChild.textContent = displayDomain;
- newItem.setAttribute("oncommand", `this.callback(event)`);
- newItem.callback = function(event) {
- let notification = browser.ownerGlobal.PopupNotifications.getNotification(
- "identity-credential",
- browser
- );
- browser.ownerGlobal.PopupNotifications.remove(notification);
- resolve(providerIndex);
- event.stopPropagation();
- };
+ let newRadio = newItem.getElementsByClassName(
+ "identity-credential-list-item-radio"
+ )[0];
+ newRadio.value = providerIndex;
+ newRadio.addEventListener("change", function(event) {
+ for (let item of listBox.children) {
+ item.classList.remove("checked");
+ }
+ if (event.target.checked) {
+ event.target.parentElement.classList.add("checked");
+ }
+ });
+ if (providerIndex == 0) {
+ newRadio.checked = true;
+ newItem.classList.add("checked");
+ }
+ newItem.getElementsByClassName(
+ "identity-credential-list-item-label"
+ )[0].textContent = displayDomain;
listBox.append(newItem);
}
// Construct the necessary arguments for notification behavior
- let currentOrigin =
- browsingContext.currentWindowContext.documentPrincipal.originNoSuffix;
let options = {
- name: currentOrigin,
- };
- let mainAction = {
- label: cancelLabel,
- accessKey: cancelKey,
- callback(event) {
- reject();
+ hideClose: true,
+ eventCallback: (topic, nextRemovalReason, isCancel) => {
+ if (topic == "removed" && isCancel) {
+ reject();
+ }
},
};
+ let mainAction = {
+ label: acceptLabel,
+ accessKey: acceptKey,
+ callback(event) {
+ let result = listBox.querySelector(
+ ".identity-credential-list-item-radio:checked"
+ ).value;
+ resolve(parseInt(result));
+ },
+ };
+ let secondaryActions = [
+ {
+ label: cancelLabel,
+ accessKey: cancelKey,
+ callback(event) {
+ reject();
+ },
+ },
+ ];
// Show the popup
browser.ownerDocument.getElementById(
@@ -141,7 +164,7 @@ export class IdentityCredentialPromptService {
headerMessage,
"identity-credential-notification-icon",
mainAction,
- null,
+ secondaryActions,
options
);
});
@@ -167,8 +190,8 @@ export class IdentityCredentialPromptService {
}
if (
!identityCredentialMetadata ||
- (!identityCredentialMetadata.privacy_policy_url &&
- !identityCredentialMetadata.terms_of_service_url)
+ !identityCredentialMetadata.privacy_policy_url ||
+ !identityCredentialMetadata.terms_of_service_url
) {
return Promise.resolve(true);
}
@@ -179,9 +202,9 @@ export class IdentityCredentialPromptService {
return;
}
- let providerURI = new URL(identityProvider.configURL);
+ let providerURL = new URL(identityProvider.configURL);
let providerDisplayDomain = lazy.IDNService.convertToDisplayIDN(
- providerURI.host,
+ providerURL.host,
{}
);
let currentBaseDomain =
@@ -192,13 +215,6 @@ export class IdentityCredentialPromptService {
["preview/identityCredentialNotification.ftl"],
true
);
- let descriptionMessage = localization.formatValueSync(
- "identity-credential-policy-description",
- {
- host: currentBaseDomain,
- provider: providerDisplayDomain,
- }
- );
let [accept, cancel] = localization.formatMessagesSync([
{ id: "identity-credential-accept-button" },
{ id: "identity-credential-cancel-button" },
@@ -208,36 +224,53 @@ export class IdentityCredentialPromptService {
let cancelKey = cancel.attributes.find(x => x.name == "accesskey").value;
let acceptLabel = accept.attributes.find(x => x.name == "label").value;
let acceptKey = accept.attributes.find(x => x.name == "accesskey").value;
+
let title = localization.formatValueSync(
- "identity-credential-policy-title"
+ "identity-credential-policy-title",
+ {
+ provider: providerDisplayDomain,
+ }
);
+ let privacyPolicyAnchor = browser.ownerDocument.getElementById(
+ "identity-credential-privacy-policy"
+ );
+ privacyPolicyAnchor.href = identityCredentialMetadata.privacy_policy_url;
+ let termsOfServiceAnchor = browser.ownerDocument.getElementById(
+ "identity-credential-terms-of-service"
+ );
+ termsOfServiceAnchor.href =
+ identityCredentialMetadata.terms_of_service_url;
+
// Populate the content of the policy panel
let description = browser.ownerDocument.getElementById(
"identity-credential-policy-explanation"
);
- description.textContent = descriptionMessage;
- let privacyPolicyAnchor = browser.ownerDocument.getElementById(
- "identity-credential-privacy-policy"
+ description.setAttribute(
+ "data-l10n-args",
+ JSON.stringify({
+ host: currentBaseDomain,
+ provider: providerDisplayDomain,
+ })
);
- privacyPolicyAnchor.hidden = true;
- if (identityCredentialMetadata.privacy_policy_url) {
- privacyPolicyAnchor.href =
- identityCredentialMetadata.privacy_policy_url;
- privacyPolicyAnchor.hidden = false;
- }
- let termsOfServiceAnchor = browser.ownerDocument.getElementById(
- "identity-credential-terms-of-service"
+ browser.ownerDocument.l10n.setAttributes(
+ description,
+ "identity-credential-policy-description",
+ {
+ host: currentBaseDomain,
+ provider: providerDisplayDomain,
+ }
);
- termsOfServiceAnchor.hidden = true;
- if (identityCredentialMetadata.terms_of_service_url) {
- termsOfServiceAnchor.href =
- identityCredentialMetadata.terms_of_service_url;
- termsOfServiceAnchor.hidden = false;
- }
// Construct the necessary arguments for notification behavior
- let options = {};
+ let options = {
+ hideClose: true,
+ eventCallback: (topic, nextRemovalReason, isCancel) => {
+ if (topic == "removed" && isCancel) {
+ reject();
+ }
+ },
+ };
let mainAction = {
label: acceptLabel,
accessKey: acceptKey,
@@ -257,6 +290,7 @@ export class IdentityCredentialPromptService {
// Show the popup
let ownerDocument = browser.ownerDocument;
+ ownerDocument.l10n.translateFragment(description);
ownerDocument.getElementById(
"identity-credential-provider"
).hidden = true;
@@ -298,8 +332,6 @@ export class IdentityCredentialPromptService {
reject();
return;
}
- let currentOrigin =
- browsingContext.currentWindowContext.documentPrincipal.originNoSuffix;
// Localize all strings to be used
// Bug 1797154 - Convert localization calls to use the async formatValues.
@@ -307,29 +339,26 @@ export class IdentityCredentialPromptService {
["preview/identityCredentialNotification.ftl"],
true
);
+ let providerURL = new URL(provider.configURL);
+ let displayDomain = lazy.IDNService.convertToDisplayIDN(
+ providerURL.host,
+ {}
+ );
let headerMessage = localization.formatValueSync(
"identity-credential-header-accounts",
{
- host: "<>",
+ provider: displayDomain,
}
);
- let descriptionMessage = localization.formatValueSync(
- "identity-credential-description-account-explanation",
- {
- host: currentOrigin,
- }
- );
- let [cancel] = localization.formatMessagesSync([
+ let [accept, cancel] = localization.formatMessagesSync([
+ { id: "identity-credential-accept-button" },
{ id: "identity-credential-cancel-button" },
]);
let cancelLabel = cancel.attributes.find(x => x.name == "label").value;
let cancelKey = cancel.attributes.find(x => x.name == "accesskey").value;
-
- // Add the description text
- browser.ownerDocument.getElementById(
- "credential-account-explanation"
- ).textContent = descriptionMessage;
+ let acceptLabel = accept.attributes.find(x => x.name == "label").value;
+ let acceptKey = accept.attributes.find(x => x.name == "accesskey").value;
// Build the choices into the panel
let listBox = browser.ownerDocument.getElementById(
@@ -343,30 +372,59 @@ export class IdentityCredentialPromptService {
);
for (const [accountIndex, account] of accountList.accounts.entries()) {
let newItem = itemTemplate.content.firstElementChild.cloneNode(true);
- newItem.firstElementChild.textContent = account.email;
- newItem.setAttribute("oncommand", "this.callback()");
- newItem.callback = function() {
- let notification = browser.ownerGlobal.PopupNotifications.getNotification(
- "identity-credential",
- browser
- );
- browser.ownerGlobal.PopupNotifications.remove(notification);
- resolve(accountIndex);
- };
+ let newRadio = newItem.getElementsByClassName(
+ "identity-credential-list-item-radio"
+ )[0];
+ newRadio.value = accountIndex;
+ newRadio.addEventListener("change", function(event) {
+ for (let item of listBox.children) {
+ item.classList.remove("checked");
+ }
+ if (event.target.checked) {
+ event.target.parentElement.classList.add("checked");
+ }
+ });
+ if (accountIndex == 0) {
+ newRadio.checked = true;
+ newItem.classList.add("checked");
+ }
+ newItem.getElementsByClassName(
+ "identity-credential-list-item-label-name"
+ )[0].textContent = account.name;
+ newItem.getElementsByClassName(
+ "identity-credential-list-item-label-email"
+ )[0].textContent = account.email;
listBox.append(newItem);
}
// Construct the necessary arguments for notification behavior
let options = {
- name: currentOrigin,
- };
- let mainAction = {
- label: cancelLabel,
- accessKey: cancelKey,
- callback(event) {
- reject();
+ hideClose: true,
+ eventCallback: (topic, nextRemovalReason, isCancel) => {
+ if (topic == "removed" && isCancel) {
+ reject();
+ }
},
};
+ let mainAction = {
+ label: acceptLabel,
+ accessKey: acceptKey,
+ callback(event) {
+ let result = listBox.querySelector(
+ ".identity-credential-list-item-radio:checked"
+ ).value;
+ resolve(parseInt(result));
+ },
+ };
+ let secondaryActions = [
+ {
+ label: cancelLabel,
+ accessKey: cancelKey,
+ callback(event) {
+ reject();
+ },
+ },
+ ];
// Show the popup
browser.ownerDocument.getElementById(
@@ -384,7 +442,7 @@ export class IdentityCredentialPromptService {
headerMessage,
"identity-credential-notification-icon",
mainAction,
- null,
+ secondaryActions,
options
);
});
@@ -405,7 +463,7 @@ export class IdentityCredentialPromptService {
browser
);
if (notification) {
- browser.ownerGlobal.PopupNotifications.remove(notification);
+ browser.ownerGlobal.PopupNotifications.remove(notification, true);
}
}
}