diff --git a/chrome.html b/chrome.html
index 2e775a80..f2159b52 100644
--- a/chrome.html
+++ b/chrome.html
@@ -295,6 +295,18 @@
.logo .gear { font-size: 12px; opacity: .8; }
/* favorites bar — new-tab page only, single compact row */
.bookmarks { display: flex; align-items: center; gap: 4px; padding: 3px 10px 5px; height: 26px; overflow: hidden; }
+ /* Ctrl+F find-in-page bar. Sits below the address bar (above tabs body),
+ ~32px tall so it's part of the chrome view height like .tordisc / .bcnrbar.
+ syncHeight in the tabs handler picks it up automatically. */
+ .findbar { display: flex; align-items: center; gap: 6px; padding: 5px 10px; border-top: 1px solid var(--line); background: var(--surface); }
+ .findbar input { flex: 1; min-width: 120px; padding: 4px 8px; border-radius: 6px; background: var(--surface2); color: var(--ink);
+ border: 1px solid var(--line); font: inherit; font-size: 12.5px; outline: none; }
+ .findbar input:focus { border-color: color-mix(in srgb, var(--acid) 55%, transparent); }
+ .findbar input.nomatch { border-color: rgba(246,118,138,.55); background: rgba(246,118,138,.10); }
+ .findbar .findcount { font-size: 12px; color: var(--dim); font-variant-numeric: tabular-nums; min-width: 46px; text-align: right; }
+ .findbar .findbtn { border: 1px solid var(--line); background: var(--surface2); color: var(--ink); border-radius: 6px;
+ padding: 3px 8px; font-size: 13px; cursor: pointer; }
+ .findbar .findbtn:hover { background: var(--surface); border-color: color-mix(in srgb, var(--acid) 35%, transparent); }
.bm { display: flex; align-items: center; gap: 6px; max-width: 180px; padding: 3px 9px; border-radius: 6px;
background: var(--surface2); border: 1px solid var(--hover); color: var(--mut); font-size: 12px; cursor: pointer; white-space: nowrap; }
.bm:hover { background: var(--active); color: var(--ink); }
@@ -397,6 +409,13 @@
+
+
+ –
+
+
+
+
Onion mode hides your IP from sites and your ISP (including .bch lookups).
It is not full anonymity — this browser can still be fingerprinted; for that use the Tor Browser.
@@ -1303,6 +1322,60 @@
T.getSettings && T.getSettings().then(applyBarSizes);
T.onSettingsUpdate && T.onSettingsUpdate(applyBarSizes);
+ // ---- Ctrl+F find-in-page bar ----
+ // Chrome renderer owns the UI; main proxies findInPage / stopFindInPage
+ // and forwards Chromium's found-in-page events back so we can render
+ // "N of M". Enter jumps to next, Shift+Enter to previous, Esc closes.
+ const findbar = $("findbar");
+ const findInput = $("findInput");
+ const findCount = $("findCount");
+ let findQuery = "";
+ function openFind() {
+ findbar.hidden = false;
+ syncHeight();
+ setTimeout(() => { findInput.focus(); findInput.select(); }, 0);
+ // Re-highlight the current query if there was one.
+ if (findInput.value.trim()) T.findInPage && T.findInPage(findInput.value.trim(), { findNext: true });
+ }
+ function closeFind() {
+ findbar.hidden = true;
+ findQuery = "";
+ findCount.textContent = "–";
+ findInput.classList.remove("nomatch");
+ T.findStop && T.findStop();
+ syncHeight();
+ }
+ function stepFind(forward) {
+ const q = findInput.value.trim();
+ if (!q) return;
+ findQuery = q;
+ T.findInPage && T.findInPage(q, { forward, findNext: true });
+ }
+ T.onFindOpen && T.onFindOpen(openFind);
+ T.onFindResult && T.onFindResult((r) => {
+ if (r.matches === 0) {
+ findCount.textContent = "0";
+ findInput.classList.add("nomatch");
+ } else {
+ findCount.textContent = r.activeMatchOrdinal + " / " + r.matches;
+ findInput.classList.remove("nomatch");
+ }
+ });
+ findInput.addEventListener("input", () => {
+ const q = findInput.value.trim();
+ if (!q) { T.findStop && T.findStop(); findCount.textContent = "–"; findInput.classList.remove("nomatch"); return; }
+ // findNext: false → new query starts a fresh search from the top.
+ T.findInPage && T.findInPage(q, { findNext: q === findQuery, forward: true });
+ findQuery = q;
+ });
+ findInput.addEventListener("keydown", (e) => {
+ if (e.key === "Enter") { e.preventDefault(); stepFind(!e.shiftKey); }
+ else if (e.key === "Escape") { e.preventDefault(); closeFind(); }
+ });
+ $("findPrev").onclick = () => stepFind(false);
+ $("findNext").onclick = () => stepFind(true);
+ $("findClose").onclick = closeFind;
+
// Drag handles: right edge of .urlwrap resizes the URL bar; left edge of
// .searchbox resizes the search box. Persists to settings.urlBarWidthPx /
// searchBoxWidthPx on release, which then wins over the size preset.
diff --git a/main.js b/main.js
index 4025124f..f04be0ec 100644
--- a/main.js
+++ b/main.js
@@ -2085,6 +2085,12 @@ function createTab(initial, opts = {}) {
const next = (urls && urls[0]) || null;
if (tab.favicon !== next) { tab.favicon = next; emitTabs(); }
});
+ // Chromium fires found-in-page on every findInPage call + on subsequent
+ // match walks. Forward to chrome so the find bar shows "N of M".
+ wc.on("found-in-page", (_e, r) => {
+ if (tab.id !== activeId) return;
+ try { chrome?.webContents.send("find-result", { activeMatchOrdinal: r.activeMatchOrdinal, matches: r.matches, finalUpdate: r.finalUpdate }); } catch {}
+ });
wc.on("did-navigate", () => { refreshTabUrl(tab); emitTabs(); historyAdd(tab.url, tab.title); });
wc.on("did-navigate-in-page", () => { refreshTabUrl(tab); emitTabs(); historyAdd(tab.url, tab.title); });
wc.on("did-start-loading", () => setLoading(tab, true));
@@ -2923,6 +2929,27 @@ ipcMain.handle("open-update-download", (_e, url) => {
} catch (e) { console.warn("update download failed:", e?.message); return false; }
});
ipcMain.handle("dismiss-update", () => { updateDismissedThisSession = true; emitUpdateAvailable(); return true; });
+// Find-in-page. Chrome renderer's find bar drives this: the bar sends
+// `find-in-page` with the query + direction; main proxies to the active
+// tab's webContents.findInPage. Chromium raises `found-in-page` on each
+// keystroke with the running match count / active match ordinal; we
+// forward that back to chrome so the bar can render "3 of 12".
+ipcMain.handle("find-in-page", (_e, query, opts = {}) => {
+ const t = activeTab(); if (!t) return false;
+ if (typeof query !== "string" || !query) { try { t.view.webContents.stopFindInPage("clearSelection"); } catch {} return false; }
+ try {
+ t.view.webContents.findInPage(query, {
+ forward: opts.forward !== false,
+ findNext: !!opts.findNext, // false = new query; true = jump next/prev
+ matchCase: !!opts.matchCase,
+ });
+ return true;
+ } catch { return false; }
+});
+ipcMain.handle("find-stop", () => {
+ const t = activeTab(); if (!t) return false;
+ try { t.view.webContents.stopFindInPage("clearSelection"); return true; } catch { return false; }
+});
ipcMain.handle("recheck-update", async () => {
// Manual "Check for updates" also un-dismisses any chip the user closed
// in this session — they're actively asking to see the status, so honour
@@ -3992,6 +4019,14 @@ app.on("web-contents-created", (_event, wc) => {
toggleSidebar();
return e.preventDefault();
}
+ // Find-in-page: Ctrl+F opens the find bar in chrome. Chrome renderer
+ // owns the UI (input, next/prev, count, close); it drives the active
+ // tab's webContents.findInPage via IPC (find-in-page / find-stop).
+ const isF = input.key === "F" || input.key === "f";
+ if (input.control && !input.shift && !input.alt && isF) {
+ try { chrome?.webContents.send("find-open"); } catch {}
+ return e.preventDefault();
+ }
// DevTools: F12 or Ctrl+Shift+I toggles Chromium DevTools on the active
// TAB (not the chrome/overlay that received the keystroke — a user
// debugging a page wants the page's inspector, always). Detach into
diff --git a/preload.js b/preload.js
index 641eb1dd..c7458e07 100644
--- a/preload.js
+++ b/preload.js
@@ -28,6 +28,13 @@ contextBridge.exposeInMainWorld("theseus", {
getSettings: () => ipcRenderer.invoke("settings-get"),
setSetting: (key, val) => ipcRenderer.invoke("settings-set", key, val),
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
+ // Find-in-page. main.js fires 'find-open' on Ctrl+F; chrome renderer
+ // owns the bar UI and drives findInPage / stopFindInPage via these
+ // wrappers. Match count / active ordinal comes back through onFindResult.
+ onFindOpen: (cb) => ipcRenderer.on("find-open", () => cb()),
+ findInPage: (query, opts) => ipcRenderer.invoke("find-in-page", query, opts || {}),
+ findStop: () => ipcRenderer.invoke("find-stop"),
+ onFindResult: (cb) => ipcRenderer.on("find-result", (_e, d) => cb(d)),
toggleSiteInfo: (rect) => ipcRenderer.invoke("toggle-site-info", rect),
switchToBcnr: () => ipcRenderer.invoke("switch-to-bcnr"),
setChromeHeight: (h) => ipcRenderer.invoke("set-chrome-height", h),