diff --git a/main.js b/main.js index 2a85c416..70a68622 100644 --- a/main.js +++ b/main.js @@ -9576,6 +9576,11 @@ if (!process.env.THESEUS_NO_AUTOSTART && !app.requestSingleInstanceLock()) { if (typeof ses.registerPreloadScript === "function") { // Electron ≥ 35: setPreloads is deprecated in favour of per-script registration. for (const filePath of [bcnrPreload, injectPreload, tidPreload]) ses.registerPreloadScript({ type: "frame", filePath }); + // No unread counts on the taskbar icon: sites lose navigator.setAppBadge + // in pages and in their service workers (see no-badge-preload.js). + const noBadge = path.join(__dirname, "no-badge-preload.js"); + ses.registerPreloadScript({ type: "frame", filePath: noBadge }); + try { ses.registerPreloadScript({ type: "service-worker", filePath: noBadge }); } catch (err) { console.warn("[badge] service-worker preload:", err?.message ?? err); } } else { const existing = ses.getPreloads(); const wanted = [bcnrPreload, injectPreload, tidPreload].filter((p) => !existing.includes(p)); diff --git a/no-badge-preload.js b/no-badge-preload.js new file mode 100644 index 00000000..73eb14e0 --- /dev/null +++ b/no-badge-preload.js @@ -0,0 +1,27 @@ +// no-badge-preload.js — session-wide preload (frames and service workers) +// that removes the web Badging API before any site code runs. +// +// navigator.setAppBadge(n) makes Electron draw n over the app's taskbar icon. +// Every site wrote that same single number, so the icon said "40" with no way +// to tell whose 40, and Theseus shows no unread counts on its icon at all. +// Deleting the methods (rather than making them do nothing) lets a site see a +// browser without badges and fall back to its own title or favicon counter. +// The Chromium switch --disable-blink-features=Badging no longer removes the +// API, which is why this runs as a preload. Electron runs no preload in an +// about:blank or srcdoc iframe, so a page could still reach the API through +// one; sites set their counts from the page or its service worker, which is +// what this covers. +const { contextBridge } = require("electron"); + +try { + contextBridge.executeInMainWorld({ + func: () => { + for (const ctor of [globalThis.Navigator, globalThis.WorkerNavigator]) { + if (!ctor) continue; + for (const name of ["setAppBadge", "clearAppBadge"]) { + try { delete ctor.prototype[name]; } catch {} + } + } + }, + }); +} catch {} diff --git a/package.json b/package.json index 2452ac19..98661815 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "messages.html", "messages-preload.js", "bcnr-preload.js", + "no-badge-preload.js", "bcnr-origin.js", "webapps.js", "js-dialog.html",