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
  • Step 1: Replace registerDefaultPrefs()
  • Step 2: Replace registerChromeUrl()
  • Step 3: Replace registerOptionsPage()
  • Step 4: Remove the wrapper API

Was this helpful?

Edit on GitHub
Export as PDF
  1. Add-on Development
  2. Add-on Update Guides
  3. How to convert legacy extensions?

Convert wrapped WebExtensions to modern WebExtensions

PreviousHow to convert legacy extensions?NextConvert legacy WebExtensions to modern WebExtensions

Last updated 8 months ago

Was this helpful?

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 and our are good starting points.

The guide assumes that the background script is loaded .

Wrapped WebExtensions have a background script similar to the following:

await messenger.WindowListener.registerDefaultPrefs(
    "defaults/preferences/prefs.js"
);
await messenger.WindowListener.registerChromeUrl([
    ["content",  "myaddon",          "chrome/content/"],
    ["resource", "myaddon",          "chrome/"],
    ["locale",   "myaddon", "en-US", "chrome/locale/en-US/"],
    ["locale",   "myaddon", "de-DE", "chrome/locale/de-DE/"],
]);
await messenger.WindowListener.registerOptionsPage(
    "chrome://myaddon/content/options.xhtml"
);
await messenger.WindowListener.registerWindow(
    "chrome://messenger/content/messengercompose/messengercompose.xhtml",
    "chrome://myaddon/content/messengercompose.js"
);
await messenger.WindowListener.startListening();

Step 1: Replace 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:

pref("extensions.myaddon.enableDebug", false);
pref("extensions.myaddon.retries", 5);
pref("extensions.myaddon.greeting", "Hello");
const DEFAULTS = {
    enableDebug: false,
    retries: 5,
    greeting: "Hello",
}
for (let [prefName, defaultValue] of Object.entries(DEFAULTS)) {
    await browser.LegacyPrefs.setDefaultPref(
        `extensions.myaddon.${prefName}`,
        defaultValue
    );
}
let enableDebug = await browser.LegacyPrefs.getPref("extensions.myaddon.enableDebug");

Step 2: Replace registerChromeUrl()

browser.LegacyHelper.registerGlobalUrls([
    ["content",  "myaddon",          "chrome/content/"],
    ["resource", "myaddon",          "chrome/"],
    ["locale",   "myaddon", "en-US", "chrome/locale/en-US/"],
    ["locale",   "myaddon", "de-DE", "chrome/locale/de-DE/"],
]);

Step 3: Replace 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.

browser.menus.create({
    id: "oldOptions",
    contexts: ["tools_menu"],
    title: "Old XUL options dialog",
    onclick: () => browser.LegacyHelper.openDialog(
        "XulAddonOptions",
        "chrome://myaddon/content/options.xhtml"
    )
})

This will be removed after the XUL options dialog has been converted to a standard WebExtension HTML options page.

Step 4: Remove the wrapper API

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.

This file and the associated call to registerDefaultPrefs() can be removed, and the default values must be set in the background script through the Experiment:

We can now use the 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 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.

We will keep registering global legacy chrome:// or resource:// URLs, but we will use the Experiment. Use the registerGlobalUrls() function of the Experiment instead of the registerChromeUrl() function of the wrapper Experiment. For example:

In this step, we will create a menu entry on the tools menu to open the XUL options dialog via the Experiment:

Please continue at of the conversion from legacy WebExtensions to modern WebExtensions.

LegacyPrefs
LegacyPrefs
browser.storage.local.*
LegacyHelper
LegacyHelper
LegacyHelper
Developer Community
Extension guide
"Hello World" Extension Tutorial
as a module
step 5