1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-23 03:22:50 +02:00

[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:
Cesar Gonzalez 2024-09-06 15:57:12 -05:00 committed by GitHub
parent 4dbb036df1
commit 12b5ce548d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 79 additions and 67 deletions

View File

@ -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);
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<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);
}
/**

View File

@ -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(","));
}

View File

@ -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;
}