Skip to content

Commit

Permalink
[PM-4923] Form elements that fade into view contain incorrectly cache…
Browse files Browse the repository at this point in the history
…d 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
  • Loading branch information
cagonzalezcs authored Dec 7, 2023
1 parent 51c5e05 commit dafb251
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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".
Expand Down Expand Up @@ -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)) {
Expand All @@ -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>)
) {
Expand All @@ -1029,8 +1063,6 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
this.buildAutofillFieldItem(node as ElementWithOpId<FormFieldElement>, -1);
}
}

return isElementMutated;
}

/**
Expand Down

0 comments on commit dafb251

Please sign in to comment.