# Epoch Browser — Development Log

A from-scratch browser project with an IE6 visual theme, built in two phases:
first using Mozilla XULRunner/Gecko, then rewritten with Electron for full modern-web compatibility.

---

## Phase 1 — XULRunner / Gecko (browser/)

### Goal

Build a real distributable browser for Windows XP using Mozilla XULRunner 52.9.0 ESR — the last Gecko release with official XP support. IE6 chrome on top of a real Gecko rendering engine.

### File structure

```
browser/
├── application.ini                      ← App metadata
├── chrome.manifest                      ← Root: "manifest chrome/chrome.manifest"
├── chrome/
│   ├── chrome.manifest                  ← content/locale/skin registrations
│   ├── content/
│   │   ├── browser.xul                  ← Main window (IE6 chrome layout)
│   │   ├── browser.js                   ← gBrowser logic object
│   │   ├── browser.css                  ← IE6/Luna XP theme
│   │   └── aboutDialog.xul
│   ├── locale/en-US/
│   │   ├── browser.dtd                  ← All UI string entities
│   │   └── browser.properties
│   └── skin/classic/
│       ├── toolbar-back.png             ← IE6-style green arrow (22×22)
│       ├── toolbar-forward.png
│       ├── toolbar-stop.png
│       ├── toolbar-reload.png
│       ├── toolbar-home.png
│       └── throbber.gif                 ← 8-frame animated loading indicator
├── defaults/preferences/prefs.js        ← Runtime config (MANDATORY defaultChromeURI)
├── icons/default/default.ico
├── make-icons.ps1                       ← PowerShell script to generate all PNG/GIF/ICO assets
├── run.bat
├── run-debug.bat
└── xulrunner/                           ← Firefox 52.9.0 ESR extracted here
    └── firefox.exe
```

### Key implementation details

**application.ini**
- App ID must be email format: `epoch@epochbrowser.org` — bare strings fail silently
- Gecko version range: `MinVersion=52.0 MaxVersion=52.*`

**defaults/preferences/prefs.js**
- `toolkit.defaultChromeURI` is mandatory or the app silently exits with no error
- UA overridden with `lockPref` (not `pref`) so it overrides any saved profile value:
  ```
  lockPref("general.useragent.override",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...");
  ```
- This fixed YouTube redirecting to "please update your browser"

**browser.xul** — IE6 chrome layout (top to bottom):
1. `<menubar>` — File / Edit / View / Favorites / Tools / Help
2. `<toolbar id="nav-bar">` — Back / Forward / Stop / Reload / Home + Address bar + Go button + throbber
3. `<toolbar id="links-bar">` — "Links" label + bookmark quick-launch buttons
4. `<tabbox>` with `<tabs>` + `<tabpanels>` — IE7-style tab strip (manually built; `<tabbrowser>` is not available in XULRunner)
5. `<browser type="content">` in each tabpanel
6. `<findbar>`
7. `<statusbar>` — security icon + status text + progress meter + zone label ("Internet")

**browser.js — gBrowser object**
- Tabs: `_tabs[]` array of `{tab, panel, browser, _progressListener}`
- Progress listener implements `QueryInterface` for `nsIWebProgressListener`, `nsISupportsWeakReference`, `nsISupports`. Second argument to `addProgressListener` is mandatory: `browser.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL)`
- Navigation: `_looksLikeURL()` decides navigate vs. Google search fallback
- Favorites ("Favorites" not "Bookmarks", per IE6 naming) stored as JSON in profile dir
- Zoom via `browser.markupDocumentViewer.fullZoom`

**browser.css — IE6/Luna XP theme**
- Toolbar: `linear-gradient(to bottom, #78B4EC 0%, #2E7FD8 45%, #1660C0 100%)` (Luna blue)
- Chrome background: `#ECE9D8` (Windows XP Luna beige, not gray)
- "Address:" label: bold chip style with dark semi-transparent background
- Tab strip: lighter gray `#C8C5BB`, rounded-top tabs, selected tab white with bold text
- Status bar: `#ECE9D8` with groove border top

**make-icons.ps1 — PowerShell asset generator**
- Uses `System.Drawing` + `System.Windows.Media.Imaging` (PresentationCore) to generate PNGs and GIFs
- Back/Forward: vivid IE6 green (`#1EAC1E`) filled polygon arrows
- Reload: green arc (270°) with arrowhead
- Home: golden/amber house (`#F0A520`) with triangle roof and brown door
- Stop: vivid red X
- Throbber: 8-frame animated GIF, dots orbiting on toolbar-blue background
- ICO: 32×32 + 16×16 frames, white "E" on blue circle, built from DIB bytes

**PowerShell gotchas discovered:**
- `6f` float literals are invalid (C# syntax). Use `[float]6` casts instead
- `Pts @([float[]](x1,y1, x2,y2, ...))` — must assign polygon point arrays to a named variable before passing to `FillPolygon`; inline array construction in argument position fails silently
- `$pid` is a PowerShell automatic variable (current process ID) — use a different name like `$procId`

### Runtime discovery

Mozilla never published a standalone XULRunner 52. The solution: download **Firefox 52.9.0 ESR** and use its `-app` flag:

```
xulrunner\firefox.exe -app "C:\path\to\application.ini" --no-remote
```

Firefox 52 ESR's profile system uses `Vendor + Name` from `application.ini` as the root, so Epoch gets a fully isolated profile at `%APPDATA%\Epoch Browser Project\Epoch\Profiles\`.

**Confirmed working** — window title showed "Google - Epoch", profile created, favorites persisted.

### Why Phase 1 was abandoned

Firefox 52's NSS root store is from 2017. Most modern HTTPS sites reject its TLS handshake or redirect with "update your browser". Even with the Chrome 131 UA spoofing fix for YouTube, the majority of sites were broken. The Gecko 52 engine also can't run modern JS (ES2020+), so complex web apps fail to render.

---

## Phase 2 — Electron Rewrite (browser-electron/)

### Goal

Keep the identical IE6 visual chrome, swap the engine for Chromium 138 (via Electron 33). Eventually target **supermium-electron** for Windows XP deployment — a port of Electron that uses Supermium's Chromium patches + `progwrp.dll` API shims to run on XP.

### Architecture

```
Electron main process (main.js)
  ├── BrowserWindow  ← hosts index.html (the chrome UI)
  └── BrowserView × N  ← one per tab, positioned behind the chrome
        └── webContents  ← actual web page rendering
```

The chrome UI and web content are completely separate. The main process measures where to place the BrowserView by receiving chrome dimensions from the renderer via IPC.

### File structure

```
browser-electron/
├── package.json          ← electron ^33.0.0 devDependency
├── main.js               ← Main process: window, tabs, navigation, IPC handlers
├── preload.js            ← contextBridge: exposes window.epoch API to renderer
├── index.html            ← IE6 HTML chrome (menubar, toolbar, tabs, statusbar)
├── browser.css           ← IE6/Luna XP theme ported to HTML/CSS
├── renderer.js           ← Renderer: tab UI, IPC events, keyboard shortcuts
└── icons/
    ├── toolbar-back.png
    ├── toolbar-forward.png
    ├── toolbar-stop.png
    ├── toolbar-reload.png
    ├── toolbar-home.png
    ├── throbber.gif
    └── epoch.ico
```

### main.js

**Tab management**
- `createTab(url)`: creates a `BrowserView`, attaches all `webContents` events, sends `tab:created` IPC to renderer, calls `selectTab(id)`, then navigates
- `selectTab(id)`: hides all other views by setting their bounds to `{x:0,y:0,width:0,height:0}`; repositions the active view
- `repositionActiveTab()`: `setBounds({x:0, y:chromeTop, width:w, height:h-chromeTop-chromeBottom})`
- `chromeTop` and `chromeBottom` are sent from the renderer via `chrome:layout` IPC whenever the chrome height changes (find bar open/close, window resize)

**Navigation**
```javascript
// Smart URL detection
if (/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(url))  // already has scheme
else if (looksLikeDomain)  url = 'https://' + url;
else  url = 'https://www.google.com/search?q=' + encodeURIComponent(url);
```

**Security**
- Certificate errors accepted for all certs (XP compat, old CA roots): `event.preventDefault(); cb(true)`
- `_blank` link targets open as new tabs via `setWindowOpenHandler`
- `Menu.setApplicationMenu(null)` — uses the HTML menubar, no OS native menu

**User-Agent**
```
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
```

**Favorites**
- Stored as JSON at `app.getPath('userData') + '/epoch-favorites.json'`
- Loaded with `ipcMain.handle('favorites:load', ...)` (invoke/handle pattern for async response)
- Saved with `ipcMain.on('favorites:save', ...)` (fire-and-forget)

**API deprecation fix (Electron 33)**
- `wc.canGoBack()` → `wc.navigationHistory.canGoBack()`
- `wc.canGoForward()` → `wc.navigationHistory.canGoForward()`
- `wc.goBack()` → `wc.navigationHistory.goBack()`
- `wc.goForward()` → `wc.navigationHistory.goForward()`

### preload.js

Exposes `window.epoch` to the renderer via `contextBridge.exposeInMainWorld`. All IPC is channelled through this object. Incoming event channels are whitelisted:

```javascript
const VALID_RECEIVE = [
  'tab:created', 'tab:closed', 'tab:selected',
  'tab:title', 'tab:busy',
  'url:changed', 'nav:state',
  'load:start', 'load:stop',
  'status:text', 'security:changed',
];
```

### index.html

Pure HTML chrome. Dropdown menus are CSS hover-based (no JS needed for open/close). Nav buttons use `<button>` elements; icons injected by `renderer.js`. The `#content-area` div is a transparent placeholder — the BrowserView renders behind it at the same coordinates.

### browser.css

Key IE6 visual characteristics preserved in HTML/CSS:
- Toolbar: Luna blue gradient `#78B4EC → #2E7FD8 → #1660C0`
- Chrome background: `#ECE9D8` (Luna beige)
- Address chip label: bold, dark semi-transparent pill background
- Tabs: `#C8C5BB` strip, rounded-top, selected tab is white + bold
- Status bar: `#ECE9D8` with `groove` top border
- Security lock: inline SVG data URI (avoids external resource CSP issues)

### renderer.js

- `measureAndSendLayout()` — measures `contentArea.getBoundingClientRect().top` + status bar height; sends to main via `window.epoch.setLayout(top, bottom)`. Called on init, resize, and find bar toggle.
- Tab elements: `.tab` divs with `.tab-label` span + `.tab-close` button. Managed in a `Map<id, {el, labelEl, busy}>`.
- IPC event handlers wire all `window.epoch.on(channel, cb)` calls to DOM updates.
- Keyboard shortcuts: Ctrl+T (new tab), Ctrl+W (close tab), Ctrl+R/F5 (reload), Ctrl+F (find), Ctrl+L (focus urlbar), Escape (stop).
- Favorites: loaded async on init, `_buildLinksBar()` populates up to 8 quick-launch buttons in the Links bar.

### How to run

Node.js 24.14.0 was installed via winget (`winget install OpenJS.NodeJS.LTS`).

```bash
# First time only
cd browser-electron
npm install

# Every time
export PATH="/c/Program Files/nodejs:$PATH"
npx electron .
```

Or on Windows directly:
```cmd
cd browser-electron
npx electron .
```

---

## Windows XP deployment path (future)

Regular Electron uses Chromium 138 but requires Windows 10+. For XP:

1. Build the app normally with regular Electron
2. Swap the Electron runtime for **supermium-electron** binaries (github.com/win32ss/supermium-electron)
3. supermium-electron patches Chromium 138 with `progwrp.dll` Win32 API shims that backport missing Vista/7/10 APIs to XP's NT 5.1 kernel
4. The result: a real Chromium 138 engine that runs on a 2001 OS

No code changes are needed between regular Electron dev and supermium-electron XP deployment — it's a binary swap.

---

## Comparison: XULRunner vs. Electron

| | XUL / Gecko 52 | Electron 33 |
|---|---|---|
| Engine | Gecko (Firefox 52, 2017) | Chromium 138 (2024) |
| Web compat | ~60% of modern sites fail | Near 100% |
| XP support | Native (Gecko was built for XP) | Via supermium-electron binary swap |
| UI language | XUL + XPCOM JS | HTML + CSS + Node.js |
| Cert compat | Old NSS root store (2017) | Modern root store |
| Bundle size | ~44 MB (Firefox 52 ESR) | ~180 MB (Electron) |
| Status | Abandoned (web compat too poor) | **Active** |

---

## Gotchas reference

| Problem | Fix |
|---|---|
| App silently exits on launch | `toolkit.defaultChromeURI` in `prefs.js` is mandatory |
| YouTube "update your browser" | UA must be Chrome 131 string; use `lockPref` not `pref` |
| Blank icons from PowerShell | `FillPolygon` point arrays must be assigned to a var, not passed inline |
| `6f` float literals in PowerShell | Invalid syntax; use `[float]6` cast instead |
| `$pid` is read-only in PowerShell | PowerShell reserves `$PID`; use `$procId` or similar |
| XULRunner 52 not on Mozilla archive | Doesn't exist standalone; use `firefox.exe -app application.ini` instead |
| `addProgressListener` callbacks don't fire | Second argument `Ci.nsIWebProgress.NOTIFY_ALL` is mandatory |
| `<tabbrowser>` not available | XULRunner has no `<tabbrowser>`; build tabs manually with `<tabbox>/<tabs>/<tabpanels>/<browser>` |
| Electron `canGoBack` deprecated | Use `wc.navigationHistory.canGoBack()` in Electron 33+ |
| BrowserView hidden behind chrome | `selectTab` hides inactive views by setting bounds to `{0,0,0,0}` |
