Performance Considerations
Relevant source files
The following files were used as context for generating this wiki page:
webext-bridge is engineered for reliable and efficient communication across different contexts of a web extension. The library incorporates several architectural patterns to manage performance, ensure message delivery, and handle the ephemeral nature of extension components, particularly the Manifest V3 service worker. Key considerations include persistent connection management, message queuing for offline endpoints, and robust error handling for terminated sessions.
This document outlines the core mechanisms that contribute to the performance and reliability of the messaging system. It covers connection lifecycle, message routing, delivery guarantees, and the management of long-lived communication channels (streams).
Centralized Message Routing via Background Script
The background script acts as the central hub for all message traffic between different extension contexts. This centralized model simplifies routing logic and state management.
Sources: src/background.ts
Connection Management
When an extension context (like a content script, popup, or devtools panel) initializes, it establishes a connection to the background script. The background script maintains a map of all active connections.
connMap: AMapthat stores active port connections, keyed by a unique endpoint identifier string (e.g.,content-script@123.456). Each entry contains theportobject and a uniquefingerprintfor the session.browser.runtime.onConnect: This listener in the background script handles incoming connection requests, decodes connection arguments, and adds the new port to theconnMap.
Sources: src/background.ts:19, 235-251
The following diagram illustrates the connection flow from various contexts to the background script.
This diagram shows various extension contexts establishing a connection with the background script's onConnect listener, which then populates the central connMap.
Sources: src/background.ts:235-237, src/internal/persistent-port.ts:114-119
Persistent Connections in Manifest V3
Manifest V3 service workers can be terminated by the browser to conserve resources, which also closes any open messaging ports. To counteract this, webext-bridge implements a persistent port mechanism that automatically re-establishes connections.
Sources: src/internal/persistent-port.ts
Auto-Reconnection Logic
The createPersistentPort function wraps the standard browser.runtime.connect API. It attaches a listener to the port's onDisconnect event, which immediately attempts to reconnect, ensuring the communication channel is revived as soon as possible.
This diagram shows the auto-reconnection loop within createPersistentPort.
Sources: src/internal/persistent-port.ts:114-129
A unique fingerprint is generated for each port instance to prevent race conditions where an old, disconnected port's onDisconnect event fires after a new port has already connected.
Sources: src/internal/persistent-port.ts:24, 116, 271-274
Message Queuing and Delivery Guarantees
The library ensures that messages are not lost if the recipient is temporarily unavailable (e.g., a tab is reloading or the background service worker is restarting).
Undelivered Message Queue
When a client context attempts to send a message via its persistent port, and the background script determines the destination is not currently connected, it notifies the sender that the message is undeliverable.
- The
persistent-porton the sender's side catches this status and adds the message to anundeliveredQueue. - When the destination context eventually connects, the background script sends a
deliverablestatus message to the original sender. - The sender's
persistent-portreceives this notification and re-sends all queued messages for that destination.
Sources: src/internal/persistent-port.ts:54-82, src/background.ts:212-221
The sequence diagram below details this process.
This diagram illustrates how an undeliverable message is queued and later re-sent when the destination becomes available.
Sources: src/internal/persistent-port.ts:54-82, src/background.ts:101-111, 212-221
A notable exception exists for messages originating from the background script itself in Manifest V3. Due to service worker termination, the queue of messages sent from the background script is not reliable and will be disposed of when the worker terminates.
Sources: CHANGELOG.md:16-18
Stream Management for Continuous Communication
For scenarios requiring continuous, stateful communication, webext-bridge provides Stream objects. Streams are namespaced, scoped channels built on top of the core messaging bridge.
Sources: src/internal/stream.ts
Stream Lifecycle
- Opening a Stream: A client calls
openStream(channel, destination), which sends a__crx_bridge_stream_open__message to the destination. - Accepting a Stream: The destination listens for incoming streams using
onOpenStreamChannel(channel, callback). When the open message is received, it instantiates its ownStreamobject and resolves the promise on the client-side. - Communication: Both sides can now use
stream.send()andstream.onMessage()for bidirectional communication. Messages are routed with the__crx_bridge_stream_transfer__ID. - Closing a Stream: Either side can call
stream.close(), which sends a__crx_bridge_stream_transfer__message withaction: 'close'. This triggersonClosehandlers on both ends.
The Stream class uses a nanoevents emitter for handling message and closed events efficiently.
Sources: src/internal/stream.ts:15, 20-37, 131-149
Key Stream Components
| Component | Description | Source File |
|---|---|---|
Stream class | Manages a single stream instance, including sending, closing, and event handling. | src/internal/stream.ts:14 |
openStream() | Initiates a new stream to a specified destination and channel. | src/internal/stream.ts:151 |
onOpenStreamChannel() | Registers a handler to accept incoming stream requests on a specific channel. | src/internal/stream.ts:161 |
openStreams Map | A static map within the Stream class that tracks all currently active stream instances by their ID. | src/internal/stream.ts:16 |
Transaction Management and Error Handling
To manage request-response cycles, the library uses a transaction system. When a message is sent expecting a reply, it's tracked as an open transaction.
Sources: src/internal/endpoint-runtime.ts
Handling Terminated Recipients
A critical performance and reliability feature is handling cases where a recipient terminates before it can reply.
- When
sendMessageis called, a promise is created and stored in theopenTransactionsmap, keyed by a uniquetransactionId. - If a port disconnects, the background script identifies any pending responses (
pendingResponses) that were destined for the now-terminated endpoint. - It then notifies the original senders about the session termination.
- The sender's
endpointRuntimereceives this notification and rejects the corresponding promise inopenTransactionswith an error, preventing the sender from waiting indefinitely.
This prevents memory leaks and unhandled promises. This behavior was explicitly fixed to address issues where senders would wait indefinitely for a response that would never arrive.
Sources: src/internal/endpoint-runtime.ts:28, 158-161, src/background.ts:260-270, CHANGELOG.md:21-23