[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 AutofillPageDetails from "../models/autofill-page-details";
|
||||||
import AutofillScript from "../models/autofill-script";
|
import AutofillScript from "../models/autofill-script";
|
||||||
|
import { SubmitLoginButtonNames } from "../services/autofill-constants";
|
||||||
import { CollectAutofillContentService } from "../services/collect-autofill-content.service";
|
import { CollectAutofillContentService } from "../services/collect-autofill-content.service";
|
||||||
import DomElementVisibilityService from "../services/dom-element-visibility.service";
|
import DomElementVisibilityService from "../services/dom-element-visibility.service";
|
||||||
import { DomQueryService } from "../services/dom-query.service";
|
import { DomQueryService } from "../services/dom-query.service";
|
||||||
import InsertAutofillContentService from "../services/insert-autofill-content.service";
|
import InsertAutofillContentService from "../services/insert-autofill-content.service";
|
||||||
import { elementIsInputElement, nodeIsFormElement, sendExtensionMessage } from "../utils";
|
import {
|
||||||
|
elementIsInputElement,
|
||||||
|
getSubmitButtonKeywordsSet,
|
||||||
|
nodeIsFormElement,
|
||||||
|
sendExtensionMessage,
|
||||||
|
} from "../utils";
|
||||||
|
|
||||||
(function (globalContext) {
|
(function (globalContext) {
|
||||||
const domQueryService = new DomQueryService();
|
const domQueryService = new DomQueryService();
|
||||||
|
@ -19,17 +25,6 @@ import { elementIsInputElement, nodeIsFormElement, sendExtensionMessage } from "
|
||||||
domElementVisibilityService,
|
domElementVisibilityService,
|
||||||
collectAutofillContentService,
|
collectAutofillContentService,
|
||||||
);
|
);
|
||||||
const loginKeywords = [
|
|
||||||
"login",
|
|
||||||
"log in",
|
|
||||||
"log-in",
|
|
||||||
"signin",
|
|
||||||
"sign in",
|
|
||||||
"sign-in",
|
|
||||||
"submit",
|
|
||||||
"continue",
|
|
||||||
"next",
|
|
||||||
];
|
|
||||||
let autoSubmitLoginTimeout: number | NodeJS.Timeout;
|
let autoSubmitLoginTimeout: number | NodeJS.Timeout;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
@ -194,26 +189,41 @@ import { elementIsInputElement, nodeIsFormElement, sendExtensionMessage } from "
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
lastFieldIsPasswordInput = false,
|
lastFieldIsPasswordInput = false,
|
||||||
): boolean {
|
): boolean {
|
||||||
const genericSubmitElement = domQueryService.deepQueryElements<HTMLButtonElement>(
|
const genericSubmitElement = querySubmitButtonElement(element, "[type='submit']");
|
||||||
element,
|
if (genericSubmitElement) {
|
||||||
"[type='submit']",
|
clickSubmitElement(genericSubmitElement, lastFieldIsPasswordInput);
|
||||||
);
|
|
||||||
if (genericSubmitElement[0]) {
|
|
||||||
clickSubmitElement(genericSubmitElement[0], lastFieldIsPasswordInput);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttons = domQueryService.deepQueryElements<HTMLButtonElement>(element, "button");
|
const buttonElement = querySubmitButtonElement(element, "button, [type='button']");
|
||||||
for (let i = 0; i < buttons.length; i++) {
|
if (buttonElement) {
|
||||||
if (isLoginButton(buttons[i])) {
|
clickSubmitElement(buttonElement, lastFieldIsPasswordInput);
|
||||||
clickSubmitElement(buttons[i], lastFieldIsPasswordInput);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
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
|
* Handles clicking the submit element and optionally triggering
|
||||||
* a completion action for multistep login forms.
|
* a completion action for multistep login forms.
|
||||||
|
@ -236,21 +246,10 @@ import { elementIsInputElement, nodeIsFormElement, sendExtensionMessage } from "
|
||||||
* @param element - The element to check
|
* @param element - The element to check
|
||||||
*/
|
*/
|
||||||
function isLoginButton(element: HTMLElement) {
|
function isLoginButton(element: HTMLElement) {
|
||||||
const keywordValues = [
|
const keywordsSet = getSubmitButtonKeywordsSet(element);
|
||||||
element.textContent,
|
const keywordValues = Array.from(keywordsSet).join(",");
|
||||||
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();
|
|
||||||
|
|
||||||
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 AutofillField from "../models/autofill-field";
|
||||||
import AutofillPageDetails from "../models/autofill-page-details";
|
import AutofillPageDetails from "../models/autofill-page-details";
|
||||||
import { sendExtensionMessage } from "../utils";
|
import { getSubmitButtonKeywordsSet, sendExtensionMessage } from "../utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AutofillKeywordsMap,
|
AutofillKeywordsMap,
|
||||||
|
@ -1014,34 +1014,7 @@ export class InlineMenuFieldQualificationService
|
||||||
*/
|
*/
|
||||||
private getSubmitButtonKeywords(element: HTMLElement): string {
|
private getSubmitButtonKeywords(element: HTMLElement): string {
|
||||||
if (!this.submitButtonKeywordsMap.has(element)) {
|
if (!this.submitButtonKeywordsMap.has(element)) {
|
||||||
const keywords = [
|
const keywordsSet = getSubmitButtonKeywordsSet(element);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.submitButtonKeywordsMap.set(element, Array.from(keywordsSet).join(","));
|
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