[PM-29528]Remove @ts-strict-ignore in autofill.service.ts (#19032)

* [PM-29528]Remove @ts-strict-ignore in autofill.service.ts

* remove redundant explicit checks

* implement a discriminated union to account for forms with no username

* add reducer and move up const for field opid

* Update apps/browser/src/autofill/services/autofill.service.ts

Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com>

* revert suggestion

---------

Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com>
This commit is contained in:
Daniel Riera 2026-03-11 10:36:38 -04:00 committed by GitHub
parent cd2b708982
commit 25dda0c78c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 232 additions and 134 deletions

View File

@ -34,13 +34,22 @@ export interface AutoFillOptions {
inlineMenuFillType?: InlineMenuFillType;
}
export interface FormData {
interface FormDataBase {
form: AutofillForm;
password: AutofillField;
username: AutofillField;
passwords: AutofillField[];
}
export interface FormDataWithUsername extends FormDataBase {
username: AutofillField;
}
export interface FormDataPasswordOnly extends FormDataBase {
username: null;
}
export type FormData = FormDataWithUsername | FormDataPasswordOnly;
export interface GenerateFillScriptOptions {
skipUsernameOnlyFill: boolean;
onlyEmptyFields: boolean;

View File

@ -189,7 +189,7 @@ describe("AutofillService", () => {
sender: AutofillMessageSender.collectPageDetailsFromTabObservable,
tab,
},
null,
undefined,
true,
);
});
@ -631,6 +631,25 @@ describe("AutofillService", () => {
},
]);
});
it("includes forms with password but no detectable username field with username: null", () => {
const passwordInputField = createAutofillFieldMock({
opid: "password-field",
type: "password",
form: "validFormId",
elementNumber: 1,
});
pageDetailsMock.fields = [passwordInputField];
jest.spyOn(AutofillService, "loadPasswordFields").mockReturnValueOnce([passwordInputField]);
const formData = autofillService.getFormsWithPasswordFields(pageDetailsMock);
expect(formData).toHaveLength(1);
expect(formData[0].form).toBe(pageDetailsMock.forms.validFormId);
expect(formData[0].password).toBe(passwordInputField);
expect(formData[0].passwords).toEqual([passwordInputField]);
expect(formData[0].username).toBeNull();
});
});
describe("doAutoFill", () => {

View File

@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import {
filter,
firstValueFrom,
@ -75,7 +73,7 @@ import {
export default class AutofillService implements AutofillServiceInterface {
private openVaultItemPasswordRepromptPopout = openVaultItemPasswordRepromptPopout;
private openPasswordRepromptPopoutDebounce: number | NodeJS.Timeout;
private openPasswordRepromptPopoutDebounce?: ReturnType<typeof setTimeout>;
private currentlyOpeningPasswordRepromptPopout = false;
private autofillScriptPortsSet = new Set<chrome.runtime.Port>();
static searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
@ -106,7 +104,7 @@ export default class AutofillService implements AutofillServiceInterface {
*/
collectPageDetailsFromTab$(tab: chrome.tabs.Tab): Observable<PageDetail[]> {
/** Replay Subject that can be utilized when `messages$` may not emit the page details. */
const pageDetailsFallback$ = new ReplaySubject<[]>(1);
const pageDetailsFallback$ = new ReplaySubject<PageDetail[]>(1);
const pageDetailsFromTab$ = this.messageListener
.messages$(COLLECT_PAGE_DETAILS_RESPONSE_COMMAND)
@ -117,14 +115,17 @@ export default class AutofillService implements AutofillServiceInterface {
message.sender === AutofillMessageSender.collectPageDetailsFromTabObservable,
),
scan(
(acc, message) => [
...acc,
{
frameId: message.webExtSender.frameId,
tab: message.tab,
details: message.details,
},
],
(acc: PageDetail[], message): PageDetail[] =>
message.webExtSender?.frameId === undefined
? acc
: [
...acc,
{
frameId: message.webExtSender.frameId,
tab: message.tab,
details: message.details,
},
],
[] as PageDetail[],
),
);
@ -136,7 +137,7 @@ export default class AutofillService implements AutofillServiceInterface {
command: AutofillMessageCommand.collectPageDetails,
sender: AutofillMessageSender.collectPageDetailsFromTabObservable,
},
null,
undefined,
true,
).catch(() => {
// When `tabSendMessage` throws an error the `pageDetailsFromTab$` will not emit,
@ -152,7 +153,7 @@ export default class AutofillService implements AutofillServiceInterface {
"moz-extension://",
"chrome-extension://",
"safari-web-extension://",
].some((protocol) => tab.url.startsWith(protocol));
].some((protocol) => tab.url?.startsWith(protocol));
if (!tab.url || urlHasBrowserProtocol) {
pageDetailsFallback$.next([]);
}
@ -162,7 +163,7 @@ export default class AutofillService implements AutofillServiceInterface {
// Create a timeout observable that emits an empty array if pageDetailsFromTab$ hasn't emitted within 1 second.
const pageDetailsTimeout$ = timer(1000).pipe(
map((): any => []),
map(() => [] as PageDetail[]),
takeUntil(sharedPageDetailsFromTab$),
);
@ -230,6 +231,10 @@ export default class AutofillService implements AutofillServiceInterface {
frameId = 0,
triggeringOnPageLoad = true,
): Promise<void> {
const tabId = tab.id;
if (tabId === undefined) {
return;
}
// Autofill user settings loaded from state can await the active account state indefinitely
// if not guarded by an active account check (e.g. the user is logged in)
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
@ -249,7 +254,7 @@ export default class AutofillService implements AutofillServiceInterface {
if (!triggeringOnPageLoad) {
await this.scriptInjectorService.inject({
tabId: tab.id,
tabId,
injectDetails: { file: "content/content-message-handler.js", runAt: "document_start" },
});
}
@ -258,7 +263,7 @@ export default class AutofillService implements AutofillServiceInterface {
for (const injectedScript of injectedScripts) {
await this.scriptInjectorService.inject({
tabId: tab.id,
tabId,
injectDetails: {
file: `content/${injectedScript}`,
runAt: "document_start",
@ -276,7 +281,7 @@ export default class AutofillService implements AutofillServiceInterface {
* @param activeAccount - The active account
*/
private async getBootstrapAutofillContentScript(
activeAccount: { id: UserId | undefined } & AccountInfo,
activeAccount: ({ id: UserId | undefined } & AccountInfo) | null | undefined,
): Promise<string> {
let inlineMenuVisibility: InlineMenuVisibilitySetting = AutofillOverlayVisibility.Off;
@ -367,7 +372,7 @@ export default class AutofillService implements AutofillServiceInterface {
formData.push({
form: pageDetails.forms[formKey],
password: formPasswordFields[0],
username: uf,
username: uf ?? null,
passwords: formPasswordFields,
});
}
@ -411,20 +416,24 @@ export default class AutofillService implements AutofillServiceInterface {
*/
async doAutoFill(options: AutoFillOptions): Promise<string | null> {
const tab = options.tab;
if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) {
const tabUrl = tab?.url;
if (!tabUrl || !options.cipher || !options.pageDetails || !options.pageDetails.length) {
throw new Error("Nothing to autofill.");
}
let totp: string | null = null;
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
const canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeAccount.id),
);
let canAccessPremium = false;
if (activeAccount?.id) {
canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeAccount.id),
);
}
const defaultUriMatch = await this.getDefaultUriMatchStrategy();
if (!canAccessPremium) {
options.cipher.login.totp = null;
options.cipher.login.totp = undefined;
}
let didAutofill = false;
@ -450,7 +459,7 @@ export default class AutofillService implements AutofillServiceInterface {
allowTotpAutofill: options.allowTotpAutofill || false,
autoSubmitLogin: options.autoSubmitLogin || false,
cipher: options.cipher,
tabUrl: tab.url,
tabUrl,
defaultUriMatch: defaultUriMatch,
focusedFieldOpid: options.focusedFieldOpid,
inlineMenuFillType: options.inlineMenuFillType,
@ -473,7 +482,7 @@ export default class AutofillService implements AutofillServiceInterface {
fillScript.properties.delay_between_operations = 20;
didAutofill = true;
if (!options.skipLastUsed) {
if (!options.skipLastUsed && activeAccount?.id) {
await this.cipherService.updateLastUsedDate(options.cipher.id, activeAccount.id);
}
@ -544,21 +553,27 @@ export default class AutofillService implements AutofillServiceInterface {
return null;
}
if (!tab.url) {
return null;
}
const tabUrl = tab.url;
if (fromCommand) {
cipher = await this.cipherService.getNextCipherForUrl(tab.url, activeUserId);
cipher = await this.cipherService.getNextCipherForUrl(tabUrl, activeUserId);
} else {
const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl(
tab.url,
tabUrl,
activeUserId,
true,
);
const lastLaunched = lastLaunchedCipher?.localData?.lastLaunched;
if (
lastLaunchedCipher &&
Date.now().valueOf() - lastLaunchedCipher.localData?.lastLaunched?.valueOf() < 30000
lastLaunched &&
Date.now().valueOf() - lastLaunched.valueOf() < 30000
) {
cipher = lastLaunchedCipher;
} else {
cipher = await this.cipherService.getLastUsedForUrl(tab.url, activeUserId, true);
cipher = await this.cipherService.getLastUsedForUrl(tabUrl, activeUserId, true);
}
}
@ -568,7 +583,7 @@ export default class AutofillService implements AutofillServiceInterface {
if (await this.isPasswordRepromptRequired(cipher, tab)) {
if (fromCommand) {
this.cipherService.updateLastUsedIndexForUrl(tab.url);
this.cipherService.updateLastUsedIndexForUrl(tabUrl);
}
return null;
@ -589,7 +604,7 @@ export default class AutofillService implements AutofillServiceInterface {
// Update last used index as autofill has succeeded
if (fromCommand) {
this.cipherService.updateLastUsedIndexForUrl(tab.url);
this.cipherService.updateLastUsedIndexForUrl(tabUrl);
}
return totpCode;
@ -740,7 +755,7 @@ export default class AutofillService implements AutofillServiceInterface {
return null;
}
let fillScript = new AutofillScript();
const fillScript = new AutofillScript();
const filledFields: { [id: string]: AutofillField } = {};
const fields = options.cipher.fields;
@ -748,14 +763,17 @@ export default class AutofillService implements AutofillServiceInterface {
const fieldNames: string[] = [];
fields.forEach((f) => {
if (AutofillService.hasValue(f.name)) {
if (f.name != null && AutofillService.hasValue(f.name)) {
fieldNames.push(f.name.toLowerCase());
}
});
pageDetails.fields.forEach((field) => {
// eslint-disable-next-line
if (filledFields.hasOwnProperty(field.opid)) {
const fieldOpid = field.opid;
if (fieldOpid == null) {
return;
}
if (Object.prototype.hasOwnProperty.call(filledFields, fieldOpid)) {
return;
}
@ -773,40 +791,40 @@ export default class AutofillService implements AutofillServiceInterface {
const matchingField: FieldView = fields[matchingIndex];
let val: string;
if (matchingField.type === FieldType.Linked) {
if (matchingField.linkedId == null) {
return;
}
// Assumption: Linked Field is not being used to autofill a boolean value
val = options.cipher.linkedFieldValue(matchingField.linkedId) as string;
} else {
val = matchingField.value;
if (val == null && matchingField.type === FieldType.Boolean) {
val = "false";
const rawVal = matchingField.value;
if (rawVal == null) {
if (matchingField.type === FieldType.Boolean) {
val = "false";
} else {
return;
}
} else {
val = rawVal;
}
}
filledFields[field.opid] = field;
filledFields[fieldOpid] = field;
AutofillService.fillByOpid(fillScript, field, val);
}
});
}
let result: AutofillScript | null = null;
switch (options.cipher.type) {
case CipherType.Login:
fillScript = await this.generateLoginFillScript(
fillScript,
pageDetails,
filledFields,
options,
);
result = await this.generateLoginFillScript(fillScript, pageDetails, filledFields, options);
break;
case CipherType.Card:
fillScript = await this.generateCardFillScript(
fillScript,
pageDetails,
filledFields,
options,
);
result = await this.generateCardFillScript(fillScript, pageDetails, filledFields, options);
break;
case CipherType.Identity:
fillScript = await this.generateIdentityFillScript(
result = await this.generateIdentityFillScript(
fillScript,
pageDetails,
filledFields,
@ -817,7 +835,7 @@ export default class AutofillService implements AutofillServiceInterface {
return null;
}
return fillScript;
return result;
}
/**
@ -842,12 +860,17 @@ export default class AutofillService implements AutofillServiceInterface {
const passwords: AutofillField[] = [];
const usernames = new Map<string, AutofillField>();
const totps: AutofillField[] = [];
let pf: AutofillField = null;
let username: AutofillField = null;
let totp: AutofillField = null;
let pf: AutofillField | null = null;
let username: AutofillField | null = null;
let totp: AutofillField | null = null;
const login = options.cipher.login;
fillScript.savedUrls =
login?.uris?.filter((u) => u.match != UriMatchStrategy.Never).map((u) => u.uri) ?? [];
const loginURIs = login?.uris ?? [];
fillScript.savedUrls = loginURIs.reduce<string[]>((acc, savedURI) => {
if (savedURI.match != UriMatchStrategy.Never && savedURI.uri != null) {
acc.push(savedURI.uri);
}
return acc;
}, []);
fillScript.untrustedIframe = await this.inUntrustedIframe(pageDetails.url, options);
@ -874,9 +897,9 @@ export default class AutofillService implements AutofillServiceInterface {
const prioritizedPasswordFields =
loginPasswordFields.length > 0 ? loginPasswordFields : registrationPasswordFields;
const focusedField =
options.focusedFieldOpid &&
pageDetails.fields.find((f) => f.opid === options.focusedFieldOpid);
const focusedField = options.focusedFieldOpid
? pageDetails.fields.find((f) => f.opid === options.focusedFieldOpid)
: undefined;
const focusedForm = focusedField?.form;
const isFocusedTotpField =
@ -908,10 +931,12 @@ export default class AutofillService implements AutofillServiceInterface {
? true
: focusedForm != null
? pf.form === focusedForm
: focusedUsernameField &&
pf.form == null &&
this.findUsernameField(pageDetails, pf, false, false, true)?.opid ===
focusedUsernameField.opid;
: !!(
focusedUsernameField &&
pf.form == null &&
this.findUsernameField(pageDetails, pf, false, false, true)?.opid ===
focusedUsernameField.opid
);
const getUsernameForPassword = (
pf: AutofillField,
@ -924,10 +949,17 @@ export default class AutofillService implements AutofillServiceInterface {
return this.findUsernameField(pageDetails, pf, false, false, withoutForm);
};
if (focusedUsernameField && !prioritizedPasswordFields.some(passwordMatchesFocused)) {
if (
focusedUsernameField &&
focusedUsernameField.opid != null &&
!prioritizedPasswordFields.some(passwordMatchesFocused)
) {
if (!Object.prototype.hasOwnProperty.call(filledFields, focusedUsernameField.opid)) {
filledFields[focusedUsernameField.opid] = focusedUsernameField;
AutofillService.fillByOpid(fillScript, focusedUsernameField, login.username);
const usernameVal = login.username;
if (usernameVal != null) {
AutofillService.fillByOpid(fillScript, focusedUsernameField, usernameVal);
}
if (options.autoSubmitLogin && focusedUsernameField.form) {
fillScript.autosubmit = [focusedUsernameField.form];
}
@ -954,7 +986,7 @@ export default class AutofillService implements AutofillServiceInterface {
if (login.username) {
username = getUsernameForPassword(pf, false);
if (username) {
if (username?.opid != null) {
usernames.set(username.opid, username);
}
}
@ -1010,7 +1042,7 @@ export default class AutofillService implements AutofillServiceInterface {
if (firstPasswordField) {
if (login.username && firstPasswordField.elementNumber > 0) {
username = getUsernameForPassword(firstPasswordField, true);
if (username) {
if (username?.opid != null) {
usernames.set(username.opid, username);
}
}
@ -1068,46 +1100,68 @@ export default class AutofillService implements AutofillServiceInterface {
const usernamesToFill = focusedUsernameField ? [focusedUsernameField] : [...usernames.values()];
usernamesToFill.forEach((u) => {
if (Object.prototype.hasOwnProperty.call(filledFields, u.opid)) {
if (u.opid == null) {
return;
}
const uOpid = u.opid;
if (Object.prototype.hasOwnProperty.call(filledFields, uOpid)) {
return;
}
filledFields[u.opid] = u;
AutofillService.fillByOpid(fillScript, u, login.username);
formElementsSet.add(u.form);
filledFields[uOpid] = u;
const usernameVal = login.username;
if (usernameVal != null) {
AutofillService.fillByOpid(fillScript, u, usernameVal);
}
if (u.form != null) {
formElementsSet.add(u.form);
}
});
passwords.forEach((p) => {
if (p.opid == null) {
return;
}
const pOpid = p.opid;
// eslint-disable-next-line
if (filledFields.hasOwnProperty(p.opid)) {
if (filledFields.hasOwnProperty(pOpid)) {
return;
}
filledFields[p.opid] = p;
AutofillService.fillByOpid(fillScript, p, login.password);
formElementsSet.add(p.form);
filledFields[pOpid] = p;
if (login.password != null) {
AutofillService.fillByOpid(fillScript, p, login.password);
}
if (p.form != null) {
formElementsSet.add(p.form);
}
});
if (options.autoSubmitLogin && formElementsSet.size) {
fillScript.autosubmit = Array.from(formElementsSet);
}
if (options.allowTotpAutofill && login?.totp) {
const loginTotp = login?.totp;
if (options.allowTotpAutofill && typeof loginTotp === "string") {
await Promise.all(
totps.map(async (t, i) => {
if (t.opid == null) {
return;
}
if (Object.prototype.hasOwnProperty.call(filledFields, t.opid)) {
return;
}
filledFields[t.opid] = t;
const totpResponse = await firstValueFrom(this.totpService.getCode$(login.totp));
let totpValue = totpResponse.code;
if (totpValue.length == totps.length) {
totpValue = totpValue.charAt(i);
const totpResponse = await firstValueFrom(this.totpService.getCode$(loginTotp));
const totpValue = totpResponse.code;
if (totpValue == null) {
return;
}
AutofillService.fillByOpid(fillScript, t, totpValue);
const totpChar = totpValue.length === totps.length ? totpValue.charAt(i) : totpValue;
AutofillService.fillByOpid(fillScript, t, totpChar);
}),
);
}
@ -1216,23 +1270,23 @@ export default class AutofillService implements AutofillServiceInterface {
this.makeScriptAction(fillScript, card, fillFields, filledFields, "brand");
// There is an expiration month field and the cipher has an expiration month value
if (fillFields.expMonth && AutofillService.hasValue(card.expMonth)) {
if (fillFields.expMonth && card.expMonth != null && AutofillService.hasValue(card.expMonth)) {
let expMonth: string = card.expMonth;
if (fillFields.expMonth.selectInfo && fillFields.expMonth.selectInfo.options) {
let index: number = null;
let index: number | null = null;
const siOptions = fillFields.expMonth.selectInfo.options;
if (siOptions.length === 12) {
index = parseInt(card.expMonth, null) - 1;
index = parseInt(card.expMonth, 10) - 1;
} else if (siOptions.length === 13) {
if (
siOptions[0][0] != null &&
siOptions[0][0] !== "" &&
(siOptions[12][0] == null || siOptions[12][0] === "")
) {
index = parseInt(card.expMonth, null) - 1;
index = parseInt(card.expMonth, 10) - 1;
} else {
index = parseInt(card.expMonth, null);
index = parseInt(card.expMonth, 10);
}
}
@ -1250,12 +1304,14 @@ export default class AutofillService implements AutofillServiceInterface {
expMonth = "0" + expMonth;
}
filledFields[fillFields.expMonth.opid] = fillFields.expMonth;
AutofillService.fillByOpid(fillScript, fillFields.expMonth, expMonth);
if (fillFields.expMonth.opid != null) {
filledFields[fillFields.expMonth.opid] = fillFields.expMonth;
AutofillService.fillByOpid(fillScript, fillFields.expMonth, expMonth);
}
}
// There is an expiration year field and the cipher has an expiration year value
if (fillFields.expYear && AutofillService.hasValue(card.expYear)) {
if (fillFields.expYear && card.expYear != null && AutofillService.hasValue(card.expYear)) {
let expYear: string = card.expYear;
if (fillFields.expYear.selectInfo && fillFields.expYear.selectInfo.options) {
for (let i = 0; i < fillFields.expYear.selectInfo.options.length; i++) {
@ -1275,7 +1331,7 @@ export default class AutofillService implements AutofillServiceInterface {
const colonIndex = o[1].indexOf(":");
if (colonIndex > -1 && o[1].length > colonIndex + 1) {
const val = o[1].substring(colonIndex + 2);
if (val != null && val.trim() !== "" && val === card.expYear) {
if (val.trim() !== "" && val === card.expYear) {
expYear = o[1];
break;
}
@ -1286,35 +1342,44 @@ export default class AutofillService implements AutofillServiceInterface {
fillFields.expYear.maxLength === 4
) {
if (expYear.length === 2) {
expYear = normalizeExpiryYearFormat(expYear);
const normalized = normalizeExpiryYearFormat(expYear);
if (normalized != null) {
expYear = normalized;
}
}
} else if (
this.fieldAttrsContain(fillFields.expYear, "yy") ||
fillFields.expYear.maxLength === 2
) {
if (expYear.length === 4) {
expYear = expYear.substr(2);
expYear = expYear.substring(2);
}
}
filledFields[fillFields.expYear.opid] = fillFields.expYear;
AutofillService.fillByOpid(fillScript, fillFields.expYear, expYear);
if (fillFields.expYear.opid != null) {
filledFields[fillFields.expYear.opid] = fillFields.expYear;
AutofillService.fillByOpid(fillScript, fillFields.expYear, expYear);
}
}
// There is a single expiry date field (combined values) and the cipher has both expiration month and year
if (
fillFields.exp &&
card.expMonth != null &&
AutofillService.hasValue(card.expMonth) &&
card.expYear != null &&
AutofillService.hasValue(card.expYear)
) {
const combinedExpiryFillValue = this.generateCombinedExpiryValue(card, fillFields.exp);
this.makeScriptActionWithValue(
fillScript,
combinedExpiryFillValue,
fillFields.exp,
filledFields,
);
if (combinedExpiryFillValue != null) {
this.makeScriptActionWithValue(
fillScript,
combinedExpiryFillValue,
fillFields.exp,
filledFields,
);
}
}
return fillScript;
@ -1385,7 +1450,7 @@ export default class AutofillService implements AutofillServiceInterface {
* @param {CardView} cardCipher
* @param {AutofillField} field
*/
private generateCombinedExpiryValue(cardCipher: CardView, field: AutofillField): string {
private generateCombinedExpiryValue(cardCipher: CardView, field: AutofillField): string | null {
/*
Some expectations of the passed stored card cipher view:
@ -1420,7 +1485,7 @@ export default class AutofillService implements AutofillServiceInterface {
expiryDateFormatPatterns,
] = this.getExpectedExpiryDateFormat(field);
if (expectedExpiryDateFormat) {
if (expectedExpiryDateFormat && expiryDateFormatPatterns) {
const { Month, MonthShort, Year } = expiryDateFormatPatterns;
const expiryDateDelimitersPattern = "\\" + CardExpiryDateDelimiters.join("\\");
@ -1451,6 +1516,9 @@ export default class AutofillService implements AutofillServiceInterface {
useYearFull = containsLongYearPattern.test(expectedExpiryDateFormat);
}
if (cardCipher.expMonth == null || cardCipher.expYear == null) {
return null;
}
const month = useMonthPadding
? // Ensure zero-padding
("0" + cardCipher.expMonth).slice(-2)
@ -1532,7 +1600,7 @@ export default class AutofillService implements AutofillServiceInterface {
pageDetails: AutofillPageDetails,
filledFields: { [id: string]: AutofillField },
options: GenerateFillScriptOptions,
): AutofillScript {
): AutofillScript | null {
const identity = options.cipher.identity;
if (!identity) {
return null;
@ -1645,7 +1713,8 @@ export default class AutofillService implements AutofillServiceInterface {
"password",
...AutoFillConstants.ExcludedAutofillTypes,
]) ||
AutoFillConstants.ExcludedIdentityAutocompleteTypes.has(field.autoCompleteType) ||
(field.autoCompleteType != null &&
AutoFillConstants.ExcludedIdentityAutocompleteTypes.has(field.autoCompleteType)) ||
!field.viewable
);
}
@ -1660,9 +1729,10 @@ export default class AutofillService implements AutofillServiceInterface {
const keywords: Set<string> = new Set();
for (let index = 0; index < IdentityAutoFillConstants.IdentityAttributes.length; index++) {
const attribute = IdentityAutoFillConstants.IdentityAttributes[index];
if (field[attribute]) {
const value = field[attribute];
if (value != null && typeof value === "string") {
keywords.add(
field[attribute]
value
.trim()
.toLowerCase()
.replace(/[^a-zA-Z0-9]+/g, ""),
@ -2125,12 +2195,13 @@ export default class AutofillService implements AutofillServiceInterface {
private static isSearchField(field: AutofillField) {
const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder];
for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) {
if (!matchFieldAttributeValues[attrIndex]) {
const attributeValue = matchFieldAttributeValues[attrIndex];
if (!attributeValue) {
continue;
}
// Separate camel case words and case them to lower case values
const camelCaseSeparatedFieldAttribute = matchFieldAttributeValues[attrIndex]
const camelCaseSeparatedFieldAttribute = attributeValue
.replace(/([a-z])([A-Z])/g, "$1 $2")
.toLowerCase();
// Split the attribute by non-alphabetical characters to get the keywords
@ -2151,7 +2222,7 @@ export default class AutofillService implements AutofillServiceInterface {
return true;
}
if (this.isExcludedType(field.type, excludedTypes)) {
if (field.type != null && this.isExcludedType(field.type, excludedTypes)) {
return true;
}
@ -2211,12 +2282,10 @@ export default class AutofillService implements AutofillServiceInterface {
fieldProp?: string,
) {
fieldProp = fieldProp || dataProp;
this.makeScriptActionWithValue(
fillScript,
cipherData[dataProp],
fillFields[fieldProp],
filledFields,
);
const field = fillFields[fieldProp];
if (field != null) {
this.makeScriptActionWithValue(fillScript, cipherData[dataProp], field, filledFields);
}
}
/**
@ -2348,7 +2417,8 @@ export default class AutofillService implements AutofillServiceInterface {
const testedValues = [f.htmlID, f.htmlName, f.placeholder];
for (let i = 0; i < testedValues.length; i++) {
if (AutofillService.valueIsLikePassword(testedValues[i])) {
const value = testedValues[i];
if (value != null && AutofillService.valueIsLikePassword(value)) {
return true;
}
}
@ -2396,7 +2466,7 @@ export default class AutofillService implements AutofillServiceInterface {
form.htmlName?.toLowerCase?.(),
passwordField?.htmlID?.toLowerCase?.(),
passwordField?.htmlName?.toLowerCase?.(),
].filter(Boolean);
].filter((value): value is string => typeof value === "string");
return formIdentifierValues.some((value) =>
AutoFillConstants.RegistrationKeywords.some((keyword) => value.includes(keyword)),
@ -2421,8 +2491,8 @@ export default class AutofillService implements AutofillServiceInterface {
canBeReadOnly: boolean,
withoutForm: boolean,
): AutofillField | null {
let usernameField: AutofillField = null;
let usernameFieldInSameForm: AutofillField = null;
let usernameField: AutofillField | null = null;
let usernameFieldInSameForm: AutofillField | null = null;
for (let i = 0; i < pageDetails.fields.length; i++) {
const f = pageDetails.fields[i];
@ -2486,7 +2556,7 @@ export default class AutofillService implements AutofillServiceInterface {
canBeReadOnly: boolean,
withoutForm: boolean,
): AutofillField | null {
let totpField: AutofillField = null;
let totpField: AutofillField | null = null;
for (let i = 0; i < pageDetails.fields.length; i++) {
const f = pageDetails.fields[i];
if (AutofillService.forCustomFieldsOnly(f)) {
@ -2761,8 +2831,8 @@ export default class AutofillService implements AutofillServiceInterface {
filledFields: { [id: string]: AutofillField },
fillScript: AutofillScript,
): AutofillScript {
let lastField: AutofillField = null;
let lastPasswordField: AutofillField = null;
let lastField: AutofillField | null = null;
let lastPasswordField: AutofillField | null = null;
for (const opid in filledFields) {
// eslint-disable-next-line
@ -2776,9 +2846,9 @@ export default class AutofillService implements AutofillServiceInterface {
}
// Prioritize password field over others.
if (lastPasswordField) {
if (lastPasswordField?.opid != null) {
fillScript.script.push(["focus_by_opid", lastPasswordField.opid]);
} else if (lastField) {
} else if (lastField?.opid != null) {
fillScript.script.push(["focus_by_opid", lastField.opid]);
}
@ -2886,10 +2956,10 @@ export default class AutofillService implements AutofillServiceInterface {
* @param newSettingValue - The current setting value
*/
private async handleInlineMenuVisibilitySettingsChange(
oldSettingValue: InlineMenuVisibilitySetting | boolean,
newSettingValue: InlineMenuVisibilitySetting | boolean,
oldSettingValue: InlineMenuVisibilitySetting | boolean | undefined,
newSettingValue: InlineMenuVisibilitySetting | boolean | undefined,
) {
if (oldSettingValue == null || oldSettingValue === newSettingValue) {
if (oldSettingValue == null || newSettingValue == null || oldSettingValue === newSettingValue) {
return;
}