glide: drop stale browser listeners after :config_reload
Assisted-by: Claude Opus 5.5 via Claude Code
diff --git a/.config/glide/glide.ts b/.config/glide/glide.ts
index 47ae2c96..3b3d103e 100644
--- a/.config/glide/glide.ts
+++ b/.config/glide/glide.ts
@@ -16,6 +16,32 @@
//
// Try typing `glide.` and see what you can do!
+// :config_reload doesn't remove browser.* listeners, so old ones keep running
+// on their old config's state. A listener added with `listen` removes itself
+// once a newer config has loaded. Defined before any include, since
+// glide.include evaluates files asynchronously.
+const CONFIG_GENERATION_PREF = "dev.kejadlen.config_generation";
+const configGeneration = `${Date.now()}-${Math.random()}`;
+glide.prefs.set(CONFIG_GENERATION_PREF, configGeneration);
+
+function listen<T extends (...args: any[]) => any, P extends unknown[]>(
+ event: { addListener(callback: T, ...params: P): void; removeListener(callback: T): void },
+ listener: T,
+ ...params: P
+): void {
+ const guarded = ((...args: Parameters<T>) => {
+ if (glide.prefs.get(CONFIG_GENERATION_PREF) !== configGeneration) {
+ event.removeListener(guarded);
+ return;
+ }
+ return listener(...args);
+ }) as T;
+ event.addListener(guarded, ...params);
+}
+
+// utils.glide.ts fills in the rest once it's included.
+glide.g["dev.kejadlen"] = { listen } as DevKejadlen;
+
glide.include("hints.glide.ts");
glide.include("pihole.glide.ts");
glide.include("splits.glide.ts");
@@ -126,7 +152,8 @@ async function moveToContainer(
}
}
-browser.webRequest.onBeforeRequest.addListener(
+listen(
+ browser.webRequest.onBeforeRequest,
(details) => {
const url = new URL(details.url);
@@ -161,7 +188,8 @@ browser.webRequest.onBeforeRequest.addListener(
// strip tracking params (rewrites the request before it fires, no reload)
// forEach avoids Symbol.iterator, which Xray vision denies on cross-compartment values.
const trackingParams = ["utm_*", "uta_*", "fbclid", "gclid"];
-browser.webRequest.onBeforeRequest.addListener(
+listen(
+ browser.webRequest.onBeforeRequest,
(details) => {
const url = new URL(details.url);
const toDelete: string[] = [];
@@ -181,7 +209,8 @@ browser.webRequest.onBeforeRequest.addListener(
);
// doi -> sci-hub — intercept at network level before the redirect fires
-browser.webRequest.onBeforeRequest.addListener(
+listen(
+ browser.webRequest.onBeforeRequest,
(details) => {
return { redirectUrl: `https://sci-hub.st/${details.url}` };
},
diff --git a/.config/glide/utils.glide.ts b/.config/glide/utils.glide.ts
index 7ce7d93b..37ac20e2 100644
--- a/.config/glide/utils.glide.ts
+++ b/.config/glide/utils.glide.ts
@@ -30,23 +30,27 @@ glide.styles.add(`
}
`);
-interface Utils {
- /** Show a transient notification overlay in the browser UI. */
- notify(text: string): void;
-}
-
declare global {
+ interface DevKejadlen {
+ /** Show a transient notification overlay in the browser UI. */
+ notify(text: string): void;
+ /** Add a browser.* listener that removes itself after :config_reload; defined in glide.ts. */
+ listen<T extends (...args: any[]) => any, P extends unknown[]>(
+ event: { addListener(callback: T, ...params: P): void; removeListener(callback: T): void },
+ listener: T,
+ ...params: P
+ ): void;
+ }
+
interface GlideGlobals {
- "dev.kejadlen"?: Utils;
+ "dev.kejadlen"?: DevKejadlen;
}
}
// Keymap and excmd callbacks run in the main process, where `document`
// is the browser UI itself; `DOM` is main-process-only by design.
-glide.g["dev.kejadlen"] = {
- notify(text: string): void {
- const el = DOM.create_element("div", { className: "glide-notification", textContent: text });
- document.documentElement.appendChild(el);
- setTimeout(() => el.remove(), 2000);
- },
+glide.g["dev.kejadlen"]!.notify = (text: string): void => {
+ const el = DOM.create_element("div", { className: "glide-notification", textContent: text });
+ document.documentElement.appendChild(el);
+ setTimeout(() => el.remove(), 2000);
};