Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
This page has all the information you need to get your GNU/Linux development environment set up and ready to hack on Thunderbird.
You will need to be running a 64-bit version of Linux in order to build Thunderbird. You can check which version you're running by typing this command in your terminal:
if this command returns x86_64
you can proceed.
The Thunderbird build can use 30-40GB of disk space to complete depending on your operating system.
Note that while it's not technically required to have an internet connection to build, the default setup has --enable-bootstrap
so that the toolchains download automatically.
The bootstrap.py
file will create files outside of the current directory. E.g. in ~/.mozbuild
. Ensure you have enough free space in your home directory as well. Alternatively to build within the current directory and avoid writing to the home directory run HOME="$(pwd)" ./bootstrap.py
instead or use a chroot environment.
You’ll need Python 3.8
or later installed.
You can check with python3 --version
to see if you have it already. If not, you can install it with your distribution’s package manager. Make sure your system is up to date!
You will also need python3-distutils
and python3-pip
installed from your distribution's package manager.
As noted in the Getting Started page, both mozilla-central
and comm-central
are repositories using the Mercurial version control system. This means you will need to install Mercurial. Here are the quick commands to use for common Linux based operating systems but for a more complete list of instructions (if neither of these works for your use case), please see Mercurial's download page on their wiki.
Once you have Mercurial installed, you are ready to grab the source code. There are a couple of different methods to do this.
Mozilla-central will build Firefox without the comm-central repo present and a few options set. Mozilla-central is the Firefox codebase and comm-central features the additions that turn Firefox into Thunderbird.
The bootstrap.py
script will grab the two source repos you need, run ./mach bootstrap
for you, and sets up a necessary mozconfig
file. Download this file to the directory where you would like your source code folder to live, either by clicking the link and moving the file to the appropriate location or using wget
. Then make it executable and run it.
This will create a mozilla-unified
directory with both a mozconfig
and a comm/
folder inside. The mozconfig
file is setup to build Thunderbird and you can verify this with cat mozconfig
; the --enable-project
parameter should be comm/mail
:
If you would rather manually gather the source code, perform the bootstrap, and create your mozconfig
file, then follow these steps.
Get the latest Mozilla source code from Mozilla's mozilla-central
Mercurial code repository, and check it out into a local directory source
(or however you want to call it). Then, get the latest Thunderbird source code from Mozilla's comm-central
Mercurial code repository. It needs to be placed inside the Mozilla source code, in a directory named comm/
:
mozconfig
fileThis step will need to be performed if you manually checked out the code and performed the bootstrap, and it will covered in the next section you follow, Building Thunderbird.
In the source
directory run the following command to get additional dependencies needed to install Thunderbird:
You will be presented with the following options:
Please choose option 2 to proceed with a successful build.
This action should install all the remaining libraries and dependencies necessary to build Thunderbird locally.
It could happen that some libraries will not be installed by the bootstrap
command, specifically those related to the Rust
programming language. Check whether these packages are available in your system by running these commands in your terminal:
which rustc
which cargo
If one or both commands return an empty output, you need to install them manually:
Install Rust and cargo (the Rust package manager): curl https://sh.rustup.rs -sSf | sh
Install cbindgen (tool that generates C bindings from Rust code): cargo install cbindgen
If you get a command not found
error while running cargo
, but the command which cargo
returns the location of the that package, it means you need to update your PATH
inside your .bashrc
file to include the cargo
location:
If you still are unable to find rustc and cargo via the ˋwhichˋ command after installing them, you may need to restart your session (log out and back into your user account, or restart your computer) to be able to see them.
Go back to the Building Thunderbird page and continue following the guide.
This page has all the information you need to get your Windows development environment set up and ready to hack on Thunderbird.
You will need to be running a 64-bit version of Windows in order to build Thunderbird. To check this in Windows 10, open the start menu and click on the gear icon on the left-hand side of the menu. This will open up the "settings" window. Click on the "System" option and then scroll down to "About". Click on the "About" option and on the new screen next to "System Type" you should see: "64-bit operating system"
In order to get the necessary libraries in order to build Thunderbird, you will need to install Visual Studio - an IDE from Microsoft. Download the free community edition here.
During installation make sure the following workloads are checked:
"Desktop development with C++"
"Game development with C++"
Finally, download the MozillaBuild Package from Mozilla. Accept the default settings, in particular the default installation directory: c:\mozilla-build\
. On some versions of Windows an error dialog will give you the option to ‘reinstall with the correct settings’ - you should agree and proceed.
Once this is done, creating a shortcut to c:\mozilla-build\start-shell.bat
on your desktop will make your life easier.
NOTE: You will need to run the start-shell.bat to open up the shell and perform the commands listed in other parts of this guide.
Once you have run start-shell.bat, you will need to grab the source code if you haven't already.
Get the latest Mozilla source code from Mozilla's mozilla-central
Mercurial code repository, and check it out into a local directory source
(or however you want to call it). Then, get the latest Thunderbird source code from Mozilla's comm-central
Mercurial code repository. It now needs to be placed inside the Mozilla source code, in a directory named comm/
(this is inverse from Thunderbird 59 and earlier):
In the source
directory run the following command to get additional dependencies needed to install Thunderbird:
You will be presented with the following options:
Please choose option 2 to proceed with a successful build.
This action will install all the remaining libraries and dependencies necessary to build Thunderbird locally.
Make sure to restart after installing all the requirements, or Thunderbird might encounter a build error.
Now that you have the prerequisites for Windows, make sure you have the source code via the commands on the Getting Started page:
Then you can follow the instructions on the Building Thunderbird page:
This page has some information on how to get plugged into the Community. If you are ready to start hacking, head over to one of the following sections.
Get all the information you need to set up your development environment and get ready to hack on Thunderbird.
If you'd like to learn to develop add-ons for Thunderbird, check out our add-on documentation with examples, tips and links to relevant resources.
Thunderbird uses Mozilla's Bugzilla platform to report and track bugs. The site can also be used to generate enhancement bugs, which can be used for feature requests. If you want to become a contributor to Thunderbird, you will need an account on Bugzilla as you will submit patches through this platform.
Creating a performance profile can be useful for developers to find the causes of high CPU load or slowness in Thunderbird.
If you want to contribute to the Thunderbird website, this documentation, or addons.thunderbird.net - you can find those repositories and their issue trackers on the Thunderbird GitHub page. You'll need a GitHub account to contribute there.
We have a complete listing of the ways in which you can get involved with Thunderbird on our website. Below are some quick references from that page that you can use if you are looking to contribute to Thunderbird core right away.
If you want to participate in discussions about Thunderbird development, these are the main channels.
TB-Planning: This mailing list is higher level topics like: the future of Thunderbird, potential features, and changes that you would like to see happen. It is also used to discuss a variety of broader issues around community and governance of the project.
Topicbox: A moderated mailing list for discussing engineering plans for Thunderbird. It is a place where you can raise questions and ideas for core Thunderbird development.
Add-on Developers: A list for Thunderbird add-on developers and aspiring add-on developers to ask questions and share knowledge.
If you want to ask questions in real-time about how to hack on Thunderbird, you can join our development chat channel at #maildev:mozilla.org
This page has all the information you need to get your development environment set up and ready to hack on Thunderbird.
Before you can build Thunderbird, please follow your platform's build prerequisites page:
Mozilla uses the Mercurial version control software to propose, review, incorporate, and log changes to its code. In order to contribute to Thunderbird, you will need to be able to use this software.
Information for how to install Mercurial is available via the download page on their wiki.
The latest Mozilla source code comes from Mozilla's mozilla-central
Mercurial code repository, often called source/
but it can be named anything you like. The latest Thunderbird source code comes from Mozilla's comm-central
Mercurial code repository and needs to be placed inside the Mozilla source code, in a directory that must be named comm/
.
Mozilla-central will build Firefox without the comm-central repo present and a few options set (detailed on the Building Thunderbird page).
Thunderbird is built on the Mozilla platform, the same base that Firefox is built from. As such the two projects share a lot of code and much of the documentation for one will apply, in many ways, to the other. If at any point you are looking for answers that you can't find here, here are some additional useful resources:
If you have already gone through the relevant build prerequisite steps, then let's build the latest Thunderbird:
How to build and run Thunderbird.
At least 4 GB of RAM. 8 GB or more is recommended. While you can build Thunderbird on older hardware it can take quite a bit of time to compile on slower machines with less RAM.
30 GB of free space. The Thunderbird build can use up to 30-40GB of disk space to complete depending on your operating system.
Good internet connection for the initial source download.
Depending on your Operating System you will need to carry out a different process to prepare your machine. So first complete the instructions for your OS and then continue following these build instructions.
To build Thunderbird, you need a file named mozconfig
in the root directory of the mozilla-central checkout that contains the option comm/mail
enabled. If you do not already have this file, then you can create it with this line by doing this in the source/
directory:
If you omit this line, the build system will build Firefox instead. Other build configuration options can be added to this file, although it's strongly recommended that you only use options that you fully understand. For example, to create a debug build instead of a release build, that file would also contain the line:
Each of these ac_add_options entries needs to be on its own line.
Building can take a significant amount of time, depending on your system, OS, and chosen build options. Linux builds on a fast box may take under 15 minutes, but Windows builds on a slow box may take several hours.
To run your build, you can use:
There are various command line parameters you can add, e.g. to specify a profile.
Various temporary files, libraries, and the Thunderbird executable will be found in your object directory (under comm-central/
), which is prefixed with obj-
. The exact name depends on your system and OS. For example, a Mac user may get an object directory name of obj-x86_64-apple-darwin10.7.3/
.
The Thunderbird executable in particular, and its dependencies are located under the dist/bin
folder under the object directory. To run the executable from your comm-central
working directory:
Windows: obj-.../dist/bin/thunderbird.exe
Linux: obj-.../dist/bin/thunderbird
macOS: obj-.../dist/Daily.app/Contents/MacOS/thunderbird
To pull down the latest changes, in the mozilla directory run the following commands:
or to do it via one command:
To build after changes you can simply run:
If you have made many changes, but only want to rebuild specific parts, you may run the following commands.
Replace path/to/dir
with the directory with the files changed.
This is the tricky bit since you need to specify the directory that installs the files, which may be a parent directory of the changed file's directory. For example, to just rebuild the Lightning calendar extension:
… or, How To Build Without Building
This pages assumes you already have some familiarity with the concepts of building Thunderbird. It is probably not suitable for absolute beginners.
Building everything can take a long time, and in many cases is completely unnecessary. If the changes you are making are limited to the UI, or tests, or even some back-end things, then compiling and linking the unchanged C++ and Rust code produces the same results every time. So let's get someone else to do it for us.
This the idea behind artifact builds. Instead of spending our time doing the building, we instead download pre-built copies of those parts (the "binary components") from the build infrastructure. Then we build everything else (what we're interested in) around them. This can reduce time spent from hours to a few minutes.
There is a number of important caveats, so make sure you understand them before going on.
In the simplest terms, if the code you are changing appears as plain-text in the final built output, you can use an artifact build. This includes:
Javascript
CSS
XUL
HTML
Pre-processed versions of any of the above
Any support files such as images
You can't use an artifact build if the code you are changing includes:
C or C++
Rust
IDL interfaces if the binary components use the interface
Statically-linked component .conf
files (even if the component is written in JS)
Some types of build configuration changes
… and probably others
How you set up to build depends on your scenario. Many people will still need to do a complete build sometimes. In this case, make a copy of your existing mozconfig
file and add these lines:
Line 1 ensures your artifact build goes in a separate object directory. Don't use the same object directory for both types of builds. Line 2 enables artifact building.
From here, remember to make sure you're using the right mozconfig
(export MOZCONFIG=/path/to/artifact.mozconfig
).
If you don't need to do a complete build, you can just add line 2 to your existing mozconfig
.
As stated earlier, the build process downloads the binary components from the build infrastructure. Here's where things can get a little bit messy.
First, things can only be downloaded if they exist. So you need to make sure what you want is available. Mach will download from the latest successful non-nightly build that matches your current comm-central
tree. This might not be the one you expect.
Example: if your platform is 64-bit Linux and your tree was up-to-date at this point, which build will you get?
Note that an "opt" artifact build will use "shippable opt" artifacts. (This has changed since the image above was first created. It's been modified but some of the arrows are now slightly inaccurate. You get the idea.)
In this example you have a choice:
wait for the running build to complete (that is, the B
to turn green, you don't need to wait for the tests) before building locally
build now, from the last successful build and hope the changes that have landed since don't affect you
"update" your trees back to their state when the last successful build happened
To find the revisions used for a successful build, click on the B
, and go down to the bottom of the Job Details tab:
Update both comm-central and mozilla-central trees using hg update -r abcdef1234
before building. In general, when a new build begins on the build infrastructure, it uses the latest mozilla-central as well as the latest comm-central, but that may no longer be the case by the time you build.
The build process logs a lot about what it's doing. This can help you understand exactly what's going on so read it the first few times you build.
You can in many cases do an even quicker rebuild (for example to remake pre-processed files) by running ./mach build faster
(or ../mach build -C . faster
if your current directory is comm-central). This skips a bunch of steps at the beginning and end of the build process that you might not need. When in doubt, don't build faster.
Calendar is built differently in an artifact build but this shouldn't affect most people.
To do an artifact Try run, add --artifact
to your try syntax. For example:
(Debug builds also work but you'd specify just macosx64
in that case.)
You can (and should in a lot of cases) specify a mozilla-central revision to build from, by editing the file .gecko_rev.yml
, replacing default
with the revision you need. If you don't, the Try server will use the latest mozilla-central revision, which may or may not be the same one used for the latest comm-central build.
For more info on configuration options, see the page . Note that if you use a MOZ_OBJDIR it cannot be a sibling folder to your source directory. Use an absolute path to be sure!
Before you start, make sure that the version you checked out is not busted. For hg
tip, you should see green Bs on
After you have met the for your OS, the build is started in the source
directory with:
mach is our command-line tool to streamline common developer tasks. See the article for more.
Follow this guide to rely on and other .
Then just run the ./mach build
command detailed in the instructions above. This will only recompile files that changed, but it may still take a long time.
This screenshot is from . Check what's going on there before updating your build.
Using an artifact build on the can save you a lot of time, and it also reduces our infrastructure costs. The same conditions about when you can and can't do an artifact build apply, plus only 64-bit builds are working.
A high-level look at the project's architecture and a guide to where to find things.
The following directories are included in the comm-central repository:
build Miscellaneous files used by the build process.
calendar The calendar component of Thunderbird (formerly known as the Lightning extension).
chat
Files for the chat component of Thunderbird. There is also related code in mail/components/im
.
The subdirectories are:
components Various chat features, includes the interfaces that each protocol must implement.
content User interface files which become chrome://chat/content/…
.
locales The user-visible strings, in US English. Files from this directory become chrome://chat/locale/…
.
modules JavaScript modules that are specific to chat.
protocols Various protocol implementations. Each of subdirectory implements a protocol to the interfaces found under components.
themes Common and platform-specific styling specific to chat. Files from this directory become chrome://chat/skin/…
.
mail
Thunderbird specific source code. It's no coincidence that this folder is laid out a lot like the browser
and toolkit
directories on mozilla-central. Many of the subdirectories follow the same pattern:
content User interface files which become chrome://messenger/content/…
.
modules Javascript modules which become resource:///modules/…
.
test Mochitest and/or XPCShell tests.
The subdirectories are:
actors Modules which handle communication between processes. See the Firefox documentation for information about actors.
app Configuration and packaging instructions. Contains all-thunderbird.js
, the default preferences.
base The main mail window and several miscellaneous dialogs. Also, things which are common to many parts of the program.
branding Icons and imagery.
components Various Thunderbird features, including:
accountcreation The account setup wizard.
addrbook The address book user front end. (Not the address book back end, which is in mailnews/addrbook
).
cloudfile The cloud attachment feature (a.k.a. FileLink).
compose Email composition window.
extensions WebExtensions schema and implementation.
im The chat front end.
preferences The preferences tab.
config Build instructions, including the automation mozconfig files.
extensions
installer Packaging and installation instructions.
locales The user-visible strings, in US English. Files from this directory become chrome://messenger/locale/…
.
modules Shared JS modules. Files from this directory become resource:///modules/….
test The MozMill user interface tests and the code to run that test suite.
themes Common and platform-specific styling of Thunderbird user interface. Files from this directory become chrome://messenger/skin/…
.
mailnews Source code specific to the Mail and Newsgroups part of Thunderbird and SeaMonkey.
mozharness Files needed for Thunderbird's test infrastructure.
other-licenses Code that is not under the Mozilla license. See http://www.mozilla.org/MPL/ for more info.
suite SeaMonkey-specific source code. Not used in Thunderbird.
taskcluster Files needed for Thunderbird's build infrastructure.
testing Files needed for Thunderbird's test infrastructure.
third_party Libraries developed elsewhere that are included in Thunderbird
Chat Core is the code for instant messaging that is used by Thunderbird. It provides a number of functions and capabilities, including:
Account configuration
Message logging
The Chat Core code used by Thunderbird has some abstractions to deal with the differences between protocols (e.g. IRC vs. XMPP).
This is a list of the available keyboard shortcuts with brief descriptions of what they do.
This is a page for documenting the notifications in Thunderbird. This is likely out of date. Notifications are grouped by interface you need to attach the observer to.
- IRC, Matrix, XMPP (and XMPP-based protocols)
The Chat Core code lives in the chat/ directory of .
Contacts are at the heart of instant messaging, and thus the Chat Core has a way to abstract to a "person" (represented by an instance), which might connect to multiple networks, etc.
Chat Core uses a message style system based on HTML, JS and CSS that is very similar to the one created for . If you plan to create a message style, reading the Adium documentation on the topic may be helpful -- see this and this .
Contacts are at the heart of instant messaging, and thus the Chat Core has a way to abstract to a "person" (represented by an imIContact
instance), which might connect to multiple networks, etc.
Draft: This page is not complete.
The display name used in the buddy list window or in conversations can come from several sources, by precedence order:
User-set alias, stored locally (set when the user renames someone from Thunderbird)
Server-stored alias (some protocol store the aliases online)
Display name / Friendly name (set by the remote contact, stored on the server)
Username, the unique identifier of the buddy for this protocol. Can be numbers (e.g. ICQ, QQ), email addresses (e.g. MSN, XMPP) or some other string.
Possible storage locations:
mozStorage (the file blist.sqlite)
chat core (cached copy)
server read/write (server-stored alias)
server read only (display names)
From Hello World to Thunder Live Development videos, get acquainted with the codebase and learn how to contribute to the Thunderbird project.
This page describes the classes and interfaces involved in configuring mail accounts.
Chat accounts use very similar mechanisms, but we won't go into that here to avoid confusion.
The account manager controls the objects described here. It is defined by nsIMsgAccountManager
and implemented by nsMsgAccountManager
.
To get to the account manager from JS, use MailServices.accounts
. To get to it from C++, use mozilla::components::AccountManager::Service()
. (The rest of this page will describe things in JS terms only for ease of reading.)
Accounts are simple containers for incoming servers and identities. The are defined by nsIMsgAccount
and implemented by nsMsgAccount
. If you're looking to use something in a mail account, you'll probably first get a reference to an nsIMsgAccount
.
Accounts are identified by a key
property, which is the word account
and then a number. Preferences for an account have the prefix mail.accounts.accountX
.
All accounts can be found at MailServices.accounts.accounts
. To get a particular account, use MailServices.accounts.getAccount(accountKey)
.
Incoming Server objects describe a connection to a mail server, e.g. an IMAP or POP3 server, or for local mail. They are defined by nsIMsgIncomingServer
and a sub-interface and implementation exists for each type of server Thunderbird can connect to.
Incoming Servers are identified by a key
property, which is the word server
and then a number. Preferences for an account have the prefix mail.servers.serverX
.
There is a 1:1 relationship between an account and an incoming server. To get to the server from an account, use the account's incomingServer
property. To get the account for a server, use MailServices.accounts.FindAccountForServer(server)
.
All incoming servers can be found at MailServices.accounts.allServers
. To get a particular server, use MailServices.accounts.getIncomingServer(serverKey)
.
Identities describe everything about sending mail from an account, such as the user's name and email address, which SMTP server to use, and where to put sent mail. They are defined by nsIMsgIdentity
and implemented by nsMsgIdentity
.
Identities are identified by a key
property, which is the word id
and then a number. Preferences for an account have the prefix mail.identity.idX
.
Accounts can have any number of identities, although removing the last identity generally isn't allowed. Use the account's identities
and defaultIdentity
properties to access them.
Typically an identity belongs to only one account, although it's technically possible for it to belong to multiple accounts. Use MailServices.accounts.getServersForIdentity(identity)
to get the server(s) for an identity, and go from there to get the accounts. (Yes, this is weird.)
All identities can be found at MailServices.accounts.allIdentities
. To get a particular identity, use MailServices.accounts.getIdentity(identityKey)
.
SMTP breaks some of the pattern you might've noticed in the previous classes. The SMTP service, MailServices.smtp
or mozilla::Components::Smtp::Service()
, implements nsISmtpService
as SmtpService
.
SMTP server configuration is kept by objects implementing nsISmtpServer
as SmtpServer
. They also are identified by a key
property, which is the word smtp
and then a number. Preferences for an account have the prefix mail.smtpserver.smtpX
.
Identity objects reference SMTP servers in their smtpServerKey
attribute.
All SMTP servers can be found at MailServices.smtp.servers
. To get a particular server, use MailServices.smtp.getServerByKey(smtpKey)
.
This is a list of keyboard shortcuts that are available in Thunderbird with brief descriptions of what they do.
Depending on your OS, the Command key may be Ctrl.
Command + F Find
Escape to put the conversation on hold
Command + W to close the current tab
Command + Shift + H for History (Show Logs)
Command + [Plus key], Command + [Minus key] - sets the Zoom level
Command + 0 - resets the zoom level to 100%. (Use the 0 key in the top row of the keyboard, not the 0 key of the numeric keypad.)
Shift + PgUp/PgDn scrolls a page up or down
Home/End or Alt + PgUp/PgDn scrolls to the previous/next section. Sections are
the beginning and end of the conversation
the beginning or end of a session (in the log viewer)
the first non-context message
the unread message ruler.
When the textbox is empty, the usual navigation keys (cursor keys, PgUp/Dn, ...) will scroll the conversation view also from the textbox.
Command + Arrow Up, Command + Arrow Down to switch between conversations
Command + Shift + Arrow Up, Command + Shift + Arrow Down to switch between unread conversations
More available shortcuts are listed in the menu next to the commands.
A.K.A. the 3-pane tabs and message tabs/windows
In January 2023 the mail front-end was rebuilt from scratch, replacing what evolved from the original Netscape front-end. This is a developers' guide to the new UI.
The mail front-end consists of two types of tabs (and a standalone window, more about that later) – the 3-pane tab mail3PaneTab
and the message tab mailMessageTab
. These are defined in mailTabs.js and provide the tabInfo
objects for tabmail to control. Most code from outside the tabs will go through here in some form, although knowing the specific details should be unnecessary.
Each mail tab tabInfo
object has these read-only properties:
chromeBrowser
– This is a XUL <browser>
object which displays the tab's contents, either about:3pane
or about:message
. As with any <browser>
object you can access the displayed page with the contentWindow
and contentDocument
properties.
browser
and linkedBrowser
– Both refer to the XUL <browser>
currently displaying content (an email message or a web page) to the user, or null
if there isn't any.
message
– The currently displayed message as an nsIMsgDBHdr
, if there is one.
folder
– The folder containing the currently displayed message, an nsIMsgFolder
.
If the mail tab you're interested in is the current tab, the following properties of tabmail point to it:
currentTabInfo
– The tabInfo
object described above.
currentAbout3Pane
– The window
object of the page displayed in the chromeBrowser
, if the current tab is a 3-pane tab.
currentAboutMessage
– The window
object of the message display page, which for message tabs is the page displayed in the chromeBrowser
, and for 3-pane tabs a page within that.
If it's not the current tab, you can get the tabInfo
object from tabmail and use the properties listed in the previous section to access it.
The standalone mail window also contains a XUL <browser>
displaying about:message
. The browser can be accessed from the window's messageBrowser
property.
about:3pane
is the main UI that users see when Thunderbird starts: the folder pane, the thread pane, and the message pane. It lives in the tree as about3Pane.xhtml and similarly named JS, CSS and Fluent files.
The folder pane displays the accounts and folders within them. Various modes of display are available.
The thread pane displays the list of messages in the current folder, and the Quick Filter bar for filtering those messages.
This mesage pane contains more XUL <browser>
s for displaying various things:
webBrowser
– displays web content in a child process as Firefox does.
multiMessageBrowser
– displays messages if more than one is selected.
messageBrowser
– displays a single message using about:message
.
Only one is visible at any given time.
If an account is selected in the folder pane instead of a folder, yet another <browser>
, accountCentralBrowser
– displays Account Central, a page of various things you can do in Thunderbird.
about:message
is all of the UI that displays a single message, including the message headers and attachments. It is used as the message pane in about:3pane
and by itself as a message tab or window. Like about:3pane
it lives in the tree as aboutMessage.xhtml, aboutMessage.js, messageHeader.css and about3Pane.ftl files.
Message contents themselves are displayed in a <browser>
(if you're counting, we're now three deep) which can be accessed by the content
property of an about:message
window
.
The Chat Core code used by Thunderbird has some abstractions to deal with the differences between protocols (e.g. IRC vs. XMPP).
Protocols are implemented in chat core using JavaScript.
Protocols must implement the proper interfaces and be registered with the category manager in order to be found. Protocols need to implement the prplI* interfaces (this can mostly be done using jsProtoHelper). The minimum set of interfaces to implement are:
imXPCOMUtils: Additional XPCOM utilities.
JavaScript socket: Simplified socket code.
jsProtoHelper: Includes basic JavaScript implementations of the interfaces and some helper code.
XML HTTP Request helper: Simplified HTTP request code
The code for the JavaScript protocols we ship by default is here.
IRC: A full implementation, including private chats and MUCs, etc.
JavaScript Test Protocol: An extremely simple example meant to serve as test code for the interfaces.
Matrix: An implementation that heavily depends on an external SDK.
XMPP: A full implementation, including private chats and MUCs, etc. There are also other protocols which inherit and customize XMPP:
There are also some stub implementations for protocols that Thunderbird used to support, but no longer does. These exist purely for the icons to show up and for a nice error message to appear when the account tries to connect.
This lists the protocol plugins that the core service knows about. You can copy the code (as it is), paste it in the error console (linebreaks will automatically be ignored) and press "Enter" to run it.
A bug report can be of 3 different types
Enhancement Bugs that are requests for new features, or modifications of existing features. Reports that the user thinks might be incorrect behavior could be classified as bugs, but if that feature behaves as intended, the request is not a bug but a RFE (Request for Enhancement).
Bug/Defect Broken functionality, not working properly, crashes, and pretty much anything that is not working as expected.
Task Limit the use of Task to meta bugs or internal efforts related to code clean up, architectural restructuring, tests or telemetry implementation. If it affects a user of a current release, it is better categorized as a Bug.
When setting a bug priority and severity classification, note that you can click on Priority in the Category section to go to the mozilla page with the different levels and when to set them.
Move the bug into the correct component if it’s not already there.
Mark it as NEW if it’s still UNCONFIRMED.
Add the triaged keyword (that will require a severity level).
Set Priority and Severity if you feel comfortable, or ask a manager or module owner to help you define those.
Add other keywords if relevant (ux, access, perf, etc.).
If it’s a regression, try to find the bug that regressed that feature and add it along with the REGRESSION keyword. https://mozilla.github.io/mozregression/ is a good tool for you and reporters.
If you can’t find a regression, add the regressionwindow-wanted keyword.
Ask the reporter to try in troubleshoot mode without any add-on.
Ask for more info if not already provided, like OS or their particular setup.
Try to get more clear instructions - users tend to understand better if they are asked to make a list of actions or clicks they used.
DON’T simply write “It works for me”, that’s not helpful and it’s just noise, and it can increase the frustration of the reporter.
Issues are very situational, as they might be caused by errors in C++, JavaScript, translation, a custom configuration from the user, or many other things.
For instances in which a tab is blank or something doesn’t load as expected, ask the user to check the Error Console and report any messages in there (ctrl+shift+J or cmd+shift+J for macOS)
Bugzilla does a terrible job of suggesting potential duplicates when the user files a bug, so you will stumble upon the same issue reported by different users.
Try to identify the duplicates and close them by adding the original bug that we use as a reference. Please give a reason for duping, a) to educate the user, b) so user doesn’t feel like we are just closing bugs and brushing them off. Boilerplate text:
This issue is being investigated in bug XXXX. If you think that bug is missing important information that will help lead to fixing or reproducing it, please add it to that bug.
How to make a "Hello World" prompt in Thunderbird.
No project is a real project without a Hello World guide. In this guide we will create a function in JavaScript that triggers an alert that says "Hello World!" - then we will add a menu item that triggers that function. The idea is to give you a chance to dig through the code and make some fun changes beyond this guide, and learn how to change Thunderbird!
The first thing we want to do is create the function that will run and create our JavaScript alert - this alert will create a new window with our message: "Hello World!"
Open up mailCore.js
- this is the file that we'll be creating our function in. You can find this file in the content folder which is here: comm -> mail -> base -> content
.
We can put our function almost anywhere in this file, so long as we don't put it within another function. Take a little while to look at this file and see if you can figure out what the other functions in this file are doing. Once you've done that find the function openAboutSupport()
. If you are new to programming you can find this function easily is most editors by doing a search with ctrl+f to open up a search field - you can put openAboutSupport()
in there and it should highlight that function.
After you find that function place the following code below it:
As noted above, this is a JavaScript function that will create an alert that will appear in the form of a window that Thunderbird generates that will say "Hello World!"
This is what it should look like in context:
For this tutorial we are going to create a new menu item in the App Menu (often called the hamburger menu) to call our helloWorld()
function in mailCore.js
.
For this part of the tutorial we are going to interact with a XHTML file.
In the directory: comm -> mail -> components -> customizableui -> content
- we are going to open the file panelUI.inc.xhtml
and find the "appmenu_help"
toolbarbutton (you'll likely want to use Ctrl+F again to find it).
Once you found the appmenu_help
toolbarbutton, insert the following code below it:
In context:
Make sure all your work is saved and then you can build Thunderbird using the ./mach build
command. Once you have built Thunderbird with the changes, we can use ./mach run
to try out our modified version of Thunderbird.
Click to open the App Menu on the right hand side and you should see "Hello World" (pictured below):
When you click on the "Hello World" menu item, you should get an alert prompt (pictured below):
If that alert window appears when you click the menu item then it works!
Spend some time playing around with the menu and even try experimenting with the helloWorld function. Most of all have fun and don't worry about messing things up.
If you get in trouble you can reset the repository via the commands below (in the /comm
directory) - these will remove all the changes you've made:
Tutorial on how to fix a bug from beginning to end.
To ensure your work is correctly attributed to you, and to make the reviewer's task easier, these options should be set in your Mercurial configuration file (Mecurial.ini
on Windows, $HOME/.hgrc
elsewhere).
Using standard forms for commit messages not only looks better when looking at the revision logs, it also helps various automation tools parse the messages.
Commit messages should be of the form:
For follow-up commits that fix a problem with a lint test or other failure, the suggested form is:
When fixing a bug caused by a change made to mozilla-central, often referred to as "porting" a fix to Thunderbird, mention the upstream bug in the commit message like below. Doing so helps identify bugs that need uplifting to beta or release when the ported mozilla-central bug is uplifted.
Prefixing the first line of the commit message with "WIP:
" marks the patch as a work-in-progress. moz-phab
(see below) will pick that up and mark it as "Changes Planned".
All changes need to be reviewed before acceptance into the codebase. It can be pretty tricky to figure out who to ask for a review.
There is a command line tool, moz-phab
, which makes it easy to submit local changesets as patches.
With moz-phab
you can submit local mercurial changeset(s) like this:
The start/end changesets are optional. If omitted, moz-phab
will guess which one(s) you mean.
It'll ask for confirmation before uploading, so don't worry too much about accidental submissions.
moz-phab
will pick the bug number out of the commit message (Bug xxxx
), and link back to the bugzilla bug. If there is a reviewer (r=...
), it will automatically assign them and send them a notification. You can leave the reviewer out, but then one will have to be manually assigned via the phabricator web page. If the commit message starts with "WIP:
", the patch will be marked "Changes Planned".
It's very common for patches to require some updates before being accepted. Locally, you can use hg commit --amend
to update a changeset.
Phabricator tracks uploaded patches by adding a line to the commit message:
When you submit the patch again with moz-phab
, it will see that line and realise that you're updating an existing revision rather than creating a brand new one.
If you're juggling and merging local changesets with hg histedit
, make sure you preserve the Differential Revision:
line in the commit message for any patches you're planning to resubmit!
All the issues, bugs, work in progress patches, or updates related to Thunderbird, are listed on , and are properly organized per Product, Component, and Status.
Creating an account is necessary in order to submit patches, leave comments, and interact with any other aspect of Bugzilla. If you're currently using a username in one of our Matrix chat rooms (e.g. ), we recommend saving your profile name with the current format Firstname Lastname (:username)
in order to be easily searchable and allow the Thunderbird team to offer better support.
Use the section to find bugs you want to take care of, and be sure that the bug doesn't currently have any user listed as Assignee and the Status is set to NEW
.
Making sense of the Thunderbird source code, and knowing where to look, will take some time. The code base is pretty big and if you never worked with XBL
or Custom Elements
it can be overwhelming at first. We recommend using our code search engine, , to inspect the source code and find snippets and references to help you out while investigating a bug.
JavaScript code can be debugged using the built-in . Debugging core C++ code requires .
Mercurial is pretty flexible in terms of allowing writing your own code and keeping it separate from the main code base. See for more information.
Thunderbird code is divided into modules, each with an owner and peers. Generally, these are the best people to review your changes. Here's . and modules have separate lists.
Scanning through the recent commits in mercurial should also give you an idea of who is active in various areas of the code. Failing that you can always .
The way to submit a patch is via .
See the docs.
You can find more details in the moz-phab
.
The Address Book code is found in two places: the UI is primarily in mail/components/addrbook
, while the back end is in mailnews/addrbook
. This documentation only describes the back end.
The address book manager is responsible for organising individual "directories" (address books), including reading the configuration from the preferences at start-up and creating the directory objects. It is defined by the interface nsIAbManager
and implemented as AddrBookManager
. You can get a reference to the manager at MailServices.ab
.
The address book manager can get a reference to a particular directory in a number of ways:
By URI. Registered directories have a URI defined by the type of directory and any information needed to identify an individual directory. For example jsaddrbook://abook.sqlite
.
By unique identifier (UID). For example 70139e91-37a5-47fd-8856-2f0756f43ef1
.
By location in the preferences. Directories store their preferences at ldap_2.servers.<some identifier>
.
nsIAbManager
also defines the DIRECTORY_TYPE
constants mentioned below.
Individual address books are referred to as "directories" or "books" in the code. They implement nsIAbDirectory
.
There are different implementations:
A base class AddrBookDirectory
which handles most of the implementation detail using data provided by a sub-class. It should not be instantiated directly.
The most common type is SQLiteDirectory
which, as the name implies, stores data in an SQLite database in the user profile. It inherits from AddrBookDirectory
. Directories of this type have URIs with the scheme jsaddrbook
, and are of type JS_DIRECTORY_TYPE
.
CardDAVDirectory
extends SQLiteDirectory
by adding CardDAV capabilities. Directories of this type have URIs with the scheme jscarddav
, and are of type CARDDAV_DIRECTORY_TYPE
.
LDAPDirectory
extends AddrBookDirectory
and adds LDAP capabilities. Directories of this type have URIs with the scheme moz-abldapdirectory
, and are of type LDAP_DIRECTORY_TYPE.
nsAbDirProperty
provides a base C++ implementation of nsIAbDirectory
to OS-specific address books nsIAbOSXDirectory
and nsAbOutlookDirectory
. These are both of type MAPI_DIRECTORY_TYPE
. The OS-specific types are likely to be discontinued.
WebExtension APIs can provide contact suggestions to the user when composing messages. This is done via a ASYNC_DIRECTORY_TYPE
directory.
Directories contain contacts or "cards", which implement the interface nsIAbCard
. The are accessible by the childCards
property as well as methods for searching, adding, modifying, and deleting contacts. (Confusingly a "card" can also refer to a mailing list, see below.)
Contacts are implemented by AddrBookCard
for all of the JS code, or nsAbCardProperty
in C++.
The current implementation stores contact data as key/value pairs. Work is underway to replace this with a more capable data storage based on the vCard format.
A mailing list (or just a "list" in many places) is a collection of contacts. To be added to a mailing list, a contact must have an email address. A mailing lists can be both nsIAbDirectory
and nsIAbCard
, depending on the situation. Directories provide an array of their mailing lists as nsIAbDirectory
with the childNodes
property. They also appear as nsIAbCard
in the childCards
property and some of the methods that handle cards.
A mailing list can not have any childNodes
of its own. However in a confusing quirk, it can have the card of another mailing list in its childCards
.
Mailing lists are implemented in AddrBookMailingList
.
The address book code fires observer service notifications. The notification names should be self-explanatory.
In all cases the "subject" of the notification is the directory.
addrbook-directory-created
addrbook-directory-updated – the "data" contains the property that changed, and only the "DirName" property can change
addrbook-directory-deleted
addrbook-directory-invalidated – many contacts changed, anything storing or displaying contacts should be thrown away and reloaded
addrbook-directory-synced – CardDAV sync succeeded
addrbook-directory-sync-failed – CardDAV sync failed
In all cases the "subject" of the notification is the contact, and unless otherwise stated "data" contains the UID of the directory containing the contact.
addrbook-contact-created
addrbook-contact-updated
addrbook-contact-properties-updated – "data" contains a JSON-stringified object containing the old and new values of any properties that changed
addrbook-contact-deleted
In these cases the "subject" of the notification is the list as nsIAbDirectory
and "data" is the parent directory's UID.
addrbook-list-created
addrbook-list-updated
addrbook-list-deleted
In these cases the "subject" is the contact in question, and "data" is the list's UID.
addrbook-list-member-added
addrbook-list-member-removed
As mentioned above, contact properties are stored as key/value pairs. This has limitations in a modern address book:
There's a fixed set of keys. Although there's no real restrictions on keys you could use, no user interface is available for anything outside of the known keys.
Multiple values for the same key aren't possible. For example if a contact has three email addresses, the first two can be stored in PrimaryEmail
and SecondEmail
, but there's nowhere to store the third.
Storing meta information about the values is impossible. There's no way to mark an email address as a work address or a home address, for example.
To combat this, from Thunderbird 102 contacts stored in local directories will be converted to use the industry standard vCard format. CardDAV directories already use vCard, although the data was converted to key/value pairs so the UI could use it.
LDAP and OS-specific address books will continue to use keys/values.
nsIAbCard
gains two new members:
supportsVCard
a boolean value indicating support for vCard (or lack thereof). Only AddrBookCard
objects currently support vCard.
vCardProperties
is a VCardProperties
object if the card supports vCard, or null.
The VCardProperties
class contains methods for parsing, manipulating, and serialising vCards. Each piece of information in a card is represented by a VCardPropertyEntry
object. See VCardUtils.sys.mjs for more information.
In practice, we'll be storing the vCard data as just another key/value pair. The key used will be _vCard
and the value will be the entire vCard.
When a card is saved to a directory, the following things happen:
If it supports vCard, the vCardProperties
member is serialised.
If it doesn't, the existing key/value pairs are converted to a VCardProperties
object, then serialised.
Some properties are collected from the card:
display name (DisplayName
)
first and last names (FirstName
and LastName
)
first and second preference email addresses (PrimaryEmail
and SecondEmail
)
nick name (NickName
)
The serialised vCard, these properties, and any key/value properties which can't be stored in a vCard are saved.
Any key/value properties which can be stored in the vCard are abandoned. No information is lost because the values are part of the vCard.
The names and addresses stored separately are for performance reasons. The vCard is considered the true source of information.
No migration takes place when the user upgrades to a Thunderbird version that supports vCard. However at this point the version number of the database storage is incremented (from 3 to 4) and a backup is automatically created.
Cards are only migrated when they are saved.
VCardUtils.sys.mjs contains a number of utility functions for converting between the storage types:
VCardUtils.abCardToVCard
converts an nsIAbCard
(any implementation) into a vCard string.
VCardUtils.propertyMapToVCard
converts a Map
of keys and values to a vCard string.
VCardUtils.vCardToAbCard
converts a vCard string to an AddrBookCard
.
VCardProperties.fromVCard
converts vCard string to a VCardProperties
object.
VCardProperties.fromPropertyMap
converts a Map
of keys and values to a VCardProperties
object.
VCardProperties.prototype.toPropertyMap
converts a VCardProperties
object to a Map
of keys and values.
VCardProperties.prototype.toVCard
converts a VCardProperties
object to a vCard string.
Conversion from a key/value dictionary to vCard should not result in any data loss. In the other direction data loss is possible.
To see exactly what fields are converted and what they are converted to, see typeMap
in VCardUtils.sys.mjs.
This page has all the information you need to get your macOS development environment set up and ready to hack on Thunderbird.
The Thunderbird build can use 30-40GB of disk space to complete depending on your operating system.
Note that while it's not technically required to have an internet connection to build, the default when building from mozilla/comm-central
is that --enable-bootstrap
is set so that the toolchains download automatically. If you do not have an active internet connection then
Note that once homebrew is installed, the macOS SDK headers are installed already and can be found under /Library/Developer/CommandLineTools/SDKs
. There should be no additional action required to install these SDK headers.
MozPhab is the tool needed to interface with Mozilla's instance of Phabricator. This step is needed before the bootstrap step. Pipx is the tool that we will use to install MozPhab and then we will make sure the relevant ~/.local/bin
has been added to the PATH envirnoment variable.
Once you have Mercurial and MozPhab installed, you are ready to grab the source code. There are a couple of different methods to do this.
Mozilla-central will build Firefox without the comm-central repo present and a few options set. Mozilla-central is the Firefox codebase and comm-central features the additions that turn Firefox into Thunderbird.
This will create a mozilla-unified
directory with both a mozconfig
and a comm/
folder inside. The mozconfig
file is setup to build Thunderbird and you can verify this with cat mozconfig
; the --enable-project
parameter should be comm/mail
:
If you would rather manually gather the source code, perform the bootstrap, and create your mozconfig
file, then follow these steps.
Get the latest Mozilla source code from Mozilla's mozilla-central
Mercurial code repository, and check it out into a local directory source
(or however you want to call it). Then, get the latest Thunderbird source code from Mozilla's comm-central
Mercurial code repository. It needs to be placed inside the Mozilla source code, in a directory named comm/
:
mozconfig
fileIn the source
directory run the following command to get additional dependencies needed to install Thunderbird:
You will be presented with the following options:
Please choose option 2 to proceed with a successful build.
This action should install all the remaining libraries and dependencies necessary to build Thunderbird locally.
It could happen that some libraries will not be installed by the bootstrap
command, specifically Rust
and Go
. Check if these packages are available in your system by running these commands in your terminal:
which rustc
which cargo
Install Rust: brew install rust
Install C bindings: cargo install cbindgen
If you get a command not found
error while running cargo
, but the command which cargo
returns the location of the that package, it means you need to update your PATH
inside your .bashrc
file to include the cargo
location:
Tutorial on how to activate and use Mercurial Queues.
Mercurial Queues is not compatible with the moz-phab
code submission tool. This page is here for historical reference only.
You can accidentally destroy work with MQ. MQ puts you in a position where you're doing fairly complicated stuff to your uncommitted work. Certain operations make it easy to lose work. Watch your step.
Other things to keep in mind:
Don't use MQ in a repository anyone might pull from. MQ creates temporary changesets in your repo. If someone pulls one of them, you'll never get rid of it.
Avoid the -f option. It is sharp and can mess up your repository if used incorrectly.
Ensure you use the latest stable release of Mercurial.
To enable MQ, put this in your Mecurial.ini
file for Windows or the $HOME/.hgrc
file for Linux and macOS:
Don't forget the git
line. This allows changing binary files in your patches. The unified
line give 8 lines patch.
Create a new patch with the command hg qnew -m "Bug ###### - fixing something amazing" patch-name.
. Let's quickly analyze this command:
qnew
is the command to initial a new patch.
-m
is the command that allows you to write a commit message.
"Bug ###### - fixing something amazing."
is the format we recommend using for the commit message, specifying the number of the Bug you're working on and a small description stating what you fixed.
patch-name
is, obviously, the name of your patch, and that can be anything.
Each repository has its own queue of patches managed by MQ. They're just stored as files in the .hg/patches
directory under the repository.
The commit message is optional and you can add it at a later time with a qrefresh
.
Whenever you change something in your code, you need to trigger the hg qrefresh
command in order to update your current patch with the latest changes. Do a hg diff
before you issue hg qefresh
to see which changes will be added to your patch. If you use multiple patches (see section below), it may be a good idea to do a hg qseries
to make sure the right patch is on top. Otherwise the changes will be added to the wrong patch.
It's always good practice to check if the current changes have been properly saved in your patch by using the command hg qdiff
. All the diffs will be listed in your terminal.
Note that both hg diff
and hg qdiff
take a -w
argument to ignore white-space in case you reindented blocks and it's hard to see the net changes.
If you're working on a patch and you need to pull the updates from upstream, you need to "disconnect" your patches in order to prevent merge conflicts. Here's a standard workflow you should follow:
hg qpop -a
to "pop" all your patches and revert the code base to its original status.
hg pull -u
to pull all the recent changes from upstream.
hg qpush
to apply once again your patch. Trigger this command as many times as you need in order to apply all the patches in sequence.
Merge conflicts can happen when reapplying your patches after pulling updates from upstream. Simply fix the merging issues and qrefresh
your patches.
MQ allows you to work on multiple patches at once and keeping the code separate. No matter how many patches you have applied in your queue, the qrefresh
command will only update the code to the patch at the top of you series.
Use the hg qseries
command to visualize a list of currently applied patches and their order.
Sometimes the queue ends up not being in the order you want. For example, maybe you've been working on two patches, and the second one (the topmost one in your queue) is ready to be pushed before the first one is.
If you have Mercurial 1.6 or newer, the best way to reorder your queue is hg qpush --move
. For example:
With older Mercurial versions, you can do this:
Reordering patches that touch the same file can cause conflicts when you push! If this happens, hg qpush
will tell you, and it will leave .rej
files in your working directory. To avoid losing work, you must manually apply these rejected changes, then hg qrefresh
.
With MQ you can import a patch into your queue, e.g. from Bugzilla. It is unapplied by default and the filename
is used as the patch-name. You can directly import a Bugzilla patch by using the Bugzilla attachment URL as the argument. In that case you may also want to use -n patch-name
to specify the patch name.
If you have the qimportbz
extension installed, you can also import by specifying a bug number:
Here's a quick overview of a standard workflow you will be using with MQ.
Note that hg export qtip > ~/bug-123456-fix.patch
is not necessary since all the patches reside in the .hg/patches
directory in your repository.
If you think that something has gone wrong, do this:
First check your patch queue: hg qseries
. If that looks right, do a hg diff
to see the latest changes which aren't in your patch yet. You can either add them to the patch using hg qrefresh
or remove them with hg revert --all
. Your best friend is the hg out
command, it shows all the changesets you have locally which aren't pushed to the repository yet. If for some reason you committed a patch to push it (using hg qfinish
), an action that only the sheriff does, or accidentally used hg import
instead of hg qimport
, hg out
will show changes that are not controlled by patches in a MQ. In this case you can strip all changeset hg out
shows using hg strip -r
with the lowest revision shown. After that, do hg update -C default
.
Commands mentioned so far can be abbreviated, so hg qser
, hg qref
, etc.
Of course you can delete patches from your queue using hg qdelete
or rename them with hg qrename
. If you decide to combine patches, there is hg qfold
, that will merge the first unapplied patch into the patch at the tip of your apply queue. Use with care since the merged patch will be removed and the applied patch will be irreversibly changed.
Even if you're not the sheriff, it's sometimes handy to be able to backout one or more changesets. Use:
How to lint and format code.
Thunderbird's source code is linted and formatted using automated tools, which provides several benefits that include:
ensuring a consistent formatting style across the code base
preventing formatting issues from taking up developer time in code review
catching problems that might otherwise go unnoticed
making it easier for developers to write well-formatted code
Mozlint is a library that standardizes linter configuration and provides an interface for running all linters at once. It is run via mach and is run automatically by Taskcluster.
You can run all the various linters in the tree using the mach commlint
command. Simply pass in the directory or file you wish to lint (defaults to current working directory):
The Mozlint configuration files in comm/tools/lint
are written so that they can be shared with the Seamonkey project. Thunderbird developers may want to set MOZLINT_NO_SUITE=1
in their environment so mach commlint
will not check comm/suite/
. Taskcluster will also set MOZLINT_NO_SUITE
when running lint checks.
Tutorial on how to use Mercurial bookmarks.
Bookmarks are basically labels that point to a given changeset (or a given "commit" in git terminology). They can point to different changesets at different times (more on this below).
For any of the commands below, you can learn more about them by running hg commandname --help
.
A common use case for bookmarks is for working on a given feature or bug. A bookmark is a label you use to manage your work on one bug or feature, alongside other work on other bugs or features. Here's an example.
Run hg pull
to download the latest changes from the remote repository.
Run hg update tip
to update your current working directory
to the most recent changeset that you just downloaded.
Run the command hg bookmark some-bookmark-name
to create a bookmark
that points to the current changeset.
Usually the name of the bookmark is related to the bug or feature you are working on.
Run hg bookmarks
to list all of your bookmarks and see which one is currently active.
The one you just created should now be active.
You can run hg log --graph
or hg wip
to see which bookmarks point to which changesets.
Or you may prefer using a GUI tool like
that provides a more graphical overview.
Make changes to the code until you are ready to commit them.
Run hg status
and hg diff
to check what files and changes you are about to commit.
If you need to add a new file use hg add some-filename
before committing.
Commit your changes with hg commit -m "Bug ##### - fixing something amazing."
.
The -m
is short for --message
and lets you provide a commit message for your changeset.
"Bug ##### - fixing something amazing" is the format we recommend for the commit message,
specifying the number of the Bug you're working on and a small description stating what you fixed.
Use hg log --graph
to see examples of commit messages.
Run hg log --graph
or hg wip
and note how the bookmark has moved to the changeset that you
just committed.
The active bookmark will automatically move to the most recent changeset as you commit your changes.
That way you can make a series of commits and the bookmark will always point to the most recent one.
You can use hg commit --amend
to modify the current changeset rather than making a new one.
Being able to work on more than one bug or feature, and easily switch between working on one or the other, is a typical use case for bookmarks.
Say you have been working on feature A using the bookmark feature-A
as described above, but now you want to switch to working on feature B. Here is how that can work.
Run hg log --graph
or hg wip
and copy the identifying number of the first changeset
that comes before your changesets for feature A.
For example if you see "changeset: 26858:4a2e39cfc820",
you can copy either the "26858" or the "4a2e39cfc820" to identify this changeset.
Let's say we use 26858.
Run hg update --rev 26858
to change the state of your current working directory to that changeset.
Run hg bookmark feature-B
to create a new bookmark to work on feature B.
It will currently point to changeset 26858.
Make some changes and do one or more commits for feature B.
To go back to working on feature A, you run the command hg update feature-A
.
That updates your working directory to the feature-A
changeset
and makes feature-A
the active bookmark.
Once you've made some changes and done some commits for feature A,
you may want to go back to working on feature B.
To do that you would run hg update feature-B
.
That updates your working directory to the feature-B
changeset
and makes feature-B
the active bookmark.
Say you are ready to submit one or more patches. Often other changes will have landed in the Thunderbird code base since you started your work. In that case it is best to make sure that your changes will still apply to the current state of comm-central. We can do that using hg rebase
.
Pull down any new changesets with hg pull
.
Use hg log --graph
or hg wip
to see if there are new changesets and
get the number (or the hash) of the first changeset
from the newly pulled changesets.
Let's say its number is 54321.
Rebase your branch on to the newest changeset
with the command hg rebase -b my-bookmark-name -d 54321
.
The -b
is for bookmark and the -d
is for destination.
We are rebasing the changesets from the bookmark my-bookmark-name
onto the destination changeset 54321
.
For example, you have a series of changesets like so:
Where C, D, and E are new changesets you have created, and your bookmark "my-bookmark-name" points to changeset E.
Then, after you do hg pull
you have this:
Where F, G, and H are changesets that have been added to comm-central since you started working on your changes in C, D, and E.
Then, after you do hg rebase -b my-bookmark-name -d tip
you have:
Where Mercurial has re-applied C on top of H, then D on top of C2, then E on top of D2.
Rebasing can be useful in other situations too. Another variation is hg rebase -s 44444 -d 54321
. Where -s
indicates a source changset. That will apply the source changeset on top of the destination changeset.
So, in the example above, if changeset C had the number 44444, then hg rebase -s 44444 -d 54321
would do the same thing as hg rebase -b my-bookmark-name -d 54321
.
What if there are "merge conflicts"? Say, in the example above, what if the same lines of a given file were changed in different ways by both changesets H and D, and Mercurial couldn't figure out what to do?
In that case, Mercurial will pause the rebase operation and guide you through the process of resolving the conflicts.
First Mercurial will tell you which files have conflicts. Open those files in your editor to fix the conflicts manually. There you will find something like the following, where you can see the two versions of the code that conflict:
Edit those lines to resolve the conflict, so that the code is what it should be after changeset D. Once all such conflicts are fixed, save the file(s).
Then back in the terminal run the command hg resolve --mark
to mark the conflicts as resolved.
And then do hg rebase --continue
and Mercurial will continue the rebase operation.
To get back to the state of things before the rebase operation started, do hg rebase --abort
.
You will need python
(version 3.8 or later) and pipx
(used to install packages from pypi
). Both of these can be installed from homebrew. If you have not yet setup homebrew, please see .
As noted in the , both mozilla-central
and comm-central
are version controlled with Mercurial. This means you will need to install Mercurial. Here is a quick command to install it with mercurial but for a more complete list of instructions, please see .
We have created and host a script that will grab the two source repos you need, run ./mach bootstrap
for you, and sets up a necessary mozconfig
file. This script is called . Download this file to the directory where you would like your source code folder to live, either by clicking the link and moving the file to the appropriate location or using wget
. Then we will make it executable and run it.
This step will need to be performed if you manually checked out the code and performed the bootstrap, and it will covered in the next section you follow, .
If one or both commands return an empty output, you need to install them manually. We recommend using to download and install these packages in your system. After that, follow these steps:
Go back to the page and continue following the guide:
If you're already familiar with Mercurial Queues but you need a quick overview of all the available commands, take a look at the
For instance, unless you're running the , hg qrefresh
is destructive. It replaces your previous version of the current patch with what's in your working directory. The previous version is lost.
Version your patch queue to save changes. The extension can make this much easier.
There is also limited integration with Phabricator with hg phabread
. That needs a special setup. Ask the resident Thunderbird sheriff for details or read (but there's more to it).
It works the same as regular mach lint
, however it has Thunderbird configs pre-applied. If you're not familiar with using mach lint
on Firefox code, see the Firefox documentation to get started.
Several mozlint-based checks run automatically on Taskcluster, and more are being added. The Firefox documentation is a valuable resource if you're seeing errors on your try builds. If you are not sure how to run a check locally, the mach commands run by Taskcluster are in comm/taskcluster/ci/source-test/mozlint.yml
.
This is a brief "quick start" guide to using Mercurial Bookmarks for Thunderbird development. For more in-depth documentation see the section of Mercurial for Mozillians and the page on the Mercurial Wiki.
If you have used git, it will help to know that bookmarks in Mercurial are very similar to branches in git and involve similar workflows. (Note that Mercurial's branches are very different from git branches.) You may find it helpful learn more about the similarities and differences between Mercurial and Git. For example, see the in-depth discussion in .
Set an accurate version number, the earliest possible/likely version where it reproduces is ideal (eg. the version # of the bug causing the regression). “Trunk” or “unspec” should be changed to an actual number.
OS should be set if the issue is OS specific. (also add to the bug summary as a keyword, eg. “Mac”)
Summary is key. Improving the summary speeds up understandability and searchability - your improvement can save everyone valuable time. 1) remove extraneous words. 2) add key words that express how the user experiences the issue. 3) add technical/coding info if appropriate, but never remove user symptoms from the summary.
Using ESLint to Format Javascript Code
For JavaScript code we use both:
These tools can be used via the command line or right in your code editor.
After editing some JavaScript code, navigate to the comm/
directory. (The following commands need to be run from the comm/
directory so that Prettier will use the comm/.prettierignore
file, and not the .prettierignore
file in the directory just above comm/
. See Prettier issue 4081.)
For a single file, run this command, which will attempt to automatically fix any linting or formatting problems:
Or for all the files in a given directory:
To simply report any problems but not attempt to automatically fix them, just omit the --fix
flag:
Most popular code editors offer plugins for eslint and Prettier. We highly recommend installing a plugin for eslint and a plugin for Prettier so you can lint and format your code as you are editing it. Issues will be highlighted as you type and you can have Prettier format your code with a few key strokes.
Here are links to plugins for various editors:
eslint plugins for various editors
Prettier plugins for various editors
Some of us on the Thunderbird team use the VS Code editor with these plugins:
VS Code plugin by Esben Petersen
VS Code plugin by Dirk Baeumer
Chat Core uses a message style system based on HTML, JS and CSS that is very similar to the one created for Adium. If you plan to create a message style, reading the Adium documentation on the topic may be helpful -- see this tutorial and this reference sheet.
On the other hand, you may prefer to jump right in, using the default message styles as examples: https://searchfox.org/comm-central/source/mail/components/im/messages
The rest of this page includes a variety of information about the specifics for creating a message theme for Thunderbird.
The minimal content of chrome/ is:
Info.plist which contains metadata about the theme
main.css
Incoming/Content.html
The other files are optional:
Style information in CSS files
main.css is the stylesheet used for the default variant of the theme, and is also included before the variant-specific stylesheet used for other variants.
Variants/<variant name>.css contains the stylesheet for the variant <variant name>.
Other files used by the stylesheets (e.g. images)
HTML files: these files are used to build the HTML markup of the conversation.
File Name
Fallback if missing
Usage
Footer.html
None (will use an empty string)
Displayed at the bottom of a conversation.
Header.html
None (will use an empty string)
Optionally (user preference) displayed at the top of a conversation
Status.html
Incoming/Content.html
Used for status messages
NextStatus.html
Status.html
Used for status messages directly following another status message added a short time before.
Incoming/Content.html
None (this file is required)
Used for incoming messages.
Incoming/Context.html
Incoming/Content.html
Used for incoming messages in an old conversation displayed to give context.
Incoming/NextContent.html
Incoming/Content.html
Used for incoming messages directly following another incoming message added a short time before.
Incoming/NextContext.html
Incoming/NextContent.html
Used for incoming messages directly following another incoming message added a short time before in an old conversation displayed to give context.
Outgoing/Content.html
Incoming/Content.html
Used for outgoing messages.
Outoing/Context.html
Outoing/Content.html
Used for outgoing messages in an old conversation displayed to give context.
Outgoing/NextContent.html
Incoming/NextContent.html
Used for outgoing messages directly following another outgoing message added a short time before.
Outgoing/NextContext.html
Outgoing/NextContent.html
Used for outgoing messages directly following another outgoing message added a short time before in an old conversation displayed to give context.
These labels will be replaced with message-specific data when used in HTML files bracketed by percentage signs, e.g. %chatName%.
chatName: Title of the conversation,
sourceName: The account used for this conversation (the local alias is used if it is set, otherwise the account name is used).
destinationName: The name of the conversation,
destinationDisplayName: Title of the conversation,
incomingIconPath: URL to the buddy icon of the person you are talking to. Will fallback to "incoming_icon.png" if no icon is available,
outgoingIconPath: Should be the URL to the buddy icon of the account used for this conversation. Currently, this always "outgoing_icon.png",
timeOpened, timeOpened{format}: The time when the conversation started, takes an optional format argument.
message: The text of the message. The actual text will be wrapped in a span node, like this:
time, time{format}: The time when the message was sent. Takes an optional format parameter (unfortunately, the formats supported are not the same on all the OSes as this internally calls the native strftime function of the OS),
timestamp: The time when the message was sent, as an integer value (number of seconds since 1970). Useful to compute intervals between messages,
datetime: The date and time when the message was sent.
shortTime: The time when the message was sent,
messageClasses: CSS classes that apply to the message. This can typically be used in a class attribute of an HTML node. Possible values include:
"action": message starting with /me,
"message": regular message (not status/system messages),
"incoming": incoming message,
"outgoing": outgoing message,
"autoreply": message sent automatically, for example to reply with an away message,
"event": system/status message,
"nick": chat message that contains your nick,
"error": error message (for example "<user> as cancelled the transfer of <file>"),
"delayed": delayed message (Currently this seems to be used only to show the recent message history when joining a Jabber chat room),
"notification": notification message (messages requesting the user's attention).
userIconPath: URL to the buddy icon of the person you wrote the message. Fallbacks to "Incoming/buddy_icon.png" if the icon is missing. For outgoing messages, it always uses "Outgoing/buddy_icon.png",
sender: The name of the sender of the message: the alias if one exists, or the username otherwise,
senderColor: The color associated with the sender of the message. In chatrooms, this will contain a color string valid in a CSS rule. In IM conversations, it will be an empty string.
senderStatusIcon: URL of an icon associated with the current status (idle, away, offline) of the sender of the message,
messageDirection: Direction of the message. Always "ltr",
senderDisplayName: Currently identical to sender. Should be the server-side alias in the future,
senderScreenName: The username of the sender of the message,
service: Name of the protocol through which the message transited (e.g AIM, MSN, XMPP, Google Talk, ...),
textbackgroundcolor: Should be a color string based on the formatting information included in the message. Currently, this is always "transparent".
status: Should be a string indicating the nature of the event that caused this message to appear. Currently this isn't implemented, and the result is always an empty string,
statusIcon: URL of an icon associated with the current status (idle, away, offline) of the other person in the conversation.
The file Info.plist is a property list file containing metadata about the theme.
The following keys are used by Thunderbird:
DefaultVariant: The name of the default variant. Optional. "default" will be used as the name if this key doesn't exist.
MessageViewVersion: If the version number provided is >= 3, the main.css file will be used for all variants, otherwise it will be used only for the default variant (this is for compatibility with old Adium themes. In new themes, use the value 4).
DisplayNameForNoVariant: The display name for the default variant (that is, when no variant is selected). Optional. If this key doesn't exist, a localized version of the string "Default" will be used for display in the theme selection UI.
DisableCombineConsecutive: This will disable the use of NextContent.html/NextContext.html/NextStatus.html HTML templates. Consécutive messages won't be treated differently from other messages.
ActionMessageTemplate: This is used to specify how action (/me) messages should be displayed. The value of this key will be used to replace %message% in Content.html, before doing the replacement. Optional. If this key doesn't exist, the default value "* %message% *" will be used.
The following keys work, but only use them if you really feel you absolutely need them, because they make it impossible for the user to select the font in the usual way via Preferences -> Content:
DefaultFontFamily: the default font family to use for the conversation. Values in CSS stylesheet files can override this.
DefaultFontSize: the default font size to use for the conversation. Values in CSS stylesheet files can override this.
If your theme needs to work with Adium too, you need more keys, see this page for details.
Example:
The right file to put JavaScript in a message style is inline.js
. Some Adium themes use a custom Template.html
file to include JavaScript in the theme or include JavaScript in other theme files, this is NOT supported in Thunderbird.
The code that you insert in the inline.js
file will be executed as soon as the conversation is loaded. It is a good place to register event listeners.
DOM Mutation events are of particular interest, because they allow you to execute scripts when a new message is added. See the DOM Events documentation.
Example:
This can be placed in the file inline.js
:
Below lists the known differences between message styles in Adium and in Chat Core. Keep in mind that Adium displays conversation using Webkit and that Thunderbird uses Gecko, so the rendering may differ slightly.
NextStatus.html Optional. Used for status messages that quickly follow another status message. If this file doesn't exist, Status.html will be used instead.
ActionMessageTemplate This is used to specify how action (/me) messages should be displayed. The value of this key will be used to replace %message% in Content.html, before doing the replacement. If this key is not provided, the default value "* %message% *" will be used.
NoScript This theme does not include an inline.js
file for JavaScript.
These classes can be used in the CSS files of themes.
ib-msg-txt This class is present on all texts that are actually a message. The %message% replacement in the HTML templates actually adds a span node with this class around all messages.
ib-img-smile This class is present on all img tags that were added in messages by the smiley system.
ib-nick This class is present on participants' nicknames in multi-user chats (MUCs). It carries the "left" attribute when the participant has left the chatroom. You may need an !important if you wish to override the default styling of bold and coloured text. The colour hue of the nick (for use in CSS HSL colour values) is also available stored in the "nickColor" DOM attribute of the DOM element carrying the ib-nick class.
Mozilla-specific pseudo-classes, for example: *:-moz-any-link
The ruler that appears between read and unread messages (from Thunderbird 38) is a hr
element with the id #unread-ruler. When styling it, changes which override those in the default style for this element (in conv.css) must be marked !important
.
Note: it is possible to style the unread messages themselves by using a CSS sibling selector on the unread ruler.
The following element IDs are used internally by Thunderbird and must not be given to any DOM elements by message styles:
insert-before
actual-insert
next-messages-start
next-messages-end
unread-ruler
end-of-split-block
Chat
ibcontent
You will see bugs that have been around for years and received very little activity, or bugs that were originally opened as means to track new features, but went out of scope as the overall direction of the project changed.
Closing those bugs will help clean up our Bugzilla searches and keep things more organized.
Features request (RFE) that are outside our current or future scope.
Bugs that don’t apply anymore (E.g. old address book issues, UI that has been replaced).
Bugs left open with the intention of one day fixing it, but do not align with our scope.
Planned work for the 2025-2026 releases of Thunderbird.
This is a list of all the primary objectives and efforts that we will be working on.
The delivery quarters provided are tentative and those timelines could change throughout the year.
Priority: P1 Delivery Quarter: Q3 2025 High level objectives (non-exhaustive):
Full thread conversation in message pane.
Collapsible/Expandable messages.
Thread tree visualization.
Reply inline (optional).
Priority: P1 Delivery Quarter: Q2 2025 High level objectives (non-exhaustive):
Modern event modals with flexible UI.
Video conferencing integration.
Maps searching integration (Google, Apple, OpenStreetMap).
Modern HTML, markdown, plaintext editor.
Customizable UI with sensible defaults.
Customizable agenda and reminders.
Better handling of unprocessed invitations.
Priority: P1 Delivery Quarter: Q2 2025 High level objectives (non-exhaustive):
Account Hub for email, calendar, address book, feeds, and newsgroups.
Thunderbird Sync as the first option.
Guided discoveries of features when first accessing (welcome wizard).
Early customization options to set the desired defaults.
Expose important entry points like encryption and other more hidden settings.
Priority: P2 Delivery Quarter: Q3 2025 High level objectives (non-exhaustive):
Remove old customizable UI toolbar.
Unifying controls for spaces, standalone dialogs, and windows.
Add overflow space.
Add more buttons and elements (spin buttons, separators, button groups, etc).
Menu position.
Rows.
Drop the App Menu!
Priority: P2 Delivery Quarter: Q3 2025 High level objectives (non-exhaustive):
Spaces and tabs rebuild, with each space handling their own tabs.
Standalone windows per space without the need of having the "mail" space.
Priority: P2 Delivery Quarter: Q4 2025 High level objectives (non-exhaustive):
Same tray code for Linux, Windows, and macOS.
Unread indicator and counter.
Minimize to tray.
Start minimized.
Compose action per account.
Open settings.
Quit action.
Priority: P2 Delivery Quarter: Q4 2025 High level objectives (non-exhaustive):
Quick actions to reply, delete, mark messages.
Pause notifications for # time.
Manage notifications per account.
Focus modes.
Priority: P3 Delivery Quarter: Q1 2026 High level objectives (non-exhaustive):
Drop the C++ editor code.
Implement a modern JS editor component that can be reused in calendar, tasks, and any other text area field that needs it (signature, address book notes, etc).
Link previews.
Rich text with advanced features.
Markdown support and preview.
Native emojis support (unicode).
Mentions support.
Priority: P3 Delivery Quarter: Q1 2026 High level objectives (non-exhaustive):
Modal dialog, because a settings tab doesn't make sense.
More sections and tabs for an intuitive discovery path, drop the never ending single pages.
All "Appearance" settings in one page, including native themes (follow system, light, dark)
Application settings and Account settings should be in the same place.
"Labs" section for listing experiments
Priority: P3 Delivery Quarter: Q2 2026 High level objectives (non-exhaustive):
Easier onboarding! Store them in the profile and sync them if there are no supported calendars.
Use open specs to allow interoperability.
Be opinionated with what we do here.
Modern UI.
Show tasks in the calendar.
Kanban board.
Integrate into emails, address books, and other areas.
Priority: P3 Delivery Quarter: Q3 2026 High level objectives (non-exhaustive):
Stats!
Quick links to common actions.
Summarization of accounts.
Blog listing (feed).
Suggested actions.
Priority: P3 Delivery Quarter: Q4 2026 High level objectives (non-exhaustive):
Create design plan
Identify areas with technical debt
Prioritize existing features
Prioritize new features
Priority: P1 Delivery Quarter: Q1 2025 High level objectives (non-exhaustive):
Full support of email operations.
Full support of account creation.
Priority: P1 Delivery Quarter: Q2 2025 High level objectives (non-exhaustive):
SQLite based.
Rust interfaces.
Migrations.
Rollbacks.
Build in parallel and populate it alongside the current database.
Priority: P1 Delivery Quarter: Q1 2026 High level objectives (non-exhaustive):
Thunderbird should be the champion of encryption and propose a new open path.
Making it easier for non-technical users to use a reliable encrypted solution.
Priority: P2 Delivery Quarter: Q1 2025 High level objectives (non-exhaustive):
Add Sentry SDK & Initialise with DSN
Add channel/environment
Leverage default error capturing mechanisms
Add Custom Breadcrumb for one component
Configure reporting
Test
Document for future consumption in other components
Priority: P2 Delivery Quarter: Q2 2025 High level objectives (non-exhaustive):
Audit current startup code.
Slowly rebuild it.
Create performance profiles to measure startup performance impacts.
Implement the "�start minimized" back-end.
Take ownership of the "primary password"� process
Priority: P2 Delivery Quarter: Q3 2025 High level objectives (non-exhaustive):
Integrate the exchange calendar protocol.
Use it as a way to rebuild/improve the current calendar protocol.
Support exchange-specific features.
Priority: P2 Delivery Quarter: Q3 2025 High level objectives (non-exhaustive):
Integrate the exchange address book protocol (maybe it uses carddav?)
Support exchange-specific features.
Fetch users' profile pictures.
Priority: P3 Delivery Quarter: Q1 2026 High level objectives (non-exhaustive):
Audit what can be supported via GraphAPI.
Build a parallel protocol to A/B test against EWS.
Priority: P3 Delivery Quarter: Q2 2026 High level objectives (non-exhaustive):
Store notes in the profile and in Sync.
HTML, markdown, plaintext editor.
Share notes via Send.
Copy the Google approach (create notes for this event).
Potential Pro offering (cloud notes with shared access like Etherpad).
Potential syncing through IMAP.
Priority: P1 Delivery Quarter: Q1 2025 High level objectives (non-exhaustive):
Plan and implement an efficient and standard approach to links within the application
Priority: P1 Delivery Quarter: Q1 2025 High level objectives (non-exhaustive):
Implement Sentry and telemetry to capture data that will help us prioritize response to issues based on frequency.
Add or change logging tools to make it easier for the majority of users to provide logs.
Priority: P2 Delivery Quarter: Q2 2025 High level objectives (non-exhaustive):
Assign a small migration effort every 2 weeks to each developer.
Priority: P2 Delivery Quarter: Q2 2025 High level objectives (non-exhaustive):
Complete the project.
Validation that data is being captured as expected.
Help services consume the data.
Priority: P2 Delivery Quarter: Q3 2025
Priority: P2 Delivery Quarter: Q3 2025 High level objectives (non-exhaustive):
Work with Rob to create scripts, VMs, checking tools etc that we can have running regularly to catch edge cases much sooner without users having to experience then report.
Priority: P3 Delivery Quarter: Q3 2025 High level objectives (non-exhaustive):
Reimplement the ability to use Movemail in Thunderbird.
No account creation feature for now.
Priority: P3 Delivery Quarter: Q4 2025 High level objectives (non-exhaustive):
Replace overlay files with modules and system modules.
Defer non-DOM operations to system modules.
Lazy load modules when needed.
As we focus on the P1s, we need to always account for the dozens of other features we currently ship in Thunderbird.
These features always require maintenance, regressions and security fixes, and upstream porting.
What we won't do during this cycle is adding new features or rebuilding big chunks of code outside of the prioritized components and efforts listed in the front-end and back-end tables.
These "outsiders"" listed here are a way to highlight low priority maintenance efforts that can happen during the year, but that won't (shouldn't) take us away from the top priorities.
Feeds
Inheriting the email UI for now is acceptable.
It needs a complete UI and UX overhaul.
Newsgroups
Low user adoption.
Inheriting the email UI for now is acceptable.
Add-ons page
A very large effort that will happen in the future.
It's tied to the website rebuild.
Address Book space
More improvements are needed but the current state is superior to other areas.
A substantial rebuild happened 2 years ago.
OpenPGP and S/MIME integration
Currently in a usable and enough stable state.
Dedicating security fixes during this cycle.
The UI saw some improvements in the past few years.
Message Filters
They will need to be rebuilt from scratch.
Tied to the new database implementation for easier message move/copy handling.
Import/Export
Sync will make the need to import/export the whole profile hopefully uncommon.
After that we should disable exporting the entire profile because it fails when dealing with large sizes.
Account Settings
They should be integrated into the Settings space.
It needs a lot of UI/UX rethinking.
Planned work for the 2024 release of Thunderbird for Android (K-9 Mail).
Implement a more automated account setup flow with settings auto-discovery and guided setup steps.
Default implementation of unified folder alongside the ability to manage and organize long folder lists.
Improvements of the content rendering of single message view, better management of contacts, extra info, and attachment through bottom sheets.
Complete adoption of the Material 3 UI toolkit.
Improvement and reorganization of the settings in order to offer simpler and more intuitive paths to customization and control.
Thunderbird for Android will be released once all these major efforts are completed and the most glaring bugs and crashes are resolved in K-9 Mail.
Thunderbird for Android will not replace K-9 Mail as both app will be maintained and released at the same time and can be used in parallel.
Implementations of a threaded “conversation” - similar to the mobile Gmail interface.
Implementation of Mozilla Accounts and Sync between Desktop and Mobile.
The Thunderbird try server works in exactly the same way to the Firefox try server with a few minor differences. The automation is based on the same hardware and tools, so there should be few differences.
Thunderbird's Try server is often referred to as "try-comm-central", or "try-cc", but usually it's just known as Try. Use an upper-case T if it makes things clearer.
To use the try server, you'll need Level 1 Commit Access. You can learn more about Mozilla's commit access policies and start the process of signing up for an account here: Becoming a Mozilla Contributor.
You need to make sure your ~/.ssh/config
is properly configured to use the correct SSH username for the Mozilla Mercurial repository.
Your SSH config file should have something like this:
Your User should match the SSH username that has been grante Level 1 Commit Access.
Try server has a separate repository based upon comm-central. You'll need to add the address to your Mercurial configuration file at path/to/comm-central/.hg/hgrc
:
You can of course access the repository via HTTP, but not push to it, hence the ssh:// address.
Having gained level 1 access and configured Mercurial, you can push to Try. In general, it's just a matter of applying your patch(es) and running hg push -r . try
if you're planning to manually trigger tasks from the Taskcluster web interface.
For pushes via the command line, we recommend using the push-to-try
extensions in order to simplify the commands required to automatically trigger jobs and tasks on the try server.
An example of a command to trigger all mochitest jobs for an artifact build will look like this:
Read the full list of try syntax commands to write in your commit message.
Pushing to try-comm-central will create builds using the most recent mozilla-central code, which may or may not be a good idea at the time. Generally it's okay, but there may be unresolved problems between the two repositories. If you strike a problem, ask in the #maildev Matrix chat room. You can also work with a specific mozilla-central revision, see "Testing mozilla-central patches" below.
You can (and should) control what testing tasks you want to run on your push. There are several methods to do so:
This is the easiest and most common way. A special code (known as Try syntax) is put in the commit message of the tip-most revision being pushed, for example try: -b o -p linux64 -u all
creates only an "opt" build on 64-bit Linux, and runs all of the tests on that build.
Here is the Try syntax try-comm-central understands:
-b
Build type. Use o
for an opt build (most common), d
for a debug build, or do
for both.
-p
Platform. There are five platforms. Each also has a -shippable
variant, which is a complication you probably don't need to think about.
linux
32-bit Linux
linux64
64-bit Linux
macosx64
Mac OS (if you don't need a debug build, specify macosx64-shippable
instead)
win32
32-bit Windows
win64
64-bit Windows
all
for all platforms
-u
Unit test suites.
mochitest-thunderbird
xpcshell
all
--artifact
Artifact builds. See the Artifact Builds page for more information.
For more control, a special file named try_task_config.json
and containing a list of the tasks to run is included in one of the pushed revisions.
The contents of the file look like this:
use-artifact-builds
tells the Try server to do an artifact build. Set to false or remove it for a full build. See the Artifact Builds page for more information.
tasks
is a list of tasks to run. In this example it's all of the 64-bit Linux tests. A 64-bit Linux build will also run, because it is required by the tasks specified.
A copy of the file with all available tests is maintained here. A typical workflow would be to copy the file into your working directory, remove the tasks you don't want to run, and commit it. For efficiency you could export the commit as a patch and import it again when needed.
Task configurations and names change over time. If you're not getting the tasks you requested, this may be why.
To find the name of any particular task, click on existing instance in Treeherder, then look for the "job name" in the lower-left corner of the page.
If you commit with neither Try syntax nor a try_task_config.json
file (or you want to add to an existing run), you can one or more tasks using Treeherder. Once the decision (D) task has completed, click the drop-down arrow to the right of it, and choose "Add new jobs".
When the build at https://treeherder.mozilla.org/jobs?repo=try-comm-central
is complete (normally takes 1-2 hours):
In the black header below click "Artifacts and Debugging Tools".
Install the downloaded file.
If you have changes that affect mozilla-central, you may wish to do a Try run to check Thunderbird isn't broken. Here's how:
In your mozilla-central directory, apply your patch. Then run ./mach try empty
to push to the mozilla-central Try repository. You'll need to know the revision number of your push, which will be in the message printed to the console.
Move to your comm-central directory.
Modify the file .gecko_rev.yml
– change GECKO_HEAD_REPOSITORY
to https://hg.mozilla.org/try
, and GECKO_HEAD_REV
to point to the revision you previously pushed to M-C's try with mach try empty
.
Now push to try-comm-central as per usual.
You can change .gecko_rev.yml
to point to any revision on the mozilla-* trees to test your comm-central patch against them.
It's not required, but you should base your comm-central patch on a known good revision of comm-central (probably the tip), and your mozilla-central patch on the matching mozilla-central revision (also probably the tip). Otherwise changes made to one tree but not the other (such as build configuration changes) can cause problems.
To find the matching revision, open the log of the comm-central decision (D) task and search for "built from mozilla-central revision".
When doing a Try run for patches to comm-beta
or comm-esr##
, the steps are the same as when doing a Try run for comm-central
. (For example, you do not need to change anything in your hgrc
file.) The try server is smart enough to automatically detect which one to build and test. This works because of the .gecko_rev.yml
file. Note that some things might not work the same way as on comm-central
(e.g. the --artifact
option only works on comm-central
).
Follow along with UX Architect Alessandro as he works on various parts of Thunderbird, fixing bugs, changing the UX/UI, and showing how to participate in Thunderbird's development.
In this development session Alessandro creates a patch to fix issues with the dark theme on Linux and explains how to create a patch and submit it for review.
Alessandro walks through the design process and how mockups are made, iterated upon, and the thinking behind the process.
In this session Alessandro works on fixing a bug with folders in Thunderbird, he also demonstrates how to search through the code in order to find what you are looking for.
In this session Alessandro works on fixing some compose window bugs and explains how to work with the compose window interface and inspect the Thunderbird interface elements.
In this session Alessandro works on changing how attachments are displayed when composing in Thunderbird.
In this session Alessandro continues improvements to the compose windows and attachment interface. He also dives in-depth on styles and other bits that make up how the UI is built.
This is a page for documenting the notifications from Chat Core in Thunderbird. This is likely out of date. Notifications are grouped by interface you need to attach the observer to.
Draft: This page is not complete.
Note: all imIContact notifications also go to any imITag it belongs to, as well as nsIObserverService.
Tutorial on how to dive into triaging bugs.
How to efficiently triage bugs and contribute to a clean and organized Bugzilla
The best way to optimize your time and drive a productive triaging session is to set limited expectations, and only tackle a limited amount of bugs per day. It’s easy to feel overwhelmed after triaging 10 bugs (some of which may be difficult) and seeing that in the meantime 50 more bugs have been reported.
Try to manage your expectations and accept that what you are doing, as small as it can look, is important and vital for the success of Thunderbird.
Not all of these are hard requirements, but some nice to have configurations that will make it easier to triage complex reports.
Always be respectful and polite even when faced with a difficult reporter, because a) this is a customer facing environment, b) being empathetic is healthy for both you and the reporter. Doesn’t mean you have to engage with impolite reporters or accept abuse (see 3rd point below). “Thank you”, and “please”, and similar ideas go a long way to encouraging users to engage and return.
Acknowledge the issue, e.g.: “I’m sorry you are having some issues, could you please provide me with the following information so that I can look into it…”
Don’t accept harassment from a reporter, if they violate our community rules mark the comment as abuse and close the bug as invalid.
If the bug is a real issue, recreate it in a new bug. Allowing abuse in Bugzilla sets a bad precedent and is unhealthy for everyone involved and the entire community.
E.g.: “You guys are idiots. This is disgusting. The Thunderbird Team/you are stupid”.
Do not drive users against developers, and do not argue with other team members in a bug. The MZLA team is to always have a united front on Bugzilla.
E.g. Triager finds the regression and pulls in the developer. Developer says that it was intended that this feature would work differently (or is now unsupported).
Another core developer joins the conversation and publicly disagrees with the previous developer’s statement.
Don’t argue in the bug - take it to another internal forum to discuss, come back with a united front on how to proceed.
Don’t promote your own point of view. A triager should be unbiased - is it a bug? Is it reproducible? That’s it.
Do not try to create a solution to the bug. Leave the ideation and implementation of a solution to the developers, designers, and product management as there are likely many considerations that go into a resolution.
Stay neutral and take a step back when things are hard to handle. We have 20 million users, lots of bugs and lots of negativity will come in. A bug report comes from an emotional reaction, likely a negative experience. You need to detach from the emotional aspect of any bug and not escalate emotions, but also not take it as a personal attack.
Don’t triage bugs that you have no idea what they are. Stick to your area of expertise!
If you’re in the back-end, don’t try to triage front-end bugs, and vice versa.
No need to add noise to bugs if you have no clue what the topic or context is.
Stick to your strengths and to the areas you worked on. Participate in the bugs that you know what the reporter is talking about and you have historical knowledge of that area.
Point users to KB articles so they learn to use them, and hopefully are then less likely to report things that are not bugs and more likely to make good bug reports.
Try to first reproduce the issue reported.
Try to add all the flags and data points in one go in order to limit email noise for all the people CC’ed on those bugs.
Don’t let your time go to waste. If you have read a bug but are unable to confirm or add significant info (you can’t reproduce, etc) - at a minimum: assign a component, a better summary, or NI or CC someone who can take a next step.
Tutorial on how to push approved and reviewed patches to the production server
Firefoxtree's main feature is automatic configuration of remote repository paths. The documentation doesn't mention them, but the Thunderbird repositories are mapped as well:
As a new contributor, you probably don't need to land your own patches. Instead add the keyword checkin-needed-tb
to the Bugzilla bug, and one of our friendly sheriffs will land it for you.
You'll need to add the address to your Mercurial configuration file at path/to/comm-central/.hg/hgrc
:
In this example we used the label live-cc
but you can use whatever you like. Using the standard label default-push
is not recommended – having to type the name of the repository helps prevent mistakes.
Is it a good time to push? The best time to push is shortly after Mozilla updates the Firefox live server. Since Thunderbird builds on top of Firefox, pushing to live then will ensure that the build will get the latest changes.
Is there a build in progress already? If there is, please wait until you're reasonably sure the first build is not broken. In most cases this means that the Linux and OS X builds (B) are complete and tests (bct, X) are starting to turn green (free from major failures).
Is the tree green? If it isn't, do not push. Pushing something on top of an already broken build wastes resources (both computing and human).
Having gained level 3 access and configured Mercurial, you can push to comm-central. In general, it's just a matter of applying your patch(es) and running hg push
, but let's not do that right now as a series of important checks need to happen before.
These are a series of recommended steps to always go through before pushing to Live to ensure you're pushing only what you need.
Be sure your commit message is clear and has been approved during review. The standard syntax of a commit message is Bug 000000 - Description of the patch and fix. r=reviewer
.
Always check the reviewer in a commit message matches the person who actually reviewed the patch. A patch could be reviewed by someone other than the originally intended person, or it could have been sent to a group of reviewers.
Run hg qpop -a
to clean your local queue.
Run hg in
to check if there are updates on the live server. (This isn't strictly necessary if you always do the next step.)
Run hg pull -u
to download and apply the most recent changes.
Reorder your queue and run hg qpush
to apply only the patches you want to push to Live.
Run hg qseries
to double check your have the right patches applied.
Run hg qfinish --applied
to include all the currently applied patches in your local tree.
Run hg out
to see a list of patches that will be pushed to the Live server. Check your commit message(s) again.
Run hg push live-cc
(or any shorthand you used in your hgrc
file) to push your applied patches to comm-central.
Run hg pull
to download and apply the most recent changes.
Run hg rebase -b my-bookmark-name -d XXX
to rebase your patches. Replace the XXX with the latest public revision.
Run hg out -r my-bookmark-name
to see a list of patches that will be pushed to the Live server. Check only the patches you intend to send are listed. Check your commit message(s) again.
Run hg push live-cc -r my-bookmark-name
to push your applied patches to comm-central. Always specify a bookmark or revision to avoid sending more than one branch.
If your patch is faulty (i.e. it breaks the build or fails tests), it may be backed out without any warning. It's up to you to fix it.
The tree is monitored for failures by a team of people and although they are nice people they are not expected to tolerate your broken patch. A tree without failures is much easier to work with for everybody concerned.
Adding some magic words to the commit message of the tip-most revision will cause the build system to do different things. Getting it wrong or making a typo will not get the desired result.
DONTBUILD
tells the build system not to build on this push. Only the decision and linting tasks will happen, unless another process comes along and starts a build, such as the Daily automatic build.
CLOSED TREE
allows you to push to a closed tree. I hope you have permission!
a=approver
You must specify who approved the changes on some trees (not comm-central).
To land a patch you didn't write, e.g. from Bugzilla, you'll need to import it into Mercurial: hg import -e https://bugzilla.mozilla.org/attachment.cgi?id=0000000
Use the -e
flag as above, or hg commit --amend
to edit the commit message as necessary.
To import a patch or patches from Phabricator, use moz-phab patch
: moz-phab patch --apply-to tip --no-bookmark --skip-dependencies D000000
The --apply-to
argument adds the patch to a specific parent revision, in this case the tip revision.
The --no-bookmark
argument prevents a Mercurial bookmark from being created automatically. If you're just importing to land a patch, creating and then deleting a bookmark is just wasting your time.
The --skip-dependencies
argument imports only the patch in question. Otherwise moz-phab
will attempt to import all parent and child revisions in the Phabricator stack, including revisions that may already exist on your tree (and in this case fail miserably). You may want this to happen, in which case don't use this argument.
Use hg commit --amend
or hg histedit
to adjust commit messages as necessary.
Our Phabricator installation has a system for automatically landing patches: Lando. To use it click "View stack in Lando" and follow the instructions. A few minutes may pass before an actual landing attempt happens. If it fails (usually because the patches do not apply cleanly) you'll be notified by email and will have to find a solution.
To land several patches together, create a "stack" of patches by using the "Edit related revisions…" and section in Phabricator. This can get messy so plan in advance. Check the current stack of any revision in the "Revision Contents" section of Phabricator.
If you worded your commit message correctly, a bot will post a message in your bug with a link to the changes you made, and close the bug. To prevent the bot from closing a bug, add the leave-open
keyword to the bug before landing. The bot will automatically remove the checkin-needed-tb
flag if it is set.
At this point you should set the Target Milestone field in the bug to the current version, which is generally, but not always, the last option for that field.
You do not need to land patches on beta or ESR. We have authorised people to do that for you.
Request approval on Bugzilla in the same way you request review, using the approval-comm-beta
and approval-comm-esrXX
flags. At an appropriate point approval will be granted (or denied!) and your patch will be landed for you. Filling out the request form, especially the "Regression bug" and "Risk" fields, will help the approver prioritize the patch.
When requesting uplift approval for a bug with multiple patches, a single request is sufficient. It's helpful to specify in the request that multiple patches are to be uplifted. Likewise, if a bug has dependencies on another bug, specifying those dependencies and what order they should be uplifted can save a lot of time.
Uplifting patches to earlier versions is for fixes to major bugs, and regressions that break the user interface. It should not be used as a shortcut to get new features to users earlier (some exceptions apply). The release channels ensure that changes are exposed to a test audience for a period of time before being shipped to all users.
The comm-esrXX repositories in particular can be difficult to uplift patches to because of code-churn since the repository was created. Sometimes it's necessary to create a patch specifically for comm-esrXX. In these cases, attach the patch in Bugzilla rather than Phabricator. The fix still needs to be applied to comm-central and comm-beta first unless the bug really only affects comm-esrXX. (Bugs that only affect comm-esrXX are actually quite rare. At some point the bug most likely was present on comm-central and was fixed.)
Click the green "B" (for binary) next to one of the following: "Windows 2012 x64 [shippable] opt", "Linux x64 [shippable] opt", "OS X Cross Compiled [shippable] opt" (unless instructed to use a debug build).
In the Artifacts section, to download the install file click on target.installer.exe
(Windows), target.tar.bz2
(Linux), or target.dmg
(Mac).
Have the ability to test on all the Operating Systems (bare metal or VMs), and should not be blocked by technical limitations as much as possible. Run all currently supported versions (Daily, Beta, ESR). Ability to run and familiarity with the tool.
Examples: , - in general items under https://support.mozilla.org/en-US/products/thunderbird/fix-slowness-crashing-error-messages-and-other-problems. (If an existing article is inaccurate, please ping Roland, Heather, or Wayne to get to get it updated)
If you ran mach bootstrap
, you were given the option to configure Mercurial as well. If you didn't do so at the time, you can run mach vcs-setup
at any time. This will clone the repository to your .mozbuild
directory and configure your .hgrc
file.
mach vcs-setup
will offer to enable the extension. Despite its name, it is helpful for Thunderbird work as well.
Another helpful extension that mach vcs-setup
does not configure for you is . It adds several revision set specifiers and templates to Mercurial. Run hg help mozext
after enabling it to see what it offers. To enable, add to you ~/.hgrc
file's [extensions]
section (replace <home_dir>
with your actual home directory):
To push a patch to comm-central, you'll need . You can learn more about Mozilla's commit access policies and start the process of signing up for an account here:
You can of course , but not push to it, hence the ssh:// address.
Is the tree open? Check – the name of the tree in the top-left corner shows you the status of the tree. Usually it's "open" (a green circle is displayed), which means you can push. Other statuses are "approval required" (yellow padlock) and "closed" (red X) which mean you can't push without permission, and in fact the server will prevent you from doing so.
Mozilla usually updates the Firefox Live Server around 0400, 1000, 1600, and 2200 UTC on weekdays and 1000 and 2200 UTC on weekends (give or take an hour). If one of these times is approaching, it's probably not a good time to push. You can check to see when they last pushed.
Coordinate with others in the as they may already be planning to push. A Firefox push is usually followed by an assigned person landing something to check the build is not broken.
Pushing to comm-central will create builds using the most recent mozilla-central code, which may or may not be a good idea at the time. Generally it's okay, but there may be unresolved problems between the two repositories. If you strike a problem, ask for help in the .
You should not push without having complete a successful push and build to the .
Take a look at the to see your push show up at the top of the list.
account-added
null
<1.0
a new account has been created
account-connected
null
<1.0
the account has connected
account-connecting
null
<1.0
the account has started a connection attempt
account-connect-error
null
<1.0
the account has disconnecting with an error
account-connect-progress
null
<1.0
the account is attempting to connect
account-disconnected
null
<1.0
the account has disconnected
account-disconnecting
null
<1.0
the account is disconnecting without an error reason
account-list-updated
null
<1.0
the list of accounts has been updated
account-removed
null
<1.0
an account is about to be removed
account-updated
null
<1.0
app-handler-pane-loaded
null
<1.0
Applications pane in preferences window is loaded
autologin-processed
null
<1.0
imICoreService::processAutoLogin() is complete
browser-request
null
<1.0
Used by protocol plugins to bring up a browser window (e.g. for an OAuth request)
closing-conversation
null
<1.0
contact-moved
imIContactsService imIContact
null
<1.0
when a contact has been moved (removed from one tag, added to another, or both)
contact-tag-added
imIContact
tag.id
<1.0
when a tag is added to a contact
contact-tag-removed
imIContact
tag.id
<1.0
when a tag is removed from a contact
conversation-closed
null
<1.0
conversation going away
conversation-left-chat
null
<1.0
the user has left the conversation (but it might remain visible)
conversation-loaded
null or details
<1.0
subject is a <browser type="content-conversation">, if fired because an existing conversation is moved from one window to another, the data parameter ("details") will contain the string "imported".
im-sent
message text
<1.0
on sending an IM (whether or not it succeeded)
new-conversation
null
<1.0
conversation created
new-ui-conversation
null
<1.0
prpl-quit
null
1.2
Core shutdown, was previously purple-quit
status-changed
status text
<1.0
user (not buddy) status change
tag-hidden
null
<1.0
fired when a tag is hidden
tag-shown
null
<1.0
fired when a tag is set to be shown (unhiding it)
ui-conversation-closed
null
<1.0
unread-im-count-changed
count of unread messages
<1.0
fired when the number of unread messages changes
new-text
null
1.5
New message is about to be displayed.
new-directed-incoming-message
null
1.5
New incoming message that is either a direct message or highlights the user.
ui-conversation-replaced
null
91
The subject UI conversation is about to be replaced by a new instance (with different interfaces but the same ID).
conversation-update-type
null
91
The type of the underlying prplIConversation has changed. Is always preceded by a ui-conversation-replaced notification that disposes of the old UIConversation instance, while this will have the new one as subject.
account-sessions-changed
null
91
The account session list has changed
account-encryption-status-changed
null
91
The account encryption status info has changed
account-buddy-availability-changed
null
<1.0
Possibly fired on account buddy status change
account-buddy-display-name-changed
imIContactsService imIAccountBuddy
old serverAlias
<1.0
Fired when setting imIAccountBuddy::serverAlias
account-buddy-icon-changed
imIAccountBuddy
null
<1.0
Fired when setting imIAccountBuddy::buddyIconFilename
account-buddy-signed-off
imIAccountBuddy
null
<1.0
Possibly fired on account buddy status change
account-buddy-signed-on
imIAccountBuddy
null
<1.0
Possibly fired on account buddy status change
account-buddy-status-changed
imIAccountBuddy
null
<1.0
Possibly fired on account buddy status change
buddy-added
null
<1.0
In response to account-buddy-added if the buddy previously had no account buddies
buddy-availability-changed
null
<1.0
possibly fired on imIBuddy status updates
buddy-display-name-changed
old serverAlias
<1.0
possibly in response to account-buddy-display-name-changed on the preferred account
buddy-preferred-account-changed
null
<1.0
when setting imIBuddy::preferredAccountBuddy (internally)
buddy-removed
null
<1.0
In response to account-buddy-removed if the buddy no longer has any account buddies
buddy-signed-off
null
<1.0
possibly fired on imIBuddy status updates
buddy-signed-on
null
<1.0
possibly fired on imIBuddy status updates
buddy-status-changed
null
<1.0
possibly fired on imIBuddy status updates
contact-added
imIContact
null
<1.0
When a new buddy is created with no other contact
contact-availability-changed
imIContact
null
<1.0
Possibly fired on contact status updates
contact-display-name-changed
imIContact
null
<1.0
Fired on setting imIContact::Alias or imIContact::preferredBuddy, or the preferred buddy's display name changed
contact-no-longer-dummy
imIContact
null
<1.0
Fired when a contact is being loaded?
contact-preferred-buddy-changed
imIContact
null
<1.0
Fired on setting imIContact::preferredBuddy
contact-removed
imIContact
null
<1.0
When the last buddy for the contact is removed
contact-signed-off
imIContact
null
<1.0
Possibly fired on contact status updates
contact-signed-on
imIContact
null
<1.0
Possibly fired on contact status updates
contact-status-changed
imIContact
null
<1.0
Possibly fired on contact status updates
contact-moved-in
imIContact
null
<1.0
When a contact has been moved into the tag
contact-moved-out
imIContact
null
<1.0
When a contact has been moved out of the tag
tag-hidden
imIContact
null
<1.0
fired when a tag is hidden
tag-shown
imIContact
null
<1.0
fired when a tag is set to be shown (unhiding it)
chat-buddy-add
null
<1.0
When users join a chat. The enumerator elements are prplIConvChatBuddy.
chat-buddy-removed
null
<1.0
When chat buddies leave a chat. The enumerator elements are nsISupportsString of the user name.
chat-buddy-update
old name, if the chat buddy is being renamed
<1.0
data is null if this is not a rename
chat-buddy-topic
null
<1.0
The chat topic was updated
new-text
null
<1.0
A new message will be written to a conversation. This can be an incoming, outgoing or system message. Subject was purpleIMessage until 1.2.
update-text
null
91
An existing message is updated based on the remoteId
of the message.
update-buddy-status
null
<1.0
away-ness or online/offline
update-conv-chatleft
null
<1.0
the user has left the chat (it might remain visible)
update-conv-title
null
<1.0
update-typing
null
<1.0
preparing-message
null
1.5
Outgoing message before it's been prepared for sending by the protocol
sending-message
null
1.5
Outgoing message was prepared (and potentially split into multiple messages) and will be sent by the protocol after this observer.
chat-update-type
null
null
91
Conversation changed between being a chat and a direct message conv (isChat
toggled).
update-conv-icon
null
91
The convIconFilename
of the conversation changed.
update-conv-encryption
null
91
The encryptionState
of the conversation changed.
received-message
null
1.5
New message is about to be prepared for display.
new-text
null
1.5
New message is about to be displayed. (Before 1.5 probably just passed along the prplIMessage
)
new-directed-incoming-message
null
1.5
New incoming message that is either a direct message or highlights the user.
status-changed
Status Text (string)
?
user-icon-changed
New icon file name (string)
?
user-display-name-changed
New display name (string)
?
idle-time-changed
New idle time (number)
?
https://hg.mozilla.org/comm-central
comm, c-c, cc
https://hg.mozilla.org/releases/comm-beta
comm-beta, c-b, cb
https://hg.mozilla.org/releases/comm-esr102
comm-esr102
https://hg.mozilla.org/projects/ash
ash
https://hg.mozilla.org/projects/jamun
jamun
https://hg.mozilla.org/try-comm-central
try-comm
Tips on writing Mochitest tests for Thunderbird.
This document offers some basic tips for writing Mochitest tests. (See also this Mochitest page in the Firefox docs, which is Firefox-centric but may still be useful.)
You may be writing a new test in an existing test file, or you may have set up a new test file as described in Adding Tests. Either way, add a new test with the add_task
function:
Many essential functions live in these files.
EventUtils
and BrowserTestUtils
do not need to be imported as they are already available in Mochitest files. mailTestUtils
requires importing:
This document is a basic introduction. To go further, explore these files (particularly the docstrings of the various functions in them) and look at existing tests.
Use the is
and ok
functions to make test assertions. is
compares two values (using JavaScript's ===
equality comparison). ok
asserts that a single value is truthy (in JavaScript's sense of truthy).
The last argument to each is a message printed to the console to identify the assertion. It is optional but is a good practice for more understandable test logs.
Do a single click on a DOM element:
To double-click change the clickCount
from 1 to 2. There is a shorthand for a single click:
If the test is interacting with a window that is not the main one, pass the relevant window as the (optional) third argument:
Type some text with the keyboard, or type a single key, even a non-character one like the tab key:
Some other valid keys for sendKey
include: RETURN, BACK_SPACE, DELETE, HOME, END, UP, DOWN, LEFT, RIGHT, PAGE_UP, PAGE_DOWN, SHIFT, CONTROL, ALT, ESCAPE, F1, F2, etc. (Not an exhaustive list.)
If the test is interacting with a window that is not the main one, pass the relevant window as the (optional) second argument:
To press a key along with one or more modifier keys:
Some other options are altKey
, shiftKey
, and ctrlKey
(a non-exhaustive list).
Similar to sendString
and sendKey
, there is an optional third window
argument to use when interacting with a specific window.
Tests move faster than users. Sometimes too fast. The test may need to wait for an event to occur before doing the next thing.
Sometimes the test needs to let the application respond to something the test did before moving on to the next step, and there is not an event to listen for. Here is a simple way to do this:
Some tests will need to interact with windows that are not the main window. For example, below is a function that opens the address book window. It returns the window
(nsIDOMWindow
) object for the address book window, which can then be used when calling functions like EventUtils.sendKey
.
The key point is the use of BrowserTestUtils.domWindowOpened
, but this example also demonstrates some of the other tips found in this document. (See below for dialog windows which are handled differently.)
Interact with dialog windows by using BrowserTestUtils.promiseAlertDialog
.
Trees are not like other DOM elements and require special handling.
How to run Thunderbird's automated tests.
XPCShell tests test Thunderbird's components, without opening the user interface. Firefox also runs this type of test, and much of the information about Firefox's XPCShell tests also apply to Thunderbird.
To run an XPCShell test, or a directory of them, use mach:
mach
must point to mach. The path argument is always relative to the mozilla-central root, so include comm/
at the start.
If more than one test runs, just a summary of the results will be displayed. You can pass --verbose
to get the full output if necessary.
Like XPCShell tests, mochitests are a type of test used on Firefox. The main difference is that mochitest runs with the full UI, in the context of the main mail window.
To run a mochitest, or a directory of them, use mach:
mach
must point to mach. Any test path that matches the path given runs, for example instead of a path you could just put browser_foo.js
and every test with that name in any directory would run.
How to add your own tests for Thunderbird.
Generally, tests live near the code they are testing, however some old tests are in a separate test directory.
This document doesn't cover actually writing tests, for that see this page for Mochitests:
And also these pages:
Mochitest (archived MDN content)
(Just note that these pages are Firefox-centric and include some ancient ideas and practices.)
Tests should be added to a directory near the code they are located. For example, code in mail/components/extensions
is tested by tests in mail/components/extensions/test
. Inside the test
directory is a subdirectory named after the type of test: browser
for mochitests (as in Firefox terms they are "browser-chrome" mochitests), and xpcshell
or unit
for XPCShell tests.
A new directory needs a test manifest:
The default section isn't even necessary here, but you probably want to add a head.js
file if you're going to have more than one test.
The calendar preferences in line 3 is unnecessary outside of the calendar tests. Calendar tests always run in UTC.
Mochitest needs some prefs set, or automated testing will fail.
The calendar preferences in lines 3-4 are unnecessary outside of the calendar tests. Calendar tests always run in UTC with the week starting on Sunday.
The next thing you need to do is tell mach about your new test manifest. In the nearest moz.build
file, add these lines as appropriate:
Manifest V3 is the next iteration of WebExtensions, and offers the opportunity to introduce improvements that would otherwise not be possible due to concerns with backward compatibility. MV2 had architectural constraints that made some issues difficult to address; with MV3 we are able to make changes to address this.
source: Mozilla Add-on Community Blog
One of the key concepts of Manifest V3 is to deprecate persistent background pages. The original proposal from Google meant to replace background pages completely by service workers, which however resulted in criticism from the community. Most add-ons would have to be rewritten to use service workers, and some add-ons could not have been converted at all.
Mozilla introduced Limited Event Pages to allow add-on developers to keep using the known concept of background pages. These event pages are terminated when they become idle, and restarted whenever an event for which they are registered occurs.
In addition to these fundamental operational changes, some APIs have been changed to remove inconsistencies. Also, all methods and properties marked as deprecated have been removed.
The full list of required changes to convert a Manifest V2 extension to Manifest V3 can be found in our WebExtension API documentation.
Patches can land on comm-central at any time, but in general we try to organise this around when mozilla-central changes. Since the mozilla-central code can break Thunderbird in any number of ways, and they won't stop to wait for us to catch up, we try to meet every mozilla-central push with a push of our own.
Sheriffs aim to merge changes to mozilla-central at (very roughly) 0400, 1000, 1600 and 2200 UTC. After a merge, something should land on comm-central to start a new build. Land something of your own or check for bugs flagged checkin-needed.
In extreme circumstances the tree can to be closed to prevent further pushes. Some members of the Thunderbird team (Daniel, Geoff, Magnus, Rob) have authorisation to do this, or you can request the help of a MoCo sheriff in #sheriffs.
Our code is not perfect. You might have noticed. :-)
Some things fail occasionally (we call these intermittent failures) or most of the time (permanent failures). If a test fails every now and then, it's not generally a problem – usually it's a sign that a test hasn't accounted for something, like some asynchronous code happening in a different order than expected.
We file bugs about intermittent failures in the hope that one day we'll have time to investigate and fix them. Bugs with the intermittent-failure
keyword are displayed in the Failure Summary section if they are a close match with the error message in the log:
If you're sure a bug description is the right one, click the pin icon next to it. This adds the current task to the pinboard and the bug number to the classification. If you're logged in you can click save to associate the bug with the task. The task gains a little star icon to mark that somebody's looked at it and understood it.
If a new intermittent failure appears, you can click the blue bug icon next to the error message to file a new bug. This does a lot of the legwork for you, all you have to do is choose the right Bugzilla component so that it gets some attention.
Usually new intermittent failures get ignored for a while and they go away, or are so infrequent it's not worth the hassle. Don't feel the need to file a bug about every one you see.
Some things aren't usually starred, like out-of-memory failures on debug builds or failures from mozilla-central tests (ours all start with comm/
). There's little benefit to doing so.
The pinboard can be used for other things. Collect tasks there with the pin icon or ctrl+clicking on them, and you can mark tasks as expected failures or to say they've been fixed by something that has landed since.
If the build fails, we've got a serious problem that should be dealt with before anything else lands. Landing more patches on top of a broken tree just makes things worse and harder to debug. Unfortunately, Firefox developers don't stop if we have a problem.
If a test starts failing on multiple platforms, something is broken and should be dealt with. Some failures can hide other failures, and it's not nice to fix one only to find another has appeared in the meantime.
History lesson: a long time ago, when bug numbers had five digits, TreeHerder's predecessor was Tinderbox, a huge grid showing the status of every build and test machine. Statuses were displayed in various colours: green, yellow, red, and black with flames (an animated GIF!). If you see the phrase "[xyz] is burning", that's where it comes from.
This is really not an easy thing to describe. Much of it comes down to intuition and experience.
Check the Failure Summary of TreeHerder for the most basic details. Check the task log for more information. Mochitests usually produce a screenshot of the first failure in a task. This is linked from the Job Details section named mozilla-test-fail-screenshot_XxYyZz.png.
The most likely cause of an unexpected failure is a change to mozilla-central. Sometimes we are warned in advance of things we need to do, sometimes not. To figure out what has changed on mozilla-central, get the revision from the last build before the problem and the first build with the problem, by clicking on the decision task (D or Nd) and looking at the Artifacts tab:
You can then view the mozilla-central pushlog by copying the revisions into the URL: https://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=[good revision]&tochange=[bad revision]
Or, install this simple add-on, which will add buttons to TreeHerder to do the above steps for you.
From there it's a matter of finding the most likely candidates and figuring out if they are the cause of the problem. In many cases there's a change that needs to be copied into comm-central.
Is that failed task really a failure? In some cases simply starting it again can help, such as if there's network problems. Click the retrigger button (looks just like the Firefox reload button) to restart a failed task.
Great, you've figured out what's wrong and how to fix it. Get that fix reviewed and landed! If the failure is sufficiently serious, you can land the fix and then get it reviewed later. If you do this, use rs=bustage-fix
in your commit message to say why you landed it without review.
To disable a test, add the appropriate skip-if
notation to the test manifest. skip-if = true
disables the test on all platforms. Other options are available, check the tree for examples. If you disable a test there must be a bug filed about it. Use the magic words [thunderbird-disabled-test]
in the bug whiteboard and make sure appropriate developers are notified so the test can be fixed and re-enabled.
If it looks like a Thunderbird developer is responsible for causing a problem, contact them or their reviewer. If neither can be found and there's a serious failure, consider backing out their changes. Check whether you're right first – finding out your work has been backed out overnight is not the nicest way to start a day.
When performing a backout, use the hg oops
command (part of the mozext
extension from Mozilla’s Version Control Tools). The extension needs to be enabled in your .hgrc
file as described in Landing A Patch.
For backing out a single revision, use hg oops -er <rev>
. This will open an editor with a commit message started. Add a reason for the backout like:
For multiple revisions, hg oops
can condense the backout to a single commit with the -s
argument like hg oops -esr 2f665a0a379f -r 478cffed4b5f
.
After pushing the backout, update the bug in Bugzilla:
Mention the reason for the backout.
Link to the backout commit starting with https://hg.mozilla.org
Link to the push in Treeherder
Set the NEEDINFO flag in Bugzilla to make sure the patch author sees it.
The latest developments for Add-On developers.
What you need to know about making add-ons for Thunderbird.
Current Thunderbird add-ons are based on the WebExtension technology, which is also used by many web browsers. Browser vendors usually refer to their add-ons as WebExtensions. Thunderbird however has a lot of features not available in Browsers, and add-ons written for Thunderbird will most likely not work in Browsers. They are therefore sometimes referred to as MailExtensions instead.
WebExtensions are a collection of files that change the way Thunderbird looks and behaves. They can add user interface elements, alter content, or perform background tasks. Thunderbird add-ons are created using standard JavaScript, CSS and HTML.
Key features of WebExtensions:
They use stable APIs and do not need to be updated when a new version of Thunderbird is released.
They use a permission mechanism that requires users to confirm any permission requested by add-ons before they can be installed. These permission requests enable users to know what an add-on is actually doing.
An add-on can either be an extension (adding functionality or changing the way Thunderbird works) or a theme (changing the way Thunderbird looks).
There's a lot of information out there when it comes to add-on development, and finding the most relevant one can be time-consuming. We have therefore put together a tutorial that explains step by step how to create your first extension for Thunderbird:
For more detailed information on the two add-on types supported by Thunderbird, see their respective guides:
Extending the example extension to use a background page.
In the third part of the Hello World Extension Tutorial, we will introduce the concept of the WebExtension background page.
We will keep track of incoming mails, add a menu entry to the tools menu and also a context menu entry to our button in Thunderbird's main toolbar and a click on both will open notifications with the collected information from the last 24 hours.
In the first two parts of the Hello World Extension Tutorial, we used well-defined UI hooks to load HTML pages when the user opened one of our popups. In contrast, the background page - if defined - is automatically loaded when the add-on is enabled during Thunderbird start or after the add-on has been manually enabled or installed. It is automatically destroyed when the add-on is shutting down.
Actually defining a background HTML page, that uses script
tags to load the JavaScript files.
Just defining the to-be-loaded JavaScript files and let Thunderbird create a background page on-the-fly.
We place the following background.html
file into our hello-world
project folder:
Let's also create an empty background.js
script file in the hello-world
project folder.
The above code is using an inline arrow function to define the callback function for the event listener (which is called for each onNewMailReceived
event). This is identical to the following implicit function definition:
async function onNewMailReceivedCallback(folder, messages) {
...
}
messenger.messages.onNewMailReceived.addListener(
onNewMailReceivedCallback
);
The onNewMessageReceived
event requires the accountsRead
permission, which needs to be added to the permissions
key in our manifest.json
file.
In line 5
of the shown background script, we request the current messageLog
entry from the WebExtensions local storage. The used syntax allows to define a default value of []
(an empty Array), if there is currently no messageLog
entry stored.
We could also request multiple values from the local storage:
The call to storage.local.get()
returns a Promise for a single object with the requested entries, for example the above console.log(rv)
could produce the following output:
To access the content of the messageLog
member, one would have to use rv.messageLog
. That is sometimes not the desired behaviour, and instead we store the requested value directly in a variable. This is called object destructuring, and it maps the content of the messageLog
member of the returned object to the messageLog
variable. Any other non-matching returned member is ignored.
Access to the local storage requires the storage
permission, which needs to be added to the permissions
key in our manifest.json
file.
The listener is using a helper function to be able to loop over the received messages. The iterateMessagePages
function is defined in an ES6 module, which is loaded in line 2
of the background script shown above. So, we create a modules
subfolder into our hello-world
project folder. Then, we place the messageTools.mjs
file within. Here follows what it should contain.
To identify the script file as an ES6 module, which does not include file scope code, but only defines functions, we use the *.mjs
file extension.
The provided iterateMessagePages()
wrapper function is doing most of the heavy lifting and allows to asynchronously loop over the returned messages in line 7
of the shown background script. For each received message, we subsequently push a new entry into the messageLog
Array.
In line 15
we store the updated messageLog
Array back into the local storage. We use the object shorthand notation, which allows leaving out the actual value definition, if the value is stored in a variable with the same name as the object's member name. If the shorthand notation is unwanted, one could instead write the following:
Let's add the following code to our background script, which will add two menu entries and will react to them being clicked:
Using the menus
API requires the menus
permission, which needs to be added to the permissions
key in our manifest.json
file.
Using the notifications
API requires the notifications
permission, which needs to be added to the permissions
key in our manifest.json file.
This is how our manifest.json
should now look like:
Our background script should look as follows:
After you have received one or more new messages, while the add-on has been active, open the context menu of our browser_action
button in Thunderbird's main toolbar and click on "Show received emails". For each received message, you should see a notification.
Descriptions of all manifest keys supported by Thunderbird.
Several manifest keys in the following table are common to Thunderbird and Firefox and link to MDN description pages. Please be aware, that MDN is dedicated to browsers and of course to Firefox. Some information listed on MDN may not apply to Thunderbird.
The two columns MV2 and MV3 specify whether the manifest key is supported in Manifest V2 and/or Manifest V3.
Descriptions and examples of MailExtension APIs to interact with Thunderbird's user interface.
A browser action adds a button to Thunderbird's main toolbar:
It is controlled by the browser_action
manifest key in the extension's manifest.json
file:
A compose action adds a button to the toolbar of the composer window:
It is controlled by compose_action
manifest key in the extension's manifest.json
file:
A message display action adds a button to the toolbar of the message view window:
It is controlled by the message_display_action
manifest key in the extension's manifest.json
file:
Thunderbird menus are accessible through the following context types:
This document aggregates information on topics that commonly arise when developing a new Experiment. For a complete documentation on each individual topic, refer to the linked articles.
The use of optional permissions is not supported for the same reason.
When you encounter a feature that cannot get implemented with existing WebExtension APIs, it may be helpful to first read through the documentation of some built-in APIs, this document and some linked documentation to get a feeling about how existing APIs encapsulate common problems (i.e. listener registration through events, excessive use of Promise
, ...) and what limitations your API will have to live with (i.e. potential complexity when passing functions or raw DOM elements).
Afterwards, think about (hypothetical or real) add-ons that would have similar needs and try to design an API that would be useful for a wide range of add-ons:
A good API provides a single feature with as little complexity as possible (smaller is usually better).
Example: an API adding both a menu item and a toolbar item for a single callback function might be useful, but it would be better to split it in two separate operations and combine them in the WebExtension.
A good API can be used in a generic way and is not tied to your add-on's logic or functionality.
Example: an API to add a menu item that will perform a fixed action might solve the specific issue you're working on, but it would be better to permit WebExtension code to dynamically specify the action to perform.
A good API adheres to the conventions of WebExtension APIs.
Example: a registerListener
function might be a good idea in isolation, but it would be better to use events (that also simplifies the implementation!).
Depending on your time constraints, experience, and the concrete feature you're trying to write an API for, it is not necessarily reasonable to satisfy all three criteria, they are just a guideline to aim for.
A parent implementation implements the API in Thunderbird's main process. All features that were available to a bootstrapped legacy extension can be used here.
A child implementation implements the API in the content process(es). This permits more complex interactions with WebExtension code and potentially improves performance, at the cost of not being able to access the main process.
A typical entry in the manifest.json file to register an Experiment:
Technically speaking, Thunderbird is not actually using multiple processes (yet). However, the APIs were designed with multiple processes in mind and enforce at least some constraints as if the parts were in different processes.
In most cases, you can start by formalizing your API draft into a schema and adding a parent implementation using one of the linked articles as base. Add or switch to a child implementation if you have performance considerations or need to pass more complex data (see below).
Check out the Example Experiments:
Avoid declaring global variables in the implementation of your Experiment, as that can cause collisions with other Experiments loaded. Instead declare them as members of your API, or use a closure.
Experiments are loaded on demand. In order for an Experiment to get loaded, you thus need to either use the API from the WebExtension or register an implementation for the startup
event (which calls the onStartup()
method of that implementation's ExtensionAPI
object once your add-on is loaded).
If your add-on is unloaded, Thunderbird will call the onShutdown()
method of each loaded implementation's ExtensionAPI
object. You should perform any cleanup tasks in that method, for example you must invalidate Thunderbird's startup cache whenever your add-on is unloaded for a non-shutdown reason:
Failure to invalidate caches may cause parts of the add-on's Experiment APIs to be cached across updates of the Add-on, even if they are changed in the update. It is thus usually a good idea to execute the code above in the onShutdown()
method of an Experiment that is always loaded.
In addition to your Experiment being loaded and unloaded as a whole, that Experiment's API will get loaded into each WebExtension context independently. As there can be multiple contexts at the same time, an Experiment may have multiple loaded APIs in parallel. You can perform context-specific loading tasks directly in getAPI()
, and register context-specific unloading code through context.callOnClose()
.
If your API function is supposed to return a value, it must be defined as async
in the schema file.
If you want to pass more complex data structures, especially functions or instances of custom classes, you can do so from a child implementation. There is a big caveat, though: the Experiment's implementation scripts are privileged relative to WebExtension scripts, which causes their scopes to be disjunct:
Accessing data that belongs to an Experiment from a WebExtension: it is not possible to directly access chrome-scoped objects from a WebExtension (but it can hold references on it).
There are two options to work around that: either the Experiment clones the object into the unprivileged scope of the WebExtension or it directly constructs an unprivileged object.
The second option is using the constructors in context.cloneScope
directly from the Experiment. Their results can be used from the WebExtension without further cloning.
Common pitfall: async
functions return a Promise
in the scope of the function, so you need to wrap such functions before cloning them into a WebExtension scope.
The file TestModule.sys.mjs
in the modules
folder will then be accessible via
resource://myaddon/TestModule.sys.mjs
.
Since system modules cannot be unloaded, we have to append a unique query, to make sure cached files are not re-used after an update. In the following example the version
key from manifest.json
is used. This allows us to use the same identifier throughout the entire add-on, but load the new version whenever the add-on has been updated:
Experiments should be standalone and have no dependencies to any WebExtension components. If possible, design your Experiments as if they were a feature of Thunderbird that does not know about your add-on, and keep the scope as narrow as possible. If you design your APIs correctly, you will not need to access the WebExtension part of your add-on.
In some rare cases, it may be possible that that is not feasible to follow this recommended practice for the current update cycle. In that case, it is possible to tear down the separation between the Experiment and the WebExtension. That can permit you to treat an Experiment as if it were a part of the WebExtension, except for the restrictions regarding passing data outlined above.
In a child implementation, you can directly access the real WebExtension scope via Components.utils.waiveXrays(context.cloneScope)
.
Outside of a child implementation, or if you need the scope of the background page in particular, you can extract the background page's scope from an extension
object:
This hack only works because Thunderbird is internally not (yet) using multiple processes. Again, it is highly recommended to design your APIs in a way that these interactions are not necessary as it is likely that this technique will stop working in future versions of Thunderbird.
Extending the example extension to use a content script.
In the fourth part of the Hello World Extension Tutorial, we will introduce the concept of content scripts.
We will add a banner to the top of the message display area, displaying some information about the currently viewed message. The banner will also include a button to mark the currently viewed message as unread.
Content Scripts are JavaScript files that are loaded and executed in content pages. This technology was mainly developed for browsers, where it is used to interact with the currently viewed web page.
compose scripts loaded into the editor of the message composer
message display scripts loaded into rendered messages when displayed to the user
The messageDisplayScripts
API requires the messagesModify
permission, which needs to be added to the permissions
key in our manifest.json
file.
Whenever a message is displayed to the user, the registered CSS file will be added and the registered JavaScript file will be injected and executed.
Let's create a messageDisplay
directory inside our hello-world
project folder with the following two files:
Content scripts cannot yet be loaded as top level ES6 modules. They cannot load other ES6 modules. They can also not use the async
keyword in file scope code, we therefore have to create the async wrapper function showBanner()
.
The main purpose of the message-content-script.js
file is to manipulate the rendered message and add a banner at its top. We use basic DOM manipulation techniques.
In line 2
of message-content-script.js
, we send the message object {command: "getBannerDetails"}
, to request the display details from the background page. In line 24
we send the message object {command: "markUnread"}
, to request the background page to mark the currently viewed message as unread.
The background page can listen for runtime messages, by registering the following listener:
The messages.update()
function requires the messagesUpdate
permission, which needs to be added to the permissions
key in our manifest.json
file.
In this example, we check if the runtime message includes our command and based on its value either return the banner details or mark the viewed message as unread.
Note: The onMessage
listener has a third parameter sendResponse
, which is a callback function to send a synchronous response back to the sending tab. For Thunderbird however the preferred way is to return an asynchronous response using a Promise instead.
Note: It is best practice to always define a synchronous listener function for the runtime.onMessage
event. If defined asynchronously, it will always return a Promise and therefore answer all messages, even if a different listener defined elsewhere is supposed to handle these.
The listener should only return a Promise for messages it is actually supposed to handle.
This is how our manifest.json
should now look like:
Our background script should look as follows:
Open a message. There should be a red banner added at the top of it with its subject and a button labelled "Mark unread". Clicking that button should mark the message as unread
This list collects standards that the Thunderbird family of applications currently at least partially implements or supports (in our code base, ignoring things like TCP which we inherit from upstream
Legend
✔ = This line item is supported for the given platform
✖ = This line item is not (yet) supported for the given platform
empty = We have not collected the standards information for the given platform yet
N/A = The standard does not apply to the given platform
Advanced add-on developers (with in-depth knowledge of Thunderbird source code) can extend the current available set of APIs by creating their own APIs and ship them with the add-ons. These APIs are called , and they interact directly with Thunderbird's internal APIs, allowing add-ons to use additional features not yet available via built-in WebExtension APIs.
The background page is a standard HTML page, supporting the same technologies as ordinary HTML pages, but it is never shown to the user. Its main purpose is to load one or more JavaScript files into the background. Those background scripts can be used to listen for events or to initialize and properly set up the add-on. As , there are two ways to load background scripts:
The author of this example prefers the first option. It allows to use the await
keyword in file scope code and it allows to load other . We therefore add the following section to our manifest.json
file:
In order to listen for new messages, we have to add a listener for the event to our background script:
The callback function of the onNewMailReceived
event receives two parameters: folder
being a and messages
being a . The defined event listener stores the folder and the message information of the new received mail in the extensions storage.
Since Thunderbird's WebExtension API potentially has to handle a lot of messages, the data type is paginated. Please check the tutorial for more information.
In line 2
we create a new menu entry. We use the title Show received email
and we add it to the browser_action
context and to the tools_menu
context. A list of other available contexts can be found on the page.
The function returns the id
of the new menu, which we can use to identify our menu, or - for example - add submenus by using the id
as the parentId
for other menu entries.
In order to do something when our menu is clicked, we add a listener for the event. We check the id
of the clicked menu to see which of our menus was clicked (we only added one, but checking here anyhow).
After we have retrieved the current messageLog
from the local storage, we loop over all entries and for each entry in line 20
.
Let's double-check that we made the and have all the files in the right places:
As described in the , go to the Add-ons Manager to open the Debug Add-on Page and temporarily install the extension.
If a default_popup
is defined, a popup will be opened and the defined html page will be loaded, when the button is clicked. Additionally, you can use the API in your background script to interact with the button to modify badge text, icon, title or its enabled state.
If the action button is defined as a , it will open a drop-down menu when clicked.
Note: The browserAction
API has been renamed to action
in .
If a default_popup
is defined, a popup will be opened and the defined html page will be loaded, when the button is clicked. Additionally, you can use the API in your background script to interact with the button to modify badge text, icon, title or its enabled state.
If the action button is defined as a , it will open a drop-down menu when clicked.
If a default_popup
is defined, a popup will be opened and the defined html page will be loaded, when the button is clicked. Additionally, you can use the API in your background script to interact with the button to modify badge text, icon, title or its enabled state.
If the action button is defined as a , it will open a drop-down menu when clicked.
Thunderbird supports cloud providers to upload large attachments to a server, instead of attaching them directly to the email. Extensions can register such cloud providers using the API. These providers can be managed in Thunderbird's Composition options:
The API allows modifying Thunderbird's menus by adding or overriding menu items. The menu items can be added to different menus based on the provided context
type. Examples can be found in our .
Since Experiments directly interact with Thunderbird's core functions, it is necessary to get used to the source code of Thunderbird itself. We gathered the most useful resources on the page.
Thunderbird does contain a few useful features related to Experiments . Especially if you're writing an Experiment with complex interactions between the WebExtension and your Experiment, it may be helpful to read the documentation blocks within and possibly other modules in the same source code folder.
For reference, the parent implementations of all built-in APIs can be found in .
Experiment APIs have full access to Thunderbird's core functions and can bypass the WebExtension permission system entirely. Including one or more Experiment APIs will therefore disable the individual permission prompt and instead prompt the user only for the permission.
Once you have a draft for your API, you can start to build the Experiment. Experiments consist of three parts, which are registered through :
A describes the API that can be accessed by the WebExtension part of the add-on.
Either parent or child implementation may be omitted. Full examples for and are available in the Firefox source documentation.
Check out the , which creates all the needed files.
In general, you can always pass simple data structures as function parameters and return values of an API. Thunderbird will automatically adapt them using the , so you do not need to worry about them.
Accessing data that belongs to the WebExtension from an Experiment: the code in the Experiment gains , permitting it to directly access the chrome implementation of the given object. Usually, you don't need to worry about that and things work out just fine – but if they don't, you can opt-out via Components.utils.waiveXrays()
.
The first option usually boils down to invoking or a related function with context.cloneScope
as target scope. A notable exception is returning data from async API functions or wrapping Promises via , which causes automatic cloning of the result (unless the result is wrapped into a ExtensionCommon.NoCloneSpreadArgs
).
If your Experiment API is so complex that it does not reasonably fit into a single source file, you can load your own system modules with some additional boilerplate: you need to define a custom global URL. The Experiment can be used to define custom global URLs. In this example, we are registering a custom resource://
URL with the namespace myaddon
:
In addition to , Thunderbird supports the following special types of content scripts:
We will be using a message display script in this example. In order to register one, we use the API and add the following code to our background script:
What is special however is how the displayed information is retrieved. In the second part of this tutorial, we used the tabs
API and the messageDisplay
API from our background page, to learn which message is currently displayed and then used the messages
API to get the required information. This does not work for content scripts, as . Instead, we have to request this information from the background script using runtime messaging.
The method of the API will send a message to each active page, including the background page, the options page, popup pages and other HTML pages of our extension loaded using or . The message itself can be a string, an integer, a boolean, an array or an object. It must abide to the .
The message
passed to the onMessage
listener will be whatever has been sent using sendMessage().
The sender
is of type and will include the sending tab.
Let's double-check that we made the and have all the files in the right places:
As described in the , go to the Add-ons Manager to open the Debug Add-on Page and temporarily install the extension.
action
A browser action is a button that your extension can add to Thunderbird's main mailTab toolbar. The button has an icon, and may optionally have a popup whose content is specified using HTML, CSS, and JavaScript.
Note: This key has been renamed from browser_action
to action
in Manifest V3.
Deprecated. Use
browser_specific_settings
instead.
Defines the extension's author. If the developer
key is supplied and it contains the name
property, it will override the author
key. There is no way to specify multiple authors. This is a localizable property.
Use the background
key to include one or more background scripts, and optionally a background page in your extension. Background scripts are loaded as soon as the extension is loaded and stay loaded until the extension is disabled or uninstalled.
A browser action is a button that your extension can add to Thunderbird's main mailTab toolbar. The button has an icon, and may optionally have a popup whose content is specified using HTML, CSS, and JavaScript.
Note: This key has been renamed from browser_action
to action
in Manifest V3.
Defines properties that are specific to a particular host application. Information for Thunderbird are stored in:
browser_specific_settings.gecko
More details can be found in our hello world example.
Defines a file link provider, which can be used to upload large attachments to a server, instead of attaching them directly to the email.
Use the commands API to add keyboard shortcuts that trigger actions in your extension, for example, an action to open a browser action popup.
Instructs Thunderbird to load content scripts into web page tabs and windows, whose URL matches a given pattern.
The default policy restricts the sources from which a content script can load <script>
and <object>
resources, and disallows potentially unsafe practices such as the use of eval()
. See Default content security policy to learn more about the implications of this.
A compose action is a button that your extension can add to the toolbar of Thunderbird's message compose tabs. The button has an icon, and may optionally have a popup whose content is specified using HTML, CSS, and JavaScript.
This key must be present if the extension contains the _locales
directory, and must be absent otherwise. It identifies a subdirectory of _locales
, and this subdirectory will be used to find the default strings for your extension. See Internationalization.
Defines a short description of the extension, intended for display in the Add-ons Manager. This is a localizable property.
Defines the name of the extension's developer and their homepage URL, intended for display in the add-on manager tab. The name
and url
properties, if present, will override the author
and homepage_url
keys, respectively. This is a localizable property.
The dictionaries
key specifies the locale_code
for which your extension supplies a dictionary.
URL for the extension's home page. If the developer
key is supplied and it contains the url
property, this will override the homepage_url
key.
This is a localizable property.
The icons
key specifies icons for your extension. Those icons will be used to represent the extension in components such as the Add-ons Manager.
This key specifies the manifest version used by this extension. Supported are 2
and 3
(since Thunderbird Beta 110).
A message display action is a button that your extension can add to the toolbar of Thunderbird's message display tabs. The button has an icon, and may optionally have a popup whose content is specified using HTML, CSS, and JavaScript.
Defines the name of the extension. This is used to identify the extension in the Add-on manager and on sites like addons.thunderbird.net.
Defines permissions, which should be requested dynamically (when needed) and not during install.
Defines an options page for your extension.
This key requests special powers for your extension. This key is an array of strings, and each string is a request for a permission.
This key registers one or more web-based protocol handlers. It allows to register a website or an extension page as a handler for a particular protocol. Note: The default click handler in Thunderbird web tabs is currently not working correctly with custom defined protocol handlers. It does work in WebExtension windows.
Short name for the extension. If given, this will be used in contexts where the name field is too long. It's recommended that the short name should not exceed 12 characters. If the short name field is not included in manifest.json, then name will be used instead and may be truncated.
This is a localizable property.
This key defines a static theme to be applied to Thunderbird.
This key enables the definition of experimental theme
key properties for the Thunderbird interface. These experiments are a precursor to proposing new theme features for inclusion in Thunderbird.
Instructs the browser to load a script packaged in the extension, known as the API script, this script is used to export a set of custom API methods for use in user scripts.
The version of the extension, formatted as numbers and ASCII characters separated by dots. For the details of the version format, see the Version format page.
This key enables an extension to make resources bundled with the extension (for example images, HTML, CSS or JavaScript) available to web pages.
POP3
✔
✔
IMAP 4r1
✔
✔
IMAP IDLE
✔
✔
SMTP
✔
✔
NNTP
✔
✔
RSS 0.9
✔
N/A
RSS 2.0
✔
N/A
Atom 0.3
✔
N/A
Atom 1.0
✔
N/A
List Management Headers
✔
✔
MIME
✔
✔
All 3 are supported for both Desktop and Android platforms.
OpenPGP
✔
✔
Kai Engert actively involved
S/MIME version?
✔
✖
oAuth 2.0
✔
✔
See also https://oauth.net/2/
vCard
✔
✖
Version 4 used by default
CardDAV
✔
N/A
LDAP
✔
N/A
See also https://ldap.com/
LDIF
✔
N/A
Supported for import and export
iCalendar
✔
✖
CalDAV
✔
N/A
CalDAV Scheduling
N/A
iTIP
✔
✖
IRC
✔
N/A
IRC TLS Port
✔
N/A
IRC CTCP
✔
N/A
IRC DCC
✔
N/A
IRC SUPPORT
✔
N/A
TODO: What about ISUPPORT 01 and ISUPPORT 02?
IRC NAMESX
✔
N/A
IRC MONITOR
✔
N/A
IRC WATCH
✔
N/A
IRCv3 Capability Negotiation
✔
N/A
See also the IRCv3 Support Table
IRCv3 SASL 3.2
✔
N/A
IRCv3 Message Tags
✔
N/A
IRCv3 Echo Message
✔
N/A
IRCv3 Multi-Prefix
✔
N/A
IRCv3 Server Time
✔
N/A
XMPP
✔
N/A
XEP-0030
✔
N/A
XEP-0045
✔
N/A
XEP-0054
✔
N/A
XEP-0059
✔
N/A
XEP-0078
✔
N/A
XEP-0085
✔
N/A
Typing notifications
XEP-0092
✔
N/A
XEP-0199
✔
N/A
XEP-0203
✔
N/A
XEP-0245
✔
N/A
XEP-0249
✔
N/A
XEP-0280
✔
N/A
XEP-xxxx MUC Avatars
✔
N/A
Matrix
✔
N/A
Thunderbird is an Ecosystem Member of the Matrix Foundation
UI only partially supports the full body of the specificaiton, not tracking on an MSC basis at this time
Olm/Megolm
✔
N/A
OTR
✔
N/A
Required steps to update add-ons for Thunderbird Nebula 128.
This section covers the required update steps for add-ons which are already compatible with Thunderbird 115 and need to be made compatible with Thunderbird 128.
Our WebExtension APIs are meant to be stable, but we are adding new features and since this is a very young technology this might also require backward incompatible changes. All WebExtension API changes are listed in our WebExtension API documentation:
The known backward incompatible changes are:
Introduction of the messagesUpdate permission, required to use messages.update().
Thunderbird 128 is the first release to officially support Manifest Version 3. The required changes to convert a Manifest V2 extension to Manifest V3 are listed in our WebExtension API documentation:
Thunderbird WebExtensions can still run legacy code inside Experiments. Such legacy code has to be adjusted to changes made in Thunderbird Core. All known changes are listed below.
If you have encountered a change which is not yet listed there, please contact us, so we can update the list.
The Thunderbird team has finished the conversion of all its JSM files and now only uses ES6 modules instead. There is a compatibility layer, which still maps requests for the old *.jsm
files to the new *.sys.mjs
files. It is however recommended to use the new files now already. The new files are either loaded via ChromeUtils.importESModule()
:
or via ChromeUtils.defineESModuleGetters()
:
Changed in Thunderbird 126 (Bug 1887047).
OnProgress()
-> onProgress()
OnStartCopy()
-> onStartCopy()
OnStopCopy()
-> onStopCopy()
GetMessageId()
-> getMessageId()
SetMessageKey()
-> setMessageKey()
This file has been renamed to ThreadPaneColumns.mjs
in Thunderbird 128 (Bug 1890731).
Changed in Thunderbird 125 (Bug 1878401). The type of the first parameter was changed from DOMWindow
to BrowsingContext
. It will fail when a DOMWindow
is passed in. Every DOMWindow
has a browsingContext
getter:
window.browsingContext
The getFile()
method has been removed in Thunderbird 116 (Bug 1747467). Use FileUtils.File()
instead. Example:
can be replaced by
The FindAccountForServer()
method has been renamed to findAccountForServer()
in Thunderbird 121 (Bug 1865068).
The implementation of this method has become async
in Thunderbird 123. The NotificationBox Experiment has been adjusted accordingly.
The file Services.jsm
and its compatibility layer (added in Thunderbird 115) have been removed, and loading it will now cause an error in Thunderbird 128. It is safe to simply remove all code which was used to load the module in Thunderbird 115 and later.
The addLogin()
method has been replaced by the async method Services.logins.addLoginAsync()
.
This method has been a thin wrapper for ChromeUtils.defineLazyGetter()
since Thunderbird 112 (Bug 1805288). Its usage has been purged from core code and it may stop working anytime.
Required steps to update add-ons for Thunderbird Supernova 115.
This section covers the required update steps for add-ons which are already compatible with Thunderbird 102 and need to be made compatible with Thunderbird 115.
Our WebExtension APIs are meant to be stable, but we are adding new features and since this is a very young technology this might also require backward incompatible changes. All WebExtension API changes are listed in our WebExtension API documentation:
Thunderbird WebExtensions can still run legacy code inside Experiments. Such legacy code has to be adjusted to changes made in Thunderbird Core. All known changes are listed in the following document:
If you have encountered a change which is not yet listed there, please contact us, so we can update the list.
If you are using any of the shared Experiments, you probably do not have to update them on your own. Check if an updated version is already available:
Required steps to update add-ons for Thunderbird 102.
This section covers the required update steps for add-ons which are already compatible with Thunderbird 91 and need to be made compatible with Thunderbird 102.
Our WebExtension APIs are meant to be stable, but we are adding new features and since this is a very young technology this might also require backward incompatible changes. All WebExtension API changes are listed in our API documentation:
Thunderbird WebExtensions can still run legacy code inside Experiments. Such legacy code has to be adjusted to changes made in Thunderbird Core. All known changes are listed in the following document:
If you have encountered a change which is not yet listed there, please contact us, so we can update the list.
If you are using any of the shared Experiments, you probably do not have to update them on your own. Check if an updated version is already available:
This document tries to cover the internal changes that may be needed to make Experiment add-ons compatible with Thunderbird Supernova. If you find changes which are not yet listed on this page, you can ask for help and advice in one of our communication channels.
Each mail3PaneTab
and mailMessageTab
in Thunderbird's main messenger window is now loaded into its own browser element, instead of sharing and updating a single browser.
A general and very helpful introduction to the new front end can be found in its official documentation.
The mail3PaneWindow
(about:3pane
) can now be accessed as follows:
window.gTabmail.currentAbout3Pane
window.gTabmail.currentTabInfo.chromeBrowser.contentWindow
window.gTabmail.tabInfo[0].chromeBrowser.contentWindow
window.gTabmail.tabInfo.find(
t => t.mode.name == "mail3PaneTab"
).chromeBrowser.contentWindow
Options #1 & #2 will only work if the current active tab is a mail3PaneTab
. Option #3 assumes the first tab is always a mail3PaneTab
.
The mailMessageWindow
(about:message
) can be accessed similarly:
window.gTabmail.currentAboutMessage
window.gTabmail.currentTabInfo.chromeBrowser.contentWindow
mail3PaneWindow.messageBrowser.contentWindow
window.messageBrowser.contentWindow
Option #1 will only work if the current tab is a mail3PaneTab
or a mailMessageTab
. Option #2 will return the mailMessageWindow
, if the current tab is a mailMessageTab
(it will return the mail3PaneWindow
, if the current tab is a mail3PaneTab
). Option #3 will return the nested message browser of a mail3PaneTab
through its mail3PaneWindow
as defined in the previous section. Option #4 will return the message browser of a message window.
Some of the global objects defined in Thunderbird's main messenger window have been moved into the mail3PaneWindow
(about:3pane
) and/or the mailMessageWindow
(about:message
). Some objects have been removed.
gDBView
: Available in mail3PaneWindow
and in mailMessageWindow
.
gFolderDisplay
: Removed. Find displayed folder via mail3PaneWindow.gFolder
.
gMessageDisplay
: Removed. Find displayed message via mailMessageWindow.gMessage
or mailMessageWindow.gMessageURI
.
Useful functions, methods and objects which have been moved from elsewhere:
mail3PaneWindow.displayFolder(folderURI)
mail3PaneWindow.messagePane.displayMessage(messageURI)
mail3PaneWindow.messagePane.displayMessages(messageURIs)
mail3PaneWindow.messagePane.displayWebPage(url)
mail3PaneWindow.folderPane.*
mail3PaneWindow.folderTree.*
mail3PaneWindow.threadPane.*
mail3PaneWindow.threadTree.*
mailMessageWindow.currentHeaderData
mailMessageWindow.currentAttachments
This topicbox post holds instructions on how to find other moved objects and functions.
It is recommended to leverage WebExtension APIs as much a possible. Instead of adjusting to core changes, the following WebExtension APIs can be helpful:
Use the browserAction API to add buttons to Thunderbird's unified toolbar
Use the menus API to add entries to Thunderbird's context menu (for example in the folder pane or in the thread pane)
Use the commands API to register keyboard shortcuts. Additional benefit: all WebExtension shortcuts can be adjusted by the user in the Add-on Manager according to their needs.
Use the mailTabs API to interact with the mail tab.
There may already be a shared Experiment, which could help with add-on updates (or which could give helpful hints):
community Experiments listed on DTN
Experiments from the webext-support repository
Experiments from the webext-experiments repository (scheduled to be shipped with Thunderbird soon)
The mail toolbar has been replaced by the unified toolbar. Adding your own buttons will become difficult, because the unified toolbar tends to remove unknown objects. Instead, use the browserAction API to add buttons.
The folderTree
and threadTree
in about:3pane
(the mail3PaneTab
) no longer use the deprecated XUL tree
elements, but have been replaced by HTML lists
and HTML tables
.
Mozilla continued to remove XUL in favour of standard HTML5/CSS. The most relevant changes are related to the XUL flexbox. A very helpful read is this blogpost from the responsible developer.
Known attributes which have to be replaced:
height
: replace with CSS height property
width
: replace with CSS width property
flex
: replace with CSS flex (a very helpful tutorial is available at css-tricks.com)
Important: If add-ons still create old-fashioned XUL dialogues and load *.xhtml
files, it is not recommended to invest time into fixing them. It is more efficient to re-create them as pure *.html
files. They can be opened using the tabs API or the windows API.
Removed in Thunderbird 103. The service object is now globally available in API implementation scripts. If needed in self-created JSMs, it can be accessed as follows:
For a backward compatible solution, use
Removed in Thunderbird 115. Can be replaced as follows:
OS.Constants.Sys.Name
-> Services.appinfo.OS
OS.Constants.Path.profileDir
-> PathUtils.profileDir
OS.Constants.Path.tmpDir
-> PathUtils.tempDir
OS.File.*
-> IOUtils.*
OS.Path.*
-> PathUtils.*
In Thunderbird 106, this has been changed from an enumerator to a simple array.
Removed in Thunderbird 106.
Renamed in Thunderbird 108 to containsKey()
. Example msgHdr.folder.msgDatabase.containsKey()
.
Content processes, such as the extension process executing "child" experiment code, are subject to additional restrictions compared to Thunderbird 102. Known limitations include:
Raw TCP socket operations never complete, even if timeouts are set up. It is likely that other networking primitives are affected in a similar way.
In consequence, you might need to move some functionality from "child" experiment code to the "parent" context. One way to achieve this is to implement the necessary functionality as a regular asynchronous API method in the "parent" experiment (without extending the API schema), then using await context.childManager.callParentAsyncFunction("your_api_name.some_function", [arguments, passed, to, some_function])
to call it from the child experiment. Note that function arguments passed in between processes are subject to the structured clone algorithm.
The CSS feature -moz-image-region
has been removed. It is no longer possible to specify a button icon or a list-style-image
as a region from a larger image. Add-on developers have to resort to individual images.
all
Add menu entries to all supported contexts, excluding tab
and tools_menu
.
all_message_attachments
action
,
browser_action
, compose_action
, message_display_action
Note: browser_action
is available in Manifest V2 and action
is available in
Manifest V3.
action_menu
,
browser_action_menu
, compose_action_menu
, message_display_action_menu
,
Note: browser_action_menu
is available in Manifest V2 and action_menu
is available in Manifest V3.
Add entries to the drop-down menu of menu typed action buttons.
compose_attachments
editable
,
password
Add entries to the context menu of text/password input fields in WebExtension windows, web tabs or message display tabs.
folder_pane
image
,
audio
,
video
link
Add entries to the context menu of links in WebExtension windows, in web tabs or in message display tabs.
message_attachments
message_list
page
,
frame
Add entries to the context menu of WebExtension windows, web tabs or message display tabs, if none of the other content contexts apply (link, selection, image, audio, video, editable, password).
selection
Add entries to the context menu in WebExtension windows, web tabs or message display tabs, if any text has been selected.
tab
The tab
context can also be used to override the context menu of content pages (action popups or content tabs).
See the menu example for more details.
tools_menu
A collection of the most important developer resources outside of developer.thunderbird.net.
A few helpful resources relevant for developing add-ons for Thunderbird.
The official Thunderbird add-on repository.
A list of all supported values, which can be used as strict_min_version
and strict_max_version
values in add-on manifest files.
A guide from the extension workshop with the most recent information on debugging add-ons. It is written for Mozilla Firefox but applies for Thunderbird as well.
A guide how to use Visual Studio Code to debug Thunderbird extension.
WebExtension API Documentation for Thunderbird
Documentation of all WebExtension APIs supported by Thunderbird.
A collection of explanatory WebExtension examples for Thunderbird.
A collection of WebExtension examples for Firefox and other browsers. They probably won’t work directly in Thunderbird, but they may provide hints on how to use some of the WebExtension APIs that Thunderbird inherited from Firefox.
The WebExtension support repository provides additional tools, scripts, custom elements, Experiment APIs and other resources, to simplify the development of WebExtensions for Thunderbird.
Central bug tracking for Mozilla projects. Some useful pages related to Thunderbird:
The main documentation for Mozilla developers. As Thunderbird is based on the Mozilla platform, some Mozilla documentation is valid for Thunderbird as well. Useful MDN pages on WebExtension are:
Useful resources for converting legacy extensions or for creating Experiments.
The current Firefox code documentation, which might be needed when converting legacy extensions.
The archived Mozilla documentation includes information about internal components and functions used by legacy extensions. Please be aware, that those pages are not maintained and are potentially outdated. Some useful direct links:
All the information you need to get your first Thunderbird extension up and running.
In the first part of this tutorial, we will create a very simple extension, which adds a "Hello World" button to Thunderbird's main toolbar and a click on it will show a Hello, World!
popup.
First, we create an empty hello-world
project folder for our extension and navigate to it.
Extensions require a manifest.json
file that tells Thunderbird a few basic information about the add-on. Let's place the following manifest.json
file directly in the hello-world
project folder.
You can grab the icons we use for this example from the example repository. Make sure to create an images
directory in the hello-world
project folder for them.
browser_action
The above manifest includes the definition for a browser_action
. That is the button we want to add to Thunderbird's main toolbar. The reference to a browser in its name is inherited from the Firefox Browser.
The allowed keys for the browser_action
button are described in our API documentation. We define a popup HTML page, which should be loaded if the button is clicked, a title and an icon.
The location of the HTML file loaded by the popup of our browser_action
is defined in the browser_action.default_popup
key. Let's create a mainPopup
directory in the hello-world
project folder for everything related to that popup and start with the following popup.html
.
The default content security policy disallows JavaScript placed directly in <script>
tags and inline event handlers like onclick
. Place all JavaScript code into a separate file (like popup.js in this example) and use addEventListener() instead of inline event handlers.
The script
tag to include popup.js
is setting type="module"
, which loads the script as a top level ES6 module. This enables the script to use the await keyword in file scope code and to load other ES6 modules.
We will not use these features in this step of the tutorial, but we still use the modern ES6 module approach here to introduce it as a standard programming practice.
We're going to create the following file called popup.js
and place it in the same folder as the popup.html
file.
What our little script does is sending a message to the console each time we click on our "Hello World" toolbar button. We'll take a look at that in a moment when we try out our add-on. The first line is just a comment, so we can remember what our code is doing.
Now we want to create the CSS file referenced in our HTML file. We'll call it popup.css
. This is just for decoration of the page, we'll put it in the same folder as the popup.html
file.
First, let's double-check that we created the correct files and have them in the right places:
To install the add-on we created, we are going to load it temporarily. Let's start by opening the Add-ons Manager:
This will open up the Add-ons Manager, make sure "Extensions" is selected on the left-hand side and click the gear to select "Debug Add-ons".
Click on the "Load Temporary Add-on..." button:
Select the manifest.json
file from within our hello-world
project folder:
This should install the add-on for this session only:
Our extension will print messages to the error console using console.log()
, so we need to open the error console first, in order to see those log entries. Hit the "Inspect" button under the add-on's listing (pictured above), this will open the Developer Tools window.
Make sure the "Console" tab is selected in the Developer Tools. Click the "Persist Logs" checkbox in the top right-hand corner of the Developer Tools window so that we can see the output from the add-on after we've interacted with it (otherwise, it only shows output as it is happening).
Now we can give our new add-on a whirl. Head to the home tab and find the new "Hello World" button in the main toolbar in the top right-hand corner. Click on it to see a popup with the Hello, World!
message.
Now, if you look at the Developer Tools window, you should see something like the following in the console:
Once the add-on is ready for release or if you want to share it with others, you need to create a single add-on file. Simply zip the content of the add-on's project folder and use the xpi
file extension. That file can be installed from the gear menu in the Thunderbird Add-ons Manager.
Extending the simple example extension to make use of WebExtension APIs.
In the second part of the Hello World Extension Tutorial, we will add a "Details" button to the message header toolbar. A click on it will show some information about the currently displayed message, which we retrieve using Thunderbird's WebExtension APIs.
message_display_action
Similar to adding the browser_action
in the first part of the Hello World Extension Tutorial, we have to extend the manifest.json
to add the message_display_action
.:
The HTML file for our popup needs some place-holders, which we can later fill using JavaScript and Thunderbird's WebExtension APIs. We create a messagePopup
folder inside the hello-world
project folder and place the following popup.html
file in the newly created folder:
We place the following popup.css
file in the same folder as the popup.html
file.
Instead of tables, we use modern CSS styling to format our HTML into a tabular view. The display: grid
container defines how our 6 DIV elements inside the container DIV are aligned. Check the grid documentation or this grid guide for more details.
All WebExtension API functions return a Promise instead of an actual value. This aims to simplify the handling of asynchronous functions.
The author of this example prefers the async
/await
syntax over the .then()
approach for handling Promises. This allows us to avoid the so called callback-hell of asynchronous functions and instead keep writing sequential code by simply awaiting all the returned Promises.
The popup.js
file was loaded as a top level ES6 module by specifying type="module"
in its script
tag. This allows us to use the await
keyword directly in file scope code. Otherwise we would need to use an asynchronous wrapper function:
async function load() {
let tabs = await messenger.tabs.query({
active: true,
currentWindow: true,
});
...
}
load();
In Thunderbird, all WebExtension API can be accessed through the browser.*
namespace, as with Firefox, but also through the messenger.*
namespace, which is a better fit for Thunderbird.
The tabs API provides access to Thunderbird's tabs. We need to get hold of the current active tab to learn which message is displayed there. We use the query method to find it in line 3
.
Using messenger.tabs.getCurrent()
will not work, as that always returns the tab in which it is being called from. In our case, the call is executed from inside the popup of the message_display_action
and not from inside the tab we are looking for.
The getDisplayedMessage method of the messageDisplay API provides access to the currently viewed message in a given tab. It returns a Promise for a MessageHeader object from the messages API with basic information about the message in line 9
.
At this stage we are interested in the subject (line 12
) and the author (line 13
).
The getDisplayMessage method requires the messagesRead
permission, which needs to be added to the permissions
key of our manifest.json
file.
We also want to get the received
header from the message. That information is not part of the general MessageHeader
object, so we have to request the full message.
The getFull method in line 16
returns a Promise for a MessagePart object, which relates to messages containing multiple MIME parts. The headers
member of the part returned by getFull
includes the headers of the message (excluding headers which are part of nested MIME parts available through the parts
member).
Let's double-check that we made the correct changes and have all the files in the right places:
This is how our manifest.json
should now look like:
As described in the first part of the Hello World Extension Tutorial, go to the Add-ons Manager to open the Debug Add-on Page and temporarily install the extension.
Open any message and you will find a "Details" button in the message header toolbar. A click on it will show you the subject
, the author
and the first found received
header of the currently viewed message.
Tips and tricks for successful Thunderbird add-on development.
Some general tips to speed up your development workflow:
To debug code running in the browser context (e.g.: your Experiment APIs) you must use the global browser console (Ctrl+Shift+J) or developer toolbox (Ctrl+Shift+I).
To debug code running in a content page of your extension (e.g.: your background script), you need to select "debug add-ons" from the gear icon in the add-on tab and then inspect your add-on. Hint: Alternatively, you can enable content messages in the global browser console or in the developer toolbox as well. This will also show console output from popus (e.g. from browser_action):
The add-on debugging tools accessible through the add-on page's gear icon permit to directly install add-ons without packaging them. Using that option permits to reload the add-on without restarting Thunderbird.
There is also a debug guide from the extension workshop with more details and the most recent information on debugging. It is written for Mozilla Firefox but applies for Thunderbird as well.
One of the stumbling blocks that legacy add-on developers frequently encounter is Thunderbird's caching mechanisms, which cache JavaScript files for performance reasons so that they don't have to be re-read from their source and parsed or compiled repeatedly. If you're developing an Experiment, you may find that Thunderbird insists on continuing to use old versions even after you've changed them and restarted.
-purgecaches
command-line optionDMO claims that specifying the -purgecaches
command line option when launching Thunderbird will force it to purge the JavaScript cache.
Executing this JavaScript code inside Thunderbird will cause the XUL cache to be invalidated the next time it restarts:
This is described in more detail in the section Introducing Experiments.
How to create themes for Thunderbird.
A theme is a Thunderbird add-on that allows to change the appearance of Thunderbird. This document covers the following topics:
Static themes, like the name implies - are static and do not change. They have a set of colors or images that make up the theme, and this does not change. It typically consists of two files, zipped up with an .xpi extension just as any other add-on:
manifest.json
image.png or .jpg
manifest.json
fileYou must prepare a JSON manifest, named manifest.json
just as with other add-ons. Below is a basic example:
The following manifest keys define basic properties:
manifest_version
: A mandatory key defining the Manifest version used by the theme. Supported versions are 2
and 3
(since Thunderbird Beta 110). The Manifest defines the basic rules how a theme needs to be crafted and how it can interact with Thunderbird.
name
: A mandatory key to set the name of the theme.
version
: A mandatory key to define a number that denotes the version of the theme.
description
: A brief description of the theme.
author
: The name of a person or company representing the developer.
The browser_specific_settings.gecko
manifest key defines the following properties:
strict_min_version
: Defines the lowest targeted version of Thunderbird.
strict_max_version
: Defines the highest targeted version of Thunderbird. It can be set to a specific version or a broader match to limit it to a branch (for example 102.*
). Usually not needed.
id
: The id serves as a unique identifier for the theme and is mandatory in order to upload a theme to ATN or to be able to install it from an XPI file.
Although not required by Firefox, Thunderbird requires anid.
Thunderbird does not sign add-ons, and themes will not install without it.
Best practice is to use an "email-address-style" id (but not a real email address) on a domain you control, for example name-of-your-addon@example.com
, if you own example.com
. As the id of your theme cannot be changed once it is published, it is highly recommended to use a domain that you plan to keep for the forseeable future. If you don't have a domain to use, feel free to use:
<atn-user-name>.<add-on-name>@addons.thunderbird.net
\
Alternatively, you may use an UUID enclosed in curly braces, for example:
{e4aa2097-8ee9-49a4-9ec7-c633b1e8dfda}
Although the above theme will work as-is, there are other properties which can be added. All currently supported properties are listed in the ThemeType definition in our WebExtension API documentation.
You can add an icon for your theme, like other types of add-ons, with the following code:
Don't forget to put the icon.png
file in your add-on as well.
Here is a manifest.json
from a theme that uses all the above features, thanks to Paenglab:
Dynamic themes are actually normal extensions, that use the update() method of the theme API instead of a static theme manifest key. They can set the same theme properties like static themes, but they can change them dynamically. For instance the Night and Day theme is a dynamic theme that changes the theme colors based on the time of day.
The built-in theming properties do not modify the message-compose-windows and the message-display-tabs. These can be manipulated by injecting CSS files using the following WebExtension API methods:
To inject the file compose.css
into the message-compose-window, register it in your background script as follows:
A theme experiment allows modifying the user interface of Thunderbird beyond what is currently possible using the built-in color, image and property keys of the theme API. These experiments are a precursor to proposing new theme features for inclusion in Thunderbird.
Experimentation is done by exposing already existing internal CSS variables (e.g. --arrowpanel-dimmed
) to the theme API and by loading additional stylesheets to define new CSS variables, extending the theme-able areas of Thunderbird.
Use the browser toolbox to discover CSS selectors for Thunderbird UI elements or internal Thunderbird CSS variables.
Further information regarding theme experiments can be found in our WebExtension API documentation of the theme API.
Our example repository includes an add-on using a theme experiment to change the color of the chat icon.
Learn how to get in touch with the Thunderbird add-on developer community.
How to create extensions for Thunderbird.
An extension is a Thunderbird add-on, that provides additional functionality by adding new user interface elements, alter content, or perform background tasks.
manifest.json
fileThe main configuration file of an extension is called manifest.json
, also referred to as the manifest. Besides defining some of the extension's basic properties like name, description and ID, it also defines how the extension hooks into Thunderbird:
A list of all manifest keys supported by Thunderbird can be found in the following document:
The following manifest keys define basic properties:
manifest_version
: A mandatory key defining the Manifest version used by the extension. Supported versions are 2
and 3
(since Thunderbird 128). The Manifest defines the basic rules how a WebExtension needs to be crafted and how it can interact with Thunderbird.
name
: A mandatory key to set the name of the extension.
version
: A mandatory key to define a number that denotes the version of the extension.
description
: A brief description of what the extension does.
author
: The name of a person or company representing the extension developer.
The name
and the description
of the shown example are only in English. This MDN article about Localization explains how to use the WebExtension i18n API to localize these keys.
The browser_specific_settings.gecko
manifest key defines the following properties:
strict_min_version
: Defines the lowest targeted version of Thunderbird.
strict_max_version
: Defines the highest targeted version of Thunderbird. It can be set to a specific version or a broader match to limit it to a branch (for example 128.*
). Usually only needed if Experiments are included.
id
: The id serves as a unique identifier for the extension and is mandatory in order upload an extension to ATN or to be able to install it from an XPI file.
Best practice is to use an "email-address-style" id (but not a real email address) on a domain you control, for example name-of-your-addon@example.com
, if you own example.com
. As the id of your add-on cannot be changed once it is published, it is highly recommended to use a domain that you plan to keep for the forseeable future. If you don't have a domain to use, feel free to use:
<atn-user-name>.<add-on-name>@addons.thunderbird.net
\
Alternatively, you may use an UUID enclosed in curly braces, for example:
{e4aa2097-8ee9-49a4-9ec7-c633b1e8dfda}
The icons
manifest key tells Thunderbird the location of icons, which should be used to represent the MailExtension. Thunderbird supports basic image types like PNG files, but also SVG files. Thunderbird uses different file icon sizes in different places and allows registering a dedicated file for each size. The MailExtension will use the standard puzzle icon, if no icons have been defined.
The extension's background page is loaded in a hidden window when the add-on is started and can be used to load additional JavaScript files.
The background page specifies the JavaScript files to be loaded:
The type
attribute in the shown script
tag of background.js
defines it as a top level module and enables it to use modern ES6 modules.
Instead of defining a background page, the extension can specify a simple list of JavaScript files. This will auto-generate a background page and then load the JavaScript files.
The optional type
property in the shown background
definition loads all specified scripts as modules, allowing all of them to use ES6 modules.
The options_ui
manifest key defines the standard MailExtension options page. The defined page will be displayed in the add-on manager by default.
The appearance of the options page can be configured as follows:
open_in_tab
: Open the options page in a tab instead of inline in the add-on manager.
browser_style
: Use default browser styles for the options page (recommended).
An inline options page may look as follows:
Some UI elements Thunderbird WebExtensions can use are controlled by manifest keys, for example
browser_action
(renamed to action
in Manifest v3)
compose_action
message_display_action
Further information about these UI elements can be found in the following document:
A core principle of the WebExtension technology is the use of permissions, so users can see which areas of Thunderbird an add-on wants to access. Add-on developers can predefine all requested permissions in the permissions
manifest key:
Information about required permissions can be found in the following document:
For most permissions, the user must either accept all of the requested permissions during add-on install, or abort the install. Some permissions however can be requested as optional permissions, which can be managed by the user during runtime.
All JavaScript files loaded by an extension have access to:
Standard JavaScript methods
Web API (if the browser compatibility chart lists Firefox, the API also works in Thunderbird)
WebExtension APIs (see restrictions for content scripts and cloudFile scripts below)
A list of all WebExtension APIs supported by Thunderbird can be found in the following document:
Content scripts (including compose scripts and message display scripts) can only use the following WebExtension APIs:
storage.*
Content scripts can communicate with background scripts using runtime messaging, and thereby can indirectly access the WebExtension APIs. See our messageDisplayScript example extension.
A script loaded from a CloudFile management_url can only use the following WebExtension APIs:
CloudFile management scripts can communicate with background scripts using runtime messaging, and thereby can indirectly access the WebExtension APIs.
The currently available WebExtension APIs are not yet sufficient, as some areas of Thunderbird are not accessible through these APIs. We are working on improving the situation.
Currently and for the foreseeable future Thunderbird supports Experiment APIs (a.k.a. Experiments), which are WebExtension APIs that are bundled and shipped together with a MailExtension. They interact directly with Thunderbird's internal APIs and allow add-ons to use additional features not yet available via built-in WebExtension APIs.
These additional APIs can be registered in the manifest.json
file by defining an implementation script and a schema file describing the interface:
Experiment APIs have full access to Thunderbird's core functions and can bypass the WebExtension permission system entirely. Including one or more Experiment APIs will therefore disable the individual permission prompt and instead prompt the user only for the Have full, unrestricted access to Thunderbird, and your computer permission.
The use of optional permissions is not supported for the same reason.
If you'd like to learn more about experiments, check out this detailed introduction:
Developers can share and re-use Experiments, if their add-ons have similar needs. Before starting to work on your own Experiment, check if any of the following APIs could already provide the functionality you need. Using them and providing feedback to their developers will help to improve these APIs.
Draft for calendar-related APIs in Thunderbird.
Adds missing functionality to add headers to a newly composed message.
Note: Adding X-
headers is supported by the compose API since Thunderbird 102.
A generic UI extension framework based on iframes registered at fixed extension points. Note: Does not yet fully support Thunderbird Supernova
Load custom CSS files into Thunderbird windows.
Access Thunderbird system preferences.
Show notifications inside Thunderbird.
Permit WebExtensions to perform (time-limited) cleanup tasks after the add-on is disabled or uninstalled.
TCP support based on ArrayBuffers (currently client side only).
If you have created an Experiment API which you think could be beneficial to other developers, please tell us about it, so we can include it here.
Creating a good WebExtension API for Thunderbird is not an easy task. New APIs need to be generic and distinct from other APIs. Their interfaces have to be designed with foresight as we should avoid scenarios, where we have to make backward incompatible changes later because we have missed something.
If you want to propose and maybe collaborate on a new API, the following process is suggested:
Announcing the idea and a first outline of the suggested API on discuss.thunderbird.net. An actual implementation is not yet needed, but a general concept of how the API is supposed to work is helpful. This allows the add-on developer community to provide feedback and to make sure the design will cover their needs as well.
Creating a tracking bug on bugzilla, referencing the API description, so the core development team is notified and can comment as well.
Publishing a working implementation, so add-on developers can use it and provide feedback.
Adding a patch to the tracking bug and request review.
Descriptions of all WebExtension API supported by Thunderbird.
Thunderbird provides the following messenger related WebExtension APIs, which are sometimes referred to as MailExtension APIs.
accountsRead
Enables an extension to access information of accounts and identities configured in Thunderbird's account manager.
addressBooks
Enables an extension to access, modify, create and delete Thunderbird address books.
Permission info:
This API does not require a permission, but a browser_action
manifest key.
Enables an extension to register a cloudFile provider, which can be used to upload large attachments to a server, instead of attaching them directly to the email.
Permission info:
This API does not require a permission, but a cloudFile
manifest key.
The commands API adds keyboard shortcuts that can trigger actions in an extension. Permission info:
This API does not require a permission, but a commands
manifest key.
compose
Enables an extension to open a new message compose window or react to events while the message is being composed.
Permission info:
This API does not require a permission, but a compose_action
manifest key.
compose
addressBooks
Enables an extension to access, modify, create and delete contacts in Thunderbird address books.
accountsFolders
Enables an extension to access, modify, create and delete mail account folders.
addressBooks
Enables an extension to access, modify, create and delete mailing lists in Thunderbird address books.
accountsFolders
Enables an extension to interact with Thunderbird's main window. Permission info:
The accountsFolders
permission is only needed to set the currently displayed folder.
accountsRead, messagesRead,
activeTab
Enables an extension to add (context-) menu entries to Thunderbird menus. Permission info:
messagesRead
Enables an extension to react on and interact with the currently displayed messages.
This API does not require a permission, but a message_display_action
manifest key.
messagesModify
messagesRead,
messagesMove, accountsRead
Enables an extension to list, search, read, copy, move and delete messages.
Permission info:
The messagesRead
permission is needed to list, read, mark and tag messages.
The messagesMove
permission is needed to copy, move and delete messages.
The accountsRead
permission is needed by any function/event which involves folder information.
tabs, activeTab, compose, messageModify
Enables an extension to interact with Thunderbird's tab system. It allows to create, modify, and rearrange tabs and to communicate with scripts in tabs. Permission info:
The tabs
or activeTab
permission is only needed if the url
, favIconUrl
or title
information of tabs are accessed. The tab
permission will allow access to that information in all tabs, the activeTab
permission restricts that to the active tab.
Note: The activeTab
permission implies the <all_urls>
host permission for the active (web page -) tab.
The compose
permissions is needed to call tabs.executeScript()
and tabs.insertCSS()
for compose tabs.
The messageModify
permission is needed to call tabs.executeScript()
and tabs.insertCSS()
for a message display tab.
theme
The theme API can be used to create static or dynamic Thunderbird themes. Theme experiments are supported.
Permission info:
The theme
permission is needed to dynamically update the theme.
tabs
Enables an extension to interact with Thunderbird's windows which can contain webpage tabs and also other window types like composer or address books that cannot contain webpage tabs. You can use this API to create, modify, and rearrange windows.
Permission info:
You can find more information in the Thunderbird WebExtensions API documentation .
As Thunderbird is based on Firefox, many of its WebExtension APIs can be used in Thunderbird as well. The APIs listed in the following table are known to work with Thunderbird.
The following APIs link to their MDN description pages. Please be aware, that MDN is dedicated to web browsers (not limited to Firefox). Some information listed on MDN may not apply to Thunderbird and some API methods may not be supported. Each API page should include a compatibility chart and if that includes support for Firefox, it should work in Thunderbird as well.
browserSettings
clipboardWrite,
clipboardRead
Enables an extension to copy items to the system clipboard. Currently the API only supports copying images, but it's intended to support copying text and HTML in the future. Permission info:
Use this API to register content scripts to instruct the browser to insert the given content scripts into pages that match the URL patterns specified during registration. In Thunderbird, content scripts can only be used in web pages loaded into tabs. Permission info:
cookies
Enables an extension to get and set cookies, and be notified when they change. Permission info:
dns
Enables an extension to resolve domain names.
downloads,
downloads.open
Functions to internationalize an extension. It can be used to get localized strings from locale files packaged with an extension and to find out Thunderbird's current language.
identity
idle
Find out when the user's system is idle, locked, or active.
privacy
Access and modify various privacy-related settings.
management
Gets information about installed add-ons. Permission info:
The management
permission is only needed to access information of other add-on.
notifications
Display notifications to the user, using the underlying operating system's notification mechanism.
Enables extensions to request extra permissions at runtime, after they have been installed.
pkcs11
proxy
This module provides information about the extension and the environment it's running in. It also provides messaging APIs to:
communicate between different parts of the extension
communicate with other extensions
communicate with native applications
Usage info:
On macOS, Thunderbird looks for native messaging manifests in the per-user path ~/Library/Mozilla/NativeMessagingHosts/
rather than ~/Library/Application Support/Mozilla/NativeMessagingHosts/
On macOS, the global path for native messaging manifests is /Library/Application Support/Mozilla/NativeMessagingHosts/
(the same as Firefox)
Enables extensions to store and retrieve data, and listen for changes to stored items.
Use this API to register user scripts, third-party scripts designed to manipulate webpages or provide new features. Registering a user script instructs the browser to attach the script to pages that match the URL patterns specified during registration. In Thunderbird, user scripts can only be used in web pages loaded into tabs. This API offers similar capabilities to contentScripts but with features suited to handling third-party scripts.
webNavigation
Add event listeners for the various stages of a navigation. A navigation consists of a frame in the browser transitioning from one URL to another, usually (but not always) in response to a user action like clicking a link.
webRequest, webRequestBlocking
Add event listeners for the various stages of making an HTTP request, which includes websocket requests on ws://
and wss://
. The event listener receives detailed information about the request and can modify or cancel the request.
Permission info:
To use the blocking feature, the extension must also have the webRequestBlocking
API permission.
You can find more information about these APIs in the MDN WebExtension API documentation.
Required steps to update add-ons for Thunderbird 91.
Our WebExtension APIs are meant to be stable, but we are adding new features and since this is a very young technology this might also require backward incompatible changes. All WebExtension API changes are listed in our API documentation:
If you are using any of the shared Experiments, you probably do not have to update them on your own. Check if an updated version is already available:
Required steps to update add-ons for Thunderbird 78.
Support for legacy WebExtensions was removed from Thunderbird Beta version 74, released in February 2020. Only modern WebExtensions are now compatible with Thunderbird 78.
There are two types of changes which are required to make your legacy extensions compatible with Thunderbird 78:
The legacy WebExtension must be converted to a modern WebExtension
All extensions need to be updated to reflect changes in Thunderbird core, like renamed/replaced API calls, removed support for some XUL elements and much more.
Required steps to update add-ons for Thunderbird 68.
Technically, legacy bootstrap extensions and legacy overlay extensions need to be converted to modern WebExtensions, but by activating the legacy mode, their general structure does not need to be changed. Such extensions are called legacy WebExtensions.
There are two types of changes which are required to make your legacy extensions compatible with Thunderbird 68:
The legacy extension must be converted to a legacy WebExtension by replacing the old install.rdf
by a manifest.json
.
All legacy extensions need to be updated to reflect changes in Thunderbird core, like renamed/replaced API calls, removed support for some XUL elements (need to use HTML elements now) and much more.
Even though it is possible to have both install.rdf
and manifest.json
files in your extension, so you could release a version compatible with Thunderbird 60 and 68, it is not suggested for the following reasons:
The amount of changes is huge and some changes are incompatible with Thunderbird 60 so it will require extra steps to ensure the modified version still runs with Thunderbird 60.
You may actually break your add-on for Thunderbird 60 users by releasing a backward compatible version for Thunderbird 68 ("Do not fix something, that is not broken").
We think the time and resources needed to code and test backward compatible add-ons is not justified by the small amount of users running older versions of Thunderbird.
It is possible to maintain a legacy version and a WebExtension version of your add-on in parallel on ATN! You just need to use a higher major version number for the WebExtension version of your add-on and keep the old major version number when releasing a new legacy version. Basically releasing them in two different branches.
The changes are grouped by category and are listed in the order we became aware of them.
As of Thunderbird 74, the built-in overlay loader has been removed, which means XUL overlay files are no longer supported. The preferred way to interact with Thunderbird is through WebExtension and MailExtension APIs, which only support HTML/CSS.
Since XUL is still usable, this document includes information about deprecated and removed XUL elements.
Removed completely in TB71. Use
All former values of the type
parameter of the textbox
element are supported by html:input
as well. For proper styling include the following CSS file: chrome://messenger/skin/input-fields.css
The flex
parameter is no longer supported and should be removed. Attach the input-container
class to a surrounding hbox
to force the input field to behave like a former flex="1"
textbox
.
If the input fields context menu is not working, you need to include two JavaScript files by adding:
JavaScript methods that are using element.localName == "textbox"
, getElementsByTagName("textbox")
or similar need to be updated as well.
Visually compare the fields before and after the conversion to be sure the UI, sizing, and spacing doesn't change.
In TB78, the XUL element wizard
may no longer be a top level element, but must be encapsulated by a window
element which includes some fluent locales:
If you have referred to the wizard
element by document.documentElement.*
, this is now referring to the window
element. Use getElementById()
instead.
If you set the label of a wizardpage
element via JavaScript during wizard load, it will be ignored. You have to manually call _adjustWizardHeader
after the label has been set. :
With TB78, the method scrollToIndex(idx)
has been removed, replace it with:
With TB69, the default namespace for createElement has switched from XUL to HTML. So you can no longer create XUL elements with document.createElement()
(but HTML elements). To create XUL elements, use:
The first parameter used to be an array and the second one its length. This length parameter has been dropped, causing all subsequent arguments to shift by one. Furthermore, to create a default socket, you now have to pass an empty array as the first parameter, instead of null.
To stay backward compatible, check the argument count of "createTransport". In case it is 4 it is the new interface, in case it is 5 you got the old interface. Alternatively, you may also check if the version of Thunderbird is 69 or later.
Merged into nsICookie.
With TB70, window objects don't need to be and cannot be QI'ed to nsIDOMChromeWindow, nsIDOMWindow and nsIInterfaceRequestor. You can just remove its usage, this is backwards compatible to TB 68.
This does not apply to nsIXULWindow
, which for example is used in nsIWindowMediatorListener.onOpenWindow(xulWindow)
.
Equally, TreeColumns and TreeContentView don't need to be and cannot be QI'ed to nsITreeView.
Renamed. Use
Module file extension changed in TB78 from js
to jsm
, now available via
In TB78 the function AddRecipient()
in the contact sidebar has been renamed to awAddRecipientsArray()
.
In TB71 MailServices.headerParser.parseHeadersWithArray()
has been removed. Instead, use:
nsIEditorStyleSheets.addOverrideStyleSheet(uri)
-> windowUtils.loadSheetUsingURIString(uri, windowUtils.AGENT_SHEET)
\
nsIEditorStyleSheets.removeOverrideStyleSheet(uri)
-> windowUtils.removeSheet(uri, windowUtils.AGENT_SHEET)
\
nsIEditorStyleSheets.enableStyleSheet(uri, enable)
Either manually load or remove the stylesheet:
-> windowUtils.loadStyleSheetUsingURIString(uri, windowUtils.AGENT_SHEET)
-> windowUtils.removeSheet(uri, windowUtils.AGENT_SHEET)
In TB68 a nsIAbDirectory
could be searched by simply attaching a search query to the URI of the directory when calling getDirectory()
:
In TB78 getDirectory()
no longer accepts search queries and throws an error. Instead, use the search()
method, which uses an nsIAbDirSearchListener
. A simple promisified implementation could look like so:
MailServices.ab.getDirectory(uri).search(search, listener)
will not return anything, if search
is empty.
With TB70, these two attributes have been removed from XUL elements like textbox
, radio
, checkbox
and friends. This is no longer supported:
Most usage of nsIArray
and nsIMutableArray
has been replaced by standard JavaScript arrays and functions which returned a nsISimpleEnumerator
have been changed to return a JavaScript array as well. The following APIs have been updated (links to actual patches, showing how core handled the change):
nsIAbDirectory.getCardsFromProperty (returns array)
nsIAbManager.directories (returns array)\
nsIMsgCompFields.attachments (returns array)\
nsIMsgFilterCustomAction.getCustomActions (returns array)
nsISmtpService.servers (returns array)
\
The function fixIterator()
is no longer needed by any core code and was subsequently removed together with iteratorutils.jsm
. It was mostly used in the following way:
Since MailServices.accounts.accounts
or MailServices.accounts.allIdentities
return a simple array since Thunderbird 75, there is no need to pipe it through fixIterator()
anymore. If your add-on is multi-version compatible and still supports Thunderbird 68 this has to be dealt with separately.\
Renamed in Beta 80 to SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
. This is often used as the aSecurityFlags
argument in calls to Services.io.newChannelFromURI()
.
Renamed in Beta 80 to SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT
.
Changed in Beta 87. Needs a fifth parameter to specify a nsIDNSRecord
value, but can be null
if not needed:
Has been renamed to nsIMsgCopyService.copyFileMessage
.
Has been renamed to nsIMsgCopyService.copyMessages
.
Has been renamed to nsIMsgCopyService.copyFolder
and no longer accepts an Array of src folders, but a single src folder.
Has been renamed to nsIMsgCopyService.notifyCompletion
.
The file Log4moz.jsm
has been removed in Thunderbird 85. Instead, use console
:
console.debug()
console.log()
console.trace()
- same as console.log()
, but dumps a trace log additionally.
console.info()
console.warn()
console.error()
These basic log functions accept multiple parameters, which are all dumped to the console. For example:
Alternatively,console.createInstance()
allows to define a custom console logger, with a prefix
and a maxLogLevel
. The maxLogLevel
can be used to disable/enable logging in debug or production builds or set the level based on users choice.
The levels Info
, Trace
and Log
are actually identical.
Interface has been dropped in favor of an observer based approach. See
The grid element does not seem to work anymore. Alternative is to use <html:table>.
The ftvItem
object has been renamed to FtvItem
in Beta 86.
Has been replaced by gAttachmentBucket.itemCount
.
Has been replaced by gAttachmentBucket.selectedCount
.
No longer supports the gAlertListener
, gUserInitiated
and gOrigin
parameters when being opened (argument 1-3). Instead, the following parameters are are used now:
Thunderbird can now separate content into different processes, which can only communicate through limited channels. This change has advantages relative to security and performance, but restricts code from freely accessing data that belongs to a different process.
In Thunderbird 91, this is primarily affecting pages belonging to an Add-on, like the background page or frames injected with an experiment. Most other parts of Thunderbird did not (yet!) change as much.
In practical terms, this means that you may need to update your experiments:
As a consequence, if you load JavaScript modules in "child" experiment code, you will now get separate instances of the JSM: each process has its own instance, and there could be multiple child processes. If you keep using JSMs from "child" code, you may furthermore need to manually unload these separate instances on API shutdown (ExtensionAPI.onShutdown
), even if you use a catch-all unloading solution like CachingFix or the WindowListener API.
Some developers use the version string to determine which function to call in add-ons which try to be backward compatible. For example:
This fails for Thunderbird 100 and newer, because this is a string comparison and not an integer comparison. A function to get the integer values could look like so:
And then just use getThunderbirdVersion().major >= 91
to check the version.
In this specific case, one could also use feature detection itself:
The class headerName
used for the styling header entries has been renamed to message-header-label
. The classmessage-header-row
has been added, styling the entire row.
The class msgHeaderView-button
used to style toolbar buttons has been renamed to message-header-view-button
.
This stringbundle
element has been removed from messageWindow.xhtml
and messenger.xhtml
. To access the strings, you have to load the bundle directly:
The method to retrieve strings from the created bundle is slightly different, instead of getString()
use GetStringFromName()
.
getItem()
addItem()
adoptItem()
modifyItem()
deleteItem()
deleteOfflineItem()
getItemOfflineFlag()
The former methods to promisify these functions have been removed together with calAsyncUtils.jsm
. Additionally, the getItem()
method will return the item directly instead of an array with the item. Replace
by
The calIOperationListener
and calIOperation
interfaces are still used in various places but the general direction is to remove them eventually after 102 in favor of Promises and ReadableStreams. If you have code that uses them internally, please update where feasible.
Calendar providers need to change above mentioned functions to be asynchronous. Calling the listeners is no longer necessary. Instead, you should return the item from the addItem
/adoptItem
/modifyItem
functions and make sure to throw an error in case of failure.
For providers with offline support, you need to call listeners set by the cache layer using the _cachedAdoptItemCallback
and _cachedModifyItemCallback
properties on your provider class. This is an unfortunate hack needed to maintain the order the onAddItem
event is fired by calCachedCalendar
. These listeners need to be called just before returning.
Note: It is important to store the callbacks before executing any async work to avoid issues when the same operation is run in concurrently. See the example below.
Since Thunderbird 96, calICalendar.getItems()
returns a ReadableStream
. Replace
by
If you are implementing a provider you will need to adapt your code to return a ReadableStream
. For cached providers, ensure you are returning the result from the offline cache:
This is a new addition to the API that returns the results as an array instead of a ReadableStream
. The BaseClass
provider has a default implementation however providers not extending it should provide their own implementation. If you intend to use this method, please be careful about memory usage with large queries.
Since Thunderbird 96, this function returns a Promise. Replace
by
ChromeUtils.import()
Since Thunderbird 101, it is no longer possible to load JSMs via extension URLs, for example
This method has been removed in 102.3.0. Replace its former usage
by
NotificationBox.appendNotification()
with this
The interface itself has not changed much, but contact details are handled differently now. Instead of storing the individual contact details as key/value pairs, they are now stored as a vCard string in the _vCard
property. The interface has gained two new members:
supportsVCard
a boolean value indicating support for vCard (or lack thereof).
vCardProperties
is a VCardProperties
object if the card supports vCard, or null
The new AddrBookCard
object is a wrapper for nsIAbCard
to enable support for vCards. No longer use nsIAbCard
to create a new card:
Instead, create an AddrBookCard
:
To update a card, create a new AddrBookCard
from the updated vCard string and enforce the same UID:
Alternativly, modify the card's vCardProperties
:
After an AddrBookCard
has been created, its vCardProperties
object is populated on first access from the cards _vCard
string property. While saving the card, its _vCard
string property is re-generated from its vCardProperties
.
This effectivly means that all changes to the _vCard
string property after its vCardProperties
object has been used, are ignored.
Read more about the address book implementation in Thunderbirds core documentation:
Has been removed in Thunderbird 93. One of its use case was to compare locale strings. You can replace the following:
by
The method getURLSpecFromFile
has been replaced by getURLSpecFromActualFile
and getURLSpecFromDir
in Thunderbird 92. Use the variant which fits your file
object.
Its generic callback functions have been renamed from onItem*()
to onFolder*()
in Thunderbird 94. Where needed, dedicated onMessage*()
callback functions have been added:
OnItemAdded(in nsIMsgFolder aParentItem, in nsISupports aItem) -> onFolderAdded(in nsIMsgFolder parent, in nsIMsgFolder child) -> onMessageAdded(in nsIMsgFolder parent, in nsIMsgDBHdr msg)
OnItemRemoved(in nsIMsgFolder aParentItem, in nsISupports aItem) -> onFolderRemoved(in nsIMsgFolder parent, in nsIMsgFolder child) -> onMessageRemoved(in nsIMsgFolder parent, in nsIMsgDBHdr msg)
The following callback functions were renamed without changing their parameters:
OnItemEvent -> onFolderEvent
OnItemPropertyChanged -> onFolderPropertyChanged
OnItemIntPropertyChanged -> onFolderIntPropertyChanged
OnItemBoolPropertyChanged -> onFolderBoolPropertyChanged
OnItemUnicharPropertyChanged -> onFolderUnicharPropertyChanged
OnItemPropertyFlagChanged -> onFolderPropertyFlagChanged
Has been renamed to nsIMsgCompSendFormat.Auto
in Thunderbird 101.
The parameters of this listener have been changed in Thunderbird 102. The header messageId has been added:
A few members and methods have been removed without replacement:
isPrintSelectionRBEnabled
(see alternative described in the following section)
isCancelled
saveOnCancel
showPrintProgress
SetupSilentPrinting()
Has been replaced by nsIPrintDialogService.*
.
The function showPrintDialog()
has also been updated to accept a new parameter to indicate if the entire document or just the current selection should be printed (replacement for the removed nsIPrintSettings.isPrintSelectionRBEnabled
).
The parameters of this function have changed: It is now possible to specify, which msgHdr is to be displayed. To open the currently selected message, update your usage from
to
Since Thunderbird 97, the JSM no longer exports a set of functions but a single object. The file has also been renamed from folderUtils.jsm
to FolderUtils.jsm
. Replace this former usage:
by
or use the FolderUtils namespace to access its functions.
Modernized update guides and Experiment examples
With the experience of the last few years, we have revised our guides for converting legacy extensions to WebExtensions. The information is now more compact and the examples have also been made clearer.
Introducing the menu typed action button
Since Thunderbird 115, all our action buttons can be defined as menus:
See our example for more details.
Support for Manifest Version 3
Thunderbird 128 ESR is the first Thunderbird release to officially support Manifest V3. The required changes to convert a Manifest V2 extension to Manifest V3 are listed in our WebExtension API documentation.
Updated sample extensions to Manifest Version 3
We updated our sample extensions to be compatible with MV3. Each extension includes comments to highlight the required changes.
Add entries to the context menu of the summary area of message attachments.
If the message has only one attachment, then the message_attachments
context is used instead.
Add entries to the context menu of action buttons.
Add entries to the context menu of attachments in the compose window.
Limited to content areas.
Add entries to the context menu of the folder pane of mail tabs.
Add entries to the context menu of embedded images, audio or video players in WebExtension windows, web tabs or message display tabs. Limited to content areas.
Limited to content areas.
Add entries to the context menu of attachments of messages.
Add entries to the context menu of the message list area of a mail tab.
If the page is loaded inside a frame, the frame
context applies, otherwise the page
context.
Limited to content areas.
Limited to content areas.
Add entries to the context menu of tab ribbons.
Add entries to Thunderbird's tools menu.
Thunderbird Beta (, )
Thunderbird 128 (, )
Note: The documentation for Thunderbird 128 and newer are available for (MV3) and Manifest Version 2 (MV2).
An online viewer to search the Firefox code base () and the Thunderbird code base ().
The current Thunderbird code documentation, in addition to comments in . This is still work in progress.
A communication platform / mailing list for Thunderbird add-on developers and aspiring add-on developers to ask questions and share knowledge.
A matrix chat room for Thunderbird add-on developers. More information on the Mozilla matrix server and how to establish a connection apart from the web app, can be found .
A matrix chat room to get in touch with add-on reviewers.
An almost weekly meeting where add-on developers can discuss technical difficulties or general add-on related issues.
Thursday 21:00 - 22:30 CET (Europe/Berlin)
An API to access files in the users profile folder. Until Mozilla has made a final decision about including the , this API can be used as an interim solution.
Enables an extension to interact with a .
Note: This API has been renamed to action in .
Enables an extension to interact with a .
Functionally is the same as the API except that it works on the document of email messages during composition.
menus, ,
The menus.overrideContext
permission is needed to .
The accountsRead
and messagesRead
permissions are needed to populate the associated fields in the object.
The activeTab
permission is (currently) needed to get advanced information for the onShown
event (see )
Enables an extension to interact with a . Permission info:
Functionally is the same as the API except that it works on the document of email messages being displayed.
The tabs
permission is needed to populate the url
, favIconUrl
and title
information in the tabs
member of the object.
Enables an extension to modify certain global browser settings. Because these are global settings, it's possible for extensions to conflict. See the documentation for for details of how conflicts are handled.
The clipboardRead
permission is not used by this API but is listed here for completeness. To read from the clipboard, the has to be used and needs the clipboardRead
permission.
This API does not require a dedicated permission, but an appropriate for any patterns it passes to register()
.
In addition to the cookies
permission, this API also needs a for the sites whose cookies are to be accessed. See .
Enables extensions to interact with the browser's download manager. You can use this API to . Permission info:
The permission is needed to open the saved file with the systems default application.
Utilities related to an extension. Gets URLs to resources packages with an extension. Gets the object for some of the extension's pages. Get the values for various settings.
Use the identity API to get an authorization code or access token, which an extension can then use to access user data from a service that supports OAuth2 access (such as Google or Facebook).
Enables an extension to enumerate security modules and to make them accessible as sources of keys and certificates.
Enables an extension to proxy web requests. Use the event listener to intercept web requests, and return an object that describes whether and how to proxy them. Permission info:
In addition to the proxy
permission, this API also needs a for the URLs of intercepted requests.
storage,
In addition to the webRequest
permission, this API also needs the for the requested host.
Support for legacy extensions was removed from Thunderbird Beta version 74, released in February 2020. Since Thunderbird 78 only modern are supported. This section only covers the required update steps for add-ons which are already compatible with Thunderbird 78 and need to be made compatible with Thunderbird 91.
MailExtensions can still run legacy code inside . Such legacy code has to be adjusted to changes made in Thunderbird Core. All known changes are listed in the following document:
If you have encountered a change which is not yet listed there, please , so we can update the list.
This document tries to cover all the internal changes that may be needed to make add-ons compatible with Thunderbird 78. If you find changes which are not yet listed on this page, you can ask for help and advice in one of our .
However, Thunderbird itself is still using XUL for its UI, and it is still possible to interact with that XUL based UI through experimental APIs. More information can be found in the .
Some XUL elements have been converted to , which have an additional is="something"
attribute. If such a custom element is to be created with createXULElement()
, the is
parameter needs to be passend in via the second argument:
In TB78, the XUL element toolbar
with attribute customizable
has been re-implemented as a . It needs an additional is
attribute. The following example is taken from the :
Bug introduced a breaking change to the nsISocketTransportService
interface:
The 3rd parameter has been dropped, which was the length of the array passed in as the 2nd parameter. See the .
Since TB69 nsIPromptService.select()
/ Services.prompt.select()
has dropped the parameter which specifies the length of the array of the item list which the user can choose from. See the .
The in TB77 necessitates the following changes:
These two methods are leftovers from de-XBL effort and have not been working since TB68. In TB72 they have been removed completely and using them will now throw an error. They have to be replaced by , or similar methods.
The documents these methods have been used with have probably changed dramatically. Check out to learn about the current layouts.
Most of the XUL based preferences system has been long removed and had to be replaced by either using the or by manually reimplementing that functionality using thensIPrefService
.
The second approach is not affected by this change, as it has full control over the load and save process for all its preferences. The has been updated and now supports:
The nsIPrefService
is not available via WebExtension APIs. It is therefore advised to move away from storing addon preferences in a preference branch and instead use the local storage via the API. More information can be found in the .
This document tries to cover all the internal changes that may be needed to make Experiment add-ons compatible with Thunderbird 91. If you find changes which are not yet listed on this page, you can ask for help and advice in one of our .
(returns array)
(returns array)
(returns array)
(returns array)
(returns array)
(returns array)\
(has been and now only accepts a single src folder)
(expects an array for the second argument)
(expects an array for the first argument)
(expects an array for the first argument)
(returns array)
(expects an array for the first argument)
(expects an array for the first argument)
(expects an array for the first argument)
(expects an array for the first argument)
(returns array)\
(first parameter is an array)\
For example .\
Renamed in Beta 86 to nsIMsgCompose.sendMsg
. It also returns a Promise now. More details can be found .\
See for more details.
The Thunderbird-specific file was removed without a direct replacement. You need to alter your logic or copy code from the old JSM into your add-on. might be a good inspiration, as it contains the changes that were necessary for core code.
Has been replaced by .
Use instead.
Use the dedicated function or instead.
Has been replaced bygAttachmentBucket
. More information can be found .
If you create frames in Thunderbird's UI, these frames now need the appropriate attributes to load content in the right process. You can find an example of required changes in .
If you exchange raw objects between WebExtension scopes / "child" experiment code and Thunderbird / "parent" experiment code, you need to migrate to an indirect approach, usually based on explicit message passing. Depending on the complexity of your task, this can either happen through notifyTools, a custom experiment API or through custom experiment code directly using cross-process APIs (likely either or ).
This document tries to cover all the internal changes that may be needed to make Experiment add-ons compatible with Thunderbird 102. If you find changes which are not yet listed on this page, you can ask for help and advice in one of our .
The id of the editor element in the composer has been renamed from to .
The id of the additional header area in the message display window has been renamed from to . The element has also been converted from a table
to a div
.
Since Thunderbird 96, . This includes:
If your code is synchronous, you will have to rework it to make use of asynchronous functions. Feel free to reach out for further help on this through our .
It is now mandatory to register an internal URL, for example a resource://
URL. See our .
The parameters have in Thunderbird 94. Most former properties have moved into an object. Replace this
All former standard contact properties (now referred to as ) are migrated into the vCard string and can no longer be updated directly via card.setProperty()
. A limited set of banished properties can still be read from: DisplayName
, FirstName
, LastName
, PrimaryEmail
, SecondEmail
, and NickName
.
contains a number of utility functions for converting between the storage types:
The new parameter has been added at the first position to , which already return the messageId.
Instead of printToFile
, use with a value from .
You must switch from an RDF manifest (install.rdf
) to a JSON manifest (manifest.json
). Here is a basic example. This RDF manifest:
Becomes this JSON manifest:
Detailed information about the possible config options for manifest.json
can be found in the MDN documentation.
The legacy
key enables Thunderbird’s legacy WebExtension support. Setting the type
key to xul
engages the new XUL overlay loader. The overlay loader is a Thunderbird component that takes XUL code as written in an overlay extension and applies it to the UI. In Thunderbird 60, this was a part of the core UI library, but it was removed. We have built a new overlay loader to replace as much of the removed code as possible.
The URL for icons must no longer be full chrome URL as before, but a simple path relative to the root directory of the add-on.
The shown example also specifies an optional options
key to define the options page. The key open_in_tab
is optional and defaults to a value offalse
. If your old RDF manifest included an em:optionsType
of 3, you can set open_in_tab
to true
, to have your options opened again in a new tab instead of a new window.
This example is only in English. You probably want to use translated strings in your manifest. Read this MDN article about it. Unfortunately that means you now need two sets of translated strings, one (that you already have) for your extension and another for the manifest.
Examples of overlay extension converted like this are:
A lot of effort has been done to create the new overlay loader, but still things might not work as before. We are tracking this in bug 1476259.
Overlays in Thunderbird itself (except the calendar extensions) have been removed, so extensions can not overlay the removed Thunderbird overlays any more. For example, if your add-on overlaid mailWindowOverlay.xul
, that needs to be changed; in this example you most likely need to overlay messenger.xul
now.
Furthermore, the new overlay loader does not properly support dependencies between overlays in different add-ons. As a result, you should only reference elements from the original document you're overlaying, or other overlays in the same extension. Most notably, you need to switch to non-overlay methods when altering the calendar user interface in the main window, or your add-on will not load reliably.
The overlay
and style
lines in yourchrome.manifest
are now handled by the new overlay loader. You’ll see lines like this in the Error Console:
Those errors come from the old system, which no longer deals with such things. You might see the same line, but regarding interfaces
.
<script>
tags<script>
tags added to an overlay file are now run after the application of the entire overlay, regardless of their position in the overlay. This may cause unexpected behaviour if your script previously ran before elements were inserted. For elements with event handlers these event handlers may run when the element is added, and they may fail if they rely on content being set up by a script which now runs after the creation of the element.
You may be used to putting the contents of a script directly in a document. This currently still works, but it may break in the future. Inline scripts are strongly discouraged. Use a file instead.
You must switch from an RDF manifest (install.rdf
) to a JSON manifest (manifest.json
). Here is a basic example. This RDF manifest:
Becomes this JSON manifest:
Detailed information about the possible config options for manifest.json
can be found in the MDN documentation.
The legacy
key enables Thunderbird’s legacy WebExtenion support, for bootstrap extension you have to set the type
key to bootstrap
.
The URL for icons must no longer be full chrome URL as before, but a simple path relative to the root directory of the add-on.
The shown example also specifies an optional options
key to define the options page. The key open_in_tab
is optional and defaults to a value offalse
. If your old RDF manifest included an em:optionsType
of 3, you can set open_in_tab
to true
, to have your options opened again in a new tab instead of a new window.
This example is only in English. You probably want to use translated strings in your manifest. Read this MDN article about it. Unfortunately that means you now need two sets of translated strings, one (that you already have) for your extension and another for the manifest.
The changes made in Thunderbird for bootstrapped add-ons to use manifest.json
may have changed when your code runs relative to events or notifications you've been listening for.
Use the window mediator or window watcher services to be notified about opening and closing windows, rather than listening for notifications.
Wherever you access a window, always check if it has been completely loaded
(document.readyState == "complete"
), or otherwise, wait for the load event.
In the following example, loadIntoWindow
is waiting for the window to be fully loaded and eventually calls loadIntoWindowAfterWindowIsReady
to actually do something with it. There is no need to listen to any other load events outside of loadIntoWindow
. This example also checks the state of already open windows during startup (line 14).
So you basically have to rename your current loadIntoWindow
to loadIntoWindowAfterWindowIsReady
and add the new asynchronous loadIntoWindow
, to make sure to access the window only after it has been fully loaded.
This document tries to cover all the changes that may by needed to make add-ons compatible with Thunderbird 68. If you find stuff that is no longer working but is not yet on this list, check our communication channels.
The changes are grouped by category and are listed in the order we became aware of them.
A bunch of global variables available in some window scopes were removed. If you have used any of these, there are still available as the same names underneath Components.interfaces.
nsMsgFolderFlags
nsIMsgCompDeliverMode
nsIMsgCompSendFormat
nsIMsgCompConvertible
nsIMsgCompType
nsIMsgCompFormat
nsIAbPreferMailFormat
nsIPlaintextEditorMail
nsISupportsString
mozISpellCheckingEngine
XBL is on death row. Many XBL bindings have been replaced or simply no longer exist. The remainder are being removed. This may result in slight behavior changes for some UI components.
With this query, you can see all the bugs related to de-XBL-ing Thunderbird, and see how the removal of each binding is handled.
If you have your own XBL bindings, you can convert them to custom elements. Here are some notes on the process of converting an XBL binding into a custom element.
As part of the de-XBL effort, the usage of the nsIDOMDocumentXBL Interface has also been deprecated. This includes:
getAnonymousElementByAttribute()
getAnonymousNodes()
They have to be replaced by document.getElementById(), document.querySelectorAll() or similar methods. The documents these methods have been used with have probably changed dramatically. Check out searchfox.org to learn about the current layouts.
Some XUL elements (or some of their attributes) no longer exist and must be replaced by an HTML element or some other XUL element. It does not matter, if you use these elements in a XUL file (as in overlay extensions) or create them via JavaScript (as in bootstrapped extensions).
In order to use HTML elements in a XUL file, you must load the HTML namespace into your overlay or dialog:
The following list also includes deprecated elements, which could still be used but Thunderbird has already started to purge their usage. In that case you must update your overlay files which are overlaying such elements, otherwise your overlay will not be applied. Check SearchFox for the current state of the files you are overlaying.
The replacements listed here might work in subtly different ways. Check your functionality!
Removed. Use
Removed. Use
All <listbox>
related elements have been removed. Use instead. A <richlistbox>
does not support cells or columns, just one <richlistitem>
per row (which can contain multiple other elements like hbox
, vbox
, label
or image
elements)
Furthermore, a few dedicated listbox/richtlistbox
methods have been removed and can be replaced as follows:
listbox.appendItem(label, value)
:
listbox.insertItemAt(index, label, value)
:
listbox.removeItemAt(index)
:
Removed. Use
All preference related XUL elements have been removed. If you have something like this in your add-on:
it must be replaced by a dialog
as follows:
Note that the DOCTYPE
changed and the preference
attribute now contains the full ID of the preference. If you used more than one prefpane
you need to rework the UI into tabs.
Furthermore, note the included JavaScript file preferencesBindings.js at the bottom, which is mandatory to recreate the functionality of the preference
attribute. It is also mandatory, that you include a custom JavaScript file (aspreferences.js
in the above example) afterwards, which defines the types of the used preferences (which was formerly done inside the preferences
tag). The file can be as short as this:
Per default, all preferences will be saved instantly after they have been changed. If you want to postpone saving until the user clicks the OK button, add a type
attribute and a cancel button to the dialog:
If you are doing or plan to do any advanced stuff with the preferences in JavaScript, like validating user entered values and such, it is recommended to abandon the usage of the preference
attribute (and preferencesBindings.js
) and directly use the preferences service instead.
Both elements have been removed. To load and access your own string property files, include the following in your JavaScript:
Both elements are deprecated and its usage in Thunderbird is removed. They can be replaced by hbox
elements with an appropriate class
identifier:
You may actually use other elements besides hbox
as a replacement for the statusbarpanel
, like label
or image
.
The XUL element menulist
no longer supports the editable
attribute. However, editable menulists have been re-implemented as custom elements. To be able to use it, you need some extra files to be linked from your document:
An editable menulist can also be created via JavaScript:
Even though the menupopup
element is defined as a direct child of the menulist
element in the above example, it cannot be accessed by
anymore. You may assign its own ID to the menupop
element, or get it by
You may use the inspector of the developer tools to see the full DOM structure of the menulist
element.
These two do not display as before. You now need to include the following css file:
While the groupbox
tag continues to work, the caption
tag has been removed. Use the following now:
In the current Thunderbird 68 Beta, this still looks a bit wrong, but will be fixed in Beta 3. More details can be found in bug 1559964.
Removed. Currently there is no working replacement part of Thunderbird itself, but Lightning has its own datetimepicker
, which can be used.
Its value is a JavaScript Date
object:
To be able to use the datetimepicker
, the following CSS files need to be included:
The following JavaScript files also need to be included:
If you do not want to be dependent on Lightning being installed, you need to include the above files with your add-on.
Removed. Thunderbird now has fixed build in notification boxes, where notifications can be added. The following list shows how to access some of them:
Main window:
let notifyBox = specialTabs.msgNotificationBar;
Message composer window:
let notifyBox = gNotification.notificationbox;
Below the recipient list in the message composer window: let notifyBox = gMessageNotificationBar.msgNotificationBar;
Most calendar dialogs:
let notifyBox = gNotification.notificationbox;
Since you no longer "own" notification boxes, you should not clear them by calling
notifyBox.removeAllNotifications();
as that would remove notifications added by others. You can get a specific notification by calling
let notification = notifyBox.getNotificationWithValue(value);
and remove only that via
notificationbox.removeNotification(notification);
You can still add notification boxes wherever you want, if you do not want to use the build in notification boxes (or if there is none). More details can be found here.
Removed. Use:
The element mail-multi-emailHeaderField
has been renamed into mail-multi-emailheaderfield
(no camelCase anymore).
See e.g. https://searchfox.org/comm-esr68/source/mail/base/content/msgHdrView.inc.xul#282
Each treecol
can be either shown or hidden via the column picker. Up to TB68 the column picker closed after a column had been selected/toggled. By adding closemenu="none"
to a treecol
, the column picker stays open after the display state of associated treecol
has been toggled.
Previously, if you had a <dialog>
and you wanted to respond to the buttons being pressed, you’d have something like this:
The event handler would return true if the dialog should close, or false to prevent closing. This no longer works. Instead, add the event handlers in JavaScript:
To prevent closing of the dialog, call preventDefault()
. A return value is not needed.
This is valid for ondialogaccept
, ondialogextra1
, ondialogextra2
and ondialogcancel
.
The section about <dialog>
events also applies to all onwizard…
events on <wizard>
, and onpage…
events on <wizardpage>
.
As JavaScript itself evolves, browser engines will change accordingly. The following changes represent changes that affect JavaScript extension developers.
The nonstandard generic string methods have been deprecated and removed in the Geko engine and therefore Thunderbird 68+. A deprecation exception is issued if any methods are used. The updated paradigm uses String instance methods allowing for the use on any object.
Deprecated Generics Syntax:
Instance Method Replacement:
The following String methods are affected by the change:
(All other String methods use the instance paradigm already)
A number of JavaScript modules have been renamed with the .jsm
extension. Most notably:
mailServices.js
has been renamed to MailServices.jsm
. This change was originally backwards-compatible with a deprecation warning, but the changes to module importing (see below) made that pointless and the old file has now been removed completely.
MailUtils.js
is now MailUtils.jsm
.
MailUtils.getFolderForUri
was renamed to MailUtils.getExistingFolder
.
In Thunderbird 67, a major backwards-incompatible change was made to importing JavaScript modules. Where once you used any of these:
Or the two-argument variation:
You should now do this:
ChromeUtils.import
is a replacement for Components.utils.import
(which was also changed in this way). Note that no second argument is supplied. The returned object is a dictionary of only the objects listed in EXPORTED_SYMBOLS
.
The function to retrieve passwords has lost its first parameter. Instead of
call it as
Removed, use:
Note: document.persist()
used the ID attribute whereas xulStore.persist()
uses the actual DOM node. More details in bug 1476678.
All methods of the AddonManager API now return a Promise instead of executing a callback. Instead of calling it as
you need to call it as
The defaultAccount
member can (since TB 65) have a null
value - if for some reason it doesn't have, or can't work out, the default account.
Hence you need to null-check it, and possibly handle not having an account at all (if in no other way than by an error message).
Removed. Use
The onDataAvailable
method lost its context
argument. This was removed in bug 1525319 which breaks the API.
To be backward compatible you need to probe the parameters. In case the third parameter is an nsIInputStream it is the old API. If the second one is an input stream it is the new API.
The onStartRequest
and onStopRequest
methods also no longer have a context
argument, which could be detected in a similar way.
The obsolete method newChannel
was removed and newChannel2
was renamed to newChannel
. (Bug 1528971.)
As newChannel
has been unused for a long time it should be safe to just replace the old newChannel
implementation with the newChannel2
and forward calls from newChannel2
.
The method convertFromByteArray
has been removed. The new preferred way to deal with unicode is through the TextEncoder
and TextDecoder
classes.
To convert byte arrays to different charsets, use
Removed. Use Element
, Node
, etc. instead, which are now available in all scopes.
These no longer need to be created by Cc[...].createInstance(Ci....)
, but simply via the new
keyword:
new DOMParser();
new XMLSerializer();
new XMLHttpRequest();
They should be available in all scopes now. If not, the following is needed:
Cu.importGlobalProperties(["DOMParser"]);
Cu.importGlobalProperties(["XMLSerializer"]);
Cu.importGlobalProperties(["XMLHttpRequest"]);
Trees have changed a lot. The tree
object is now a XULTreeElement
. It has lost thetreeBoxObject
property, because the nsITreeBoxObject
has been removed. Most of its methods can now be accessed directly through the tree
object. For example:
Furthermore, nsITreeColumn
has been replaced by the TreeColumn
object. Even though their interfaces look the same, they could behave differently. Check your implementation, as this affects almost all methods of nsITreeView
.
Some noteworthy changes:
tree.getCellAt() now returns a TreeCellInfo.
If a method requires a TreeColumn
parameter, a simple { id: columnName }
object no longer works. Get a proper TreeColumn
object via tree.columns.getNamedColumn(columnName)
.
Trees no longer support selecting individual cells. The TreeColumn
object no longer has a selectable
attribute and nsITreeView
has lost its isSelectable()
method.
In general, check searchfox.com to see the current definitions of tree related implementations:
Removed. Use
Renamed to
A long time ago newChannelFromURI2() has been added as an alternative to newChannelFromURI(), which as become deprecated by now. With ESR68 the old name is used again.
Feature Flags are used to manage features that are not yet ready for general use. Their behavior varies depending on the branch:
Feature flags are enabled once all related code for the feature have landed.
Feature flags remain enabled once the feature is complete unless the developers decide to temporarily pause it.
Feature flags are disabled by default until an explicit decision is made to enable the feature for all users.
If you believe a bug needs to be addressed in a release, follow these steps:
Set the Tracking Flag
Use ?
to nominate the bug for tracking for the specific Thunderbird version
Provide a justification explaining why the bug needs to be addressed in the release.
Mark the Status Flag
Set the corresponding version status flag to affected
.
Release Management will review bugs nominated for tracking. If they agree that the bug requires investigation for the release, they will change the tracking flag from ?
to +
.
Thunderbird provides releases through four distinct channels, each serving a unique purpose. Below are the channels, their corresponding branches, and a summary of their purpose:
Thunderbird Daily (comm-central): Where development and testing begin, and new features are developed.
Thunderbird Beta (comm-beta): The monthly stabilization release.
Thunderbird Release (comm-release): The official monthly release.
Thunderbird ESR (comm-esr): The official annual extended support release.
These four channels offer increasing levels of stability, increasing as code moves from Daily to Beta and being the highest in Release and ESR.
After legacy WebExtensions had been deprecated in Thunderbird 78, the Thunderbird team provided two so-called wrapper Experiments (the WindowListener
Experiment and the BootstrapLoader
Experiment), which re-implemented the loading framework of legacy extensions and required only little changes for add-ons to be usable in Thunderbird 78. This mechanism was intended as an intermediate solution.
This document describes how to remove the wrapper Experiment and how to properly convert a legacy extension to a modern WebExtension.
If you need any help, get in touch with the add-on developer community:
Converting a wrapped WebExtension into a modern WebExtension will be a complex task: almost all interactions with Thunderbird will need to be re-written to use the new APIs. If these APIs are not yet sufficient for your add-on, you may even need to implement additional Experiment APIs yourself. Don't worry though: you can find information on all aspects of the migration process below, including links to many advanced topics.
Before working on an update, it is advised to read some information about the WebExtension technology first. Our Extension guide and our "Hello World" Extension Tutorial are good starting points.
The guide assumes that the background script is loaded as a module.
Wrapped WebExtensions have a background script similar to the following:
registerDefaultPrefs()
Most legacy extensions stored their preferences in an nsIPrefBranch
, and the registerDefaultPrefs()
function loaded a JavaScript file with default preference values. An example default preference file could look like this:
This file and the associated call to registerDefaultPrefs()
can be removed, and the default values must be set in the background script through the LegacyPrefs Experiment:
We can now use the LegacyPrefs Experiment to access existing preferences, for example the preference entry at extensions.myaddon.enableDebug
can be read from any WebExtension script via:
Modern WebExtension should eventually use browser.storage.local.* for their preferences, but to simplify the conversion process, we will keep using the nsIPrefBranch
for now. The very last conversion step will migrate the preferences.
registerChromeUrl()
We will keep registering global legacy chrome://
or resource://
URLs, but we will use the LegacyHelper Experiment. Use the registerGlobalUrls()
function of the LegacyHelper Experiment instead of the registerChromeUrl()
function of the wrapper Experiment. For example:
registerOptionsPage()
Modern WebExtensions show their options in an HTML page in a tab or in a frame inside the Add-on Manger. The wrapper APIs instead allowed to register a legacy XUL dialog to be opened when the wrench icon in the add-on card of the Add-on Manger was clicked. This has to be removed to allow that wrench icon to show the standard WebExtension HTML options page.
In this step, we will create a menu entry on the tools
menu to open the XUL options dialog via the LegacyHelper Experiment:
This will be removed after the XUL options dialog has been converted to a standard WebExtension HTML options page.
This step will interrupt the main functionality of your add-on. Remove the registration for the wrapper Experiment from manifest.json
, remove its implementation and schema files and any usage from your background script. The only remaining working part of your add-on should now be your XUL options dialog.
Please continue at step 5 of the conversion from legacy WebExtensions to modern WebExtensions.
The architecture of Thunderbird extensions has changed over the years. The following table describes the different legacy extension types, and how they can be converted to modern WebExtensions.
The current Thunderbird ESR no longer supports legacy extensions.
If you are currently maintaining a legacy extension, please identify the type of your extension in the table below and check the provided guides on updating your extension to remain compatible with the latest versions of Thunderbird.
Status: Unsupported in Thunderbird 128.
Wrapped WebExtension are modern WebExtensions, which use a so-called wrapper Experiment (the WindowListener
Experiment or the BootstrapLoader
Experiment). These Experiments were provided as an intermediate solution after legacy WebExtensions had been deprecated in Thunderbird 78. After 4 years, the Thunderbird team is no longer able to maintain the two mentioned wrapper Experiments and developers should no longer use them.
Wrapped WebExtensions have a background script similar to the following:
Status: Deprecated in Thunderbird 68.
This type of extension uses a bootstrap file (bootstrap.js
) as an entry point to the extension. The file defines four methods (install
, uninstall
, startup
, and shutdown
) from which all extension behaviour is controlled. These extensions can be installed/uninstalled and enabled/disabled without restarting Thunderbird, so they are sometimes called "restartless" extensions. They use an RDF manifest (install.rdf
).
It is recommended to update legacy bootstrap extensions to legacy WebExtensions first, before converting them to modern WebExtension. The update guide assumes, the extension is currently compatible with Thunderbird 60. If that is not the case, you can find further update instructions here.
Status: Deprecated in Thunderbird 68.
The original type of extension for Thunderbird and Firefox, using documents that overlay the Thunderbird UI, adding to and modifying it. These extensions use an RDF manifest (install.rdf
) and require a restart of Thunderbird for installation/uninstallation, upgrading/downgrading and enabling/disabling.
It is recommended to update legacy overlay extensions to legacy WebExtensions first, before converting them to modern WebExtensions. The update guide assumes, the extension is currently compatible with Thunderbird 60. If that is not the case, you can find further update instructions here.
We do not suggest to convert older legacy bootstrapped extensions or legacy overlay extensions (as used in Thunderbird 60) directly to modern WebExtensions. They should first be converted to legacy WebExtensions.
If you need any help, get in touch with the add-on developer community:
Converting a legacy WebExtension into a modern WebExtension will be a complex task: almost all interactions with Thunderbird will need to be re-written to use the new WebExtension APIs. If these APIs are not yet sufficient for your add-on, you may even need to implement additional Experiment APIs yourself. Don't worry though: you can find information on all aspects of the migration process below, including links to many advanced topics.
Before working on an update, it is advised to read some information about the WebExtension technology first. Our Extension guide and our "Hello World" Extension Tutorial are good starting points.
Please add a background script to your extension, which will be needed during the update process. The guide assumes that the background script is loaded as a module.
The technical conversion from a legacy WebExtension to a modern WebExtension is simple: drop the legacy
key from the manifest.json
file.
Your add-on should now install in current versions of Thunderbird without issues, but it will not yet do anything, because the chrome.manifest
file is no longer read.
chrome.manifest
fileThe most common entries in the chrome.manifest
file are listed below:
These entries registered global URLs used by the extension to access its assets. Use the LegacyHelper Experiment to register content
, resource
and locale
entries. To replicate the entries shown in the previous example, add the following to your background script:
There are no direct equivalents to manifest flags, so add-ons now need to provide their own mechanisms to switch code or resources depending on the runtime environment. Relevant information is accessible through the runtime API.
This entry type is no longer supported, it has to be replaced by a resource://
URL. In the above example we had the following skin
definition:
The skin
folder is a subfolder of /chrome/
, which is already available as a resource://
URL. We can therefore replace all usages of
by
This entry is no longer supported, it has to be replaced by the LegacyCSS Experiment. To replicate the style
entry shown in the previous example, add the following to your background script:
All *.xul
files have been renamed to *.xhtml
files in recent versions of Thunderbird! Still using *.xul
files is unsupported and will cause issues.
This should only be a temporary step. After the initial conversion from a style
entry to using the LegacyCSS Experiment, the required styles should be applied by using standard WebExtension theming support.
This entry type is no longer supported. Replacing it will be the main conversion work, which is described in step 7 and later.
These entries are no longer supported.
/defaults/preferences/
folderMost legacy extensions stored their preferences in an nsIPrefBranch
, and the /defaults/preferences/
folder contained JavaScript files with default preference values. An example default preference file could look like this:
This file can be removed, and the default values must be set in the background script through the LegacyPrefs Experiment:
We can now use the LegacyPrefs Experiment to access existing preferences, for example the preference entry at extensions.myaddon.enableDebug
can be read from any WebExtension script via:
Modern WebExtension should eventually use browser.storage.local.* for their preferences, but to simplify the conversion process, we will keep using the nsIPrefBranch
for now. The very last conversion step will migrate the preferences.
The XUL options dialog is no longer registered, after the legacy
key has been removed from manifest.json
. We will use the LegacyHelper Experiment to open the XUL options dialog via a menu entry in the tools
menu. Add the following to your background script:
This will be removed after the XUL options dialog has been converted to a standard WebExtension HTML options page.
Even though the LegacyHelper Experiment allows to register legacy locales, the technology itself is deprecated: WebExtension HTML pages cannot access DTD or property files. Instead, they use the i18n API to access locales stored in simple JSON files.
The localeConverter.py python script will do most of the work to convert your locale files (DTD and property files) into the new JSON format.
The new locale data can be accessed from any WebExtension script:
Instead of a XUL dialog, WebExtensions use an HTML page for their options page, which will be accessible to the user through the add-on manager. The page is registered in manifest.json
:
JavaScript loaded by that options.html
document can access all WebExtension APIs in the same way as for example the background script.
In this step the old XUL options dialog has to be re-created as an HTML page, using only HTML elements, JavaScript and CSS. It is no longer possible to use XUL elements. Some custom elements and 3rd party libraries to simplify this step can be found in the webext-support repository.
It may help during development, that the old XUL options page can still be opened through the tools
menu.
There is no automatic replacement of locale placeholder entities like &myLocaleIdentifier;
in WebExtension HTML files any more. Instead, you can use placeholders like __MSG_myLocaleIdentifier__
in your markup and include the i18n.mjs module and automatically replace all __MSG_*__
locale placeholders on page load.
The script is using the i18n
API to read the modern JSON locale files created in the previous step.
preferencesBindings.js
The legacy XUL options page used a framework to automatically load and save preference values, controlled by the preferencesBindings.js script. That automatism does not exist for HTML option pages. But it is possible to implement a similar mechanism using a data-preference
attribute:
We can loop over all elements which have such an attribute, and load their value from storage. Additionally we can attach an event listener to store the value after the input field has been changed by the user:
Now it's time to find out how your add-on can leverage the existing WebExtension entry points. What UI elements did you use? Do any of the supported WebExtension UI elements fit?
Even if they are not a perfect match, try to replace as many of your legacy UI entry points by WebExtension entry points:
action buttons (normal or menu-typed)
action popus
message display scripts (manipulate/overlay the displayed message)
content tabs
content popup windows\
The goal of this step is to re-create as much of the functionality of your add-on by using only WebExtension technology. Browse through the list of supported WebExtension APIs to see if any of them provide what is needed by your add-on. Check available Web APIs, there is a high chance to find simple replacements for complicated XPCOM calls:
Do not hesitate to ask in our community channels for help.
If certain crucial features of your add-on cannot be implemented using the available WebExtension APIs or Web APIs, you can create your own Experiment APIs.
As Experiments usually run in the main process and have unrestricted access to any aspect of Thunderbird, they are expected to require updates for each new version of Thunderbird. To reduce the maintenance burden in the future, it is in your own interest to use Experiment APIs only to the extent necessary for the add-on.
Best practice: Try to write APIs that would be useful for a wide range of add-ons, not just the one you're porting. That way, you can later on propose the API you designed for inclusion in Thunderbird, with your add-on serving as the reference implementation. If your APIs become a part of Thunderbird, you no longer need to maintain them as part of the add-on.
A basic description of Experiment APIs can be found in a separate article:
Manipulating Thunderbirds UI through Experiments is historically referred to as overlaying. The basic principle of overlaying is to get hold of a native Thunderbird window object and to add or remove DOM elements (or monkey-patch functions living inside that native window to change some behaviour).
Adding or removing DOM elements can be achieved through JavaScript (note that Thunderbird sometimes still uses non-standard XUL elements, which are however slowly replaced by standard HTML elements):
A more detailed explanation of the shown code snippet is beyond the scope of this guide. It is advised to study Thunderbird's code for more details.
Generating complex and nested DOM elements through JavaScript can become cumbersome, and legacy add-ons were able to provide a simple DOM string instead. This is still possible by using the following helper function:
The function supports XUL strings with WebExtension __MSG_*__
locale placeholders. It also supports insertbefore
or insertafter
attributes, to specify where the element should be added. If an existing id
is specified, the element will be added as a child inside the existing element:
A more detailed explanation of the shown code snippet is beyond the scope of this guide. The shown code is taken from the FolderFlags add-on. The Restart Experiment Example is also using this method.
In order to add custom UI entry points, the add-on has to manipulate the native window object of all already open windows/tabs and also any window/tab which is opened in the future. The two most common concepts to achieve this are described below.
This is the preferred method, since the add-on can leverage existing WebExtension APIs and reduces the amount of code which has to be maintained by the add-on developer. For example, to manipulate all message display tabs, the following code can be used in the WebExtension background script:
The following API implementation is based on the Remove Attachments If Junk Experiment Example:
An example which manipulates the main window using the same strategy is the Restart Experiment Example.
If the window of interest is not supported by WebExtension APIs, it is not detectable through WebExtension APIs and the detection code has to live inside an Experiment.
The following example is based on the Activity Manager Experiment Example. Its background script triggers the Experiment to register a global window listener, which manipulates the window of interest:
The implementation of the Experiment could be:
So far we have only discussed Experiments which perform a direct action inside the Experiment implementation. To move as much code out of the Experiment implementation, we can trigger a standard WebExtension event and let any follow-up action be handled by the WebExtension.
A common use case is a custom button added to Thunderbird's UI through an Experiment. The action which is triggered by clicking on the button should not be handled in the Experiment, but by the WebExtension background script, which has registered a listener for that button being pressed. For this to work we need to define an EventEmitter
in the Experiment:
The boilerplate, which connects the internals of a WebExtension event to the defined EventEmitter
, is the added EventManger
in lines 5-21 of the following example. The glue part to actually trigger the event is
in line 31:
The callback of the EventEmitter
(line 9) has the x
and y
parameters, which are passed through to fire.async()
, transmitting them to the WebExtension:
A working implementation of this example can be found in the Activity Manager Experiment Example.
So far we stored our preferences in an nsIPrefBranch
, which could be accessed from WebExtension scripts through the LegacyPrefs Experiment, and from other Experiments directly through the nsIPrefBranch
.
WebExtensions should eventually store their preferences in browser.storage.local.*, which removes the data when the add-on is uninstalled. The user should be able to start fresh by uninstalling and reinstalling an extension, if a specific configuration causes the add-on to malfunction. This is a common pattern, which however does not work for preferences stored in an nsIPrefBranch,
as they are not cleared on add-on uninstall.
The user actually expects that all his data associated with a certain add-on is removed from the Thunderbird profile, when the add-on is removed. An add-on can of course offer import and export functions.
In order to migrate preferences, your custom Experiments may no longer access the nsIPrefBranch
directly, as they later cannot access the migrated values in browser.storage.local.*. All your custom Experiments must be independent of the used storage. The two most common strategies are outlined below.
Consider a simple debug log in an Experiment function, which used to query the extensions.myaddon.enableDebug
preference directly:
The function can receive the debug flag as a parameter:
In the WebExtension script calling that method, we continue (for now) to use the LegacyPrefs Experiment to retrieve the value for the enableDebug
preference before passing it to the Experiment:
Cached preferences can be accessed everywhere inside the Experiment implementation. A simple implementation could be:
In the background script we have to monitor the extensions.myaddon.*
preference branch and update the cache if needed. The LegacyPrefs Experiment provides an onChanged
event for that purpose:
The last step is to move all preferences into browser.storage.local.* and update all WebExtension scripts to no longer use the LegacyPrefs Experiment. The preferences.mjs module can be used as a drop-in replacement for the LegacyPrefs Experiment. Add the following to the top of your background script:
Move the definition of the DEFAULTS
object from the top of your background script into your copy of the preferences.mjs module.
Remove all code which used browser.LegacyPrefs.setDefaultPref()
and update all other calls to access your preferences through the LegacyPrefs Experiment by the matching method of the preferences.mjs module.
The preference caching mechanism for Experiments can be updated as follows:
Wait about 6-12 months after the migration code has been shipped to your users, before removing the migration code and the LegacyPrefs Experiment.
Release notes are an essential part of keeping our users informed. If you believe your knowledge can enhance the release notes, we encourage developers to contribute suggested release notes alongside their patch fixes.
Write Effective Release Notes
Suggest a Release Note
Set the relnote-thunderbird
tracking flag in Bugzilla to ?
. This will provide a template to write your note and will nominate it for inclusion in the release notes.
Contributing release notes is especially valuable for changes that land in comm-central. During the merge from comm-central to comm-beta, hundreds of commits are often reviewed, and release note suggestions help to make this process more efficient and accurate.
By contributing, you help ensure that our users remain informed about new features, fixes, and changes.
Some fixes are more urgent than others and "riding the train" will take too long to get the fix to users. This is where uplifts come into play. Uplifts allow a fix to be fast-tracked into the Beta, Release, or ESR channels.
Uplifts to Beta are limited to bug and security fixes only. Features and enhancements will be included in the next merge.
Patches nominated for uplift to Beta should:
Be limited to bug and security fixes.
Have already landed on comm-central.
Have been tested and stabilized on comm-central.
Include tests or provide a strong justification for the absence of tests.
Not change any localizable strings.
Uplifts to Beta can include:
Proven performance improvements.
Top crash fixes.
Security fixes.
Fixes for recent regressions.
Low-risk improvements early in the Beta cycle.
Uplifts to Release and ESR are limited to stability and security fixes only. Features and enhancements will be included in the next merge.
Patches nominated for uplift to Release or ESR should:
Be limited to stability and security fixes only.
Have already landed in comm-beta.
Have been tested and stabilized on comm-beta.
Include tests or provide a strong justification for the absence of tests.
Not change any localizable strings.
Uplifts to Release and ESR can include:
Major top crash fixes.
High volume startup crash fixes.
Security fixes.
Fixes for regressions with a broad impact.
Fixes for problems in a major feature.
Requesting an uplift is done by setting the relevant flag (approval-comm-beta
, approval-comm-release
, or approval-comm-esr
) on a Bugzilla patch attachment to ?
. The Uplift Request Form (see below) is automatically generated to provide the required information. All uplifts must use the Uplift Request Form.
Ensure uplift requests are submitted at least two working days prior to any scheduled release date. This will allow the release team to build the release the day before the scheduled release date, giving ample time for troubleshooting and testing prior to release day.
Please state case for uplift consideration and ensure bug severity is set
User impact if declined
Provide details on how an end user would be impacted with or without this change. In addition to the Steps to Reproduce (STR), explain the deeper implications for users.
Is this code covered by automated tests?
Options: Yes / No / Unknown
Has the fix been verified in Daily?
Options: Yes / No / N/A
Has the fix been verified in Beta?
Options: Yes / No / N/A
Needs manual test from QA?
Options: Yes / No
If "Yes," include the steps to reproduce either by referencing an existing comment with STR or elaborating directly.
List of other uplifts needed
If this patch depends on other changes not present on the target branch, list those dependencies here.
Ensure approval is also requested for dependent patches.
Risk to taking this patch
Options: Low / Medium / High
Provide a justification as to why the benefits of the uplift outweighs the associated risks.
Examples:
Low: A one-line CSS change impacting only the settings page.
Medium: Due to code complexity and integration with other areas, there might be regressions in related functionality.
High: The patch involves complex changes with a high likelihood of fallout or regressions, which may require extensive manual testing or mitigation strategies.
Why is the change risky/not risky? (and alternatives if risky)
If the change is risky, explain the potential impact. In other words, describe the potential negative impacts that could occur if the change leads to a regression. What mitigations were taken to reduce the risk? What alternative solutions were considered?
String changes made/needed
Answer "none" if no string changes were made.
If there are string changes, add details and justification to help with l10n review.
Thunderbird follows a release train model to ensure timely and predictable releases. This approach allows for regular feature rollouts, stability improvements, and bug fixes.
The general release cadence consists of 4-week cycles, with each branch corresponding to a specific release channel. Each branch, starting from comm-central, is based on the previous branch.
Release Timeline: A new major version of Thunderbird Daily begins every 4 weeks.
Releases: Thunderbird Daily is automatically released on a daily basis from comm-central.
Release Timeline:
A new major version of Thunderbird Beta begins every 4 weeks.
4 weeks after Daily starts for a new version, comm-central is merged into comm-beta.
Releases: Thunderbird Beta for this version is released weekly for the next 3 weeks.
Beta 1: Includes the merged code.
Beta 2 and above: Include any new uplifts.
Release Timeline:
A new major version of Thunderbird Release begins every 4 weeks.
3 weeks after Beta starts for a new version, comm-beta is merged into comm-release.
1 week after this merge, the monthly Thunderbird Release is published.
Releases: Thunderbird Release is released every 4 weeks, with point releases as needed every 2 weeks.
Release Timeline: The code from comm-release is merged into comm-esr<version> once a year.
Releases: Thunderbird ESR is released every year, typically in July, with point releases as needed every 2 weeks.
A 1-week soft code freeze occurs for comm-central prior to merging into comm-beta.
During this time:
Risky code should not land in comm-central.
Features controlled by a feature flag that were disabled in Daily should not be enabled.
Prior to both merges, reviews are conducted for all changes included in the merge.
Code may be backed out if:
It is deemed too risky.
It introduces new crashes or high-severity bugs.
It reduces the overall quality of Thunderbird.
It introduces a bug and a follow-up fix cannot be provided in a reasonable timeframe.
Refer to as examples.
Follow on how to write brief, clear, and user-focused release notes.
Ensure the fix adheres to criteria specified in or .
You can view Thunderbird release milestones on the .
Thunderbird 136.0a1 starts
Jan 6
Thunderbird 136.0a1 soft code freeze
Jan 27–Feb 3
Thunderbird 136.0a1 pre-merge review
Jan 30
Thunderbird merge 136.0a1 central → beta
Feb 3
Thunderbird 136.0b1
Feb 5
Thunderbird 136.0b2
Feb 12
Thunderbird 136.0b3
Feb 19
Thunderbird 136.0b4
Feb 24
Thunderbird 136.0b4 pre-merge review
Feb 24
Thunderbird merge 136.0b4 beta → release
Feb 25
Thunderbird 136.0
Mar 4
Thunderbird 136.0.1
Mar 18