From 56587c8c517a80fb11dda45f14e34725977a5e14 Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:25:20 +0000 Subject: [PATCH 1/2] refactor: add once option to the listeners --- src/ext/content-scripts/entry-userscripts.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/ext/content-scripts/entry-userscripts.js b/src/ext/content-scripts/entry-userscripts.js index 5721dd90..e160d7e3 100644 --- a/src/ext/content-scripts/entry-userscripts.js +++ b/src/ext/content-scripts/entry-userscripts.js @@ -25,19 +25,23 @@ function triageJS(userscript) { if (document.readyState !== "loading") { injectJS(userscript); } else { - document.addEventListener("DOMContentLoaded", () => { - injectJS(userscript); - }); + document.addEventListener( + "DOMContentLoaded", + () => injectJS(userscript), + { once: true }, + ); } } else if (runAt === "document-idle") { if (document.readyState === "complete") { injectJS(userscript); } else { - document.addEventListener("readystatechange", () => { + const handle = () => { if (document.readyState === "complete") { injectJS(userscript); + document.removeEventListener("readystatechange", handle); } - }); + }; + document.addEventListener("readystatechange", handle); } } } @@ -233,6 +237,10 @@ async function injection() { } function listeners() { + /** listen for CSP violations */ + document.addEventListener("securitypolicyviolation", cspFallback, { + once: true, + }); // listens for messages from background, popup, etc... browser.runtime.onMessage.addListener((request) => { const name = request.name; @@ -255,8 +263,6 @@ function listeners() { console.error(`Couldn't find ${filename} code!`); } }); - // listen for CSP violations - document.addEventListener("securitypolicyviolation", cspFallback); } async function initialize() { From 7e9301b00048b0eeac6266a727e07e7f4266bedc Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:33:59 +0000 Subject: [PATCH 2/2] fix: dynamically remove listeners when tab unload `browser.runtime.onMessage` listeners seems to prevent content scripts recycle from causing memory leaks --- .../content-scripts/entry-script-market.js | 23 +++++++++++++++---- src/ext/content-scripts/entry-userscripts.js | 23 +++++++++++++++---- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/ext/content-scripts/entry-script-market.js b/src/ext/content-scripts/entry-script-market.js index 67022e47..45ba17d6 100644 --- a/src/ext/content-scripts/entry-script-market.js +++ b/src/ext/content-scripts/entry-script-market.js @@ -9,10 +9,10 @@ async function injection() { ); } for (const link of links) { + if (link["href"]) url = link["href"]; link.addEventListener( "click", (e) => { - url = link["href"]; e.stopImmediatePropagation(); e.preventDefault(); browser.runtime.sendMessage({ name: "WEB_USERJS_POPUP" }); @@ -23,14 +23,27 @@ async function injection() { } async function listeners() { - browser.runtime.onMessage.addListener(async (message) => { + /** + * handle messages from background, popup, etc... + * @type {import("webextension-polyfill").Runtime.OnMessageListener} + */ + const handleMessage = async (message) => { if (import.meta.env.MODE === "development") { console.debug(message, url); } if (message === "TAB_CLICK_USERJS") { - const response = url; - url = undefined; // respond only once of click event - return response; + return url; + } + }; + /** Dynamically remove listeners to avoid memory leaks */ + if (document.visibilityState === "visible") { + browser.runtime.onMessage.addListener(handleMessage); + } + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + browser.runtime.onMessage.removeListener(handleMessage); + } else { + browser.runtime.onMessage.addListener(handleMessage); } }); } diff --git a/src/ext/content-scripts/entry-userscripts.js b/src/ext/content-scripts/entry-userscripts.js index e160d7e3..362bfacb 100644 --- a/src/ext/content-scripts/entry-userscripts.js +++ b/src/ext/content-scripts/entry-userscripts.js @@ -241,17 +241,19 @@ function listeners() { document.addEventListener("securitypolicyviolation", cspFallback, { once: true, }); - // listens for messages from background, popup, etc... - browser.runtime.onMessage.addListener((request) => { - const name = request.name; + /** + * listens for messages from background, popup, etc... + * @type {import("webextension-polyfill").Runtime.OnMessageListener} + */ + const handleMessage = (message) => { + const name = message.name; if (name === "CONTEXT_RUN") { // from bg script when context-menu item is clicked // double check to ensure context-menu scripts only run in top windows if (window !== window.top) return; - // loop through context-menu scripts saved to data object and find match // if no match found, nothing will execute and error will log - const filename = request.menuItemId; + const filename = message.menuItemId; for (let i = 0; i < data.files.menu.length; i++) { const item = data.files.menu[i]; if (item.scriptObject.filename === filename) { @@ -262,6 +264,17 @@ function listeners() { } console.error(`Couldn't find ${filename} code!`); } + }; + /** Dynamically remove listeners to avoid memory leaks */ + if (document.visibilityState === "visible") { + browser.runtime.onMessage.addListener(handleMessage); + } + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + browser.runtime.onMessage.removeListener(handleMessage); + } else { + browser.runtime.onMessage.addListener(handleMessage); + } }); }