[PM-11689] Incorporate refined submit button capture logic within auto-submit login content script (#10909)
* [PM-11689] Incorporate refined submit button capture logic within auto-submit login content script * [PM-11689] Refactoring implementation to better unify logic * [PM-11689] Adjusting regex value in order to avoid iterating over element unnecessarily * [PM-11689] Adjusting regex value in order to avoid iterating over element unnecessarily * [PM-11689] Adjusting regex value in order to avoid iterating over element unnecessarily
This commit is contained in:
parent
4dbb036df1
commit
12b5ce548d
|
@ -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<HTMLButtonElement>(
|
||||
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<HTMLButtonElement>(element, "button");
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
if (isLoginButton(buttons[i])) {
|
||||
clickSubmitElement(buttons[i], lastFieldIsPasswordInput);
|
||||
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<HTMLButtonElement>(
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<string>();
|
||||
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(","));
|
||||
}
|
||||
|
||||
|
|
|
@ -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<string> {
|
||||
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<string>();
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue