1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +00:00

[PM-15436] Standalone password entry should trigger save to bitwarden prompt. (#14110)

* Modify behavior so standalone password entry (with or without generator) should trigger save to bitwarden prompt.

* Rename intent to action, extend button/action styles.

* Ensure font weight is returned to normal.

* Make save login message a button to handle accessibility, adds helper function.

* Fix failing snapshot by reintigrating erroneously removed line.

* Update snapshot to match new saveLoginButton.

* Add add'l open in new window message to aria label.

* Update snapshot with open in new window message.
This commit is contained in:
Miles Blackwood
2025-04-15 20:00:08 -04:00
committed by GitHub
parent a61d878081
commit cb86948423
6 changed files with 61 additions and 56 deletions

View File

@@ -4928,8 +4928,8 @@
"message": "Password regenerated", "message": "Password regenerated",
"description": "Notification message for when a password has been regenerated" "description": "Notification message for when a password has been regenerated"
}, },
"saveLoginToBitwarden": { "saveToBitwarden": {
"message": "Save login to Bitwarden?", "message": "Save to Bitwarden",
"description": "Confirmation message for saving a login to Bitwarden" "description": "Confirmation message for saving a login to Bitwarden"
}, },
"spaceCharacterDescriptor": { "spaceCharacterDescriptor": {

View File

@@ -1852,7 +1852,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
/** /**
* Verifies whether the save login inline menu view should be shown. This requires that * Verifies whether the save login inline menu view should be shown. This requires that
* the login data on the page contains a username and either a current or new password. * the login data on the page contains either a current or new password.
* *
* @param tab - The tab to check for login data * @param tab - The tab to check for login data
*/ */
@@ -1869,7 +1869,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
return ( return (
(this.shouldShowInlineMenuAccountCreation() || (this.shouldShowInlineMenuAccountCreation() ||
this.focusedFieldMatchesFillType(InlineMenuFillType.PasswordGeneration)) && this.focusedFieldMatchesFillType(InlineMenuFillType.PasswordGeneration)) &&
!!(loginData.username && (loginData.password || loginData.newPassword)) !!(loginData.password || loginData.newPassword)
); );
} }
@@ -2157,7 +2157,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
"passwordRegenerated", "passwordRegenerated",
"passwords", "passwords",
"regeneratePassword", "regeneratePassword",
"saveLoginToBitwarden", "saveToBitwarden",
"toggleBitwardenVaultOverlay", "toggleBitwardenVaultOverlay",
"totpCodeAria", "totpCodeAria",
"totpSecondsSpanAria", "totpSecondsSpanAria",

View File

@@ -4,47 +4,14 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList creates the build sav
<div <div
class="inline-menu-list-container theme_light" class="inline-menu-list-container theme_light"
> >
<div
class="save-login inline-menu-list-message"
/>
<div <div
class="inline-menu-list-button-container" class="inline-menu-list-button-container"
> >
<button <button
aria-label="" aria-label=", opensInANewWindow"
class="add-new-item-button inline-menu-list-button inline-menu-list-action" class="save-login inline-menu-list-button inline-menu-list-action"
id="new-item-button"
tabindex="-1" tabindex="-1"
> />
<svg
aria-hidden="true"
fill="none"
height="17"
width="17"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
clip-rule="evenodd"
d="M9.607 7.15h5.35c.322 0 .627.133.847.362a1.213 1.213 0 0 1 .002 1.68c-.221.23-.527.363-.85.363H9.607v5.652c0 .312-.12.613-.336.839a1.176 1.176 0 0 1-1.696.003 1.21 1.21 0 0 1-.34-.842V9.555H1.888a1.173 1.173 0 0 1-.847-.361A1.193 1.193 0 0 1 .7 8.352a1.219 1.219 0 0 1 .336-.838 1.175 1.175 0 0 1 .85-.364h5.349V1.635c0-.31.118-.611.336-.84A1.176 1.176 0 0 1 9.268.795c.222.228.34.533.34.841V7.15Z"
fill="#175DDC"
fill-rule="evenodd"
/>
</g>
<defs>
<clippath
id="a"
>
<path
d="M.421.421h16v16h-16z"
fill="#fff"
/>
</clippath>
</defs>
</svg>
</button>
</div> </div>
</div> </div>
`; `;

View File

@@ -1089,12 +1089,12 @@ describe("AutofillInlineMenuList", () => {
}); });
describe("displaying the save login view", () => { describe("displaying the save login view", () => {
let buildSaveLoginInlineMenuListSpy: jest.SpyInstance; let buildSaveLoginInlineMenuSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
buildSaveLoginInlineMenuListSpy = jest.spyOn( buildSaveLoginInlineMenuSpy = jest.spyOn(
autofillInlineMenuList as any, autofillInlineMenuList as any,
"buildSaveLoginInlineMenuList", "buildSaveLoginInlineMenu",
); );
}); });
@@ -1108,7 +1108,7 @@ describe("AutofillInlineMenuList", () => {
postWindowMessage({ command: "showSaveLoginInlineMenuList" }); postWindowMessage({ command: "showSaveLoginInlineMenuList" });
expect(buildSaveLoginInlineMenuListSpy).not.toHaveBeenCalled(); expect(buildSaveLoginInlineMenuSpy).not.toHaveBeenCalled();
}); });
it("builds the save login item view", async () => { it("builds the save login item view", async () => {
@@ -1117,7 +1117,7 @@ describe("AutofillInlineMenuList", () => {
postWindowMessage({ command: "showSaveLoginInlineMenuList" }); postWindowMessage({ command: "showSaveLoginInlineMenuList" });
expect(buildSaveLoginInlineMenuListSpy).toHaveBeenCalled(); expect(buildSaveLoginInlineMenuSpy).toHaveBeenCalled();
}); });
}); });

View File

@@ -3,6 +3,8 @@
import "@webcomponents/custom-elements"; import "@webcomponents/custom-elements";
import "lit/polyfill-support.js"; import "lit/polyfill-support.js";
import { FocusableElement } from "tabbable";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EVENTS, UPDATE_PASSKEYS_HEADINGS_ON_SCROLL } from "@bitwarden/common/autofill/constants"; import { EVENTS, UPDATE_PASSKEYS_HEADINGS_ON_SCROLL } from "@bitwarden/common/autofill/constants";
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
@@ -117,7 +119,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
} }
if (showSaveLoginMenu) { if (showSaveLoginMenu) {
this.buildSaveLoginInlineMenuList(); this.buildSaveLoginInlineMenu();
return; return;
} }
@@ -165,24 +167,52 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
/** /**
* Builds the inline menu list as a prompt that asks the user if they'd like to save the login data. * Builds the inline menu list as a prompt that asks the user if they'd like to save the login data.
*/ */
private buildSaveLoginInlineMenuList() { private buildSaveLoginInlineMenu() {
const saveLoginMessage = globalThis.document.createElement("div"); const saveLoginButton = globalThis.document.createElement("button");
saveLoginMessage.classList.add("save-login", "inline-menu-list-message"); saveLoginButton.classList.add(
saveLoginMessage.textContent = this.getTranslation("saveLoginToBitwarden"); "save-login",
"inline-menu-list-button",
"inline-menu-list-action",
);
saveLoginButton.tabIndex = -1;
saveLoginButton.setAttribute(
"aria-label",
`${this.getTranslation("saveToBitwarden")}, ${this.getTranslation("opensInANewWindow")}`,
);
saveLoginButton.textContent = this.getTranslation("saveToBitwarden");
saveLoginButton.addEventListener(EVENTS.CLICK, this.handleNewLoginVaultItemAction);
saveLoginButton.addEventListener(EVENTS.KEYUP, this.handleSaveLoginInlineMenuKeyUp);
const inlineMenuListButtonContainer = this.buildButtonContainer(saveLoginButton);
const newItemButton = this.buildNewItemButton(true);
this.showInlineMenuAccountCreation = true; this.showInlineMenuAccountCreation = true;
this.inlineMenuListContainer.append(saveLoginMessage, newItemButton); this.inlineMenuListContainer.append(inlineMenuListButtonContainer);
} }
private handleSaveLoginInlineMenuKeyUp = (event: KeyboardEvent) => {
const listenedForKeys = new Set(["ArrowDown"]);
if (!listenedForKeys.has(event.code) || !(event.target instanceof Element)) {
return;
}
event.preventDefault();
if (event.code === "ArrowDown") {
(event.target as FocusableElement).focus();
return;
}
};
/** /**
* Handles the show save login inline menu list message that is triggered from the background script. * Handles the show save login inline menu list message that is triggered from the background script.
*/ */
private handleShowSaveLoginInlineMenuList() { private handleShowSaveLoginInlineMenuList() {
if (this.authStatus === AuthenticationStatus.Unlocked) { if (this.authStatus === AuthenticationStatus.Unlocked) {
this.resetInlineMenuContainer(); this.resetInlineMenuContainer();
this.buildSaveLoginInlineMenuList(); this.buildSaveLoginInlineMenu();
} }
} }
@@ -521,7 +551,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
this.newItemButtonElement.textContent = this.getNewItemButtonText(showLogin); this.newItemButtonElement.textContent = this.getNewItemButtonText(showLogin);
this.newItemButtonElement.setAttribute("aria-label", this.getNewItemAriaLabel(showLogin)); this.newItemButtonElement.setAttribute("aria-label", this.getNewItemAriaLabel(showLogin));
this.newItemButtonElement.prepend(buildSvgDomElement(plusIcon)); this.newItemButtonElement.prepend(buildSvgDomElement(plusIcon));
this.newItemButtonElement.addEventListener(EVENTS.CLICK, this.handeNewItemButtonClick); this.newItemButtonElement.addEventListener(EVENTS.CLICK, this.handleNewLoginVaultItemAction);
return this.buildButtonContainer(this.newItemButtonElement); return this.buildButtonContainer(this.newItemButtonElement);
} }
@@ -581,7 +611,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
* Handles the click event for the new item button. * Handles the click event for the new item button.
* Sends a message to the parent window to add a new vault item. * Sends a message to the parent window to add a new vault item.
*/ */
private handeNewItemButtonClick = () => { private handleNewLoginVaultItemAction = () => {
let addNewCipherType = this.inlineMenuFillType; let addNewCipherType = this.inlineMenuFillType;
if (this.showInlineMenuAccountCreation) { if (this.showInlineMenuAccountCreation) {

View File

@@ -45,6 +45,14 @@ body * {
&.no-items, &.no-items,
&.save-login { &.save-login {
font-size: 1.6rem; font-size: 1.6rem;
&:has(:focus-visible) {
outline-width: 0.2rem;
outline-style: solid;
@include themify($themes) {
outline-color: themed("focusOutlineColor");
}
}
} }
} }