From 3dbbc452cd2ccef3605b0bc7a531225b26868aab Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sun, 4 Oct 2026 19:15:11 +0200 Subject: [PATCH] fix(theseus): an installed app can be closed and removed; Settings lists apps An app whose page has a beforeunload handler (CoinSpectrum) could not be closed: tabs answer the page's "stay?" request, app windows had nothing listening, and Electron reads silence as a veto. Closing the window is now always the user's call; a reload or navigation inside the app asks, as a tab does. Removing it looked dead for a related reason. The confirmation was drawn as Theseus's sheet in the main window, where the user was not looking (or nowhere, with the main window closed), and the removal then closed the app window with the same call the page vetoes. The question is now asked on the app window that asked, and removal destroys the window. Installed apps were only reachable from the address-bar chip while on the site. Settings gets an Apps page, at theseus://settings/apps, that lists them with Open and Remove and follows installs, removals and open windows. The three list calls it uses are settings-only now. --- main.js | 9 +++++++-- settings-preload.js | 5 +++++ settings.html | 45 +++++++++++++++++++++++++++++++++++++++++++++ webapps.js | 45 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/main.js b/main.js index ead3e359..65984578 100644 --- a/main.js +++ b/main.js @@ -889,6 +889,7 @@ const SETTINGS_ONLY = new Set([ "password-setup", "password-status", "password-unlock", "password-update", "recheck-update", "remove-from-list", "set-engine-enabled", "set-engine-order", "settings-open-panel", "settings-section", "tor-state", + "webapps-list", "webapps-open", "webapps-remove", "vault-pin-clear", "vault-pin-set", "vault-pin-status", "vault-pin-unlock", // Raw seeds and WIFs. No page uses these (add-ons go through the // vaultImports shim in main), but an unguarded handler is reachable by any @@ -5993,7 +5994,7 @@ ipcMain.handle("webapp-prompt", async (e) => { const r = await installWebAppFromTab(tab); return r && r.ok ? "accepted" : "dismissed"; }); -ipcMain.handle("webapps-list", () => webapps.list().map((a) => ({ key: a.key, name: a.name, host: a.host, startUrl: a.startUrl, installedAt: a.installedAt }))); +ipcMain.handle("webapps-list", () => webapps.list().map((a) => ({ key: a.key, name: a.name, host: a.host, startUrl: a.startUrl, installedAt: a.installedAt, icon: webapps.iconDataUrl(a), open: webapps.isOpen(a.key) }))); ipcMain.handle("webapps-open", (_e, key) => { const a = webapps.find(String(key || "")); if (a) webapps.open(a); return !!a; }); ipcMain.handle("webapps-remove", (_e, key) => webapps.uninstall(String(key || ""), true).then((ok) => { emitTabs(); return ok; })); // Only the catalog site may ask, and only from its top frame. Any page used @@ -8694,7 +8695,11 @@ if (!process.env.THESEUS_NO_AUTOSTART && !app.requestSingleInstanceLock()) { openInTab: openInMainTab, targetUrlFor, isBnsHost, prepareContents: (wc) => { styleScrollbars(wc); try { wc.setWebRTCIPHandlingPolicy(webrtcPolicy()); } catch {} try { wc.setBackgroundThrottling(settings.backgroundThrottle); } catch {} applyFingerprint(wc); }, - changed: () => emitTabs(), + changed: () => { + try { emitTabs(); } catch {} + // Settings › Apps re-reads its list when an app is installed, removed, opened or closed. + for (const t of tabs) if (t.settings) { try { t.view.webContents.send("webapps-changed"); } catch {} } + }, }); // Kick off signed add-on update polling 30 s after boot so it never // slows launch. Any staged update lands in /addons-updates- diff --git a/settings-preload.js b/settings-preload.js index 97fd9503..2f217317 100644 --- a/settings-preload.js +++ b/settings-preload.js @@ -92,6 +92,11 @@ contextBridge.exposeInMainWorld("cfg", { // Manual "Check for updates" — un-dismisses any existing chip and re- // fetches the release manifest. Returns { updateAvailable, currentVersion }. recheckUpdate: () => ipcRenderer.invoke("recheck-update"), + // Sites installed as apps (Settings › Apps). + webapps: () => ipcRenderer.invoke("webapps-list"), + webappOpen: (key) => ipcRenderer.invoke("webapps-open", String(key || "")), + webappRemove: (key) => ipcRenderer.invoke("webapps-remove", String(key || "")), + onWebappsChanged: (cb) => ipcRenderer.on("webapps-changed", () => cb()), appVersion: () => ipcRenderer.invoke("app-version"), restartApp: () => ipcRenderer.invoke("app-restart"), }); diff --git a/settings.html b/settings.html index b0bd696f..de69bef6 100644 --- a/settings.html +++ b/settings.html @@ -116,6 +116,13 @@ .ariadne-sources .asrc-row .astate.err{color:#f6768a} .ariadne-sources input[type=checkbox]{accent-color:var(--acid);width:18px;height:18px;cursor:pointer} .pmuted{color:var(--mut)} + /* Settings › Apps */ + .app{display:flex;align-items:center;gap:12px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:10px 14px;margin-bottom:8px} + .app img,.app .ph{width:32px;height:32px;border-radius:7px;flex:none;display:grid;place-items:center;font-size:20px} + .app .nm{flex:1;min-width:0} .app .nm b{display:block;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} + .app .nm span{color:var(--mut);font-size:12.5px} + .app .btn.rm{background:transparent;color:#f6768a;border-color:rgba(246,118,138,.4)} + .app .btn.rm:hover{background:rgba(246,118,138,.12)} .ceng{display:flex;align-items:center;gap:8px;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:6px 10px;margin-bottom:6px;font-size:13px} .ceng .cs{font-size:14px;flex:none} /* engine checklist */ @@ -265,6 +272,7 @@ Privacy Plug-ins Extensions + Apps
@@ -851,6 +859,12 @@

Cookie pop-ups left alone

+