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); } } }