Window Management API: Complete Technical Deep Dive
Window Management API: Complete Technical Deep Dive
The Window Management API enables web applications to enumerate connected displays and position windows across them. Understanding its internals reveals how browsers provide multi-monitor awareness while maintaining the security boundaries that protect user privacy.
Why Window Management API Exists
Before this API, web apps were blind to multi-monitor setups:
┌─────────────────────────────────────────────────────────────────────┐
│ Pre-Window Management API Era │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Problems Web Applications Faced: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. No Multi-Monitor Awareness │ │
│ │ • window.screen only showed current monitor │ │
│ │ • No way to know how many monitors existed │ │
│ │ • Couldn't detect monitor arrangement │ │
│ │ │ │
│ │ 2. Limited Window Positioning │ │
│ │ • window.open() could only position on current screen │ │
│ │ • Couldn't request specific monitor for new windows │ │
│ │ • moveTo() clamped to current screen bounds │ │
│ │ │ │
│ │ 3. Poor Multi-Monitor UX │ │
│ │ • Presentation apps couldn't show slides on projector │ │
│ │ • Trading apps couldn't span multiple monitors │ │
│ │ • Video editors couldn't have timeline on second screen │ │
│ │ • Dialogs would open on wrong monitor │ │
│ │ │ │
│ │ 4. Fullscreen Limitations │ │
│ │ • requestFullscreen() only worked on current monitor │ │
│ │ • No way to fullscreen to secondary display │ │
│ │ • Presentation mode was severely limited │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Workarounds (All Hacky/Incomplete): │
│ • Open window with extreme coordinates hoping it lands elsewhere │
│ • Ask user to manually drag windows to desired monitor │
│ • Use screen.availWidth as proxy (unreliable) │
│ • Native app wrappers (Electron) for multi-monitor support │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Window Management API Solution │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Complete Multi-Monitor Control │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Monitor 1 │ │ Monitor 2 │ │ │
│ │ │ (Primary) │ │ (Secondary) │ │ │
│ │ │ │ │ │ │ │
│ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ │
│ │ │ │ Main App │ │ │ │ Slideshow │ │ │ │
│ │ │ │ Controls │ │ │ │ Fullscreen│ │ │ │
│ │ │ └───────────┘ │ │ └───────────┘ │ │ │
│ │ │ │ │ │ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ │ API Capabilities: │ │
│ │ • Enumerate all connected displays │ │
│ │ • Get screen geometry (position, size, color depth) │ │
│ │ • Position windows on specific monitors │ │
│ │ • Request fullscreen on any monitor │ │
│ │ • Detect display changes (connect/disconnect) │ │
│ │ • Get internal vs external display info │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Use Cases Enabled: │
│ • Presentation mode (notes on laptop, slides on projector) │
│ • Trading platforms (multiple windows across monitors) │
│ • Video editing (preview on external, timeline on primary) │
│ • Photo culling (full image on color-accurate external) │
│ • Productivity apps (tools on one screen, canvas on another) │
│ │
└─────────────────────────────────────────────────────────────────────┘
Architecture Deep Dive
Browser Internal Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ Window Management Internal Architecture │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Web Application (Renderer Process) │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ JavaScript Context │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ const screens = await window.getScreenDetails(); │ │ │
│ │ │ const externalScreen = screens.screens.find(s => !s.isPrimary);│ │ │
│ │ │ window.open(url, '_blank', `left=${externalScreen.left}`) │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ Window Management Interface │ │ │
│ │ │ • Check permission state │ │ │
│ │ │ • Validate secure context │ │ │
│ │ │ • Send IPC to Browser Process │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ IPC (Mojo) │
│ ▼ │
│ Browser Process (Privileged) │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ │
│ │ │ Permission Service │ │ Screen Enumeration Service │ │ │
│ │ │ • Check granted │────▶│ • Query display configuration │ │ │
│ │ │ • Show prompt if │ │ • Calculate virtual screen space │ │ │
│ │ │ needed │ │ • Monitor for changes │ │ │
│ │ │ • Persist decision │ │ • Filter based on permission │ │ │
│ │ └─────────────────────┘ └─────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ Permission Prompt (if needed) │ │ │
│ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ "example.com wants to know about your screens" │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ This allows the site to: │ │ │ │
│ │ │ │ • See how many screens you have │ │ │ │
│ │ │ │ • Place windows on different screens │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ [Block] [Allow] │ │ │ │
│ │ │ └───────────────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ Permission granted │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ Platform Display APIs │ │ │
│ │ │ ┌───────────────┬───────────────┬──────────────────────┐ │ │ │
│ │ │ │ Windows │ macOS │ Linux │ │ │ │
│ │ │ │ EnumDisplay │ NSScreen │ X11/Wayland │ │ │ │
│ │ │ │ Monitors() │ screens │ display APIs │ │ │ │
│ │ │ │ DEVMODE │ deviceDesc. │ xrandr/wl_output │ │ │ │
│ │ │ └───────────────┴───────────────┴──────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ └──────────────────────────────────────────┼─────────────────────────┘ │
│ │ │
│ ▼ │
│ ScreenDetails Object Returned to Renderer │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ screens: [ │ │
│ │ { │ │
│ │ availLeft: 0, availTop: 0, │ │
│ │ availWidth: 1920, availHeight: 1040, │ │
│ │ left: 0, top: 0, width: 1920, height: 1080, │ │
│ │ colorDepth: 24, pixelDepth: 24, │ │
│ │ devicePixelRatio: 1, isInternal: true, isPrimary: true, │ │
│ │ label: "Built-in Retina Display" │ │
│ │ }, │ │
│ │ { ... secondary screen ... } │ │
│ │ ], │ │
│ │ currentScreen: { ... } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
Screen Coordinate System
┌─────────────────────────────────────────────────────────────────────┐
│ Virtual Screen Space │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Multiple monitors form a virtual coordinate space: │
│ │
│ (-1920, 0) (0, 0) (1920, 0) │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┬───────────────┬───────────────┐ │
│ │ │ │ │ │
│ │ Monitor 1 │ Monitor 2 │ Monitor 3 │ │
│ │ 1920x1080 │ 1920x1080 │ 1920x1080 │ │
│ │ (Left) │ (Primary) │ (Right) │ │
│ │ │ │ │ │
│ └───────────────┴───────────────┴───────────────┘ │
│ │
│ Common Arrangements: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Side-by-Side: Stacked: L-Shaped: │ │
│ │ ┌────┬────┐ ┌────┐ ┌────┐ │ │
│ │ │ 1 │ 2 │ │ 1 │ │ 1 ├────┐ │ │
│ │ └────┴────┘ ├────┤ └────┤ 2 │ │ │
│ │ │ 2 │ └────┘ │ │
│ │ └────┘ │ │
│ │ │ │
│ │ Note: Origin (0,0) is typically top-left of primary │ │
│ │ Monitors to the left have negative X coordinates │ │
│ │ Monitors above have negative Y coordinates │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Screen Properties Explained: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ left, top: Position in virtual space │ │
│ │ width, height: Total screen dimensions │ │
│ │ │ │
│ │ availLeft, availTop: Usable area (excludes taskbar) │ │
│ │ availWidth, availHeight: Usable dimensions │ │
│ │ │ │
│ │ devicePixelRatio: Physical pixels per CSS pixel │ │
│ │ (2 for Retina/HiDPI displays) │ │
│ │ │ │
│ │ isInternal: Built-in display (laptop screen) │ │
│ │ isPrimary: Primary/main display │ │
│ │ label: User-friendly display name │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Complete API Implementation
Screen Manager
// window-management.ts
interface ScreenInfo {
availLeft: number;
availTop: number;
availWidth: number;
availHeight: number;
left: number;
top: number;
width: number;
height: number;
colorDepth: number;
pixelDepth: number;
devicePixelRatio: number;
isInternal: boolean;
isPrimary: boolean;
label: string;
}
interface ScreenDetails {
screens: ScreenInfo[];
currentScreen: ScreenInfo;
}
class WindowManagementManager {
private screenDetails: ScreenDetails | null = null;
private listeners = new Set<(screens: ScreenInfo[]) => void>();
private changeListener: ((event: Event) => void) | null = null;
static isSupported(): boolean {
return 'getScreenDetails' in window;
}
async requestPermission(): Promise<PermissionState> {
if (!WindowManagementManager.isSupported()) {
return 'denied';
}
try {
// Requesting screen details implicitly requests permission
const details = await (window as any).getScreenDetails();
this.screenDetails = details;
this.setupChangeListener(details);
return 'granted';
} catch (error) {
if (error instanceof DOMException) {
if (error.name === 'NotAllowedError') {
return 'denied';
}
}
throw error;
}
}
async getScreenDetails(): Promise<ScreenDetails> {
if (!WindowManagementManager.isSupported()) {
// Fallback to basic screen info
return this.getFallbackScreenDetails();
}
if (this.screenDetails) {
return this.screenDetails;
}
try {
this.screenDetails = await (window as any).getScreenDetails();
this.setupChangeListener(this.screenDetails);
return this.screenDetails;
} catch (error) {
console.warn('Failed to get screen details, using fallback');
return this.getFallbackScreenDetails();
}
}
private getFallbackScreenDetails(): ScreenDetails {
// Use basic window.screen for single-monitor fallback
const screen: ScreenInfo = {
availLeft: window.screen.availLeft || 0,
availTop: window.screen.availTop || 0,
availWidth: window.screen.availWidth,
availHeight: window.screen.availHeight,
left: 0,
top: 0,
width: window.screen.width,
height: window.screen.height,
colorDepth: window.screen.colorDepth,
pixelDepth: window.screen.pixelDepth,
devicePixelRatio: window.devicePixelRatio,
isInternal: true,
isPrimary: true,
label: 'Primary Display'
};
return {
screens: [screen],
currentScreen: screen
};
}
private setupChangeListener(details: any): void {
if (this.changeListener) return;
this.changeListener = () => {
// Re-fetch screen details on change
this.refreshScreenDetails();
};
details.addEventListener('screenschange', this.changeListener);
}
private async refreshScreenDetails(): Promise<void> {
try {
this.screenDetails = await (window as any).getScreenDetails();
this.notifyListeners();
} catch (error) {
console.error('Failed to refresh screen details:', error);
}
}
// Subscribe to screen changes
onScreensChange(callback: (screens: ScreenInfo[]) => void): () => void {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
private notifyListeners(): void {
const screens = this.screenDetails?.screens || [];
this.listeners.forEach(listener => listener(screens));
}
// Utility methods
getPrimaryScreen(): ScreenInfo | null {
return this.screenDetails?.screens.find(s => s.isPrimary) || null;
}
getExternalScreens(): ScreenInfo[] {
return this.screenDetails?.screens.filter(s => !s.isInternal) || [];
}
getCurrentScreen(): ScreenInfo | null {
return this.screenDetails?.currentScreen || null;
}
getScreenCount(): number {
return this.screenDetails?.screens.length || 1;
}
isMultiScreen(): boolean {
return this.getScreenCount() > 1;
}
// Find screen by position
getScreenAtPoint(x: number, y: number): ScreenInfo | null {
if (!this.screenDetails) return null;
return this.screenDetails.screens.find(screen =>
x >= screen.left &&
x < screen.left + screen.width &&
y >= screen.top &&
y < screen.top + screen.height
) || null;
}
// Get screen by label (for persistence)
getScreenByLabel(label: string): ScreenInfo | null {
return this.screenDetails?.screens.find(s => s.label === label) || null;
}
}
Window Placement Utilities
// window-placement.ts
interface WindowPlacementOptions {
screen?: ScreenInfo;
position?: 'center' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
width?: number;
height?: number;
fullscreen?: boolean;
}
class WindowPlacement {
private screenManager = new WindowManagementManager();
async initialize(): Promise<void> {
await this.screenManager.requestPermission();
}
// Open window on specific screen
async openOnScreen(
url: string,
options: WindowPlacementOptions = {}
): Promise<Window | null> {
const targetScreen = options.screen ||
await this.getBestScreen(options);
const width = options.width || 800;
const height = options.height || 600;
const { left, top } = this.calculatePosition(
targetScreen,
width,
height,
options.position || 'center'
);
const features = [
`left=${left}`,
`top=${top}`,
`width=${width}`,
`height=${height}`
].join(',');
const newWindow = window.open(url, '_blank', features);
// Request fullscreen if specified
if (options.fullscreen && newWindow) {
// Need to wait for window to load
newWindow.addEventListener('load', () => {
this.makeFullscreen(newWindow, targetScreen);
});
}
return newWindow;
}
private async getBestScreen(options: WindowPlacementOptions): Promise<ScreenInfo> {
const details = await this.screenManager.getScreenDetails();
// Default to primary screen
return details.screens.find(s => s.isPrimary) || details.screens[0];
}
private calculatePosition(
screen: ScreenInfo,
width: number,
height: number,
position: WindowPlacementOptions['position']
): { left: number; top: number } {
const availLeft = screen.availLeft;
const availTop = screen.availTop;
const availWidth = screen.availWidth;
const availHeight = screen.availHeight;
switch (position) {
case 'topLeft':
return { left: availLeft, top: availTop };
case 'topRight':
return { left: availLeft + availWidth - width, top: availTop };
case 'bottomLeft':
return { left: availLeft, top: availTop + availHeight - height };
case 'bottomRight':
return {
left: availLeft + availWidth - width,
top: availTop + availHeight - height
};
case 'center':
default:
return {
left: availLeft + (availWidth - width) / 2,
top: availTop + (availHeight - height) / 2
};
}
}
// Request fullscreen on specific screen
async makeFullscreen(
target: Window | Element,
screen?: ScreenInfo
): Promise<void> {
const element = target instanceof Window
? target.document.documentElement
: target;
const fullscreenOptions: FullscreenOptions & { screen?: ScreenInfo } = {};
if (screen) {
fullscreenOptions.screen = screen;
}
try {
await element.requestFullscreen(fullscreenOptions);
} catch (error) {
console.error('Fullscreen request failed:', error);
}
}
// Move existing window to screen
async moveWindowToScreen(
targetWindow: Window,
screen: ScreenInfo,
options: { position?: WindowPlacementOptions['position'] } = {}
): Promise<void> {
const width = targetWindow.outerWidth;
const height = targetWindow.outerHeight;
const { left, top } = this.calculatePosition(
screen,
width,
height,
options.position || 'center'
);
targetWindow.moveTo(left, top);
}
// Tile windows across screens
async tileWindows(
windows: Window[],
screens?: ScreenInfo[]
): Promise<void> {
const availableScreens = screens ||
(await this.screenManager.getScreenDetails()).screens;
windows.forEach((win, index) => {
const screen = availableScreens[index % availableScreens.length];
const { left, top } = this.calculatePosition(
screen,
screen.availWidth,
screen.availHeight,
'topLeft'
);
win.moveTo(left, top);
win.resizeTo(screen.availWidth, screen.availHeight);
});
}
}
Real-World Production Patterns
1. Presentation Mode
// presentation-mode.ts
interface Slide {
id: string;
content: string;
notes?: string;
}
class PresentationMode {
private screenManager = new WindowManagementManager();
private presenterWindow: Window | null = null;
private slideshowWindow: Window | null = null;
private slides: Slide[] = [];
private currentSlideIndex = 0;
async initialize(slides: Slide[]): Promise<void> {
this.slides = slides;
await this.screenManager.requestPermission();
}
async startPresentation(): Promise<void> {
const details = await this.screenManager.getScreenDetails();
if (details.screens.length < 2) {
// Single monitor - show slideshow only
await this.startSingleMonitorMode();
return;
}
// Multi-monitor - presenter view on primary, slideshow on external
await this.startMultiMonitorMode(details);
}
private async startSingleMonitorMode(): Promise<void> {
// Open slideshow in current window, fullscreen
this.renderSlideshow(document.body);
await document.documentElement.requestFullscreen();
// Show presenter notes in a small overlay
this.addNotesOverlay();
}
private async startMultiMonitorMode(details: ScreenDetails): Promise<void> {
const primaryScreen = details.screens.find(s => s.isPrimary)!;
const externalScreen = details.screens.find(s => !s.isPrimary)!;
// Open slideshow on external screen
const slideshowUrl = this.createSlideshowDataUrl();
this.slideshowWindow = window.open(
slideshowUrl,
'slideshow',
this.getWindowFeatures(externalScreen, true)
);
// Wait for window to load, then fullscreen
if (this.slideshowWindow) {
this.slideshowWindow.addEventListener('load', async () => {
await this.slideshowWindow!.document.documentElement.requestFullscreen({
screen: externalScreen
} as FullscreenOptions);
});
}
// Keep presenter view on primary screen
this.renderPresenterView(document.body);
// Setup communication between windows
this.setupWindowCommunication();
}
private getWindowFeatures(screen: ScreenInfo, fullSize: boolean): string {
if (fullSize) {
return `left=${screen.left},top=${screen.top},width=${screen.width},height=${screen.height}`;
}
return `left=${screen.availLeft},top=${screen.availTop},width=${screen.availWidth},height=${screen.availHeight}`;
}
private createSlideshowDataUrl(): string {
const html = `
<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; background: #000; overflow: hidden; }
.slide { width: 100vw; height: 100vh; display: flex;
align-items: center; justify-content: center;
color: white; font-size: 48px; font-family: system-ui; }
</style>
</head>
<body>
<div class="slide" id="slideContent"></div>
<script>
window.addEventListener('message', (e) => {
if (e.data.type === 'SLIDE_CHANGE') {
document.getElementById('slideContent').innerHTML = e.data.content;
}
});
</script>
</body>
</html>
`;
return `data:text/html,${encodeURIComponent(html)}`;
}
private renderPresenterView(container: HTMLElement): void {
container.innerHTML = `
<div class="presenter-view">
<div class="current-slide"></div>
<div class="next-slide"></div>
<div class="notes"></div>
<div class="controls">
<button id="prevSlide">Previous</button>
<span id="slideCounter">1 / ${this.slides.length}</span>
<button id="nextSlide">Next</button>
</div>
<div class="timer" id="timer">00:00:00</div>
</div>
`;
this.setupPresenterControls(container);
this.updatePresenterView();
this.startTimer(container.querySelector('#timer')!);
}
private setupPresenterControls(container: HTMLElement): void {
container.querySelector('#prevSlide')?.addEventListener('click', () => {
this.previousSlide();
});
container.querySelector('#nextSlide')?.addEventListener('click', () => {
this.nextSlide();
});
// Keyboard controls
document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowRight':
case 'PageDown':
case ' ':
this.nextSlide();
break;
case 'ArrowLeft':
case 'PageUp':
this.previousSlide();
break;
case 'Escape':
this.endPresentation();
break;
}
});
}
private setupWindowCommunication(): void {
// Send initial slide
this.sendSlideToWindow();
}
private sendSlideToWindow(): void {
if (this.slideshowWindow) {
this.slideshowWindow.postMessage({
type: 'SLIDE_CHANGE',
content: this.slides[this.currentSlideIndex].content
}, '*');
}
}
private updatePresenterView(): void {
const currentSlide = this.slides[this.currentSlideIndex];
const nextSlide = this.slides[this.currentSlideIndex + 1];
const currentEl = document.querySelector('.current-slide');
const nextEl = document.querySelector('.next-slide');
const notesEl = document.querySelector('.notes');
const counterEl = document.querySelector('#slideCounter');
if (currentEl) currentEl.innerHTML = currentSlide.content;
if (nextEl) nextEl.innerHTML = nextSlide?.content || 'End of presentation';
if (notesEl) notesEl.textContent = currentSlide.notes || '';
if (counterEl) counterEl.textContent = `${this.currentSlideIndex + 1} / ${this.slides.length}`;
}
nextSlide(): void {
if (this.currentSlideIndex < this.slides.length - 1) {
this.currentSlideIndex++;
this.sendSlideToWindow();
this.updatePresenterView();
}
}
previousSlide(): void {
if (this.currentSlideIndex > 0) {
this.currentSlideIndex--;
this.sendSlideToWindow();
this.updatePresenterView();
}
}
goToSlide(index: number): void {
if (index >= 0 && index < this.slides.length) {
this.currentSlideIndex = index;
this.sendSlideToWindow();
this.updatePresenterView();
}
}
private startTimer(element: HTMLElement): void {
const startTime = Date.now();
setInterval(() => {
const elapsed = Date.now() - startTime;
const hours = Math.floor(elapsed / 3600000);
const minutes = Math.floor((elapsed % 3600000) / 60000);
const seconds = Math.floor((elapsed % 60000) / 1000);
element.textContent = [hours, minutes, seconds]
.map(n => n.toString().padStart(2, '0'))
.join(':');
}, 1000);
}
private addNotesOverlay(): void {
// For single-monitor mode
const overlay = document.createElement('div');
overlay.className = 'notes-overlay';
overlay.innerHTML = `
<div class="notes-content">${this.slides[this.currentSlideIndex].notes || ''}</div>
<button class="toggle-notes">Toggle Notes</button>
`;
document.body.appendChild(overlay);
}
private renderSlideshow(container: HTMLElement): void {
container.innerHTML = `
<div class="slideshow">
<div class="slide-content">${this.slides[this.currentSlideIndex].content}</div>
</div>
`;
}
async endPresentation(): Promise<void> {
// Exit fullscreen
if (document.fullscreenElement) {
await document.exitFullscreen();
}
// Close slideshow window
if (this.slideshowWindow) {
this.slideshowWindow.close();
this.slideshowWindow = null;
}
}
}
2. Multi-Window Trading Dashboard
// trading-dashboard.ts
interface DashboardWindow {
id: string;
type: 'chart' | 'watchlist' | 'orderbook' | 'positions' | 'news';
screen: ScreenInfo;
bounds: { left: number; top: number; width: number; height: number };
window?: Window;
}
interface DashboardLayout {
name: string;
windows: DashboardWindow[];
}
class TradingDashboard {
private screenManager = new WindowManagementManager();
private windows = new Map<string, DashboardWindow>();
private savedLayouts = new Map<string, DashboardLayout>();
async initialize(): Promise<void> {
await this.screenManager.requestPermission();
this.loadSavedLayouts();
// Listen for screen changes
this.screenManager.onScreensChange((screens) => {
this.handleScreenChange(screens);
});
}
// Save current window arrangement
async saveLayout(name: string): Promise<void> {
const layout: DashboardLayout = {
name,
windows: Array.from(this.windows.values()).map(w => ({
id: w.id,
type: w.type,
screen: w.screen,
bounds: this.getWindowBounds(w.window!)
}))
};
this.savedLayouts.set(name, layout);
localStorage.setItem(
'trading-layouts',
JSON.stringify(Array.from(this.savedLayouts.entries()))
);
}
// Restore a saved layout
async restoreLayout(name: string): Promise<void> {
const layout = this.savedLayouts.get(name);
if (!layout) {
throw new Error(`Layout "${name}" not found`);
}
const currentScreens = await this.screenManager.getScreenDetails();
for (const windowConfig of layout.windows) {
// Find matching screen or closest alternative
const targetScreen = this.findMatchingScreen(
windowConfig.screen,
currentScreens.screens
);
await this.openWindow(
windowConfig.type,
targetScreen,
windowConfig.bounds
);
}
}
private findMatchingScreen(
savedScreen: ScreenInfo,
currentScreens: ScreenInfo[]
): ScreenInfo {
// Try to match by label first
const byLabel = currentScreens.find(s => s.label === savedScreen.label);
if (byLabel) return byLabel;
// Try to match by position
const byPosition = currentScreens.find(
s => s.left === savedScreen.left && s.top === savedScreen.top
);
if (byPosition) return byPosition;
// Match by characteristics (internal/external, primary)
const byType = currentScreens.find(
s => s.isInternal === savedScreen.isInternal &&
s.isPrimary === savedScreen.isPrimary
);
if (byType) return byType;
// Fall back to first available
return currentScreens[0];
}
async openWindow(
type: DashboardWindow['type'],
screen: ScreenInfo,
bounds?: DashboardWindow['bounds']
): Promise<Window | null> {
const id = `${type}-${Date.now()}`;
const url = this.getUrlForType(type);
const defaultBounds = this.getDefaultBounds(type, screen);
const finalBounds = bounds || defaultBounds;
const features = [
`left=${screen.left + finalBounds.left}`,
`top=${screen.top + finalBounds.top}`,
`width=${finalBounds.width}`,
`height=${finalBounds.height}`
].join(',');
const newWindow = window.open(url, id, features);
if (newWindow) {
this.windows.set(id, {
id,
type,
screen,
bounds: finalBounds,
window: newWindow
});
// Track window close
newWindow.addEventListener('beforeunload', () => {
this.windows.delete(id);
});
}
return newWindow;
}
private getUrlForType(type: DashboardWindow['type']): string {
const baseUrl = window.location.origin;
return `${baseUrl}/dashboard/${type}`;
}
private getDefaultBounds(
type: DashboardWindow['type'],
screen: ScreenInfo
): DashboardWindow['bounds'] {
const availWidth = screen.availWidth;
const availHeight = screen.availHeight;
switch (type) {
case 'chart':
return { left: 0, top: 0, width: availWidth * 0.6, height: availHeight };
case 'watchlist':
return { left: availWidth * 0.6, top: 0, width: availWidth * 0.4, height: availHeight * 0.5 };
case 'orderbook':
return { left: availWidth * 0.6, top: availHeight * 0.5, width: availWidth * 0.4, height: availHeight * 0.5 };
case 'positions':
return { left: 0, top: 0, width: availWidth * 0.5, height: availHeight * 0.3 };
case 'news':
return { left: availWidth * 0.5, top: 0, width: availWidth * 0.5, height: availHeight * 0.3 };
default:
return { left: 0, top: 0, width: 800, height: 600 };
}
}
private getWindowBounds(win: Window): DashboardWindow['bounds'] {
return {
left: win.screenLeft || win.screenX || 0,
top: win.screenTop || win.screenY || 0,
width: win.outerWidth,
height: win.outerHeight
};
}
private handleScreenChange(screens: ScreenInfo[]): void {
// Check if any windows are now on disconnected screens
for (const [id, dashWindow] of this.windows) {
const screenStillExists = screens.some(
s => s.label === dashWindow.screen.label
);
if (!screenStillExists && dashWindow.window) {
// Move window to primary screen
const primaryScreen = screens.find(s => s.isPrimary) || screens[0];
const newBounds = this.getDefaultBounds(dashWindow.type, primaryScreen);
dashWindow.window.moveTo(
primaryScreen.left + newBounds.left,
primaryScreen.top + newBounds.top
);
dashWindow.window.resizeTo(newBounds.width, newBounds.height);
dashWindow.screen = primaryScreen;
}
}
}
private loadSavedLayouts(): void {
try {
const saved = localStorage.getItem('trading-layouts');
if (saved) {
this.savedLayouts = new Map(JSON.parse(saved));
}
} catch (e) {
console.warn('Failed to load saved layouts');
}
}
// Close all windows
closeAll(): void {
for (const [id, dashWindow] of this.windows) {
dashWindow.window?.close();
}
this.windows.clear();
}
}
Permission & Security
┌─────────────────────────────────────────────────────────────────────┐
│ Window Management Security Model │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ PERMISSION REQUIRED │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ The "window-management" permission must be granted to: │ │
│ │ • Enumerate screens beyond the current one │ │
│ │ • Get detailed screen information (labels, isInternal) │ │
│ │ • Request fullscreen on non-current screens │ │
│ │ │ │
│ │ Without permission: │ │
│ │ • getScreenDetails() rejects with NotAllowedError │ │
│ │ • Fallback to basic window.screen info still works │ │
│ │ • window.open() still works but limited to current screen │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ PRIVACY CONSIDERATIONS │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Screen info can reveal: │ │
│ │ • Number of monitors (somewhat identifying) │ │
│ │ • Screen resolutions and arrangement │ │
│ │ • Display labels (might include manufacturer) │ │
│ │ • Internal vs external (laptop vs desktop inference) │ │
│ │ │ │
│ │ Mitigations: │ │
│ │ • Requires explicit user permission │ │
│ │ • User can revoke permission in settings │ │
│ │ • Permission is per-origin │ │
│ │ • Secure context (HTTPS) required │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ PERMISSIONS POLICY │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ <!-- Allow window management for this origin --> │ │
│ │ Permissions-Policy: window-management=(self) │ │
│ │ │ │
│ │ <!-- Allow in iframe --> │ │
│ │ <iframe allow="window-management" src="..."></iframe> │ │
│ │ │ │
│ │ <!-- Disable completely --> │ │
│ │ Permissions-Policy: window-management=() │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Browser Support
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| getScreenDetails() | 100+ | No | No | 100+ |
| Screen change event | 100+ | No | No | 100+ |
| Fullscreen on specific screen | 100+ | No | No | 100+ |
| isInternal/isPrimary | 100+ | No | No | 100+ |
Note: This is a Chromium-only feature as of 2024.
Key Takeaways
-
Permission Required: Unlike basic
window.screen, full multi-monitor awareness requires user permission. -
Graceful Degradation: Always provide fallback behavior using
window.screenwhen permission is denied or API unavailable. -
Screen Coordinates: Screens form a virtual coordinate space - left/top can be negative for screens positioned left/above primary.
-
Layout Persistence: Save window layouts with screen labels for restoration, but handle the case where screens change.
-
Screen Change Events: Subscribe to
screenschangeto handle monitors being connected/disconnected. -
Fullscreen Control: With permission, you can request fullscreen on any connected screen, not just the current one.
-
Privacy Aware: The API requires explicit permission because screen configuration can be somewhat identifying.
-
Chromium-Only: Firefox and Safari don't support this API. Plan for single-monitor fallback on those browsers.
What did you think?