1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-28 04:08:47 +02: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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 120 additions and 11 deletions

View File

@ -2051,6 +2051,83 @@ describe("CollectAutofillContentService", () => {
collectAutofillContentService["handleAutofillElementAttributeMutation"],
).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", () => {

View File

@ -303,7 +303,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
element.opid = `__${index}`;
const existingAutofillField = this.autofillFieldElements.get(element);
if (existingAutofillField) {
if (index >= 0 && existingAutofillField) {
existingAutofillField.opid = element.opid;
existingAutofillField.elementNumber = index;
this.autofillFieldElements.set(element, existingAutofillField);
@ -325,7 +325,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
};
if (element instanceof HTMLSpanElement) {
this.autofillFieldElements.set(element, autofillFieldBase);
this.cacheAutofillFieldElement(index, element, autofillFieldBase);
this.autofillOverlayContentService?.setupAutofillOverlayListenerOnField(
element,
autofillFieldBase,
@ -366,11 +366,31 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
"data-stripe": this.getPropertyOrAttribute(element, "data-stripe"),
};
this.autofillFieldElements.set(element, autofillField);
this.cacheAutofillFieldElement(index, element, autofillField);
this.autofillOverlayContentService?.setupAutofillOverlayListenerOnField(element, 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
* the value of the attribute if it is not set to "off".
@ -987,7 +1007,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
}
let isElementMutated = false;
const mutatedElements = [];
const mutatedElements: Node[] = [];
for (let index = 0; index < nodes.length; index++) {
const node = nodes[index];
if (!(node instanceof HTMLElement)) {
@ -1010,17 +1030,31 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
}
}
for (let elementIndex = 0; elementIndex < mutatedElements.length; elementIndex++) {
const node = mutatedElements[elementIndex];
if (isRemovingNodes) {
if (isRemovingNodes) {
for (let elementIndex = 0; elementIndex < mutatedElements.length; elementIndex++) {
const node = mutatedElements[elementIndex];
this.deleteCachedAutofillElement(
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 (
this.autofillOverlayContentService &&
this.isNodeFormFieldElement(node) &&
!this.autofillFieldElements.get(node as ElementWithOpId<FormFieldElement>)
) {
@ -1029,8 +1063,6 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
this.buildAutofillFieldItem(node as ElementWithOpId<FormFieldElement>, -1);
}
}
return isElementMutated;
}
/**