fix(theseus/boot): paint the toolbar first — stop gating startup on chrome.html's load event
Users saw a blank window with a white strip across the top for seconds on launch. Root cause: every part of startup, including session restore, waited for chrome.html's did-finish-load. That event also waits for the page's subresources, and the bookmarks bar loads its favicons over bns:// — a BNS lookup plus a network fetch each — so a slow link held the whole boot. On top of that, seven hidden overlay renderers, every restored tab, the BNS index build and three network fetches all started in the same tick and stalled the main thread ~1 s while the toolbar tried to paint. - Continue boot at chrome.html's dom-ready (toolbar scripts have run, IPC listeners exist) instead of did-finish-load; 8 s fallback timer. - Window and chrome view get the toolbar's --bg for the active theme so the pre-paint frame is never white. - Overlay pages (site info, engine picker, downloads, suggestions, password fill, link status, approval) load 250 ms after the toolbar or on first use; the approval modal awaits its page so a dapp request can't hang. - Session restore is staggered: active tab first, then one background tab per 150 ms slotted into its saved strip position. Session file v2 records the active index; v1 arrays still load (active = last, as the old loop effectively did). - AddonHost gains api.whenUiReady(); Aegis 0.6.2 defers its heavy dependency loading (noble precompute, bitcoinjs, libauth, WizardConnect) behind it. - BNS snapshot warm-up still starts right after createWindow (bookmark favicons need it); Sia refresh, update check and home-card fetch move to the post-paint phase. Measured on a clone of the real profile with nine restored tabs: toolbar usable at ~0.7 s instead of ~1.5 s, main-thread stall during toolbar load down from ~1.1 s to ~0.2 s.
This commit is contained in:
parent
bf6bcfade2
commit
c9a3db26ce
4 changed files with 187 additions and 43 deletions
|
|
@ -200,6 +200,19 @@ class AddonHost {
|
||||||
// manifest gate.
|
// manifest gate.
|
||||||
this._captureTab = typeof captureTab === "function" ? captureTab : null;
|
this._captureTab = typeof captureTab === "function" ? captureTab : null;
|
||||||
this._saveCapture = typeof saveCapture === "function" ? saveCapture : null;
|
this._saveCapture = typeof saveCapture === "function" ? saveCapture : null;
|
||||||
|
// api.whenUiReady() plumbing — see signalUiReady().
|
||||||
|
this._uiReady = false;
|
||||||
|
this._uiReadyWaiters = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// main calls this once the browser chrome has painted. Add-ons that pull
|
||||||
|
// in heavy dependencies (Aegis: noble curve precompute, bitcoinjs, libauth,
|
||||||
|
// WizardConnect) gate that work on api.whenUiReady() so module evaluation
|
||||||
|
// doesn't land on the main thread while chrome.html is still trying to
|
||||||
|
// paint. Sticky: a later discoverAndActivate() resolves immediately.
|
||||||
|
signalUiReady() {
|
||||||
|
this._uiReady = true;
|
||||||
|
for (const resolve of this._uiReadyWaiters.splice(0)) { try { resolve(); } catch {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureDirs() {
|
ensureDirs() {
|
||||||
|
|
@ -432,6 +445,10 @@ class AddonHost {
|
||||||
if (!this._openSettings) throw new Error("openSettings unavailable (host not wired)");
|
if (!this._openSettings) throw new Error("openSettings unavailable (host not wired)");
|
||||||
this._openSettings(typeof section === "string" ? section : "");
|
this._openSettings(typeof section === "string" ? section : "");
|
||||||
},
|
},
|
||||||
|
// Resolves once the browser chrome has painted (immediately if it
|
||||||
|
// already has). Put expensive dependency loading behind this so it
|
||||||
|
// never competes with the first frame at launch.
|
||||||
|
whenUiReady: () => (this._uiReady ? Promise.resolve() : new Promise((resolve) => this._uiReadyWaiters.push(resolve))),
|
||||||
// Panel ↔ activate() messaging. Panels (and, for page-inject add-ons,
|
// Panel ↔ activate() messaging. Panels (and, for page-inject add-ons,
|
||||||
// injected page bridges) call into the add-on with a message name +
|
// injected page bridges) call into the add-on with a message name +
|
||||||
// one JSON payload; the handler's return value goes back as the
|
// one JSON payload; the handler's return value goes back as the
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "aegis",
|
"id": "aegis",
|
||||||
"name": "Aegis Wallet",
|
"name": "Aegis Wallet",
|
||||||
"version": "0.6.1",
|
"version": "0.6.2",
|
||||||
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash on .x sites; window.tronWeb / window.tronLink / window.ethereum / window.solana on any https page.",
|
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash on .x sites; window.tronWeb / window.tronLink / window.ethereum / window.solana on any https page.",
|
||||||
"author": "Silent Mode",
|
"author": "Silent Mode",
|
||||||
"icon": "data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none'%3E%3Cpolygon points='16,2 28,9 28,23 16,30 4,23 4,9' fill='%230a0a0d' stroke='%23D6FF3D' stroke-width='1.6' stroke-linejoin='round'/%3E%3Ccircle cx='16' cy='16' r='4.5' fill='none' stroke='%23D6FF3D' stroke-width='1.4'/%3E%3Ccircle cx='16' cy='16' r='1.6' fill='%23D6FF3D'/%3E%3C/svg%3E",
|
"icon": "data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none'%3E%3Cpolygon points='16,2 28,9 28,23 16,30 4,23 4,9' fill='%230a0a0d' stroke='%23D6FF3D' stroke-width='1.6' stroke-linejoin='round'/%3E%3Ccircle cx='16' cy='16' r='4.5' fill='none' stroke='%23D6FF3D' stroke-width='1.4'/%3E%3Ccircle cx='16' cy='16' r='1.6' fill='%23D6FF3D'/%3E%3C/svg%3E",
|
||||||
|
|
|
||||||
|
|
@ -1873,7 +1873,12 @@ module.exports = {
|
||||||
// Restore the user's opt-in choice from storage. Off by default so a
|
// Restore the user's opt-in choice from storage. Off by default so a
|
||||||
// fresh install never hits CoinGecko without asking.
|
// fresh install never hits CoinGecko without asking.
|
||||||
if (api.storage.get("pricesEnabled", false)) c.priceFeed.setEnabled(true).catch(() => {});
|
if (api.storage.get("pricesEnabled", false)) c.priceFeed.setEnabled(true).catch(() => {});
|
||||||
loadDeps(api).then((d) => {
|
// The deps are heavy to evaluate (noble curve precompute, bitcoinjs,
|
||||||
|
// libauth, WizardConnect) and that all happens on the main thread. Wait
|
||||||
|
// for the browser chrome to paint so the cost never delays Theseus's
|
||||||
|
// first frame; older hosts without whenUiReady load immediately.
|
||||||
|
const uiReady = typeof api.whenUiReady === "function" ? api.whenUiReady() : Promise.resolve();
|
||||||
|
uiReady.then(() => loadDeps(api)).then((d) => {
|
||||||
if (ctx !== c) return;
|
if (ctx !== c) return;
|
||||||
c.d = d;
|
c.d = d;
|
||||||
// Start the WizardConnect manager once deps are ready. Per-wallet
|
// Start the WizardConnect manager once deps are ready. Per-wallet
|
||||||
|
|
|
||||||
204
main.js
204
main.js
|
|
@ -761,13 +761,113 @@ function applyClientHintsSpoof() {
|
||||||
}
|
}
|
||||||
// ---- session restore + background throttling ----
|
// ---- session restore + background throttling ----
|
||||||
const sessionFile = () => path.join(app.getPath("userData"), "session.json");
|
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).
|
||||||
function saveSession() {
|
function saveSession() {
|
||||||
try { fs.writeFileSync(sessionFile(), JSON.stringify(tabs.filter((t) => !t.settings && t.url).map((t) => t.url))); }
|
try {
|
||||||
catch (e) { console.error("session save failed:", e.message); }
|
const live = tabs.filter((t) => !t.settings && t.url);
|
||||||
|
const active = Math.max(0, live.findIndex((t) => t.id === activeId));
|
||||||
|
fs.writeFileSync(sessionFile(), JSON.stringify({ v: 2, urls: live.map((t) => t.url), active }));
|
||||||
|
} catch (e) { console.error("session save failed:", e.message); }
|
||||||
}
|
}
|
||||||
function loadSession() {
|
function loadSession() {
|
||||||
try { if (fs.existsSync(sessionFile())) return JSON.parse(fs.readFileSync(sessionFile(), "utf8")); } catch {}
|
try {
|
||||||
return [];
|
if (!fs.existsSync(sessionFile())) return { urls: [], active: 0 };
|
||||||
|
const raw = JSON.parse(fs.readFileSync(sessionFile(), "utf8"));
|
||||||
|
const list = Array.isArray(raw) ? raw : (raw && Array.isArray(raw.urls) ? raw.urls : []);
|
||||||
|
const urls = list.filter((u) => typeof u === "string" && u);
|
||||||
|
const last = Math.max(urls.length - 1, 0);
|
||||||
|
const active = Array.isArray(raw) ? last : Math.min(Math.max(Number(raw.active) || 0, 0), last);
|
||||||
|
return { urls, active };
|
||||||
|
} catch { return { urls: [], active: 0 }; }
|
||||||
|
}
|
||||||
|
// Session restore, staggered. Creating every saved tab in one synchronous
|
||||||
|
// burst meant N renderer processes launching on top of chrome.html's first
|
||||||
|
// paint — on a slow or busy machine the window sat blank for seconds. The
|
||||||
|
// active tab is created first (so the user's page is on screen right away)
|
||||||
|
// and the rest arrive one per RESTORE_STAGGER_MS as background tabs, slotted
|
||||||
|
// into their saved strip positions.
|
||||||
|
const RESTORE_STAGGER_MS = 150;
|
||||||
|
function restoreTabs() {
|
||||||
|
const saved = settings.restoreSession ? loadSession() : { urls: [], active: 0 };
|
||||||
|
if (!saved.urls.length) { createTab(); return; }
|
||||||
|
createTab(saved.urls[saved.active]);
|
||||||
|
const rest = saved.urls.map((url, i) => ({ url, i })).filter((x) => x.i !== saved.active);
|
||||||
|
const step = () => {
|
||||||
|
if (!rest.length || !win || win.isDestroyed()) return;
|
||||||
|
const { url, i } = rest.shift();
|
||||||
|
try {
|
||||||
|
createTab(url, { background: true });
|
||||||
|
// createTab appends; move the new tab to its saved slot. Lower indices
|
||||||
|
// are created first, so `i` is the right position on both sides of
|
||||||
|
// the active tab.
|
||||||
|
const t = tabs.pop();
|
||||||
|
tabs.splice(Math.min(i, tabs.length), 0, t);
|
||||||
|
emitTabs();
|
||||||
|
} catch (e) { console.warn("session restore: tab failed:", e?.message); }
|
||||||
|
setTimeout(step, RESTORE_STAGGER_MS);
|
||||||
|
};
|
||||||
|
setTimeout(step, RESTORE_STAGGER_MS);
|
||||||
|
}
|
||||||
|
// ---- deferred overlay loads ----
|
||||||
|
// The floating overlays (site-info popover, engine picker, downloads,
|
||||||
|
// address suggestions, password fill, link-status pill, add-on approval)
|
||||||
|
// are WebContentsViews created with the window, but their HTML used to be
|
||||||
|
// loaded in the same tick as chrome.html — seven extra renderers racing the
|
||||||
|
// toolbar for the first paint. They now load shortly after chrome reports
|
||||||
|
// did-finish-load, or on first use, whichever comes first.
|
||||||
|
const overlayLoads = []; // [{ view, file }] waiting for loadOverlays()
|
||||||
|
let overlaysLoaded = false;
|
||||||
|
function deferOverlayLoad(view, file) { overlayLoads.push({ view, file }); }
|
||||||
|
function loadOverlays() {
|
||||||
|
if (overlaysLoaded) return;
|
||||||
|
overlaysLoaded = true;
|
||||||
|
for (const { view, file } of overlayLoads.splice(0)) {
|
||||||
|
try { view.webContents.loadFile(file); } catch (e) { console.warn(`overlay load failed (${file}):`, e?.message); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Resolves once `view` has finished loading its page, loading the overlays
|
||||||
|
// first if that hasn't happened yet. Bounded so a wedged renderer can't
|
||||||
|
// hang the caller forever.
|
||||||
|
function overlayReady(view, timeoutMs = 4000) {
|
||||||
|
loadOverlays();
|
||||||
|
const wc = view.webContents;
|
||||||
|
try { if (wc.getURL() && !wc.isLoading()) return Promise.resolve(); } catch {}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const timer = setTimeout(done, timeoutMs);
|
||||||
|
function done() { clearTimeout(timer); wc.removeListener("did-finish-load", done); resolve(); }
|
||||||
|
wc.on("did-finish-load", done);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Post-paint boot. Runs once chrome.html has loaded (createWindow arms a
|
||||||
|
// fallback timer in case it never does). Everything here used to start in
|
||||||
|
// whenReady, before the toolbar had painted — the BNS index build, three
|
||||||
|
// network fetches, every restored tab and seven overlay renderers all piled
|
||||||
|
// onto the main thread in the same ~300 ms, and the window sat blank with
|
||||||
|
// a white toolbar strip until they drained.
|
||||||
|
let chromeReadyDone = false;
|
||||||
|
let bnsWarm = null; // promise from the warmFromSnapshot() kicked off in whenReady
|
||||||
|
function onChromeReady() {
|
||||||
|
if (chromeReadyDone) return;
|
||||||
|
chromeReadyDone = true;
|
||||||
|
if (addonHost) addonHost.signalUiReady();
|
||||||
|
// Tabs wait for the BNS warm-up so sharedIndex is set before the first
|
||||||
|
// restored tab navigates; the rest of the multi-source refresh follows
|
||||||
|
// (see the comment block above startBnsPolling for how the sources
|
||||||
|
// cooperate).
|
||||||
|
(bnsWarm || warmFromSnapshot().catch(() => null)).then(() => {
|
||||||
|
try { restoreTabs(); }
|
||||||
|
catch (e) { console.error("restoreTabs failed:", e?.message); try { createTab(); } catch {} }
|
||||||
|
startBnsPolling();
|
||||||
|
ensureIndex().catch(() => {}); // fallback for first launch without a bundled snapshot
|
||||||
|
});
|
||||||
|
// Re-emit any pending update notice — harmless if nothing is pending.
|
||||||
|
emitUpdateAvailable();
|
||||||
|
setTimeout(loadOverlays, 250);
|
||||||
|
refreshSnapshotFromSia().catch(() => {});
|
||||||
|
checkForUpdate().catch(() => {});
|
||||||
|
refreshRemoteHomeCards().catch(() => {});
|
||||||
}
|
}
|
||||||
function applyThrottle() {
|
function applyThrottle() {
|
||||||
for (const t of tabs) { try { t.view.webContents.setBackgroundThrottling(settings.backgroundThrottle); } catch {} }
|
for (const t of tabs) { try { t.view.webContents.setBackgroundThrottling(settings.backgroundThrottle); } catch {} }
|
||||||
|
|
@ -1960,6 +2060,7 @@ function positionPopover() {
|
||||||
function showPopover(show) {
|
function showPopover(show) {
|
||||||
if (!popover) return;
|
if (!popover) return;
|
||||||
if (show) {
|
if (show) {
|
||||||
|
loadOverlays();
|
||||||
positionPopover();
|
positionPopover();
|
||||||
// Re-add to the top of the z-order (tabs added later would otherwise cover it).
|
// Re-add to the top of the z-order (tabs added later would otherwise cover it).
|
||||||
win.contentView.removeChildView(popover);
|
win.contentView.removeChildView(popover);
|
||||||
|
|
@ -1977,6 +2078,7 @@ function positionEnginePicker() {
|
||||||
function showEnginePicker(show) {
|
function showEnginePicker(show) {
|
||||||
if (!enginePicker) return;
|
if (!enginePicker) return;
|
||||||
if (show) {
|
if (show) {
|
||||||
|
loadOverlays();
|
||||||
positionEnginePicker();
|
positionEnginePicker();
|
||||||
win.contentView.removeChildView(enginePicker);
|
win.contentView.removeChildView(enginePicker);
|
||||||
win.contentView.addChildView(enginePicker);
|
win.contentView.addChildView(enginePicker);
|
||||||
|
|
@ -1993,6 +2095,7 @@ function positionDownloads() {
|
||||||
function showDownloads(show) {
|
function showDownloads(show) {
|
||||||
if (!downloadsPop) return;
|
if (!downloadsPop) return;
|
||||||
if (show) {
|
if (show) {
|
||||||
|
loadOverlays();
|
||||||
positionDownloads();
|
positionDownloads();
|
||||||
win.contentView.removeChildView(downloadsPop);
|
win.contentView.removeChildView(downloadsPop);
|
||||||
win.contentView.addChildView(downloadsPop);
|
win.contentView.addChildView(downloadsPop);
|
||||||
|
|
@ -2067,6 +2170,7 @@ function setSidebar(show, panelId) {
|
||||||
function showPwFill(show, matches) {
|
function showPwFill(show, matches) {
|
||||||
if (!pwFillPop) return;
|
if (!pwFillPop) return;
|
||||||
if (show) {
|
if (show) {
|
||||||
|
loadOverlays();
|
||||||
positionPwFill();
|
positionPwFill();
|
||||||
win.contentView.removeChildView(pwFillPop);
|
win.contentView.removeChildView(pwFillPop);
|
||||||
win.contentView.addChildView(pwFillPop);
|
win.contentView.addChildView(pwFillPop);
|
||||||
|
|
@ -2130,6 +2234,7 @@ function showAddressPicker(show, suggestions) {
|
||||||
if (!addressPicker) return;
|
if (!addressPicker) return;
|
||||||
if (show) {
|
if (show) {
|
||||||
if (!suggestions || !suggestions.length) return showAddressPicker(false);
|
if (!suggestions || !suggestions.length) return showAddressPicker(false);
|
||||||
|
loadOverlays();
|
||||||
positionAddressPicker();
|
positionAddressPicker();
|
||||||
win.contentView.removeChildView(addressPicker);
|
win.contentView.removeChildView(addressPicker);
|
||||||
win.contentView.addChildView(addressPicker);
|
win.contentView.addChildView(addressPicker);
|
||||||
|
|
@ -2620,8 +2725,15 @@ function closeTab(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
|
chromeReadyDone = false;
|
||||||
|
overlaysLoaded = false;
|
||||||
|
// 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
|
||||||
|
// window" users saw on a slow launch.
|
||||||
|
const uiBg = nativeTheme.shouldUseDarkColors ? "#0f1420" : "#e6e8ec";
|
||||||
win = new BrowserWindow({
|
win = new BrowserWindow({
|
||||||
width: 1220, height: 840, title: "Theseus Navigator", backgroundColor: "#0f1420",
|
width: 1220, height: 840, title: "Theseus Navigator", backgroundColor: uiBg,
|
||||||
// Taskbar / titlebar icon. Packaged builds ship build/icon.ico as
|
// Taskbar / titlebar icon. Packaged builds ship build/icon.ico as
|
||||||
// extraResource; dev reads the source file directly.
|
// extraResource; dev reads the source file directly.
|
||||||
icon: app.isPackaged
|
icon: app.isPackaged
|
||||||
|
|
@ -2629,50 +2741,53 @@ function createWindow() {
|
||||||
: path.join(__dirname, "build", "icon.ico"),
|
: path.join(__dirname, "build", "icon.ico"),
|
||||||
});
|
});
|
||||||
chrome = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "preload.js") } });
|
chrome = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "preload.js") } });
|
||||||
|
try { chrome.setBackgroundColor(uiBg); } catch {}
|
||||||
win.contentView.addChildView(chrome);
|
win.contentView.addChildView(chrome);
|
||||||
styleScrollbars(chrome.webContents);
|
styleScrollbars(chrome.webContents);
|
||||||
chrome.webContents.loadFile("chrome.html");
|
chrome.webContents.loadFile("chrome.html");
|
||||||
|
// The overlays below are created now (cheap) but their pages load via
|
||||||
|
// deferOverlayLoad → loadOverlays() after chrome has painted.
|
||||||
// Floating site-info overlay (hidden until the address-bar badge is clicked).
|
// Floating site-info overlay (hidden until the address-bar badge is clicked).
|
||||||
popover = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "popover-preload.js") } });
|
popover = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "popover-preload.js") } });
|
||||||
try { popover.setBackgroundColor("#00000000"); } catch {}
|
try { popover.setBackgroundColor("#00000000"); } catch {}
|
||||||
win.contentView.addChildView(popover);
|
win.contentView.addChildView(popover);
|
||||||
styleScrollbars(popover.webContents);
|
styleScrollbars(popover.webContents);
|
||||||
popover.webContents.loadFile("popover.html");
|
deferOverlayLoad(popover, "popover.html");
|
||||||
popover.setVisible(false);
|
popover.setVisible(false);
|
||||||
// Floating engine-picker overlay (custom dropdown with real favicons).
|
// Floating engine-picker overlay (custom dropdown with real favicons).
|
||||||
enginePicker = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "engine-picker-preload.js") } });
|
enginePicker = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "engine-picker-preload.js") } });
|
||||||
try { enginePicker.setBackgroundColor("#00000000"); } catch {}
|
try { enginePicker.setBackgroundColor("#00000000"); } catch {}
|
||||||
win.contentView.addChildView(enginePicker);
|
win.contentView.addChildView(enginePicker);
|
||||||
styleScrollbars(enginePicker.webContents);
|
styleScrollbars(enginePicker.webContents);
|
||||||
enginePicker.webContents.loadFile("engine-picker.html");
|
deferOverlayLoad(enginePicker, "engine-picker.html");
|
||||||
enginePicker.setVisible(false);
|
enginePicker.setVisible(false);
|
||||||
// Floating downloads panel — shows active + recent downloads.
|
// Floating downloads panel — shows active + recent downloads.
|
||||||
downloadsPop = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "downloads-preload.js") } });
|
downloadsPop = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "downloads-preload.js") } });
|
||||||
try { downloadsPop.setBackgroundColor("#00000000"); } catch {}
|
try { downloadsPop.setBackgroundColor("#00000000"); } catch {}
|
||||||
win.contentView.addChildView(downloadsPop);
|
win.contentView.addChildView(downloadsPop);
|
||||||
styleScrollbars(downloadsPop.webContents);
|
styleScrollbars(downloadsPop.webContents);
|
||||||
downloadsPop.webContents.loadFile("downloads.html");
|
deferOverlayLoad(downloadsPop, "downloads.html");
|
||||||
downloadsPop.setVisible(false);
|
downloadsPop.setVisible(false);
|
||||||
// Floating address-bar suggestions dropdown.
|
// Floating address-bar suggestions dropdown.
|
||||||
addressPicker = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "address-picker-preload.js") } });
|
addressPicker = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "address-picker-preload.js") } });
|
||||||
try { addressPicker.setBackgroundColor("#00000000"); } catch {}
|
try { addressPicker.setBackgroundColor("#00000000"); } catch {}
|
||||||
win.contentView.addChildView(addressPicker);
|
win.contentView.addChildView(addressPicker);
|
||||||
styleScrollbars(addressPicker.webContents);
|
styleScrollbars(addressPicker.webContents);
|
||||||
addressPicker.webContents.loadFile("address-picker.html");
|
deferOverlayLoad(addressPicker, "address-picker.html");
|
||||||
addressPicker.setVisible(false);
|
addressPicker.setVisible(false);
|
||||||
// Floating password-fill picker.
|
// Floating password-fill picker.
|
||||||
pwFillPop = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "pw-fill-preload.js") } });
|
pwFillPop = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "pw-fill-preload.js") } });
|
||||||
try { pwFillPop.setBackgroundColor("#00000000"); } catch {}
|
try { pwFillPop.setBackgroundColor("#00000000"); } catch {}
|
||||||
win.contentView.addChildView(pwFillPop);
|
win.contentView.addChildView(pwFillPop);
|
||||||
styleScrollbars(pwFillPop.webContents);
|
styleScrollbars(pwFillPop.webContents);
|
||||||
pwFillPop.webContents.loadFile("pw-fill.html");
|
deferOverlayLoad(pwFillPop, "pw-fill.html");
|
||||||
pwFillPop.setVisible(false);
|
pwFillPop.setVisible(false);
|
||||||
// Link-hover status pill (bottom-left of window).
|
// Link-hover status pill (bottom-left of window).
|
||||||
linkStatus = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "link-status-preload.js") } });
|
linkStatus = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "link-status-preload.js") } });
|
||||||
try { linkStatus.setBackgroundColor("#00000000"); } catch {}
|
try { linkStatus.setBackgroundColor("#00000000"); } catch {}
|
||||||
win.contentView.addChildView(linkStatus);
|
win.contentView.addChildView(linkStatus);
|
||||||
styleScrollbars(linkStatus.webContents);
|
styleScrollbars(linkStatus.webContents);
|
||||||
linkStatus.webContents.loadFile("link-status.html");
|
deferOverlayLoad(linkStatus, "link-status.html");
|
||||||
linkStatus.setVisible(false);
|
linkStatus.setVisible(false);
|
||||||
// Add-on sidebar host. Doesn't loadFile until an add-on panel is opened —
|
// Add-on sidebar host. Doesn't loadFile until an add-on panel is opened —
|
||||||
// styleScrollbars hooks dom-ready, which fires per navigation, so every
|
// styleScrollbars hooks dom-ready, which fires per navigation, so every
|
||||||
|
|
@ -2688,16 +2803,18 @@ function createWindow() {
|
||||||
try { approvalPop.setBackgroundColor("#00000000"); } catch {}
|
try { approvalPop.setBackgroundColor("#00000000"); } catch {}
|
||||||
win.contentView.addChildView(approvalPop);
|
win.contentView.addChildView(approvalPop);
|
||||||
styleScrollbars(approvalPop.webContents);
|
styleScrollbars(approvalPop.webContents);
|
||||||
approvalPop.webContents.loadFile("approval.html");
|
deferOverlayLoad(approvalPop, "approval.html");
|
||||||
approvalPop.setVisible(false);
|
approvalPop.setVisible(false);
|
||||||
chrome.webContents.once("did-finish-load", () => {
|
// Everything that isn't needed to paint the toolbar waits for chrome.html.
|
||||||
const saved = settings.restoreSession ? loadSession() : [];
|
// dom-ready, NOT did-finish-load: the load event also waits for every
|
||||||
if (saved.length) saved.forEach((u) => createTab(u)); else createTab();
|
// subresource, and the bookmarks bar pulls its favicons over bns:// —
|
||||||
// Re-emit any pending update notice — the fetch may have completed
|
// a BNS lookup plus a network fetch each. On a slow link that held the
|
||||||
// before chrome finished loading, in which case the initial send was
|
// load event (and with it every restored tab) for seconds while the
|
||||||
// a no-op.
|
// toolbar sat blank. By dom-ready the toolbar's scripts have run and its
|
||||||
emitUpdateAvailable();
|
// IPC listeners exist, which is all the rest of boot needs. The fallback
|
||||||
});
|
// timer covers a chrome.html that fails to load at all.
|
||||||
|
chrome.webContents.once("dom-ready", onChromeReady);
|
||||||
|
setTimeout(onChromeReady, 8000);
|
||||||
win.on("resize", layout);
|
win.on("resize", layout);
|
||||||
layout();
|
layout();
|
||||||
}
|
}
|
||||||
|
|
@ -3246,17 +3363,23 @@ function pumpApproval() {
|
||||||
if (approvalCurrent || !approvalQueue.length || !approvalPop) return;
|
if (approvalCurrent || !approvalQueue.length || !approvalPop) return;
|
||||||
const next = approvalQueue.shift();
|
const next = approvalQueue.shift();
|
||||||
approvalCurrent = next;
|
approvalCurrent = next;
|
||||||
try {
|
// The overlay's page loads lazily after first paint; a dapp on a restored
|
||||||
approvalPop.webContents.send("approval-show", next.req);
|
// tab can ask for approval before that, so wait for the page rather than
|
||||||
approvalPop.setVisible(true);
|
// sending into an empty renderer (the request would silently hang).
|
||||||
try { win.contentView.removeChildView(approvalPop); win.contentView.addChildView(approvalPop); } catch {}
|
overlayReady(approvalPop).then(() => {
|
||||||
layout();
|
if (approvalCurrent !== next) return;
|
||||||
approvalPop.webContents.focus();
|
try {
|
||||||
} catch (err) {
|
approvalPop.webContents.send("approval-show", next.req);
|
||||||
approvalCurrent = null;
|
approvalPop.setVisible(true);
|
||||||
next.resolve("cancel");
|
try { win.contentView.removeChildView(approvalPop); win.contentView.addChildView(approvalPop); } catch {}
|
||||||
console.warn("[addons] approval show failed:", err?.message);
|
layout();
|
||||||
}
|
approvalPop.webContents.focus();
|
||||||
|
} catch (err) {
|
||||||
|
approvalCurrent = null;
|
||||||
|
next.resolve("cancel");
|
||||||
|
console.warn("[addons] approval show failed:", err?.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function showApprovalModal(opts, addonId) {
|
function showApprovalModal(opts, addonId) {
|
||||||
const a = addonHost && addonHost.getInstalled().find((x) => x.manifest && x.manifest.id === addonId);
|
const a = addonHost && addonHost.getInstalled().find((x) => x.manifest && x.manifest.id === addonId);
|
||||||
|
|
@ -4752,9 +4875,12 @@ if (!process.env.THESEUS_NO_AUTOSTART) {
|
||||||
createWindow();
|
createWindow();
|
||||||
// Multi-source BNS warm-up so the first .bch page opens near-instantly and
|
// Multi-source BNS warm-up so the first .bch page opens near-instantly and
|
||||||
// stays fresh for as long as the browser is running. Every source runs in
|
// stays fresh for as long as the browser is running. Every source runs in
|
||||||
// parallel — none of them can block a navigation.
|
// parallel — none of them can block a navigation. Source 1 starts now
|
||||||
|
// (chrome.html's bookmark favicons are bns:// fetches, so the index has
|
||||||
|
// to be warm by the time the toolbar asks for them); 2–4 are kicked off
|
||||||
|
// from onChromeReady() once the toolbar has painted.
|
||||||
// 1) sync: load the on-disk snapshot (user cache > bundled).
|
// 1) sync: load the on-disk snapshot (user cache > bundled).
|
||||||
// sharedIndex is set BEFORE the first navigation can even fire.
|
// sharedIndex is set BEFORE the first restored tab navigates.
|
||||||
// 2) async, continuous: startBnsPolling() opens one electrum connection
|
// 2) async, continuous: startBnsPolling() opens one electrum connection
|
||||||
// every 30 s, fetches the beacon history (one call), and only pulls
|
// every 30 s, fetches the beacon history (one call), and only pulls
|
||||||
// the tx bodies we don't already have. Merges into currentSnapshotState
|
// the tx bodies we don't already have. Merges into currentSnapshotState
|
||||||
|
|
@ -4767,20 +4893,16 @@ if (!process.env.THESEUS_NO_AUTOSTART) {
|
||||||
// 4) fallback: ensureIndex() still exists for the very first launch
|
// 4) fallback: ensureIndex() still exists for the very first launch
|
||||||
// where the bundled snapshot is absent AND the poll hasn't landed
|
// where the bundled snapshot is absent AND the poll hasn't landed
|
||||||
// yet — a full-walk build.
|
// yet — a full-walk build.
|
||||||
warmFromSnapshot().catch(() => {});
|
bnsWarm = warmFromSnapshot().catch(() => null);
|
||||||
startBnsPolling();
|
|
||||||
refreshSnapshotFromSia().catch(() => {});
|
|
||||||
ensureIndex().catch(() => {}); // fallback for first launch without a bundled snapshot
|
|
||||||
// Cheap update check: fetch the releases manifest and, if a newer
|
// Cheap update check: fetch the releases manifest and, if a newer
|
||||||
// version is out, surface a chip in the toolbar. No auto-install —
|
// version is out, surface a chip in the toolbar. No auto-install —
|
||||||
// clicking the chip opens the download URL. Recheck every 6h so a
|
// clicking the chip opens the download URL. First check runs from
|
||||||
// browser left running for days catches updates without a relaunch.
|
// onChromeReady(); recheck every 6h so a browser left running for days
|
||||||
checkForUpdate().catch(() => {});
|
// catches updates without a relaunch.
|
||||||
setInterval(() => checkForUpdate().catch(() => {}), 6 * 60 * 60 * 1000);
|
setInterval(() => checkForUpdate().catch(() => {}), 6 * 60 * 60 * 1000);
|
||||||
// Home cards are also polled from a remote URL — brand copy updates then
|
// Home cards are also polled from a remote URL — brand copy updates then
|
||||||
// reach every install without a browser release. User's local edits stay
|
// reach every install without a browser release. User's local edits stay
|
||||||
// authoritative (loadHomeCards checks them first).
|
// authoritative (loadHomeCards checks them first).
|
||||||
refreshRemoteHomeCards().catch(() => {});
|
|
||||||
setInterval(() => refreshRemoteHomeCards().catch(() => {}), HOME_CARDS_REFRESH_MS);
|
setInterval(() => refreshRemoteHomeCards().catch(() => {}), HOME_CARDS_REFRESH_MS);
|
||||||
app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
|
app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue