[PM-8338] New Autofill Settings Components (#10184)

* add v2 autofill settings component

* add and update entries in message catalog for new autofill settings view

* add confirmation dialogs ahead of new tabs for browser settings from autofill settings

* fix autofill on page load warning styling and improper concatenation

* code cleanup
This commit is contained in:
Jonathan Prusik 2024-07-24 11:39:48 -04:00 committed by GitHub
parent d2afe221f0
commit 0ff62a5cc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 1055 additions and 338 deletions

View File

@ -108,7 +108,7 @@
"message": "Copy security code"
},
"autoFill": {
"message": "Auto-fill"
"message": "Autofill"
},
"autoFillLogin": {
"message": "Auto-fill login"
@ -789,12 +789,18 @@
"addLoginNotificationDescAlt": {
"message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts."
},
"showCardsInVaultView": {
"message": "Show cards as Autofill suggestions on Vault view"
},
"showCardsCurrentTab": {
"message": "Show cards on Tab page"
},
"showCardsCurrentTabDesc": {
"message": "List card items on the Tab page for easy auto-fill."
},
"showIdentitiesInVaultView": {
"message": "Show identifies as Autofill suggestions on Vault view"
},
"showIdentitiesCurrentTab": {
"message": "Show identities on Tab page"
},
@ -1227,11 +1233,20 @@
"message": "Show auto-fill menu on form fields",
"description": "Represents the message for allowing the user to enable the auto-fill overlay"
},
"showAutoFillMenuOnFormFieldsDescAlt": {
"autofillSuggestionsSectionTitle": {
"message": "Autofill suggestions"
},
"showInlineMenuLabel": {
"message": "Show autofill suggestions on form fields"
},
"showInlineMenuOnIconSelectionLabel": {
"message": "Display suggestions when icon is selected"
},
"showInlineMenuOnFormFieldsDescAlt": {
"message": "Applies to all logged in accounts."
},
"turnOffBrowserBuiltInPasswordManagerSettings": {
"message": "Turn off your browsers built in password manager settings to avoid conflicts."
"message": "Turn off your browser's built in password manager settings to avoid conflicts."
},
"turnOffBrowserBuiltInPasswordManagerSettingsLink": {
"message": "Edit browser settings."
@ -1248,23 +1263,43 @@
"message": "When auto-fill icon is selected",
"description": "Overlay appearance select option for showing the field on click of the overlay icon"
},
"enableAutoFillOnPageLoadSectionTitle": {
"message": "Autofill on page load"
},
"enableAutoFillOnPageLoad": {
"message": "Auto-fill on page load"
"message": "Autofill on page load"
},
"enableAutoFillOnPageLoadDesc": {
"message": "If a login form is detected, auto-fill when the web page loads."
"message": "If a login form is detected, autofill when the web page loads."
},
"autofillOnPageLoadWarning": {
"message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.",
"placeholders": {
"openTag": {
"content": "$1",
"example": "<b>"
},
"closeTag": {
"content": "$2",
"example": "</b>"
}
}
},
"experimentalFeature": {
"message": "Compromised or untrusted websites can exploit auto-fill on page load."
"message": "Compromised or untrusted websites can exploit autofill on page load."
},
"learnMoreAboutAutofillOnPageLoadLinkText": {
"message": "Learn more about risks"
},
"learnMoreAboutAutofill": {
"message": "Learn more about auto-fill"
"message": "Learn more about autofill"
},
"defaultAutoFillOnPageLoad": {
"message": "Default autofill setting for login items"
},
"defaultAutoFillOnPageLoadDesc": {
"message": "You can turn off auto-fill on page load for individual login items from the item's Edit view."
"message": "You can turn off autofill on page load for individual login items from the item's Edit view."
},
"itemAutoFillOnPageLoad": {
"message": "Auto-fill on page load (if set up in Options)"
@ -1273,10 +1308,10 @@
"message": "Use default setting"
},
"autoFillOnPageLoadYes": {
"message": "Auto-fill on page load"
"message": "Autofill on page load"
},
"autoFillOnPageLoadNo": {
"message": "Do not auto-fill on page load"
"message": "Do not autofill on page load"
},
"commandOpenPopup": {
"message": "Open vault popup"
@ -2004,7 +2039,7 @@
"placeholders": {
"domain": {
"content": "$1",
"example": "google.com"
"example": "duckduckgo.com"
}
}
},
@ -2706,14 +2741,20 @@
"autofillSettings": {
"message": "Auto-fill settings"
},
"autofillKeyboardShortcutSectionTitle": {
"message": "Autofill shortcut"
},
"autofillKeyboardShortcutUpdateLabel": {
"message": "Change shortcut"
},
"autofillShortcut": {
"message": "Auto-fill keyboard shortcut"
"message": "Autofill keyboard shortcut"
},
"autofillShortcutNotSet": {
"message": "The auto-fill shortcut is not set. Change this in the browser's settings."
"message": "The autofill shortcut is not set. Change this in the browser's settings."
},
"autofillShortcutText": {
"message": "The auto-fill shortcut is: $COMMAND$. Change this in the browser's settings.",
"message": "The autofill shortcut is: $COMMAND$. Change this in the browser's settings.",
"placeholders": {
"command": {
"content": "$1",
@ -3369,7 +3410,7 @@
"placeholders": {
"domain": {
"content": "$1",
"example": "google.com"
"example": "duckduckgo.com"
}
}
},
@ -3377,12 +3418,36 @@
"message": "Common formats",
"description": "Label indicating the most common import formats"
},
"confirmContinueToBrowserSettingsTitle": {
"message": "Continue to browser settings?",
"description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page"
},
"confirmContinueToHelpCenter": {
"message": "Continue to Help Center?",
"description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page"
},
"confirmContinueToHelpCenterPasswordManagementContent": {
"message": "Change your browser's autofill and password management settings.",
"description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings"
},
"confirmContinueToHelpCenterKeyboardShortcutsContent": {
"message": "You can view and set extension shortcuts in your browser's settings.",
"description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings"
},
"confirmContinueToBrowserPasswordManagementSettingsContent": {
"message": "Change your browser's autofill and password management settings.",
"description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page"
},
"confirmContinueToBrowserKeyboardShortcutSettingsContent": {
"message": "You can view and set extension shortcuts in your browser's settings.",
"description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page"
},
"overrideDefaultBrowserAutofillTitle": {
"message": "Make Bitwarden your default password manager?",
"description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior"
},
"overrideDefaultBrowserAutofillDescription": {
"message": "Ignoring this option may cause conflicts between the Bitwarden auto-fill menu and your browser's.",
"message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.",
"description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior"
},
"overrideDefaultBrowserAutoFillSettings": {

View File

@ -0,0 +1,221 @@
<header>
<div class="left">
<button type="button" routerLink="/tabs/settings">
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
<span>{{ "back" | i18n }}</span>
</button>
</div>
<h1 class="center">
<span class="title">{{ "autofill" | i18n }}</span>
</h1>
<div class="right"></div>
</header>
<main tabindex="-1">
<div class="box tw-mt-4">
<div class="box-content">
<button
type="button"
class="box-content-row box-content-row-link box-content-row-flex"
(click)="commandSettings()"
>
<div class="row-main">{{ "autofillShortcut" | i18n }}</div>
<i class="bwi bwi-external-link bwi-lg bwi-fw" aria-hidden="true"></i>
</button>
</div>
<div id="autofillKeyboardHelp" class="box-footer">
{{ autofillKeyboardHelperText }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="autofill-overlay-settings">{{ "showAutoFillMenuOnFormFields" | i18n }}</label>
<select
id="autofill-overlay-settings"
name="autofill-overlay-settings"
[(ngModel)]="autoFillOverlayVisibility"
(change)="updateAutoFillOverlayVisibility()"
>
<option *ngFor="let o of autoFillOverlayVisibilityOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div class="box-footer" *ngIf="accountSwitcherEnabled && canOverrideBrowserAutofillSetting">
{{ "showInlineMenuOnFormFieldsDescAlt" | i18n }}
</div>
</div>
</div>
<div class="box">
<div class="box-content" *ngIf="canOverrideBrowserAutofillSetting">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="overrideBrowserAutofill" class="!tw-mr-0">{{
"overrideDefaultBrowserAutoFillSettings" | i18n
}}</label>
<input
id="overrideBrowserAutofill"
type="checkbox"
(change)="updateDefaultBrowserAutofillDisabled()"
[(ngModel)]="defaultBrowserAutofillDisabled"
/>
</div>
</div>
<div class="box-footer">
<span *ngIf="accountSwitcherEnabled">{{ "showInlineMenuOnFormFieldsDescAlt" | i18n }}</span>
{{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }}
<a
[attr.href]="disablePasswordManagerLink"
(click)="openDisablePasswordManagerLink($event)"
target="_blank"
rel="noreferrer"
>
{{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }}
</a>
</div>
</div>
<div class="box tw-mt-4">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="autofill">{{ "enableAutoFillOnPageLoad" | i18n }}</label>
<input
id="autofill"
type="checkbox"
aria-describedby="autofillHelp"
(change)="updateAutoFillOnPageLoad()"
[(ngModel)]="enableAutoFillOnPageLoad"
/>
</div>
</div>
<div id="autofillHelp" class="box-footer">
{{ "enableAutoFillOnPageLoadDesc" | i18n }}
<b>{{ "warning" | i18n }}</b
>: {{ "experimentalFeature" | i18n }}
<a href="https://bitwarden.com/help/auto-fill-browser/" target="_blank" rel="noreferrer">
{{ "learnMoreAboutAutofill" | i18n }}.
<i
[attr.aria-label]="'opensInANewWindow' | i18n"
class="bwi bwi-external-link bwi-sm bwi-fw"
></i>
</a>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="defaultAutofill">{{ "defaultAutoFillOnPageLoad" | i18n }}</label>
<select
id="defaultAutofill"
name="DefaultAutofill"
aria-describedby="defaultAutofillHelp"
[(ngModel)]="autoFillOnPageLoadDefault"
(change)="updateAutoFillOnPageLoadDefault()"
[disabled]="!enableAutoFillOnPageLoad"
>
<option *ngFor="let o of autoFillOnPageLoadOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
</div>
<div id="defaultAutofillHelp" class="box-footer">
{{ "defaultAutoFillOnPageLoadDesc" | i18n }}
</div>
</div>
<div class="box">
<h2 class="box-header">{{ "additionalOptions" | i18n }}</h2>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="context-menu">{{ "enableContextMenuItem" | i18n }}</label>
<input
id="context-menu"
type="checkbox"
aria-describedby="context-menuHelp"
(change)="updateContextMenuItem()"
[(ngModel)]="enableContextMenuItem"
/>
</div>
</div>
<div id="context-menuHelp" class="box-footer">
{{
accountSwitcherEnabled ? ("contextMenuItemDescAlt" | i18n) : ("contextMenuItemDesc" | i18n)
}}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="totp">{{ "enableAutoTotpCopy" | i18n }}</label>
<input
id="totp"
type="checkbox"
aria-describedby="totpHelp"
(change)="updateAutoTotpCopy()"
[(ngModel)]="enableAutoTotpCopy"
/>
</div>
</div>
<div id="totpHelp" class="box-footer">{{ "disableAutoTotpCopyDesc" | i18n }}</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="clearClipboard">{{ "clearClipboard" | i18n }}</label>
<select
id="clearClipboard"
name="ClearClipboard"
aria-describedby="clearClipboardHelp"
[(ngModel)]="clearClipboard"
(change)="saveClearClipboard()"
>
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
</div>
<div id="clearClipboardHelp" class="box-footer">{{ "clearClipboardDesc" | i18n }}</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="defaultUriMatch">{{ "defaultUriMatchDetection" | i18n }}</label>
<select
id="defaultUriMatch"
name="DefaultUriMatch"
aria-describedby="defaultUriMatchHelp"
[(ngModel)]="defaultUriMatch"
(change)="saveDefaultUriMatch()"
>
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
</div>
<div id="defaultUriMatchHelp" class="box-footer">
{{ "defaultUriMatchDetectionDesc" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="showCardsCurrentTab">{{ "showCardsCurrentTab" | i18n }}</label>
<input
id="showCardsCurrentTab"
type="checkbox"
aria-describedby="showCardsCurrentTabHelp"
(change)="updateShowCardsCurrentTab()"
[(ngModel)]="showCardsCurrentTab"
/>
</div>
</div>
<div id="showCardsCurrentTabHelp" class="box-footer">
{{ "showCardsCurrentTabDesc" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="showIdentitiesCurrentTab">{{ "showIdentitiesCurrentTab" | i18n }}</label>
<input
id="showIdentitiesCurrentTab"
type="checkbox"
aria-describedby="showIdentitiesCurrentTabHelp"
(change)="updateShowIdentitiesCurrentTab()"
[(ngModel)]="showIdentitiesCurrentTab"
/>
</div>
</div>
<div id="showIdentitiesCurrentTabHelp" class="box-footer">
{{ "showIdentitiesCurrentTabDesc" | i18n }}
</div>
</div>
</main>

View File

@ -0,0 +1,301 @@
import { Component, OnInit } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import {
InlineMenuVisibilitySetting,
ClearClipboardDelaySetting,
} from "@bitwarden/common/autofill/types";
import {
UriMatchStrategy,
UriMatchStrategySetting,
} from "@bitwarden/common/models/domain/domain-service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { DialogService } from "@bitwarden/components";
import { BrowserApi } from "../../../platform/browser/browser-api";
import { enableAccountSwitching } from "../../../platform/flags";
import { AutofillService } from "../../services/abstractions/autofill.service";
@Component({
selector: "app-autofill-v1",
templateUrl: "autofill-v1.component.html",
})
export class AutofillV1Component implements OnInit {
protected canOverrideBrowserAutofillSetting = false;
protected defaultBrowserAutofillDisabled = false;
protected autoFillOverlayVisibility: InlineMenuVisibilitySetting;
protected autoFillOverlayVisibilityOptions: any[];
protected disablePasswordManagerLink: string;
enableAutoFillOnPageLoad = false;
autoFillOnPageLoadDefault = false;
autoFillOnPageLoadOptions: any[];
enableContextMenuItem = false;
enableAutoTotpCopy = false; // TODO: Does it matter if this is set to false or true?
clearClipboard: ClearClipboardDelaySetting;
clearClipboardOptions: any[];
defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain;
uriMatchOptions: any[];
showCardsCurrentTab = false;
showIdentitiesCurrentTab = false;
autofillKeyboardHelperText: string;
accountSwitcherEnabled = false;
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private domainSettingsService: DomainSettingsService,
private autofillService: AutofillService,
private dialogService: DialogService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private messagingService: MessagingService,
private vaultSettingsService: VaultSettingsService,
) {
this.autoFillOverlayVisibilityOptions = [
{
name: i18nService.t("autofillOverlayVisibilityOff"),
value: AutofillOverlayVisibility.Off,
},
{
name: i18nService.t("autofillOverlayVisibilityOnFieldFocus"),
value: AutofillOverlayVisibility.OnFieldFocus,
},
{
name: i18nService.t("autofillOverlayVisibilityOnButtonClick"),
value: AutofillOverlayVisibility.OnButtonClick,
},
];
this.autoFillOnPageLoadOptions = [
{ name: i18nService.t("autoFillOnPageLoadYes"), value: true },
{ name: i18nService.t("autoFillOnPageLoadNo"), value: false },
];
this.clearClipboardOptions = [
{ name: i18nService.t("never"), value: null },
{ name: i18nService.t("tenSeconds"), value: 10 },
{ name: i18nService.t("twentySeconds"), value: 20 },
{ name: i18nService.t("thirtySeconds"), value: 30 },
{ name: i18nService.t("oneMinute"), value: 60 },
{ name: i18nService.t("twoMinutes"), value: 120 },
{ name: i18nService.t("fiveMinutes"), value: 300 },
];
this.uriMatchOptions = [
{ name: i18nService.t("baseDomain"), value: UriMatchStrategy.Domain },
{ name: i18nService.t("host"), value: UriMatchStrategy.Host },
{ name: i18nService.t("startsWith"), value: UriMatchStrategy.StartsWith },
{ name: i18nService.t("regEx"), value: UriMatchStrategy.RegularExpression },
{ name: i18nService.t("exact"), value: UriMatchStrategy.Exact },
{ name: i18nService.t("never"), value: UriMatchStrategy.Never },
];
this.accountSwitcherEnabled = enableAccountSwitching();
this.disablePasswordManagerLink = this.getDisablePasswordManagerLink();
}
async ngOnInit() {
this.canOverrideBrowserAutofillSetting =
this.platformUtilsService.isChrome() ||
this.platformUtilsService.isEdge() ||
this.platformUtilsService.isOpera() ||
this.platformUtilsService.isVivaldi();
this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden();
this.autoFillOverlayVisibility = await firstValueFrom(
this.autofillSettingsService.inlineMenuVisibility$,
);
this.enableAutoFillOnPageLoad = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoad$,
);
this.autoFillOnPageLoadDefault = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoadDefault$,
);
this.enableContextMenuItem = await firstValueFrom(
this.autofillSettingsService.enableContextMenu$,
);
this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$);
this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$);
const defaultUriMatch = await firstValueFrom(
this.domainSettingsService.defaultUriMatchStrategy$,
);
this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch;
const command = await this.platformUtilsService.getAutofillKeyboardShortcut();
await this.setAutofillKeyboardHelperText(command);
this.showCardsCurrentTab = await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$);
this.showIdentitiesCurrentTab = await firstValueFrom(
this.vaultSettingsService.showIdentitiesCurrentTab$,
);
}
async updateAutoFillOverlayVisibility() {
await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility);
await this.requestPrivacyPermission();
}
async updateAutoFillOnPageLoad() {
await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad);
}
async updateAutoFillOnPageLoadDefault() {
await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault);
}
async saveDefaultUriMatch() {
await this.domainSettingsService.setDefaultUriMatchStrategy(this.defaultUriMatch);
}
private async setAutofillKeyboardHelperText(command: string) {
if (command) {
this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutText", command);
} else {
this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutNotSet");
}
}
async commandSettings() {
if (this.platformUtilsService.isChrome()) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("chrome://extensions/shortcuts");
} else if (this.platformUtilsService.isOpera()) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("opera://extensions/shortcuts");
} else if (this.platformUtilsService.isEdge()) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("edge://extensions/shortcuts");
} else if (this.platformUtilsService.isVivaldi()) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("vivaldi://extensions/shortcuts");
} else {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("https://bitwarden.com/help/keyboard-shortcuts");
}
}
private getDisablePasswordManagerLink(): string {
if (this.platformUtilsService.isChrome()) {
return "chrome://settings/autofill";
}
if (this.platformUtilsService.isOpera()) {
return "opera://settings/autofill";
}
if (this.platformUtilsService.isEdge()) {
return "edge://settings/passwords";
}
if (this.platformUtilsService.isVivaldi()) {
return "vivaldi://settings/autofill";
}
return "https://bitwarden.com/help/disable-browser-autofill/";
}
protected openDisablePasswordManagerLink(event: Event) {
event.preventDefault();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab(this.disablePasswordManagerLink);
}
async requestPrivacyPermission() {
if (
this.autoFillOverlayVisibility === AutofillOverlayVisibility.Off ||
!this.canOverrideBrowserAutofillSetting ||
(await this.browserAutofillSettingCurrentlyOverridden())
) {
return;
}
await this.dialogService.openSimpleDialog({
title: { key: "overrideDefaultBrowserAutofillTitle" },
content: { key: "overrideDefaultBrowserAutofillDescription" },
acceptButtonText: { key: "makeDefault" },
acceptAction: async () => await this.handleOverrideDialogAccept(),
cancelButtonText: { key: "ignore" },
type: "info",
});
}
async updateDefaultBrowserAutofillDisabled() {
const privacyPermissionGranted = await this.privacyPermissionGranted();
if (!this.defaultBrowserAutofillDisabled && !privacyPermissionGranted) {
return;
}
if (
!privacyPermissionGranted &&
!(await BrowserApi.requestPermission({ permissions: ["privacy"] }))
) {
await this.dialogService.openSimpleDialog({
title: { key: "privacyPermissionAdditionNotGrantedTitle" },
content: { key: "privacyPermissionAdditionNotGrantedDescription" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: "warning",
});
this.defaultBrowserAutofillDisabled = false;
return;
}
BrowserApi.updateDefaultBrowserAutofillSettings(!this.defaultBrowserAutofillDisabled);
}
private handleOverrideDialogAccept = async () => {
this.defaultBrowserAutofillDisabled = true;
await this.updateDefaultBrowserAutofillDisabled();
};
async browserAutofillSettingCurrentlyOverridden() {
if (!this.canOverrideBrowserAutofillSetting) {
return false;
}
if (!(await this.privacyPermissionGranted())) {
return false;
}
return await BrowserApi.browserAutofillSettingsOverridden();
}
async privacyPermissionGranted(): Promise<boolean> {
return await BrowserApi.permissionsGranted(["privacy"]);
}
async updateContextMenuItem() {
await this.autofillSettingsService.setEnableContextMenu(this.enableContextMenuItem);
this.messagingService.send("bgUpdateContextMenu");
}
async updateAutoTotpCopy() {
await this.autofillSettingsService.setAutoCopyTotp(this.enableAutoTotpCopy);
}
async saveClearClipboard() {
await this.autofillSettingsService.setClearClipboardDelay(this.clearClipboard);
}
async updateShowCardsCurrentTab() {
await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab);
}
async updateShowIdentitiesCurrentTab() {
await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab);
}
}

View File

@ -1,221 +1,234 @@
<header>
<div class="left">
<button type="button" routerLink="/tabs/settings">
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
<span>{{ "back" | i18n }}</span>
</button>
<popup-page>
<popup-header slot="header" pageTitle="{{ 'autofill' | i18n }}" showBackButton>
<ng-container slot="end">
<app-pop-out></app-pop-out>
</ng-container>
</popup-header>
<div class="tw-bg-background-alt tw-p-2">
<bit-section>
<bit-section-header>
<h2 bitTypography="h5">{{ "autofillSuggestionsSectionTitle" | i18n }}</h2>
</bit-section-header>
<bit-card>
<bit-form-control>
<input
bitCheckbox
id="show-inline-menu"
type="checkbox"
(change)="updateInlineMenuVisibility()"
[(ngModel)]="enableInlineMenu"
/>
<bit-label for="show-inline-menu">{{ "showInlineMenuLabel" | i18n }}</bit-label>
<bit-hint
*ngIf="accountSwitcherEnabled && canOverrideBrowserAutofillSetting"
class="tw-text-sm"
>
{{ "showInlineMenuOnFormFieldsDescAlt" | i18n }}
</bit-hint>
</bit-form-control>
<bit-form-control *ngIf="enableInlineMenu" class="tw-pl-5">
<input
bitCheckbox
id="show-autofill-suggestions-on-icon"
type="checkbox"
(change)="updateInlineMenuVisibility()"
[(ngModel)]="enableInlineMenuOnIconSelect"
/>
<bit-label for="show-autofill-suggestions-on-icon">
{{ "showInlineMenuOnIconSelectionLabel" | i18n }}
</bit-label>
<bit-hint class="tw-text-sm" *ngIf="!canOverrideBrowserAutofillSetting">
{{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }}
<a
bitLink
class="tw-no-underline"
rel="noreferrer"
target="_blank"
(click)="openURI($event, disablePasswordManagerURI)"
[attr.href]="disablePasswordManagerURI"
>
{{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }}
</a>
</bit-hint>
</bit-form-control>
<bit-form-control *ngIf="canOverrideBrowserAutofillSetting">
<input
bitCheckbox
id="overrideBrowserAutofill"
type="checkbox"
(change)="updateDefaultBrowserAutofillDisabled()"
[(ngModel)]="defaultBrowserAutofillDisabled"
/>
<bit-label for="overrideBrowserAutofill">{{
"overrideDefaultBrowserAutoFillSettings" | i18n
}}</bit-label>
<bit-hint class="tw-text-sm">
{{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }}
<a
bitLink
class="tw-no-underline"
rel="noreferrer"
target="_blank"
(click)="openURI($event, disablePasswordManagerURI)"
[attr.href]="disablePasswordManagerURI"
>
{{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }}
</a>
</bit-hint>
</bit-form-control>
<bit-form-control>
<input
bitCheckbox
id="showCardsSuggestions"
type="checkbox"
(change)="updateShowCardsCurrentTab()"
[(ngModel)]="showCardsCurrentTab"
/>
<bit-label for="showCardsSuggestions">{{ "showCardsInVaultView" | i18n }}</bit-label>
</bit-form-control>
<bit-form-control>
<input
bitCheckbox
id="showIdentitiesSuggestions"
type="checkbox"
(change)="updateShowIdentitiesCurrentTab()"
[(ngModel)]="showIdentitiesCurrentTab"
/>
<bit-label for="showIdentitiesSuggestions" class="tw-whitespace-normal">
{{ "showIdentitiesInVaultView" | i18n }}
</bit-label>
</bit-form-control>
</bit-card>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h5">{{ "autofillKeyboardShortcutSectionTitle" | i18n }}</h2>
</bit-section-header>
<bit-item>
<button bit-item-content type="button" (click)="openURI($event, browserShortcutsURI)">
<h3 bitTypography="h5">{{ "autofillKeyboardShortcutUpdateLabel" | i18n }}</h3>
<bit-hint slot="secondary" class="tw-text-sm tw-whitespace-normal">
{{ autofillKeyboardHelperText }}
</bit-hint>
<i
appA11yTitle="{{ 'opensInANewWindow' | i18n }}"
aria-hidden="true"
class="bwi bwi-fw bwi-external-link bwi-lg tw-text-muted"
slot="end"
></i>
</button>
</bit-item>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h5">{{ "enableAutoFillOnPageLoadSectionTitle" | i18n }}</h2>
</bit-section-header>
<bit-card>
<bit-hint class="tw-mb-6 tw-text-sm">
{{ "enableAutoFillOnPageLoadDesc" | i18n }}
<span [innerHTML]="'autofillOnPageLoadWarning' | i18n: '\<b>' : '\</b>'"></span>
<a
bitLink
class="tw-no-underline"
href="https://bitwarden.com/help/auto-fill-browser/"
rel="noreferrer"
target="_blank"
>
{{ "learnMoreAboutAutofillOnPageLoadLinkText" | i18n }}
</a>
</bit-hint>
<bit-form-control>
<input
bitCheckbox
id="autofillOnPageLoad"
type="checkbox"
(change)="updateAutofillOnPageLoad()"
[(ngModel)]="enableAutofillOnPageLoad"
/>
<bit-label for="autofillOnPageLoad">{{ "enableAutoFillOnPageLoad" | i18n }}</bit-label>
</bit-form-control>
<bit-form-field>
<bit-label for="defaultAutofill">{{ "defaultAutoFillOnPageLoad" | i18n }}</bit-label>
<select
bitInput
id="defaultAutofill"
(change)="updateAutofillOnPageLoadDefault()"
[(ngModel)]="autofillOnPageLoadDefault"
[disabled]="!enableAutofillOnPageLoad"
>
<option
*ngFor="let o of autofillOnPageLoadOptions"
[value]="o.value"
[label]="o.name"
></option>
</select>
<bit-hint class="tw-text-sm">
{{ "defaultAutoFillOnPageLoadDesc" | i18n }}
</bit-hint>
</bit-form-field>
</bit-card>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h5">{{ "additionalOptions" | i18n }}</h2>
</bit-section-header>
<bit-card>
<bit-form-control>
<input
bitCheckbox
id="context-menu"
type="checkbox"
(change)="updateContextMenuItem()"
[(ngModel)]="enableContextMenuItem"
/>
<bit-label for="context-menu">{{ "enableContextMenuItem" | i18n }}</bit-label>
</bit-form-control>
<bit-form-control>
<input
bitCheckbox
id="totp"
type="checkbox"
(change)="updateAutoTotpCopy()"
[(ngModel)]="enableAutoTotpCopy"
/>
<bit-label for="totp">{{ "enableAutoTotpCopy" | i18n }}</bit-label>
</bit-form-control>
<bit-form-field>
<bit-label for="clearClipboard">{{ "clearClipboard" | i18n }}</bit-label>
<select
aria-describedby="clearClipboardHelp"
bitInput
id="clearClipboard"
(change)="saveClearClipboard()"
[(ngModel)]="clearClipboard"
>
<option
*ngFor="let o of clearClipboardOptions"
[label]="o.name"
[value]="o.value"
></option>
</select>
<bit-hint class="tw-text-sm" id="clearClipboardHelp">
{{ "clearClipboardDesc" | i18n }}
</bit-hint>
</bit-form-field>
<bit-form-field>
<bit-label for="defaultUriMatch">{{ "defaultUriMatchDetection" | i18n }}</bit-label>
<select
aria-describedby="defaultUriMatchHelp"
bitInput
id="defaultUriMatch"
(change)="saveDefaultUriMatch()"
[(ngModel)]="defaultUriMatch"
>
<option *ngFor="let o of uriMatchOptions" [label]="o.name" [value]="o.value"></option>
</select>
<bit-hint class="tw-text-sm" id="defaultUriMatchHelp">
{{ "defaultUriMatchDetectionDesc" | i18n }}
</bit-hint>
</bit-form-field>
</bit-card>
</bit-section>
</div>
<h1 class="center">
<span class="title">{{ "autofill" | i18n }}</span>
</h1>
<div class="right"></div>
</header>
<main tabindex="-1">
<div class="box tw-mt-4">
<div class="box-content">
<button
type="button"
class="box-content-row box-content-row-link box-content-row-flex"
(click)="commandSettings()"
>
<div class="row-main">{{ "autofillShortcut" | i18n }}</div>
<i class="bwi bwi-external-link bwi-lg bwi-fw" aria-hidden="true"></i>
</button>
</div>
<div id="autofillKeyboardHelp" class="box-footer">
{{ autofillKeyboardHelperText }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="autofill-overlay-settings">{{ "showAutoFillMenuOnFormFields" | i18n }}</label>
<select
id="autofill-overlay-settings"
name="autofill-overlay-settings"
[(ngModel)]="autoFillOverlayVisibility"
(change)="updateAutoFillOverlayVisibility()"
>
<option *ngFor="let o of autoFillOverlayVisibilityOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div class="box-footer" *ngIf="accountSwitcherEnabled && canOverrideBrowserAutofillSetting">
{{ "showAutoFillMenuOnFormFieldsDescAlt" | i18n }}
</div>
</div>
</div>
<div class="box">
<div class="box-content" *ngIf="canOverrideBrowserAutofillSetting">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="overrideBrowserAutofill" class="!tw-mr-0">{{
"overrideDefaultBrowserAutoFillSettings" | i18n
}}</label>
<input
id="overrideBrowserAutofill"
type="checkbox"
(change)="updateDefaultBrowserAutofillDisabled()"
[(ngModel)]="defaultBrowserAutofillDisabled"
/>
</div>
</div>
<div class="box-footer">
<span *ngIf="accountSwitcherEnabled">{{ "showAutoFillMenuOnFormFieldsDescAlt" | i18n }}</span>
{{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }}
<a
[attr.href]="disablePasswordManagerLink"
(click)="openDisablePasswordManagerLink($event)"
target="_blank"
rel="noreferrer"
>
{{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }}
</a>
</div>
</div>
<div class="box tw-mt-4">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="autofill">{{ "enableAutoFillOnPageLoad" | i18n }}</label>
<input
id="autofill"
type="checkbox"
aria-describedby="autofillHelp"
(change)="updateAutoFillOnPageLoad()"
[(ngModel)]="enableAutoFillOnPageLoad"
/>
</div>
</div>
<div id="autofillHelp" class="box-footer">
{{ "enableAutoFillOnPageLoadDesc" | i18n }}
<b>{{ "warning" | i18n }}</b
>: {{ "experimentalFeature" | i18n }}
<a href="https://bitwarden.com/help/auto-fill-browser/" target="_blank" rel="noreferrer">
{{ "learnMoreAboutAutofill" | i18n }}.
<i
[attr.aria-label]="'opensInANewWindow' | i18n"
class="bwi bwi-external-link bwi-sm bwi-fw"
></i>
</a>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="defaultAutofill">{{ "defaultAutoFillOnPageLoad" | i18n }}</label>
<select
id="defaultAutofill"
name="DefaultAutofill"
aria-describedby="defaultAutofillHelp"
[(ngModel)]="autoFillOnPageLoadDefault"
(change)="updateAutoFillOnPageLoadDefault()"
[disabled]="!enableAutoFillOnPageLoad"
>
<option *ngFor="let o of autoFillOnPageLoadOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
</div>
<div id="defaultAutofillHelp" class="box-footer">
{{ "defaultAutoFillOnPageLoadDesc" | i18n }}
</div>
</div>
<div class="box">
<h2 class="box-header">{{ "additionalOptions" | i18n }}</h2>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="context-menu">{{ "enableContextMenuItem" | i18n }}</label>
<input
id="context-menu"
type="checkbox"
aria-describedby="context-menuHelp"
(change)="updateContextMenuItem()"
[(ngModel)]="enableContextMenuItem"
/>
</div>
</div>
<div id="context-menuHelp" class="box-footer">
{{
accountSwitcherEnabled ? ("contextMenuItemDescAlt" | i18n) : ("contextMenuItemDesc" | i18n)
}}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="totp">{{ "enableAutoTotpCopy" | i18n }}</label>
<input
id="totp"
type="checkbox"
aria-describedby="totpHelp"
(change)="updateAutoTotpCopy()"
[(ngModel)]="enableAutoTotpCopy"
/>
</div>
</div>
<div id="totpHelp" class="box-footer">{{ "disableAutoTotpCopyDesc" | i18n }}</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="clearClipboard">{{ "clearClipboard" | i18n }}</label>
<select
id="clearClipboard"
name="ClearClipboard"
aria-describedby="clearClipboardHelp"
[(ngModel)]="clearClipboard"
(change)="saveClearClipboard()"
>
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
</div>
<div id="clearClipboardHelp" class="box-footer">{{ "clearClipboardDesc" | i18n }}</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="defaultUriMatch">{{ "defaultUriMatchDetection" | i18n }}</label>
<select
id="defaultUriMatch"
name="DefaultUriMatch"
aria-describedby="defaultUriMatchHelp"
[(ngModel)]="defaultUriMatch"
(change)="saveDefaultUriMatch()"
>
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
</div>
<div id="defaultUriMatchHelp" class="box-footer">
{{ "defaultUriMatchDetectionDesc" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="showCardsCurrentTab">{{ "showCardsCurrentTab" | i18n }}</label>
<input
id="showCardsCurrentTab"
type="checkbox"
aria-describedby="showCardsCurrentTabHelp"
(change)="updateShowCardsCurrentTab()"
[(ngModel)]="showCardsCurrentTab"
/>
</div>
</div>
<div id="showCardsCurrentTabHelp" class="box-footer">
{{ "showCardsCurrentTabDesc" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="showIdentitiesCurrentTab">{{ "showIdentitiesCurrentTab" | i18n }}</label>
<input
id="showIdentitiesCurrentTab"
type="checkbox"
aria-describedby="showIdentitiesCurrentTabHelp"
(change)="updateShowIdentitiesCurrentTab()"
[(ngModel)]="showIdentitiesCurrentTab"
/>
</div>
</div>
<div id="showIdentitiesCurrentTabHelp" class="box-footer">
{{ "showIdentitiesCurrentTabDesc" | i18n }}
</div>
</div>
</main>
</popup-page>

View File

@ -1,12 +1,25 @@
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { RouterModule } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
AutofillOverlayVisibility,
BrowserClientVendors,
BrowserShortcutsUris,
ClearClipboardDelay,
DisablePasswordManagerUris,
} from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import {
InlineMenuVisibilitySetting,
BrowserClientVendor,
BrowserShortcutsUri,
ClearClipboardDelaySetting,
DisablePasswordManagerUri,
InlineMenuVisibilitySetting,
} from "@bitwarden/common/autofill/types";
import {
UriMatchStrategy,
@ -16,33 +29,77 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { DialogService } from "@bitwarden/components";
import {
CardComponent,
CheckboxModule,
DialogService,
FormFieldModule,
IconButtonModule,
ItemModule,
LinkModule,
SectionComponent,
SectionHeaderComponent,
SelectModule,
TypographyModule,
} from "@bitwarden/components";
import { BrowserApi } from "../../../platform/browser/browser-api";
import { enableAccountSwitching } from "../../../platform/flags";
import { AutofillService } from "../../services/abstractions/autofill.service";
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component";
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
@Component({
selector: "app-autofill",
templateUrl: "autofill.component.html",
standalone: true,
imports: [
CardComponent,
CheckboxModule,
CommonModule,
FormFieldModule,
FormsModule,
IconButtonModule,
ItemModule,
JslibModule,
LinkModule,
PopOutComponent,
PopupFooterComponent,
PopupHeaderComponent,
PopupPageComponent,
RouterModule,
SectionComponent,
SectionHeaderComponent,
SelectModule,
TypographyModule,
],
})
export class AutofillComponent implements OnInit {
/*
* Default values set here are used in component state operations
* until corresponding stored settings have loaded on init.
*/
protected canOverrideBrowserAutofillSetting = false;
protected defaultBrowserAutofillDisabled = false;
protected autoFillOverlayVisibility: InlineMenuVisibilitySetting;
protected autoFillOverlayVisibilityOptions: any[];
protected disablePasswordManagerLink: string;
enableAutoFillOnPageLoad = false;
autoFillOnPageLoadDefault = false;
autoFillOnPageLoadOptions: any[];
protected inlineMenuVisibility: InlineMenuVisibilitySetting =
AutofillOverlayVisibility.OnFieldFocus;
protected browserClientVendor: BrowserClientVendor = BrowserClientVendors.Unknown;
protected disablePasswordManagerURI: DisablePasswordManagerUri =
DisablePasswordManagerUris.Unknown;
protected browserShortcutsURI: BrowserShortcutsUri = BrowserShortcutsUris.Unknown;
protected browserClientIsUnknown: boolean;
enableAutofillOnPageLoad = false;
enableInlineMenu = false;
enableInlineMenuOnIconSelect = false;
autofillOnPageLoadDefault = false;
autofillOnPageLoadOptions: { name: string; value: boolean }[];
enableContextMenuItem = false;
enableAutoTotpCopy = false; // TODO: Does it matter if this is set to false or true?
enableAutoTotpCopy = false;
clearClipboard: ClearClipboardDelaySetting;
clearClipboardOptions: any[];
clearClipboardOptions: { name: string; value: ClearClipboardDelaySetting }[];
defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain;
uriMatchOptions: any[];
showCardsCurrentTab = false;
showIdentitiesCurrentTab = false;
uriMatchOptions: { name: string; value: UriMatchStrategySetting }[];
showCardsCurrentTab = true;
showIdentitiesCurrentTab = true;
autofillKeyboardHelperText: string;
accountSwitcherEnabled = false;
@ -50,38 +107,23 @@ export class AutofillComponent implements OnInit {
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private domainSettingsService: DomainSettingsService,
private autofillService: AutofillService,
private dialogService: DialogService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private messagingService: MessagingService,
private vaultSettingsService: VaultSettingsService,
) {
this.autoFillOverlayVisibilityOptions = [
{
name: i18nService.t("autofillOverlayVisibilityOff"),
value: AutofillOverlayVisibility.Off,
},
{
name: i18nService.t("autofillOverlayVisibilityOnFieldFocus"),
value: AutofillOverlayVisibility.OnFieldFocus,
},
{
name: i18nService.t("autofillOverlayVisibilityOnButtonClick"),
value: AutofillOverlayVisibility.OnButtonClick,
},
];
this.autoFillOnPageLoadOptions = [
this.autofillOnPageLoadOptions = [
{ name: i18nService.t("autoFillOnPageLoadYes"), value: true },
{ name: i18nService.t("autoFillOnPageLoadNo"), value: false },
];
this.clearClipboardOptions = [
{ name: i18nService.t("never"), value: null },
{ name: i18nService.t("tenSeconds"), value: 10 },
{ name: i18nService.t("twentySeconds"), value: 20 },
{ name: i18nService.t("thirtySeconds"), value: 30 },
{ name: i18nService.t("oneMinute"), value: 60 },
{ name: i18nService.t("twoMinutes"), value: 120 },
{ name: i18nService.t("fiveMinutes"), value: 300 },
{ name: i18nService.t("never"), value: ClearClipboardDelay.Never },
{ name: i18nService.t("tenSeconds"), value: ClearClipboardDelay.TenSeconds },
{ name: i18nService.t("twentySeconds"), value: ClearClipboardDelay.TwentySeconds },
{ name: i18nService.t("thirtySeconds"), value: ClearClipboardDelay.ThirtySeconds },
{ name: i18nService.t("oneMinute"), value: ClearClipboardDelay.OneMinute },
{ name: i18nService.t("twoMinutes"), value: ClearClipboardDelay.TwoMinutes },
{ name: i18nService.t("fiveMinutes"), value: ClearClipboardDelay.FiveMinutes },
];
this.uriMatchOptions = [
{ name: i18nService.t("baseDomain"), value: UriMatchStrategy.Domain },
@ -92,28 +134,32 @@ export class AutofillComponent implements OnInit {
{ name: i18nService.t("never"), value: UriMatchStrategy.Never },
];
this.accountSwitcherEnabled = enableAccountSwitching();
this.disablePasswordManagerLink = this.getDisablePasswordManagerLink();
this.browserClientVendor = this.getBrowserClientVendor();
this.disablePasswordManagerURI = DisablePasswordManagerUris[this.browserClientVendor];
this.browserShortcutsURI = BrowserShortcutsUris[this.browserClientVendor];
this.browserClientIsUnknown = this.browserClientVendor === BrowserClientVendors.Unknown;
}
async ngOnInit() {
this.canOverrideBrowserAutofillSetting =
this.platformUtilsService.isChrome() ||
this.platformUtilsService.isEdge() ||
this.platformUtilsService.isOpera() ||
this.platformUtilsService.isVivaldi();
this.canOverrideBrowserAutofillSetting = !this.browserClientIsUnknown;
this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden();
this.autoFillOverlayVisibility = await firstValueFrom(
this.inlineMenuVisibility = await firstValueFrom(
this.autofillSettingsService.inlineMenuVisibility$,
);
this.enableAutoFillOnPageLoad = await firstValueFrom(
this.enableInlineMenuOnIconSelect =
this.inlineMenuVisibility === AutofillOverlayVisibility.OnButtonClick;
this.enableInlineMenu =
this.inlineMenuVisibility === AutofillOverlayVisibility.OnFieldFocus ||
this.enableInlineMenuOnIconSelect;
this.enableAutofillOnPageLoad = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoad$,
);
this.autoFillOnPageLoadDefault = await firstValueFrom(
this.autofillOnPageLoadDefault = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoadDefault$,
);
@ -140,17 +186,27 @@ export class AutofillComponent implements OnInit {
);
}
async updateAutoFillOverlayVisibility() {
await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility);
async updateInlineMenuVisibility() {
if (!this.enableInlineMenu) {
this.enableInlineMenuOnIconSelect = false;
}
const newInlineMenuVisibilityValue = this.enableInlineMenuOnIconSelect
? AutofillOverlayVisibility.OnButtonClick
: this.enableInlineMenu
? AutofillOverlayVisibility.OnFieldFocus
: AutofillOverlayVisibility.Off;
await this.autofillSettingsService.setInlineMenuVisibility(newInlineMenuVisibilityValue);
await this.requestPrivacyPermission();
}
async updateAutoFillOnPageLoad() {
await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad);
async updateAutofillOnPageLoad() {
await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutofillOnPageLoad);
}
async updateAutoFillOnPageLoadDefault() {
await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault);
async updateAutofillOnPageLoadDefault() {
await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autofillOnPageLoadDefault);
}
async saveDefaultUriMatch() {
@ -165,57 +221,81 @@ export class AutofillComponent implements OnInit {
}
}
async commandSettings() {
private getBrowserClientVendor(): BrowserClientVendor {
if (this.platformUtilsService.isChrome()) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("chrome://extensions/shortcuts");
} else if (this.platformUtilsService.isOpera()) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("opera://extensions/shortcuts");
} else if (this.platformUtilsService.isEdge()) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("edge://extensions/shortcuts");
} else if (this.platformUtilsService.isVivaldi()) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("vivaldi://extensions/shortcuts");
} else {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("https://bitwarden.com/help/keyboard-shortcuts");
return BrowserClientVendors.Chrome;
}
}
private getDisablePasswordManagerLink(): string {
if (this.platformUtilsService.isChrome()) {
return "chrome://settings/autofill";
}
if (this.platformUtilsService.isOpera()) {
return "opera://settings/autofill";
}
if (this.platformUtilsService.isEdge()) {
return "edge://settings/passwords";
}
if (this.platformUtilsService.isVivaldi()) {
return "vivaldi://settings/autofill";
return BrowserClientVendors.Opera;
}
return "https://bitwarden.com/help/disable-browser-autofill/";
if (this.platformUtilsService.isEdge()) {
return BrowserClientVendors.Edge;
}
if (this.platformUtilsService.isVivaldi()) {
return BrowserClientVendors.Vivaldi;
}
return BrowserClientVendors.Unknown;
}
protected openDisablePasswordManagerLink(event: Event) {
protected async openURI(event: Event, uri: BrowserShortcutsUri | DisablePasswordManagerUri) {
event.preventDefault();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab(this.disablePasswordManagerLink);
// If the destination is a password management settings page, ask the user to confirm before proceeding
if (uri === DisablePasswordManagerUris[this.browserClientVendor]) {
await this.dialogService.openSimpleDialog({
...(this.browserClientIsUnknown
? {
content: { key: "confirmContinueToHelpCenterPasswordManagementContent" },
title: { key: "confirmContinueToHelpCenter" },
}
: {
content: { key: "confirmContinueToBrowserPasswordManagementSettingsContent" },
title: { key: "confirmContinueToBrowserSettingsTitle" },
}),
acceptButtonText: { key: "continue" },
acceptAction: async () => {
await BrowserApi.createNewTab(uri);
},
cancelButtonText: { key: "cancel" },
type: "info",
});
return;
}
// If the destination is a browser shortcut settings page, ask the user to confirm before proceeding
if (uri === BrowserShortcutsUris[this.browserClientVendor]) {
await this.dialogService.openSimpleDialog({
...(this.browserClientIsUnknown
? {
content: { key: "confirmContinueToHelpCenterKeyboardShortcutsContent" },
title: { key: "confirmContinueToHelpCenter" },
}
: {
content: { key: "confirmContinueToBrowserKeyboardShortcutSettingsContent" },
title: { key: "confirmContinueToBrowserSettingsTitle" },
}),
acceptButtonText: { key: "continue" },
acceptAction: async () => {
await BrowserApi.createNewTab(uri);
},
cancelButtonText: { key: "cancel" },
type: "info",
});
return;
}
await BrowserApi.createNewTab(uri);
}
async requestPrivacyPermission() {
if (
this.autoFillOverlayVisibility === AutofillOverlayVisibility.Off ||
this.inlineMenuVisibility === AutofillOverlayVisibility.Off ||
!this.canOverrideBrowserAutofillSetting ||
(await this.browserAutofillSettingCurrentlyOverridden())
) {
@ -225,9 +305,9 @@ export class AutofillComponent implements OnInit {
await this.dialogService.openSimpleDialog({
title: { key: "overrideDefaultBrowserAutofillTitle" },
content: { key: "overrideDefaultBrowserAutofillDescription" },
acceptButtonText: { key: "makeDefault" },
acceptButtonText: { key: "continue" },
acceptAction: async () => await this.handleOverrideDialogAccept(),
cancelButtonText: { key: "ignore" },
cancelButtonText: { key: "cancel" },
type: "info",
});
}

View File

@ -38,6 +38,7 @@ import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component"
import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component";
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component";
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component";
import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component";
@ -278,12 +279,11 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: { state: "export" },
}),
{
...extensionRefreshSwap(AutofillV1Component, AutofillComponent, {
path: "autofill",
component: AutofillComponent,
canActivate: [AuthGuard],
data: { state: "autofill" },
},
}),
{
path: "account-security",
component: AccountSecurityComponent,

View File

@ -36,6 +36,7 @@ import { SsoComponent } from "../auth/popup/sso.component";
import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component";
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component";
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component";
import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component";
@ -93,6 +94,7 @@ import "../platform/popup/locales";
imports: [
A11yModule,
AppRoutingModule,
AutofillComponent,
ToastModule.forRoot({
maxOpened: 2,
autoDismiss: true,
@ -180,7 +182,7 @@ import "../platform/popup/locales";
RemovePasswordComponent,
VaultSelectComponent,
Fido2Component,
AutofillComponent,
AutofillV1Component,
EnvironmentSelectorComponent,
AccountSwitcherComponent,
],

View File

@ -61,3 +61,27 @@ export const AutofillOverlayVisibility = {
OnButtonClick: 1,
OnFieldFocus: 2,
} as const;
export const BrowserClientVendors = {
Chrome: "Chrome",
Opera: "Opera",
Edge: "Edge",
Vivaldi: "Vivaldi",
Unknown: "Unknown",
} as const;
export const BrowserShortcutsUris = {
Chrome: "chrome://extensions/shortcuts",
Opera: "opera://extensions/shortcuts",
Edge: "edge://extensions/shortcuts",
Vivaldi: "vivaldi://extensions/shortcuts",
Unknown: "https://bitwarden.com/help/keyboard-shortcuts",
} as const;
export const DisablePasswordManagerUris = {
Chrome: "chrome://settings/autofill",
Opera: "opera://settings/autofill",
Edge: "edge://settings/passwords",
Vivaldi: "vivaldi://settings/autofill",
Unknown: "https://bitwarden.com/help/disable-browser-autofill/",
} as const;

View File

@ -1,7 +1,18 @@
import { ClearClipboardDelay, AutofillOverlayVisibility } from "../constants";
import {
AutofillOverlayVisibility,
BrowserClientVendors,
BrowserShortcutsUris,
ClearClipboardDelay,
DisablePasswordManagerUris,
} from "../constants";
export type ClearClipboardDelaySetting =
(typeof ClearClipboardDelay)[keyof typeof ClearClipboardDelay];
export type InlineMenuVisibilitySetting =
(typeof AutofillOverlayVisibility)[keyof typeof AutofillOverlayVisibility];
export type BrowserClientVendor = (typeof BrowserClientVendors)[keyof typeof BrowserClientVendors];
export type BrowserShortcutsUri = (typeof BrowserShortcutsUris)[keyof typeof BrowserShortcutsUris];
export type DisablePasswordManagerUri =
(typeof DisablePasswordManagerUris)[keyof typeof DisablePasswordManagerUris];