diff --git a/renderer/script.js b/renderer/script.js index 22b398e..e229bc0 100644 --- a/renderer/script.js +++ b/renderer/script.js @@ -1,4 +1,7 @@ const ipcRenderer = window.electronAPI; +// Lightweight debug logger (toggleable) +const DEBUG = false; +const debug = (...args) => { if (DEBUG) console.log(...args); }; // Site history management using localStorage function getSiteHistory() { @@ -35,6 +38,7 @@ const webviewsEl = document.getElementById('webviews'); const menuPopup = document.getElementById('menu-popup'); const contextMenu = document.getElementById('context-menu'); const menuItems = contextMenu ? contextMenu.querySelectorAll('li') : []; +let lastContextPos = { x: 0, y: 0 }; // Select all text on focus and prevent mouseup from deselecting urlBox.addEventListener('focus', () => { @@ -64,6 +68,19 @@ function scheduleRenderTabs() { }); } +// Debounce nav button updates to reduce layout work +let navButtonsPending = false; +let backBtnCached = null; +let fwdBtnCached = null; +function scheduleUpdateNavButtons() { + if (navButtonsPending) return; + navButtonsPending = true; + requestAnimationFrame(() => { + navButtonsPending = false; + try { updateNavButtons(); } catch {} + }); +} + // Derive a stable, safe label for a tab without throwing on non-URLs function getTabLabel(tab) { if (tab.title && tab.title !== 'New Tab') return tab.title; @@ -105,13 +122,13 @@ loadBookmarks(); // Listen for site history updates from main process ipcRenderer.on('record-site-history', (event, url) => { - console.log('[DEBUG] Received site history update:', url); + debug('[DEBUG] Received site history update:', url); addToSiteHistory(url); }); function createTab(inputUrl) { inputUrl = inputUrl || 'browser://home'; - console.log('[DEBUG] createTab() inputUrl =', inputUrl); + debug('[DEBUG] createTab() inputUrl =', inputUrl); const id = crypto.randomUUID(); // Handle home page specially @@ -139,7 +156,7 @@ function createTab(inputUrl) { // For all other URLs, use webview const resolvedUrl = resolveInternalUrl(inputUrl); - console.log('[DEBUG] createTab() resolvedUrl =', resolvedUrl); + debug('[DEBUG] createTab() resolvedUrl =', resolvedUrl); const webview = document.createElement('webview'); // give the webview an id and set its source and attributes so it actually loads and can be managed @@ -173,37 +190,17 @@ function createTab(inputUrl) { // Consolidated navigation recording - only use did-navigate to avoid duplicates webview.addEventListener('did-navigate', e => { handleNavigation(id, e.url); - // Record ALL HTTP navigations - if (e.url.startsWith('http')) { - console.log('[DEBUG] Recording navigation to:', e.url); - addToSiteHistory(e.url); - // Also save to file for cross-context sharing - ipcRenderer.invoke('save-site-history-entry', e.url).catch(err => - console.error('Failed to save to file:', err) - ); - } + if (e.url.startsWith('http')) debug('[DEBUG] Recording navigation to:', e.url); }); webview.addEventListener('did-navigate-in-page', e => { handleNavigation(id, e.url); - // Record in-page navigations too - if (e.url.startsWith('http')) { - console.log('[DEBUG] Recording in-page navigation to:', e.url); - addToSiteHistory(e.url); - ipcRenderer.invoke('save-site-history-entry', e.url).catch(err => - console.error('Failed to save to file:', err) - ); - } + if (e.url.startsWith('http')) debug('[DEBUG] Recording in-page navigation to:', e.url); }); - // Also capture when pages finish loading + // After load, just refresh nav buttons to avoid jank webview.addEventListener('did-finish-load', () => { - const currentUrl = webview.getURL(); - if (currentUrl.startsWith('http') && !currentUrl.includes('browser://')) { - console.log('[DEBUG] Webview did-finish-load, recording:', currentUrl); - addToSiteHistory(currentUrl); - ipcRenderer.invoke('save-site-history-entry', currentUrl); - } + scheduleUpdateNavButtons(); }); // catch any target="_blank" or window.open() calls and open them as new tabs @@ -310,7 +307,7 @@ function navigate() { webview.src = resolved; scheduleRenderTabs(); - updateNavButtons(); + scheduleUpdateNavButtons(); } function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) { @@ -340,30 +337,12 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) { webview.addEventListener('did-navigate', e => { handleNavigation(tabId, e.url); - if (e.url.startsWith('http')) { - addToSiteHistory(e.url); - ipcRenderer.invoke('save-site-history-entry', e.url).catch(err => - console.error('Failed to save to file:', err) - ); - } }); - webview.addEventListener('did-navigate-in-page', e => { handleNavigation(tabId, e.url); - if (e.url.startsWith('http')) { - addToSiteHistory(e.url); - ipcRenderer.invoke('save-site-history-entry', e.url).catch(err => - console.error('Failed to save to file:', err) - ); - } }); - webview.addEventListener('did-finish-load', () => { - const currentUrl = webview.getURL(); - if (currentUrl.startsWith('http') && !currentUrl.includes('browser://')) { - addToSiteHistory(currentUrl); - ipcRenderer.invoke('save-site-history-entry', currentUrl); - } + scheduleUpdateNavButtons(); }); webview.addEventListener('new-window', e => { @@ -393,7 +372,7 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) { if (homeContainer) homeContainer.classList.remove('active'); webview.classList.add('active'); - updateNavButtons(); + scheduleUpdateNavButtons(); // Activate converted webview tab and update UI setActiveTab(tabId); scheduleRenderTabs(); @@ -403,7 +382,7 @@ function handleNavigation(tabId, newUrl) { const tab = tabs.find(t => t.id === tabId); if (!tab) return; - console.log('[DEBUG] handleNavigation called with:', newUrl); + debug('[DEBUG] handleNavigation called with:', newUrl); // --- record every real navigation into history --- if (tab.history[tab.historyIndex] !== newUrl) { @@ -418,7 +397,7 @@ function handleNavigation(tabId, newUrl) { !newUrl.startsWith('file://') && !newUrl.includes('browser://') && newUrl.startsWith('http')) { - console.log('[DEBUG] Adding to site history:', newUrl); + debug('[DEBUG] Adding to site history:', newUrl); addToSiteHistory(newUrl); // Also send to main process for file storage ipcRenderer.invoke('save-site-history-entry', newUrl); @@ -440,7 +419,7 @@ function handleNavigation(tabId, newUrl) { } scheduleRenderTabs(); - updateNavButtons(); + scheduleUpdateNavButtons(); } @@ -620,18 +599,19 @@ function goForward() { function updateNavButtons() { const webview = document.getElementById(`tab-${activeTabId}`); - const backBtn = document.querySelector('.nav-left button:nth-child(1)'); - const forwardBtn = document.querySelector('.nav-left button:nth-child(2)'); - - backBtn.disabled = !webview || !webview.canGoBack(); - forwardBtn.disabled = !webview || !webview.canGoForward(); + if (!backBtnCached || !fwdBtnCached) { + backBtnCached = document.querySelector('.nav-left button:nth-child(1)'); + fwdBtnCached = document.querySelector('.nav-left button:nth-child(2)'); + } + if (backBtnCached) backBtnCached.disabled = !webview || !webview.canGoBack(); + if (fwdBtnCached) fwdBtnCached.disabled = !webview || !webview.canGoForward(); } function reload() { const webview = document.getElementById(`tab-${activeTabId}`); if (webview) { webview.reload(); - updateNavButtons(); // keep back/forward buttons in sync after a reload + scheduleUpdateNavButtons(); // keep back/forward buttons in sync after a reload } } @@ -711,6 +691,13 @@ window.addEventListener('DOMContentLoaded', () => { const forwardBtn = document.querySelector('.nav-left button:nth-child(2)'); backBtn.addEventListener('click', goBack); forwardBtn.addEventListener('click', goForward); + // cache for faster updates + backBtnCached = backBtn; + fwdBtnCached = forwardBtn; + + // settings button + const settingsBtn = document.getElementById('open-settings-btn'); + if (settingsBtn) settingsBtn.addEventListener('click', openSettings); // window control bindings const minBtn = document.getElementById('min-btn'); @@ -782,6 +769,7 @@ try { // 4) unify context-menu wiring function showContextMenu(x,y) { if (!contextMenu) return; + lastContextPos = { x, y }; contextMenu.style.top = `${y}px`; contextMenu.style.left = `${x}px`; contextMenu.classList.add('visible'); @@ -793,38 +781,40 @@ document.addEventListener('contextmenu', e => { } }); document.addEventListener('click', ()=> contextMenu && contextMenu.classList.remove('visible')); -menuItems.forEach(item => item.addEventListener('click', async evt => { - const action = item.dataset.action; - const win = remote.getCurrentWindow(); +if (remote && fs) { + menuItems.forEach(item => item.addEventListener('click', async evt => { + const action = item.dataset.action; + const win = remote.getCurrentWindow(); - switch (action) { - case 'save-page': { - const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'page.html' }); - if (!canceled && filePath) win.webContents.savePage(filePath, 'HTMLComplete'); - break; + switch (action) { + case 'save-page': { + const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'page.html' }); + if (!canceled && filePath) win.webContents.savePage(filePath, 'HTMLComplete'); + break; + } + case 'select-all': + document.execCommand('selectAll'); + break; + case 'screenshot': { + const image = await win.webContents.capturePage(); + const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'screenshot.png' }); + if (!canceled && filePath) fs.writeFileSync(filePath, image.toPNG()); + break; + } + case 'view-source': { + const html = document.documentElement.outerHTML; + const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'source.html' }); + if (!canceled && filePath) fs.writeFileSync(filePath, html); + break; + } + case 'inspect-accessibility': + win.webContents.inspectAccessibilityNode(lastContextPos.x, lastContextPos.y); + break; + case 'inspect-element': + win.webContents.inspectElement(lastContextPos.x, lastContextPos.y); + break; } - case 'select-all': - document.execCommand('selectAll'); - break; - case 'screenshot': { - const image = await win.webContents.capturePage(); - const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'screenshot.png' }); - if (!canceled && filePath) fs.writeFileSync(filePath, image.toPNG()); - break; - } - case 'view-source': { - const html = document.documentElement.outerHTML; - const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'source.html' }); - if (!canceled && filePath) fs.writeFileSync(filePath, html); - break; - } - case 'inspect-accessibility': - win.webContents.inspectAccessibilityNode(e.clientX, e.clientY); - break; - case 'inspect-element': - win.webContents.inspectElement(e.clientX, e.clientY); - break; - } - contextMenu.classList.remove('visible'); -})); + contextMenu.classList.remove('visible'); + })); +} diff --git a/renderer/settings.js b/renderer/settings.js index 83705e7..4b7d0d7 100644 --- a/renderer/settings.js +++ b/renderer/settings.js @@ -74,10 +74,8 @@ window.addEventListener('DOMContentLoaded', () => { // Tabs: simple controller function activateTab(tabName) { - console.log('[TABS] Activating tab:', tabName); const links = document.querySelectorAll('.tab-link'); const panels = document.querySelectorAll('.tab-panel'); - console.log('[TABS] Found', links.length, 'tab links and', panels.length, 'panels'); links.forEach(l => { const isActive = l.dataset.tab === tabName; @@ -89,21 +87,17 @@ function activateTab(tabName) { const isActive = p.id === `panel-${tabName}`; p.classList.toggle('active', isActive); p.hidden = !isActive; - console.log('[TABS] Panel', p.id, 'active:', isActive); + // noop }); try { localStorage.setItem(TAB_STORAGE_KEY, tabName); } catch {} } function initTabs() { - console.log('[TABS] Initializing tabs...'); const links = document.querySelectorAll('.tab-link'); - console.log('[TABS] Found tab links:', links.length); // Direct listeners (for accessibility focus handling) links.forEach((link, index) => { - console.log('[TABS] Setting up listener for tab', index, 'with data-tab:', link.dataset.tab); link.addEventListener('click', (e) => { - console.log('[TABS] Tab clicked:', link.dataset.tab); e.preventDefault(); e.stopPropagation(); const name = link.dataset.tab; @@ -118,12 +112,9 @@ function initTabs() { // Delegation as a fallback if elements are re-rendered const tabContainer = document.querySelector('.tabs'); if (tabContainer) { - console.log('[TABS] Setting up delegation on tabs container'); tabContainer.addEventListener('click', (e) => { - console.log('[TABS] Container click detected'); const btn = e.target && e.target.closest ? e.target.closest('.tab-link') : null; if (!btn || !tabContainer.contains(btn)) return; - console.log('[TABS] Delegated click for tab:', btn.dataset.tab); const name = btn.dataset.tab; if (!name) return; if (location.hash !== `#${name}`) { @@ -139,13 +130,10 @@ function initTabs() { try { initial = localStorage.getItem(TAB_STORAGE_KEY) || null; } catch {} } if (!initial) initial = 'general'; - console.log('[TABS] Initial tab:', initial); activateTab(initial); } // Initialize tabs after DOM is ready but before customization init uses the DOM window.addEventListener('DOMContentLoaded', () => { - console.log('[TABS] DOM loaded, initializing tabs...'); initTabs(); - console.log('[TABS] Tabs initialized'); }); diff --git a/renderer/style.css b/renderer/style.css index f47308b..29283f2 100644 --- a/renderer/style.css +++ b/renderer/style.css @@ -28,7 +28,8 @@ html, body { padding: 6px 10px; background: #1e1e2e; gap: 12px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + /* flatter header to reduce paint cost */ + box-shadow: none; } .nav-left, @@ -72,7 +73,7 @@ html, body { padding: 6px 10px; border-radius: 5px; cursor: pointer; - transition: background 0.2s; + transition: background 120ms ease; } #nav button:hover { @@ -179,7 +180,8 @@ html, body { margin: 0; height: 28px; /* reduce overall tab height */ color: #ddd; - background: linear-gradient(180deg, #24262b 0%, #1f2025 100%); + /* use flat background to avoid gradient paints */ + background: #24262b; border: 1px solid #2b2d34; border-bottom: none; /* let it visually merge with the strip line */ border-radius: 6px 6px 0 0; /* slightly tighter radius */ @@ -189,17 +191,16 @@ html, body { min-width: 120px; flex: 0 1 180px; /* like Chrome: shrink when crowded */ overflow: hidden; - transition: background .15s ease, color .15s ease, box-shadow .15s ease; + transition: background 120ms ease, color 120ms ease; } -.tab:hover { - background: linear-gradient(180deg, #2a2c33 0%, #23252b 100%); -} +.tab:hover { background: #2a2c33; } .tab.active { color: #fff; - background: linear-gradient(180deg, #2f323a 0%, #2a2c33 100%); - box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset, 0 0 0 1px #3a3d46 inset; + background: #2f323a; + /* remove expensive inner shadows */ + box-shadow: none; } .tab .tab-favicon { @@ -228,7 +229,7 @@ html, body { background: transparent; color: #b5b5b5; opacity: 0; /* hidden by default */ - transition: background .15s ease, color .15s ease, opacity .15s ease; + transition: background 120ms ease, color 120ms ease, opacity 120ms ease; } .tab:hover .tab-close, @@ -249,7 +250,7 @@ html, body { background: #23252b; color: #d5d5d5; cursor: pointer; - transition: background .15s ease, color .15s ease, border-color .15s ease; + transition: background 120ms ease, color 120ms ease, border-color 120ms ease; } .new-tab-button:hover { background: #2b2d34; color: #fff; border-color: #3a3d46; } .new-tab-button:active { background: #262830; } @@ -301,7 +302,7 @@ html, body { color: white; font-size: 12px; cursor: pointer; - transition: background 0.2s; + transition: background 120ms ease; } #window-controls button:hover { background: rgba(255,255,255,0.1); @@ -324,3 +325,12 @@ html, body { #tab-bar::-webkit-scrollbar-thumb:hover { background: #555; } + +/* Respect reduced motion preferences */ +@media (prefers-reduced-motion: reduce) { + * { + animation: none !important; + transition: none !important; + scroll-behavior: auto !important; + } +}