diff --git a/src/ext/content-scripts/entry-userscripts.js b/src/ext/content-scripts/entry-userscripts.js index 13ea2027..f3a48317 100644 --- a/src/ext/content-scripts/entry-userscripts.js +++ b/src/ext/content-scripts/entry-userscripts.js @@ -24,19 +24,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); } } } @@ -222,17 +226,23 @@ async function injection() { } function listeners() { - // listens for messages from background, popup, etc... - browser.runtime.onMessage.addListener((request) => { - const name = request.name; + /** listen for CSP violations */ + document.addEventListener("securitypolicyviolation", cspFallback, { + once: true, + }); + /** + * 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) { @@ -243,9 +253,18 @@ 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); + } }); - // listen for CSP violations - document.addEventListener("securitypolicyviolation", cspFallback); } async function initialize() { diff --git a/xcode/xcconfig/Userscripts-Release.xcconfig b/xcode/xcconfig/Userscripts-Release.xcconfig index c82a5cee..d80d5fbc 100644 --- a/xcode/xcconfig/Userscripts-Release.xcconfig +++ b/xcode/xcconfig/Userscripts-Release.xcconfig @@ -1,8 +1,8 @@ #include "Userscripts-Base.xcconfig" INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2018–2025 Justin Wasack. All rights reserved. -IOS_APP_VERSION = 1.8.1 -MAC_APP_VERSION = 4.8.1 +IOS_APP_VERSION = 1.8.2 +MAC_APP_VERSION = 4.8.2 // Distribution CODE_SIGN_STYLE = Manual