From 6129ca536686cfceb385e9b87f6c569f0ecf9558 Mon Sep 17 00:00:00 2001 From: Miles Blackwood Date: Mon, 8 Sep 2025 14:28:40 -0400 Subject: [PATCH] Lets shadow DOM check signal page update (#16114) * Removes overprotective check, signal fn intent, ensure proper scope of callback. * Lets shadow DOM check be called dyanmically; triggers page detail update. * Restores behavior of using static value to reduce calls to shadow query. * Restores check page contains shadow DOM on init. --- .../abstractions/dom-query.service.ts | 2 +- .../collect-autofill-content.service.spec.ts | 31 ++++++++++++++++++- .../collect-autofill-content.service.ts | 13 ++++++-- .../services/dom-query.service.spec.ts | 2 -- .../autofill/services/dom-query.service.ts | 9 ++---- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/apps/browser/src/autofill/services/abstractions/dom-query.service.ts b/apps/browser/src/autofill/services/abstractions/dom-query.service.ts index da7354403e..3280957322 100644 --- a/apps/browser/src/autofill/services/abstractions/dom-query.service.ts +++ b/apps/browser/src/autofill/services/abstractions/dom-query.service.ts @@ -6,5 +6,5 @@ export interface DomQueryService { mutationObserver?: MutationObserver, forceDeepQueryAttempt?: boolean, ): T[]; - checkPageContainsShadowDom(): void; + checkPageContainsShadowDom(): boolean; } diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts index f0aa9c1c44..1e6c38bdce 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts @@ -395,7 +395,7 @@ describe("CollectAutofillContentService", () => { }); }); - it("sets the noFieldsFond property to true if the page has no forms or fields", async function () { + it("sets the noFieldsFound property to true if the page has no forms or fields", async function () { document.body.innerHTML = ""; collectAutofillContentService["noFieldsFound"] = false; jest.spyOn(collectAutofillContentService as any, "buildAutofillFormsData"); @@ -2649,4 +2649,33 @@ describe("CollectAutofillContentService", () => { ); }); }); + + describe("processMutations", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + + it("will require an update to page details if shadow DOM is present", () => { + jest + .spyOn(domQueryService as any, "checkPageContainsShadowDom") + .mockImplementationOnce(() => true); + + collectAutofillContentService["requirePageDetailsUpdate"] = jest.fn(); + + collectAutofillContentService["mutationsQueue"] = [[], []]; + + collectAutofillContentService["processMutations"](); + + jest.runOnlyPendingTimers(); + + expect(domQueryService.checkPageContainsShadowDom).toHaveBeenCalled(); + expect(collectAutofillContentService["mutationsQueue"]).toHaveLength(0); + expect(collectAutofillContentService["requirePageDetailsUpdate"]).toHaveBeenCalled(); + }); + }); }); diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.ts index 570a8010ec..0629621fad 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -996,6 +996,13 @@ export class CollectAutofillContentService implements CollectAutofillContentServ * within an idle callback to help with performance and prevent excessive updates. */ private processMutations = () => { + // If the page contains shadow DOM, we require a page details update from the autofill service. + // Will wait for an idle moment on main thread to execute, unless timeout has passed. + requestIdleCallbackPolyfill( + () => this.domQueryService.checkPageContainsShadowDom() && this.requirePageDetailsUpdate(), + { timeout: 500 }, + ); + const queueLength = this.mutationsQueue.length; for (let queueIndex = 0; queueIndex < queueLength; queueIndex++) { @@ -1018,13 +1025,13 @@ export class CollectAutofillContentService implements CollectAutofillContentServ * Triggers several flags that indicate that a collection of page details should * occur again on a subsequent call after a mutation has been observed in the DOM. */ - private flagPageDetailsUpdateIsRequired() { + private requirePageDetailsUpdate = () => { this.domRecentlyMutated = true; if (this.autofillOverlayContentService) { this.autofillOverlayContentService.pageDetailsUpdateRequired = true; } this.noFieldsFound = false; - } + }; /** * Processes all mutation records encountered by the mutation observer. @@ -1052,7 +1059,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ (this.isAutofillElementNodeMutated(mutation.removedNodes, true) || this.isAutofillElementNodeMutated(mutation.addedNodes)) ) { - this.flagPageDetailsUpdateIsRequired(); + this.requirePageDetailsUpdate(); return; } diff --git a/apps/browser/src/autofill/services/dom-query.service.spec.ts b/apps/browser/src/autofill/services/dom-query.service.spec.ts index 53862aef73..87645c98a4 100644 --- a/apps/browser/src/autofill/services/dom-query.service.spec.ts +++ b/apps/browser/src/autofill/services/dom-query.service.spec.ts @@ -72,7 +72,6 @@ describe("DomQueryService", () => { }); it("queries form field elements that are nested within multiple ShadowDOM elements", () => { - domQueryService["pageContainsShadowDom"] = true; const root = document.createElement("div"); const shadowRoot1 = root.attachShadow({ mode: "open" }); const root2 = document.createElement("div"); @@ -95,7 +94,6 @@ describe("DomQueryService", () => { }); it("will fallback to using the TreeWalker API if a depth larger than 4 ShadowDOM elements is encountered", () => { - domQueryService["pageContainsShadowDom"] = true; const root = document.createElement("div"); const shadowRoot1 = root.attachShadow({ mode: "open" }); const root2 = document.createElement("div"); diff --git a/apps/browser/src/autofill/services/dom-query.service.ts b/apps/browser/src/autofill/services/dom-query.service.ts index d3da86a9e3..b681e8e9fb 100644 --- a/apps/browser/src/autofill/services/dom-query.service.ts +++ b/apps/browser/src/autofill/services/dom-query.service.ts @@ -79,8 +79,9 @@ export class DomQueryService implements DomQueryServiceInterface { /** * Checks if the page contains any shadow DOM elements. */ - checkPageContainsShadowDom = (): void => { + checkPageContainsShadowDom = (): boolean => { this.pageContainsShadowDom = this.queryShadowRoots(globalThis.document.body, true).length > 0; + return this.pageContainsShadowDom; }; /** @@ -109,7 +110,7 @@ export class DomQueryService implements DomQueryServiceInterface { ): T[] { let elements = this.queryElements(root, queryString); - const shadowRoots = this.recursivelyQueryShadowRoots(root); + const shadowRoots = this.pageContainsShadowDom ? this.recursivelyQueryShadowRoots(root) : []; for (let index = 0; index < shadowRoots.length; index++) { const shadowRoot = shadowRoots[index]; elements = elements.concat(this.queryElements(shadowRoot, queryString)); @@ -152,10 +153,6 @@ export class DomQueryService implements DomQueryServiceInterface { root: Document | ShadowRoot | Element, depth: number = 0, ): ShadowRoot[] { - if (!this.pageContainsShadowDom) { - return []; - } - if (depth >= MAX_DEEP_QUERY_RECURSION_DEPTH) { throw new Error("Max recursion depth reached"); }