Adapt to Changes in Thunderbird 92-102
This document tries to cover all the internal changes that may be needed to make add-ons compatible with Thunderbird Beta. 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.

String comparison against the version string

Some developers use the version string to determine which function to call in add-ons which try to be backward compatible. For example:
1
if (xulAppInfo.version >= "91.0") {
2
restartButton.addEventListener(
3
"command",
4
() => MailUtils.restartApplication()
5
);
6
} else {
7
restartButton.addEventListener(
8
"command",
9
() => BrowserUtils.restartApplication()
10
);
11
}
Copied!
This fails for TB100 and newer, because this is a string comparison and not an integer comparison. A function to get the integer values could look like so:
1
function getThunderbirdVersion() {
2
let parts = Services.appinfo.version.split(".");
3
return {
4
major: parseInt(parts[0]),
5
minor: parseInt(parts[1]),
6
revision: parts.length > 2 ? parseInt(parts[2]) : 0,
7
}
8
}
Copied!
And then just use getThunderbirdVersion().major >= 91 to check the version.
In this specific case, one could also use feature detection itself:
1
if ("restartApplication" in MailUtils)
2
restartButton.addEventListener(
3
"command",
4
() => MailUtils.restartApplication()
5
);
6
} else {
7
restartButton.addEventListener(
8
"command",
9
() => BrowserUtils.restartApplication()
10
);
11
}
Copied!

Changed DOM Elements

composer

The id of the editor element in the composer has been renamed from content-frame to messageEditor.

message window

The id of the additional header area in the message display window has been renamed from expandedHeaders2 to extraHeadersArea. The element has also been converted from a table to a div.
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.

Changed API

calICalendar.*

Since TB 96, many calendar functions return Promises. This includes:
  • addItem()
  • adoptItem()
  • deleteItem()
  • deleteOfflineItem()
  • getItem()
  • getItemOfflineFlag()
  • modifyItem()
Please note: The getItem() method has been changed to return one item instead of an array.
The former methods to promisify these functions have been removed together with calAsyncUtils.jsm. Replace
1
let pcal = cal.async.promisifyCalendar(calendar.wrappedJSObject);
2
let item = await pcal.getItem(itemId);
Copied!
by
1
let item = await calendar.getItem(itemId);
Copied!
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.
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 community channels.

calICalendar.getItems()

Since TB 96, calICalendar.getItems() returns a ReadableStream. Replace
1
let aCalIOperationListener = {
2
QueryInterface: ChromeUtils.generateQI(["calIOperationListener"]),
3
onOperationComplete(calendar, status, operationType, id, detail) {
4
currentView().setSelectedItems(items, false);
5
},
6
onGetResult(calendar, status, itemType, detail, itemsArg) {
7
for (let item of itemsArg) {
8
// Do something with item.
9
}
10
},
11
};
12
calendar.getItems(
13
aItemFilter,
14
aCount,
15
aCalIDateTimeRangeStart,
16
aCalIDateTimeRangeEndEx,
17
aCalIOperationListener
18
);
Copied!
by
1
let iterator = cal.iterate.streamValues(
2
calendar.getItems(
3
aItemFilter,
4
aCount,
5
aCalIDateTimeRangeStart,
6
aCalIDateTimeRangeEndEx
7
)
8
);
9
10
for await (let items of this.iterator) {
11
for (let item of items) {
12
// Do something with item.
13
}
14
}
Copied!

Offline Support

For providers with offline support, you may need to support a _cachedAdoptItemCallback property on your provider class. This is an unfortunate hack needed to maintain the order the "onAddItem" event is fired by calCachedCalendar.
calCachedCalendar sets this property in the doAdoptItem() method and it should be called by your provider just before returning in the adopItem() method. An example of this can be seen here in the ICS provider.

calICalendar.getItemsAsArray()

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.

calStorageCalendar.resetItemOfflineFlag()

Since TB 96, this function returns a Promise. Replace
1
let resetListener = {
2
QueryInterface: ChromeUtils.generateQI(["calIOperationListener"]),
3
onGetResult(calendar, status, itemType, detail, items) { },
4
onOperationComplete(calendar, status, opType, id, detail) {
5
// Reset completed.
6
},
7
}
8
storage.resetItemOfflineFlag(aItem, resetListener);
Copied!
by
1
await storage.resetItemOfflineFlag(aItem);
2
// Reset completed.
Copied!

ChromeUtils.import()

Since TB 101, it is no longer possible to load JSMs via extension URLs, for example
1
var { myModule } = ChromeUtils.import(extension.rootURI.resolve("myModule.jsm"));
Copied!
It is now mandatory to register an internal URL, for example a resource:// URL. We have created the ResourceUrl Experiment API, which is doing the heavy lifting. It is used by our Experiment API example.

NotificationBox.appendNotification()

The parameters have changed in TB 94.