mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-18 15:47:57 +01:00
PM-5550 Implement on-page autofil for single line TOTP (#12058)
* PM-5550 initial commit -Initial render -Edit tests -Clean up styling -New method to validate totpfields * add refresh overlay * localize and clean up * - Clean up code - Remove unnecessary data from buildtotpelement - Add feature flag - Add aria labels to buildtotpelement - Add tests and update relevant snapshots * Add and translate aria labels * add aria labels * implement feature flag * address totp tests * clean up totpfield function * fix styling and tests, update snapshots * Update apps/browser/src/_locales/en/messages.json Formatting suggestion Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com> * Update apps/browser/src/_locales/en/messages.json Formatting suggestion Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com> * remove group tag * update snapshots * adress feedback --------- Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com>
This commit is contained in:
parent
649590ad62
commit
6383048197
@ -192,6 +192,13 @@
|
||||
"autoFillIdentity": {
|
||||
"message": "Autofill identity"
|
||||
},
|
||||
"fillVerificationCode": {
|
||||
"message": "Fill verification code"
|
||||
},
|
||||
"fillVerificationCodeAria": {
|
||||
"message": "Fill Verification Code",
|
||||
"description": "Aria label for the heading displayed the inline menu for totp code autofill"
|
||||
},
|
||||
"generatePasswordCopied": {
|
||||
"message": "Generate password (copied)"
|
||||
},
|
||||
@ -3580,6 +3587,14 @@
|
||||
"message": "Unlock your account, opens in a new window",
|
||||
"description": "Screen reader text (aria-label) for unlock account button in overlay"
|
||||
},
|
||||
"totpCodeAria": {
|
||||
"message": "Time-based One-Time Password Verification Code",
|
||||
"description": "Aria label for the totp code displayed in the inline menu for autofill"
|
||||
},
|
||||
"totpSecondsSpanAria": {
|
||||
"message": "Time remaining before current TOTP expires",
|
||||
"description": "Aria label for the totp seconds displayed in the inline menu for autofill"
|
||||
},
|
||||
"fillCredentialsFor": {
|
||||
"message": "Fill credentials for",
|
||||
"description": "Screen reader text for when overlay item is in focused"
|
||||
|
@ -160,6 +160,9 @@ export type InlineMenuCipherData = {
|
||||
icon: WebsiteIconData;
|
||||
accountCreationFieldType?: string;
|
||||
login?: {
|
||||
totp?: string;
|
||||
totpField?: boolean;
|
||||
totpCodeTimeInterval?: number;
|
||||
username: string;
|
||||
passkey: {
|
||||
rpName: string;
|
||||
@ -262,6 +265,7 @@ export type InlineMenuListPortMessageHandlers = {
|
||||
updateAutofillInlineMenuListHeight: ({ message, port }: PortOnMessageHandlerParams) => void;
|
||||
refreshGeneratedPassword: () => Promise<void>;
|
||||
fillGeneratedPassword: ({ port }: PortConnectionParam) => Promise<void>;
|
||||
refreshOverlayCiphers: () => Promise<void>;
|
||||
};
|
||||
|
||||
export interface OverlayBackground {
|
||||
|
@ -928,6 +928,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: "username-1",
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
name: "name-1",
|
||||
reprompt: loginCipher1.reprompt,
|
||||
@ -1065,6 +1066,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: loginCipher1.login.username,
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
name: loginCipher1.name,
|
||||
reprompt: loginCipher1.reprompt,
|
||||
@ -1189,6 +1191,7 @@ describe("OverlayBackground", () => {
|
||||
rpName: passkeyCipher.login.fido2Credentials[0].rpName,
|
||||
userName: passkeyCipher.login.fido2Credentials[0].userName,
|
||||
},
|
||||
totpField: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1207,6 +1210,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: passkeyCipher.login.username,
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1225,6 +1229,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: loginCipher1.login.username,
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -1272,6 +1277,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: passkeyCipher.login.username,
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1290,6 +1296,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: loginCipher1.login.username,
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -1337,6 +1344,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: passkeyCipher.login.username,
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1355,6 +1363,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: loginCipher1.login.username,
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -1400,6 +1409,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: loginCipher1.login.username,
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1418,6 +1428,7 @@ describe("OverlayBackground", () => {
|
||||
login: {
|
||||
username: loginCipher2.login.username,
|
||||
passkey: null,
|
||||
totpField: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -204,6 +204,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
updateAutofillInlineMenuListHeight: ({ message }) => this.updateInlineMenuListHeight(message),
|
||||
refreshGeneratedPassword: () => this.updateGeneratedPassword(true),
|
||||
fillGeneratedPassword: ({ port }) => this.fillGeneratedPassword(port),
|
||||
refreshOverlayCiphers: () => this.updateOverlayCiphers(false),
|
||||
};
|
||||
|
||||
constructor(
|
||||
@ -464,7 +465,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
this.showPasskeysLabelsWithinInlineMenu = false;
|
||||
|
||||
if (this.shouldShowInlineMenuAccountCreation()) {
|
||||
inlineMenuCipherData = this.buildInlineMenuAccountCreationCiphers(
|
||||
inlineMenuCipherData = await this.buildInlineMenuAccountCreationCiphers(
|
||||
inlineMenuCiphersArray,
|
||||
true,
|
||||
);
|
||||
@ -485,7 +486,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* @param inlineMenuCiphersArray - Array of inline menu ciphers
|
||||
* @param showFavicons - Identifies whether favicons should be shown
|
||||
*/
|
||||
private buildInlineMenuAccountCreationCiphers(
|
||||
private async buildInlineMenuAccountCreationCiphers(
|
||||
inlineMenuCiphersArray: [string, CipherView][],
|
||||
showFavicons: boolean,
|
||||
) {
|
||||
@ -497,7 +498,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
|
||||
if (cipher.type === CipherType.Login) {
|
||||
accountCreationLoginCiphers.push(
|
||||
this.buildCipherData({
|
||||
await this.buildCipherData({
|
||||
inlineMenuCipherId,
|
||||
cipher,
|
||||
showFavicons,
|
||||
@ -517,7 +518,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
}
|
||||
|
||||
inlineMenuCipherData.push(
|
||||
this.buildCipherData({
|
||||
await this.buildCipherData({
|
||||
inlineMenuCipherId,
|
||||
cipher,
|
||||
showFavicons,
|
||||
@ -561,13 +562,13 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
|
||||
if (!passkeysEnabled || !(await this.showCipherAsPasskey(cipher, domainExclusionsSet))) {
|
||||
inlineMenuCipherData.push(
|
||||
this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }),
|
||||
await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
passkeyCipherData.push(
|
||||
this.buildCipherData({
|
||||
await this.buildCipherData({
|
||||
inlineMenuCipherId,
|
||||
cipher,
|
||||
showFavicons,
|
||||
@ -577,7 +578,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
|
||||
if (cipher.login?.password && cipher.login.username) {
|
||||
inlineMenuCipherData.push(
|
||||
this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }),
|
||||
await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -620,6 +621,23 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
return this.inlineMenuFido2Credentials.has(credentialId);
|
||||
}
|
||||
|
||||
private isTotpFieldForCurrentField(): boolean {
|
||||
if (!this.focusedFieldData) {
|
||||
return false;
|
||||
}
|
||||
const { tabId, frameId } = this.focusedFieldData;
|
||||
const pageDetailsMap = this.pageDetailsForTab[tabId];
|
||||
if (!pageDetailsMap || !pageDetailsMap.has(frameId)) {
|
||||
return false;
|
||||
}
|
||||
const pageDetail = pageDetailsMap.get(frameId);
|
||||
return (
|
||||
pageDetail?.details?.fields?.every((field) =>
|
||||
this.inlineMenuFieldQualificationService.isTotpField(field),
|
||||
) || false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the cipher data for the inline menu list.
|
||||
*
|
||||
@ -630,14 +648,14 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* @param hasPasskey - Identifies whether the cipher has a FIDO2 credential
|
||||
* @param identityData - Pre-created identity data
|
||||
*/
|
||||
private buildCipherData({
|
||||
private async buildCipherData({
|
||||
inlineMenuCipherId,
|
||||
cipher,
|
||||
showFavicons,
|
||||
showInlineMenuAccountCreation,
|
||||
hasPasskey,
|
||||
identityData,
|
||||
}: BuildCipherDataParams): InlineMenuCipherData {
|
||||
}: BuildCipherDataParams): Promise<InlineMenuCipherData> {
|
||||
const inlineMenuData: InlineMenuCipherData = {
|
||||
id: inlineMenuCipherId,
|
||||
name: cipher.name,
|
||||
@ -649,8 +667,13 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
};
|
||||
|
||||
if (cipher.type === CipherType.Login) {
|
||||
const totpCode = await this.totpService.getCode(cipher.login?.totp);
|
||||
const totpCodeTimeInterval = this.totpService.getTimeInterval(cipher.login?.totp);
|
||||
inlineMenuData.login = {
|
||||
username: cipher.login.username,
|
||||
totp: totpCode,
|
||||
totpField: this.isTotpFieldForCurrentField(),
|
||||
totpCodeTimeInterval: totpCodeTimeInterval,
|
||||
passkey: hasPasskey
|
||||
? {
|
||||
rpName: cipher.login.fido2Credentials[0].rpName,
|
||||
@ -1980,35 +2003,39 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
private getInlineMenuTranslations() {
|
||||
if (!this.inlineMenuPageTranslations) {
|
||||
const translationKeys = [
|
||||
"addNewCardItemAria",
|
||||
"addNewIdentityItemAria",
|
||||
"addNewLoginItemAria",
|
||||
"addNewVaultItem",
|
||||
"authenticating",
|
||||
"cardNumberEndsWith",
|
||||
"fillCredentialsFor",
|
||||
"fillGeneratedPassword",
|
||||
"fillVerificationCode",
|
||||
"fillVerificationCodeAria",
|
||||
"generatedPassword",
|
||||
"lowercaseAriaLabel",
|
||||
"logInWithPasskeyAriaLabel",
|
||||
"newCard",
|
||||
"newIdentity",
|
||||
"newItem",
|
||||
"newLogin",
|
||||
"noItemsToShow",
|
||||
"opensInANewWindow",
|
||||
"passkeys",
|
||||
"passwordRegenerated",
|
||||
"passwords",
|
||||
"regeneratePassword",
|
||||
"saveLoginToBitwarden",
|
||||
"toggleBitwardenVaultOverlay",
|
||||
"unlockYourAccountToViewAutofillSuggestions",
|
||||
"totpCodeAria",
|
||||
"totpSecondsSpanAria",
|
||||
"unlockAccount",
|
||||
"unlockAccountAria",
|
||||
"fillCredentialsFor",
|
||||
"unlockYourAccountToViewAutofillSuggestions",
|
||||
"uppercaseAriaLabel",
|
||||
"username",
|
||||
"view",
|
||||
"noItemsToShow",
|
||||
"newItem",
|
||||
"addNewVaultItem",
|
||||
"newLogin",
|
||||
"addNewLoginItemAria",
|
||||
"newCard",
|
||||
"addNewCardItemAria",
|
||||
"newIdentity",
|
||||
"addNewIdentityItemAria",
|
||||
"cardNumberEndsWith",
|
||||
"passkeys",
|
||||
"passwords",
|
||||
"logInWithPasskeyAriaLabel",
|
||||
"authenticating",
|
||||
"fillGeneratedPassword",
|
||||
"regeneratePassword",
|
||||
"passwordRegenerated",
|
||||
"saveLoginToBitwarden",
|
||||
"lowercaseAriaLabel",
|
||||
"uppercaseAriaLabel",
|
||||
"generatedPassword",
|
||||
...Object.values(specialCharacterToKeyMap),
|
||||
];
|
||||
this.inlineMenuPageTranslations = translationKeys.reduce(
|
||||
|
@ -681,10 +681,121 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f
|
||||
class="cipher-container"
|
||||
>
|
||||
<button
|
||||
aria-description="username: username5"
|
||||
aria-label="fillCredentialsFor website login 5"
|
||||
class="fill-cipher-button inline-menu-list-action"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="cipher-icon"
|
||||
>
|
||||
<div
|
||||
style="position: relative;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 29 29"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
|
||||
|
||||
<circle
|
||||
class="circle-color"
|
||||
cx="14.5"
|
||||
cy="14.5"
|
||||
fill="none"
|
||||
r="12.5"
|
||||
stroke-dasharray="78.5"
|
||||
stroke-dashoffset="78.5"
|
||||
stroke-width="3"
|
||||
style="stroke-dashoffset: NaN;"
|
||||
transform="rotate(-90 14.5 14.5)"
|
||||
/>
|
||||
|
||||
|
||||
<circle
|
||||
class="circle-color"
|
||||
cx="14.5"
|
||||
cy="14.5"
|
||||
fill="none"
|
||||
r="14"
|
||||
stroke-width="1"
|
||||
/>
|
||||
|
||||
|
||||
</svg>
|
||||
<span
|
||||
aria-label=""
|
||||
bittypography="helper"
|
||||
class="totp-sec-span"
|
||||
>
|
||||
NaN
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<div
|
||||
class="cipher-details"
|
||||
>
|
||||
<span
|
||||
aria-label=""
|
||||
class="cipher-name"
|
||||
/>
|
||||
<span
|
||||
aria-label=""
|
||||
class="cipher-subtitle"
|
||||
data-testid="totp-code"
|
||||
>
|
||||
123 456
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="view website login 5, opensInANewWindow"
|
||||
class="view-cipher-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<path
|
||||
d="M0 .113h20v19.773H0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
class="cipher-container"
|
||||
>
|
||||
<button
|
||||
aria-description="username: username6"
|
||||
aria-label="fillCredentialsFor website login 6"
|
||||
class="fill-cipher-button inline-menu-list-action"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -696,18 +807,420 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f
|
||||
>
|
||||
<span
|
||||
class="cipher-name"
|
||||
title="website login 5"
|
||||
title="website login 6"
|
||||
>
|
||||
website login 5
|
||||
website login 6
|
||||
</span>
|
||||
<span
|
||||
class="cipher-subtitle"
|
||||
title="username5"
|
||||
title="username6"
|
||||
>
|
||||
username5
|
||||
username6
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="view website login 6, opensInANewWindow"
|
||||
class="view-cipher-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<path
|
||||
d="M0 .113h20v19.773H0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a totp field 1`] = `
|
||||
<div
|
||||
class="inline-menu-list-container theme_light"
|
||||
>
|
||||
<ul
|
||||
class="inline-menu-list-actions"
|
||||
role="list"
|
||||
>
|
||||
<li
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
class="cipher-container"
|
||||
>
|
||||
<button
|
||||
aria-description="username: username1"
|
||||
aria-label="fillCredentialsFor website login 1"
|
||||
class="fill-cipher-button inline-menu-list-action"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="cipher-icon"
|
||||
style="background-image: url(https://jest-testing-website.com/image.png);"
|
||||
/>
|
||||
<span
|
||||
class="cipher-details"
|
||||
>
|
||||
<span
|
||||
class="cipher-name"
|
||||
title="website login 1"
|
||||
>
|
||||
website login 1
|
||||
</span>
|
||||
<span
|
||||
class="cipher-subtitle"
|
||||
title="username1"
|
||||
>
|
||||
username1
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="view website login 1, opensInANewWindow"
|
||||
class="view-cipher-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<path
|
||||
d="M0 .113h20v19.773H0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
class="cipher-container"
|
||||
>
|
||||
<button
|
||||
aria-description="username: username2"
|
||||
aria-label="fillCredentialsFor website login 2"
|
||||
class="fill-cipher-button inline-menu-list-action"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="cipher-icon bwi bw-icon"
|
||||
/>
|
||||
<span
|
||||
class="cipher-details"
|
||||
>
|
||||
<span
|
||||
class="cipher-name"
|
||||
title="website login 2"
|
||||
>
|
||||
website login 2
|
||||
</span>
|
||||
<span
|
||||
class="cipher-subtitle"
|
||||
title="username2"
|
||||
>
|
||||
username2
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="view website login 2, opensInANewWindow"
|
||||
class="view-cipher-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<path
|
||||
d="M0 .113h20v19.773H0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
class="cipher-container"
|
||||
>
|
||||
<button
|
||||
aria-label="fillCredentialsFor "
|
||||
class="fill-cipher-button inline-menu-list-action"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="cipher-icon bwi bw-icon"
|
||||
/>
|
||||
<span
|
||||
class="cipher-details"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-label="view , opensInANewWindow"
|
||||
class="view-cipher-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<path
|
||||
d="M0 .113h20v19.773H0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
class="cipher-container"
|
||||
>
|
||||
<button
|
||||
aria-description="username: username4"
|
||||
aria-label="fillCredentialsFor website login 4"
|
||||
class="fill-cipher-button inline-menu-list-action"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="cipher-icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="25"
|
||||
viewBox="0 0 24 25"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M18.026 17.842c-1.418 1.739-3.517 2.84-5.86 2.84a7.364 7.364 0 0 1-3.431-.848l.062-.15.062-.151.063-.157c.081-.203.17-.426.275-.646.133-.28.275-.522.426-.68.026-.028.101-.075.275-.115.165-.037.376-.059.629-.073.138-.008.288-.014.447-.02.399-.016.847-.034 1.266-.092.314-.044.566-.131.755-.271a.884.884 0 0 0 .352-.555c.037-.2.008-.392-.03-.543-.035-.135-.084-.264-.12-.355l-.01-.03a4.26 4.26 0 0 0-.145-.33c-.126-.264-.237-.497-.288-1.085-.03-.344.09-.73.251-1.138l.089-.22c.05-.123.1-.247.14-.355.064-.171.129-.375.129-.566a1.51 1.51 0 0 0-.134-.569 2.573 2.573 0 0 0-.319-.547c-.246-.323-.635-.669-1.093-.669-.44 0-1.006.169-1.487.368-.246.102-.48.216-.68.33-.192.111-.372.235-.492.359-.93.96-1.48 1.239-1.81 1.258-.277.017-.478-.15-.736-.525a9.738 9.738 0 0 1-.19-.29l-.006-.01a11.568 11.568 0 0 0-.198-.305 2.76 2.76 0 0 0-.521-.6 1.39 1.39 0 0 0-1.088-.314 8.302 8.302 0 0 1 1.987-3.936c.055.342.146.626.272.856.23.42.561.64.926.716.406.086.857-.061 1.26-.216.125-.047.248-.097.372-.147.309-.125.618-.25.947-.341.26-.072.581-.057.959.012.264.049.529.118.8.19l.36.091c.379.094.782.178 1.135.148.374-.032.733-.197.934-.623a.874.874 0 0 0 .024-.752c-.087-.197-.24-.355-.35-.47-.26-.267-.412-.427-.412-.685 0-.125.037-.2.09-.263a.982.982 0 0 1 .303-.211c.059-.03.119-.058.183-.089l.036-.016a3.79 3.79 0 0 0 .236-.118c.047-.026.098-.056.148-.093 1.936.747 3.51 2.287 4.368 4.249a7.739 7.739 0 0 0-.031-.004c-.38-.047-.738-.056-1.063.061-.34.123-.603.368-.817.74-.122.211-.284.43-.463.67l-.095.129c-.207.281-.431.595-.58.92-.15.326-.245.705-.142 1.103.104.397.387.738.837 1.036.099.065.225.112.314.145l.02.008c.108.04.195.074.268.117.07.042.106.08.124.114.017.03.037.087.022.206-.047.376-.069.73-.052 1.034.017.292.071.59.218.809.118.174.12.421.108.786v.01a2.46 2.46 0 0 0 .021.518.809.809 0 0 0 .15.35Zm1.357.059a9.654 9.654 0 0 0 1.62-5.386c0-5.155-3.957-9.334-8.836-9.334-4.88 0-8.836 4.179-8.836 9.334 0 3.495 1.82 6.543 4.513 8.142v.093h.161a8.426 8.426 0 0 0 4.162 1.098c2.953 0 5.568-1.53 7.172-3.882a.569.569 0 0 0 .048-.062l-.004-.003ZM8.152 19.495a43.345 43.345 0 0 1 .098-.238l.057-.142c.082-.205.182-.455.297-.698.143-.301.323-.624.552-.864.163-.172.392-.254.602-.302.219-.05.473-.073.732-.088.162-.01.328-.016.495-.023.386-.015.782-.03 1.168-.084.255-.036.392-.099.461-.15.06-.045.076-.084.083-.12a.534.534 0 0 0-.02-.223 2.552 2.552 0 0 0-.095-.278l-.01-.027a3.128 3.128 0 0 0-.104-.232c-.134-.282-.31-.65-.374-1.381-.046-.533.138-1.063.3-1.472.035-.09.069-.172.1-.249.046-.11.086-.21.123-.31.062-.169.083-.264.083-.312a.812.812 0 0 0-.076-.283 1.867 1.867 0 0 0-.23-.394c-.21-.274-.428-.408-.577-.408-.315 0-.788.13-1.246.32a5.292 5.292 0 0 0-.603.293 1.727 1.727 0 0 0-.347.244c-.936.968-1.641 1.421-2.235 1.457-.646.04-1.036-.413-1.31-.813-.07-.103-.139-.21-.203-.311l-.005-.007c-.064-.101-.125-.197-.188-.29a2.098 2.098 0 0 0-.387-.453.748.748 0 0 0-.436-.18c-.1-.006-.22.005-.365.046a8.707 8.707 0 0 0-.056.992c0 2.957 1.488 5.547 3.716 6.98Zm10.362-2.316.003-.192.002-.046c.01-.305.026-.786-.232-1.169-.036-.054-.082-.189-.096-.444-.014-.243.003-.55.047-.9a1.051 1.051 0 0 0-.105-.649.987.987 0 0 0-.374-.374 2.285 2.285 0 0 0-.367-.166h-.003a1.243 1.243 0 0 1-.205-.088c-.369-.244-.505-.46-.549-.629-.044-.168-.015-.364.099-.61.115-.25.297-.511.507-.796l.089-.12c.178-.239.368-.495.512-.745.152-.263.302-.382.466-.441.18-.065.416-.073.77-.03.142.018.275.04.397.063.274.837.423 1.736.423 2.671a8.45 8.45 0 0 1-1.384 4.665Zm-4.632-12.63a7.362 7.362 0 0 0-1.715-.201c-1.89 0-3.621.716-4.965 1.905.025.54.12.887.24 1.105.13.238.295.34.482.38.2.042.484-.027.905-.188l.328-.13c.32-.13.681-.275 1.048-.377.398-.111.833-.075 1.24 0 .289.053.59.132.871.205l.326.084c.383.094.694.151.932.13.216-.017.326-.092.395-.237.039-.083.027-.114.014-.144-.027-.062-.088-.136-.212-.264l-.043-.044c-.218-.222-.567-.578-.567-1.142 0-.305.101-.547.262-.734.137-.159.308-.267.46-.347Z"
|
||||
fill="#777"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="cipher-details"
|
||||
>
|
||||
<span
|
||||
class="cipher-name"
|
||||
title="website login 4"
|
||||
>
|
||||
website login 4
|
||||
</span>
|
||||
<span
|
||||
class="cipher-subtitle"
|
||||
title="username4"
|
||||
>
|
||||
username4
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="view website login 4, opensInANewWindow"
|
||||
class="view-cipher-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#a)"
|
||||
>
|
||||
<path
|
||||
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
|
||||
fill="#175DDC"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="a"
|
||||
>
|
||||
<path
|
||||
d="M0 .113h20v19.773H0z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="inline-menu-list-actions-item"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
class="cipher-container"
|
||||
>
|
||||
<button
|
||||
aria-label="fillCredentialsFor website login 5"
|
||||
class="fill-cipher-button inline-menu-list-action"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="cipher-icon"
|
||||
>
|
||||
<div
|
||||
style="position: relative;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 29 29"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
|
||||
|
||||
<circle
|
||||
class="circle-color"
|
||||
cx="14.5"
|
||||
cy="14.5"
|
||||
fill="none"
|
||||
r="12.5"
|
||||
stroke-dasharray="78.5"
|
||||
stroke-dashoffset="78.5"
|
||||
stroke-width="3"
|
||||
style="stroke-dashoffset: NaN;"
|
||||
transform="rotate(-90 14.5 14.5)"
|
||||
/>
|
||||
|
||||
|
||||
<circle
|
||||
class="circle-color"
|
||||
cx="14.5"
|
||||
cy="14.5"
|
||||
fill="none"
|
||||
r="14"
|
||||
stroke-width="1"
|
||||
/>
|
||||
|
||||
|
||||
</svg>
|
||||
<span
|
||||
aria-label=""
|
||||
bittypography="helper"
|
||||
class="totp-sec-span"
|
||||
>
|
||||
NaN
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<div
|
||||
class="cipher-details"
|
||||
>
|
||||
<span
|
||||
aria-label=""
|
||||
class="cipher-name"
|
||||
/>
|
||||
<span
|
||||
aria-label=""
|
||||
class="cipher-subtitle"
|
||||
data-testid="totp-code"
|
||||
>
|
||||
123 456
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="view website login 5, opensInANewWindow"
|
||||
class="view-cipher-button"
|
||||
@ -1112,7 +1625,6 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f
|
||||
class="cipher-container"
|
||||
>
|
||||
<button
|
||||
aria-description="username: username5"
|
||||
aria-label="fillCredentialsFor website login 5"
|
||||
class="fill-cipher-button inline-menu-list-action"
|
||||
tabindex="-1"
|
||||
@ -1120,24 +1632,66 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="cipher-icon"
|
||||
style="background-image: url(https://jest-testing-website.com/image.png);"
|
||||
/>
|
||||
<span
|
||||
>
|
||||
<div
|
||||
style="position: relative;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 29 29"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
|
||||
|
||||
<circle
|
||||
class="circle-color"
|
||||
cx="14.5"
|
||||
cy="14.5"
|
||||
fill="none"
|
||||
r="12.5"
|
||||
stroke-dasharray="78.5"
|
||||
stroke-dashoffset="78.5"
|
||||
stroke-width="3"
|
||||
style="stroke-dashoffset: NaN;"
|
||||
transform="rotate(-90 14.5 14.5)"
|
||||
/>
|
||||
|
||||
|
||||
<circle
|
||||
class="circle-color"
|
||||
cx="14.5"
|
||||
cy="14.5"
|
||||
fill="none"
|
||||
r="14"
|
||||
stroke-width="1"
|
||||
/>
|
||||
|
||||
|
||||
</svg>
|
||||
<span
|
||||
aria-label=""
|
||||
bittypography="helper"
|
||||
class="totp-sec-span"
|
||||
>
|
||||
NaN
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<div
|
||||
class="cipher-details"
|
||||
>
|
||||
<span
|
||||
aria-label=""
|
||||
class="cipher-name"
|
||||
title="website login 5"
|
||||
>
|
||||
website login 5
|
||||
</span>
|
||||
/>
|
||||
<span
|
||||
aria-label=""
|
||||
class="cipher-subtitle"
|
||||
title="username5"
|
||||
data-testid="totp-code"
|
||||
>
|
||||
username5
|
||||
123 456
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="view website login 5, opensInANewWindow"
|
||||
@ -1543,7 +2097,6 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f
|
||||
class="cipher-container"
|
||||
>
|
||||
<button
|
||||
aria-description="username: username5"
|
||||
aria-label="fillCredentialsFor website login 5"
|
||||
class="fill-cipher-button inline-menu-list-action"
|
||||
tabindex="-1"
|
||||
@ -1551,24 +2104,66 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="cipher-icon"
|
||||
style="background-image: url(https://jest-testing-website.com/image.png);"
|
||||
/>
|
||||
<span
|
||||
>
|
||||
<div
|
||||
style="position: relative;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 29 29"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
|
||||
|
||||
<circle
|
||||
class="circle-color"
|
||||
cx="14.5"
|
||||
cy="14.5"
|
||||
fill="none"
|
||||
r="12.5"
|
||||
stroke-dasharray="78.5"
|
||||
stroke-dashoffset="78.5"
|
||||
stroke-width="3"
|
||||
style="stroke-dashoffset: NaN;"
|
||||
transform="rotate(-90 14.5 14.5)"
|
||||
/>
|
||||
|
||||
|
||||
<circle
|
||||
class="circle-color"
|
||||
cx="14.5"
|
||||
cy="14.5"
|
||||
fill="none"
|
||||
r="14"
|
||||
stroke-width="1"
|
||||
/>
|
||||
|
||||
|
||||
</svg>
|
||||
<span
|
||||
aria-label=""
|
||||
bittypography="helper"
|
||||
class="totp-sec-span"
|
||||
>
|
||||
NaN
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<div
|
||||
class="cipher-details"
|
||||
>
|
||||
<span
|
||||
aria-label=""
|
||||
class="cipher-name"
|
||||
title="website login 5"
|
||||
>
|
||||
website login 5
|
||||
</span>
|
||||
/>
|
||||
<span
|
||||
aria-label=""
|
||||
class="cipher-subtitle"
|
||||
title="username5"
|
||||
data-testid="totp-code"
|
||||
>
|
||||
username5
|
||||
123 456
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="view website login 5, opensInANewWindow"
|
||||
|
@ -140,6 +140,31 @@ describe("AutofillInlineMenuList", () => {
|
||||
expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("creates the view for a totp field", () => {
|
||||
postWindowMessage(
|
||||
createInitAutofillInlineMenuListMessageMock({
|
||||
inlineMenuFillType: CipherType.Login,
|
||||
ciphers: [
|
||||
createAutofillOverlayCipherDataMock(5, {
|
||||
type: CipherType.Login,
|
||||
login: {
|
||||
totp: "123456",
|
||||
totpField: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const cipherSubtitleElement = autofillInlineMenuList[
|
||||
"inlineMenuListContainer"
|
||||
].querySelector('[data-testid="totp-code"]');
|
||||
|
||||
expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot();
|
||||
expect(cipherSubtitleElement).not.toBeNull();
|
||||
expect(cipherSubtitleElement.textContent).toBe("123 456");
|
||||
});
|
||||
|
||||
it("creates the views for a list of card ciphers", () => {
|
||||
postWindowMessage(
|
||||
createInitAutofillInlineMenuListMessageMock({
|
||||
|
@ -1046,6 +1046,64 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
cipherIcon.classList.add("cipher-icon");
|
||||
cipherIcon.setAttribute("aria-hidden", "true");
|
||||
|
||||
if (cipher.login?.totpField && cipher.login?.totp) {
|
||||
const totpContainer = document.createElement("div");
|
||||
totpContainer.style.position = "relative";
|
||||
|
||||
const svgElement = buildSvgDomElement(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29 29">
|
||||
<circle fill="none" cx="14.5" cy="14.5" r="12.5"
|
||||
stroke-width="3" stroke-dasharray="78.5"
|
||||
stroke-dashoffset="78.5" transform="rotate(-90 14.5 14.5)"></circle>
|
||||
<circle fill="none" cx="14.5" cy="14.5" r="14" stroke-width="1"></circle>
|
||||
</svg>
|
||||
`);
|
||||
|
||||
const [innerCircleElement, outerCircleElement] = svgElement.querySelectorAll("circle");
|
||||
innerCircleElement.classList.add("circle-color");
|
||||
outerCircleElement.classList.add("circle-color");
|
||||
|
||||
totpContainer.appendChild(svgElement);
|
||||
|
||||
const totpSecondsSpan = document.createElement("span");
|
||||
totpSecondsSpan.classList.add("totp-sec-span");
|
||||
totpSecondsSpan.setAttribute("bitTypography", "helper");
|
||||
totpSecondsSpan.setAttribute("aria-label", this.getTranslation("totpSecondsSpanAria"));
|
||||
totpContainer.appendChild(totpSecondsSpan);
|
||||
|
||||
cipherIcon.appendChild(totpContainer);
|
||||
|
||||
const intervalSeconds = cipher.login.totpCodeTimeInterval;
|
||||
|
||||
const updateCountdown = () => {
|
||||
const epoch = Math.round(Date.now() / 1000);
|
||||
const mod = epoch % intervalSeconds;
|
||||
const totpSeconds = intervalSeconds - mod;
|
||||
|
||||
totpSecondsSpan.textContent = `${totpSeconds}`;
|
||||
|
||||
/**
|
||||
* Design specifies a seven-second time span as the period where expiry is approaching.
|
||||
*/
|
||||
const totpExpiryApproaching = totpSeconds <= 7;
|
||||
|
||||
totpSecondsSpan.classList.toggle("totp-sec-span-danger", totpExpiryApproaching);
|
||||
innerCircleElement.classList.toggle("circle-danger-color", totpExpiryApproaching);
|
||||
outerCircleElement.classList.toggle("circle-danger-color", totpExpiryApproaching);
|
||||
|
||||
innerCircleElement.style.strokeDashoffset = `${((intervalSeconds - totpSeconds) / intervalSeconds) * (2 * Math.PI * 12.5)}`;
|
||||
|
||||
if (mod === 0) {
|
||||
this.postMessageToParent({ command: "refreshOverlayCiphers" });
|
||||
}
|
||||
};
|
||||
|
||||
updateCountdown();
|
||||
setInterval(updateCountdown, 1000);
|
||||
|
||||
return cipherIcon;
|
||||
}
|
||||
|
||||
if (cipher.icon?.image) {
|
||||
try {
|
||||
const url = new URL(cipher.icon.image);
|
||||
@ -1104,6 +1162,9 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
return this.buildPasskeysCipherDetailsElement(cipher, cipherDetailsElement);
|
||||
}
|
||||
|
||||
if (cipher.login?.totpField && cipher.login?.totp) {
|
||||
return this.buildTotpElement(cipher.login?.totp);
|
||||
}
|
||||
const subTitleText = this.getSubTitleText(cipher);
|
||||
const cipherSubtitleElement = this.buildCipherSubtitleElement(subTitleText);
|
||||
if (cipherSubtitleElement) {
|
||||
@ -1113,6 +1174,38 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
|
||||
return cipherDetailsElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a TOTP element for a given TOTP code.
|
||||
*
|
||||
* @param totp - The TOTP code to display.
|
||||
*/
|
||||
|
||||
private buildTotpElement(totpCode: string): HTMLDivElement | null {
|
||||
if (!totpCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const formattedTotpCode = `${totpCode.substring(0, 3)} ${totpCode.substring(3)}`;
|
||||
|
||||
const containerElement = globalThis.document.createElement("div");
|
||||
containerElement.classList.add("cipher-details");
|
||||
const totpHeading = document.createElement("span");
|
||||
totpHeading.classList.add("cipher-name");
|
||||
totpHeading.textContent = this.getTranslation("fillVerificationCode");
|
||||
totpHeading.setAttribute("aria-label", this.getTranslation("fillVerificationCodeAria"));
|
||||
|
||||
containerElement.appendChild(totpHeading);
|
||||
|
||||
const subtitleElement = document.createElement("span");
|
||||
subtitleElement.classList.add("cipher-subtitle");
|
||||
subtitleElement.textContent = formattedTotpCode;
|
||||
subtitleElement.setAttribute("aria-label", this.getTranslation("totpCodeAria"));
|
||||
subtitleElement.setAttribute("data-testid", "totp-code");
|
||||
containerElement.appendChild(subtitleElement);
|
||||
|
||||
return containerElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the name element for a given cipher.
|
||||
*
|
||||
|
@ -350,6 +350,32 @@ body * {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.totp-sec-span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
@include themify($themes) {
|
||||
color: themed("textColor");
|
||||
}
|
||||
}
|
||||
|
||||
.totp-sec-span-danger {
|
||||
@include themify($themes) {
|
||||
color: themed("passwordSpecialColor");
|
||||
}
|
||||
}
|
||||
.circle-color {
|
||||
@include themify($themes) {
|
||||
stroke: themed("primaryColor");
|
||||
}
|
||||
}
|
||||
.circle-danger-color {
|
||||
@include themify($themes) {
|
||||
stroke: themed("passwordSpecialColor");
|
||||
}
|
||||
}
|
||||
|
||||
.cipher-name,
|
||||
.cipher-subtitle {
|
||||
display: block;
|
||||
|
@ -45,4 +45,5 @@ export interface InlineMenuFieldQualificationService {
|
||||
isFieldForIdentityUsername(field: AutofillField): boolean;
|
||||
isElementLoginSubmitButton(element: Element): boolean;
|
||||
isElementChangePasswordSubmitButton(element: Element): boolean;
|
||||
isTotpField(field: AutofillField): boolean;
|
||||
}
|
||||
|
@ -21,7 +21,23 @@ describe("InlineMenuFieldQualificationService", () => {
|
||||
});
|
||||
|
||||
describe("isFieldForLoginForm", () => {
|
||||
it("disqualifies totp fields", () => {
|
||||
it("does not disqualify totp fields with flag set to true", () => {
|
||||
inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = true;
|
||||
const field = mock<AutofillField>({
|
||||
type: "text",
|
||||
autoCompleteType: "one-time-code",
|
||||
htmlName: "totp",
|
||||
htmlID: "totp",
|
||||
placeholder: "totp",
|
||||
});
|
||||
|
||||
expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("disqualify totp fields with flag set to false", () => {
|
||||
inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = false;
|
||||
const field = mock<AutofillField>({
|
||||
type: "text",
|
||||
autoCompleteType: "one-time-code",
|
||||
|
@ -150,12 +150,16 @@ export class InlineMenuFieldQualificationService
|
||||
]);
|
||||
private totpFieldAutocompleteValue = "one-time-code";
|
||||
private inlineMenuFieldQualificationFlagSet = false;
|
||||
private inlineMenuTotpFeatureFlag = false;
|
||||
|
||||
constructor() {
|
||||
void sendExtensionMessage("getInlineMenuFieldQualificationFeatureFlag").then(
|
||||
(getInlineMenuFieldQualificationFlag) =>
|
||||
(this.inlineMenuFieldQualificationFlagSet = !!getInlineMenuFieldQualificationFlag?.result),
|
||||
);
|
||||
void Promise.all([
|
||||
sendExtensionMessage("getInlineMenuFieldQualificationFeatureFlag"),
|
||||
sendExtensionMessage("getInlineMenuTotpFeatureFlag"),
|
||||
]).then(([fieldQualificationFlag, totpFeatureFlag]) => {
|
||||
this.inlineMenuFieldQualificationFlagSet = !!fieldQualificationFlag?.result;
|
||||
this.inlineMenuTotpFeatureFlag = !!totpFeatureFlag?.result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,8 +173,15 @@ export class InlineMenuFieldQualificationService
|
||||
return this.isFieldForLoginFormFallback(field);
|
||||
}
|
||||
|
||||
if (this.isTotpField(field)) {
|
||||
return false;
|
||||
/**
|
||||
* Autofill does not fill password type totp input fields
|
||||
*/
|
||||
if (this.inlineMenuTotpFeatureFlag) {
|
||||
const isTotpField = this.isTotpField(field);
|
||||
const passwordType = field.type === "password";
|
||||
if (isTotpField && !passwordType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const isCurrentPasswordField = this.isCurrentPasswordField(field);
|
||||
@ -987,7 +998,7 @@ export class InlineMenuFieldQualificationService
|
||||
*
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
private isTotpField = (field: AutofillField): boolean => {
|
||||
isTotpField = (field: AutofillField): boolean => {
|
||||
if (this.fieldContainsAutocompleteValues(field, this.totpFieldAutocompleteValue)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -5,20 +5,34 @@ $dark-icon-themes: "theme_dark", "theme_solarizedDark", "theme_nord";
|
||||
$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
$font-family-source-code-pro: "Source Code Pro", monospace;
|
||||
$font-size-base: 14px;
|
||||
$text-color: #212529;
|
||||
$muted-text-color: #6c747c;
|
||||
$border-color: #ced4dc;
|
||||
|
||||
$text-color-light: #212529;
|
||||
$muted-text-color-light: #6c747c;
|
||||
$background-color-light: #ffffff;
|
||||
$background-offset-color-light: #f0f0f0;
|
||||
$brand-primary-light: #175ddc;
|
||||
$password-special-color-light: #b80017;
|
||||
$password-number-color-light: #1452c1;
|
||||
$success-color-light: #017e45;
|
||||
$error-color-light: #c83522;
|
||||
|
||||
$text-color-dark: #ffffff;
|
||||
$muted-text-color-dark: #bac0ce;
|
||||
$background-color-dark: #2f343d;
|
||||
$background-offset-color-dark: darken(#2f343d, 2.75%);
|
||||
$border-color-dark: #ddd;
|
||||
$brand-primary-dark: #6f9df1;
|
||||
$password-special-color-dark: #ff8d85;
|
||||
$password-number-color-dark: #6f9df1;
|
||||
$success-color-dark: #8db89b;
|
||||
$error-color-dark: #ee9792;
|
||||
|
||||
$muted-blue: #5a6d91;
|
||||
$muted-grey: #bac0ce;
|
||||
$border-color: #ced4dc;
|
||||
|
||||
$border-radius: 3px;
|
||||
$focus-outline-color: #1252a3;
|
||||
$muted-blue: #5a6d91;
|
||||
$password-special-color: #b80017;
|
||||
$password-number-color: #1452c1;
|
||||
|
||||
$brand-primary: #175ddc;
|
||||
|
||||
$background-color: #ffffff;
|
||||
$background-offset-color: #f0f0f0;
|
||||
|
||||
$solarizedDarkBase0: #839496;
|
||||
$solarizedDarkBase03: #002b36;
|
||||
@ -28,48 +42,42 @@ $solarizedDarkBase2: #eee8d5;
|
||||
$solarizedDarkCyan: #2aa198;
|
||||
$solarizedDarkGreen: #859900;
|
||||
|
||||
$success-color-light: #017e45;
|
||||
$success-color-dark: #8db89b;
|
||||
|
||||
$error-color-light: #c83522;
|
||||
$error-color-dark: #ee9792;
|
||||
|
||||
$themes: (
|
||||
light: (
|
||||
textColor: $text-color,
|
||||
mutedTextColor: $muted-text-color,
|
||||
backgroundColor: $background-color,
|
||||
backgroundOffsetColor: $background-offset-color,
|
||||
primaryColor: $brand-primary,
|
||||
buttonPrimaryColor: $brand-primary,
|
||||
textContrast: $background-color,
|
||||
textColor: $text-color-light,
|
||||
mutedTextColor: $muted-text-color-light,
|
||||
backgroundColor: $background-color-light,
|
||||
backgroundOffsetColor: $background-offset-color-light,
|
||||
primaryColor: $brand-primary-light,
|
||||
buttonPrimaryColor: $brand-primary-light,
|
||||
textContrast: $background-color-light,
|
||||
inputBorderColor: darken($border-color-dark, 2.75%),
|
||||
inputBackgroundColor: #ffffff,
|
||||
inputBackgroundColor: $background-color-light,
|
||||
borderColor: $border-color,
|
||||
focusOutlineColor: $focus-outline-color,
|
||||
successColor: $success-color-light,
|
||||
errorColor: $error-color-light,
|
||||
passkeysAuthenticating: $muted-blue,
|
||||
passwordSpecialColor: $password-special-color,
|
||||
passwordNumberColor: $password-number-color,
|
||||
passwordSpecialColor: $password-special-color-light,
|
||||
passwordNumberColor: $password-number-color-light,
|
||||
),
|
||||
dark: (
|
||||
textColor: #ffffff,
|
||||
mutedTextColor: #bac0ce,
|
||||
backgroundColor: #2f343d,
|
||||
backgroundOffsetColor: darken(#2f343d, 2.75%),
|
||||
buttonPrimaryColor: #6f9df1,
|
||||
primaryColor: #6f9df1,
|
||||
textContrast: #2f343d,
|
||||
textColor: $text-color-dark,
|
||||
mutedTextColor: $muted-text-color-dark,
|
||||
backgroundColor: $background-color-dark,
|
||||
backgroundOffsetColor: $background-offset-color-dark,
|
||||
buttonPrimaryColor: $brand-primary-dark,
|
||||
primaryColor: $brand-primary-dark,
|
||||
textContrast: $background-color-dark,
|
||||
inputBorderColor: #4c525f,
|
||||
inputBackgroundColor: #2f343d,
|
||||
inputBackgroundColor: $background-color-dark,
|
||||
borderColor: #4c525f,
|
||||
focusOutlineColor: lighten($focus-outline-color, 25%),
|
||||
successColor: $success-color-dark,
|
||||
errorColor: $error-color-dark,
|
||||
passkeysAuthenticating: #bac0ce,
|
||||
passwordSpecialColor: #ff8d85,
|
||||
passwordNumberColor: #6f9df1,
|
||||
passkeysAuthenticating: $muted-grey,
|
||||
passwordSpecialColor: $password-special-color-dark,
|
||||
passwordNumberColor: $password-number-color-dark,
|
||||
),
|
||||
nord: (
|
||||
textColor: $nord5,
|
||||
|
@ -241,7 +241,7 @@ export function createInitAutofillInlineMenuListMessageMock(
|
||||
createAutofillOverlayCipherDataMock(4, {
|
||||
icon: { imageEnabled: false, image: "", fallbackImage: "", icon: "" },
|
||||
}),
|
||||
createAutofillOverlayCipherDataMock(5),
|
||||
createAutofillOverlayCipherDataMock(5, { login: { totp: "123456", totpField: true } }),
|
||||
createAutofillOverlayCipherDataMock(6),
|
||||
createAutofillOverlayCipherDataMock(7),
|
||||
createAutofillOverlayCipherDataMock(8),
|
||||
|
@ -73,6 +73,7 @@ export default class RuntimeBackground {
|
||||
"biometricUnlockAvailable",
|
||||
"getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag",
|
||||
"getInlineMenuFieldQualificationFeatureFlag",
|
||||
"getInlineMenuTotpFeatureFlag",
|
||||
];
|
||||
|
||||
if (messagesWithResponse.includes(msg.command)) {
|
||||
@ -197,6 +198,9 @@ export default class RuntimeBackground {
|
||||
case "getInlineMenuFieldQualificationFeatureFlag": {
|
||||
return await this.configService.getFeatureFlag(FeatureFlag.InlineMenuFieldQualification);
|
||||
}
|
||||
case "getInlineMenuTotpFeatureFlag": {
|
||||
return await this.configService.getFeatureFlag(FeatureFlag.InlineMenuTotp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,7 @@ export enum FeatureFlag {
|
||||
NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss",
|
||||
NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss",
|
||||
DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship",
|
||||
InlineMenuTotp = "inline-menu-totp",
|
||||
MacOsNativeCredentialSync = "macos-native-credential-sync",
|
||||
PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission",
|
||||
PM12443RemovePagingLogic = "pm-12443-remove-paging-logic",
|
||||
@ -89,6 +90,7 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE,
|
||||
[FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE,
|
||||
[FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE,
|
||||
[FeatureFlag.InlineMenuTotp]: FALSE,
|
||||
[FeatureFlag.MacOsNativeCredentialSync]: FALSE,
|
||||
[FeatureFlag.PM11360RemoveProviderExportPermission]: FALSE,
|
||||
[FeatureFlag.PM12443RemovePagingLogic]: FALSE,
|
||||
|
Loading…
Reference in New Issue
Block a user