From c3f771ac1cf44f1c23bb8477c86194386681e246 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sun, 2 Aug 2026 15:21:21 +0200 Subject: [PATCH] Theseus: fix collision blank-page + broken-BCDN-open bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related bugs both caused by the tab's will-navigate handler racing with programmatic loads: Bug A (chip switch BCDN -> ICANN shows blank page, BCDN-priority mode): fallbackToWeb() calls webContents.loadURL('https:///') to serve the ICANN version. That fired will-navigate, which saw a dotted host, ran isBnsHost() -> true, and RE-INVOKED navigateTab() recursively — but the transient='icann' override had already been consumed, so the recursive call fell back to bcnr-first, canceling the fallback mid-flight. Tab showed blank because both loads collided. Fix: mark programmatic loads with t.internalNav so will-navigate skips them. Bug B ('Ask each time' -> Open BCDN doesn't load): Was going through a meta-refresh from bns://collision-choose/ to bns:///?_collision=bcnr. The meta-refresh bypassed navigateTab, so the chrome/prov state was never updated (address bar, badges stayed stale). Fix: will-navigate now catches bns://collision-choose/ FIRST, applies the remember flag, sets t.collisionOverride, and routes via navigateTab so chrome + prov update correctly. Removed the serveBns collision-choose handler + the ?_collision URL-param path in loadBns (both dead now that will-navigate handles it). Result: - Soft-mode 'Open with...' -> Open BCDN loads the BCDN site cleanly. - Chip switcher flips BCDN <-> ICANN with no blank flash, correct chrome. --- main.js | 58 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/main.js b/main.js index 8426a2e..fbfb09d 100644 --- a/main.js +++ b/main.js @@ -532,34 +532,8 @@ async function serveBns(request) { const host = url.hostname.toLowerCase(); const reqPath = decodeURIComponent(url.pathname) || "/"; - // Special host: apply a soft-mode "Open with…" choice submitted by - // collision.html, then redirect the tab to the actual target. Persistent - // memory (byName/byTld) is written HERE; the one-shot BCDN choice rides - // along via a ?_collision=bcnr param that loadBns consumes. - if (host === "collision-choose") { - const p = url.searchParams; - const target = String(p.get("host") || "").toLowerCase(); - const cTld = String(p.get("tld") || "").toLowerCase(); - const cChoice = p.get("choice"); - const cRem = p.get("remember") || "no"; - const rest = p.get("resturl") || "/"; - if (cChoice === "bcnr" || cChoice === "icann") { - rememberCollision(target, cTld, cChoice, cRem); - } - let dest; - if (cChoice === "icann") { - dest = "https://" + target + rest; - } else { - // BCNR/BCDN: bounce back through the bns:// resolver with a one-shot - // marker so loadBns won't re-prompt for this specific load. - const sep = rest.includes("?") ? "&" : "?"; - dest = "bns://" + target + rest + sep + "_collision=bcnr"; - } - const html = `Opening…` + - `` + - `Opening ${target}…`; - return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } }); - } + // (bns://collision-choose/ is handled by the will-navigate listener attached + // to each tab — it fires BEFORE the request reaches this protocol handler.) let rec = entries.get(host); if (!rec) { try { await resolveHost(host); } catch {} rec = entries.get(host); } @@ -778,7 +752,30 @@ function createTab(initial, opts = {}) { wc.on("did-stop-loading", () => setLoading(tab, false)); wc.on("will-navigate", (e, u) => { try { + // Skip our own programmatic loads. fallbackToWeb calls loadURL("https:///") + // and that host is often a BCNR-registered dotted name — without this guard, + // isBnsHost() would send us right back into navigateTab, canceling the + // fallback (blank-page bug 2026-08-02). + if (tab.internalNav) return; const parsed = new URL(u); + // Intercept the collision-choose posted by the in-tab "Open with…" page, + // apply the remember flag, set a one-shot transient override so loadBns + // doesn't re-prompt, and route via navigateTab so chrome/prov stay in sync. + if (parsed.protocol === "bns:" && parsed.hostname === "collision-choose") { + e.preventDefault(); + const p = parsed.searchParams; + const target = String(p.get("host") || "").toLowerCase(); + const cTld = String(p.get("tld") || "").toLowerCase(); + const cChoice = p.get("choice"); + const cRem = p.get("remember") || "no"; + const rest = p.get("resturl") || "/"; + if (!target) return; + if (cChoice === "bcnr" || cChoice === "icann") { + rememberCollision(target, cTld, cChoice, cRem); + tab.collisionOverride = cChoice; // one-shot, consumed by loadBns + } + return navigateTab(id, target + rest); + } if (parsed.protocol === "bns:") return; if (isBnsHost(parsed.hostname)) { e.preventDefault(); navigateTab(id, parsed.hostname + parsed.pathname); } } catch {} @@ -901,7 +898,10 @@ async function loadBns(t, id, host, rest, tld) { if (id === activeId) pushNav({ host, kind: "resolving", tld, registry }); const fallbackToWeb = async (reason) => { t.url = "https://" + host + (rest === "/" ? "" : rest); - try { await t.view.webContents.loadURL(t.url); } catch {} + t.internalNav = true; + try { await t.view.webContents.loadURL(t.url); } + catch (e) { console.warn("fallback loadURL failed:", e?.message); } + finally { t.internalNav = false; } t.prov = { host, kind: "web", note: `fallback: ${reason}`, tld, registry }; if (id === activeId) pushNav(t.prov); emitTabs();