1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 14:53:33 +00:00

[PM-4923] Form elements that fade into view contain incorrectly cached page details (#6953)

* [PM-4923] Form Elements that Fade into View Contain Incorrectly Cached Page Details

* [PM-4923] Form Elements that Fade into View Contain Incorrectly Cached Page Details

* [PM-4923] Running prettier on implementation
This commit is contained in:
Cesar Gonzalez
2023-12-07 16:23:42 -06:00
committed by GitHub
parent 51c5e053f7
commit dafb251cac
2 changed files with 120 additions and 11 deletions

View File

@@ -2051,6 +2051,83 @@ describe("CollectAutofillContentService", () => {
collectAutofillContentService["handleAutofillElementAttributeMutation"], collectAutofillContentService["handleAutofillElementAttributeMutation"],
).not.toBeCalled(); ).not.toBeCalled();
}); });
it("will setup the overlay listeners on mutated elements", async () => {
jest.useFakeTimers();
const form = document.createElement("form");
document.body.appendChild(form);
const addedNodes = document.querySelectorAll("form");
const removedNodes = document.querySelectorAll("li");
const mutationRecord: MutationRecord = {
type: "childList",
addedNodes: addedNodes,
attributeName: null,
attributeNamespace: null,
nextSibling: null,
oldValue: null,
previousSibling: null,
removedNodes: removedNodes,
target: document.body,
};
collectAutofillContentService["domRecentlyMutated"] = false;
collectAutofillContentService["noFieldsFound"] = true;
collectAutofillContentService["currentLocationHref"] = window.location.href;
jest.spyOn(collectAutofillContentService as any, "setupOverlayListenersOnMutatedElements");
collectAutofillContentService["handleMutationObserverMutation"]([mutationRecord]);
jest.runAllTimers();
expect(collectAutofillContentService["setupOverlayListenersOnMutatedElements"]).toBeCalled();
});
});
describe("setupOverlayListenersOnMutatedElements", () => {
it("skips building the autofill field item if the node is not a form field element", () => {
const divElement = document.createElement("div");
const nodes = [divElement];
jest.spyOn(collectAutofillContentService as any, "buildAutofillFieldItem");
collectAutofillContentService["setupOverlayListenersOnMutatedElements"](nodes);
expect(collectAutofillContentService["buildAutofillFieldItem"]).not.toBeCalled();
});
it("skips building the autofill field item if the node is already a field element", () => {
const inputElement = document.createElement("input") as ElementWithOpId<HTMLInputElement>;
inputElement.setAttribute("type", "password");
const nodes = [inputElement];
collectAutofillContentService["autofillFieldElements"].set(inputElement, {
opid: "1234",
} as AutofillField);
jest.spyOn(collectAutofillContentService as any, "buildAutofillFieldItem");
collectAutofillContentService["setupOverlayListenersOnMutatedElements"](nodes);
expect(collectAutofillContentService["buildAutofillFieldItem"]).not.toBeCalled();
});
it("builds the autofill field item to ensure the overlay listeners are set", () => {
document.body.innerHTML = `
<form>
<label for="username-id">Username Label</label>
<input type="text" id="username-id">
</form>
`;
const inputElement = document.getElementById(
"username-id",
) as ElementWithOpId<HTMLInputElement>;
inputElement.setAttribute("type", "password");
const nodes = [inputElement];
jest.spyOn(collectAutofillContentService as any, "buildAutofillFieldItem");
collectAutofillContentService["setupOverlayListenersOnMutatedElements"](nodes);
expect(collectAutofillContentService["buildAutofillFieldItem"]).toBeCalledWith(
inputElement,
-1,
);
});
}); });
describe("deleteCachedAutofillElement", () => { describe("deleteCachedAutofillElement", () => {

View File

@@ -303,7 +303,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
element.opid = `__${index}`; element.opid = `__${index}`;
const existingAutofillField = this.autofillFieldElements.get(element); const existingAutofillField = this.autofillFieldElements.get(element);
if (existingAutofillField) { if (index >= 0 && existingAutofillField) {
existingAutofillField.opid = element.opid; existingAutofillField.opid = element.opid;
existingAutofillField.elementNumber = index; existingAutofillField.elementNumber = index;
this.autofillFieldElements.set(element, existingAutofillField); this.autofillFieldElements.set(element, existingAutofillField);
@@ -325,7 +325,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
}; };
if (element instanceof HTMLSpanElement) { if (element instanceof HTMLSpanElement) {
this.autofillFieldElements.set(element, autofillFieldBase); this.cacheAutofillFieldElement(index, element, autofillFieldBase);
this.autofillOverlayContentService?.setupAutofillOverlayListenerOnField( this.autofillOverlayContentService?.setupAutofillOverlayListenerOnField(
element, element,
autofillFieldBase, autofillFieldBase,
@@ -366,11 +366,31 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
"data-stripe": this.getPropertyOrAttribute(element, "data-stripe"), "data-stripe": this.getPropertyOrAttribute(element, "data-stripe"),
}; };
this.autofillFieldElements.set(element, autofillField); this.cacheAutofillFieldElement(index, element, autofillField);
this.autofillOverlayContentService?.setupAutofillOverlayListenerOnField(element, autofillField); this.autofillOverlayContentService?.setupAutofillOverlayListenerOnField(element, autofillField);
return autofillField; return autofillField;
}; };
/**
* Caches the autofill field element and its data.
* Will not cache the element if the index is less than 0.
*
* @param index - The index of the autofill field element
* @param element - The autofill field element to cache
* @param autofillFieldData - The autofill field data to cache
*/
private cacheAutofillFieldElement(
index: number,
element: ElementWithOpId<FormFieldElement>,
autofillFieldData: AutofillField,
) {
if (index < 0) {
return;
}
this.autofillFieldElements.set(element, autofillFieldData);
}
/** /**
* Identifies the autocomplete attribute associated with an element and returns * Identifies the autocomplete attribute associated with an element and returns
* the value of the attribute if it is not set to "off". * the value of the attribute if it is not set to "off".
@@ -987,7 +1007,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
} }
let isElementMutated = false; let isElementMutated = false;
const mutatedElements = []; const mutatedElements: Node[] = [];
for (let index = 0; index < nodes.length; index++) { for (let index = 0; index < nodes.length; index++) {
const node = nodes[index]; const node = nodes[index];
if (!(node instanceof HTMLElement)) { if (!(node instanceof HTMLElement)) {
@@ -1010,17 +1030,31 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
} }
} }
for (let elementIndex = 0; elementIndex < mutatedElements.length; elementIndex++) { if (isRemovingNodes) {
const node = mutatedElements[elementIndex]; for (let elementIndex = 0; elementIndex < mutatedElements.length; elementIndex++) {
if (isRemovingNodes) { const node = mutatedElements[elementIndex];
this.deleteCachedAutofillElement( this.deleteCachedAutofillElement(
node as ElementWithOpId<HTMLFormElement> | ElementWithOpId<FormFieldElement>, node as ElementWithOpId<HTMLFormElement> | ElementWithOpId<FormFieldElement>,
); );
continue;
} }
} else if (this.autofillOverlayContentService) {
setTimeout(() => this.setupOverlayListenersOnMutatedElements(mutatedElements), 1000);
}
return isElementMutated;
}
/**
* Sets up the overlay listeners on the passed mutated elements. This ensures
* that the overlay can appear on elements that are injected into the DOM after
* the initial page load.
*
* @param mutatedElements - HTML elements that have been mutated
*/
private setupOverlayListenersOnMutatedElements(mutatedElements: Node[]) {
for (let elementIndex = 0; elementIndex < mutatedElements.length; elementIndex++) {
const node = mutatedElements[elementIndex];
if ( if (
this.autofillOverlayContentService &&
this.isNodeFormFieldElement(node) && this.isNodeFormFieldElement(node) &&
!this.autofillFieldElements.get(node as ElementWithOpId<FormFieldElement>) !this.autofillFieldElements.get(node as ElementWithOpId<FormFieldElement>)
) { ) {
@@ -1029,8 +1063,6 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
this.buildAutofillFieldItem(node as ElementWithOpId<FormFieldElement>, -1); this.buildAutofillFieldItem(node as ElementWithOpId<FormFieldElement>, -1);
} }
} }
return isElementMutated;
} }
/** /**