Create and control top-level application windows. BrowserWindow is Bunmaska’s drop-in equivalent of Electron’s class of the same name: it extends Node’s EventEmitter, owns a WebContents for all page-related operations, and is backed by NSWindow on macOS, a GTK4 GtkWindow on Linux, and a Win32 HWND on Windows.
Like Electron, you cannot use this module until the app ready event has fired.
// In the main process.
import { app, BrowserWindow } from 'bunmaska';
app.whenReady().then(() => {
const win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL('https://github.com');
// Or a local file:
// win.loadFile('index.html');
});
Constructor
new BrowserWindow([options])
Creates a new window. All options are optional; unspecified options fall back to Bunmaska’s defaults (800x600, title "Bunmaska", shown immediately, resizable).
The supported options are a deliberately small subset of Electron’s BrowserWindowConstructorOptions:
widthnumber - content width in pixels. Default800.heightnumber - content height in pixels. Default600.titlestring - initial window title. Default"Bunmaska".showboolean - show the window immediately on creation. Defaulttrue.resizableboolean - whether the user can resize the window. Defaulttrue.frameboolean - draw the OS frame/title bar.falseopens a frameless window.fullscreenboolean - open in fullscreen. Defaultfalse.webPreferencesobject - per-window renderer preferences. The only supported key ispreload: an absolute-resolved path to a script run before the page’s own scripts - in an isolated world on macOS and Linux (Electron’scontextIsolation: true), in the page world on Windows (see contextBridge). It is read synchronously at construction; an unreadable path throws. A preload that usesimport/exportmust go throughbunmaska dev/build(they bundle it) - handed to the constructor raw, construction throwsInvalidArgumentError.
import { BrowserWindow } from 'bunmaska';
import { join } from 'node:path';
const win = new BrowserWindow({
width: 1024,
height: 768,
title: 'My App',
frame: false,
webPreferences: {
preload: join(import.meta.dir, 'preload.ts'),
},
});
There is no BrowserWindowConstructorOptions mega-bag here - if an option isn’t in the list above, it isn’t wired yet. See Not in Bunmaska (yet).
Methods
Objects created with new BrowserWindow have the following instance methods.
win.loadURL(url)
loadURL(url: string): void
Navigates the window’s web contents to a URL (remote http(s):// or local file://). Delegates to webContents.loadURL. Unlike Electron, this returns void, not a Promise - await navigation via webContents events if you need to.
const win = new BrowserWindow();
win.loadURL('https://example.com');
win.loadFile(filePath[, options])
loadFile(filePath: string, options?: LoadFileOptions): void
Loads a local HTML file into the window’s web contents. Delegates to webContents.loadFile (same behavior and options), so relative paths resolve against the current working directory and the resolved path is percent-encoded before becoming a file:// URL - spaces, #, or ? in the file name load correctly. Put a route fragment in options.hash, not inside filePath. Returns void.
The optional options mirror Electron:
hashstring - URL fragment appended after#(e.g. a hash-router route).queryobject - query params as aRecord<string, string>, serialized to a query string.searchstring - a raw query string; takes precedence overquery.
const win = new BrowserWindow();
win.loadFile('index.html');
// With a hash-router route and query params:
win.loadFile('app.html', { hash: '/settings', query: { theme: 'dark' } });
win.setTitle(title)
setTitle(title: string): void
Changes the title of the native window.
win.setTitle('Editing - untitled.txt');
win.getTitle()
getTitle(): string
Returns the current title of the native window. (As in Electron, this can differ from the page’s document.title.)
console.log(win.getTitle());
win.setSize(width, height)
setSize(width: number, height: number): void
Resizes the window to width by height - the outer window (frame) size, as in Electron. On macOS the top-left corner stays anchored. On Linux it sets the GTK default (content) size, not the outer frame. There is no animate argument.
win.setSize(1280, 720);
win.getSize()
getSize(): [number, number]
Returns the window’s [width, height] in pixels (derived from getBounds()).
const [w, h] = win.getSize();
win.getBounds()
getBounds(): { x: number; y: number; width: number; height: number }
Returns the window’s bounds as a rectangle - Electron’s contract: the real on-screen frame, with x/y in global top-left screen coordinates. On macOS this is read from the window server (via the CGWindowList), so it reflects where the user actually dragged the window, not just where you last placed it. Linux reports x/y as 0 (GTK4/Wayland forbid introspecting global window coordinates), so treat position as size-only there.
const { x, y, width, height } = win.getBounds();
win.setBounds(bounds)
setBounds(bounds: { x: number; y: number; width: number; height: number }): void
Moves and resizes the window to the given frame rectangle, in global top-left coordinates. Full on macOS and Windows. On Linux only the size is applied - GTK4 removed programmatic positioning and Wayland forbids clients moving themselves, so position stays with the compositor.
win.setBounds({ x: 100, y: 80, width: 1280, height: 720 });
win.setPosition(x, y)
setPosition(x: number, y: number): void
Moves the window so its top-left corner sits at (x, y) in screen pixels, keeping its size. Full on macOS and Windows; a deliberate no-op on Linux (compositor-owned placement, as above). No animate argument.
win.setPosition(100, 80);
win.getPosition()
getPosition(): [number, number]
Returns the window’s [x, y] top-left position (derived from getBounds(), so the Linux caveat applies).
const [x, y] = win.getPosition();
win.setResizable(resizable)
setResizable(resizable: boolean): void
Enables or disables user resizing. On macOS this flips the NSResizableWindowMask style bit; on Linux it calls gtk_window_set_resizable.
win.setResizable(false);
win.isResizable()
isResizable(): boolean
Returns whether the window is user-resizable (tracked from the constructor option and setResizable).
if (!win.isResizable()) { /* ... */ }
win.setMinimumSize(width, height)
setMinimumSize(width: number, height: number): void
Constrains the window’s minimum content size. No-op on Windows: enforcing a minimum size needs WM_GETMINMAXINFO, which the pump-routed native window proc can’t intercept, so the value is tracked (and returned by getMinimumSize) but not enforced there.
win.setMinimumSize(400, 300);
win.getMinimumSize()
getMinimumSize(): [number, number]
Returns the window’s minimum [width, height], or [0, 0] if none was set.
const [minW, minH] = win.getMinimumSize();
win.setOpacity(opacity)
setOpacity(opacity: number): void
Sets window opacity, clamped to [0, 1] (1 = fully opaque). Backed by -[NSWindow setAlphaValue:] on macOS and gtk_widget_set_opacity on Linux, and wired on Windows too. (Unlike Electron, where setOpacity is a no-op on Linux.)
win.setOpacity(0.85);
win.getOpacity()
getOpacity(): number
Returns the last-set opacity in [0, 1].
console.log(win.getOpacity()); // 0.85
win.center()
center(): void
Centers the window on the current screen. Works on macOS and Windows; on Linux this is a deliberate no-op, since GTK4 removed programmatic positioning and Wayland forbids clients from moving themselves (the compositor places the window).
win.center();
win.show()
show(): void
Shows the window and brings it to the front. Emits show (and, via the focus path, may emit focus).
const win = new BrowserWindow({ show: false });
win.once('ready-to-show', () => win.show());
win.hide()
hide(): void
Hides the window. Emits hide.
win.hide();
win.isVisible()
isVisible(): boolean
Returns whether the window is currently visible.
if (win.isVisible()) win.hide();
win.focus()
focus(): void
Gives the window keyboard focus and brings it forward.
win.focus();
win.isFocused()
isFocused(): boolean
Returns whether the window is the key/active window.
if (win.isFocused()) { /* ... */ }
win.minimize()
minimize(): void
Minimizes the window to the Dock/taskbar.
win.minimize();
win.isMinimized()
isMinimized(): boolean
Returns whether the window is minimized.
if (win.isMinimized()) win.restore();
win.restore()
restore(): void
Restores the window from a minimized state.
win.restore();
win.maximize()
maximize(): void
Maximizes (macOS: zooms) the window. On macOS it no-ops if already zoomed.
win.maximize();
win.unmaximize()
unmaximize(): void
Restores the window from a maximized state.
win.unmaximize();
win.isMaximized()
isMaximized(): boolean
Returns whether the window is maximized (macOS: zoomed).
console.log(win.isMaximized());
win.setFullScreen(flag)
setFullScreen(flag: boolean): void
Enters or leaves fullscreen mode. On macOS this toggles native fullscreen (only when the state actually needs to change); on Linux it calls gtk_window_fullscreen / unfullscreen.
win.setFullScreen(true);
win.isFullScreen()
isFullScreen(): boolean
Returns whether the window is in fullscreen mode.
if (win.isFullScreen()) win.setFullScreen(false);
win.setAlwaysOnTop(flag)
setAlwaysOnTop(flag: boolean): void
Sets whether the window floats above other windows. Works on macOS (-[NSWindow setLevel:], floating level) and Windows. On Linux this is a best-effort no-op - GTK4 dropped the keep-above hint and offers no portable client API.
win.setAlwaysOnTop(true); // honored on macOS and Windows, no-op on Linux
win.close()
close(): void
Tries to close the window, routing through the same path as the user clicking the title-bar close button. A close listener may veto it via event.preventDefault(). If not vetoed, the closed event fires.
win.on('close', (e) => {
if (hasUnsavedChanges) e.preventDefault();
});
win.close();
win.destroy()
destroy(): void
Force-closes the window without consulting close listeners. The closed event still fires.
win.destroy();
win.isDestroyed()
isDestroyed(): boolean
Returns whether the window has been closed/destroyed. After closed, drop your reference and stop using the instance.
if (!win.isDestroyed()) win.focus();
Static methods
BrowserWindow.getAllWindows()
static getAllWindows(): BrowserWindow[]
Returns all open windows in creation order.
import { BrowserWindow } from 'bunmaska';
for (const win of BrowserWindow.getAllWindows()) {
win.close();
}
BrowserWindow.fromId(id)
static fromId(id: number): BrowserWindow | undefined
Returns the window with the given id, or undefined if none. Note: unlike Electron (which returns null), the miss value here is undefined.
const win = BrowserWindow.fromId(1);
win?.focus();
Events
BrowserWindow is an EventEmitter. The following events are emitted.
Event: ‘closed’
Emitted when the window has been closed. After receiving it, remove your reference to the window and stop using it.
win.on('closed', () => {
console.log('window gone');
});
Event: ‘close’
Returns: event - an object with preventDefault() and a defaultPrevented getter.
Emitted when the window is about to close. Calling event.preventDefault() vetoes the close and keeps the window open. This is the hook for “unsaved changes?” prompts.
win.on('close', (event) => {
if (hasUnsavedWork()) {
event.preventDefault();
}
});
Event: ‘focus’
Emitted when the window gains focus. (Also drives app’s browser-window-focus.)
win.on('focus', () => console.log('focused'));
Event: ‘blur’
Emitted when the window loses focus. (Also drives app’s browser-window-blur.)
win.on('blur', () => console.log('blurred'));
Event: ‘show’
Emitted when the window is shown.
Event: ‘hide’
Emitted when the window is hidden.
Event: ‘resize’
Emitted after the window has been resized.
Event: ‘move’
Emitted after the window has been moved - a user drag or a programmatic setPosition/setBounds. macOS only today: Linux never emits it (window position is compositor-owned under GTK4/Wayland), and Windows does not wire it yet.
Event: ‘maximize’
Emitted when the window is maximized. Fires on all three platforms (macOS via the window delegate).
Event: ‘unmaximize’
Emitted when the window leaves a maximized state. Fires on all three platforms.
Event: ‘minimize’
Emitted when the window is minimized. Never fires on Linux - GTK4 exposes no minimized state (isMinimized() still tracks what you called).
Event: ‘restore’
Emitted when the window is restored from a minimized state. Never fires on Linux, for the same reason as minimize.
Event: ‘ready-to-show’
Emitted when the page has been rendered (while not yet shown) and the window can be displayed without a visual flash. Fires at dom-ready on Windows and at the first did-finish-load on macOS and Linux. The standard pattern is to construct with show: false and show on this event.
const win = new BrowserWindow({ show: false });
win.once('ready-to-show', () => win.show());
win.loadURL('https://example.com');
Properties
win.id Readonly
A process-unique number identifying the window, matching Electron’s BrowserWindow.id. Used as the key for BrowserWindow.fromId.
const win = new BrowserWindow();
console.log(win.id); // e.g. 1
win.webContents Readonly
The WebContents this window owns. All page-related operations (navigation, IPC, JS execution) go through it. See the WebContents reference for its methods and events.
const win = new BrowserWindow();
win.webContents.on('did-finish-load', () => {
win.webContents.executeJavaScript('document.title');
});
Not in Bunmaska (yet)
Bunmaska implements the window-management core but omits large swaths of Electron’s BrowserWindow surface. Notable gaps:
setContentBounds/getContentBounds/getNormalBounds- the content-bounds variants. (setPosition/getPosition/setBounds/getBoundsare documented above: real frame geometry on macOS and Windows, size-only on Linux where GTK4/Wayland leaves placement to the compositor.)setContentSize/getContentSize/getMaximumSize/setMaximumSize- only the minimum-size pair andsetSizeare wired.setMovable/setMinimizable/setMaximizable/setClosable/setFocusableand their getters - the constraint setters beyondsetResizableare absent.setBackgroundColor/getBackgroundColor,setHasShadow/hasShadow,setVibrancy- no appearance/material APIs.setIcon,setProgressBar,flashFrame,setSkipTaskbar,setKiosk/isKiosk,setMenu/removeMenu- taskbar/dock/menu-bar and kiosk controls are not implemented.capturePage,getNativeWindowHandle,getMediaSourceId,moveTop/moveAbove,setAspectRatio- missing.- Parent/child & modal windows - no
parent/modalconstructor options, and nosetParentWindow/getParentWindow/getChildWindows. Child and modal windows don’t exist yet. - macOS tabbing, Touch Bar, simple-fullscreen, represented file, traffic-light positioning, content protection - none of the macOS-only flair (
setSimpleFullScreen,addTabbedWindow,setTouchBar,setRepresentedFilename,setWindowButtonVisibility,setContentProtection, …) is wired. BrowserWindow.getFocusedWindow/fromWebContents- onlygetAllWindowsandfromIdare exposed as statics.- Events -
page-title-updated,enter-full-screen/leave-full-screen,moved(the throttled variant; plainmoveis emitted, macOS only),will-resize/resized,always-on-top-changed, and the various platform-specific gesture events (swipe,rotate-gesture,app-command, …) are not emitted. The implemented set is the lifecycle list above. - Constructor options - beyond the seven documented keys (plus
webPreferences.preload), the rest ofBrowserWindowConstructorOptions(e.g.backgroundColor,transparent,alwaysOnTop,parent,modal,minWidth/minHeight,titleBarStyle, the fullwebPreferencesbag) is ignored.
If you need one of these, it’s genuinely not there - not hidden behind a flag.