Wer mehrere Web-Inhalte in einem Fenster braucht — etwa eine Tab-leiste mit echten Browser-Tabs, einen Side-Panel-Viewer oder eingebettete Web-Apps — nutzt seit Electron 28 die WebContentsView-API. Sie ersetzt das deprecated BrowserView mit einer saubereren Ergonomie.

Grundbeispiel

JavaScript
import { app, BaseWindow, WebContentsView } from 'electron';

app.whenReady().then(() => {
    const win = new BaseWindow({ width: 1200, height: 800 });

    // Haupt-View (z. B. Tab-Bar)
    const tabBar = new WebContentsView();
    tabBar.webContents.loadFile('tabs.html');
    tabBar.setBounds({ x: 0, y: 0, width: 1200, height: 40 });
    win.contentView.addChildView(tabBar);

    // Aktiver Tab-Inhalt
    const tabContent = new WebContentsView();
    tabContent.webContents.loadURL('https://example.com');
    tabContent.setBounds({ x: 0, y: 40, width: 1200, height: 760 });
    win.contentView.addChildView(tabContent);
});

BaseWindow ist das neue, leichtgewichtige Fenster ohne eigenen Web-Inhalt. Inhalt kommt ausschließlich über WebContentsView-Kinder.

BrowserWindow ist der Komfort-Wrapper, der genau einen WebContentsView automatisch hat — für Standard-Apps weiter die richtige Wahl.

BrowserView vs. WebContentsView

BrowserViewWebContentsView
Statusdeprecatedmoderner Standard
Verfügbar seitElectron 1Electron 28
API-StilManuelle Bounds-Verwaltung in BrowserWindowÜber contentView mit Child-Views
Mehrere parallelJa, aber ohne klare Layout-APIJa, mit Child-View-Hierarchie
EmpfehlungMigration auf WebContentsViewNehmen für Neues

Migration: addBrowserViewcontentView.addChildView, setBounds weiter unverändert.

Klassischer Tab-Browser

JavaScript
const tabs = new Map();
let activeTabId = null;

function createTab(url) {
    const id = crypto.randomUUID();
    const view = new WebContentsView({
        webPreferences: {
            contextIsolation: true,
            sandbox: true
        }
    });
    view.webContents.loadURL(url);
    tabs.set(id, view);
    return id;
}

function activateTab(id) {
    // Alten View entfernen
    if (activeTabId && tabs.has(activeTabId)) {
        win.contentView.removeChildView(tabs.get(activeTabId));
    }
    // Neuen View einsetzen
    const view = tabs.get(id);
    view.setBounds({ x: 0, y: 40, width: 1200, height: 760 });
    win.contentView.addChildView(view);
    activeTabId = id;
}

function closeTab(id) {
    const view = tabs.get(id);
    if (view) {
        win.contentView.removeChildView(view);
        view.webContents.close();
        tabs.delete(id);
    }
}

Pattern: alle Tabs im Map halten, beim Tab-Wechsel den alten View aus der contentView entfernen, neuen einsetzen. Schließen erfordert webContents.close() — sonst leakt der Renderer-Prozess.

Resize-Handling

Bei Fenster-Größenänderung müssen alle Child-Views ihre Bounds aktualisieren — das macht WebContentsView nicht automatisch.

JavaScript
win.on('resize', () => {
    const { width, height } = win.getBounds();
    tabBar.setBounds({ x: 0, y: 0, width, height: 40 });
    tabContent.setBounds({ x: 0, y: 40, width, height: height - 40 });
});

Häufige Falle: Resize-Listener vergessen, dann skaliert Inhalt nicht mit. Pflicht-Pattern.

Z-Order und Kommunikation

JavaScript
// Reihenfolge der addChildView-Aufrufe = Z-Order (letzter = oben)
win.contentView.addChildView(background);   // unten
win.contentView.addChildView(content);      // mitte
win.contentView.addChildView(overlay);      // oben

// Manuell umsortieren — ältere Variante:
win.contentView.removeChildView(content);
win.contentView.addChildView(content);      // jetzt zuoberst

Jeder WebContentsView ist ein eigener Renderer-Prozess. Kommunikation zwischen Tab-Bar und Content geht nicht direkt — über IPC zum Main, der weiterleitet.

Wann BrowserWindow nehmen, wann BaseWindow?

App-TypEmpfehlung
Single-Window mit einer UIBrowserWindow (einfacher)
Tab-Browser, Multi-ViewBaseWindow + mehrere WebContentsView
App mit Side-Panel als WebviewBrowserWindow + 1-2 zusätzliche WebContentsView
Komplexes Custom-LayoutBaseWindow

BrowserWindow bleibt der pragmatische Default. BaseWindow ist die explizite Wahl, wenn man volle Kontrolle über alle Views will.

Besonderheiten

BrowserView nicht mehr für Neues nehmen.

Status ist deprecated, mit klarer Empfehlung auf WebContentsView zu migrieren. In neuen Projekten direkt das moderne API verwenden — die Migration später ist sonst Aufwand für genau nichts.

Resize-Listener pflichtig.

WebContentsViews skalieren nicht automatisch mit dem Fenster. Bei jedem resize-Event manuell setBounds aufrufen. Häufige Falle, weil bei Single-View-Apps mit BrowserWindow Auto-Resize gewohnt ist.

Jeder View ist ein eigener Renderer-Prozess.

10 Tabs offen = 10 Renderer-Prozesse. Bei Memory-knappen Setups merkbar. Pattern: Tabs unsichtbar lassen statt zu schließen, oder via webContents.suspend() (Electron 31+) den Speicher reduzieren.

Z-Order über Add-Reihenfolge.

Es gibt kein dediziertes „bring-to-front", sondern nur Add/Remove. Wer dynamisch umsortiert, muss Views zwischendurch removen und wieder addChildView'en. Etwas unhandlich, aber funktioniert.

Kommunikation zwischen Views = Main als Hub.

WebContentsView A kann nicht direkt mit WebContentsView B reden. Üblicher Weg: A schickt IPC zum Main, Main forwarded an B via b.webContents.send. Bei vielen Views entsteht ein Hub-and-Spoke-Pattern im Main.

webContents.close() beim Entfernen — sonst Leak.

removeChildView versteckt den View, killt aber den Renderer-Prozess nicht. Wer einen View dauerhaft entfernen will: anschließend view.webContents.close(). Sonst sammeln sich tote Renderer im RAM.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Main-Prozess

Zur Übersicht