diff --git a/chrome.html b/chrome.html
index b96af37b..7064b88e 100644
--- a/chrome.html
+++ b/chrome.html
@@ -36,6 +36,12 @@
content means scrollHeight is the natural chrome height, and syncHeight
shrinks correctly after every menu-close. */
body { margin: 0; background: var(--bg); color: var(--ink); user-select: none; overflow: hidden; }
+ /* While a prompt or menu floats over the page this view is taller than the
+ bars and transparent below them: the background is painted only behind
+ the bars (--bars-h), so the page shows through the rest. */
+ html.overlaying body { background: transparent; }
+ html.overlaying body::before { content: ""; position: absolute; left: 0; top: 0; right: 0;
+ height: var(--bars-h, 0px); background: var(--bg); z-index: -1; }
/* The tab row doubles as the window's title bar on Windows (main.js hides
the native frame): the row is a drag region, everything clickable in it
opts out, and --wco-reserve keeps tabs clear of the native minimise /
@@ -568,13 +574,23 @@
// Coalesced with a zero timeout, not requestAnimationFrame: frames stop
// whenever the window is occluded, and a height change queued then (a
// prompt closing, a row hiding) would only land when the window came back.
- let syncHeightTimer = 0;
+ // Prompts and menus that reach below the bars no longer make the bars
+ // taller (that pushed the page down and left an empty band under the
+ // favorites row): main grows only this view, transparent below the bars,
+ // over the page. A prompt takes the whole window so its backdrop dims it.
+ let syncHeightTimer = 0, overlayShown = 0;
function syncHeight() {
clearTimeout(syncHeightTimer);
syncHeightTimer = setTimeout(() => {
- let h = document.body.scrollHeight;
- const box = document.querySelector(".promptmask .promptbox");
- if (box) h = Math.max(h, Math.ceil(box.getBoundingClientRect().height) + 56);
+ const h = document.body.scrollHeight;
+ let over = 0;
+ if (document.querySelector(".promptmask")) over = 4000; // main clamps to the window
+ else for (const m of document.querySelectorAll(".ctxmenu, .grouppop")) over = Math.max(over, Math.ceil(m.getBoundingClientRect().bottom) + 4);
+ if (over <= h) over = 0;
+ const root = document.documentElement;
+ if (over) { root.style.setProperty("--bars-h", h + "px"); root.classList.add("overlaying"); }
+ else root.classList.remove("overlaying");
+ if (over !== overlayShown) { overlayShown = over; T.setChromeOverlay(over); }
T.setChromeHeight(h);
}, 0);
}
@@ -1321,7 +1337,7 @@
// - On a specific bookmark: Open / Edit title / Remove
// - On empty area OR the placeholder text: Add current page (or Remove
// if the page is already bookmarked), and Add a page… by hand
- function closeBmMenu() { const m = document.getElementById("bmMenu"); if (m) m.remove(); }
+ function closeBmMenu() { const m = document.getElementById("bmMenu"); if (m) { m.remove(); syncHeight(); } }
function openBmMenu(x, y, ctx) {
closeBmMenu();
const items = [];
@@ -1352,9 +1368,12 @@
).join("");
document.body.appendChild(m);
const r = m.getBoundingClientRect();
- const vw = document.documentElement.clientWidth, vh = document.documentElement.clientHeight;
+ const vw = document.documentElement.clientWidth;
m.style.left = Math.min(x, vw - r.width - 6) + "px";
- m.style.top = Math.min(y, vh - r.height - 6) + "px";
+ // Opens downward over the page (syncHeight floats this view), so it is
+ // never squeezed into the height of the bars.
+ m.style.top = y + "px";
+ growChromeForMenu();
m.querySelectorAll(".mi").forEach((el) => {
const it = items[Number(el.dataset.i)];
if (it.disabled) return;
@@ -1866,17 +1885,8 @@
if (r.right > window.innerWidth) m.style.left = Math.max(4, window.innerWidth - r.width - 4) + "px";
growChromeForMenu();
}
- function growChromeForMenu() {
- // Wait a frame so all newly-added overlays contribute to the bounding box.
- requestAnimationFrame(() => {
- let needed = document.body.scrollHeight;
- for (const m of document.querySelectorAll(".ctxmenu, .grouppop")) {
- const r = m.getBoundingClientRect();
- needed = Math.max(needed, Math.ceil(r.bottom) + 4);
- }
- T.setChromeHeight(needed);
- });
- }
+ // A menu that reaches below the bars floats over the page (see syncHeight).
+ function growChromeForMenu() { syncHeight(); }
function openGroupSubmenu(x, y, t) {
document.querySelectorAll(".ctxmenu.sub2").forEach((m) => m.remove());
const m = document.createElement("div");
diff --git a/main.js b/main.js
index 7cc6b140..8cfc14a0 100644
--- a/main.js
+++ b/main.js
@@ -2429,6 +2429,7 @@ let win, chrome;
// The window can be gone while tabs and app windows still fire events.
const winAlive = () => !!win && !win.isDestroyed();
let CHROME_H = 84; // grows when an extra bar (Tor notice / BCNR offer) is shown
+let CHROME_OVERLAY_H = 0; // > 0 while a chrome.html prompt floats over the page
// Site-info popover: a floating overlay VIEW on top of the page content, so it
// never pushes the page down. Positioned under the address-bar badge on demand.
let popover, popVisible = false, popPos = { x: 8, y: 90 };
@@ -3561,7 +3562,11 @@ function layout() {
const fsTab = fsTabId != null ? tabs.find((t) => t.id === fsTabId) : null;
if (!fsTab && fsTabId != null) fsTabId = null;
const chromeH = fsTab ? 0 : CHROME_H;
- chrome.setBounds({ x: 0, y: 0, width, height: chromeH });
+ // A prompt open in chrome.html (Add a page…, rename) floats over the page:
+ // only the chrome view grows, transparent below its bars, and every other
+ // view keeps y = chromeH, so the page neither moves nor leaves a gap.
+ const chromeViewH = fsTab ? 0 : Math.min(height, Math.max(chromeH, CHROME_OVERLAY_H));
+ chrome.setBounds({ x: 0, y: 0, width, height: chromeViewH });
const bodyH = Math.max(0, height - chromeH);
// Quick-links strip on the LEFT edge (optional, 44px). Hidden in HTML
// fullscreen so a video truly fills the window. The strip spans the full
@@ -8861,6 +8866,19 @@ ipcMain.handle("set-chrome-height", (_e, h) => {
const next = Math.max(74, Math.min(260, Math.round(h) || 84));
if (next !== CHROME_H) { CHROME_H = next; layout(); }
});
+// Height a chrome.html prompt needs over the page (0 = none). While it is set
+// the chrome view is transparent below its bars and sits above every other
+// view, so the prompt and its dimmed backdrop draw over the page.
+ipcMain.handle("set-chrome-overlay", (e, h) => {
+ if (!chrome || e.sender !== chrome.webContents) throw new Error("set-chrome-overlay: untrusted sender");
+ const next = Math.max(0, Math.min(4000, Math.round(h) || 0));
+ if (next === CHROME_OVERLAY_H) return;
+ CHROME_OVERLAY_H = next;
+ try { chrome.setBackgroundColor(next ? "#00000000" : (nativeTheme.shouldUseDarkColors ? "#0f1420" : "#e6e8ec")); } catch {}
+ // Re-adding a child view moves it to the top of the stack.
+ if (next) try { win.contentView.addChildView(chrome); } catch {}
+ layout();
+});
ipcMain.handle("switch-to-bcnr", () => {
const t = activeTab(); if (!t || !t.bcnrOffer) return;
const { host, rest, tld } = t.bcnrOffer;
diff --git a/preload.js b/preload.js
index 9a9885c0..032cdb66 100644
--- a/preload.js
+++ b/preload.js
@@ -58,6 +58,7 @@ contextBridge.exposeInMainWorld("theseus", {
toggleSiteInfo: (rect) => ipcRenderer.invoke("toggle-site-info", rect),
switchToBcnr: () => ipcRenderer.invoke("switch-to-bcnr"),
setChromeHeight: (h) => ipcRenderer.invoke("set-chrome-height", h),
+ setChromeOverlay: (h) => ipcRenderer.invoke("set-chrome-overlay", h),
getSearchEngines: () => ipcRenderer.invoke("search-engines"),
setSearchEngine: (id) => ipcRenderer.invoke("set-search-engine", id),
addEngine: (eng) => ipcRenderer.invoke("add-engine", eng),