1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-17 07:15:13 +02:00

[PM-12345] Add cipher type settings for inline autofill menu (#11260)

* add inline menu identity and card visibility settings state to autofill settings service

* add inline menu identity and card visibility settings to autofill settings view component

* add inline menu identity and card visibility settings to legacy autofill settings view component

* do not show inline menu card and identity visibility settings if inline-menu-positioning-improvements feature flag is off

* show card and identity inline menus based on their visibility settings

* do not show identities in account creation username/email fields if user setting disallows it

* reload local tab settings for inline menu visibility when an inline visibility setting value changes

* take out tabSendMessageData call for inline menu visibility sub-settings

---------

Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>
This commit is contained in:
Jonathan Prusik 2024-10-15 11:07:52 -04:00 committed by GitHub
parent 40ab96a7ce
commit 1c2cb4440b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 291 additions and 29 deletions

View File

@ -1408,6 +1408,12 @@
"showInlineMenuLabel": { "showInlineMenuLabel": {
"message": "Show autofill suggestions on form fields" "message": "Show autofill suggestions on form fields"
}, },
"showInlineMenuIdentitiesLabel": {
"message": "Display identities as suggestions"
},
"showInlineMenuCardsLabel": {
"message": "Display cards as suggestions"
},
"showInlineMenuOnIconSelectionLabel": { "showInlineMenuOnIconSelectionLabel": {
"message": "Display suggestions when icon is selected" "message": "Display suggestions when icon is selected"
}, },

View File

@ -188,6 +188,8 @@ export type OverlayBackgroundExtensionMessageHandlers = {
updateIsFieldCurrentlyFilling: ({ message }: BackgroundMessageParam) => void; updateIsFieldCurrentlyFilling: ({ message }: BackgroundMessageParam) => void;
checkIsFieldCurrentlyFilling: () => boolean; checkIsFieldCurrentlyFilling: () => boolean;
getAutofillInlineMenuVisibility: () => void; getAutofillInlineMenuVisibility: () => void;
getInlineMenuCardsVisibility: () => void;
getInlineMenuIdentitiesVisibility: () => void;
openAutofillInlineMenu: () => void; openAutofillInlineMenu: () => void;
closeAutofillInlineMenu: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; closeAutofillInlineMenu: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
checkAutofillInlineMenuFocused: ({ sender }: BackgroundSenderParam) => void; checkAutofillInlineMenuFocused: ({ sender }: BackgroundSenderParam) => void;

View File

@ -132,6 +132,8 @@ export class OverlayBackground implements OverlayBackgroundInterface {
updateIsFieldCurrentlyFilling: ({ message }) => this.updateIsFieldCurrentlyFilling(message), updateIsFieldCurrentlyFilling: ({ message }) => this.updateIsFieldCurrentlyFilling(message),
checkIsFieldCurrentlyFilling: () => this.checkIsFieldCurrentlyFilling(), checkIsFieldCurrentlyFilling: () => this.checkIsFieldCurrentlyFilling(),
getAutofillInlineMenuVisibility: () => this.getInlineMenuVisibility(), getAutofillInlineMenuVisibility: () => this.getInlineMenuVisibility(),
getInlineMenuCardsVisibility: () => this.getInlineMenuCardsVisibility(),
getInlineMenuIdentitiesVisibility: () => this.getInlineMenuIdentitiesVisibility(),
openAutofillInlineMenu: () => this.openInlineMenu(false), openAutofillInlineMenu: () => this.openInlineMenu(false),
closeAutofillInlineMenu: ({ message, sender }) => this.closeInlineMenu(sender, message), closeAutofillInlineMenu: ({ message, sender }) => this.closeInlineMenu(sender, message),
checkAutofillInlineMenuFocused: ({ sender }) => this.checkInlineMenuFocused(sender), checkAutofillInlineMenuFocused: ({ sender }) => this.checkInlineMenuFocused(sender),
@ -1483,6 +1485,20 @@ export class OverlayBackground implements OverlayBackgroundInterface {
return await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$); return await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$);
} }
/**
* Gets the inline menu's visibility setting for Cards from the settings service.
*/
private async getInlineMenuCardsVisibility(): Promise<boolean> {
return await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$);
}
/**
* Gets the inline menu's visibility setting for Identities from the settings service.
*/
private async getInlineMenuIdentitiesVisibility(): Promise<boolean> {
return await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$);
}
/** /**
* Gets the user's authentication status from the auth service. * Gets the user's authentication status from the auth service.
*/ */

View File

@ -1,4 +1,5 @@
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { AutofillOverlayElementType } from "../../enums/autofill-overlay.enum"; import { AutofillOverlayElementType } from "../../enums/autofill-overlay.enum";
@ -23,7 +24,7 @@ export type AutofillExtensionMessage = {
data?: { data?: {
direction?: "previous" | "next" | "current"; direction?: "previous" | "next" | "current";
forceCloseInlineMenu?: boolean; forceCloseInlineMenu?: boolean;
inlineMenuVisibility?: number; newSettingValue?: InlineMenuVisibilitySetting;
}; };
}; };

View File

@ -46,6 +46,32 @@
</div> </div>
</div> </div>
</div> </div>
<div class="box tw-mb-5" *ngIf="inlineMenuPositioningImprovementsEnabled && inlineMenuIsEnabled">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="show-inline-menu-identities" class="!tw-mr-0">{{
"showInlineMenuIdentitiesLabel" | i18n
}}</label>
<input
id="show-inline-menu-identities"
type="checkbox"
(change)="updateShowInlineMenuIdentities()"
[(ngModel)]="showInlineMenuIdentities"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="show-inline-menu-cards" class="!tw-mr-0">{{
"showInlineMenuCardsLabel" | i18n
}}</label>
<input
id="show-inline-menu-cards"
type="checkbox"
(change)="updateShowInlineMenuCards()"
[(ngModel)]="showInlineMenuCards"
/>
</div>
</div>
</div>
<div class="box"> <div class="box">
<div class="box-content" *ngIf="canOverrideBrowserAutofillSetting"> <div class="box-content" *ngIf="canOverrideBrowserAutofillSetting">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>

View File

@ -8,10 +8,12 @@ import {
InlineMenuVisibilitySetting, InlineMenuVisibilitySetting,
ClearClipboardDelaySetting, ClearClipboardDelaySetting,
} from "@bitwarden/common/autofill/types"; } from "@bitwarden/common/autofill/types";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { import {
UriMatchStrategy, UriMatchStrategy,
UriMatchStrategySetting, UriMatchStrategySetting,
} from "@bitwarden/common/models/domain/domain-service"; } from "@bitwarden/common/models/domain/domain-service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -20,7 +22,6 @@ import { DialogService } from "@bitwarden/components";
import { BrowserApi } from "../../../platform/browser/browser-api"; import { BrowserApi } from "../../../platform/browser/browser-api";
import { enableAccountSwitching } from "../../../platform/flags"; import { enableAccountSwitching } from "../../../platform/flags";
import { AutofillService } from "../../services/abstractions/autofill.service";
@Component({ @Component({
selector: "app-autofill-v1", selector: "app-autofill-v1",
@ -32,6 +33,10 @@ export class AutofillV1Component implements OnInit {
protected autoFillOverlayVisibility: InlineMenuVisibilitySetting; protected autoFillOverlayVisibility: InlineMenuVisibilitySetting;
protected autoFillOverlayVisibilityOptions: any[]; protected autoFillOverlayVisibilityOptions: any[];
protected disablePasswordManagerLink: string; protected disablePasswordManagerLink: string;
protected inlineMenuPositioningImprovementsEnabled: boolean = false;
protected showInlineMenuIdentities: boolean = true;
protected showInlineMenuCards: boolean = true;
inlineMenuIsEnabled: boolean = false;
enableAutoFillOnPageLoad = false; enableAutoFillOnPageLoad = false;
autoFillOnPageLoadDefault = false; autoFillOnPageLoadDefault = false;
autoFillOnPageLoadOptions: any[]; autoFillOnPageLoadOptions: any[];
@ -50,7 +55,7 @@ export class AutofillV1Component implements OnInit {
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private domainSettingsService: DomainSettingsService, private domainSettingsService: DomainSettingsService,
private autofillService: AutofillService, private configService: ConfigService,
private dialogService: DialogService, private dialogService: DialogService,
private autofillSettingsService: AutofillSettingsServiceAbstraction, private autofillSettingsService: AutofillSettingsServiceAbstraction,
private messagingService: MessagingService, private messagingService: MessagingService,
@ -109,6 +114,20 @@ export class AutofillV1Component implements OnInit {
this.autofillSettingsService.inlineMenuVisibility$, this.autofillSettingsService.inlineMenuVisibility$,
); );
this.inlineMenuPositioningImprovementsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.InlineMenuPositioningImprovements,
);
this.inlineMenuIsEnabled = this.isInlineMenuEnabled();
this.showInlineMenuIdentities =
this.inlineMenuPositioningImprovementsEnabled &&
(await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$));
this.showInlineMenuCards =
this.inlineMenuPositioningImprovementsEnabled &&
(await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$));
this.enableAutoFillOnPageLoad = await firstValueFrom( this.enableAutoFillOnPageLoad = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoad$, this.autofillSettingsService.autofillOnPageLoad$,
); );
@ -140,9 +159,18 @@ export class AutofillV1Component implements OnInit {
); );
} }
isInlineMenuEnabled() {
return (
this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnFieldFocus ||
this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnButtonClick
);
}
async updateAutoFillOverlayVisibility() { async updateAutoFillOverlayVisibility() {
await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility); await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility);
await this.requestPrivacyPermission(); await this.requestPrivacyPermission();
this.inlineMenuIsEnabled = this.isInlineMenuEnabled();
} }
async updateAutoFillOnPageLoad() { async updateAutoFillOnPageLoad() {
@ -298,4 +326,12 @@ export class AutofillV1Component implements OnInit {
async updateShowIdentitiesCurrentTab() { async updateShowIdentitiesCurrentTab() {
await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab); await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab);
} }
async updateShowInlineMenuCards() {
await this.autofillSettingsService.setShowInlineMenuCards(this.showInlineMenuCards);
}
async updateShowInlineMenuIdentities() {
await this.autofillSettingsService.setShowInlineMenuIdentities(this.showInlineMenuIdentities);
}
} }

View File

@ -27,7 +27,37 @@
{{ "showInlineMenuOnFormFieldsDescAlt" | i18n }} {{ "showInlineMenuOnFormFieldsDescAlt" | i18n }}
</bit-hint> </bit-hint>
</bit-form-control> </bit-form-control>
<bit-form-control *ngIf="enableInlineMenu" class="tw-pl-5"> <bit-form-control
*ngIf="inlineMenuPositioningImprovementsEnabled && enableInlineMenu"
class="tw-ml-5"
>
<input
bitCheckbox
id="show-inline-menu-identities"
type="checkbox"
(change)="updateShowInlineMenuIdentities()"
[(ngModel)]="showInlineMenuIdentities"
/>
<bit-label for="show-inline-menu-identities">
{{ "showInlineMenuIdentitiesLabel" | i18n }}
</bit-label>
</bit-form-control>
<bit-form-control
*ngIf="inlineMenuPositioningImprovementsEnabled && enableInlineMenu"
class="tw-ml-5"
>
<input
bitCheckbox
id="show-inline-menu-cards"
type="checkbox"
(change)="updateShowInlineMenuCards()"
[(ngModel)]="showInlineMenuCards"
/>
<bit-label for="show-inline-menu-cards">
{{ "showInlineMenuCardsLabel" | i18n }}
</bit-label>
</bit-form-control>
<bit-form-control *ngIf="enableInlineMenu" class="tw-ml-5">
<input <input
bitCheckbox bitCheckbox
id="show-autofill-suggestions-on-icon" id="show-autofill-suggestions-on-icon"

View File

@ -21,10 +21,12 @@ import {
DisablePasswordManagerUri, DisablePasswordManagerUri,
InlineMenuVisibilitySetting, InlineMenuVisibilitySetting,
} from "@bitwarden/common/autofill/types"; } from "@bitwarden/common/autofill/types";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { import {
UriMatchStrategy, UriMatchStrategy,
UriMatchStrategySetting, UriMatchStrategySetting,
} from "@bitwarden/common/models/domain/domain-service"; } from "@bitwarden/common/models/domain/domain-service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -82,6 +84,7 @@ export class AutofillComponent implements OnInit {
protected defaultBrowserAutofillDisabled: boolean = false; protected defaultBrowserAutofillDisabled: boolean = false;
protected inlineMenuVisibility: InlineMenuVisibilitySetting = protected inlineMenuVisibility: InlineMenuVisibilitySetting =
AutofillOverlayVisibility.OnFieldFocus; AutofillOverlayVisibility.OnFieldFocus;
protected inlineMenuPositioningImprovementsEnabled: boolean = false;
protected browserClientVendor: BrowserClientVendor = BrowserClientVendors.Unknown; protected browserClientVendor: BrowserClientVendor = BrowserClientVendors.Unknown;
protected disablePasswordManagerURI: DisablePasswordManagerUri = protected disablePasswordManagerURI: DisablePasswordManagerUri =
DisablePasswordManagerUris.Unknown; DisablePasswordManagerUris.Unknown;
@ -93,6 +96,8 @@ export class AutofillComponent implements OnInit {
enableAutofillOnPageLoad: boolean = false; enableAutofillOnPageLoad: boolean = false;
enableInlineMenu: boolean = false; enableInlineMenu: boolean = false;
enableInlineMenuOnIconSelect: boolean = false; enableInlineMenuOnIconSelect: boolean = false;
showInlineMenuIdentities: boolean = true;
showInlineMenuCards: boolean = true;
autofillOnPageLoadDefault: boolean = false; autofillOnPageLoadDefault: boolean = false;
autofillOnPageLoadOptions: { name: string; value: boolean }[]; autofillOnPageLoadOptions: { name: string; value: boolean }[];
enableContextMenuItem: boolean = false; enableContextMenuItem: boolean = false;
@ -114,6 +119,7 @@ export class AutofillComponent implements OnInit {
private autofillSettingsService: AutofillSettingsServiceAbstraction, private autofillSettingsService: AutofillSettingsServiceAbstraction,
private messagingService: MessagingService, private messagingService: MessagingService,
private vaultSettingsService: VaultSettingsService, private vaultSettingsService: VaultSettingsService,
private configService: ConfigService,
) { ) {
this.autofillOnPageLoadOptions = [ this.autofillOnPageLoadOptions = [
{ name: i18nService.t("autoFillOnPageLoadYes"), value: true }, { name: i18nService.t("autoFillOnPageLoadYes"), value: true },
@ -151,6 +157,18 @@ export class AutofillComponent implements OnInit {
this.autofillSettingsService.inlineMenuVisibility$, this.autofillSettingsService.inlineMenuVisibility$,
); );
this.inlineMenuPositioningImprovementsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.InlineMenuPositioningImprovements,
);
this.showInlineMenuIdentities =
this.inlineMenuPositioningImprovementsEnabled &&
(await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$));
this.showInlineMenuCards =
this.inlineMenuPositioningImprovementsEnabled &&
(await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$));
this.enableInlineMenuOnIconSelect = this.enableInlineMenuOnIconSelect =
this.inlineMenuVisibility === AutofillOverlayVisibility.OnButtonClick; this.inlineMenuVisibility === AutofillOverlayVisibility.OnButtonClick;
@ -381,4 +399,12 @@ export class AutofillComponent implements OnInit {
async updateShowIdentitiesCurrentTab() { async updateShowIdentitiesCurrentTab() {
await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab); await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab);
} }
async updateShowInlineMenuCards() {
await this.autofillSettingsService.setShowInlineMenuCards(this.showInlineMenuCards);
}
async updateShowInlineMenuIdentities() {
await this.autofillSettingsService.setShowInlineMenuIdentities(this.showInlineMenuIdentities);
}
} }

View File

@ -2238,7 +2238,7 @@ describe("AutofillOverlayContentService", () => {
it("updates the inlineMenuVisibility property", () => { it("updates the inlineMenuVisibility property", () => {
sendMockExtensionMessage({ sendMockExtensionMessage({
command: "updateAutofillInlineMenuVisibility", command: "updateAutofillInlineMenuVisibility",
data: { inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick }, data: { newSettingValue: AutofillOverlayVisibility.OnButtonClick },
}); });
expect(autofillOverlayContentService["inlineMenuVisibility"]).toEqual( expect(autofillOverlayContentService["inlineMenuVisibility"]).toEqual(

View File

@ -9,6 +9,7 @@ import {
AUTOFILL_OVERLAY_HANDLE_REPOSITION, AUTOFILL_OVERLAY_HANDLE_REPOSITION,
AUTOFILL_TRIGGER_FORM_FIELD_SUBMIT, AUTOFILL_TRIGGER_FORM_FIELD_SUBMIT,
} from "@bitwarden/common/autofill/constants"; } from "@bitwarden/common/autofill/constants";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { import {
@ -51,7 +52,9 @@ import { AutoFillConstants } from "./autofill-constants";
export class AutofillOverlayContentService implements AutofillOverlayContentServiceInterface { export class AutofillOverlayContentService implements AutofillOverlayContentServiceInterface {
pageDetailsUpdateRequired = false; pageDetailsUpdateRequired = false;
inlineMenuVisibility: number; inlineMenuVisibility: InlineMenuVisibilitySetting;
private showInlineMenuIdentities: boolean;
private showInlineMenuCards: boolean;
private readonly findTabs = tabbable; private readonly findTabs = tabbable;
private readonly sendExtensionMessage = sendExtensionMessage; private readonly sendExtensionMessage = sendExtensionMessage;
private formFieldElements: Map<ElementWithOpId<FormFieldElement>, AutofillField> = new Map(); private formFieldElements: Map<ElementWithOpId<FormFieldElement>, AutofillField> = new Map();
@ -183,6 +186,18 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
autofillFieldData: AutofillField, autofillFieldData: AutofillField,
pageDetails: AutofillPageDetails, pageDetails: AutofillPageDetails,
) { ) {
if (!this.inlineMenuVisibility) {
await this.getInlineMenuVisibility();
}
if (this.showInlineMenuCards == null) {
await this.getInlineMenuCardsVisibility();
}
if (this.showInlineMenuIdentities == null) {
await this.getInlineMenuIdentitiesVisibility();
}
if ( if (
this.formFieldElements.has(formFieldElement) || this.formFieldElements.has(formFieldElement) ||
this.isIgnoredField(autofillFieldData, pageDetails) this.isIgnoredField(autofillFieldData, pageDetails)
@ -1019,10 +1034,16 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
const { width, height, top, left } = const { width, height, top, left } =
await this.getMostRecentlyFocusedFieldRects(formFieldElement); await this.getMostRecentlyFocusedFieldRects(formFieldElement);
const autofillFieldData = this.formFieldElements.get(formFieldElement); const autofillFieldData = this.formFieldElements.get(formFieldElement);
let accountCreationFieldType = null; let accountCreationFieldType = null;
if ( if (
// user setting allows display of identities in inline menu
this.showInlineMenuIdentities &&
// `showInlineMenuAccountCreation` has been set or field is filled by Login cipher
(autofillFieldData?.showInlineMenuAccountCreation || (autofillFieldData?.showInlineMenuAccountCreation ||
autofillFieldData?.filledByCipherType === CipherType.Login) && autofillFieldData?.filledByCipherType === CipherType.Login) &&
// field is a username field, which is relevant to both Identity and Login ciphers
this.inlineMenuFieldQualificationService.isUsernameField(autofillFieldData) this.inlineMenuFieldQualificationService.isUsernameField(autofillFieldData)
) { ) {
accountCreationFieldType = this.inlineMenuFieldQualificationService.isEmailField( accountCreationFieldType = this.inlineMenuFieldQualificationService.isEmailField(
@ -1125,6 +1146,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
} }
if ( if (
this.showInlineMenuCards &&
this.inlineMenuFieldQualificationService.isFieldForCreditCardForm( this.inlineMenuFieldQualificationService.isFieldForCreditCardForm(
autofillFieldData, autofillFieldData,
pageDetails, pageDetails,
@ -1135,6 +1157,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
} }
if ( if (
this.showInlineMenuIdentities &&
this.inlineMenuFieldQualificationService.isFieldForAccountCreationForm( this.inlineMenuFieldQualificationService.isFieldForAccountCreationForm(
autofillFieldData, autofillFieldData,
pageDetails, pageDetails,
@ -1146,6 +1169,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
} }
if ( if (
this.showInlineMenuIdentities &&
this.inlineMenuFieldQualificationService.isFieldForIdentityForm( this.inlineMenuFieldQualificationService.isFieldForIdentityForm(
autofillFieldData, autofillFieldData,
pageDetails, pageDetails,
@ -1244,6 +1268,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
autofillFieldData.readonly = getAttributeBoolean(formFieldElement, "disabled"); autofillFieldData.readonly = getAttributeBoolean(formFieldElement, "disabled");
autofillFieldData.disabled = getAttributeBoolean(formFieldElement, "disabled"); autofillFieldData.disabled = getAttributeBoolean(formFieldElement, "disabled");
autofillFieldData.viewable = true; autofillFieldData.viewable = true;
void this.setupOverlayListenersOnQualifiedField(formFieldElement, autofillFieldData); void this.setupOverlayListenersOnQualifiedField(formFieldElement, autofillFieldData);
} }
@ -1266,10 +1291,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
await this.updateMostRecentlyFocusedField(formFieldElement); await this.updateMostRecentlyFocusedField(formFieldElement);
} }
if (!this.inlineMenuVisibility) {
await this.getInlineMenuVisibility();
}
this.setupFormFieldElementEventListeners(formFieldElement); this.setupFormFieldElementEventListeners(formFieldElement);
this.setupFormSubmissionEventListeners(formFieldElement, autofillFieldData); this.setupFormSubmissionEventListeners(formFieldElement, autofillFieldData);
@ -1291,6 +1312,30 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
this.inlineMenuVisibility = inlineMenuVisibility || AutofillOverlayVisibility.OnFieldFocus; this.inlineMenuVisibility = inlineMenuVisibility || AutofillOverlayVisibility.OnFieldFocus;
} }
/**
* Queries the background script for the autofill inline menu's Cards visibility setting.
* If the setting is not found, a default value of true will be used
* @private
*/
private async getInlineMenuCardsVisibility() {
const inlineMenuCardsVisibility = await this.sendExtensionMessage(
"getInlineMenuCardsVisibility",
);
this.showInlineMenuCards = inlineMenuCardsVisibility ?? true;
}
/**
* Queries the background script for the autofill inline menu's Identities visibility setting.
* If the setting is not found, a default value of true will be used
* @private
*/
private async getInlineMenuIdentitiesVisibility() {
const inlineMenuIdentitiesVisibility = await this.sendExtensionMessage(
"getInlineMenuIdentitiesVisibility",
);
this.showInlineMenuIdentities = inlineMenuIdentitiesVisibility ?? true;
}
/** /**
* Returns a value that indicates if we should hide the inline menu list due to a filled field. * Returns a value that indicates if we should hide the inline menu list due to a filled field.
* *
@ -1318,8 +1363,10 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
* @param data - The data object from the extension message. * @param data - The data object from the extension message.
*/ */
private updateInlineMenuVisibility({ data }: AutofillExtensionMessage) { private updateInlineMenuVisibility({ data }: AutofillExtensionMessage) {
if (!isNaN(data?.inlineMenuVisibility)) { const newSettingValue = data?.newSettingValue;
this.inlineMenuVisibility = data.inlineMenuVisibility;
if (!isNaN(newSettingValue)) {
this.inlineMenuVisibility = newSettingValue;
} }
} }

View File

@ -75,6 +75,8 @@ describe("AutofillService", () => {
let autofillService: AutofillService; let autofillService: AutofillService;
const cipherService = mock<CipherService>(); const cipherService = mock<CipherService>();
let inlineMenuVisibilityMock$!: BehaviorSubject<InlineMenuVisibilitySetting>; let inlineMenuVisibilityMock$!: BehaviorSubject<InlineMenuVisibilitySetting>;
let showInlineMenuCardsMock$!: BehaviorSubject<boolean>;
let showInlineMenuIdentitiesMock$!: BehaviorSubject<boolean>;
let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>; let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>;
const mockUserId = Utils.newGuid() as UserId; const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
@ -98,8 +100,12 @@ describe("AutofillService", () => {
beforeEach(() => { beforeEach(() => {
scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService);
inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus); inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus);
showInlineMenuCardsMock$ = new BehaviorSubject(false);
showInlineMenuIdentitiesMock$ = new BehaviorSubject(false);
autofillSettingsService = mock<AutofillSettingsServiceAbstraction>(); autofillSettingsService = mock<AutofillSettingsServiceAbstraction>();
autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$; autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$;
autofillSettingsService.showInlineMenuCards$ = showInlineMenuCardsMock$;
autofillSettingsService.showInlineMenuIdentities$ = showInlineMenuIdentitiesMock$;
activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked); activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked);
authService = mock<AuthService>(); authService = mock<AuthService>();
authService.activeAccountStatus$ = activeAccountStatusMock$; authService.activeAccountStatus$ = activeAccountStatusMock$;
@ -291,12 +297,12 @@ describe("AutofillService", () => {
expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith(
tab1, tab1,
"updateAutofillInlineMenuVisibility", "updateAutofillInlineMenuVisibility",
{ inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick }, { newSettingValue: AutofillOverlayVisibility.OnButtonClick },
); );
expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith(
tab2, tab2,
"updateAutofillInlineMenuVisibility", "updateAutofillInlineMenuVisibility",
{ inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick }, { newSettingValue: AutofillOverlayVisibility.OnButtonClick },
); );
}); });
@ -308,12 +314,12 @@ describe("AutofillService", () => {
expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith(
tab1, tab1,
"updateAutofillInlineMenuVisibility", "updateAutofillInlineMenuVisibility",
{ inlineMenuVisibility: AutofillOverlayVisibility.OnFieldFocus }, { newSettingValue: AutofillOverlayVisibility.OnFieldFocus },
); );
expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith(
tab2, tab2,
"updateAutofillInlineMenuVisibility", "updateAutofillInlineMenuVisibility",
{ inlineMenuVisibility: AutofillOverlayVisibility.OnFieldFocus }, { newSettingValue: AutofillOverlayVisibility.OnFieldFocus },
); );
}); });
}); });

View File

@ -130,10 +130,23 @@ export default class AutofillService implements AutofillServiceInterface {
async loadAutofillScriptsOnInstall() { async loadAutofillScriptsOnInstall() {
BrowserApi.addListener(chrome.runtime.onConnect, this.handleInjectedScriptPortConnection); BrowserApi.addListener(chrome.runtime.onConnect, this.handleInjectedScriptPortConnection);
void this.injectAutofillScriptsInAllTabs(); void this.injectAutofillScriptsInAllTabs();
this.autofillSettingsService.inlineMenuVisibility$ this.autofillSettingsService.inlineMenuVisibility$
.pipe(startWith(undefined), pairwise()) .pipe(startWith(undefined), pairwise())
.subscribe(([previousSetting, currentSetting]) => .subscribe(([previousSetting, currentSetting]) =>
this.handleInlineMenuVisibilityChange(previousSetting, currentSetting), this.handleInlineMenuVisibilitySettingsChange(previousSetting, currentSetting),
);
this.autofillSettingsService.showInlineMenuCards$
.pipe(startWith(undefined), pairwise())
.subscribe(([previousSetting, currentSetting]) =>
this.handleInlineMenuVisibilitySettingsChange(previousSetting, currentSetting),
);
this.autofillSettingsService.showInlineMenuIdentities$
.pipe(startWith(undefined), pairwise())
.subscribe(([previousSetting, currentSetting]) =>
this.handleInlineMenuVisibilitySettingsChange(previousSetting, currentSetting),
); );
} }
@ -3043,27 +3056,36 @@ export default class AutofillService implements AutofillServiceInterface {
} }
/** /**
* Updates the autofill inline menu visibility setting in all active tabs * Updates the autofill inline menu visibility settings in all active tabs
* when the InlineMenuVisibilitySetting observable is updated. * when the inlineMenuVisibility, showInlineMenuCards, or showInlineMenuIdentities
* observables are updated.
* *
* @param previousSetting - The previous setting value * @param oldSettingValue - The previous setting value
* @param currentSetting - The current setting value * @param newSettingValue - The current setting value
* @param cipherType - The cipher type of the changed inline menu setting
*/ */
private async handleInlineMenuVisibilityChange( private async handleInlineMenuVisibilitySettingsChange(
previousSetting: InlineMenuVisibilitySetting, oldSettingValue: InlineMenuVisibilitySetting | boolean,
currentSetting: InlineMenuVisibilitySetting, newSettingValue: InlineMenuVisibilitySetting | boolean,
) { ) {
if (previousSetting === undefined || previousSetting === currentSetting) { if (oldSettingValue === undefined || oldSettingValue === newSettingValue) {
return; return;
} }
const inlineMenuPreviouslyDisabled = previousSetting === AutofillOverlayVisibility.Off; const isInlineMenuVisibilitySubSetting =
const inlineMenuCurrentlyDisabled = currentSetting === AutofillOverlayVisibility.Off; typeof oldSettingValue === "boolean" || typeof newSettingValue === "boolean";
if (!inlineMenuPreviouslyDisabled && !inlineMenuCurrentlyDisabled) { const inlineMenuPreviouslyDisabled = oldSettingValue === AutofillOverlayVisibility.Off;
const inlineMenuCurrentlyDisabled = newSettingValue === AutofillOverlayVisibility.Off;
if (
!isInlineMenuVisibilitySubSetting &&
!inlineMenuPreviouslyDisabled &&
!inlineMenuCurrentlyDisabled
) {
const tabs = await BrowserApi.tabsQuery({}); const tabs = await BrowserApi.tabsQuery({});
tabs.forEach((tab) => tabs.forEach((tab) =>
BrowserApi.tabSendMessageData(tab, "updateAutofillInlineMenuVisibility", { BrowserApi.tabSendMessageData(tab, "updateAutofillInlineMenuVisibility", {
inlineMenuVisibility: currentSetting, newSettingValue,
}), }),
); );
return; return;

View File

@ -59,6 +59,24 @@ const INLINE_MENU_VISIBILITY = new KeyDefinition(
}, },
); );
const SHOW_INLINE_MENU_IDENTITIES = new UserKeyDefinition(
AUTOFILL_SETTINGS_DISK,
"showInlineMenuIdentities",
{
deserializer: (value: boolean) => value ?? true,
clearOn: [],
},
);
const SHOW_INLINE_MENU_CARDS = new UserKeyDefinition(
AUTOFILL_SETTINGS_DISK,
"showInlineMenuCards",
{
deserializer: (value: boolean) => value ?? true,
clearOn: [],
},
);
const ENABLE_CONTEXT_MENU = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "enableContextMenu", { const ENABLE_CONTEXT_MENU = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "enableContextMenu", {
deserializer: (value: boolean) => value ?? true, deserializer: (value: boolean) => value ?? true,
}); });
@ -86,6 +104,10 @@ export abstract class AutofillSettingsServiceAbstraction {
setAutoCopyTotp: (newValue: boolean) => Promise<void>; setAutoCopyTotp: (newValue: boolean) => Promise<void>;
inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>; inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>;
setInlineMenuVisibility: (newValue: InlineMenuVisibilitySetting) => Promise<void>; setInlineMenuVisibility: (newValue: InlineMenuVisibilitySetting) => Promise<void>;
showInlineMenuIdentities$: Observable<boolean>;
setShowInlineMenuIdentities: (newValue: boolean) => Promise<void>;
showInlineMenuCards$: Observable<boolean>;
setShowInlineMenuCards: (newValue: boolean) => Promise<void>;
enableContextMenu$: Observable<boolean>; enableContextMenu$: Observable<boolean>;
setEnableContextMenu: (newValue: boolean) => Promise<void>; setEnableContextMenu: (newValue: boolean) => Promise<void>;
clearClipboardDelay$: Observable<ClearClipboardDelaySetting>; clearClipboardDelay$: Observable<ClearClipboardDelaySetting>;
@ -113,6 +135,12 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
private inlineMenuVisibilityState: GlobalState<InlineMenuVisibilitySetting>; private inlineMenuVisibilityState: GlobalState<InlineMenuVisibilitySetting>;
readonly inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>; readonly inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>;
private showInlineMenuIdentitiesState: ActiveUserState<boolean>;
readonly showInlineMenuIdentities$: Observable<boolean>;
private showInlineMenuCardsState: ActiveUserState<boolean>;
readonly showInlineMenuCards$: Observable<boolean>;
private enableContextMenuState: GlobalState<boolean>; private enableContextMenuState: GlobalState<boolean>;
readonly enableContextMenu$: Observable<boolean>; readonly enableContextMenu$: Observable<boolean>;
@ -157,6 +185,14 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
map((x) => x ?? AutofillOverlayVisibility.Off), map((x) => x ?? AutofillOverlayVisibility.Off),
); );
this.showInlineMenuIdentitiesState = this.stateProvider.getActive(SHOW_INLINE_MENU_IDENTITIES);
this.showInlineMenuIdentities$ = this.showInlineMenuIdentitiesState.state$.pipe(
map((x) => x ?? true),
);
this.showInlineMenuCardsState = this.stateProvider.getActive(SHOW_INLINE_MENU_CARDS);
this.showInlineMenuCards$ = this.showInlineMenuCardsState.state$.pipe(map((x) => x ?? true));
this.enableContextMenuState = this.stateProvider.getGlobal(ENABLE_CONTEXT_MENU); this.enableContextMenuState = this.stateProvider.getGlobal(ENABLE_CONTEXT_MENU);
this.enableContextMenu$ = this.enableContextMenuState.state$.pipe(map((x) => x ?? true)); this.enableContextMenu$ = this.enableContextMenuState.state$.pipe(map((x) => x ?? true));
@ -190,6 +226,14 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
await this.inlineMenuVisibilityState.update(() => newValue); await this.inlineMenuVisibilityState.update(() => newValue);
} }
async setShowInlineMenuIdentities(newValue: boolean): Promise<void> {
await this.showInlineMenuIdentitiesState.update(() => newValue);
}
async setShowInlineMenuCards(newValue: boolean): Promise<void> {
await this.showInlineMenuCardsState.update(() => newValue);
}
async setEnableContextMenu(newValue: boolean): Promise<void> { async setEnableContextMenu(newValue: boolean): Promise<void> {
await this.enableContextMenuState.update(() => newValue); await this.enableContextMenuState.update(() => newValue);
} }