Table of Contents#
- Understanding Electron Basics
- Setting Up the Project
- Creating Independent Browser Instances
- Managing Separate Storage (Cookies & LocalStorage)
- Implementing the UI
- Testing the Application
- Conclusion
- References
1. Understanding Electron Basics#
Before diving in, let’s cover key Electron concepts:
- Main Process vs. Renderer Process: Electron apps have two types of processes. The main process (Node.js) controls the app lifecycle and window management. The renderer process (Chromium) runs the web UI (HTML/CSS/JS) in each window.
- Sessions: Electron uses
sessionobjects to manage browser data like cookies, localStorage, and cache. Each session is isolated by default, meaning data from one session won’t leak into another. - WebViews: A
<webview>tag (similar to<iframe>) embeds web content in the renderer process. Unlike iframes, webviews can use custom sessions, making them ideal for isolating browser instances.
2. Setting Up the Project#
Let’s start by setting up a basic Electron project. Ensure you have Node.js (v14+) installed.
Step 1: Initialize the Project#
Create a new folder and initialize a Node.js project:
mkdir electron-multi-browser
cd electron-multi-browser
npm init -y Step 2: Install Electron#
Install Electron as a development dependency:
npm install electron --save-dev Step 3: Configure package.json#
Update package.json to include a start script and set main to the entry file (we’ll name it main.js):
{
"name": "electron-multi-browser",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"devDependencies": {
"electron": "^28.0.0"
}
} 3. Creating Independent Browser Instances#
To run two independent browsers, we’ll use two <webview> tags, each linked to a separate Electron session. Sessions are isolated via "partitions," unique identifiers that ensure storage (cookies, localStorage) isn’t shared.
Step 1: Create the Main Window (main.js)#
The main process creates the app window and configures sessions. Create main.js with the following code:
const { app, BrowserWindow } = require('electron');
const path = require('path');
// Keep a global reference to the window object to prevent garbage collection
let mainWindow;
function createWindow() {
// Create the main browser window
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
// Enable nodeIntegration and contextIsolation for webview access
nodeIntegration: true,
contextIsolation: false,
webviewTag: true, // Required to use <webview> tags
},
});
// Load the index.html file (our UI)
mainWindow.loadFile('index.html');
// Open DevTools (optional, for debugging)
mainWindow.webContents.openDevTools();
// Emitted when the window is closed
mainWindow.on('closed', () => {
mainWindow = null;
});
}
// Electron is ready to create browser windows
app.whenReady().then(createWindow);
// Quit when all windows are closed (except on macOS)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', () => {
if (mainWindow === null) createWindow();
}); Step 2: Create the UI with WebViews (index.html)#
The renderer process (UI) will host two <webview> tags, each using a unique session partition. Create index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Multi-Browser Electron App</title>
<style>
body { margin: 0; padding: 10px; }
.browser-container {
display: flex;
gap: 10px;
height: 100vh;
}
.browser-pane {
flex: 1;
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
}
.webview {
width: 100%;
height: calc(100% - 40px); /* Leave space for header */
}
.header {
padding: 10px;
background: #f0f0f0;
font-weight: bold;
}
</style>
</head>
<body>
<div class="browser-container">
<!-- Browser 1: Session "account1" -->
<div class="browser-pane">
<div class="header">Account 1 (Gmail)</div>
<webview
class="webview"
id="webview1"
partition="persist:account1" <!-- Unique partition for isolation -->
src="https://mail.google.com"
></webview>
</div>
<!-- Browser 2: Session "account2" -->
<div class="browser-pane">
<div class="header">Account 2 (Gmail)</div>
<webview
class="webview"
id="webview2"
partition="persist:account2" <!-- Unique partition for isolation -->
src="https://mail.google.com"
></webview>
</div>
</div>
</body>
</html> 4. Managing Separate Storage (Cookies & LocalStorage)#
The magic lies in the partition attribute of the <webview> tags. Let’s break down how this ensures isolation:
How Sessions Isolate Storage#
- Partition Names: The
partitionattribute uses a string likepersist:account1. Thepersist:prefix tells Electron to save the session data to disk (so storage persists between app restarts). Withoutpersist:, the session is in-memory and resets when the app closes. - Isolation: Sessions with different partition names (e.g.,
account1vs.account2) never share data. Cookies, localStorage, cache, and even service worker data are stored separately.
Verifying Isolation (Optional)#
To confirm storage is separated, add code to log cookies from each session. In index.html, add a script tag:
<script>
// Wait for webviews to load
document.getElementById('webview1').addEventListener('dom-ready', () => {
const webview1 = document.getElementById('webview1');
// Get cookies for webview1's session
webview1.getWebContents().session.cookies.get({}, (error, cookies) => {
console.log('Cookies for Account 1:', cookies);
});
});
document.getElementById('webview2').addEventListener('dom-ready', () => {
const webview2 = document.getElementById('webview2');
// Get cookies for webview2's session
webview2.getWebContents().session.cookies.get({}, (error, cookies) => {
console.log('Cookies for Account 2:', cookies);
});
});
</script> When you run the app, check the DevTools console. You’ll see distinct cookie lists for each account after logging in.
5. Implementing the UI#
Our current UI splits the window into two panes, each loading Gmail. Let’s enhance it with basic controls (e.g., reload buttons) and better styling.
Update the CSS in index.html to add controls:
/* Add to existing styles */
.controls {
display: flex;
gap: 5px;
padding: 10px;
background: #f0f0f0;
}
button {
padding: 5px 10px;
cursor: pointer;
} Update the HTML for each browser pane to include controls:
<!-- Updated Browser 1 Pane -->
<div class="browser-pane">
<div class="header">Account 1 (Gmail)</div>
<div class="controls">
<button onclick="document.getElementById('webview1').reload()">Reload</button>
<button onclick="document.getElementById('webview1').goBack()">Back</button>
<button onclick="document.getElementById('webview1').goForward()">Forward</button>
</div>
<webview class="webview" id="webview1" partition="persist:account1" src="https://mail.google.com"></webview>
</div>
<!-- Repeat for Browser 2 with webview2 --> 6. Testing the Application#
Step 1: Run the App#
Start the app with:
npm start Step 2: Log Into Gmail Accounts#
- In the left pane ("Account 1"), log into your first Gmail account.
- In the right pane ("Account 2"), log into a second Gmail account.
Step 3: Verify Isolation#
- Close and restart the app. Both accounts should remain logged in (thanks to
persist:partitions). - Check that actions in one pane (e.g., reading an email, composing a draft) don’t affect the other.
Troubleshooting#
- Sessions sharing data? Ensure partition names are unique (e.g., don’t use
account1for both webviews). - Storage not persisting? Forgot the
persist:prefix in the partition name. - Webview not loading? Ensure
webviewTag: trueis set inmain.jsunderwebPreferences.
7. Conclusion#
You’ve built an Electron app that runs two independent browsers with isolated storage! This lets you manage multiple Gmail accounts (or any web apps) side-by-side without conflicts.
Enhancements to Explore#
- Add more webviews (e.g., 3+ accounts) by adding additional
<webview>tags with unique partitions. - Save custom URLs per account (e.g., load Outlook in one pane and Gmail in another).
- Add a "Clear Storage" button to reset a session’s cookies/localStorage.
- Use
session.fromPartition()in the main process to programmatically control sessions (e.g., delete persistent data).