Event Routing
Relevant source files
The following files were used as context for generating this wiki page:
Event Routing is the system responsible for capturing and directing user actions and system events to the appropriate handlers within the Ghostbird add-on. The architecture separates event handling into three distinct contexts, each with its own dedicated router: the background script, the compose window content script, and the options page. This separation ensures that logic is contained within the relevant part of the extension and simplifies interactions between components.
Each router acts as the primary entry point for events in its context. They are instantiated at startup and are responsible for listening to specific Thunderbird MailExtension API events, validating them, and delegating the processing to specialized handler classes. This follows the Ports and Adapters architecture mentioned in the design documents.
Sources: doc/design.md:7-39, src/test/sanity.test.ts:12-46
Overall Architecture
The Ghostbird add-on operates in three main JavaScript environments, each with a corresponding event router that gets initialized by a top-level script.
background.js: The persistent background script. ItsBackgroundEventRouterhandles global events like toolbar clicks and keyboard shortcuts.compose.js: A content script injected into each mail compose window. ItsComposeEventRoutermanages communication with the background script.options.js: The script for the add-on's options page. ItsOptionsEventRouterhandles UI interactions for configuration.
The following diagram illustrates the relationship between these components.
This diagram shows how Thunderbird API events are captured by the respective event routers in each script context.
Sources: doc/design.md:7-39, src/app-background/background_event_router.ts, src/app-compose/compose_event_router.ts, src/app-options/options_event_router.ts
Background Event Routing
The BackgroundEventRouter is the central hub for events in the background script. It handles user interactions that are not specific to a single compose window's content, such as clicks on the main toolbar button or keyboard shortcuts.
Sources: src/app-background/background_event_router.ts
Key Responsibilities
- Handling keyboard shortcuts defined in
manifest.json. - Processing clicks on the
composeActiontoolbar button. - Managing the context menu for the toolbar button.
- Validating that events originate from a mail compose tab before processing.
Handled Events
| Method | Event Trigger | Description |
|---|---|---|
handleCommand | browser.commands.onCommand | Handles a keyboard shortcut press. Delegates to commandHandler. |
handleComposeAction | browser.composeAction.onClicked | Handles a click on the toolbar button. Always triggers the start_ghostbird command. |
handleMenuShown | browser.menus.onShown | Handles the right-click context menu appearing on the toolbar button. Delegates to menuHandler. |
handleMenuClick | browser.menus.onClicked | Handles a click on a context menu item. Delegates to menuHandler. |
handleMessage | browser.runtime.onMessage | Handles one-off messages from content scripts. |
Sources: src/app-background/background_event_router.ts:15-60
Event Flow
Most event handlers in BackgroundEventRouter follow a similar pattern: they first verify that the event's associated tab is a compose window using IComposeWindowDetector. If the check passes, the event is delegated to a more specific handler.
The diagram below shows the flow for handling a command event.
This flow ensures that commands are only executed in the context of a compose window.
Sources: src/app-background/background_event_router.ts:15-22
Compose Window Event Routing
The ComposeEventRouter operates within the content script injected into the mail compose window. Its primary role is to manage communication with the background script, listening for connection requests and messages that initiate and control the GhostText session.
Sources: src/app-compose/compose_event_router.ts, src/root/compose.ts
Initialization
The router is prepared and its listeners are attached in src/root/compose.ts. This script sets up listeners for runtime.onMessage and runtime.onConnect, which are the two channels for the background script to communicate with the compose script.
// File: src/root/compose.ts:26-36
messenger.runtime.onMessage.addListener((message, _sender, sendResponse) => {
try {
sendResponse(composeEventRouter.handleMessage(message))
} catch (e) {
console.error({ composeOnMessage: e })
}
return undefined
})
messenger.runtime.onConnect.addListener(async (port) => {
try {
await composeEventRouter.handleConnect(PromisifiedPort.listenTo(port))
//...
Sources: src/root/compose.ts:26-36
Connection Handling
The most critical task for ComposeEventRouter is handling new connections from the background script via handleConnect. This establishes the persistent communication channel (Port) for the GhostText session.
The following sequence diagram illustrates the connection process.
This diagram shows how a connection from the background script is received by compose.js, passed to the ComposeEventRouter, and finally delegated to a PortHandler to manage the session lifecycle.
Sources: src/root/compose.ts:38-44, src/app-compose/compose_event_router.ts:18-20
Options Page Event Routing
The OptionsEventRouter is responsible for the functionality of the add-on's options page. It initializes the page, loads current settings, and saves changes made by the user.
Sources: src/app-options/options_event_router.ts, src/root/options.ts
Initialization Flow
The options page logic is initiated in src/root/options.ts. It uses a custom HTML element, <ghostbird-options>, to encapsulate the UI. The OptionsEventRouter is created and then linked to this element to manage its state and events.
This flow shows the setup process, from waiting for the custom element to be ready to attaching the final event listeners.
Sources: src/root/options.ts:14-25, src/app-options/options_event_router.ts:16-24
The initOptions method within OptionsEventRouter is central to this process. It takes the GhostbirdOptionsElement as an argument, uses OptionsIO to load and display saved settings, and attaches a submit event listener to the form to save any changes.
Sources: src/app-options/options_event_router.ts:16-24
Conclusion
The Event Routing system is a cornerstone of the Ghostbird architecture, providing a structured and decoupled way to handle events across the add-on's different execution contexts. By using dedicated EventRouter classes for the background, compose, and options scripts, the system maintains a clear separation of concerns. These routers act as dispatchers, validating incoming events and delegating them to specialized handlers, which makes the codebase more modular, easier to test, and more maintainable in the face of future changes to the Thunderbird API.