Using a Background Page
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.
Background Page and Background Scripts
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.
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 described in the MailExtension guide, there are two ways to load background scripts:
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.
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 ES6 modules. We therefore add the following section to our manifest.json
file:
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.
Listening for New Messages
In order to listen for new messages, we have to add a listener for the onNewMessageReceived event to our background script:
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 callback function of the onNewMailReceived
event receives two parameters: folder
being a MailFolder and messages
being a MessageList. The defined event listener stores the folder and the message information of the new received mail in the extensions storage.
The onNewMessageReceived
event requires the accountsRead
permission, which needs to be added to the permissions
key in our manifest.json
file.
messenger.storage.local.get()
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 destructering, 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.
messageTools.iterateMessagePages()
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.
Since Thunderbird's WebExtension API potentially has to handle a lot of messages, the MessageList data type is paginated. Please check the Working with Message Lists tutorial for more information.
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.
messenger.storage.local.set()
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:
Adding Menu Entries and their Actions
Let's add the following code to our background script, which will add two menu entries and will react to them being clicked:
messenger.menus.create()
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 Supported UI Elements page.
The menus.create() 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.
Using the menus
API requires the menus
permission, which needs to be added to the permissions
key in our manifest.json
file.
messenger.menus.onClicked()
In order to do something when our menu is clicked, we add a listener for the onClicked 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).
messenger.notifications.create()
After we have retrieved the current messageLog
from the local storage, we loop over all entries and create a notification for each entry in line 20
.
Using the notifications
API requires the notifications
permission, which needs to be added to the permissions
key in our manifest.json file.
Testing the Extension
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:
Our background script should look as follows:
Installing
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.
Trying it Out
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.
Last updated