diff --git a/main.js b/main.js index 9a9cbdf4..27b760f8 100644 --- a/main.js +++ b/main.js @@ -733,6 +733,11 @@ const SETTINGS_DEFAULTS = { // forced closed; the two share the right area (add-on sidebar keeps its // width, DevTools takes what's left). devToolsDock: "bottom", // bottom | sidebar | two-sidebars + // Show an "Open Dev Panel" / "Inspect element" entry in the page right- + // click menu and in the tab-strip tab menu. F12 and Ctrl+Shift+I always + // work regardless of this; the menu entry is just a discoverability + // escape hatch for users who hit the F12-vs-airplane-mode foot-gun. + contextMenuDevTools: true, }; // Applying the theme via nativeTheme.themeSource makes prefers-color-scheme update // in every renderer (chrome, settings, popover, page views) with no per-view IPC. @@ -5265,6 +5270,30 @@ function styleScrollbars(wc) { // fast during startup), fire once explicitly. try { if (!wc.isLoading()) inject(); } catch {} } +// DevTools dock resolution shared by the F12 shortcut and the context-menu +// entries below. Keeps the three call-sites honest about the settings mapping. +function devToolsOpenMode() { + return settings.devToolsDock === "sidebar" ? "right" + : settings.devToolsDock === "two-sidebars" ? "right" + : "bottom"; +} +function toggleTabDevTools(wc) { + if (!wc || wc.isDestroyed?.()) return; + try { + if (wc.isDevToolsOpened()) { wc.closeDevTools(); return; } + wc.openDevTools({ mode: devToolsOpenMode() }); + try { wc.devToolsWebContents?.focus?.(); } catch {} + } catch {} +} +function inspectTabElement(wc, x, y) { + if (!wc || wc.isDestroyed?.()) return; + try { + if (!wc.isDevToolsOpened()) wc.openDevTools({ mode: devToolsOpenMode() }); + const px = Number(x), py = Number(y); + if (Number.isFinite(px) && Number.isFinite(py)) wc.inspectElement(Math.round(px), Math.round(py)); + try { wc.devToolsWebContents?.focus?.(); } catch {} + } catch {} +} function createTab(initial, opts = {}) { const id = ++tabSeq; // Non-settings tabs get home-preload so the built-in home page can round- @@ -5741,6 +5770,21 @@ function createTab(initial, opts = {}) { { label: "Image (PNG)…", click: () => savePageAsImage(wc).catch((e) => console.warn("save-png:", e?.message)) }, ]}, ); + // Dev Panel entry. The F12 shortcut doubles as the airplane-mode toggle + // in some environments, so a right-click escape hatch avoids users + // flipping their connection off while debugging. Skipped on add-on + // tabs (which have their own loading model) and when the user has + // turned the entry off in Settings. Inspect element uses the click + // point Electron hands us; it also opens the panel if closed. + if (!tab.addonId && settings.contextMenuDevTools !== false) { + const open = (() => { try { return wc.isDevToolsOpened(); } catch { return false; } })(); + const inspectAt = { x: p.x, y: p.y }; + items.push( + { type: "separator" }, + { label: "Inspect element", click: () => inspectTabElement(wc, inspectAt.x, inspectAt.y) }, + { label: open ? "Close Dev Panel" : "Open Dev Panel", accelerator: "F12", click: () => toggleTabDevTools(wc) }, + ); + } if (tab.webapp) { const inst = webapps.find(tab.webapp.key); items.push({ type: "separator" }, inst @@ -6681,6 +6725,16 @@ ipcMain.handle("tab-context-menu-popup", (e, tabId, rect) => { { label: t.muted ? "Unmute" : "Mute", click: () => { try { t.view.webContents.setAudioMuted(!t.muted); t.muted = !t.muted; emitTabs(); } catch {} } }, + // Dev Panel toggle on the tab-strip menu — mirrors the page right-click + // entry so users can reach it without clicking into the page first. + ...(!t.addonId && settings.contextMenuDevTools !== false ? [ + { type: "separator" }, + { + label: (() => { try { return t.view.webContents.isDevToolsOpened() ? "Close Dev Panel" : "Open Dev Panel"; } catch { return "Open Dev Panel"; } })(), + accelerator: "F12", + click: () => toggleTabDevTools(t.view?.webContents), + }, + ] : []), { type: "separator" }, { label: "Close", click: () => closeTab(t.id) }, ]; @@ -12168,6 +12222,15 @@ async function openLinkWindow(input) { { label: "Forward", enabled: wc.navigationHistory.canGoForward(), click: () => wc.navigationHistory.goForward() }, { label: "Reload", click: () => wc.reload() }, ); + if (settings.contextMenuDevTools !== false) { + const open = (() => { try { return wc.isDevToolsOpened(); } catch { return false; } })(); + const ix = p.x, iy = p.y; + items.push( + { type: "separator" }, + { label: "Inspect element", click: () => inspectTabElement(wc, ix, iy) }, + { label: open ? "Close Dev Panel" : "Open Dev Panel", accelerator: "F12", click: () => toggleTabDevTools(wc) }, + ); + } Menu.buildFromTemplate(items).popup({ window: w }); }); linkWindows.add(w); diff --git a/settings.html b/settings.html index 155e329c..eac8f778 100644 --- a/settings.html +++ b/settings.html @@ -382,6 +382,7 @@ +

Registries

@@ -2521,6 +2522,11 @@ r.checked = (r.value === devToolsDock); r.addEventListener("change", () => { if (r.checked) C.set("devToolsDock", r.value); }); }); + const cmDev = document.getElementById("contextMenuDevTools"); + if (cmDev) { + cmDev.checked = s.contextMenuDevTools !== false; + cmDev.addEventListener("change", () => { C.set("contextMenuDevTools", !!cmDev.checked); }); + } // ---- Plug-ins: Ariadne's Thread + Aegis -------------------------------- // Two views per plug-in that share one truth source: