diff --git a/apps/browser/src/autofill/content/auto-submit-login.ts b/apps/browser/src/autofill/content/auto-submit-login.ts index 19ffac61bc..ab7f09f804 100644 --- a/apps/browser/src/autofill/content/auto-submit-login.ts +++ b/apps/browser/src/autofill/content/auto-submit-login.ts @@ -2,11 +2,17 @@ import { EVENTS } from "@bitwarden/common/autofill/constants"; import AutofillPageDetails from "../models/autofill-page-details"; import AutofillScript from "../models/autofill-script"; +import { SubmitLoginButtonNames } from "../services/autofill-constants"; import { CollectAutofillContentService } from "../services/collect-autofill-content.service"; import DomElementVisibilityService from "../services/dom-element-visibility.service"; import { DomQueryService } from "../services/dom-query.service"; import InsertAutofillContentService from "../services/insert-autofill-content.service"; -import { elementIsInputElement, nodeIsFormElement, sendExtensionMessage } from "../utils"; +import { + elementIsInputElement, + getSubmitButtonKeywordsSet, + nodeIsFormElement, + sendExtensionMessage, +} from "../utils"; (function (globalContext) { const domQueryService = new DomQueryService(); @@ -19,17 +25,6 @@ import { elementIsInputElement, nodeIsFormElement, sendExtensionMessage } from " domElementVisibilityService, collectAutofillContentService, ); - const loginKeywords = [ - "login", - "log in", - "log-in", - "signin", - "sign in", - "sign-in", - "submit", - "continue", - "next", - ]; let autoSubmitLoginTimeout: number | NodeJS.Timeout; init(); @@ -194,26 +189,41 @@ import { elementIsInputElement, nodeIsFormElement, sendExtensionMessage } from " element: HTMLElement, lastFieldIsPasswordInput = false, ): boolean { - const genericSubmitElement = domQueryService.deepQueryElements( - element, - "[type='submit']", - ); - if (genericSubmitElement[0]) { - clickSubmitElement(genericSubmitElement[0], lastFieldIsPasswordInput); + const genericSubmitElement = querySubmitButtonElement(element, "[type='submit']"); + if (genericSubmitElement) { + clickSubmitElement(genericSubmitElement, lastFieldIsPasswordInput); return true; } - const buttons = domQueryService.deepQueryElements(element, "button"); - for (let i = 0; i < buttons.length; i++) { - if (isLoginButton(buttons[i])) { - clickSubmitElement(buttons[i], lastFieldIsPasswordInput); - return true; - } + const buttonElement = querySubmitButtonElement(element, "button, [type='button']"); + if (buttonElement) { + clickSubmitElement(buttonElement, lastFieldIsPasswordInput); + return true; } return false; } + /** + * Queries the element for a submit button element. If an element is found and has keywords + * that indicate a login action, the element is returned. + * + * @param element - The element to query for submit buttons + * @param selector - The selector to query for submit buttons + */ + function querySubmitButtonElement(element: HTMLElement, selector: string) { + const submitButtonElements = domQueryService.deepQueryElements( + element, + selector, + ); + for (let index = 0; index < submitButtonElements.length; index++) { + const submitElement = submitButtonElements[index]; + if (isLoginButton(submitElement)) { + return submitElement; + } + } + } + /** * Handles clicking the submit element and optionally triggering * a completion action for multistep login forms. @@ -236,21 +246,10 @@ import { elementIsInputElement, nodeIsFormElement, sendExtensionMessage } from " * @param element - The element to check */ function isLoginButton(element: HTMLElement) { - const keywordValues = [ - element.textContent, - element.getAttribute("value"), - element.getAttribute("aria-label"), - element.getAttribute("aria-labelledby"), - element.getAttribute("aria-describedby"), - element.getAttribute("title"), - element.getAttribute("id"), - element.getAttribute("name"), - element.getAttribute("class"), - ] - .join(",") - .toLowerCase(); + const keywordsSet = getSubmitButtonKeywordsSet(element); + const keywordValues = Array.from(keywordsSet).join(","); - return loginKeywords.some((keyword) => keywordValues.includes(keyword)); + return SubmitLoginButtonNames.some((keyword) => keywordValues.indexOf(keyword) > -1); } /** diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts index 089c48cd23..67bf94e5cb 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts @@ -1,6 +1,6 @@ import AutofillField from "../models/autofill-field"; import AutofillPageDetails from "../models/autofill-page-details"; -import { sendExtensionMessage } from "../utils"; +import { getSubmitButtonKeywordsSet, sendExtensionMessage } from "../utils"; import { AutofillKeywordsMap, @@ -1014,34 +1014,7 @@ export class InlineMenuFieldQualificationService */ private getSubmitButtonKeywords(element: HTMLElement): string { if (!this.submitButtonKeywordsMap.has(element)) { - const keywords = [ - element.textContent, - element.getAttribute("value"), - element.getAttribute("aria-label"), - element.getAttribute("aria-labelledby"), - element.getAttribute("aria-describedby"), - element.getAttribute("title"), - element.getAttribute("id"), - element.getAttribute("name"), - element.getAttribute("class"), - ]; - - const keywordsSet = new Set(); - for (let i = 0; i < keywords.length; i++) { - if (typeof keywords[i] === "string") { - keywords[i] - .toLowerCase() - .replace(/[-\s]/g, "") - .replace(/[^a-zA-Z0-9]+/g, "|") - .split("|") - .forEach((keyword) => { - if (keyword) { - keywordsSet.add(keyword); - } - }); - } - } - + const keywordsSet = getSubmitButtonKeywordsSet(element); this.submitButtonKeywordsMap.set(element, Array.from(keywordsSet).join(",")); } diff --git a/apps/browser/src/autofill/utils/index.ts b/apps/browser/src/autofill/utils/index.ts index 98e58a022e..7c18e7fd12 100644 --- a/apps/browser/src/autofill/utils/index.ts +++ b/apps/browser/src/autofill/utils/index.ts @@ -360,3 +360,43 @@ export function throttle(callback: (_args: any) => any, limit: number) { } }; } + +/** + * Gathers and normalizes keywords from a potential submit button element. Used + * to verify if the element submits a login or change password form. + * + * @param element - The element to gather keywords from. + */ +export function getSubmitButtonKeywordsSet(element: HTMLElement): Set { + const keywords = [ + element.textContent, + element.getAttribute("type"), + element.getAttribute("value"), + element.getAttribute("aria-label"), + element.getAttribute("aria-labelledby"), + element.getAttribute("aria-describedby"), + element.getAttribute("title"), + element.getAttribute("id"), + element.getAttribute("name"), + element.getAttribute("class"), + ]; + + const keywordsSet = new Set(); + for (let i = 0; i < keywords.length; i++) { + if (typeof keywords[i] === "string") { + // Iterate over all keywords metadata and split them by non-letter characters. + // This ensures we check against individual words and not the entire string. + keywords[i] + .toLowerCase() + .replace(/[-\s]/g, "") + .split(/[^\p{L}]+/gu) + .forEach((keyword) => { + if (keyword) { + keywordsSet.add(keyword); + } + }); + } + } + + return keywordsSet; +}