diff --git a/chrome/content/api/LegacyPrefs/README.md b/chrome/content/api/LegacyPrefs/README.md
index 88d08af89..9bdf5e1c3 100644
--- a/chrome/content/api/LegacyPrefs/README.md
+++ b/chrome/content/api/LegacyPrefs/README.md
@@ -4,31 +4,46 @@ Use this API to access Thunderbird's system preferences or to migrate your own p
## Usage
-### API Functions
+Add the [LegacyPrefs API](https://github.com/thundernest/addon-developer-support/tree/master/auxiliary-apis/LegacyPrefs) to your add-on. Your `manifest.json` needs an entry like this:
+
+```
+ "experiment_apis": {
+ "LegacyPrefs": {
+ "schema": "api/LegacyPrefs/schema.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "paths": [["LegacyPrefs"]],
+ "script": "api/LegacyPrefs/implementation.js"
+ }
+ }
+ },
+```
+
+## API Functions
This API provides the following functions:
-#### async getPref(aName, [aFallback])
+### async getPref(aName, [aFallback])
Returns the value for the ``aName`` preference. If it is not defined or has no default value assigned, ``aFallback`` will be returned (which defaults to ``null``).
-#### async getUserPref(aName)
+### async getUserPref(aName)
Returns the user defined value for the ``aName`` preference. This will ignore any defined default value and will only return an explicitly set value, which differs from the default. Otherwise it will return ``null``.
-#### clearUserPref(aName)
+### clearUserPref(aName)
Clears the user defined value for preference ``aName``. Subsequent calls to ``getUserPref(aName)`` will return ``null``.
-#### async setPref(aName, aValue)
+### async setPref(aName, aValue)
Set the ``aName`` preference to the given value. Will return false and log an error to the console, if the type of ``aValue`` does not match the type of the preference.
-### API Events
+## API Events
This API provides the following events:
-#### onChanged.addListener(listener, branch)
+### onChanged.addListener(listener, branch)
Register a listener which is notified each time a value in the specified branch is changed. The listener returns the name and the new value of the changed preference.
diff --git a/chrome/content/api/LegacyPrefs/implementation.js b/chrome/content/api/LegacyPrefs/implementation.js
index 528688e4f..18372288f 100644
--- a/chrome/content/api/LegacyPrefs/implementation.js
+++ b/chrome/content/api/LegacyPrefs/implementation.js
@@ -2,28 +2,31 @@
* This file is provided by the addon-developer-support repository at
* https://github.com/thundernest/addon-developer-support
*
- * Version: 1.9
- * fixed fallback issue reported by Axel Grude
+ * Version 1.10
+ * - adjusted to Thunderbird Supernova (Services is now in globalThis)
*
- * Version: 1.8
- * reworked onChanged event to allow registering multiple branches
+ * Version 1.9
+ * - fixed fallback issue reported by Axel Grude
*
- * Version: 1.7
- * add onChanged event
+ * Version 1.8
+ * - reworked onChanged event to allow registering multiple branches
*
- * Version: 1.6
- * add setDefaultPref()
+ * Version 1.7
+ * - add onChanged event
*
- * Version: 1.5
- * replace set/getCharPref by set/getStringPref to fix encoding issue
+ * Version 1.6
+ * - add setDefaultPref()
*
- * Version: 1.4
+ * Version 1.5
+ * - replace set/getCharPref by set/getStringPref to fix encoding issue
+ *
+ * Version 1.4
* - setPref() function returns true if the value could be set, otherwise false
*
- * Version: 1.3
+ * Version 1.3
* - add setPref() function
*
- * Version: 1.2
+ * Version 1.2
* - add getPref() function
*
* Author: John Bieling (john@thunderbird.net)
@@ -39,10 +42,12 @@ var { ExtensionCommon } = ChromeUtils.import(
var { ExtensionUtils } = ChromeUtils.import(
"resource://gre/modules/ExtensionUtils.jsm"
);
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
var { ExtensionError } = ExtensionUtils;
+var Services = globalThis.Services ||
+ ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+
var LegacyPrefs = class extends ExtensionCommon.ExtensionAPI {
getAPI(context) {
diff --git a/chrome/content/api/NotifyTools/README.md b/chrome/content/api/NotifyTools/README.md
index 16c5653ac..0e4a6ef83 100644
--- a/chrome/content/api/NotifyTools/README.md
+++ b/chrome/content/api/NotifyTools/README.md
@@ -148,4 +148,4 @@ You must remove all added listeners when your add-on is disabled/reloaded. Inste
### setAddOnId(add-on-id)
-The `notifyTools.js` script needs to know the ID of your add-on to be able to listen for messages. You may either define the ID directly in the first line of the script, or set it using `setAddOnId()`.
\ No newline at end of file
+The `notifyTools.js` script needs to know the ID of your add-on to be able to listen for messages. You may either define the ID directly in the first line of the script, or set it using `setAddOnId()`.
diff --git a/chrome/content/api/NotifyTools/implementation.js b/chrome/content/api/NotifyTools/implementation.js
index 3159c2f28..e284298f9 100644
--- a/chrome/content/api/NotifyTools/implementation.js
+++ b/chrome/content/api/NotifyTools/implementation.js
@@ -2,6 +2,9 @@
* This file is provided by the addon-developer-support repository at
* https://github.com/thundernest/addon-developer-support
*
+ * Version 1.5
+ * - adjusted to Thunderbird Supernova (Services is now in globalThis)
+ *
* Version 1.4
* - updated implementation to not assign this anymore
*
@@ -26,7 +29,8 @@
// Get various parts of the WebExtension framework that we need.
var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
- var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ var Services = globalThis.Services ||
+ ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
var observerTracker = new Set();
diff --git a/chrome/content/api/NotifyTools/schema.json b/chrome/content/api/NotifyTools/schema.json
index 3d0e4e75f..b25bbc284 100644
--- a/chrome/content/api/NotifyTools/schema.json
+++ b/chrome/content/api/NotifyTools/schema.json
@@ -1,6 +1,6 @@
[
{
- "namespace": "NotifyTools",
+ "namespace": "NotifyTools",
"events": [
{
"name": "onNotifyBackground",
@@ -31,4 +31,4 @@
}
]
}
-]
+]
\ No newline at end of file
diff --git a/chrome/content/api/Utilities/implementation.js b/chrome/content/api/Utilities/implementation.js
index 6624acd9b..fcea9e67c 100644
--- a/chrome/content/api/Utilities/implementation.js
+++ b/chrome/content/api/Utilities/implementation.js
@@ -1,7 +1,6 @@
/* eslint-disable object-shorthand */
var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
// might be better to get the parent window of the current window
// because we may be screwed otherwise.
diff --git a/chrome/content/api/WindowListener/changelog.md b/chrome/content/api/WindowListener/changelog.md
index 0e7690a27..57054d320 100644
--- a/chrome/content/api/WindowListener/changelog.md
+++ b/chrome/content/api/WindowListener/changelog.md
@@ -1,3 +1,33 @@
+Version: 1.62
+-------------
+- fix bug in fullyLoaded()
+
+Version: 1.61
+-------------
+- adjusted to Thunderbird Supernova (Services is now in globalThis)
+
+Version: 1.60
+-------------
+- explicitly set hasAddonManagerEventListeners flag to false on uninstall
+
+Version: 1.59
+-------------
+- store hasAddonManagerEventListeners flag in add-on scope instead on the global
+ window again, and clear it upon add-on removal
+
+Version: 1.58
+-------------
+- hard fork WindowListener v1.57 implementation and continue to serve it for
+ Thunderbird 111 and older
+- WindowListener v1.58 supports injection into nested browsers of the new
+ mailTab front end of Thunderbird Supernova and allows "about:message" and
+ "about:3pane" to be valid injection targets. More information can be found here:
+ https://developer.thunderbird.net/thunderbird-development/codebase-overview/mail-front-end
+
+Version: 1.57
+-------------
+- fix race condition which could prevent the AOM tab to be monkey patched correctly
+
Version: 1.56
-------------
- be precise on which revision the wrench symbol should be displayed, instead of
diff --git a/chrome/content/api/WindowListener/implementation.js b/chrome/content/api/WindowListener/implementation.js
index 642f6b29f..9f29f441e 100644
--- a/chrome/content/api/WindowListener/implementation.js
+++ b/chrome/content/api/WindowListener/implementation.js
@@ -2,7 +2,7 @@
* This file is provided by the addon-developer-support repository at
* https://github.com/thundernest/addon-developer-support
*
- * Version: 1.56
+ * Version 1.62
*
* Author: John Bieling (john@thunderbird.net)
*
@@ -18,32 +18,32 @@ var { ExtensionCommon } = ChromeUtils.import(
var { ExtensionSupport } = ChromeUtils.import(
"resource:///modules/ExtensionSupport.jsm"
);
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var Services = globalThis.Services ||
+ ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+function getThunderbirdVersion() {
+ let parts = Services.appinfo.version.split(".");
+ return {
+ major: parseInt(parts[0]),
+ minor: parseInt(parts[1]),
+ }
+}
-var WindowListener = class extends ExtensionCommon.ExtensionAPI {
+var WindowListener_102 = class extends ExtensionCommon.ExtensionAPI {
log(msg) {
if (this.debug) console.log("WindowListener API: " + msg);
}
- getThunderbirdVersion() {
- let parts = Services.appinfo.version.split(".");
- return {
- major: parseInt(parts[0]),
- minor: parseInt(parts[1]),
- revision: parts.length > 2 ? parseInt(parts[2]) : 0,
- }
- }
-
getCards(e) {
// This gets triggered by real events but also manually by providing the outer window.
// The event is attached to the outer browser, get the inner one.
let doc;
// 78,86, and 87+ need special handholding. *Yeah*.
- if (this.getThunderbirdVersion().major < 86) {
+ if (getThunderbirdVersion().major < 86) {
let ownerDoc = e.document || e.target.ownerDocument;
doc = ownerDoc.getElementById("html-view-browser").contentDocument;
- } else if (this.getThunderbirdVersion().major < 87) {
+ } else if (getThunderbirdVersion().major < 87) {
let ownerDoc = e.document || e.target;
doc = ownerDoc.getElementById("html-view-browser").contentDocument;
} else {
@@ -67,11 +67,11 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
let name = this.extension.manifest.name;
let entry = icon
? event.target.ownerGlobal.MozXULElement.parseXULToFragment(
- ``
- )
+ ``
+ )
: event.target.ownerGlobal.MozXULElement.parseXULToFragment(
- ``
- );
+ ``
+ );
event.target.appendChild(entry);
let noPrefsElem = event.target.querySelector('[disabled="true"]');
@@ -133,10 +133,10 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
// Setup either the options entry in the menu or the button
//window.document.getElementById(id).addEventListener("command", function() {window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", WL)});
if (card.addon.id == this.extension.id) {
- let optionsMenu =
- (this.getThunderbirdVersion().major > 78 && this.getThunderbirdVersion().major < 88) ||
- (this.getThunderbirdVersion().major == 78 && this.getThunderbirdVersion().minor < 10) ||
- (this.getThunderbirdVersion().major == 78 && this.getThunderbirdVersion().minor == 10 && this.getThunderbirdVersion().revision < 2);
+ let optionsMenu =
+ (getThunderbirdVersion().major > 78 && getThunderbirdVersion().major < 88) ||
+ (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor < 10) ||
+ (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor == 10 && getThunderbirdVersion().revision < 2);
if (optionsMenu) {
// Options menu in 78.0-78.10 and 79-87
let addonOptionsLegacyEntry = card.querySelector(
@@ -200,7 +200,7 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
// returns the outer browser, not the nested browser of the add-on manager
// events must be attached to the outer browser
getAddonManagerFromTab(tab) {
- if (tab.browser) {
+ if (tab.browser && tab.mode.name == "contentTab") {
let win = tab.browser.contentWindow;
if (win && win.location.href == "about:addons") {
return win;
@@ -211,9 +211,28 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
getAddonManagerFromWindow(window) {
let tabMail = this.getTabMail(window);
for (let tab of tabMail.tabInfo) {
- let win = this.getAddonManagerFromTab(tab);
- if (win) {
- return win;
+ let managerWindow = this.getAddonManagerFromTab(tab);
+ if (managerWindow) {
+ return managerWindow;
+ }
+ }
+ }
+
+ async getAddonManagerFromWindowWaitForLoad(window) {
+ let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane");
+
+ let tabMail = this.getTabMail(window);
+ for (let tab of tabMail.tabInfo) {
+ if (tab.browser && tab.mode.name == "contentTab") {
+ // Instead of registering a load observer, wait until its loaded. Not nice,
+ // but gets aroud a lot of edge cases.
+ while(!tab.pageLoaded) {
+ await new Promise(r => setTimeout(r, 150));
+ }
+ let managerWindow = this.getAddonManagerFromTab(tab);
+ if (managerWindow) {
+ return managerWindow;
+ }
}
}
}
@@ -229,7 +248,7 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
)) {
managerWindow.document.addEventListener("ViewChanged", this);
managerWindow.document.addEventListener("update", this);
- managerWindow.document.addEventListener("view-loaded", this);
+ managerWindow.document.addEventListener("view-loaded", this);
managerWindow[this.uniqueRandomID] = {};
managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true;
}
@@ -329,41 +348,20 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
// TabMonitor to detect opening of tabs, to setup the options button in the add-on manager.
this.tabMonitor = {
- onTabTitleChanged(aTab) {},
- onTabClosing(aTab) {},
- onTabPersist(aTab) {},
- onTabRestored(aTab) {},
- onTabSwitched(aNewTab, aOldTab) {
- //self.setupAddonManager(self.getAddonManagerFromTab(aNewTab));
- },
- async onTabOpened(aTab) {
- if (aTab.browser) {
- if (!aTab.pageLoaded) {
- // await a location change if browser is not loaded yet
- await new Promise((resolve) => {
- let reporterListener = {
- QueryInterface: ChromeUtils.generateQI([
- "nsIWebProgressListener",
- "nsISupportsWeakReference",
- ]),
- onStateChange() {},
- onProgressChange() {},
- onLocationChange(
- /* in nsIWebProgress*/ aWebProgress,
- /* in nsIRequest*/ aRequest,
- /* in nsIURI*/ aLocation
- ) {
- aTab.browser.removeProgressListener(reporterListener);
- resolve();
- },
- onStatusChange() {},
- onSecurityChange() {},
- onContentBlockingEvent() {},
- };
- aTab.browser.addProgressListener(reporterListener);
- });
+ onTabTitleChanged(tab) { },
+ onTabClosing(tab) { },
+ onTabPersist(tab) { },
+ onTabRestored(tab) { },
+ onTabSwitched(aNewTab, aOldTab) { },
+ async onTabOpened(tab) {
+ if (tab.browser && tab.mode.name == "contentTab") {
+ let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane");
+ // Instead of registering a load observer, wait until its loaded. Not nice,
+ // but gets aroud a lot of edge cases.
+ while(!tab.pageLoaded) {
+ await new Promise(r => setTimeout(r, 150));
}
- self.setupAddonManager(self.getAddonManagerFromTab(aTab));
+ self.setupAddonManager(self.getAddonManagerFromTab(tab));
}
},
};
@@ -382,8 +380,8 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
aDocumentExistsAt(uriString) {
self.log(
"Checking if document at <" +
- uriString +
- "> used in registration actually exists."
+ uriString +
+ "> used in registration actually exists."
);
try {
let uriObject = Services.io.newURI(uriString);
@@ -405,9 +403,8 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
let url = context.extension.rootURI.resolve(defaultUrl);
let prefsObj = {};
- prefsObj.Services = ChromeUtils.import(
- "resource://gre/modules/Services.jsm"
- ).Services;
+ prefsObj.Services = globalThis.Services||
+ ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
prefsObj.pref = function (aName, aDefault) {
let defaults = Services.prefs.getDefaultBranch("");
switch (typeof aDefault) {
@@ -423,10 +420,10 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
default:
throw new Error(
"Preference <" +
- aName +
- "> has an unsupported type <" +
- typeof aDefault +
- ">. Allowed are string, number and boolean."
+ aName +
+ "> has an unsupported type <" +
+ typeof aDefault +
+ ">. Allowed are string, number and boolean."
);
}
};
@@ -475,7 +472,7 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
if (self.debug && !this.aDocumentExistsAt(windowHref)) {
self.error(
"Attempt to register an injector script for non-existent window: " +
- windowHref
+ windowHref
);
return;
}
@@ -538,8 +535,8 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
// delay startup until startup has been finished
self.log(
"Waiting for async startup() in <" +
- self.pathToStartupScript +
- "> to finish."
+ self.pathToStartupScript +
+ "> to finish."
);
if (startupJS.startup) {
await startupJS.startup();
@@ -580,12 +577,12 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
// Special action #1: If this is the main messenger window
if (
window.location.href ==
- "chrome://messenger/content/messenger.xul" ||
+ "chrome://messenger/content/messenger.xul" ||
window.location.href ==
- "chrome://messenger/content/messenger.xhtml"
+ "chrome://messenger/content/messenger.xhtml"
) {
if (self.pathToOptionsPage) {
- if (self.getThunderbirdVersion().major < 78) {
+ if (getThunderbirdVersion().major < 78) {
let element_addonPrefs = window.document.getElementById(
self.menu_addonPrefs_id
);
@@ -594,16 +591,14 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
self
);
} else {
- // Setup the options button/menu in the add-on manager, if it is already open.
- self.setupAddonManager(
- self.getAddonManagerFromWindow(window),
- true
- );
// Add a tabmonitor, to be able to setup the options button/menu in the add-on manager.
self
.getTabMail(window)
.registerTabMonitor(self.tabMonitor);
window[self.uniqueRandomID].hasTabMonitor = true;
+ // Setup the options button/menu in the add-on manager, if it is already open.
+ let managerWindow = await self.getAddonManagerFromWindowWaitForLoad(window);
+ self.setupAddonManager(managerWindow, true);
}
}
}
@@ -638,7 +633,7 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
if (
targetWindow &&
targetWindow.location.href ==
- mutation.target.getAttribute("src") &&
+ mutation.target.getAttribute("src") &&
targetWindow.document.readyState == "complete"
) {
loaded = true;
@@ -810,10 +805,10 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
if (debug)
console.log(
elements[i].tagName +
- "#" +
- elements[i].id +
- ": insertafter " +
- insertAfterElement.id
+ "#" +
+ elements[i].id +
+ ": insertafter " +
+ insertAfterElement.id
);
if (
debug &&
@@ -822,8 +817,8 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
) {
console.error(
"The id <" +
- elements[i].id +
- "> of the injected element already exists in the document!"
+ elements[i].id +
+ "> of the injected element already exists in the document!"
);
}
elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
@@ -842,10 +837,10 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
if (debug)
console.log(
elements[i].tagName +
- "#" +
- elements[i].id +
- ": insertbefore " +
- insertBeforeElement.id
+ "#" +
+ elements[i].id +
+ ": insertbefore " +
+ insertBeforeElement.id
);
if (
debug &&
@@ -854,8 +849,8 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
) {
console.error(
"The id <" +
- elements[i].id +
- "> of the injected element already exists in the document!"
+ elements[i].id +
+ "> of the injected element already exists in the document!"
);
}
elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
@@ -871,10 +866,10 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
if (debug)
console.log(
elements[i].tagName +
- "#" +
- elements[i].id +
- " is an existing container, injecting into " +
- elements[i].id
+ "#" +
+ elements[i].id +
+ " is an existing container, injecting into " +
+ elements[i].id
);
injectChildren(
Array.from(elements[i].children),
@@ -918,10 +913,10 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
if (debug)
console.log(
elements[i].tagName +
- "#" +
- elements[i].id +
- ": append to " +
- container.id
+ "#" +
+ elements[i].id +
+ ": append to " +
+ container.id
);
elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
container.appendChild(elements[i]);
@@ -1046,7 +1041,7 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
if (isAppShutdown) {
return; // the application gets unloaded anyway
}
-
+
// Unload from all still open windows
let urls = Object.keys(this.registeredWindows);
if (urls.length > 0) {
@@ -1056,9 +1051,9 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
this.pathToOptionsPage &&
(window.location.href == "chrome://messenger/content/messenger.xul" ||
window.location.href ==
- "chrome://messenger/content/messenger.xhtml")
+ "chrome://messenger/content/messenger.xhtml")
) {
- if (this.getThunderbirdVersion().major < 78) {
+ if (getThunderbirdVersion().major < 78) {
let element_addonPrefs = window.document.getElementById(
this.menu_addonPrefs_id
);
@@ -1086,9 +1081,10 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
managerWindow.document.removeEventListener("ViewChanged", this);
managerWindow.document.removeEventListener("view-loaded", this);
managerWindow.document.removeEventListener("update", this);
+ managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false;
let cards = this.getCards(managerWindow);
- if (this.getThunderbirdVersion().major < 88) {
+ if (getThunderbirdVersion().major < 88) {
// Remove options menu in 78-87
for (let card of cards) {
let addonOptionsLegacyEntry = card.querySelector(
@@ -1184,3 +1180,991 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
}
}
};
+
+var WindowListener_115 = class extends ExtensionCommon.ExtensionAPI {
+ log(msg) {
+ if (this.debug) console.log("WindowListener API: " + msg);
+ }
+
+ getCards(e) {
+ // This gets triggered by real events but also manually by providing the outer window.
+ // The event is attached to the outer browser, get the inner one.
+ let doc = e.document || e.target;
+ return doc.querySelectorAll("addon-card");
+ }
+
+
+ // Event handler for the addon manager, to update the state of the options button.
+ handleEvent(e) {
+ switch (e.type) {
+ case "click": {
+ e.preventDefault();
+ e.stopPropagation();
+ let WL = {};
+ WL.extension = this.extension;
+ WL.messenger = this.getMessenger(this.context);
+ let w = Services.wm.getMostRecentWindow("mail:3pane");
+ w.openDialog(
+ this.pathToOptionsPage,
+ "AddonOptions",
+ "chrome,resizable,centerscreen",
+ WL
+ );
+ }
+ break;
+
+ // update, ViewChanged and manual call for add-on manager options overlay
+ default: {
+ let cards = this.getCards(e);
+ for (let card of cards) {
+ // Setup either the options entry in the menu or the button
+ if (card.addon.id == this.extension.id) {
+ // Add-on button
+ let addonOptionsButton = card.querySelector(
+ ".windowlistener-options-button"
+ );
+ if (card.addon.isActive && !addonOptionsButton) {
+ let origAddonOptionsButton = card.querySelector(".extension-options-button")
+ origAddonOptionsButton.setAttribute("hidden", "true");
+
+ addonOptionsButton = card.ownerDocument.createElement("button");
+ addonOptionsButton.classList.add("windowlistener-options-button");
+ addonOptionsButton.classList.add("extension-options-button");
+ card.optionsButton.parentNode.insertBefore(
+ addonOptionsButton,
+ card.optionsButton
+ );
+ card
+ .querySelector(".windowlistener-options-button")
+ .addEventListener("click", this);
+ } else if (!card.addon.isActive && addonOptionsButton) {
+ addonOptionsButton.remove();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Some tab/add-on-manager related functions
+ getTabMail(window) {
+ return window.document.getElementById("tabmail");
+ }
+
+ // returns the outer browser, not the nested browser of the add-on manager
+ // events must be attached to the outer browser
+ getAddonManagerFromTab(tab) {
+ if (tab.browser) {
+ let win = tab.browser.contentWindow;
+ if (win && win.location.href == "about:addons") {
+ return win;
+ }
+ }
+ }
+
+ getAddonManagerFromWindow(window) {
+ let tabMail = this.getTabMail(window);
+ for (let tab of tabMail.tabInfo) {
+ let win = this.getAddonManagerFromTab(tab);
+ if (win) {
+ return win;
+ }
+ }
+ }
+
+ setupAddonManager(managerWindow, forceLoad = false) {
+ if (!managerWindow) {
+ return;
+ }
+ if (!this.pathToOptionsPage) {
+ return;
+ }
+ if (
+ managerWindow &&
+ managerWindow[this.uniqueRandomID] &&
+ managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners
+ ) {
+ return;
+ }
+
+ managerWindow.document.addEventListener("ViewChanged", this);
+ managerWindow.document.addEventListener("update", this);
+ managerWindow.document.addEventListener("view-loaded", this);
+ managerWindow[this.uniqueRandomID] = {};
+ managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true;
+ if (forceLoad) {
+ this.handleEvent(managerWindow);
+ }
+ }
+
+ getMessenger(context) {
+ let apis = ["storage", "runtime", "extension", "i18n"];
+
+ function getStorage() {
+ let localstorage = null;
+ try {
+ localstorage = context.apiCan.findAPIPath("storage");
+ localstorage.local.get = (...args) =>
+ localstorage.local.callMethodInParentProcess("get", args);
+ localstorage.local.set = (...args) =>
+ localstorage.local.callMethodInParentProcess("set", args);
+ localstorage.local.remove = (...args) =>
+ localstorage.local.callMethodInParentProcess("remove", args);
+ localstorage.local.clear = (...args) =>
+ localstorage.local.callMethodInParentProcess("clear", args);
+ } catch (e) {
+ console.info("Storage permission is missing");
+ }
+ return localstorage;
+ }
+
+ let messenger = {};
+ for (let api of apis) {
+ switch (api) {
+ case "storage":
+ XPCOMUtils.defineLazyGetter(messenger, "storage", () => getStorage());
+ break;
+
+ default:
+ XPCOMUtils.defineLazyGetter(messenger, api, () =>
+ context.apiCan.findAPIPath(api)
+ );
+ }
+ }
+ return messenger;
+ }
+
+ error(msg) {
+ if (this.debug) console.error("WindowListener API: " + msg);
+ }
+
+ // async sleep function using Promise
+ async sleep(delay) {
+ let timer = Components.classes["@mozilla.org/timer;1"].createInstance(
+ Components.interfaces.nsITimer
+ );
+ return new Promise(function (resolve, reject) {
+ let event = {
+ notify: function (timer) {
+ resolve();
+ },
+ };
+ timer.initWithCallback(
+ event,
+ delay,
+ Components.interfaces.nsITimer.TYPE_ONE_SHOT
+ );
+ });
+ }
+
+ getAPI(context) {
+ // Track if this is the background/main context
+ if (context.viewType != "background")
+ throw new Error(
+ "The WindowListener API may only be called from the background page."
+ );
+
+ this.context = context;
+
+ this.uniqueRandomID = "AddOnNS" + context.extension.instanceId;
+ this.menu_addonPrefs_id = "addonPrefs";
+
+ this.registeredWindows = {};
+ this.pathToStartupScript = null;
+ this.pathToShutdownScript = null;
+ this.pathToOptionsPage = null;
+ this.chromeHandle = null;
+ this.chromeData = null;
+ this.resourceData = null;
+ this.openWindows = [];
+ this.debug = context.extension.addonData.temporarilyInstalled;
+
+ const aomStartup = Cc[
+ "@mozilla.org/addons/addon-manager-startup;1"
+ ].getService(Ci.amIAddonManagerStartup);
+ const resProto = Cc[
+ "@mozilla.org/network/protocol;1?name=resource"
+ ].getService(Ci.nsISubstitutingProtocolHandler);
+
+ let self = this;
+
+ // TabMonitor to detect opening of tabs, to setup the options button in the add-on manager.
+ this.tabMonitor = {
+ onTabTitleChanged(aTab) { },
+ onTabClosing(aTab) { },
+ onTabPersist(aTab) { },
+ onTabRestored(aTab) { },
+ onTabSwitched(aNewTab, aOldTab) {
+ //self.setupAddonManager(self.getAddonManagerFromTab(aNewTab));
+ },
+ async onTabOpened(aTab) {
+ if (aTab.browser) {
+ if (!aTab.pageLoaded) {
+ // await a location change if browser is not loaded yet
+ await new Promise((resolve) => {
+ let reporterListener = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ onStateChange() { },
+ onProgressChange() { },
+ onLocationChange(
+ /* in nsIWebProgress*/ aWebProgress,
+ /* in nsIRequest*/ aRequest,
+ /* in nsIURI*/ aLocation
+ ) {
+ aTab.browser.removeProgressListener(reporterListener);
+ resolve();
+ },
+ onStatusChange() { },
+ onSecurityChange() { },
+ onContentBlockingEvent() { },
+ };
+ aTab.browser.addProgressListener(reporterListener);
+ });
+ }
+ self.setupAddonManager(self.getAddonManagerFromTab(aTab));
+ self._loadIntoWindow(aTab.browser.contentWindow, false);
+ }
+
+ if (aTab.chromeBrowser) {
+ self._loadIntoWindow(aTab.chromeBrowser.contentWindow, false);
+ }
+ },
+ };
+
+ return {
+ WindowListener: {
+ async waitForMasterPassword() {
+ // Wait until master password has been entered (if needed)
+ while (!Services.logins.isLoggedIn) {
+ self.log("Waiting for master password.");
+ await self.sleep(1000);
+ }
+ self.log("Master password has been entered.");
+ },
+
+ aDocumentExistsAt(uriString) {
+ // No sane way yet to detect if about:urls exists, maybe lookup the definition?
+ if (uriString.startsWith("about:")) {
+ return true;
+ }
+
+ self.log(
+ "Checking if document at <" +
+ uriString +
+ "> used in registration actually exists."
+ );
+ try {
+ let uriObject = Services.io.newURI(uriString);
+ let content = Cu.readUTF8URI(uriObject);
+ } catch (e) {
+ Components.utils.reportError(e);
+ return false;
+ }
+ return true;
+ },
+
+ registerOptionsPage(optionsUrl) {
+ self.pathToOptionsPage = optionsUrl.startsWith("chrome://")
+ ? optionsUrl
+ : context.extension.rootURI.resolve(optionsUrl);
+ },
+
+ registerDefaultPrefs(defaultUrl) {
+ let url = context.extension.rootURI.resolve(defaultUrl);
+
+ let prefsObj = {};
+ prefsObj.Services = globalThis.Services||
+ ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+ prefsObj.pref = function (aName, aDefault) {
+ let defaults = Services.prefs.getDefaultBranch("");
+ switch (typeof aDefault) {
+ case "string":
+ return defaults.setStringPref(aName, aDefault);
+
+ case "number":
+ return defaults.setIntPref(aName, aDefault);
+
+ case "boolean":
+ return defaults.setBoolPref(aName, aDefault);
+
+ default:
+ throw new Error(
+ "Preference <" +
+ aName +
+ "> has an unsupported type <" +
+ typeof aDefault +
+ ">. Allowed are string, number and boolean."
+ );
+ }
+ };
+ Services.scriptloader.loadSubScript(url, prefsObj, "UTF-8");
+ },
+
+ registerChromeUrl(data) {
+ let chromeData = [];
+ let resourceData = [];
+ for (let entry of data) {
+ if (entry[0] == "resource") resourceData.push(entry);
+ else chromeData.push(entry);
+ }
+
+ if (chromeData.length > 0) {
+ const manifestURI = Services.io.newURI(
+ "manifest.json",
+ null,
+ context.extension.rootURI
+ );
+ self.chromeHandle = aomStartup.registerChrome(
+ manifestURI,
+ chromeData
+ );
+ }
+
+ for (let res of resourceData) {
+ // [ "resource", "shortname" , "path" ]
+ let uri = Services.io.newURI(
+ res[2],
+ null,
+ context.extension.rootURI
+ );
+ resProto.setSubstitutionWithFlags(
+ res[1],
+ uri,
+ resProto.ALLOW_CONTENT_ACCESS
+ );
+ }
+
+ self.chromeData = chromeData;
+ self.resourceData = resourceData;
+ },
+
+ registerWindow(windowHref, jsFile) {
+ if (self.debug && !this.aDocumentExistsAt(windowHref)) {
+ self.error(
+ "Attempt to register an injector script for non-existent window: " +
+ windowHref
+ );
+ return;
+ }
+
+ if (!self.registeredWindows.hasOwnProperty(windowHref)) {
+ // path to JS file can either be chrome:// URL or a relative URL
+ let path = jsFile.startsWith("chrome://")
+ ? jsFile
+ : context.extension.rootURI.resolve(jsFile);
+
+ self.registeredWindows[windowHref] = path;
+ } else {
+ self.error(
+ "Window <" + windowHref + "> has already been registered"
+ );
+ }
+ },
+
+ registerStartupScript(aPath) {
+ self.pathToStartupScript = aPath.startsWith("chrome://")
+ ? aPath
+ : context.extension.rootURI.resolve(aPath);
+ },
+
+ registerShutdownScript(aPath) {
+ self.pathToShutdownScript = aPath.startsWith("chrome://")
+ ? aPath
+ : context.extension.rootURI.resolve(aPath);
+ },
+
+ openOptionsDialog(windowId) {
+ let window = context.extension.windowManager.get(windowId, context)
+ .window;
+ let WL = {};
+ WL.extension = self.extension;
+ WL.messenger = self.getMessenger(self.context);
+ window.openDialog(
+ self.pathToOptionsPage,
+ "AddonOptions",
+ "chrome,resizable,centerscreen",
+ WL
+ );
+ },
+
+ async startListening() {
+ // load the registered startup script, if one has been registered
+ // (mail3:pane may not have been fully loaded yet)
+ if (self.pathToStartupScript) {
+ let startupJS = {};
+ startupJS.WL = {};
+ startupJS.WL.extension = self.extension;
+ startupJS.WL.messenger = self.getMessenger(self.context);
+ try {
+ if (self.pathToStartupScript) {
+ Services.scriptloader.loadSubScript(
+ self.pathToStartupScript,
+ startupJS,
+ "UTF-8"
+ );
+ // delay startup until startup has been finished
+ self.log(
+ "Waiting for async startup() in <" +
+ self.pathToStartupScript +
+ "> to finish."
+ );
+ if (startupJS.startup) {
+ await startupJS.startup();
+ self.log(
+ "startup() in <" + self.pathToStartupScript + "> finished"
+ );
+ } else {
+ self.log(
+ "No startup() in <" + self.pathToStartupScript + "> found."
+ );
+ }
+ }
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+ let urls = Object.keys(self.registeredWindows);
+ if (urls.length > 0) {
+ // Before registering the window listener, check which windows are already open
+ self.openWindows = [];
+ for (let window of Services.wm.getEnumerator(null)) {
+ self.openWindows.push(window);
+ }
+
+ // Register window listener for all pre-registered windows
+ ExtensionSupport.registerWindowListener(
+ "injectListener_" + self.uniqueRandomID,
+ {
+ // React on all windows and manually reduce to the registered
+ // windows, so we can do special actions when the main
+ // messenger window is opened.
+ //chromeURLs: Object.keys(self.registeredWindows),
+ async onLoadWindow(window) {
+ // Load JS into window
+ await self._loadIntoWindow(
+ window,
+ self.openWindows.includes(window)
+ );
+ },
+
+ onUnloadWindow(window) {
+ // Remove JS from window, window is being closed, addon is not shut down
+ self._unloadFromWindow(window, false);
+ },
+ }
+ );
+ } else {
+ self.error("Failed to start listening, no windows registered");
+ }
+ },
+ },
+ };
+ }
+
+ _loadIntoNestedBrowsers(window, isAddonActivation) {
+ let elements = [];
+ elements = elements.concat(...window.document.getElementsByTagName("browser"));
+ elements = elements.concat(...window.document.getElementsByTagName("xul:browser"));
+ for (let element of elements) {
+ this._loadIntoWindow(element.contentWindow, isAddonActivation);
+ }
+
+ }
+
+ async _loadIntoWindow(window, isAddonActivation) {
+ const fullyLoaded = async window => {
+ for (let i = 0; i < 20; i++) {
+ await this.sleep(50);
+ if (
+ window &&
+ window.location.href != "about:blank" &&
+ window.document.readyState == "complete"
+ ) {
+ return;
+ }
+ }
+ throw new Error("Window ignored");
+ }
+
+ try {
+ await fullyLoaded(window);
+ } catch(ex) {
+ return;
+ }
+
+ if (!window || window.hasOwnProperty(this.uniqueRandomID)) {
+ return;
+ }
+
+ // Special action if this is the main messenger window.
+ if (window.location.href == "chrome://messenger/content/messenger.xhtml") {
+ // Add a tab monitor. The tabMonitor checks newly opened tabs and injects us.
+ this.getTabMail(window).registerTabMonitor(this.tabMonitor);
+ window[this.uniqueRandomID] = {};
+ window[this.uniqueRandomID].hasTabMonitor = true;
+
+ // Setup the options button/menu in the add-on manager, if it is already open.
+ this.setupAddonManager(this.getAddonManagerFromWindow(window), true);
+ }
+
+ // Load into nested browsers
+ this._loadIntoNestedBrowsers(window, isAddonActivation);
+
+ if (this.registeredWindows.hasOwnProperty(window.location.href)) {
+ if (!window.hasOwnProperty(this.uniqueRandomID)) {
+ window[this.uniqueRandomID] = {};
+ }
+
+ try {
+ let uniqueRandomID = this.uniqueRandomID;
+ let extension = this.extension;
+
+ // Add reference to window to add-on scope
+ window[this.uniqueRandomID].window = window;
+ window[this.uniqueRandomID].document = window.document;
+
+ // Keep track of toolbarpalettes we are injecting into
+ window[this.uniqueRandomID]._toolbarpalettes = {};
+
+ //Create WLDATA object
+ window[this.uniqueRandomID].WL = {};
+ window[this.uniqueRandomID].WL.scopeName = this.uniqueRandomID;
+
+ // Add helper function to inject CSS to WLDATA object
+ window[this.uniqueRandomID].WL.injectCSS = function (cssFile) {
+ let element;
+ let v = parseInt(Services.appinfo.version.split(".").shift());
+
+ // using createElementNS in TB78 delays the insert process and hides any security violation errors
+ if (v > 68) {
+ element = window.document.createElement("link");
+ } else {
+ let ns = window.document.documentElement.lookupNamespaceURI("html");
+ element = window.document.createElementNS(ns, "link");
+ }
+
+ element.setAttribute("wlapi_autoinjected", uniqueRandomID);
+ element.setAttribute("rel", "stylesheet");
+ element.setAttribute("href", cssFile);
+ return window.document.documentElement.appendChild(element);
+ };
+
+ // Add helper function to inject XUL to WLDATA object
+ window[this.uniqueRandomID].WL.injectElements = function (
+ xulString,
+ dtdFiles = [],
+ debug = false
+ ) {
+ let toolbarsToResolve = [];
+
+ function checkElements(stringOfIDs) {
+ let arrayOfIDs = stringOfIDs.split(",").map((e) => e.trim());
+ for (let id of arrayOfIDs) {
+ let element = window.document.getElementById(id);
+ if (element) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+ function localize(entity) {
+ let msg = entity.slice("__MSG_".length, -2);
+ return extension.localeData.localizeMessage(msg);
+ }
+
+ function injectChildren(elements, container) {
+ if (debug) console.log(elements);
+
+ for (let i = 0; i < elements.length; i++) {
+ // take care of persists
+ const uri = window.document.documentURI;
+ for (const persistentNode of elements[i].querySelectorAll(
+ "[persist]"
+ )) {
+ for (const persistentAttribute of persistentNode
+ .getAttribute("persist")
+ .trim()
+ .split(" ")) {
+ if (
+ Services.xulStore.hasValue(
+ uri,
+ persistentNode.id,
+ persistentAttribute
+ )
+ ) {
+ persistentNode.setAttribute(
+ persistentAttribute,
+ Services.xulStore.getValue(
+ uri,
+ persistentNode.id,
+ persistentAttribute
+ )
+ );
+ }
+ }
+ }
+
+ if (
+ elements[i].hasAttribute("insertafter") &&
+ checkElements(elements[i].getAttribute("insertafter"))
+ ) {
+ let insertAfterElement = checkElements(
+ elements[i].getAttribute("insertafter")
+ );
+
+ if (debug)
+ console.log(
+ elements[i].tagName +
+ "#" +
+ elements[i].id +
+ ": insertafter " +
+ insertAfterElement.id
+ );
+ if (
+ debug &&
+ elements[i].id &&
+ window.document.getElementById(elements[i].id)
+ ) {
+ console.error(
+ "The id <" +
+ elements[i].id +
+ "> of the injected element already exists in the document!"
+ );
+ }
+ elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
+ insertAfterElement.parentNode.insertBefore(
+ elements[i],
+ insertAfterElement.nextSibling
+ );
+ } else if (
+ elements[i].hasAttribute("insertbefore") &&
+ checkElements(elements[i].getAttribute("insertbefore"))
+ ) {
+ let insertBeforeElement = checkElements(
+ elements[i].getAttribute("insertbefore")
+ );
+
+ if (debug)
+ console.log(
+ elements[i].tagName +
+ "#" +
+ elements[i].id +
+ ": insertbefore " +
+ insertBeforeElement.id
+ );
+ if (
+ debug &&
+ elements[i].id &&
+ window.document.getElementById(elements[i].id)
+ ) {
+ console.error(
+ "The id <" +
+ elements[i].id +
+ "> of the injected element already exists in the document!"
+ );
+ }
+ elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
+ insertBeforeElement.parentNode.insertBefore(
+ elements[i],
+ insertBeforeElement
+ );
+ } else if (
+ elements[i].id &&
+ window.document.getElementById(elements[i].id)
+ ) {
+ // existing container match, dive into recursivly
+ if (debug)
+ console.log(
+ elements[i].tagName +
+ "#" +
+ elements[i].id +
+ " is an existing container, injecting into " +
+ elements[i].id
+ );
+ injectChildren(
+ Array.from(elements[i].children),
+ window.document.getElementById(elements[i].id)
+ );
+ } else if (elements[i].localName === "toolbarpalette") {
+ // These vanish from the document but still exist via the palette property
+ if (debug) console.log(elements[i].id + " is a toolbarpalette");
+ let boxes = [
+ ...window.document.getElementsByTagName("toolbox"),
+ ];
+ let box = boxes.find(
+ (box) => box.palette && box.palette.id === elements[i].id
+ );
+ let palette = box ? box.palette : null;
+
+ if (!palette) {
+ if (debug)
+ console.log(
+ `The palette for ${elements[i].id} could not be found, deferring to later`
+ );
+ continue;
+ }
+
+ if (debug)
+ console.log(`The toolbox for ${elements[i].id} is ${box.id}`);
+
+ toolbarsToResolve.push(...box.querySelectorAll("toolbar"));
+ toolbarsToResolve.push(
+ ...window.document.querySelectorAll(
+ `toolbar[toolboxid="${box.id}"]`
+ )
+ );
+ for (let child of elements[i].children) {
+ child.setAttribute("wlapi_autoinjected", uniqueRandomID);
+ }
+ window[uniqueRandomID]._toolbarpalettes[palette.id] = palette;
+ injectChildren(Array.from(elements[i].children), palette);
+ } else {
+ // append element to the current container
+ if (debug)
+ console.log(
+ elements[i].tagName +
+ "#" +
+ elements[i].id +
+ ": append to " +
+ container.id
+ );
+ elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
+ container.appendChild(elements[i]);
+ }
+ }
+ }
+
+ if (debug) console.log("Injecting into root document:");
+ let localizedXulString = xulString.replace(
+ /__MSG_(.*?)__/g,
+ localize
+ );
+ injectChildren(
+ Array.from(
+ window.MozXULElement.parseXULToFragment(
+ localizedXulString,
+ dtdFiles
+ ).children
+ ),
+ window.document.documentElement
+ );
+
+ for (let bar of toolbarsToResolve) {
+ let currentset = Services.xulStore.getValue(
+ window.location,
+ bar.id,
+ "currentset"
+ );
+ if (currentset) {
+ bar.currentSet = currentset;
+ } else if (bar.getAttribute("defaultset")) {
+ bar.currentSet = bar.getAttribute("defaultset");
+ }
+ }
+ };
+
+ // Add extension object to WLDATA object
+ window[this.uniqueRandomID].WL.extension = this.extension;
+ // Add messenger object to WLDATA object
+ window[this.uniqueRandomID].WL.messenger = this.getMessenger(
+ this.context
+ );
+ // Load script into add-on scope
+ Services.scriptloader.loadSubScript(
+ this.registeredWindows[window.location.href],
+ window[this.uniqueRandomID],
+ "UTF-8"
+ );
+ window[this.uniqueRandomID].onLoad(isAddonActivation);
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+ }
+
+ _unloadFromWindow(window, isAddonDeactivation) {
+ // Unload any contained browser elements.
+ let elements = [];
+ elements = elements.concat(...window.document.getElementsByTagName("browser"));
+ elements = elements.concat(...window.document.getElementsByTagName("xul:browser"));
+ for (let element of elements) {
+ if (element.contentWindow) {
+ this._unloadFromWindow(
+ element.contentWindow,
+ isAddonDeactivation
+ );
+ }
+ }
+
+ if (
+ window.hasOwnProperty(this.uniqueRandomID) &&
+ this.registeredWindows.hasOwnProperty(window.location.href)
+ ) {
+ // Remove this window from the list of open windows
+ this.openWindows = this.openWindows.filter((e) => e != window);
+
+ if (window[this.uniqueRandomID].onUnload) {
+ try {
+ // Call onUnload()
+ window[this.uniqueRandomID].onUnload(isAddonDeactivation);
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+ // Remove all auto injected objects
+ let elements = Array.from(
+ window.document.querySelectorAll(
+ '[wlapi_autoinjected="' + this.uniqueRandomID + '"]'
+ )
+ );
+ for (let element of elements) {
+ element.remove();
+ }
+
+ // Remove all autoinjected toolbarpalette items
+ for (const palette of Object.values(
+ window[this.uniqueRandomID]._toolbarpalettes
+ )) {
+ let elements = Array.from(
+ palette.querySelectorAll(
+ '[wlapi_autoinjected="' + this.uniqueRandomID + '"]'
+ )
+ );
+ for (let element of elements) {
+ element.remove();
+ }
+ }
+ }
+
+ // Remove add-on scope, if it exists
+ if (window.hasOwnProperty(this.uniqueRandomID)) {
+ delete window[this.uniqueRandomID];
+ }
+ }
+
+ onShutdown(isAppShutdown) {
+ if (isAppShutdown) {
+ return; // the application gets unloaded anyway
+ }
+
+ // Unload from all still open windows
+ let urls = Object.keys(this.registeredWindows);
+ if (urls.length > 0) {
+ for (let window of Services.wm.getEnumerator(null)) {
+ //remove our entry in the add-on options menu
+ if (window.location.href == "chrome://messenger/content/messenger.xhtml") {
+ // Remove event listener for addon manager view changes
+ let managerWindow = this.getAddonManagerFromWindow(window);
+ if (
+ managerWindow &&
+ managerWindow[this.uniqueRandomID] &&
+ managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners
+ ) {
+ managerWindow.document.removeEventListener("ViewChanged", this);
+ managerWindow.document.removeEventListener("view-loaded", this);
+ managerWindow.document.removeEventListener("update", this);
+ managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false;
+
+ let buttons = managerWindow.document.getElementsByClassName("extension-options-button");
+ for (let button of buttons) {
+ button.removeAttribute("hidden");
+ }
+ let cards = this.getCards(managerWindow);
+ // Remove options button in 88+
+ for (let card of cards) {
+ if (card.addon.id == this.extension.id) {
+ let origAddonOptionsButton = card.querySelector(".extension-options-button")
+ origAddonOptionsButton.removeAttribute("hidden");
+
+ let addonOptionsButton = card.querySelector(
+ ".windowlistener-options-button"
+ );
+ if (addonOptionsButton) addonOptionsButton.remove();
+ break;
+ }
+ }
+ }
+
+ // Remove tabmonitor
+ if (window[this.uniqueRandomID].hasTabMonitor) {
+ this.getTabMail(window).unregisterTabMonitor(this.tabMonitor);
+ window[this.uniqueRandomID].hasTabMonitor = false;
+ }
+ }
+
+ // if it is app shutdown, it is not just an add-on deactivation
+ this._unloadFromWindow(window, !isAppShutdown);
+ }
+ // Stop listening for new windows.
+ ExtensionSupport.unregisterWindowListener(
+ "injectListener_" + this.uniqueRandomID
+ );
+ }
+
+ // Load registered shutdown script
+ let shutdownJS = {};
+ shutdownJS.extension = this.extension;
+ try {
+ if (this.pathToShutdownScript)
+ Services.scriptloader.loadSubScript(
+ this.pathToShutdownScript,
+ shutdownJS,
+ "UTF-8"
+ );
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ // Extract all registered chrome content urls
+ let chromeUrls = [];
+ if (this.chromeData) {
+ for (let chromeEntry of this.chromeData) {
+ if (chromeEntry[0].toLowerCase().trim() == "content") {
+ chromeUrls.push("chrome://" + chromeEntry[1] + "/");
+ }
+ }
+ }
+
+ // Unload JSMs of this add-on
+ const rootURI = this.extension.rootURI.spec;
+ for (let module of Cu.loadedModules) {
+ if (
+ module.startsWith(rootURI) ||
+ (module.startsWith("chrome://") &&
+ chromeUrls.find((s) => module.startsWith(s)))
+ ) {
+ this.log("Unloading: " + module);
+ Cu.unload(module);
+ }
+ }
+
+ // Flush all caches
+ Services.obs.notifyObservers(null, "startupcache-invalidate");
+ this.registeredWindows = {};
+
+ if (this.resourceData) {
+ const resProto = Cc[
+ "@mozilla.org/network/protocol;1?name=resource"
+ ].getService(Ci.nsISubstitutingProtocolHandler);
+ for (let res of this.resourceData) {
+ // [ "resource", "shortname" , "path" ]
+ resProto.setSubstitution(res[1], null);
+ }
+ }
+
+ if (this.chromeHandle) {
+ this.chromeHandle.destruct();
+ this.chromeHandle = null;
+ }
+ }
+};
+
+var WindowListener = getThunderbirdVersion().major < 111
+ ? WindowListener_102
+ : WindowListener_115;
diff --git a/chrome/content/api/WindowListener/schema.json b/chrome/content/api/WindowListener/schema.json
index a33c705fa..5d3bbb28d 100644
--- a/chrome/content/api/WindowListener/schema.json
+++ b/chrome/content/api/WindowListener/schema.json
@@ -1,6 +1,6 @@
[
{
- "namespace": "WindowListener",
+ "namespace": "WindowListener",
"functions": [
{
"name": "registerDefaultPrefs",
@@ -34,7 +34,7 @@
"type": "array",
"items": {
"type": "array",
- "items" : {
+ "items": {
"type": "string"
}
},
@@ -105,4 +105,4 @@
}
]
}
-]
+]
\ No newline at end of file
diff --git a/chrome/content/help.js b/chrome/content/help.js
index 5f29fd441..be39fa380 100644
--- a/chrome/content/help.js
+++ b/chrome/content/help.js
@@ -1,5 +1,3 @@
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
function containerClick(el, evt) {
var code = evt.target;
diff --git a/chrome/content/scripts/notifyTools.js b/chrome/content/scripts/notifyTools.js
index f5e6afa27..7a48ed91b 100644
--- a/chrome/content/scripts/notifyTools.js
+++ b/chrome/content/scripts/notifyTools.js
@@ -1,4 +1,4 @@
-// Set this to the ID of your add-on.
+// Set this to the ID of your add-on, or call notifyTools.setAddonID().
var ADDON_ID = "smarttemplate4@thunderbird.extension";
/*
@@ -8,7 +8,17 @@ var ADDON_ID = "smarttemplate4@thunderbird.extension";
* For usage descriptions, please check:
* https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools
*
- * Version: 1.3
+ * Version 1.6
+ * - adjusted to Thunderbird Supernova (Services is now in globalThis)
+ *
+ * Version 1.5
+ * - deprecate enable(), disable() and registerListener()
+ * - add setAddOnId()
+ *
+ * Version 1.4
+ * - auto enable/disable
+ *
+ * Version 1.3
* - registered listeners for notifyExperiment can return a value
* - remove WindowListener from name of observer
*
@@ -19,21 +29,30 @@ var ADDON_ID = "smarttemplate4@thunderbird.extension";
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var Services = globalThis.Services ||
+ ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
var notifyTools = {
registeredCallbacks: {},
registeredCallbacksNextId: 1,
+ addOnId: ADDON_ID,
+
+ setAddOnId: function (addOnId) {
+ this.addOnId = addOnId;
+ },
onNotifyExperimentObserver: {
observe: async function (aSubject, aTopic, aData) {
- if (ADDON_ID == "") {
+ if (notifyTools.addOnId == "") {
throw new Error("notifyTools: ADDON_ID is empty!");
}
- if (aData != ADDON_ID) {
+ if (aData != notifyTools.addOnId) {
return;
}
let payload = aSubject.wrappedJSObject;
+
+ // Make sure payload has a resolve function, which we use to resolve the
+ // observer notification.
if (payload.resolve) {
let observerTrackerPromises = [];
// Push listener into promise array, so they can run in parallel
@@ -61,17 +80,26 @@ var notifyTools = {
payload.resolve(results[0]);
}
} else {
- // Just call the listener.
+ // Older version of NotifyTools, which is not sending a resolve function, deprecated.
+ console.log("Please update the notifyTools API to at least v1.5");
for (let registeredCallback of Object.values(
notifyTools.registeredCallbacks
)) {
registeredCallback(payload.data);
}
- }
+ }
},
},
- registerListener: function (listener) {
+ addListener: function (listener) {
+ if (Object.values(this.registeredCallbacks).length == 0) {
+ Services.obs.addObserver(
+ this.onNotifyExperimentObserver,
+ "NotifyExperimentObserver",
+ false
+ );
+ }
+
let id = this.registeredCallbacksNextId++;
this.registeredCallbacks[id] = listener;
return id;
@@ -79,50 +107,61 @@ var notifyTools = {
removeListener: function (id) {
delete this.registeredCallbacks[id];
+ if (Object.values(this.registeredCallbacks).length == 0) {
+ Services.obs.removeObserver(
+ this.onNotifyExperimentObserver,
+ "NotifyExperimentObserver"
+ );
+ }
+ },
+
+ removeAllListeners: function () {
+ if (Object.values(this.registeredCallbacks).length != 0) {
+ Services.obs.removeObserver(
+ this.onNotifyExperimentObserver,
+ "NotifyExperimentObserver"
+ );
+ }
+ this.registeredCallbacks = {};
},
notifyBackground: function (data) {
- if (ADDON_ID == "") {
+ if (this.addOnId == "") {
throw new Error("notifyTools: ADDON_ID is empty!");
}
return new Promise((resolve) => {
Services.obs.notifyObservers(
{ data, resolve },
"NotifyBackgroundObserver",
- ADDON_ID
+ this.addOnId
);
});
},
-
- enable: function() {
- Services.obs.addObserver(
- this.onNotifyExperimentObserver,
- "NotifyExperimentObserver",
- false
- );
+
+
+ // Deprecated.
+
+ enable: function () {
+ console.log("Manually calling notifyTools.enable() is no longer needed.");
+ },
+
+ disable: function () {
+ console.log("notifyTools.disable() has been deprecated, use notifyTools.removeAllListeners() instead.");
+ this.removeAllListeners();
},
- disable: function() {
- Services.obs.removeObserver(
- this.onNotifyExperimentObserver,
- "NotifyExperimentObserver"
- );
+ registerListener: function (listener) {
+ console.log("notifyTools.registerListener() has been deprecated, use notifyTools.addListener() instead.");
+ this.addListener(listener);
},
-};
+};
-if (window) {
+if (typeof window != "undefined" && window) {
window.addEventListener(
- "load",
+ "unload",
function (event) {
- notifyTools.enable();
- window.addEventListener(
- "unload",
- function (event) {
- notifyTools.disable();
- },
- false
- );
+ notifyTools.removeAllListeners();
},
false
);
diff --git a/chrome/content/scripts/st-am-adressing.js b/chrome/content/scripts/st-am-adressing.js
index 91e7bffeb..439d47bd0 100644
--- a/chrome/content/scripts/st-am-adressing.js
+++ b/chrome/content/scripts/st-am-adressing.js
@@ -1,7 +1,5 @@
// Likely Obsolete for THunderbird 78
// Web Extensions will probably not be allowed to modify Thunderbird Options. :(
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
Services.scriptloader.loadSubScript("chrome://smarttemplate4/content/smartTemplate-main.js", window, "UTF-8");
Services.scriptloader.loadSubScript("chrome://smarttemplate4/content/smartTemplate-accounts.js", window, "UTF-8");
diff --git a/chrome/content/scripts/st-composer.js b/chrome/content/scripts/st-composer.js
index 8b92b9158..b4ab6170f 100644
--- a/chrome/content/scripts/st-composer.js
+++ b/chrome/content/scripts/st-composer.js
@@ -1,5 +1,3 @@
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
//original lds this after xul!!
Services.scriptloader.loadSubScript("chrome://smarttemplate4/content/smartTemplate-main.js", window, "UTF-8");
diff --git a/chrome/content/scripts/st-messageWindow.js b/chrome/content/scripts/st-messageWindow.js
index fcf81d4ad..4d1bfb018 100644
--- a/chrome/content/scripts/st-messageWindow.js
+++ b/chrome/content/scripts/st-messageWindow.js
@@ -1,5 +1,3 @@
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
//original lds this after xul!!
Services.scriptloader.loadSubScript("chrome://smarttemplate4/content/smartTemplate-main.js", window, "UTF-8");
Services.scriptloader.loadSubScript("chrome://smarttemplate4/content/scripts/hackToolbarbutton.js", window.SmartTemplate4, "UTF-8");
diff --git a/chrome/content/scripts/st-messenger.js b/chrome/content/scripts/st-messenger.js
index 4ddbc3fbc..f05dff93d 100644
--- a/chrome/content/scripts/st-messenger.js
+++ b/chrome/content/scripts/st-messenger.js
@@ -1,6 +1,3 @@
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-
//original lds this after xul!!
Services.scriptloader.loadSubScript("chrome://smarttemplate4/content/smartTemplate-main.js", window, "UTF-8");
diff --git a/chrome/content/settings.js b/chrome/content/settings.js
index 008d3c0dc..ce05c0e31 100644
--- a/chrome/content/settings.js
+++ b/chrome/content/settings.js
@@ -9,7 +9,6 @@
END LICENSE BLOCK
*/
-var { Services } = ChromeUtils.import('resource://gre/modules/Services.jsm');
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
var LastInput = {
diff --git a/chrome/content/smartTemplate-compose.js b/chrome/content/smartTemplate-compose.js
index 983839605..4ce9938ec 100644
--- a/chrome/content/smartTemplate-compose.js
+++ b/chrome/content/smartTemplate-compose.js
@@ -9,9 +9,6 @@ BEGIN LICENSE BLOCK
END LICENSE BLOCK
*/
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-
// -------------------------------------------------------------------
// Insert template message and edit quote header
// -------------------------------------------------------------------
diff --git a/chrome/content/smartTemplate-composer.js b/chrome/content/smartTemplate-composer.js
index e950a0e81..6aedb4ae7 100644
--- a/chrome/content/smartTemplate-composer.js
+++ b/chrome/content/smartTemplate-composer.js
@@ -1,6 +1,5 @@
"use strict";
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
SmartTemplate4.composer = {
load: function st4_composerLoad() {
const Ci = Components.interfaces,
diff --git a/chrome/content/smartTemplate-fileTemplates.js b/chrome/content/smartTemplate-fileTemplates.js
index 679b5a571..c7ef16751 100644
--- a/chrome/content/smartTemplate-fileTemplates.js
+++ b/chrome/content/smartTemplate-fileTemplates.js
@@ -12,8 +12,6 @@
// Support external HTML files that can be selected during the button press
// write / reply and forward.
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
SmartTemplate4.fileTemplates = {
Entries: {
templatesNew : [],
diff --git a/chrome/content/smartTemplate-main.js b/chrome/content/smartTemplate-main.js
index cd4506fb3..518ea9874 100644
--- a/chrome/content/smartTemplate-main.js
+++ b/chrome/content/smartTemplate-main.js
@@ -275,8 +275,6 @@ END LICENSE BLOCK
*/
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
var SmartTemplate4 = {
// definitions for whatIsX (time of %A-Za-z%)
XisToday : 0,
diff --git a/chrome/content/smartTemplate-overlay.js b/chrome/content/smartTemplate-overlay.js
index a78a7fff9..4f3670238 100644
--- a/chrome/content/smartTemplate-overlay.js
+++ b/chrome/content/smartTemplate-overlay.js
@@ -9,7 +9,6 @@ BEGIN LICENSE BLOCK
END LICENSE BLOCK
*/
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
var { VCardProperties } = ChromeUtils.import( "resource:///modules/VCardUtils.jsm");
diff --git a/chrome/content/smartTemplate-prefs.js b/chrome/content/smartTemplate-prefs.js
index b349d1e2d..a61464321 100644
--- a/chrome/content/smartTemplate-prefs.js
+++ b/chrome/content/smartTemplate-prefs.js
@@ -9,8 +9,6 @@ BEGIN LICENSE BLOCK
END LICENSE BLOCK
*/
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
SmartTemplate4.Preferences = {
Prefix: "extensions.smartTemplate4.",
service: Services.prefs,
diff --git a/chrome/content/smartTemplate-util.js b/chrome/content/smartTemplate-util.js
index 4c470fc58..b0739cf00 100644
--- a/chrome/content/smartTemplate-util.js
+++ b/chrome/content/smartTemplate-util.js
@@ -10,7 +10,6 @@ BEGIN LICENSE BLOCK
END LICENSE BLOCK
*/
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
var SmartTemplate4_TabURIregexp = {
diff --git a/scripts/st-parser.mjs.js b/scripts/st-parser.mjs.js
index a3c69f45d..f3771008a 100644
--- a/scripts/st-parser.mjs.js
+++ b/scripts/st-parser.mjs.js
@@ -2235,8 +2235,6 @@ export class Parser {
}
}
catch(ex) {
- var { Services } = ChromeUtils.import('resource://gre/modules/Services.jsm');
-
Util.logException("FAILED: insertFileLink(" + txt + ") \n You may get more information if you enable debug mode.",ex );
Services.prompt.alert(null, "SmartTemplates", "Something went wrong trying to read a file: " + txt + "\n" +
"Please check Javascript error console for detailed error message.");