diff --git a/chrome.html b/chrome.html
index e3d36bcc..b0cc4796 100644
--- a/chrome.html
+++ b/chrome.html
@@ -559,6 +559,27 @@
$("search").addEventListener("keydown", (e) => { if (e.key === "Enter") { const q = $("search").value.trim(); if (q) T.search(q); } });
$("back").onclick = () => T.back();
$("fwd").onclick = () => T.forward();
+ // Hold Back/Forward (or right-click) for the history list, like Chrome.
+ // A hold longer than 450 ms opens the native menu and swallows the click
+ // that would otherwise fire on release.
+ for (const [id, dir] of [["back", "back"], ["fwd", "forward"]]) {
+ const btn = $(id);
+ let timer = null, held = false;
+ const open = () => {
+ held = true;
+ const r = btn.getBoundingClientRect();
+ T.navHistoryMenu && T.navHistoryMenu(dir, { x: r.left, y: r.bottom });
+ };
+ btn.addEventListener("mousedown", (e) => {
+ if (e.button !== 0 || btn.disabled) return;
+ held = false; clearTimeout(timer); timer = setTimeout(open, 450);
+ });
+ const cancel = () => { clearTimeout(timer); timer = null; };
+ btn.addEventListener("mouseup", cancel);
+ btn.addEventListener("mouseleave", cancel);
+ btn.addEventListener("click", (e) => { if (held) { held = false; e.stopImmediatePropagation(); e.preventDefault(); } }, true);
+ btn.addEventListener("contextmenu", (e) => { e.preventDefault(); if (!btn.disabled) open(); held = false; });
+ }
// Shift-click on the reload button = hard reload (bypass cache), same
// convention as Chrome/Firefox. Keyboard alternative: Ctrl+Shift+R.
$("reload").onclick = (e) => T.reload(e.shiftKey);
diff --git a/main.js b/main.js
index 3c8b677c..af310bca 100644
--- a/main.js
+++ b/main.js
@@ -4189,6 +4189,32 @@ ipcMain.handle("registry-menu-popup", (e, rect) => {
const y = Math.max(0, Math.round(chromeBounds.y + (rect?.y || 0)));
popup.popup({ window: win, x, y });
});
+// Back/Forward history menu (press-and-hold or right-click on the buttons).
+// Lists up to 15 entries in the requested direction, nearest first, from the
+// active tab's navigation history; picking one jumps straight to it.
+ipcMain.handle("nav-history-menu", (e, dir, rect) => {
+ if (chrome && e.sender !== chrome.webContents) throw new Error("nav-history-menu: untrusted sender");
+ const t = activeTab(); const wc = t?.view?.webContents;
+ if (!wc) return false;
+ const nh = wc.navigationHistory;
+ const entries = nh.getAllEntries();
+ const cur = nh.getActiveIndex();
+ const picks = [];
+ if (dir === "forward") for (let i = cur + 1; i < entries.length && picks.length < 15; i++) picks.push(i);
+ else for (let i = cur - 1; i >= 0 && picks.length < 15; i--) picks.push(i);
+ if (!picks.length) return false;
+ const label = (en) => {
+ const title = String(en.title || "").trim();
+ let host = ""; try { host = new URL(en.url).host; } catch {}
+ const text = title || en.url || "";
+ return (text.length > 60 ? text.slice(0, 57) + "…" : text) + (host && title ? ` — ${host}` : "");
+ };
+ const template = picks.map((i) => ({ label: label(entries[i]), click: () => { try { nh.goToIndex(i); } catch {} } }));
+ const popup = Menu.buildFromTemplate(template);
+ const chromeBounds = chrome ? chrome.getBounds() : { x: 0, y: 0 };
+ popup.popup({ window: win, x: Math.max(0, Math.round(chromeBounds.x + (rect?.x || 0))), y: Math.max(0, Math.round(chromeBounds.y + (rect?.y || 0))) });
+ return true;
+});
ipcMain.handle("collision-state", () => ({
policy: settings.collisionPolicy,
byName: collisions.byName,
diff --git a/preload.js b/preload.js
index 8da50ec3..92e4840c 100644
--- a/preload.js
+++ b/preload.js
@@ -21,6 +21,9 @@ contextBridge.exposeInMainWorld("theseus", {
goHome: () => ipcRenderer.invoke("go-home"),
back: () => ipcRenderer.invoke("go-back"),
forward: () => ipcRenderer.invoke("go-forward"),
+ // Press-and-hold / right-click on Back or Forward: native menu of the
+ // tab's history entries in that direction; picking one jumps to it.
+ navHistoryMenu: (dir, rect) => ipcRenderer.invoke("nav-history-menu", dir, rect),
reload: (hard) => ipcRenderer.invoke("reload", !!hard),
stop: () => ipcRenderer.invoke("stop"),
toggleTor: () => ipcRenderer.invoke("toggle-tor"),