# TMWebDriver in GenericAgent: How It Controls Multiple Browser Tabs

> Learn how TMWebDriver controls multiple browser tabs in GenericAgent. Discover its WebSocket and HTTP servers for tab management and session tracking.

- Repository: [LJQ/GenericAgent](https://github.com/lsdefine/GenericAgent)
- Tags: how-to-guide
- Published: 2026-04-16

---

**`TMWebDriver` is the core browser-automation bridge in GenericAgent that manages multiple browser tabs by running a WebSocket server for Chrome extensions and an HTTP long-poll server for headless environments, tracking every open tab as a `Session` object in a centralized dictionary.**

`TMWebDriver` enables GenericAgent to treat each browser tab as an independent, addressable session. According to the source code in [`TMWebDriver.py`](https://github.com/lsdefine/GenericAgent/blob/main/TMWebDriver.py), the driver maintains a `self.sessions` dictionary that maps unique session IDs to `Session` objects, allowing the agent to execute JavaScript, navigate pages, and synchronize state across any number of concurrent tabs.

## What Is TMWebDriver?

`TMWebDriver` serves as the automation layer between GenericAgent and the browser. It is implemented in [`TMWebDriver.py`](https://github.com/lsdefine/GenericAgent/blob/main/TMWebDriver.py) and functions as a dual-protocol server:

- **WebSocket Server**: Handles real-time connections from the Chrome extension (`ext_ws` type) using `simple_websocket_server.WebSocketServer`
- **HTTP Long-Poll Server**: Provides a `/api/longpoll` endpoint via Bottle for pure-HTTP environments where WebSockets are unavailable

When instantiated, the driver checks if a remote server is already listening via `is_remote`. If not, it automatically launches both servers on consecutive ports by calling `start_ws_server()` and `start_http_server()` (lines 44‑46 in [`TMWebDriver.py`](https://github.com/lsdefine/GenericAgent/blob/main/TMWebDriver.py)).

## The Session Model for Multi-Tab Management

Every open browser tab is represented by a **`Session`** object that encapsulates the connection state, tab metadata, and communication channel.

### Session Class Structure

The `Session` class definition appears at lines 8‑34 in [`TMWebDriver.py`](https://github.com/lsdefine/GenericAgent/blob/main/TMWebDriver.py):

```python
class Session:
    def __init__(self, session_id, info, client=None):
        self.id   = session_id
        self.info = info               # url, title, type ('ws', 'ext_ws', 'http')

        self.type = info.get('type', 'ws')
        self.ws_client   = client if self.type in ('ws','ext_ws') else None
        self.http_queue  = client if self.type == 'http' else None

```

Key attributes include:
- **`session_id`**: A unique identifier (usually the Chrome tab ID for extension-based sessions)
- **`type`**: The transport protocol—`'ws'` for WebSocket, `'ext_ws'` for Chrome extension, or `'http'` for long-polling
- **`ws_client`** or **`http_queue`**: The active communication channel for sending and receiving messages

### Session Lifecycle and Cleanup

The driver tracks connection timestamps (`connect_at` and `disconnect_at`) and automatically cleans up stale sessions through the `clean_sessions` method. When a tab disconnects, the session is marked via `mark_disconnected()` but remains in `self.sessions` until the cleanup routine removes it.

## Initializing the Browser Automation Bridge

Upon initialization, `TMWebDriver` prepares the communication infrastructure:

```python
from TMWebDriver import TMWebDriver

# Automatically starts WS & HTTP servers if no remote server exists

driver = TMWebDriver()  # Lines 38-44 in TMWebDriver.py

```

The initialization sequence:
1. Checks for existing remote server (`is_remote`)
2. Starts the WebSocket server on the configured port
3. Starts the HTTP server on the next consecutive port
4. Initializes empty dictionaries for `self.sessions`, `self.acks`, and `self.results`

## Registering and Synchronizing Browser Tabs

### Tab Registration Workflow

When the Chrome extension connects, it sends a `ready` or `ext_ready` message. The `JSExecutor.handle` method extracts the `sessionId` and invokes `_register_client` (lines 31‑33):

```python
driver._register_client(session_id, self, session_info)

```

Inside `_register_client` (lines 68‑70), the driver creates a new `Session` instance and updates the registry:

```python
session = Session(session_id, session_info, client)
self.sessions[session_id] = session

```

The driver also updates `self.default_session_id` and `self.latest_session_id` to ensure subsequent commands can target the most recent or user-selected tab by default.

### Keeping Tab Lists Synchronized

The extension periodically transmits a `tabs_update` payload containing all open tabs. `JSExecutor.handle` processes this to maintain synchronization (lines 33‑45):

```python
tabs = data.get('tabs', [])
current_tab_ids = {str(tab['id']) for tab in tabs}

# Mark missing tabs as disconnected

for sid in list(driver.sessions.keys()):
    sess = driver.sessions[sid]
    if sess.type == 'ext_ws' and sid not in current_tab_ids:
        sess.mark_disconnected()

# Register or refresh existing tabs

for tab in tabs:
    session_id = str(tab['id'])
    session_info = {'url': tab.get('url'), 'title': tab.get('title')}
    driver._register_client(session_id, self, session_info)

```

This synchronization ensures that **multiple tabs are tracked concurrently**, each maintaining its own isolated `Session` object with current URL and title metadata.

## Executing JavaScript Across Multiple Tabs

### The execute_js Method

To run code in a specific tab, `execute_js` constructs a UUID-identified payload and transmits it through the appropriate channel (WebSocket, extension WebSocket, or HTTP queue):

```python
payload = json.dumps({
    'id': exec_id, 
    'code': code,
    'tabId': int(session.id) if session.type == 'ext_ws' else None
})
session.ws_client.send_message(payload)  # Lines 13-14 in TMWebDriver.py

```

The driver then waits for an **ACK** (stored in `self.acks`) and the final **result** (stored in `self.results`). If the target tab disconnects mid-execution, the driver can automatically fall back to the newest active session (lines 94‑104).

### High-Level Tab Control Helpers

`TMWebDriver` provides convenience methods that abstract the underlying session management:

- **`newtab(url)`**: Opens a new Chrome tab by executing `GM_openInTab` in the current context (lines 81‑82)
- **`jump(url)`**: Navigates the current default session to a new URL
- **`set_session(url_pattern)`**: Selects a tab whose URL matches the given pattern and sets it as `self.default_session_id` (lines 68‑77)

All helper methods rely on the `self.sessions` dictionary to resolve tab targets, enabling parallel actions across unlimited browser tabs.

## Practical Implementation Example

The following workflow demonstrates initializing the driver, opening multiple tabs, switching contexts, and executing JavaScript:

```python
from TMWebDriver import TMWebDriver

# 1. Initialize the driver (starts WS & HTTP servers automatically)

driver = TMWebDriver()  # Lines 38-44 in TMWebDriver.py

# 2. Open a new tab and load a page

driver.newtab('https://example.com')  # Lines 81-82

# 3. Switch default session to a tab containing "example" in URL

driver.set_session('example')  # Lines 68-77

# 4. Execute JavaScript in the active tab

result = driver.execute_js('document.title')
print(result['data'])  # Output: "Example Domain"

# 5. Retrieve snapshot of all active sessions

all_tabs = driver.get_all_sessions()  # Lines 48-52

for tab in all_tabs:
    print(f"Tab {tab['id']}: {tab['url']}")

```

## Summary

- **`TMWebDriver`** acts as a dual-protocol automation bridge using WebSocket and HTTP long-polling to communicate with browser tabs.
- **Session objects** in `self.sessions` provide isolated state management for each tab, tracking connection type, metadata, and communication channels.
- **Automatic synchronization** via `tabs_update` messages from the Chrome extension keeps the session registry aligned with actual browser state.
- **JavaScript execution** targets specific tabs through UUID-tracked payloads, with fallback mechanisms for disconnected sessions.
- **High-level helpers** like `newtab()` and `set_session()` abstract the complexity of multi-tab coordination.

## Frequently Asked Questions

### How does TMWebDriver handle tab disconnections?

When the Chrome extension detects a closed tab, it stops including that tab ID in the `tabs_update` payload. The `JSExecutor.handle` method marks the corresponding session as disconnected via `sess.mark_disconnected()`. The driver also implements automatic cleanup routines that prune stale sessions from `self.sessions` based on disconnect timestamps.

### Can TMWebDriver operate without the Chrome extension?

Yes. The driver supports an HTTP long-polling mode (`http` type) for environments where WebSocket connections are unavailable. The HTTP server (implemented in Bottle) exposes a `/api/longpoll` endpoint that allows headless or restricted browsers to poll for commands and return results, though this mode has higher latency than the WebSocket implementation.

### How do I execute commands in a specific tab rather than the default?

Use the `set_session(url_pattern)` method to select a tab by URL pattern, which updates `self.default_session_id`. Alternatively, you can access the `self.sessions` dictionary directly using the Chrome tab ID as the key, then pass that specific session object to low-level execution methods. The `execute_js` method always targets the session specified in its internal lookup logic.

### What is the difference between `ext_ws` and standard `ws` session types?

The **`ext_ws`** type indicates a connection from the Chrome extension (TMWebDriver CDP Bridge) located in `assets/tmwd_cdp_bridge/`, which provides full tab control including `GM_openInTab` functionality. The standard **`ws`** type represents direct WebSocket connections from other sources. Both use the same `Session` class but handle tab-specific commands like `tabId` injection differently during JavaScript execution.