Theseus: an extension update applied in place reloads its open panels
Applying an extension update without a restart swapped the files and restarted the add-on, but a panel or tab that was already open kept running the previous version's page, so the extension looked unchanged until Theseus restarted (Pithos showed its old layout this way). The add-on's open left panel, sidebar panel and tabs now reload after the swap. Extensions can also apply their own staged update with api.applySelfUpdate(), so their own update button needs no restart; plug-ins such as wallets still update on the next launch.
This commit is contained in:
parent
f3cd63134a
commit
99d7684590
2 changed files with 48 additions and 5 deletions
|
|
@ -282,7 +282,7 @@ function validateManifest(raw, folderName) {
|
||||||
// Loader singleton. `discoverAndActivate(opts)` returns a snapshot the rest
|
// Loader singleton. `discoverAndActivate(opts)` returns a snapshot the rest
|
||||||
// of the app queries via `getActive()` / `getInstalled()`.
|
// of the app queries via `getActive()` / `getInstalled()`.
|
||||||
class AddonHost {
|
class AddonHost {
|
||||||
constructor({ addonsDir, dataDir, isDisabled, activationFor, onActivated, logger, setSessionProxy, vaultDerive, vaultImports, approvalModal, emitToPanel, hostRequire, hostImport, openTab, openAddonTab, openSettings, captureTab, saveCapture, scanTabForUris, checkAndStageUpdates, restartApp, revealSidebar, requestFilter, tabs }) {
|
constructor({ addonsDir, dataDir, isDisabled, activationFor, onActivated, logger, setSessionProxy, vaultDerive, vaultImports, approvalModal, emitToPanel, hostRequire, hostImport, openTab, openAddonTab, openSettings, captureTab, saveCapture, scanTabForUris, checkAndStageUpdates, restartApp, applySelfUpdate, revealSidebar, requestFilter, tabs }) {
|
||||||
this.addonsDir = addonsDir;
|
this.addonsDir = addonsDir;
|
||||||
this.dataDir = dataDir;
|
this.dataDir = dataDir;
|
||||||
this.isDisabled = isDisabled || (() => false);
|
this.isDisabled = isDisabled || (() => false);
|
||||||
|
|
@ -351,6 +351,7 @@ class AddonHost {
|
||||||
// add-on ever gets to hand-write into its own installed folder.
|
// add-on ever gets to hand-write into its own installed folder.
|
||||||
this._checkAndStageUpdates = typeof checkAndStageUpdates === "function" ? checkAndStageUpdates : null;
|
this._checkAndStageUpdates = typeof checkAndStageUpdates === "function" ? checkAndStageUpdates : null;
|
||||||
this._restartApp = typeof restartApp === "function" ? restartApp : null;
|
this._restartApp = typeof restartApp === "function" ? restartApp : null;
|
||||||
|
this._applySelfUpdate = typeof applySelfUpdate === "function" ? applySelfUpdate : null;
|
||||||
// revealSidebar: opens the sidebar and switches to the given panel id.
|
// revealSidebar: opens the sidebar and switches to the given panel id.
|
||||||
// Signature: (addonId, panelId) => void. Add-ons use this from their
|
// Signature: (addonId, panelId) => void. Add-ons use this from their
|
||||||
// context-menu handler to surface a result in their sidebar UI.
|
// context-menu handler to surface a result in their sidebar UI.
|
||||||
|
|
@ -837,6 +838,13 @@ class AddonHost {
|
||||||
staged: (full?.staged || []).find((s) => s.id === manifest.id) || null,
|
staged: (full?.staged || []).find((s) => s.id === manifest.id) || null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
// Apply this add-on's staged update now, without a restart: Theseus
|
||||||
|
// swaps it in, restarts the add-on and reloads its open panels and
|
||||||
|
// tabs. Plug-ins (wallets) still update on the next launch.
|
||||||
|
applySelfUpdate: async () => {
|
||||||
|
if (!this._applySelfUpdate) throw new Error("applySelfUpdate unavailable (host not wired)");
|
||||||
|
return this._applySelfUpdate(manifest.id);
|
||||||
|
},
|
||||||
// Ask Theseus to relaunch (the plug-in card's "apply update" chip).
|
// Ask Theseus to relaunch (the plug-in card's "apply update" chip).
|
||||||
// The host asks the user first and resolves { restarted, deferred }
|
// The host asks the user first and resolves { restarted, deferred }
|
||||||
// — an add-on cannot restart the browser on its own.
|
// — an add-on cannot restart the browser on its own.
|
||||||
|
|
|
||||||
43
main.js
43
main.js
|
|
@ -2746,6 +2746,15 @@ function initAddons() {
|
||||||
// update) but never gets to perform one: the user is asked first, in a
|
// update) but never gets to perform one: the user is asked first, in a
|
||||||
// native dialog the panel cannot draw over. Declining costs nothing —
|
// native dialog the panel cannot draw over. Declining costs nothing —
|
||||||
// a staged update applies on the next normal launch anyway.
|
// a staged update applies on the next normal launch anyway.
|
||||||
|
// An add-on applying its own staged update in place (no restart). Runs a
|
||||||
|
// moment later so the add-on's own call can return before the host is
|
||||||
|
// rebuilt underneath it.
|
||||||
|
applySelfUpdate: (addonId) => {
|
||||||
|
if (isPluginAddon(addonId)) return { ok: false, reason: "plug-ins update on the next launch" };
|
||||||
|
if (!listStagedAddons(addonsStagedDir()).some((s) => s.id === addonId)) return { ok: false, reason: "nothing staged" };
|
||||||
|
setTimeout(() => applyStagedNow(addonId), 250);
|
||||||
|
return { ok: true, scheduled: true };
|
||||||
|
},
|
||||||
restartApp: async (addonName) => {
|
restartApp: async (addonName) => {
|
||||||
const who = String(addonName || "An add-on").slice(0, 60);
|
const who = String(addonName || "An add-on").slice(0, 60);
|
||||||
console.log(`[restart] ${who} asked to relaunch Theseus`);
|
console.log(`[restart] ${who} asked to relaunch Theseus`);
|
||||||
|
|
@ -5924,9 +5933,9 @@ ipcMain.handle("addons-check-updates", () => pollAddonUpdates("manual"));
|
||||||
// launch — a wallet mid-session is not something to hot-swap. `id` limits
|
// launch — a wallet mid-session is not something to hot-swap. `id` limits
|
||||||
// the apply to one extension (Settings row button); omitted = every
|
// the apply to one extension (Settings row button); omitted = every
|
||||||
// staged extension (toolbar chip).
|
// staged extension (toolbar chip).
|
||||||
ipcMain.handle("addons-apply-staged", (_e, id) => {
|
ipcMain.handle("addons-apply-staged", (_e, id) => applyStagedNow(typeof id === "string" && id ? id : null));
|
||||||
|
function applyStagedNow(want) {
|
||||||
if (!addonHost) return { ok: false, error: "add-ons not ready", applied: [] };
|
if (!addonHost) return { ok: false, error: "add-ons not ready", applied: [] };
|
||||||
const want = typeof id === "string" && id ? id : null;
|
|
||||||
const applied = addonUpdater.promoteStagedUpdates({
|
const applied = addonUpdater.promoteStagedUpdates({
|
||||||
addonsDir: addonsUserDir(),
|
addonsDir: addonsUserDir(),
|
||||||
backupsDir: addonsBackupDir(),
|
backupsDir: addonsBackupDir(),
|
||||||
|
|
@ -5934,10 +5943,36 @@ ipcMain.handle("addons-apply-staged", (_e, id) => {
|
||||||
logger: (...a) => console.log("[addons]", ...a),
|
logger: (...a) => console.log("[addons]", ...a),
|
||||||
only: (m) => (want ? m.id === want : true) && !isPluginAddon(m.id),
|
only: (m) => (want ? m.id === want : true) && !isPluginAddon(m.id),
|
||||||
});
|
});
|
||||||
if (applied.length) { addonHost.discoverAndActivate(); emitSidebarState(); }
|
if (applied.length) {
|
||||||
|
addonHost.discoverAndActivate();
|
||||||
|
emitSidebarState();
|
||||||
|
reloadAddonViews(applied.map((a) => a.id || a));
|
||||||
|
}
|
||||||
notifyStagedAddons();
|
notifyStagedAddons();
|
||||||
return { ok: true, applied, pending: listStagedAddons(addonsStagedDir()).map((s) => ({ id: s.id, version: s.version })) };
|
return { ok: true, applied, pending: listStagedAddons(addonsStagedDir()).map((s) => ({ id: s.id, version: s.version })) };
|
||||||
});
|
}
|
||||||
|
// After an update is applied in place, an open panel or tab of that add-on
|
||||||
|
// still runs the previous version's page (it was loaded before the swap),
|
||||||
|
// so it shows the old UI until Theseus restarts. Reload them from disk.
|
||||||
|
function reloadAddonViews(ids) {
|
||||||
|
const mine = (panelId) => panelId && ids.some((id) => String(panelId).startsWith(`${id}:`));
|
||||||
|
try {
|
||||||
|
if (mine(leftPanelLoadedId)) {
|
||||||
|
const open = leftPanelId;
|
||||||
|
leftPanelLoadedId = null;
|
||||||
|
if (open && mine(open)) openLeftPanel(open);
|
||||||
|
}
|
||||||
|
} catch (e) { console.warn("[addons] left panel reload failed:", e?.message); }
|
||||||
|
try {
|
||||||
|
if (mine(sidebarActivePanelId) && sidebar) {
|
||||||
|
const p = addonHost.getSidebarPanels().find((x) => x.panelId === sidebarActivePanelId);
|
||||||
|
if (p) sidebar.webContents.loadFile(p.pageFile);
|
||||||
|
}
|
||||||
|
} catch (e) { console.warn("[addons] sidebar reload failed:", e?.message); }
|
||||||
|
for (const t of tabs) {
|
||||||
|
try { if (t.addonId && ids.includes(t.addonId)) t.view.webContents.reload(); } catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
ipcMain.handle("addons-list-staged", () => listStagedAddons(addonsStagedDir()));
|
ipcMain.handle("addons-list-staged", () => listStagedAddons(addonsStagedDir()));
|
||||||
function listStagedAddons(stagedDir) {
|
function listStagedAddons(stagedDir) {
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue