LogoLogo
  • About Thunderbird
  • Contributing to Thunderbird
    • Getting Started Contributing
    • Setting Up A Build Environment
    • Building Thunderbird
      • Windows Build Prerequisites
      • Linux Build Prerequisites
      • macOS Build Prerequisites
      • Artifact Builds
    • Codebase Overview
      • Account Configuration
      • Address Book
      • Chat Core
        • Notifications
        • Message Styles
        • Keyboard shortcuts
        • Chat Core Protocols
        • Contacts
      • Mail Front-End
    • Tutorials and Examples
      • Hello World Example
      • Thunderbird Live Development Videos
    • Fixing a Bug
      • Bug Triaging 101
        • Bug Status Classicification
        • Bug Types
        • Garbage Collection
        • Narrow the Scope
      • Using Mercurial Bookmarks
      • Using Mercurial Queues
      • Lint and Format Code
      • Using ESLint to Format Javascript Code
      • Try Server
      • Landing a Patch
      • Care and Feeding of the Tree
    • Testing
      • Running Tests
      • Adding Tests
      • Writing Mochitest Tests
  • Planning
    • Roadmap
    • Android Roadmap
    • Supported Standards
  • Add-on Development
    • Introduction
    • What's new?
      • Manifest Version 3
    • A "Hello World" Extension Tutorial
      • Using WebExtension APIs
      • Using a Background Page
      • Using Content Scripts
    • A Guide to Extensions
      • Supported Manifest Keys
      • Supported UI Elements
      • Supported WebExtension APIs
      • Thunderbird's WebExtension API Documentation
      • Thunderbird WebExtension Examples
      • Introducing Experiments
    • A Guide to Themes
    • Developer Community
    • Documentation & Resources
      • Tips and Tricks
    • Add-on Update Guides
      • Update for Thunderbird 128
      • Update for Thunderbird 115
        • Adapt to Changes in Thunderbird 103-115
      • Update for Thunderbird 102
        • Adapt to Changes in Thunderbird 92-102
      • Update for Thunderbird 91
        • Adapt to Changes in Thunderbird 79-91
      • Update for Thunderbird 78
        • Adapt to Changes in Thunderbird 69-78
      • Update for Thunderbird 68
        • Adapt to Changes in Thunderbird 61-68
      • How to convert legacy extensions?
        • Convert wrapped WebExtensions to modern WebExtensions
        • Convert legacy WebExtensions to modern WebExtensions
        • Convert legacy overlay extension to legacy WebExtension
        • Convert legacy bootstrapped extension to legacy WebExtension
  • Releases
    • Thunderbird Channels
    • Release Cadence
    • Uplifting Fixes
    • Feature Flags
    • Tracking Fixes for Releases
    • Contributing to Release Notes
Powered by GitBook
On this page
  • Using a message_display_action
  • popup.html
  • popup.css
  • popup.js
  • Testing the Extension
  • Installing
  • Trying it Out

Was this helpful?

Edit on GitHub
Export as PDF
  1. Add-on Development
  2. A "Hello World" Extension Tutorial

Using WebExtension APIs

Extending the simple example extension to make use of WebExtension APIs.

PreviousA "Hello World" Extension TutorialNextUsing a Background Page

Last updated 7 months ago

Was this helpful?

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.

Using a 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.:

"message_display_action": {
    "default_popup": "messagePopup/popup.html",
    "default_title": "Details",
    "default_icon": "images/internet-32px.png"
},

popup.html

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:

popup.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Details</title>
    <link rel="stylesheet" type="text/css" media="screen" href="popup.css">
</head>

<body>
    <div class="grid-container">
        <div class="header">Subject:</div>
        <div id="subject" class="content"></div>
        <div class="header">From:</div>
        <div id="from" class="content"></div>
        <div class="header">Received-Header:</div>
        <div id="received" class="content"></div>
    </div>
    <script type="module" src="popup.js"></script>
</body>

</html>

popup.css

We place the following popup.css file in the same folder as the popup.html file.

popup.css
.grid-container {
    display: grid;
    grid-template-columns: 1fr 6fr;
}

.header {
    font-weight: bold
}

.grid-container div {
    margin: 1ex;
}

popup.js

popup.js
// The user clicked our button, get the active tab in the current window using
// the tabs API.
let tabs = await messenger.tabs.query({ active: true, currentWindow: true });

// Get the message currently displayed in the active tab, using the
// messageDisplay API. Note: This needs the messagesRead permission.
// The returned message is a MessageHeader object with the most relevant
// information.
let message = await messenger.messageDisplay.getDisplayedMessage(tabs[0].id);

// Update the HTML fields with the message subject and sender.
document.getElementById("subject").textContent = message.subject;
document.getElementById("from").textContent = message.author;

// Request the full message to access its full set of headers.
let full = await messenger.messages.getFull(message.id);
document.getElementById("received").textContent = full.headers.received[0];

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.

messenger.tabs.query()

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.

messenger.messageDisplay.getDisplayedMessage()

At this stage we are interested in the subject (line 12) and the author (line 13).

"permissions": [
    "messagesRead"
],

messenger.messages.getFull()

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.

Testing the Extension

hello-world/
  ├── manifest.json
  ├── images/
      ├── internet.png
      ├── internet-16px.png
      └── internet-32px.png
  ├── mainPopup/
      ├── popup.css
      ├── popup.html
      └── popup.js
  └── messagePopup/
      ├── popup.css
      ├── popup.html
      └── popup.js

This is how our manifest.json should now look like:

manifest.json
{
    "manifest_version": 2,
    "name": "Hello World Example",
    "description": "A basic Hello World example extension!",
    "version": "2.0",
    "author": "Thunderbird Team",
    "browser_specific_settings": {
        "gecko": {
            "id": "helloworld@yoursite.com",
            "strict_min_version": "128.0"
        }
    },
    "browser_action": {
        "default_popup": "mainPopup/popup.html",
        "default_title": "Hello World",
        "default_icon": "images/internet-32px.png"
    },
    "message_display_action": {
        "default_popup": "messagePopup/popup.html",
        "default_title": "Details",
        "default_icon": "images/internet-32px.png"
    },
    "permissions": [
        "messagesRead"
    ],
    "icons": {
        "64": "images/internet.png",
        "32": "images/internet-32px.png",
        "16": "images/internet-16px.png"
    }
}

Installing

Trying it Out

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.

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 or this for more details.

All WebExtension API functions return a instead of an actual value. This aims to simplify the handling of asynchronous functions.

The author of this example prefers 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 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 method to find it in line 3.

The method of the provides access to the currently viewed message in a given tab. It returns a Promise for a object from the with basic information about the message in line 9.

The method requires the messagesRead permission, which needs to be added to the permissions key of our manifest.json file.

The method in line 16 returns a Promise for a 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 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.

grid documentation
grid guide
Promise
the async/await syntax
getDisplayedMessage
messageDisplay API
MessageHeader
messages API
getDisplayMessage
getFull
MessagePart
correct changes
tabs API
query
first part of the Hello World Extension Tutorial