Skip to content

Commit 5e6a8ec

Browse files
committed
resizing and placeholder menu
1 parent 56b17eb commit 5e6a8ec

8 files changed

Lines changed: 474 additions & 2 deletions

File tree

src/main/index.ts

Lines changed: 203 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { app, BrowserWindow, globalShortcut, session, nativeTheme, dialog } from "electron";
1+
import { app, BrowserWindow, globalShortcut, session, nativeTheme, dialog, Menu, MenuItemConstructorOptions } from "electron";
22
import * as path from "path";
33
import * as fs from "fs";
44
import { sessionManager } from "./session";
55
import { setupIpcHandlers, removeIpcHandlers } from "./ipc";
66
import { IPC_CHANNELS } from "../shared/types";
77

88
let mainWindow: BrowserWindow | null = null;
9+
let resizeTimeout: ReturnType<typeof setTimeout> | null = null;
910

1011
// Window state persistence
1112
interface WindowState {
@@ -65,6 +66,178 @@ function validateEnvironment(): boolean {
6566
return true;
6667
}
6768

69+
function createApplicationMenu(): void {
70+
const isMac = process.platform === "darwin";
71+
72+
const template: MenuItemConstructorOptions[] = [
73+
// App menu (macOS only)
74+
...(isMac
75+
? [
76+
{
77+
label: app.name,
78+
submenu: [
79+
{ role: "about" as const },
80+
{ type: "separator" as const },
81+
{ role: "services" as const },
82+
{ type: "separator" as const },
83+
{ role: "hide" as const },
84+
{ role: "hideOthers" as const },
85+
{ role: "unhide" as const },
86+
{ type: "separator" as const },
87+
{ role: "quit" as const },
88+
],
89+
},
90+
]
91+
: []),
92+
// File menu
93+
{
94+
label: "File",
95+
submenu: [
96+
{
97+
label: "New Tab",
98+
accelerator: "CmdOrCtrl+T",
99+
click: () => sessionManager.newTab(),
100+
},
101+
{
102+
label: "Close Tab",
103+
accelerator: "CmdOrCtrl+W",
104+
click: () => {
105+
const tabs = sessionManager.getTabs();
106+
const activeTab = tabs.find((t) => t.active);
107+
if (activeTab && tabs.length > 1) {
108+
sessionManager.closeTab(activeTab.id);
109+
} else if (mainWindow) {
110+
mainWindow.close();
111+
}
112+
},
113+
},
114+
{ type: "separator" as const },
115+
isMac ? { role: "close" as const } : { role: "quit" as const },
116+
],
117+
},
118+
// Edit menu
119+
{
120+
label: "Edit",
121+
submenu: [
122+
{ role: "undo" as const },
123+
{ role: "redo" as const },
124+
{ type: "separator" as const },
125+
{ role: "cut" as const },
126+
{ role: "copy" as const },
127+
{ role: "paste" as const },
128+
...(isMac
129+
? [
130+
{ role: "pasteAndMatchStyle" as const },
131+
{ role: "delete" as const },
132+
{ role: "selectAll" as const },
133+
]
134+
: [
135+
{ role: "delete" as const },
136+
{ type: "separator" as const },
137+
{ role: "selectAll" as const },
138+
]),
139+
],
140+
},
141+
// View menu
142+
{
143+
label: "View",
144+
submenu: [
145+
{
146+
label: "Reload Page",
147+
accelerator: "CmdOrCtrl+R",
148+
click: () => sessionManager.reload(),
149+
},
150+
{ type: "separator" as const },
151+
{ role: "resetZoom" as const },
152+
{ role: "zoomIn" as const },
153+
{ role: "zoomOut" as const },
154+
{ type: "separator" as const },
155+
{ role: "togglefullscreen" as const },
156+
{ type: "separator" as const },
157+
{
158+
label: "Toggle Bookmarks Bar",
159+
accelerator: "CmdOrCtrl+Shift+B",
160+
click: () => mainWindow?.webContents.send(IPC_CHANNELS.BOOKMARKS_TOGGLE),
161+
},
162+
{ type: "separator" as const },
163+
{
164+
label: "Developer Tools",
165+
accelerator: isMac ? "Cmd+Option+I" : "Ctrl+Shift+I",
166+
click: () => mainWindow?.webContents.openDevTools(),
167+
},
168+
],
169+
},
170+
// Navigate menu
171+
{
172+
label: "Navigate",
173+
submenu: [
174+
{
175+
label: "Back",
176+
accelerator: "Alt+Left",
177+
click: () => sessionManager.goBack(),
178+
},
179+
{
180+
label: "Forward",
181+
accelerator: "Alt+Right",
182+
click: () => sessionManager.goForward(),
183+
},
184+
{ type: "separator" as const },
185+
{
186+
label: "Next Tab",
187+
accelerator: "CmdOrCtrl+Tab",
188+
click: () => {
189+
const tabs = sessionManager.getTabs();
190+
const activeIndex = tabs.findIndex((t) => t.active);
191+
const nextIndex = (activeIndex + 1) % tabs.length;
192+
if (tabs[nextIndex]) {
193+
sessionManager.switchTab(tabs[nextIndex].id);
194+
}
195+
},
196+
},
197+
{
198+
label: "Previous Tab",
199+
accelerator: "CmdOrCtrl+Shift+Tab",
200+
click: () => {
201+
const tabs = sessionManager.getTabs();
202+
const activeIndex = tabs.findIndex((t) => t.active);
203+
const prevIndex = activeIndex === 0 ? tabs.length - 1 : activeIndex - 1;
204+
if (tabs[prevIndex]) {
205+
sessionManager.switchTab(tabs[prevIndex].id);
206+
}
207+
},
208+
},
209+
],
210+
},
211+
// Window menu
212+
{
213+
label: "Window",
214+
submenu: [
215+
{ role: "minimize" as const },
216+
{ role: "zoom" as const },
217+
...(isMac
218+
? [{ type: "separator" as const }, { role: "front" as const }]
219+
: [{ role: "close" as const }]),
220+
],
221+
},
222+
// Help menu
223+
{
224+
label: "Help",
225+
submenu: [
226+
{
227+
label: "About Browserbase",
228+
click: async () => {
229+
const { shell } = require("electron");
230+
await shell.openExternal("https://browserbase.com");
231+
},
232+
},
233+
],
234+
},
235+
];
236+
237+
const menu = Menu.buildFromTemplate(template);
238+
Menu.setApplicationMenu(menu);
239+
}
240+
68241
async function createWindow(): Promise<void> {
69242
const windowState = loadWindowState();
70243

@@ -97,7 +270,23 @@ async function createWindow(): Promise<void> {
97270
}
98271

99272
// Save window state on resize/move
100-
mainWindow.on("resize", () => mainWindow && saveWindowState(mainWindow));
273+
mainWindow.on("resize", () => {
274+
if (mainWindow) {
275+
saveWindowState(mainWindow);
276+
// Debounce viewport updates to avoid too many CDP calls during drag
277+
if (resizeTimeout) {
278+
clearTimeout(resizeTimeout);
279+
}
280+
resizeTimeout = setTimeout(() => {
281+
if (mainWindow) {
282+
const bounds = mainWindow.getContentBounds();
283+
const chromeUIHeight = 76; // Tab bar (36px) + nav bar (40px)
284+
const viewportHeight = Math.max(400, bounds.height - chromeUIHeight);
285+
sessionManager.updateViewport(bounds.width, viewportHeight);
286+
}
287+
}, 150); // 150ms debounce
288+
}
289+
});
101290
mainWindow.on("move", () => mainWindow && saveWindowState(mainWindow));
102291
mainWindow.on("close", () => mainWindow && saveWindowState(mainWindow));
103292

@@ -211,6 +400,17 @@ function registerShortcuts(): void {
211400
}
212401
});
213402

403+
// Ctrl+Shift+I / Cmd+Option+I - DevTools
404+
const devToolsAccelerator = process.platform === "darwin" ? "Command+Option+I" : "Control+Shift+I";
405+
globalShortcut.register(devToolsAccelerator, () => {
406+
mainWindow?.webContents.openDevTools();
407+
});
408+
409+
// F12 - DevTools (common shortcut)
410+
globalShortcut.register("F12", () => {
411+
mainWindow?.webContents.openDevTools();
412+
});
413+
214414
// Ctrl+1-9 - Switch to specific tab
215415
for (let i = 1; i <= 9; i++) {
216416
globalShortcut.register(`CommandOrControl+${i}`, () => {
@@ -248,6 +448,7 @@ app.whenReady().then(async () => {
248448
return;
249449
}
250450

451+
createApplicationMenu();
251452
setupContentSecurityPolicy();
252453
await createWindow();
253454

src/main/ipc.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,29 @@ export function setupIpcHandlers(mainWindow: BrowserWindow): void {
104104
ipcMain.on(IPC_CHANNELS.BOOKMARKS_TOGGLE, () => {
105105
mainWindow.webContents.send(IPC_CHANNELS.BOOKMARKS_TOGGLE);
106106
});
107+
108+
// Menu action handlers
109+
ipcMain.on(IPC_CHANNELS.OPEN_DEVTOOLS, () => {
110+
mainWindow.webContents.openDevTools();
111+
});
112+
113+
ipcMain.on(IPC_CHANNELS.TOGGLE_FULLSCREEN, () => {
114+
mainWindow.setFullScreen(!mainWindow.isFullScreen());
115+
});
116+
117+
ipcMain.on(IPC_CHANNELS.ZOOM_IN, () => {
118+
const currentZoom = mainWindow.webContents.getZoomLevel();
119+
mainWindow.webContents.setZoomLevel(currentZoom + 0.5);
120+
});
121+
122+
ipcMain.on(IPC_CHANNELS.ZOOM_OUT, () => {
123+
const currentZoom = mainWindow.webContents.getZoomLevel();
124+
mainWindow.webContents.setZoomLevel(currentZoom - 0.5);
125+
});
126+
127+
ipcMain.on(IPC_CHANNELS.ZOOM_RESET, () => {
128+
mainWindow.webContents.setZoomLevel(0);
129+
});
107130
}
108131

109132
export function removeIpcHandlers(): void {

src/main/preload.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ contextBridge.exposeInMainWorld("electronAPI", {
2323
maximizeWindow: () => ipcRenderer.send(IPC_CHANNELS.WINDOW_MAXIMIZE),
2424
closeWindow: () => ipcRenderer.send(IPC_CHANNELS.WINDOW_CLOSE),
2525

26+
// Menu actions
27+
openDevTools: () => ipcRenderer.send(IPC_CHANNELS.OPEN_DEVTOOLS),
28+
toggleFullscreen: () => ipcRenderer.send(IPC_CHANNELS.TOGGLE_FULLSCREEN),
29+
zoomIn: () => ipcRenderer.send(IPC_CHANNELS.ZOOM_IN),
30+
zoomOut: () => ipcRenderer.send(IPC_CHANNELS.ZOOM_OUT),
31+
zoomReset: () => ipcRenderer.send(IPC_CHANNELS.ZOOM_RESET),
32+
2633
// Bookmarks
2734
toggleBookmarks: () => ipcRenderer.send(IPC_CHANNELS.BOOKMARKS_TOGGLE),
2835

@@ -105,6 +112,11 @@ declare global {
105112
minimizeWindow: () => void;
106113
maximizeWindow: () => void;
107114
closeWindow: () => void;
115+
openDevTools: () => void;
116+
toggleFullscreen: () => void;
117+
zoomIn: () => void;
118+
zoomOut: () => void;
119+
zoomReset: () => void;
108120
toggleBookmarks: () => void;
109121
onTabsUpdated: (callback: (tabs: TabInfo[]) => void) => () => void;
110122
onUrlChanged: (callback: (url: string) => void) => () => void;

src/main/session.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,40 @@ export class SessionManager {
2525
this.mainWindow = window;
2626
}
2727

28+
async updateViewport(width: number, height: number): Promise<void> {
29+
const activePage = this.getActivePage();
30+
if (!activePage) {
31+
console.log("[Viewport] No active page to update viewport");
32+
return;
33+
}
34+
35+
try {
36+
// Get or create CDP session for the active page
37+
let cdp = this.cdpSession;
38+
if (!cdp) {
39+
cdp = await activePage.context().newCDPSession(activePage);
40+
this.cdpSession = cdp;
41+
}
42+
43+
const deviceScaleFactor = process.platform === "darwin" ? 2 : 1;
44+
45+
console.log(`[Viewport] Updating viewport to ${width}x${height} (scale: ${deviceScaleFactor})`);
46+
47+
await cdp.send("Emulation.setDeviceMetricsOverride", {
48+
width,
49+
height,
50+
deviceScaleFactor,
51+
mobile: false,
52+
});
53+
54+
console.log("[Viewport] Viewport updated successfully");
55+
} catch (error) {
56+
console.error("[Viewport] Failed to update viewport:", error);
57+
// Try to recreate CDP session if it failed
58+
this.cdpSession = null;
59+
}
60+
}
61+
2862
async initialize(): Promise<BrowserbaseSession> {
2963
try {
3064
// Calculate viewport size based on window content area
@@ -316,6 +350,11 @@ export class SessionManager {
316350
const newPage = await this.context.newPage();
317351
const pages = this.context.pages();
318352
this.activeTabIndex = pages.indexOf(newPage);
353+
354+
// Navigate to Google like the initial tab
355+
const defaultUrl = process.env.BROWSERBASE_DEFAULT_URL || "https://www.google.com";
356+
await newPage.goto(defaultUrl, { waitUntil: "domcontentloaded", timeout: 60000 });
357+
319358
await this.syncTabs();
320359

321360
// Wait a moment for the page to register, then get the updated debug URL

0 commit comments

Comments
 (0)