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),