1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 03:33:30 +00:00

add teardown of listeners/observers

This commit is contained in:
Jonathan Prusik
2026-01-23 15:32:54 -05:00
parent fe1410bed3
commit a33b2e6888
5 changed files with 64 additions and 16 deletions

View File

@@ -72,21 +72,24 @@ class AutofillInit implements AutofillInitInterface {
* to act on the page.
*/
private collectPageDetailsOnLoad() {
const sendCollectDetailsMessage = () => {
this.clearCollectPageDetailsOnLoadTimeout();
this.collectPageDetailsOnLoadTimeout = setTimeout(
() => this.sendExtensionMessage("bgCollectPageDetails", { sender: "autofillInit" }),
750,
);
};
if (globalThis.document.readyState === "complete") {
sendCollectDetailsMessage();
this.sendCollectDetailsMessage();
}
globalThis.addEventListener(EVENTS.LOAD, sendCollectDetailsMessage);
globalThis.addEventListener(EVENTS.LOAD, this.sendCollectDetailsMessage);
}
/**
* Sends a message to collect page details after a short delay.
*/
private sendCollectDetailsMessage = () => {
this.clearCollectPageDetailsOnLoadTimeout();
this.collectPageDetailsOnLoadTimeout = setTimeout(
() => this.sendExtensionMessage("bgCollectPageDetails", { sender: "autofillInit" }),
750,
);
};
/**
* Collects the page details and sends them to the
* extension background script. If the `sendDetailsInResponse`
@@ -218,6 +221,7 @@ class AutofillInit implements AutofillInitInterface {
*/
destroy() {
this.clearCollectPageDetailsOnLoadTimeout();
globalThis.removeEventListener(EVENTS.LOAD, this.sendCollectDetailsMessage);
chrome.runtime.onMessage.removeListener(this.handleExtensionMessage);
this.collectAutofillContentService.destroy();
this.autofillOverlayContentService?.destroy();

View File

@@ -32,4 +32,5 @@ export type BackgroundPortMessageHandlers = {
export interface AutofillInlineMenuIframeService {
initMenuIframe(): void;
destroy(): void;
}

View File

@@ -41,7 +41,9 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
globalThis.navigator.userAgent.indexOf(" Firefox/") !== -1 ||
globalThis.navigator.userAgent.indexOf(" Gecko/") !== -1;
private buttonElement?: HTMLElement;
private buttonIframe?: AutofillInlineMenuButtonIframe;
private listElement?: HTMLElement;
private listIframe?: AutofillInlineMenuListIframe;
private htmlMutationObserver: MutationObserver;
private bodyMutationObserver: MutationObserver;
private inlineMenuElementsMutationObserver: MutationObserver;
@@ -264,18 +266,19 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
if (this.isFirefoxBrowser) {
this.buttonElement = globalThis.document.createElement("div");
this.buttonElement.setAttribute("popover", "manual");
new AutofillInlineMenuButtonIframe(this.buttonElement);
this.buttonIframe = new AutofillInlineMenuButtonIframe(this.buttonElement);
return this.buttonElement;
}
const customElementName = this.generateRandomCustomElementName();
const self = this;
globalThis.customElements?.define(
customElementName,
class extends HTMLElement {
constructor() {
super();
new AutofillInlineMenuButtonIframe(this);
self.buttonIframe = new AutofillInlineMenuButtonIframe(this);
}
},
);
@@ -293,18 +296,19 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
if (this.isFirefoxBrowser) {
this.listElement = globalThis.document.createElement("div");
this.listElement.setAttribute("popover", "manual");
new AutofillInlineMenuListIframe(this.listElement);
this.listIframe = new AutofillInlineMenuListIframe(this.listElement);
return this.listElement;
}
const customElementName = this.generateRandomCustomElementName();
const self = this;
globalThis.customElements?.define(
customElementName,
class extends HTMLElement {
constructor() {
super();
new AutofillInlineMenuListIframe(this);
self.listIframe = new AutofillInlineMenuListIframe(this);
}
},
);
@@ -778,5 +782,13 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
this.closeInlineMenu();
this.clearPersistentLastChildOverrideTimeout();
this.unobservePageAttributes();
this.unobserveCustomElements();
this.unobserveContainerElement();
if (this.mutationObserverIterationsResetTimeout) {
clearTimeout(this.mutationObserverIterationsResetTimeout);
this.mutationObserverIterationsResetTimeout = null;
}
this.buttonIframe?.destroy();
this.listIframe?.destroy();
}
}

View File

@@ -1,6 +1,8 @@
import { AutofillInlineMenuIframeService } from "./autofill-inline-menu-iframe.service";
export class AutofillInlineMenuIframeElement {
private autofillInlineMenuIframeService: AutofillInlineMenuIframeService;
constructor(
element: HTMLElement,
portName: string,
@@ -12,14 +14,14 @@ export class AutofillInlineMenuIframeElement {
const shadow: ShadowRoot = element.attachShadow({ mode: "closed" });
shadow.prepend(style);
const autofillInlineMenuIframeService = new AutofillInlineMenuIframeService(
this.autofillInlineMenuIframeService = new AutofillInlineMenuIframeService(
shadow,
portName,
initStyles,
iframeTitle,
ariaAlert,
);
autofillInlineMenuIframeService.initMenuIframe();
this.autofillInlineMenuIframeService.initMenuIframe();
}
/**
@@ -67,4 +69,11 @@ export class AutofillInlineMenuIframeElement {
return style;
}
/**
* Cleans up the iframe service to prevent memory leaks.
*/
destroy() {
this.autofillInlineMenuIframeService?.destroy();
}
}

View File

@@ -555,4 +555,26 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe
return false;
}
/**
* Cleans up all event listeners, timeouts, and observers to prevent memory leaks.
*/
destroy() {
this.iframe?.removeEventListener(EVENTS.LOAD, this.setupPortMessageListener);
this.clearAriaAlert();
this.clearFadeInTimeout();
if (this.delayedCloseTimeout) {
clearTimeout(this.delayedCloseTimeout);
this.delayedCloseTimeout = null;
}
if (this.mutationObserverIterationsResetTimeout) {
clearTimeout(this.mutationObserverIterationsResetTimeout);
this.mutationObserverIterationsResetTimeout = null;
}
this.unobserveIframe();
this.port?.onMessage.removeListener(this.handlePortMessage);
this.port?.onDisconnect.removeListener(this.handlePortDisconnect);
this.port?.disconnect();
this.port = null;
}
}