fix(theseus): closing the browser window must not leave tabs talking to a destroyed window
Tabs keep emitting events while the window is torn down: a hovered
link fires update-target-url, which positioned the link-status pill
against win.getContentBounds() on a destroyed window ("Object has been
destroyed", 2026-09-27). With installed web apps the browser window can
now close while their windows keep the process alive, so this stops
being a quit-time blip and becomes a normal state.
The overlay helpers and layout() now check the window is alive, the
session is captured on close (the quit-time save no longer overwrites it
with an empty list once the tabs are gone), and "closed" drops every
reference to the window's views. A later createWindow() starts from a
clean tab list, so a page opened from an app window after the browser
window was closed brings the window back with the restored session.
This commit is contained in:
parent
729930f0b5
commit
0774235486
1 changed files with 27 additions and 7 deletions
34
main.js
34
main.js
|
|
@ -1009,7 +1009,9 @@ const sessionFile = () => path.join(app.getPath("userData"), "session.json");
|
|||
// v2 format: { v: 2, urls, active }. v1 was a bare array of URLs; reading one
|
||||
// maps to active = last tab, which is what the old restore loop ended up
|
||||
// showing (each createTab activated itself, so the rightmost tab won).
|
||||
let sessionSavedAtClose = false;
|
||||
function saveSession() {
|
||||
if (sessionSavedAtClose && !winAlive()) return; // already captured when the window closed
|
||||
try {
|
||||
const live = tabs.filter((t) => !t.settings && t.url);
|
||||
const active = Math.max(0, live.findIndex((t) => t.id === activeId));
|
||||
|
|
@ -1702,6 +1704,8 @@ a.btn{display:inline-block;margin-top:16px;padding:9px 16px;border-radius:999px;
|
|||
|
||||
// ---- window + tabs ----
|
||||
let win, chrome;
|
||||
// The window can be gone while tabs and app windows still fire events.
|
||||
const winAlive = () => !!win && !win.isDestroyed();
|
||||
let CHROME_H = 84; // grows when an extra bar (Tor notice / BCNR offer) is shown
|
||||
// Site-info popover: a floating overlay VIEW on top of the page content, so it
|
||||
// never pushes the page down. Positioned under the address-bar badge on demand.
|
||||
|
|
@ -2398,7 +2402,7 @@ function pushNav(prov) {
|
|||
}
|
||||
|
||||
function layout() {
|
||||
if (!win) return;
|
||||
if (!winAlive()) return;
|
||||
const { width, height } = win.getContentBounds();
|
||||
chrome.setBounds({ x: 0, y: 0, width, height: CHROME_H });
|
||||
const bodyH = Math.max(0, height - CHROME_H);
|
||||
|
|
@ -2471,19 +2475,19 @@ function showDownloads(show) {
|
|||
} else { downloadsPop.setVisible(false); dlVisible = false; }
|
||||
}
|
||||
function positionAddressPicker() {
|
||||
if (!addressPicker) return;
|
||||
if (!addressPicker || !winAlive()) return;
|
||||
const { width } = win.getContentBounds();
|
||||
const x = Math.max(6, Math.min(apPos.x, width - apW - 6));
|
||||
addressPicker.setBounds({ x, y: apPos.y, width: apW, height: apH });
|
||||
}
|
||||
function positionPwFill() {
|
||||
if (!pwFillPop) return;
|
||||
if (!pwFillPop || !winAlive()) return;
|
||||
const { width } = win.getContentBounds();
|
||||
const x = Math.max(6, Math.min(pwfPos.x, width - PWF_W - 6));
|
||||
pwFillPop.setBounds({ x, y: pwfPos.y, width: PWF_W, height: pwfH });
|
||||
}
|
||||
function positionLinkStatus() {
|
||||
if (!linkStatus || !win) return;
|
||||
if (!linkStatus || !winAlive()) return;
|
||||
const { width, height } = win.getContentBounds();
|
||||
// The pill may grow to (almost) the full width of the tab area — long
|
||||
// URLs stay readable — and ellipsises past that. It never runs under
|
||||
|
|
@ -2494,7 +2498,7 @@ function positionLinkStatus() {
|
|||
linkStatus.setBounds({ x: 0, y: Math.max(0, height - h), width: w, height: h });
|
||||
}
|
||||
function showLinkStatus(url) {
|
||||
if (!linkStatus) return;
|
||||
if (!linkStatus || !winAlive()) return;
|
||||
const s = String(url || "");
|
||||
if (!s) {
|
||||
if (linkStatusVisible) { linkStatus.setVisible(false); linkStatusVisible = false; }
|
||||
|
|
@ -2802,8 +2806,8 @@ function emitTabs() {
|
|||
collapsedGroups: [...tabGroupCollapsed],
|
||||
url: t?.url || "",
|
||||
loading: !!t?.loading,
|
||||
canBack: wc ? wc.navigationHistory.canGoBack() : false,
|
||||
canForward: wc ? wc.navigationHistory.canGoForward() : false,
|
||||
canBack: (() => { try { return !!wc && wc.navigationHistory.canGoBack(); } catch { return false; } })(),
|
||||
canForward: (() => { try { return !!wc && wc.navigationHistory.canGoForward(); } catch { return false; } })(),
|
||||
zoom: zoomPercent(t),
|
||||
webapp: webapps.chipState(t),
|
||||
});
|
||||
|
|
@ -3279,6 +3283,11 @@ function closeTab(id) {
|
|||
function createWindow() {
|
||||
chromeReadyDone = false;
|
||||
overlaysLoaded = false;
|
||||
sessionSavedAtClose = false;
|
||||
if (tabs.length) { // leftovers from a window that closed while app windows kept the process alive
|
||||
for (const t of tabs.splice(0)) { try { t.view.webContents.destroy?.(); } catch {} }
|
||||
activeId = null;
|
||||
}
|
||||
// Window ground + chrome view ground both match chrome.html's --bg for the
|
||||
// active theme. The chrome view used to sit on Chromium's default white
|
||||
// until chrome.html painted, which is the "white strip over a dark
|
||||
|
|
@ -3382,6 +3391,17 @@ function createWindow() {
|
|||
chrome.webContents.once("dom-ready", onChromeReady);
|
||||
setTimeout(onChromeReady, 8000);
|
||||
win.on("resize", layout);
|
||||
// The browser window can close while app windows (webapps.js) keep the
|
||||
// process alive. Capture the session while the tabs are still here, then
|
||||
// let go of the window's views: tab events (hover → update-target-url,
|
||||
// title updates) keep arriving during teardown and used to reach the
|
||||
// destroyed window through positionLinkStatus (2026-09-27).
|
||||
win.on("close", () => { saveSession(); sessionSavedAtClose = true; });
|
||||
win.on("closed", () => {
|
||||
win = null; chrome = null; popover = null; enginePicker = null; downloadsPop = null;
|
||||
addressPicker = null; pwFillPop = null; linkStatus = null; sidebar = null; approvalPop = null;
|
||||
linkStatusVisible = false;
|
||||
});
|
||||
layout();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue