Startup Sequence
Relevant source files
The following files were used as context for generating this wiki page:
The startup sequence in Ghostbird is not a traditional linear script execution but rather a sophisticated dependency injection (DI) system based on the "Composition Root" pattern. This system is responsible for instantiating and wiring together the various components of the add-on's different contexts: the background script, the compose window script, and the options page. It is designed to make the codebase more modular, testable, and easier to maintain, particularly when adapting to breaking changes in the Thunderbird API.
The core of this system lies within the src/root/startup/ directory. Instead of manually creating instances with new, the application scans for classes marked with a special static isSingleton property and automatically resolves their dependencies based on their constructor signatures. This autowiring mechanism is restricted to the root/ module to keep the dependency graph manageable and understandable.
Sources: doc/design.md, doc/faq-architectural.md
Core Concept: Automatic Instantiation
The startup process leverages a custom DI container to automate object creation. This process is initiated by one of the startup_*.ts files, which acts as the "Composition Root" for its respective part of the application. The system scans for classes that have a static isSingleton property, instantiates them, and injects them as dependencies into other classes that require them.
Sources: doc/design.md, doc/faq-architectural.md
Class Registration and Configuration
Classes are registered for automatic instantiation by defining specific static properties. These properties control how the DI container manages the class instances.
| Property | Type | Description |
|---|---|---|
isSingleton | boolean | If true, only one instance of the class is created and shared across all dependents. If false, a new instance is created each time it is needed as a dependency. Classes without this property are not instantiated automatically. |
wantArray | boolean | If true, the constructor can receive an array of instances for a dependency that has multiple implementations (e.g., multiple classes with the same alias). |
aliases | string[] or string | Provides one or more alternative names for the class. This is useful for resolving dependencies when multiple implementations of an interface-like dependency exist. |
Sources: doc/design.md
Instantiation Logic
The following diagram illustrates the decision-making process for instantiating a class and its dependencies.
This diagram shows the logic flow for resolving and instantiating dependencies based on their static properties.
Sources: doc/design.md
Startup Entry Points
The add-on is divided into three main JavaScript contexts, each with its own entry point and corresponding startup file.
- Background Script (
background.ts): Manages the WebSocket connection and core logic. It is initialized bystartup_background.ts. - Compose Script (
compose.ts): Injected into the mail compose window to handle UI elements and editor interaction. It is initialized bystartup_compose.ts. - Options Page (
options.ts): Handles user configuration. It is initialized bystartup_options.ts.
Each top-level script calls its respective startup*() function, which in turn uses the wireless() utility to perform the dependency resolution and instantiation for that context.
The following sequence diagram shows how the background script is initialized.
This diagram illustrates the initialization flow for the background script, from being loaded by Thunderbird to having its components instantiated by the DI container.
Sources: src/root/background.ts, src/root/startup/startup_background.ts, doc/design.md
Dependency Graph
The relationships between all automatically instantiated classes form a dependency graph. This graph is crucial for understanding the application's architecture. The project includes a tool that generates a visual representation of this graph.
Graph Generation
After running yarn test, a file named build/test/dependency_tree.md is generated. This file contains a Mermaid diagram of the complete dependency tree for all classes with static isSingleton defined. This provides a clear, up-to-date overview of the system's structure. The test src/test/startup.test.ts is responsible for collecting the dependencies and using src/test/util/dependency_reporter.ts to generate this output.
Sources: doc/faq-architectural.md, src/test/startup.test.ts:43-49, src/test/util/dependency_reporter.ts
Example Dependency Diagram
The following diagram is a simplified representation of the module dependencies within the project, showing how different parts of the application relate to each other. The root/ module acts as the entry point, depending on all other application modules.
This diagram shows the high-level module dependencies, with arrows pointing from the dependent to the dependency.
Sources: doc/design.md
Sanity Testing
To ensure the DI system is correctly configured and that all dependencies can be resolved at build time, a sanity test is executed as part of the build process (yarn build) and can be run manually with yarn test sanity. This test, located in src/test/startup.test.ts, attempts to instantiate all registered components. If a dependency is missing, misspelled, or if there's a name clash, the test will fail, preventing broken code from being committed.
This test uses a TestRegistry to collect all components from the different modules and then invokes the wireless function to resolve them. It also checks that dependencies declared in a class's constructor are actually used within the class body.
Sources: src/test/startup.test.ts:29-37, 70-82, doc/faq-architectural.md
Summary
The startup sequence of Ghostbird is a powerful dependency injection system that promotes a clean, decoupled architecture. By using static properties on classes to declare their lifecycle and dependencies, the system automates the complex task of object instantiation and wiring. This approach, while unconventional, enhances testability and maintainability, allowing developers to more easily manage dependencies and adapt to future changes in the Thunderbird platform. The process is validated through automated sanity tests and visualized via a generated dependency graph, ensuring the system remains robust and understandable.
Sources: doc/design.md, doc/faq-architectural.md