Skip to main content
Flow Browser implements a sophisticated tab system with features like sleeping tabs to save memory, navigation history restoration, and intelligent lifecycle management.

Tab Architecture

Tabs in Flow Browser are managed by the Tab class at src/main/controllers/tabs-controller/tab.ts:137. Each tab has:

Stable Identity

Counter-based IDs that persist across sleep/wake cycles

WebContents View

Chromium rendering context (nullable when sleeping)

Navigation History

Full session history with restoration support

State Management

Loading, audio, fullscreen, and PiP states

Tab Data Structure

Persisted Data

Tab data saved to disk includes:
interface PersistedTabData {
  schemaVersion: number;
  uniqueId: string;           // Persistent unique identifier
  createdAt: number;
  lastActiveAt: number;
  position: number;
  
  profileId: string;          // Parent profile
  spaceId: string;            // Parent space
  windowGroupId: string;      // Logical window grouping
  
  title: string;
  url: string;
  faviconURL: string | null;
  muted: boolean;
  
  navHistory: NavigationEntry[];   // Navigation history
  navHistoryIndex: number;         // Current position
}

Runtime Data

Data sent to the renderer includes runtime state:
interface TabData extends Omit<PersistedTabData, 'navHistory' | 'navHistoryIndex'> {
  id: number;                 // Stable counter-based ID
  windowId: number;           // Current Electron window ID
  isLoading: boolean;
  audible: boolean;
  fullScreen: boolean;
  isPictureInPicture: boolean;
  asleep: boolean;
}
Navigation history is omitted from runtime data to avoid serializing large arrays on every state update, improving performance.

Sleeping Tabs

Sleeping tabs is a memory-saving feature that destroys the WebContentsView while preserving tab state.

How Sleeping Works

1

Tab Becomes Inactive

When a tab hasn’t been active for a while, it becomes eligible for sleep
2

State Preservation

Navigation history, title, URL, and favicon are saved
3

WebContents Destroyed

The WebContentsView is destroyed, freeing 20-50MB of RAM per tab
4

Tab Appears Normal

The tab still appears in the UI with its title and favicon

Waking Tabs

When you switch to a sleeping tab:
// From src/main/controllers/tabs-controller/tab.ts:307
public initializeView(): void {
  if (this.view) return; // Already initialized
  
  const webContentsView = createWebContentsView(this.session, this._webContentsViewOptions);
  const webContents = webContentsView.webContents;
  
  this.view = webContentsView;
  this.webContents = webContents;
  
  // Restore navigation history
  this.restoreNavigationHistory(this.navHistory, this.navHistoryIndex);
}
The tab is recreated with:
  • Fresh WebContentsView
  • Restored navigation history
  • Re-initialized event listeners
  • Re-registered with extensions

Sleep Configuration

The sleep system checks tabs periodically:
const ARCHIVE_CHECK_INTERVAL_MS = 10 * 1000; // Every 10 seconds
From src/main/controllers/tabs-controller/index.ts:23.

Tab State Updates

Tabs use an intelligent state update system that coalesces rapid events:
// From src/main/controllers/tabs-controller/tab.ts:544
public scheduleUpdateTabState() {
  if (this._updatePending) return;
  this._updatePending = true;
  queueMicrotask(() => {
    this._updatePending = false;
    this.updateTabState();
  });
}
This batches multiple WebContents events (page load, title update, navigation) into a single state update per event loop tick, dramatically improving performance. Tabs maintain complete navigation history that survives sleep/wake cycles:
interface NavigationEntry {
  title: string;
  url: string;
}

Smart History Comparison

Instead of using JSON.stringify() on every update, Flow uses a fast-path comparison:
const lengthChanged = newNavHistory.length !== this.lastNavHistoryLength;
const indexChanged = newNavHistoryIndex !== this.lastNavHistoryIndex;

if (!lengthChanged && !indexChanged) {
  // Check if active entry changed (e.g., replaceState)
  const oldActiveEntry = this.navHistory[this.navHistoryIndex];
  const newActiveEntry = newNavHistory[newNavHistoryIndex];
  
  activeEntryChanged = 
    (oldActiveEntry?.url ?? '') !== (newActiveEntry?.url ?? '') ||
    (oldActiveEntry?.title ?? '') !== (newActiveEntry?.title ?? '');
}
From src/main/controllers/tabs-controller/tab.ts:596-626.

Tab Events

The Tab class emits events that other systems listen to:
type TabEvents = {
  'space-changed': [];
  'window-changed': [oldWindowId: number];
  'fullscreen-changed': [boolean];
  'new-tab-requested': [url, disposition, options, details];
  'focused': [];
  'updated': [TabPublicProperty[]];
  'destroyed': [];
}
These events drive:
  • Tab persistence to disk
  • UI updates in the renderer
  • Extension notifications
  • Tab lifecycle management

Window Open Handling

Tabs handle window.open() and links with target="_blank":
webContents.setWindowOpenHandler((handlerDetails) => {
  switch (handlerDetails.disposition) {
    case 'foreground-tab':
    case 'background-tab':
    case 'new-window': {
      return {
        action: 'allow',
        outlivesOpener: true,
        createWindow: (constructorOptions) => {
          this.emit('new-tab-requested', 
            handlerDetails.url,
            handlerDetails.disposition,
            constructorOptions,
            handlerDetails
          );
          return this._lastCreatedWebContents!;
        }
      };
    }
  }
});
From src/main/controllers/tabs-controller/tab.ts:487-518.

Tab Shortcuts

Flow Browser provides keyboard shortcuts for tab management:
ShortcutAction
Cmd/Ctrl + TNew Tab
Cmd/Ctrl + WClose Tab
Cmd/Ctrl + RReload
Cmd/Ctrl + Shift + RForce Reload
Cmd/Ctrl + Shift + CCopy URL
F12Toggle DevTools
From src/main/modules/shortcuts.ts:5-43.

Background Color

Tabs apply different background colors based on the URL protocol:
private static readonly WHITELISTED_PROTOCOLS = ['flow-internal:', 'flow:'];
private static readonly COLOR_TRANSPARENT = '#00000000';
private static readonly COLOR_BACKGROUND = '#ffffffff';

public applyUrlBackground(): void {
  if (!this.url || !this.view) return;
  const url = URL.parse(this.url);
  if (url && Tab.WHITELISTED_PROTOCOLS.includes(url.protocol)) {
    this.view.setBackgroundColor(Tab.COLOR_TRANSPARENT);
  } else {
    this.view.setBackgroundColor(Tab.COLOR_BACKGROUND);
  }
}
Internal pages get transparent backgrounds, while web pages get opaque white. From src/main/controllers/tabs-controller/tab.ts:366-383.

Error Handling

When pages fail to load, tabs show a custom error page:
webContents.on('did-fail-load', (event, errorCode, _errorDescription, validatedURL, isMainFrame) => {
  event.preventDefault();
  // Skip aborted operations (user navigation cancellations)
  if (isMainFrame && errorCode !== -3) {
    this.loadErrorPage(errorCode, validatedURL);
  }
});
The error page displays at flow://error with URL parameters for error details.

Favicon Caching

Favicons are cached when pages update them:
webContents.on('page-favicon-updated', (_event, favicons) => {
  const faviconURL = favicons[0];
  const url = webContents.getURL();
  if (faviconURL && url) {
    cacheFavicon(url, faviconURL, this.session);
  }
  if (faviconURL && faviconURL !== this.faviconURL) {
    this.updateStateProperty('faviconURL', faviconURL);
  }
});
From src/main/controllers/tabs-controller/tab.ts:431-440.

Audio Controls

Tabs track audio state and support muting:
// Mute state persists across sleep/wake
if (this.muted) {
  webContents.setAudioMuted(true);
}

// Audio state is monitored
const newAudible = webContents.isCurrentlyAudible();
const newMuted = webContents.isAudioMuted();

Zoom Controls

Tabs set zoom level limits:
webContents.on('did-finish-load', () => {
  webContents.setVisualZoomLevelLimits(1, 5);
});
This allows 1x to 5x zoom range.

Tab Groups

Tabs can be organized into groups for features like:
  • Glance Mode: Stack tabs with quick switching
  • Split View: Display multiple tabs simultaneously
  • Normal Groups: Visual grouping (synthetic, not persisted)
Tab groups are managed by the TabsController at src/main/controllers/tabs-controller/index.ts.

Tab Lifecycle Managers

Each tab has dedicated managers:
  • TabLifecycleManager: Handles sleep/wake, fullscreen, PiP
  • TabLayoutManager: Manages view bounds and positioning
  • TabBoundsController: Calculates correct tab bounds
These are stored alongside each tab and orchestrated by the TabsController.

Performance Optimizations

Event Coalescing

Multiple WebContents events batched into single state updates

Smart History Diffs

Fast-path length/index comparison before deep equality checks

Lazy View Creation

Views created only when tabs are active, destroyed when sleeping

Cached Favicon Data

Favicons cached to avoid repeated network requests
  • Spaces - Tabs are organized within Spaces
  • Shortcuts - Keyboard shortcuts for tab operations
  • Extensions - Extensions interact with tabs

Build docs developers (and LLMs) love