mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 16:53:34 +00:00
[PM-5189] Reworking project structure to ensure we can better differentiate the inline menu feature from other features
This commit is contained in:
@@ -9,13 +9,3 @@ exports[`AutofillInlineMenuIframeService initMenuIframe sets up the iframe's att
|
||||
title="title"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`AutofillOverlayIframeService initMenuIframe sets up the iframe's attributes 1`] = `
|
||||
<iframe
|
||||
allowtransparency="true"
|
||||
src="chrome-extension://id/overlay/menu.html"
|
||||
style="all: initial !important; position: fixed !important; display: block !important; z-index: 2147483647 !important; line-height: 0 !important; overflow: hidden !important; transition: opacity 125ms ease-out 0s !important; visibility: visible !important; clip-path: none !important; pointer-events: auto !important; margin: 0px !important; padding: 0px !important; color-scheme: normal !important; opacity: 0 !important; height: 0px;"
|
||||
tabindex="-1"
|
||||
title="title"
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AutofillOverlayIframeService initMenuIframe sets up the iframe's attributes 1`] = `
|
||||
<iframe
|
||||
allowtransparency="true"
|
||||
src="chrome-extension://id/overlay/menu.html"
|
||||
style="all: initial !important; position: fixed !important; display: block !important; z-index: 2147483647 !important; line-height: 0 !important; overflow: hidden !important; transition: opacity 125ms ease-out 0s !important; visibility: visible !important; clip-path: none !important; pointer-events: auto !important; margin: 0px !important; padding: 0px !important; color-scheme: normal !important; opacity: 0 !important; height: 0px;"
|
||||
tabindex="-1"
|
||||
title="title"
|
||||
/>
|
||||
`;
|
||||
@@ -81,85 +81,3 @@ exports[`AutofillInlineMenuButton initAutofillInlineMenuButton creates the butto
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`AutofillOverlayButton initAutofillInlineMenuButton creates the button element with the locked icon when the user's auth status is not Unlocked 1`] = `
|
||||
<button
|
||||
aria-label="toggleBitwardenVaultOverlay"
|
||||
class="inline-menu-button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="inline-menu-button-svg-icon logo-locked-icon"
|
||||
fill="none"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
<path
|
||||
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<circle
|
||||
cx="12.889"
|
||||
cy="12.889"
|
||||
fill="#F8F9FA"
|
||||
r="4.889"
|
||||
/>
|
||||
<path
|
||||
d="M11.26 11.717h2.37v-.848c0-.313-.116-.58-.348-.8a1.17 1.17 0 0 0-.838-.332c-.327 0-.606.11-.838.332a1.066 1.066 0 0 0-.347.8v.848Zm3.851.424v2.546a.4.4 0 0 1-.13.3.44.44 0 0 1-.314.124h-4.445a.44.44 0 0 1-.315-.124.4.4 0 0 1-.13-.3V12.14a.4.4 0 0 1 .13-.3.44.44 0 0 1 .315-.124h.148v-.848c0-.542.204-1.008.612-1.397a2.044 2.044 0 0 1 1.462-.583c.568 0 1.056.194 1.463.583.408.39.611.855.611 1.397v.848h.149a.44.44 0 0 1 .315.124.4.4 0 0 1 .13.3Z"
|
||||
fill="#555"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<rect
|
||||
fill="#fff"
|
||||
height="16"
|
||||
rx="2"
|
||||
width="16"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`AutofillOverlayButton initAutofillInlineMenuButton creates the button element with the normal icon when the user's auth status is Unlocked 1`] = `
|
||||
<button
|
||||
aria-label="toggleBitwardenVaultOverlay"
|
||||
class="inline-menu-button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="inline-menu-button-svg-icon logo-icon"
|
||||
fill="none"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
width="14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
<path
|
||||
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AutofillOverlayButton initAutofillOverlayButton creates the button element with the locked icon when the user's auth status is not Unlocked 1`] = `
|
||||
<button
|
||||
aria-label="toggleBitwardenVaultOverlay"
|
||||
class="inline-menu-button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="inline-menu-button-svg-icon logo-locked-icon"
|
||||
fill="none"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
<path
|
||||
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<circle
|
||||
cx="12.889"
|
||||
cy="12.889"
|
||||
fill="#F8F9FA"
|
||||
r="4.889"
|
||||
/>
|
||||
<path
|
||||
d="M11.26 11.717h2.37v-.848c0-.313-.116-.58-.348-.8a1.17 1.17 0 0 0-.838-.332c-.327 0-.606.11-.838.332a1.066 1.066 0 0 0-.347.8v.848Zm3.851.424v2.546a.4.4 0 0 1-.13.3.44.44 0 0 1-.314.124h-4.445a.44.44 0 0 1-.315-.124.4.4 0 0 1-.13-.3V12.14a.4.4 0 0 1 .13-.3.44.44 0 0 1 .315-.124h.148v-.848c0-.542.204-1.008.612-1.397a2.044 2.044 0 0 1 1.462-.583c.568 0 1.056.194 1.463.583.408.39.611.855.611 1.397v.848h.149a.44.44 0 0 1 .315.124.4.4 0 0 1 .13.3Z"
|
||||
fill="#555"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<rect
|
||||
fill="#fff"
|
||||
height="16"
|
||||
rx="2"
|
||||
width="16"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`AutofillOverlayButton initAutofillOverlayButton creates the button element with the normal icon when the user's auth status is Unlocked 1`] = `
|
||||
<button
|
||||
aria-label="toggleBitwardenVaultOverlay"
|
||||
class="inline-menu-button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="inline-menu-button-svg-icon logo-icon"
|
||||
fill="none"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
width="14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
<path
|
||||
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
@@ -41,7 +41,7 @@ class AutofillInlineMenuButton extends AutofillInlineMenuPageElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the overlay button. Facilitates ensuring that the page
|
||||
* Initializes the inline menu button. Facilitates ensuring that the page
|
||||
* is set up with the expected styles and translations.
|
||||
*
|
||||
* @param authStatus - The authentication status of the user
|
||||
@@ -114,7 +114,7 @@ class AutofillInlineMenuButton extends AutofillInlineMenuPageElement {
|
||||
|
||||
/**
|
||||
* Checks if the button is focused. If it is not, then it posts a message
|
||||
* to the parent window indicating that the overlay should be closed.
|
||||
* to the parent window indicating that the inline menu should be closed.
|
||||
*/
|
||||
private checkButtonFocused() {
|
||||
if (globalThis.document.hasFocus()) {
|
||||
|
||||
@@ -1,15 +1,66 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a list of ciphers 1`] = `
|
||||
exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with an empty list of ciphers creates the views for the no results inline menu 1`] = `
|
||||
<div
|
||||
class="overlay-list-container theme_light"
|
||||
class="inline-menu-list-container theme_light"
|
||||
>
|
||||
<div
|
||||
class="no-items inline-menu-list-message"
|
||||
>
|
||||
noItemsToShow
|
||||
</div>
|
||||
<div
|
||||
class="inline-menu-list-button-container"
|
||||
>
|
||||
<button
|
||||
aria-label="addNewVaultItem, opensInANewWindow"
|
||||
class="add-new-item-button inline-menu-list-button"
|
||||
id="new-item-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="17"
|
||||
viewBox="0 0 16 17"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M15.222 7.914H8.963a.471.471 0 0 1-.34-.147.512.512 0 0 1-.142-.353V.99c0-.133-.05-.26-.14-.354a.471.471 0 0 0-.68 0 .51.51 0 0 0-.142.354v6.424c0 .132-.051.26-.142.353a.474.474 0 0 1-.34.147H.777a.47.47 0 0 0-.34.146.5.5 0 0 0-.14.354.522.522 0 0 0 .14.353.48.48 0 0 0 .34.147h6.26c.128 0 .25.052.34.146.09.094.142.221.142.354v6.576c0 .132.05.26.14.353a.471.471 0 0 0 .68 0 .512.512 0 0 0 .142-.353V9.414c0-.133.051-.26.142-.354a.474.474 0 0 1 .34-.146h6.26c.127 0 .25-.053.34-.147a.511.511 0 0 0 0-.707.472.472 0 0 0-.34-.146Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<path
|
||||
d="M0 .49h16v16H0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
newItem
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a list of ciphers 1`] = `
|
||||
<div
|
||||
class="inline-menu-list-container theme_light"
|
||||
>
|
||||
<ul
|
||||
class="overlay-actions-list"
|
||||
class="inline-menu-list-actions"
|
||||
role="list"
|
||||
>
|
||||
<li
|
||||
class="overlay-actions-list-item"
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
@@ -79,7 +130,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="overlay-actions-list-item"
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
@@ -148,7 +199,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="overlay-actions-list-item"
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
@@ -204,7 +255,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="overlay-actions-list-item"
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
@@ -289,7 +340,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="overlay-actions-list-item"
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
@@ -359,7 +410,7 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="overlay-actions-list-item"
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
@@ -432,22 +483,22 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AutofillOverlayList initAutofillInlineMenuList the locked overlay for an unauthenticated user creates the views for the locked overlay 1`] = `
|
||||
exports[`AutofillInlineMenuList initAutofillInlineMenuList the locked inline menu for an unauthenticated user creates the views for the locked inline menu 1`] = `
|
||||
<div
|
||||
class="overlay-list-container theme_light"
|
||||
class="inline-menu-list-container theme_light"
|
||||
>
|
||||
<div
|
||||
class="locked-overlay overlay-list-message"
|
||||
id="locked-overlay-description"
|
||||
class="locked-inline-menu inline-menu-list-message"
|
||||
id="locked-inline-menu-description"
|
||||
>
|
||||
unlockYourAccount
|
||||
</div>
|
||||
<div
|
||||
class="overlay-list-button-container"
|
||||
class="inline-menu-list-button-container"
|
||||
>
|
||||
<button
|
||||
aria-label="unlockAccount, opensInANewWindow"
|
||||
class="unlock-button overlay-list-button"
|
||||
class="unlock-button inline-menu-list-button"
|
||||
id="unlock-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
@@ -483,54 +534,3 @@ exports[`AutofillOverlayList initAutofillInlineMenuList the locked overlay for a
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AutofillOverlayList initAutofillInlineMenuList the overlay with an empty list of ciphers creates the views for the no results overlay 1`] = `
|
||||
<div
|
||||
class="overlay-list-container theme_light"
|
||||
>
|
||||
<div
|
||||
class="no-items overlay-list-message"
|
||||
>
|
||||
noItemsToShow
|
||||
</div>
|
||||
<div
|
||||
class="overlay-list-button-container"
|
||||
>
|
||||
<button
|
||||
aria-label="addNewVaultItem, opensInANewWindow"
|
||||
class="add-new-item-button overlay-list-button"
|
||||
id="new-item-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="17"
|
||||
viewBox="0 0 16 17"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M15.222 7.914H8.963a.471.471 0 0 1-.34-.147.512.512 0 0 1-.142-.353V.99c0-.133-.05-.26-.14-.354a.471.471 0 0 0-.68 0 .51.51 0 0 0-.142.354v6.424c0 .132-.051.26-.142.353a.474.474 0 0 1-.34.147H.777a.47.47 0 0 0-.34.146.5.5 0 0 0-.14.354.522.522 0 0 0 .14.353.48.48 0 0 0 .34.147h6.26c.128 0 .25.052.34.146.09.094.142.221.142.354v6.576c0 .132.05.26.14.353a.471.471 0 0 0 .68 0 .512.512 0 0 0 .142-.353V9.414c0-.133.051-.26.142-.354a.474.474 0 0 1 .34-.146h6.26c.127 0 .25-.053.34-.147a.511.511 0 0 0 0-.707.472.472 0 0 0-.34-.146Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<path
|
||||
d="M0 .49h16v16H0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
newItem
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,12 @@ import { mock } from "jest-mock-extended";
|
||||
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
|
||||
import { createInitAutofillOverlayListMessageMock } from "../../../../spec/autofill-mocks";
|
||||
import { createInitAutofillInlineMenuListMessageMock } from "../../../../spec/autofill-mocks";
|
||||
import { flushPromises, postWindowMessage } from "../../../../spec/testing-utils";
|
||||
|
||||
import { AutofillInlineMenuList } from "./autofill-inline-menu-list";
|
||||
|
||||
describe("AutofillOverlayList", () => {
|
||||
describe("AutofillInlineMenuList", () => {
|
||||
globalThis.customElements.define("autofill-inline-menu-list", AutofillInlineMenuList);
|
||||
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
@@ -15,12 +15,12 @@ describe("AutofillOverlayList", () => {
|
||||
disconnect: jest.fn(),
|
||||
}));
|
||||
|
||||
let autofillOverlayList: AutofillInlineMenuList;
|
||||
const portKey: string = "overlayListPortKey";
|
||||
let autofillInlineMenuList: AutofillInlineMenuList;
|
||||
const portKey: string = "inlineMenuListPortKey";
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `<autofill-inline-menu-list></autofill-inline-menu-list>`;
|
||||
autofillOverlayList = document.querySelector("autofill-inline-menu-list");
|
||||
autofillInlineMenuList = document.querySelector("autofill-inline-menu-list");
|
||||
jest.spyOn(globalThis.document, "createElement");
|
||||
jest.spyOn(globalThis.parent, "postMessage");
|
||||
});
|
||||
@@ -30,10 +30,10 @@ describe("AutofillOverlayList", () => {
|
||||
});
|
||||
|
||||
describe("initAutofillInlineMenuList", () => {
|
||||
describe("the locked overlay for an unauthenticated user", () => {
|
||||
describe("the locked inline menu for an unauthenticated user", () => {
|
||||
beforeEach(() => {
|
||||
postWindowMessage(
|
||||
createInitAutofillOverlayListMessageMock({
|
||||
createInitAutofillInlineMenuListMessageMock({
|
||||
authStatus: AuthenticationStatus.Locked,
|
||||
cipherList: [],
|
||||
portKey,
|
||||
@@ -41,13 +41,13 @@ describe("AutofillOverlayList", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("creates the views for the locked overlay", () => {
|
||||
expect(autofillOverlayList["overlayListContainer"]).toMatchSnapshot();
|
||||
it("creates the views for the locked inline menu", () => {
|
||||
expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("allows the user to unlock the vault", () => {
|
||||
const unlockButton =
|
||||
autofillOverlayList["overlayListContainer"].querySelector("#unlock-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector("#unlock-button");
|
||||
|
||||
unlockButton.dispatchEvent(new Event("click"));
|
||||
|
||||
@@ -58,10 +58,10 @@ describe("AutofillOverlayList", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("the overlay with an empty list of ciphers", () => {
|
||||
describe("the inline menu with an empty list of ciphers", () => {
|
||||
beforeEach(() => {
|
||||
postWindowMessage(
|
||||
createInitAutofillOverlayListMessageMock({
|
||||
createInitAutofillInlineMenuListMessageMock({
|
||||
authStatus: AuthenticationStatus.Unlocked,
|
||||
ciphers: [],
|
||||
portKey,
|
||||
@@ -69,13 +69,13 @@ describe("AutofillOverlayList", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("creates the views for the no results overlay", () => {
|
||||
expect(autofillOverlayList["overlayListContainer"]).toMatchSnapshot();
|
||||
it("creates the views for the no results inline menu", () => {
|
||||
expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("allows the user to add a vault item", () => {
|
||||
const addVaultItemButton =
|
||||
autofillOverlayList["overlayListContainer"].querySelector("#new-item-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector("#new-item-button");
|
||||
|
||||
addVaultItemButton.dispatchEvent(new Event("click"));
|
||||
|
||||
@@ -88,23 +88,23 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
describe("the list of ciphers for an authenticated user", () => {
|
||||
beforeEach(() => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock());
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock());
|
||||
});
|
||||
|
||||
it("creates the view for a list of ciphers", () => {
|
||||
expect(autofillOverlayList["overlayListContainer"]).toMatchSnapshot();
|
||||
expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("loads ciphers on scroll one page at a time", () => {
|
||||
jest.useFakeTimers();
|
||||
const originalListOfElements =
|
||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".cipher-container");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(".cipher-container");
|
||||
|
||||
window.dispatchEvent(new Event("scroll"));
|
||||
jest.runAllTimers();
|
||||
|
||||
const updatedListOfElements =
|
||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".cipher-container");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(".cipher-container");
|
||||
|
||||
expect(originalListOfElements.length).toBe(6);
|
||||
expect(updatedListOfElements.length).toBe(8);
|
||||
@@ -112,9 +112,9 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("debounces the ciphers scroll handler", () => {
|
||||
jest.useFakeTimers();
|
||||
autofillOverlayList["cipherListScrollDebounceTimeout"] = setTimeout(jest.fn, 0);
|
||||
autofillInlineMenuList["cipherListScrollDebounceTimeout"] = setTimeout(jest.fn, 0);
|
||||
const handleDebouncedScrollEventSpy = jest.spyOn(
|
||||
autofillOverlayList as any,
|
||||
autofillInlineMenuList as any,
|
||||
"handleDebouncedScrollEvent",
|
||||
);
|
||||
|
||||
@@ -130,12 +130,12 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
describe("fill cipher button event listeners", () => {
|
||||
beforeEach(() => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||
});
|
||||
|
||||
it("allows the user to fill a cipher on click", () => {
|
||||
const fillCipherButton =
|
||||
autofillOverlayList["overlayListContainer"].querySelector(".fill-cipher-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".fill-cipher-button");
|
||||
|
||||
fillCipherButton.dispatchEvent(new Event("click"));
|
||||
|
||||
@@ -147,7 +147,9 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("allows the user to move keyboard focus to the next cipher element on ArrowDown", () => {
|
||||
const fillCipherElements =
|
||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".fill-cipher-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(
|
||||
".fill-cipher-button",
|
||||
);
|
||||
const firstFillCipherElement = fillCipherElements[0];
|
||||
const secondFillCipherElement = fillCipherElements[1];
|
||||
jest.spyOn(secondFillCipherElement as HTMLElement, "focus");
|
||||
@@ -159,7 +161,9 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("directs focus to the first item in the cipher list if no cipher is present after the current one when pressing ArrowDown", () => {
|
||||
const fillCipherElements =
|
||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".fill-cipher-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(
|
||||
".fill-cipher-button",
|
||||
);
|
||||
const lastFillCipherElement = fillCipherElements[fillCipherElements.length - 1];
|
||||
const firstFillCipherElement = fillCipherElements[0];
|
||||
jest.spyOn(firstFillCipherElement as HTMLElement, "focus");
|
||||
@@ -171,7 +175,9 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("allows the user to move keyboard focus to the previous cipher element on ArrowUp", () => {
|
||||
const fillCipherElements =
|
||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".fill-cipher-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(
|
||||
".fill-cipher-button",
|
||||
);
|
||||
const firstFillCipherElement = fillCipherElements[0];
|
||||
const secondFillCipherElement = fillCipherElements[1];
|
||||
jest.spyOn(firstFillCipherElement as HTMLElement, "focus");
|
||||
@@ -183,7 +189,9 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("directs focus to the last item in the cipher list if no cipher is present before the current one when pressing ArrowUp", () => {
|
||||
const fillCipherElements =
|
||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".fill-cipher-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(
|
||||
".fill-cipher-button",
|
||||
);
|
||||
const firstFillCipherElement = fillCipherElements[0];
|
||||
const lastFillCipherElement = fillCipherElements[fillCipherElements.length - 1];
|
||||
jest.spyOn(lastFillCipherElement as HTMLElement, "focus");
|
||||
@@ -195,7 +203,7 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("allows the user to move keyboard focus to the view cipher button on ArrowRight", () => {
|
||||
const cipherContainerElement =
|
||||
autofillOverlayList["overlayListContainer"].querySelector(".cipher-container");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".cipher-container");
|
||||
const fillCipherElement = cipherContainerElement.querySelector(".fill-cipher-button");
|
||||
const viewCipherButton = cipherContainerElement.querySelector(".view-cipher-button");
|
||||
jest.spyOn(viewCipherButton as HTMLElement, "focus");
|
||||
@@ -207,7 +215,7 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("ignores keyup events that do not include ArrowUp, ArrowDown, or ArrowRight", () => {
|
||||
const fillCipherElement =
|
||||
autofillOverlayList["overlayListContainer"].querySelector(".fill-cipher-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".fill-cipher-button");
|
||||
jest.spyOn(fillCipherElement as HTMLElement, "focus");
|
||||
|
||||
fillCipherElement.dispatchEvent(new KeyboardEvent("keyup", { code: "ArrowLeft" }));
|
||||
@@ -218,12 +226,12 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
describe("view cipher button event listeners", () => {
|
||||
beforeEach(() => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||
});
|
||||
|
||||
it("allows the user to view a cipher on click", () => {
|
||||
const viewCipherButton =
|
||||
autofillOverlayList["overlayListContainer"].querySelector(".view-cipher-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".view-cipher-button");
|
||||
|
||||
viewCipherButton.dispatchEvent(new Event("click"));
|
||||
|
||||
@@ -235,7 +243,7 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("allows the user to move keyboard focus to the current cipher element on ArrowLeft", () => {
|
||||
const cipherContainerElement =
|
||||
autofillOverlayList["overlayListContainer"].querySelector(".cipher-container");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".cipher-container");
|
||||
const fillCipherButton = cipherContainerElement.querySelector(".fill-cipher-button");
|
||||
const viewCipherButton = cipherContainerElement.querySelector(".view-cipher-button");
|
||||
jest.spyOn(fillCipherButton as HTMLElement, "focus");
|
||||
@@ -247,7 +255,7 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("allows the user to move keyboard to the next cipher element on ArrowDown", () => {
|
||||
const cipherContainerElements =
|
||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".cipher-container");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(".cipher-container");
|
||||
const viewCipherButton = cipherContainerElements[0].querySelector(".view-cipher-button");
|
||||
const secondFillCipherButton =
|
||||
cipherContainerElements[1].querySelector(".fill-cipher-button");
|
||||
@@ -260,7 +268,7 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("allows the user to move keyboard focus to the previous cipher element on ArrowUp", () => {
|
||||
const cipherContainerElements =
|
||||
autofillOverlayList["overlayListContainer"].querySelectorAll(".cipher-container");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll(".cipher-container");
|
||||
const viewCipherButton = cipherContainerElements[1].querySelector(".view-cipher-button");
|
||||
const firstFillCipherButton =
|
||||
cipherContainerElements[0].querySelector(".fill-cipher-button");
|
||||
@@ -273,7 +281,7 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
it("ignores keyup events that do not include ArrowUp, ArrowDown, or ArrowRight", () => {
|
||||
const viewCipherButton =
|
||||
autofillOverlayList["overlayListContainer"].querySelector(".view-cipher-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".view-cipher-button");
|
||||
jest.spyOn(viewCipherButton as HTMLElement, "focus");
|
||||
|
||||
viewCipherButton.dispatchEvent(new KeyboardEvent("keyup", { code: "ArrowRight" }));
|
||||
@@ -286,10 +294,10 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
describe("global event listener handlers", () => {
|
||||
beforeEach(() => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||
});
|
||||
|
||||
it("does not post a `checkAutofillInlineMenuButtonFocused` message to the parent if the overlay is currently focused", () => {
|
||||
it("does not post a `checkAutofillInlineMenuButtonFocused` message to the parent if the inline menu is currently focused", () => {
|
||||
jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(true);
|
||||
|
||||
postWindowMessage({ command: "checkAutofillInlineMenuListFocused" });
|
||||
@@ -297,7 +305,7 @@ describe("AutofillOverlayList", () => {
|
||||
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("posts a `checkAutofillInlineMenuButtonFocused` message to the parent if the overlay is not currently focused", () => {
|
||||
it("posts a `checkAutofillInlineMenuButtonFocused` message to the parent if the inline menu is not currently focused", () => {
|
||||
jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(false);
|
||||
|
||||
postWindowMessage({ command: "checkAutofillInlineMenuListFocused" });
|
||||
@@ -309,43 +317,43 @@ describe("AutofillOverlayList", () => {
|
||||
});
|
||||
|
||||
it("updates the list of ciphers", () => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock());
|
||||
const updateCiphersSpy = jest.spyOn(autofillOverlayList as any, "updateListItems");
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock());
|
||||
const updateCiphersSpy = jest.spyOn(autofillInlineMenuList as any, "updateListItems");
|
||||
|
||||
postWindowMessage({ command: "updateAutofillInlineMenuListCiphers" });
|
||||
|
||||
expect(updateCiphersSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("directing user focus into the overlay list", () => {
|
||||
describe("directing user focus into the inline menu list", () => {
|
||||
it("sets ARIA attributes that define the list as a `dialog` to screen reader users", () => {
|
||||
postWindowMessage(
|
||||
createInitAutofillOverlayListMessageMock({
|
||||
createInitAutofillInlineMenuListMessageMock({
|
||||
authStatus: AuthenticationStatus.Locked,
|
||||
cipherList: [],
|
||||
}),
|
||||
);
|
||||
const overlayContainerSetAttributeSpy = jest.spyOn(
|
||||
autofillOverlayList["overlayListContainer"],
|
||||
const inlineMenuContainerSetAttributeSpy = jest.spyOn(
|
||||
autofillInlineMenuList["inlineMenuListContainer"],
|
||||
"setAttribute",
|
||||
);
|
||||
|
||||
postWindowMessage({ command: "focusInlineMenuList" });
|
||||
|
||||
expect(overlayContainerSetAttributeSpy).toHaveBeenCalledWith("role", "dialog");
|
||||
expect(overlayContainerSetAttributeSpy).toHaveBeenCalledWith("aria-modal", "true");
|
||||
expect(inlineMenuContainerSetAttributeSpy).toHaveBeenCalledWith("role", "dialog");
|
||||
expect(inlineMenuContainerSetAttributeSpy).toHaveBeenCalledWith("aria-modal", "true");
|
||||
});
|
||||
|
||||
it("focuses the unlock button element if the user is not authenticated", async () => {
|
||||
postWindowMessage(
|
||||
createInitAutofillOverlayListMessageMock({
|
||||
createInitAutofillInlineMenuListMessageMock({
|
||||
authStatus: AuthenticationStatus.Locked,
|
||||
cipherList: [],
|
||||
}),
|
||||
);
|
||||
await flushPromises();
|
||||
const unlockButton =
|
||||
autofillOverlayList["overlayListContainer"].querySelector("#unlock-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector("#unlock-button");
|
||||
jest.spyOn(unlockButton as HTMLElement, "focus");
|
||||
|
||||
postWindowMessage({ command: "focusInlineMenuList" });
|
||||
@@ -354,10 +362,10 @@ describe("AutofillOverlayList", () => {
|
||||
});
|
||||
|
||||
it("focuses the new item button element if the cipher list is empty", async () => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ ciphers: [] }));
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ ciphers: [] }));
|
||||
await flushPromises();
|
||||
const newItemButton =
|
||||
autofillOverlayList["overlayListContainer"].querySelector("#new-item-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector("#new-item-button");
|
||||
jest.spyOn(newItemButton as HTMLElement, "focus");
|
||||
|
||||
postWindowMessage({ command: "focusInlineMenuList" });
|
||||
@@ -366,9 +374,9 @@ describe("AutofillOverlayList", () => {
|
||||
});
|
||||
|
||||
it("focuses the first cipher button element if the cipher list is populated", () => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock());
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock());
|
||||
const firstCipherItem =
|
||||
autofillOverlayList["overlayListContainer"].querySelector(".fill-cipher-button");
|
||||
autofillInlineMenuList["inlineMenuListContainer"].querySelector(".fill-cipher-button");
|
||||
jest.spyOn(firstCipherItem as HTMLElement, "focus");
|
||||
|
||||
postWindowMessage({ command: "focusInlineMenuList" });
|
||||
@@ -378,8 +386,8 @@ describe("AutofillOverlayList", () => {
|
||||
});
|
||||
|
||||
describe("blur event", () => {
|
||||
it("posts a message to the parent window indicating that the overlay has lost focus", () => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
||||
it("posts a message to the parent window indicating that the inline menu has lost focus", () => {
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||
|
||||
globalThis.dispatchEvent(new Event("blur"));
|
||||
|
||||
@@ -392,7 +400,7 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
describe("keydown event", () => {
|
||||
beforeEach(() => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||
});
|
||||
|
||||
it("skips redirecting keyboard focus when a KeyDown event triggers and the key is not a `Tab` or `Escape` key", () => {
|
||||
@@ -401,7 +409,7 @@ describe("AutofillOverlayList", () => {
|
||||
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("redirects the overlay focus out to the previous element on KeyDown of the `Tab+Shift` keys", () => {
|
||||
it("redirects the inline menu focus out to the previous element on KeyDown of the `Tab+Shift` keys", () => {
|
||||
globalThis.document.dispatchEvent(
|
||||
new KeyboardEvent("keydown", { code: "Tab", shiftKey: true }),
|
||||
);
|
||||
@@ -412,7 +420,7 @@ describe("AutofillOverlayList", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("redirects the overlay focus out to the next element on KeyDown of the `Tab` key", () => {
|
||||
it("redirects the inline menu focus out to the next element on KeyDown of the `Tab` key", () => {
|
||||
globalThis.document.dispatchEvent(new KeyboardEvent("keydown", { code: "Tab" }));
|
||||
|
||||
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
|
||||
@@ -421,7 +429,7 @@ describe("AutofillOverlayList", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("redirects the overlay focus out to the current element on KeyDown of the `Escape` key", () => {
|
||||
it("redirects the inline menu focus out to the current element on KeyDown of the `Escape` key", () => {
|
||||
globalThis.document.dispatchEvent(new KeyboardEvent("keydown", { code: "Escape" }));
|
||||
|
||||
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
|
||||
@@ -434,10 +442,10 @@ describe("AutofillOverlayList", () => {
|
||||
|
||||
describe("handleResizeObserver", () => {
|
||||
beforeEach(() => {
|
||||
postWindowMessage(createInitAutofillOverlayListMessageMock({ portKey }));
|
||||
postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey }));
|
||||
});
|
||||
|
||||
it("ignores resize entries whose target is not the overlay list", () => {
|
||||
it("ignores resize entries whose target is not the inline menu list", () => {
|
||||
const entries = [
|
||||
{
|
||||
target: mock<HTMLElement>(),
|
||||
@@ -445,20 +453,20 @@ describe("AutofillOverlayList", () => {
|
||||
},
|
||||
];
|
||||
|
||||
autofillOverlayList["handleResizeObserver"](entries as unknown as ResizeObserverEntry[]);
|
||||
autofillInlineMenuList["handleResizeObserver"](entries as unknown as ResizeObserverEntry[]);
|
||||
|
||||
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("posts a message to update the overlay list height if the list container is resized", () => {
|
||||
it("posts a message to update the inline menu list height if the list container is resized", () => {
|
||||
const entries = [
|
||||
{
|
||||
target: autofillOverlayList["overlayListContainer"],
|
||||
target: autofillInlineMenuList["inlineMenuListContainer"],
|
||||
contentRect: { height: 300 },
|
||||
},
|
||||
];
|
||||
|
||||
autofillOverlayList["handleResizeObserver"](entries as unknown as ResizeObserverEntry[]);
|
||||
autofillInlineMenuList["handleResizeObserver"](entries as unknown as ResizeObserverEntry[]);
|
||||
|
||||
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
|
||||
{ command: "updateAutofillInlineMenuListHeight", styles: { height: "300px" }, portKey },
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { AutofillInlineMenuPageElement } from "../shared/autofill-inline-menu-page-element";
|
||||
|
||||
export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
private overlayListContainer: HTMLDivElement;
|
||||
private inlineMenuListContainer: HTMLDivElement;
|
||||
private resizeObserver: ResizeObserver;
|
||||
private eventHandlersMemo: { [key: string]: EventListener } = {};
|
||||
private ciphers: OverlayCipherData[] = [];
|
||||
@@ -22,28 +22,29 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
private cipherListScrollDebounceTimeout: number | NodeJS.Timeout;
|
||||
private currentCipherIndex = 0;
|
||||
private readonly showCiphersPerPage = 6;
|
||||
private readonly overlayListWindowMessageHandlers: AutofillInlineMenuListWindowMessageHandlers = {
|
||||
initAutofillInlineMenuList: ({ message }) => this.initAutofillInlineMenuList(message),
|
||||
checkAutofillInlineMenuListFocused: () => this.checkInlineMenuListFocused(),
|
||||
updateAutofillInlineMenuListCiphers: ({ message }) => this.updateListItems(message.ciphers),
|
||||
focusInlineMenuList: () => this.focusInlineMenuList(),
|
||||
};
|
||||
private readonly inlineMenuListWindowMessageHandlers: AutofillInlineMenuListWindowMessageHandlers =
|
||||
{
|
||||
initAutofillInlineMenuList: ({ message }) => this.initAutofillInlineMenuList(message),
|
||||
checkAutofillInlineMenuListFocused: () => this.checkInlineMenuListFocused(),
|
||||
updateAutofillInlineMenuListCiphers: ({ message }) => this.updateListItems(message.ciphers),
|
||||
focusInlineMenuList: () => this.focusInlineMenuList(),
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.setupOverlayListGlobalListeners();
|
||||
this.setupInlineMenuListGlobalListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the overlay list and updates the list items with the passed ciphers.
|
||||
* If the auth status is not `Unlocked`, the locked overlay is built.
|
||||
* Initializes the inline menu list and updates the list items with the passed ciphers.
|
||||
* If the auth status is not `Unlocked`, the locked inline menu is built.
|
||||
*
|
||||
* @param translations - The translations to use for the overlay list.
|
||||
* @param styleSheetUrl - The URL of the stylesheet to use for the overlay list.
|
||||
* @param theme - The theme to use for the overlay list.
|
||||
* @param translations - The translations to use for the inline menu list.
|
||||
* @param styleSheetUrl - The URL of the stylesheet to use for the inline menu list.
|
||||
* @param theme - The theme to use for the inline menu list.
|
||||
* @param authStatus - The current authentication status.
|
||||
* @param ciphers - The ciphers to display in the overlay list.
|
||||
* @param ciphers - The ciphers to display in the inline menu list.
|
||||
* @param portKey - Background generated key that allows the port to communicate with the background.
|
||||
*/
|
||||
private async initAutofillInlineMenuList({
|
||||
@@ -64,34 +65,34 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
const themeClass = `theme_${theme}`;
|
||||
globalThis.document.documentElement.classList.add(themeClass);
|
||||
|
||||
this.overlayListContainer = globalThis.document.createElement("div");
|
||||
this.overlayListContainer.classList.add("overlay-list-container", themeClass);
|
||||
this.resizeObserver.observe(this.overlayListContainer);
|
||||
this.inlineMenuListContainer = globalThis.document.createElement("div");
|
||||
this.inlineMenuListContainer.classList.add("inline-menu-list-container", themeClass);
|
||||
this.resizeObserver.observe(this.inlineMenuListContainer);
|
||||
|
||||
this.shadowDom.append(linkElement, this.overlayListContainer);
|
||||
this.shadowDom.append(linkElement, this.inlineMenuListContainer);
|
||||
|
||||
if (authStatus === AuthenticationStatus.Unlocked) {
|
||||
this.updateListItems(ciphers);
|
||||
return;
|
||||
}
|
||||
|
||||
this.buildLockedOverlay();
|
||||
this.buildLockedInlineMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the locked overlay, which is displayed when the user is not authenticated.
|
||||
* Facilitates the ability to unlock the extension from the overlay.
|
||||
* Builds the locked inline menu, which is displayed when the user is not authenticated.
|
||||
* Facilitates the ability to unlock the extension from the inline menu.
|
||||
*/
|
||||
private buildLockedOverlay() {
|
||||
const lockedOverlay = globalThis.document.createElement("div");
|
||||
lockedOverlay.id = "locked-overlay-description";
|
||||
lockedOverlay.classList.add("locked-overlay", "overlay-list-message");
|
||||
lockedOverlay.textContent = this.getTranslation("unlockYourAccount");
|
||||
private buildLockedInlineMenu() {
|
||||
const lockedInlineMenu = globalThis.document.createElement("div");
|
||||
lockedInlineMenu.id = "locked-inline-menu-description";
|
||||
lockedInlineMenu.classList.add("locked-inline-menu", "inline-menu-list-message");
|
||||
lockedInlineMenu.textContent = this.getTranslation("unlockYourAccount");
|
||||
|
||||
const unlockButtonElement = globalThis.document.createElement("button");
|
||||
unlockButtonElement.id = "unlock-button";
|
||||
unlockButtonElement.tabIndex = -1;
|
||||
unlockButtonElement.classList.add("unlock-button", "overlay-list-button");
|
||||
unlockButtonElement.classList.add("unlock-button", "inline-menu-list-button");
|
||||
unlockButtonElement.textContent = this.getTranslation("unlockAccount");
|
||||
unlockButtonElement.setAttribute(
|
||||
"aria-label",
|
||||
@@ -100,11 +101,11 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
unlockButtonElement.prepend(buildSvgDomElement(lockIcon));
|
||||
unlockButtonElement.addEventListener(EVENTS.CLICK, this.handleUnlockButtonClick);
|
||||
|
||||
const overlayListButtonContainer = globalThis.document.createElement("div");
|
||||
overlayListButtonContainer.classList.add("overlay-list-button-container");
|
||||
overlayListButtonContainer.appendChild(unlockButtonElement);
|
||||
const inlineMenuListButtonContainer = globalThis.document.createElement("div");
|
||||
inlineMenuListButtonContainer.classList.add("inline-menu-list-button-container");
|
||||
inlineMenuListButtonContainer.appendChild(unlockButtonElement);
|
||||
|
||||
this.overlayListContainer.append(lockedOverlay, overlayListButtonContainer);
|
||||
this.inlineMenuListContainer.append(lockedInlineMenu, inlineMenuListButtonContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,45 +118,45 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
|
||||
/**
|
||||
* Updates the list items with the passed ciphers.
|
||||
* If no ciphers are passed, the no results overlay is built.
|
||||
* If no ciphers are passed, the no results inline menu is built.
|
||||
*
|
||||
* @param ciphers - The ciphers to display in the overlay list.
|
||||
* @param ciphers - The ciphers to display in the inline menu list.
|
||||
*/
|
||||
private updateListItems(ciphers: OverlayCipherData[]) {
|
||||
this.ciphers = ciphers;
|
||||
this.currentCipherIndex = 0;
|
||||
if (this.overlayListContainer) {
|
||||
this.overlayListContainer.innerHTML = "";
|
||||
if (this.inlineMenuListContainer) {
|
||||
this.inlineMenuListContainer.innerHTML = "";
|
||||
}
|
||||
|
||||
if (!ciphers?.length) {
|
||||
this.buildNoResultsOverlayList();
|
||||
this.buildNoResultsInlineMenuList();
|
||||
return;
|
||||
}
|
||||
|
||||
this.ciphersList = globalThis.document.createElement("ul");
|
||||
this.ciphersList.classList.add("overlay-actions-list");
|
||||
this.ciphersList.classList.add("inline-menu-list-actions");
|
||||
this.ciphersList.setAttribute("role", "list");
|
||||
globalThis.addEventListener(EVENTS.SCROLL, this.handleCiphersListScrollEvent);
|
||||
|
||||
this.loadPageOfCiphers();
|
||||
|
||||
this.overlayListContainer.appendChild(this.ciphersList);
|
||||
this.inlineMenuListContainer.appendChild(this.ciphersList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overlay view that is presented when no ciphers are found for a given page.
|
||||
* Facilitates the ability to add a new vault item from the overlay.
|
||||
* Inline menu view that is presented when no ciphers are found for a given page.
|
||||
* Facilitates the ability to add a new vault item from the inline menu.
|
||||
*/
|
||||
private buildNoResultsOverlayList() {
|
||||
private buildNoResultsInlineMenuList() {
|
||||
const noItemsMessage = globalThis.document.createElement("div");
|
||||
noItemsMessage.classList.add("no-items", "overlay-list-message");
|
||||
noItemsMessage.classList.add("no-items", "inline-menu-list-message");
|
||||
noItemsMessage.textContent = this.getTranslation("noItemsToShow");
|
||||
|
||||
const newItemButton = globalThis.document.createElement("button");
|
||||
newItemButton.tabIndex = -1;
|
||||
newItemButton.id = "new-item-button";
|
||||
newItemButton.classList.add("add-new-item-button", "overlay-list-button");
|
||||
newItemButton.classList.add("add-new-item-button", "inline-menu-list-button");
|
||||
newItemButton.textContent = this.getTranslation("newItem");
|
||||
newItemButton.setAttribute(
|
||||
"aria-label",
|
||||
@@ -164,11 +165,11 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
newItemButton.prepend(buildSvgDomElement(plusIcon));
|
||||
newItemButton.addEventListener(EVENTS.CLICK, this.handeNewItemButtonClick);
|
||||
|
||||
const overlayListButtonContainer = globalThis.document.createElement("div");
|
||||
overlayListButtonContainer.classList.add("overlay-list-button-container");
|
||||
overlayListButtonContainer.appendChild(newItemButton);
|
||||
const inlineMenuListButtonContainer = globalThis.document.createElement("div");
|
||||
inlineMenuListButtonContainer.classList.add("inline-menu-list-button-container");
|
||||
inlineMenuListButtonContainer.appendChild(newItemButton);
|
||||
|
||||
this.overlayListContainer.append(noItemsMessage, overlayListButtonContainer);
|
||||
this.inlineMenuListContainer.append(noItemsMessage, inlineMenuListButtonContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +181,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads a page of ciphers into the overlay list container.
|
||||
* Loads a page of ciphers into the inline menu list container.
|
||||
*/
|
||||
private loadPageOfCiphers() {
|
||||
const lastIndex = Math.min(
|
||||
@@ -188,7 +189,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
this.ciphers.length,
|
||||
);
|
||||
for (let cipherIndex = this.currentCipherIndex; cipherIndex < lastIndex; cipherIndex++) {
|
||||
this.ciphersList.appendChild(this.buildOverlayActionsListItem(this.ciphers[cipherIndex]));
|
||||
this.ciphersList.appendChild(this.buildInlineMenuListActionsItem(this.ciphers[cipherIndex]));
|
||||
this.currentCipherIndex++;
|
||||
}
|
||||
|
||||
@@ -230,7 +231,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
*
|
||||
* @param cipher - The cipher to build the list item for.
|
||||
*/
|
||||
private buildOverlayActionsListItem(cipher: OverlayCipherData) {
|
||||
private buildInlineMenuListActionsItem(cipher: OverlayCipherData) {
|
||||
const fillCipherElement = this.buildFillCipherElement(cipher);
|
||||
const viewCipherElement = this.buildViewCipherElement(cipher);
|
||||
|
||||
@@ -238,12 +239,12 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
cipherContainerElement.classList.add("cipher-container");
|
||||
cipherContainerElement.append(fillCipherElement, viewCipherElement);
|
||||
|
||||
const overlayActionsListItem = globalThis.document.createElement("li");
|
||||
overlayActionsListItem.setAttribute("role", "listitem");
|
||||
overlayActionsListItem.classList.add("overlay-actions-list-item");
|
||||
overlayActionsListItem.appendChild(cipherContainerElement);
|
||||
const inlineMenuListActionsItem = globalThis.document.createElement("li");
|
||||
inlineMenuListActionsItem.setAttribute("role", "listitem");
|
||||
inlineMenuListActionsItem.classList.add("inline-menu-list-actions-item");
|
||||
inlineMenuListActionsItem.appendChild(cipherContainerElement);
|
||||
|
||||
return overlayActionsListItem;
|
||||
return inlineMenuListActionsItem;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,7 +307,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const currentListItem = event.target.closest(".overlay-actions-list-item") as HTMLElement;
|
||||
const currentListItem = event.target.closest(".inline-menu-list-actions-item") as HTMLElement;
|
||||
if (event.code === "ArrowDown") {
|
||||
this.focusNextListItem(currentListItem);
|
||||
return;
|
||||
@@ -369,7 +370,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const currentListItem = event.target.closest(".overlay-actions-list-item") as HTMLElement;
|
||||
const currentListItem = event.target.closest(".inline-menu-list-actions-item") as HTMLElement;
|
||||
const cipherContainer = currentListItem.querySelector(".cipher-container") as HTMLElement;
|
||||
cipherContainer?.classList.remove("remove-outline");
|
||||
if (event.code === "ArrowDown") {
|
||||
@@ -484,7 +485,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the overlay list iframe is currently focused.
|
||||
* Validates whether the inline menu list iframe is currently focused.
|
||||
* If not focused, will check if the button element is focused.
|
||||
*/
|
||||
private checkInlineMenuListFocused() {
|
||||
@@ -496,15 +497,15 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the overlay list iframe. The element that receives focus is
|
||||
* Focuses the inline menu list iframe. The element that receives focus is
|
||||
* determined by the presence of the unlock button, new item button, or
|
||||
* the first cipher button.
|
||||
*/
|
||||
private focusInlineMenuList() {
|
||||
this.overlayListContainer.setAttribute("role", "dialog");
|
||||
this.overlayListContainer.setAttribute("aria-modal", "true");
|
||||
this.inlineMenuListContainer.setAttribute("role", "dialog");
|
||||
this.inlineMenuListContainer.setAttribute("aria-modal", "true");
|
||||
|
||||
const unlockButtonElement = this.overlayListContainer.querySelector(
|
||||
const unlockButtonElement = this.inlineMenuListContainer.querySelector(
|
||||
"#unlock-button",
|
||||
) as HTMLElement;
|
||||
if (unlockButtonElement) {
|
||||
@@ -512,7 +513,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const newItemButtonElement = this.overlayListContainer.querySelector(
|
||||
const newItemButtonElement = this.inlineMenuListContainer.querySelector(
|
||||
"#new-item-button",
|
||||
) as HTMLElement;
|
||||
if (newItemButtonElement) {
|
||||
@@ -520,31 +521,31 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstCipherElement = this.overlayListContainer.querySelector(
|
||||
const firstCipherElement = this.inlineMenuListContainer.querySelector(
|
||||
".fill-cipher-button",
|
||||
) as HTMLElement;
|
||||
firstCipherElement?.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the global listeners for the overlay list iframe.
|
||||
* Sets up the global listeners for the inline menu list iframe.
|
||||
*/
|
||||
private setupOverlayListGlobalListeners() {
|
||||
this.setupGlobalListeners(this.overlayListWindowMessageHandlers);
|
||||
private setupInlineMenuListGlobalListeners() {
|
||||
this.setupGlobalListeners(this.inlineMenuListWindowMessageHandlers);
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.handleResizeObserver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the resize observer event. Facilitates updating the height of the
|
||||
* overlay list iframe when the height of the list changes.
|
||||
* inline menu list iframe when the height of the list changes.
|
||||
*
|
||||
* @param entries - The resize observer entries.
|
||||
*/
|
||||
private handleResizeObserver = (entries: ResizeObserverEntry[]) => {
|
||||
for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
|
||||
const entry = entries[entryIndex];
|
||||
if (entry.target !== this.overlayListContainer) {
|
||||
if (entry.target !== this.inlineMenuListContainer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -568,7 +569,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
};
|
||||
|
||||
/**
|
||||
* Focuses the next list item in the overlay list. If the current list item is the last
|
||||
* Focuses the next list item in the inline menu list. If the current list item is the last
|
||||
* item in the list, the first item is focused.
|
||||
*
|
||||
* @param currentListItem - The current list item.
|
||||
@@ -587,7 +588,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the previous list item in the overlay list. If the current list item is the first
|
||||
* Focuses the previous list item in the inline menu list. If the current list item is the first
|
||||
* item in the list, the last item is focused.
|
||||
*
|
||||
* @param currentListItem - The current list item.
|
||||
|
||||
@@ -22,7 +22,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-list-message {
|
||||
.inline-menu-list-message {
|
||||
font-family: $font-family-sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
@@ -39,7 +39,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-list-button-container {
|
||||
.inline-menu-list-button-container {
|
||||
width: 100%;
|
||||
padding: 0.2rem;
|
||||
background: transparent;
|
||||
@@ -58,7 +58,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-list-button {
|
||||
.inline-menu-list-button {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: flex-start;
|
||||
@@ -116,12 +116,12 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-actions-list {
|
||||
.inline-menu-list-actions {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.overlay-actions-list-item {
|
||||
.inline-menu-list-actions-item {
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
list-style: none;
|
||||
padding: 0.2rem;
|
||||
|
||||
@@ -195,7 +195,7 @@ function createAutofillOverlayCipherDataMock(index: number, customFields = {}):
|
||||
};
|
||||
}
|
||||
|
||||
function createInitAutofillOverlayListMessageMock(
|
||||
function createInitAutofillInlineMenuListMessageMock(
|
||||
customFields = {},
|
||||
): InitAutofillInlineMenuListMessage {
|
||||
return {
|
||||
@@ -298,7 +298,7 @@ export {
|
||||
createGenerateFillScriptOptionsMock,
|
||||
createAutofillScriptMock,
|
||||
createInitAutofillInlineMenuButtonMessageMock,
|
||||
createInitAutofillOverlayListMessageMock,
|
||||
createInitAutofillInlineMenuListMessageMock,
|
||||
createFocusedFieldDataMock,
|
||||
createPortSpyMock,
|
||||
createMutationRecordMock,
|
||||
|
||||
Reference in New Issue
Block a user