TMWebDriver in GenericAgent: How It Controls Multiple Browser Tabs

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, 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 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).

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:

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:

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):

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:

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):

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):

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:

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →