[Pkg-mozext-maintainers] Bug#1082111: bookworm-pu: package quicktext/5.16-1~deb12u1

Mechtilde Stehmann mechtilde at debian.org
Wed Sep 18 14:37:21 BST 2024


Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: quicktext at packages.debian.org, mechtilde at debian.org
Control: affects -1 + src:quicktext
User: release.debian.org at packages.debian.org
Usertags: pu

[ Reason ]
Thunderbird will come with a new version (>=128.2.x) into stable. This need an
update for the Add-Ons (here: quicktext) too

[ Impact ]
If the update isn't approved the user can't anymore use their autotext in
recent thunderbird

[ Tests ]
The same upstream code works with thunderbird >= 128.2 in testing.

[ Risks ]
code is trivial so no risk

[ Checklist ]
  [X] *all* changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in (old)stable
  [X] the issue is verified as fixed in unstable

[ Changes ]
The new version of thunderbird needs a new version of webext-quicktext and
there
dependencies

[ Other info ]
The only reason is the new upcomming version of the thunderbird.
-------------- next part --------------
diffstat for quicktext-5.2 quicktext-5.16

 CONTRIBUTORS.md                             |    1 
 _locales/ja/messages.json                   |    2 
 api/LegacyPrefs/README.md                   |   35 +
 api/LegacyPrefs/implementation.js           |  183 ++++---
 api/LegacyPrefs/schema.json                 |   22 
 api/NotifyTools/README.md                   |   62 ++
 api/NotifyTools/implementation.js           |  157 +++---
 api/NotifyTools/schema.json                 |    4 
 api/WindowListener/CHANGELOG.md             |   35 +
 api/WindowListener/implementation.js        |  673 ++++++++--------------------
 api/WindowListener/schema.json              |   45 -
 background.js                               |    1 
 chrome/content/modules/utils.jsm            |    2 
 chrome/content/modules/wzQuicktext.jsm      |   55 +-
 chrome/content/modules/wzQuicktextGroup.jsm |   23 
 chrome/content/modules/wzQuicktextVar.jsm   |  199 +++-----
 chrome/content/notifyTools/notifyTools.js   |  102 ++--
 chrome/content/quicktext.js                 |   43 -
 chrome/content/scripts/messenger.js         |    2 
 chrome/content/scripts/messengercompose.js  |   33 -
 chrome/content/settings.js                  |   11 
 chrome/locale/ja/quicktext.dtd              |   66 +-
 chrome/locale/ja/quicktext.properties       |   10 
 chrome/skin/quicktext.css                   |   29 -
 debian/changelog                            |   39 +
 debian/control                              |    5 
 manifest.json                               |   13 
 27 files changed, 860 insertions(+), 992 deletions(-)

diff -Nru quicktext-5.2/api/LegacyPrefs/implementation.js quicktext-5.16/api/LegacyPrefs/implementation.js
--- quicktext-5.2/api/LegacyPrefs/implementation.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/api/LegacyPrefs/implementation.js	2024-07-26 13:08:57.000000000 +0200
@@ -2,22 +2,31 @@
  * This file is provided by the addon-developer-support repository at
  * https://github.com/thundernest/addon-developer-support
  *
- * Version: 1.7
- * add onChanged event
+ * Version 1.10
+ * - adjusted to Thunderbird Supernova (Services is now in globalThis)
  *
- * Version: 1.6
- * add setDefaultPref()
+ * Version 1.9
+ * - fixed fallback issue reported by Axel Grude
  *
- * Version: 1.5
- * replace set/getCharPref by set/getStringPref to fix encoding issue
+ * Version 1.8
+ * - reworked onChanged event to allow registering multiple branches
  *
- * Version: 1.4
+ * Version 1.7
+ * - add onChanged event
+ *
+ * Version 1.6
+ * - add setDefaultPref()
+ *
+ * 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 at thunderbird.net)
@@ -30,111 +39,119 @@
 var { ExtensionCommon } = ChromeUtils.import(
   "resource://gre/modules/ExtensionCommon.jsm"
 );
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { ExtensionUtils } = ChromeUtils.import(
+  "resource://gre/modules/ExtensionUtils.jsm"
+);
+var { ExtensionError } = ExtensionUtils;
 
 var LegacyPrefs = class extends ExtensionCommon.ExtensionAPI {
   getAPI(context) {
-    let self = this;
 
-    this.getLegacyPref = async function (
-      aName,
-      aFallback = null,
-      userPrefOnly = true
-    ) {
-      let prefType = Services.prefs.getPrefType(aName);
-      if (prefType == Services.prefs.PREF_INVALID) {
-        return aFallback;
-      }
-
-      let value = aFallback;
-      if (!userPrefOnly || Services.prefs.prefHasUserValue(aName)) {
-        switch (prefType) {
-          case Services.prefs.PREF_STRING:
-            value = Services.prefs.getStringPref(aName, aFallback);
-            break;
-
-          case Services.prefs.PREF_INT:
-            value = Services.prefs.getIntPref(aName, aFallback);
-            break;
-
-          case Services.prefs.PREF_BOOL:
-            value = Services.prefs.getBoolPref(aName, aFallback);
-            break;
-
-          default:
-            console.error(
-              `Legacy preference <${aName}> has an unknown type of <${prefType}>.`
-            );
-        }
+    class LegacyPrefsManager {
+      constructor() {
+        this.observedBranches = new Map();
+        this.QueryInterface = ChromeUtils.generateQI([
+          "nsIObserver",
+          "nsISupportsWeakReference",
+        ])
+      }
+
+      addObservedBranch(branch, fire) {
+        return this.observedBranches.set(branch, fire);
+      }
+
+      hasObservedBranch(branch) {
+        return this.observedBranches.has(branch);
+      }
+
+      removeObservedBranch(branch) {
+        return this.observedBranches.delete(branch);
       }
-      return value;
-    };
 
-    this.observerTracker = null;
-    this.observing = false;
-    this.observedBranch = null;
-    this.observer = {
       async observe(aSubject, aTopic, aData) {
         if (aTopic == "nsPref:changed") {
-          let name = aData.substr(self.observedBranch.length);
-          let value = await self.getLegacyPref(aData);
-          self.observerTracker(name, value);
+          let branch = [...this.observedBranches.keys()]
+            .reduce(
+              (p, c) => aData.startsWith(c) && (!p || c.length > p.length) ? c : p,
+              null
+            );
+          if (branch) {
+            let name = aData.substr(branch.length);
+            let value = await this.getLegacyPref(aData);
+            let fire = this.observedBranches.get(branch);
+            fire(name, value);
+          }
         }
-      },
-      QueryInterface: ChromeUtils.generateQI([
-        "nsIObserver",
-        "nsISupportsWeakReference",
-      ]),
-    };
+      }
+
+      async getLegacyPref(
+        aName,
+        aFallback = null,
+        userPrefOnly = true
+      ) {
+        let prefType = Services.prefs.getPrefType(aName);
+        if (prefType == Services.prefs.PREF_INVALID) {
+          return aFallback;
+        }
+
+        let value = aFallback;
+        if (!userPrefOnly || Services.prefs.prefHasUserValue(aName)) {
+          switch (prefType) {
+            case Services.prefs.PREF_STRING:
+              value = Services.prefs.getStringPref(aName, aFallback);
+              break;
+
+            case Services.prefs.PREF_INT:
+              value = Services.prefs.getIntPref(aName, aFallback);
+              break;
+
+            case Services.prefs.PREF_BOOL:
+              value = Services.prefs.getBoolPref(aName, aFallback);
+              break;
+
+            default:
+              console.error(
+                `Legacy preference <${aName}> has an unknown type of <${prefType}>.`
+              );
+          }
+        }
+        return value;
+      }
+    }
+
+    let legacyPrefsManager = new LegacyPrefsManager();
 
     return {
       LegacyPrefs: {
         onChanged: new ExtensionCommon.EventManager({
           context,
           name: "LegacyPrefs.onChanged",
-          register: (fire) => {
-            if (self.observing)
-              throw new Error("Only one onChanged observer allowed.");
-
-            if (!self.observedBranch)
-              throw new Error(
-                "Please call setObservingBranch() before using the onChanged event"
-              );
-
-            self.observing = true;
-            self.observerTracker = fire.sync;
+          register: (fire, branch) => {
+            if (legacyPrefsManager.hasObservedBranch(branch)) {
+              throw new ExtensionError(`Cannot add more than one listener for branch "${branch}".`)
+            }
+            legacyPrefsManager.addObservedBranch(branch, fire.sync);
             Services.prefs
               .getBranch(null)
-              .addObserver(self.observedBranch, self.observer);
+              .addObserver(branch, legacyPrefsManager);
             return () => {
               Services.prefs
                 .getBranch(null)
-                .removeObserver(this.observedBranch, this.observer);
-              self.observerTracker = null;
-              self.observing = false;
+                .removeObserver(branch, legacyPrefsManager);
+              legacyPrefsManager.removeObservedBranch(branch);
             };
           },
         }).api(),
 
-        setObservingBranch: function (observeBranch) {
-          // This implementation only supports one observer
-          if (self.observing)
-            throw new Error(
-              "Already observing, cannot change observed branch."
-            );
-          if (!observeBranch) throw new Error("Invalid branch value.");
-          self.observedBranch = observeBranch;
-        },
-
         // only returns something, if a user pref value is set
         getUserPref: async function (aName) {
-          return await self.getLegacyPref(aName);
+          return await legacyPrefsManager.getLegacyPref(aName);
         },
 
         // returns the default value, if no user defined value exists,
         // and returns the fallback value, if the preference does not exist
         getPref: async function (aName, aFallback = null) {
-          return await self.getLegacyPref(aName, aFallback, false);
+          return await legacyPrefsManager.getLegacyPref(aName, aFallback, false);
         },
 
         clearUserPref: function (aName) {
diff -Nru quicktext-5.2/api/LegacyPrefs/README.md quicktext-5.16/api/LegacyPrefs/README.md
--- quicktext-5.2/api/LegacyPrefs/README.md	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/api/LegacyPrefs/README.md	2024-07-26 13:08:57.000000000 +0200
@@ -4,7 +4,24 @@
 
 ## Usage
 
-This API provides the following methods:
+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])
 
@@ -22,6 +39,22 @@
 
 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
+
+This API provides the following events:
+
+### 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.
+
+Example:
+
+```
+browser.LegacyPrefs.onChanged.addListener(async (name, value) => {
+  console.log(`Changed value in "mailnews.": ${name} = ${value}`);
+}, "mailnews.");
+```
+
 ---
 
 A detailed example using the LegacyPref API to migrate add-on preferences to the local storage can be found in [/scripts/preferences/](https://github.com/thundernest/addon-developer-support/tree/master/scripts/preferences).
diff -Nru quicktext-5.2/api/LegacyPrefs/schema.json quicktext-5.16/api/LegacyPrefs/schema.json
--- quicktext-5.2/api/LegacyPrefs/schema.json	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/api/LegacyPrefs/schema.json	2024-07-26 13:08:57.000000000 +0200
@@ -17,24 +17,18 @@
             "type": "any",
             "description": "Value of the preference."
           }
+        ],
+        "extraParameters": [
+          {
+            "name": "branch",
+            "description": "The branch to observe.",
+            "type": "string"
+          }
         ]
       }
     ],
     "functions": [
       {
-        "name": "setObservingBranch",
-        "type": "function",
-        "async": true,
-        "description": "Set the branch which onChanged should be sensitive for.",
-        "parameters": [
-          {
-            "name": "aBranch",
-            "type": "string",
-            "description": "Name of the branch to observe."
-          }
-        ]
-      },
-      {
         "name": "getUserPref",
         "type": "function",
         "async": true,
@@ -60,7 +54,7 @@
           },
           {
             "name": "aFallback",
-            "type": "string",
+            "type": "any",
             "description": "Value to be returned, if the requested preference does not exist.",
             "optional": true,
             "default": null
diff -Nru quicktext-5.2/api/NotifyTools/implementation.js quicktext-5.16/api/NotifyTools/implementation.js
--- quicktext-5.2/api/NotifyTools/implementation.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/api/NotifyTools/implementation.js	2024-07-26 13:08:57.000000000 +0200
@@ -2,6 +2,20 @@
  * 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
+ * 
+ * Version 1.3
+ *  - moved registering the observer into startup
+ *
+ * Version 1.1
+ *  - added startup event, to make sure API is ready as soon as the add-on is starting
+ *    NOTE: This requires to add the startup event to the manifest, see:
+ *    https://github.com/thundernest/addon-developer-support/tree/master/auxiliary-apis/NotifyTools#usage
+ *
  * Author: John Bieling (john at thunderbird.net)
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
@@ -9,28 +23,61 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-// Get various parts of the WebExtension framework that we need.
-var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
-var { ExtensionSupport } = ChromeUtils.import("resource:///modules/ExtensionSupport.jsm");
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-var NotifyTools = class extends ExtensionCommon.ExtensionAPI {
-  getAPI(context) {
-    var self = this;
+"use strict";
+
+(function (exports) {
+
+  // Get various parts of the WebExtension framework that we need.
+  var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
+
+  var observerTracker = new Set();
+
+  class NotifyTools extends ExtensionCommon.ExtensionAPI {
+    getAPI(context) {
+      return {
+        NotifyTools: {
+
+          notifyExperiment(data) {
+            return new Promise(resolve => {
+              Services.obs.notifyObservers(
+                { data, resolve },
+                "NotifyExperimentObserver",
+                context.extension.id
+              );
+            });
+          },
+
+          onNotifyBackground: new ExtensionCommon.EventManager({
+            context,
+            name: "NotifyTools.onNotifyBackground",
+            register: (fire) => {
+              observerTracker.add(fire.sync);
+              return () => {
+                observerTracker.delete(fire.sync);
+              };
+            },
+          }).api(),
 
-    this.onNotifyBackgroundObserver = {
-      observe: async function (aSubject, aTopic, aData) {
+        }
+      };
+    }
+
+    // Force API to run at startup, otherwise event listeners might not be added at the requested time. Also needs
+    // "events": ["startup"] in the experiment manifest
+    onStartup() {
+      this.onNotifyBackgroundObserver = async (aSubject, aTopic, aData) => {
         if (
-          Object.keys(self.observerTracker).length > 0 &&
-          aData == self.extension.id
+          observerTracker.size > 0 &&
+          aData == this.extension.id
         ) {
           let payload = aSubject.wrappedJSObject;
-          // This is called from the BL observer.js and therefore it should have a resolve
-          // payload, but better check.
+
+          // 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
-            for (let listener of Object.values(self.observerTracker)) {
+            for (let listener of observerTracker.values()) {
               observerTrackerPromises.push(listener(payload.data));
             }
             // We still have to await all of them but wait time is just the time needed
@@ -52,65 +99,39 @@
               payload.resolve(results[0]);
             }
           } else {
-            // Just call the listener.
-            for (let listener of Object.values(self.observerTracker)) {
+            // Older version of NotifyTools, which is not sending a resolve function, deprecated.
+            console.log("Please update the notifyTools API and the notifyTools script to at least v1.5");
+            for (let listener of observerTracker.values()) {
               listener(payload.data);
             }
           }
         }
-      },
-    };
+      };
 
-    this.observerTracker = {};
-    this.observerTrackerNext = 1;
-    // Add observer for notifyTools.js
-    Services.obs.addObserver(
-      this.onNotifyBackgroundObserver,
-      "NotifyBackgroundObserver",
-      false
-    );
-    
-    return {
-      NotifyTools: {
-
-        notifyExperiment(data) {
-          return new Promise(resolve => {
-            Services.obs.notifyObservers(
-              { data, resolve },
-              "NotifyExperimentObserver",
-              self.extension.id
-            );
-          });
-        },
-
-        onNotifyBackground: new ExtensionCommon.EventManager({
-          context,
-          name: "NotifyTools.onNotifyBackground",
-          register: (fire) => {
-            let trackerId = self.observerTrackerNext++;
-            self.observerTracker[trackerId] = fire.sync;
-            return () => {
-              delete self.observerTracker[trackerId];
-            };
-          },
-        }).api(),
+      // Add observer for notifyTools.js
+      Services.obs.addObserver(
+        this.onNotifyBackgroundObserver,
+        "NotifyBackgroundObserver",
+        false
+      );
+    }
 
+    onShutdown(isAppShutdown) {
+      if (isAppShutdown) {
+        return; // the application gets unloaded anyway
       }
-    };
-  }
 
-  onShutdown(isAppShutdown) {
-    if (isAppShutdown) {
-      return; // the application gets unloaded anyway
+      // Remove observer for notifyTools.js
+      Services.obs.removeObserver(
+        this.onNotifyBackgroundObserver,
+        "NotifyBackgroundObserver"
+      );
+
+      // Flush all caches
+      Services.obs.notifyObservers(null, "startupcache-invalidate");
     }
-    
-    // Remove observer for notifyTools.js
-    Services.obs.removeObserver(
-      this.onNotifyBackgroundObserver,
-      "NotifyBackgroundObserver"
-    );
-
-    // Flush all caches
-    Services.obs.notifyObservers(null, "startupcache-invalidate");
-  }
-};
+  };
+
+  exports.NotifyTools = NotifyTools;
+
+})(this)
diff -Nru quicktext-5.2/api/NotifyTools/README.md quicktext-5.16/api/NotifyTools/README.md
--- quicktext-5.2/api/NotifyTools/README.md	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/api/NotifyTools/README.md	2024-07-26 13:08:57.000000000 +0200
@@ -10,6 +10,10 @@
 
 More details can be found in [this update tutorial](https://github.com/thundernest/addon-developer-support/wiki/Tutorial:-Convert-add-on-parts-individually-by-using-a-messaging-system).
 
+# Example
+
+This repository includes the [NotifyToolsExample Add-On](https://github.com/thundernest/addon-developer-support/raw/master/auxiliary-apis/NotifyTools/notifyToolsExample.zip), showcasing how the NotifyTools can be used.
+
 # Usage
 
 Add the [NotifyTools API](https://github.com/thundernest/addon-developer-support/tree/master/auxiliary-apis/NotifyTools) to your add-on. Your `manifest.json` needs an entry like this:
@@ -21,7 +25,8 @@
       "parent": {
         "scopes": ["addon_parent"],
         "paths": [["NotifyTools"]],
-        "script": "api/NotifyTools/implementation.js"
+        "script": "api/NotifyTools/implementation.js",
+        "events": ["startup"]
       }
     }
   },
@@ -55,7 +60,7 @@
 });
 ```
 
-Include the [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script in your Experiment script to be able to use `notifyTools.notifyBackground()`.
+Include the [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script in your Experiment script to be able to use `notifyTools.notifyBackground()`. If you are injecting the script into a global Thunderbird window object, make sure to wrap it in your custom namespace, to prevent clashes with other add-ons.
 
 **Note**: If multiple `onNotifyBackground` listeners are registered in the WebExtension's background page and more than one is returning data, the value
 from the first one is returned to the Experiment. This may lead to inconsistent behavior, so make sure that for each
@@ -74,9 +79,9 @@
 
 The receiving Experiment script needs to include the [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script  and must setup a listener using the following methods:
 
-### registerListener(callback);
+### addListener(callback);
 
-Registers a callback function, which is called when a notification from the WebExtension's background page has been received. The `registerListener()` function returns an `id` which can be used to remove the listener again.
+Adds a callback function, which is called when a notification from the WebExtension's background page has been received. The `addListener()` function returns an `id` which can be used to remove the listener again.
 
 Example:
 
@@ -85,8 +90,47 @@
   console.log(data);
   return true;
 }
-let id = notifyTools.registerListener(doSomething);
+let id = notifyTools.addListener(doSomething);
+```
+
+**Note**: NotifyTools currently is not 100% compatible with the behavior of
+runtime.sendMessage. While runtime messaging is ignoring non-Promise return
+values, NotifyTools only ignores `null`.
+
+Why does this matter? Consider the following three listeners:
+ 
+```
+async function dominant_listener(data) {
+ if (data.type == "A") {
+   return { msg: "I should answer only type A" };
+ }
+}
+ 
+function silent_listener(data) {
+ if (data.type == "B") {
+   return { msg: "I should answer only type B" };
+ }
+}
+
+function selective_listener(data) {
+ if (data.type == "C") {
+   return Promise.resolve({ msg: "I should answer only type C" });
+ }
+}
 ```
+ 
+When all 3 listeners are registered for the runtime.onMessage event,
+the dominant listener will always respond, even for `data.type != "A"` requests,
+because it is always returning a Promise (it is an async function). The return
+value of the silent listener is ignored, and the selective listener returns a
+value just for `data.type == "C"`. But since the dominant listener also returns
+`null` for these requests, the actual return value depends on which listener is faster
+and/or was registered first.
+ 
+All notifyTools listener however ignore only `null` return values (so `null` can
+actually never be returned). The above dominant listener will only respond to 
+`type == "A"` requests, the silent listener will only respond to `type == "B"` 
+requests and the selective listener will respond only to `type == "C"` requests.
 
 ### removeListener(id)
 
@@ -98,10 +142,10 @@
 notifyTools.removeListener(id);
 ```
 
-### enable()
+### removeAllListeners()
 
-The [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script attaches its `enable()` method to the `load` event of the current window. If the script is loaded into a window-less environment, `enable()` needs to be called manually.
+You must remove all added listeners when your add-on is disabled/reloaded. Instead of calling `removeListener()` for each added listener, you may call `removeAllListeners()`.
 
-### disable()
+### setAddOnId(add-on-id)
 
-The [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script attaches its `disable()` method to the `unload` event of the current window. If the script is loaded into a window-less environment, `disable()` needs to be called manually.
+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()`.
\ Kein Zeilenumbruch am Dateiende.
diff -Nru quicktext-5.2/api/NotifyTools/schema.json quicktext-5.16/api/NotifyTools/schema.json
--- quicktext-5.2/api/NotifyTools/schema.json	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/api/NotifyTools/schema.json	2024-07-26 13:08:57.000000000 +0200
@@ -1,6 +1,6 @@
 [
   {
-    "namespace": "NotifyTools",    
+    "namespace": "NotifyTools",
     "events": [
       {
         "name": "onNotifyBackground",
@@ -31,4 +31,4 @@
       }
     ]
   }
-]
+]
\ Kein Zeilenumbruch am Dateiende.
diff -Nru quicktext-5.2/api/WindowListener/CHANGELOG.md quicktext-5.16/api/WindowListener/CHANGELOG.md
--- quicktext-5.2/api/WindowListener/CHANGELOG.md	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/api/WindowListener/CHANGELOG.md	2024-07-26 13:08:57.000000000 +0200
@@ -1,3 +1,38 @@
+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
+  the options button
+
 Version: 1.54
 -------------
 - fix "ownerDoc.getElementById() is undefined" bug
diff -Nru quicktext-5.2/api/WindowListener/implementation.js quicktext-5.16/api/WindowListener/implementation.js
--- quicktext-5.2/api/WindowListener/implementation.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/api/WindowListener/implementation.js	2024-07-26 13:08:57.000000000 +0200
@@ -2,8 +2,6 @@
  * This file is provided by the addon-developer-support repository at
  * https://github.com/thundernest/addon-developer-support
  *
- * Version: 1.54
- *
  * Author: John Bieling (john at thunderbird.net)
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
@@ -18,111 +16,38 @@
 var { ExtensionSupport } = ChromeUtils.import(
   "resource:///modules/ExtensionSupport.jsm"
 );
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
+// Only works in Thunderbird 115+
 var WindowListener = 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]),
-    }
-  }
-
   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) {
-      let ownerDoc = e.document || e.target.ownerDocument;
-      doc = ownerDoc.getElementById("html-view-browser").contentDocument;
-    } else if (this.getThunderbirdVersion().major < 87) {
-      let ownerDoc = e.document || e.target;
-      doc = ownerDoc.getElementById("html-view-browser").contentDocument;
-    } else {
-      doc = e.document || e.target;
-    }
+    let doc = e.document || e.target;
     return doc.querySelectorAll("addon-card");
   }
 
-  // Add pref entry to 68
-  add68PrefsEntry(event) {
-    let id = this.menu_addonPrefs_id + "_" + this.uniqueRandomID;
-
-    // Get the best size of the icon (16px or bigger)
-    let iconSizes = this.extension.manifest.icons
-      ? Object.keys(this.extension.manifest.icons)
-      : [];
-    iconSizes.sort((a, b) => a - b);
-    let bestSize = iconSizes.filter((e) => parseInt(e) >= 16).shift();
-    let icon = bestSize ? this.extension.manifest.icons[bestSize] : "";
-
-    let name = this.extension.manifest.name;
-    let entry = icon
-      ? event.target.ownerGlobal.MozXULElement.parseXULToFragment(
-          `<menuitem class="menuitem-iconic" id="${id}" image="${icon}" label="${name}" />`
-        )
-      : event.target.ownerGlobal.MozXULElement.parseXULToFragment(
-          `<menuitem id="${id}" label="${name}" />`
-        );
-
-    event.target.appendChild(entry);
-    let noPrefsElem = event.target.querySelector('[disabled="true"]');
-    // using collapse could be undone by core, so we use display none
-    // noPrefsElem.setAttribute("collapsed", "true");
-    noPrefsElem.style.display = "none";
-    event.target.ownerGlobal.document
-      .getElementById(id)
-      .addEventListener("command", this);
-  }
 
   // Event handler for the addon manager, to update the state of the options button.
   handleEvent(e) {
     switch (e.type) {
-      // 68 add-on options menu showing
-      case "popupshowing":
-        {
-          this.add68PrefsEntry(e);
-        }
-        break;
-
-      // 78/88 add-on options menu/button click
-      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;
-
-      // 68 add-on options menu command
-      case "command":
-        {
-          let WL = {};
-          WL.extension = this.extension;
-          WL.messenger = this.getMessenger(this.context);
-          e.target.ownerGlobal.openDialog(
-            this.pathToOptionsPage,
-            "AddonOptions",
-            "chrome,resizable,centerscreen",
-            WL
-          );
-        }
+      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
@@ -130,59 +55,27 @@
         let cards = this.getCards(e);
         for (let card of cards) {
           // 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);
-            if (optionsMenu) {
-              // Options menu in 78.0-78.10 and 79-87
-              let addonOptionsLegacyEntry = card.querySelector(
-                ".extension-options-legacy"
-              );
-              if (card.addon.isActive && !addonOptionsLegacyEntry) {
-                let addonOptionsEntry = card.querySelector(
-                  "addon-options panel-list panel-item[action='preferences']"
-                );
-                addonOptionsLegacyEntry = card.ownerDocument.createElement(
-                  "panel-item"
-                );
-                addonOptionsLegacyEntry.setAttribute(
-                  "data-l10n-id",
-                  "preferences-addon-button"
-                );
-                addonOptionsLegacyEntry.classList.add(
-                  "extension-options-legacy"
-                );
-                addonOptionsEntry.parentNode.insertBefore(
-                  addonOptionsLegacyEntry,
-                  addonOptionsEntry
-                );
-                card
-                  .querySelector(".extension-options-legacy")
-                  .addEventListener("click", this);
-              } else if (!card.addon.isActive && addonOptionsLegacyEntry) {
-                addonOptionsLegacyEntry.remove();
-              }
-            } else {
-              // Add-on button in 88
-              let addonOptionsButton = card.querySelector(
-                ".windowlistener-options-button"
+            // 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
               );
-              if (card.addon.isActive && !addonOptionsButton) {
-                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();
-              }
+              card
+                .querySelector(".windowlistener-options-button")
+                .addEventListener("click", this);
+            } else if (!card.addon.isActive && addonOptionsButton) {
+              addonOptionsButton.remove();
             }
           }
         }
@@ -220,18 +113,25 @@
     if (!managerWindow) {
       return;
     }
-    if (!(
+    if (!this.pathToOptionsPage) {
+      return;
+    }
+    if (
       managerWindow &&
       managerWindow[this.uniqueRandomID] &&
       managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners
-    )) {
-      managerWindow.document.addEventListener("ViewChanged", this);
-      managerWindow.document.addEventListener("update", this);
-      managerWindow.document.addEventListener("view-loaded", this);   
-      managerWindow[this.uniqueRandomID] = {};
-      managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true;
+    ) {
+      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);
     }
-    if (forceLoad) this.handleEvent(managerWindow);
   }
 
   getMessenger(context) {
@@ -259,11 +159,11 @@
     for (let api of apis) {
       switch (api) {
         case "storage":
-          XPCOMUtils.defineLazyGetter(messenger, "storage", () => getStorage());
+          ChromeUtils.defineLazyGetter(messenger, "storage", () => getStorage());
           break;
 
         default:
-          XPCOMUtils.defineLazyGetter(messenger, api, () =>
+          ChromeUtils.defineLazyGetter(messenger, api, () =>
             context.apiCan.findAPIPath(api)
           );
       }
@@ -307,8 +207,6 @@
     this.menu_addonPrefs_id = "addonPrefs";
 
     this.registeredWindows = {};
-    this.pathToStartupScript = null;
-    this.pathToShutdownScript = null;
     this.pathToOptionsPage = null;
     this.chromeHandle = null;
     this.chromeData = null;
@@ -327,10 +225,10 @@
 
     // 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) {},
+      onTabTitleChanged(aTab) { },
+      onTabClosing(aTab) { },
+      onTabPersist(aTab) { },
+      onTabRestored(aTab) { },
       onTabSwitched(aNewTab, aOldTab) {
         //self.setupAddonManager(self.getAddonManagerFromTab(aNewTab));
       },
@@ -344,8 +242,8 @@
                   "nsIWebProgressListener",
                   "nsISupportsWeakReference",
                 ]),
-                onStateChange() {},
-                onProgressChange() {},
+                onStateChange() { },
+                onProgressChange() { },
                 onLocationChange(
                   /* in nsIWebProgress*/ aWebProgress,
                   /* in nsIRequest*/ aRequest,
@@ -354,34 +252,35 @@
                   aTab.browser.removeProgressListener(reporterListener);
                   resolve();
                 },
-                onStatusChange() {},
-                onSecurityChange() {},
-                onContentBlockingEvent() {},
+                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);
+        aDocumentExistsAt(uriString) {
+          // No sane way yet to detect if about:urls exists, maybe lookup the definition?
+          if (uriString.startsWith("about:")) {
+            return true;
           }
-          self.log("Master password has been entered.");
-        },
 
-        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);
@@ -399,38 +298,6 @@
             : context.extension.rootURI.resolve(optionsUrl);
         },
 
-        registerDefaultPrefs(defaultUrl) {
-          let url = context.extension.rootURI.resolve(defaultUrl);
-
-          let prefsObj = {};
-          prefsObj.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 = [];
@@ -473,7 +340,7 @@
           if (self.debug && !this.aDocumentExistsAt(windowHref)) {
             self.error(
               "Attempt to register an injector script for non-existent window: " +
-                windowHref
+              windowHref
             );
             return;
           }
@@ -492,18 +359,6 @@
           }
         },
 
-        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;
@@ -519,42 +374,6 @@
         },
 
         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
@@ -572,117 +391,8 @@
                 // messenger window is opened.
                 //chromeURLs: Object.keys(self.registeredWindows),
                 async onLoadWindow(window) {
-                  // Create add-on scope
-                  window[self.uniqueRandomID] = {};
-
-                  // Special action #1: If this is the main messenger window
-                  if (
-                    window.location.href ==
-                      "chrome://messenger/content/messenger.xul" ||
-                    window.location.href ==
-                      "chrome://messenger/content/messenger.xhtml"
-                  ) {
-                    if (self.pathToOptionsPage) {
-                      if (self.getThunderbirdVersion().major < 78) {
-                        let element_addonPrefs = window.document.getElementById(
-                          self.menu_addonPrefs_id
-                        );
-                        element_addonPrefs.addEventListener(
-                          "popupshowing",
-                          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;
-                      }
-                    }
-                  }
-
-                  // Special action #2: If this page contains browser elements
-                  let browserElements = window.document.getElementsByTagName(
-                    "browser"
-                  );
-                  if (browserElements.length > 0) {
-                    //register a MutationObserver
-                    window[
-                      self.uniqueRandomID
-                    ]._mObserver = new window.MutationObserver(function (
-                      mutations
-                    ) {
-                      mutations.forEach(async function (mutation) {
-                        if (
-                          mutation.attributeName == "src" &&
-                          self.registeredWindows.hasOwnProperty(
-                            mutation.target.getAttribute("src")
-                          )
-                        ) {
-                          // When the MutationObserver callsback, the window is still showing "about:black" and it is going
-                          // to unload and then load the new page. Any eventListener attached to the window will be removed
-                          // so we cannot listen for the load event. We have to poll manually to learn when loading has finished.
-                          // On my system it takes 70ms.
-                          let loaded = false;
-                          for (let i = 0; i < 100 && !loaded; i++) {
-                            await self.sleep(100);
-                            let targetWindow =
-                              mutation.target.contentWindow.wrappedJSObject;
-                            if (
-                              targetWindow &&
-                              targetWindow.location.href ==
-                                mutation.target.getAttribute("src") &&
-                              targetWindow.document.readyState == "complete"
-                            ) {
-                              loaded = true;
-                              break;
-                            }
-                          }
-                          if (loaded) {
-                            let targetWindow =
-                              mutation.target.contentWindow.wrappedJSObject;
-                            // Create add-on scope
-                            targetWindow[self.uniqueRandomID] = {};
-                            // Inject with isAddonActivation = false
-                            self._loadIntoWindow(targetWindow, false);
-                          }
-                        }
-                      });
-                    });
-
-                    for (let element of browserElements) {
-                      if (
-                        self.registeredWindows.hasOwnProperty(
-                          element.getAttribute("src")
-                        )
-                      ) {
-                        let targetWindow =
-                          element.contentWindow.wrappedJSObject;
-                        // Create add-on scope
-                        targetWindow[self.uniqueRandomID] = {};
-                        // Inject with isAddonActivation = true
-                        self._loadIntoWindow(targetWindow, true);
-                      } else {
-                        // Window/Browser is not yet fully loaded, postpone injection via MutationObserver
-                        window[self.uniqueRandomID]._mObserver.observe(
-                          element,
-                          {
-                            attributes: true,
-                            childList: false,
-                            characterData: false,
-                          }
-                        );
-                      }
-                    }
-                  }
-
                   // Load JS into window
-                  self._loadIntoWindow(
+                  await self._loadIntoWindow(
                     window,
                     self.openWindows.includes(window)
                   );
@@ -702,11 +412,60 @@
     };
   }
 
-  _loadIntoWindow(window, isAddonActivation) {
-    if (
-      window.hasOwnProperty(this.uniqueRandomID) &&
-      this.registeredWindows.hasOwnProperty(window.location.href)
-    ) {
+  _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;
@@ -808,10 +567,10 @@
                 if (debug)
                   console.log(
                     elements[i].tagName +
-                      "#" +
-                      elements[i].id +
-                      ": insertafter " +
-                      insertAfterElement.id
+                    "#" +
+                    elements[i].id +
+                    ": insertafter " +
+                    insertAfterElement.id
                   );
                 if (
                   debug &&
@@ -820,8 +579,8 @@
                 ) {
                   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);
@@ -840,10 +599,10 @@
                 if (debug)
                   console.log(
                     elements[i].tagName +
-                      "#" +
-                      elements[i].id +
-                      ": insertbefore " +
-                      insertBeforeElement.id
+                    "#" +
+                    elements[i].id +
+                    ": insertbefore " +
+                    insertBeforeElement.id
                   );
                 if (
                   debug &&
@@ -852,8 +611,8 @@
                 ) {
                   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);
@@ -869,10 +628,10 @@
                 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),
@@ -916,10 +675,10 @@
                 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]);
@@ -976,20 +735,16 @@
   }
 
   _unloadFromWindow(window, isAddonDeactivation) {
-    // unload any contained browser elements
-    if (
-      window.hasOwnProperty(this.uniqueRandomID) &&
-      window[this.uniqueRandomID].hasOwnProperty("_mObserver")
-    ) {
-      window[this.uniqueRandomID]._mObserver.disconnect();
-      let browserElements = window.document.getElementsByTagName("browser");
-      for (let element of browserElements) {
-        if (element.contentWindow) {
-          this._unloadFromWindow(
-            element.contentWindow.wrappedJSObject,
-            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
+        );
       }
     }
 
@@ -997,7 +752,7 @@
       window.hasOwnProperty(this.uniqueRandomID) &&
       this.registeredWindows.hasOwnProperty(window.location.href)
     ) {
-      //  Remove this window from the list of open windows
+      // Remove this window from the list of open windows
       this.openWindows = this.openWindows.filter((e) => e != window);
 
       if (window[this.uniqueRandomID].onUnload) {
@@ -1044,75 +799,49 @@
     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 (
-          this.pathToOptionsPage &&
-          (window.location.href == "chrome://messenger/content/messenger.xul" ||
-            window.location.href ==
-              "chrome://messenger/content/messenger.xhtml")
-        ) {
-          if (this.getThunderbirdVersion().major < 78) {
-            let element_addonPrefs = window.document.getElementById(
-              this.menu_addonPrefs_id
-            );
-            element_addonPrefs.removeEventListener("popupshowing", this);
-            // Remove our entry.
-            let entry = window.document.getElementById(
-              this.menu_addonPrefs_id + "_" + this.uniqueRandomID
-            );
-            if (entry) entry.remove();
-            // Do we have to unhide the noPrefsElement?
-            if (element_addonPrefs.children.length == 1) {
-              let noPrefsElem = element_addonPrefs.querySelector(
-                '[disabled="true"]'
-              );
-              noPrefsElem.style.display = "inline";
+        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");
             }
-          } else {
-            // 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);
-
-              let cards = this.getCards(managerWindow);
-              if (this.getThunderbirdVersion().major < 88) {
-                // Remove options menu in 78-87
-                for (let card of cards) {
-                  let addonOptionsLegacyEntry = card.querySelector(
-                    ".extension-options-legacy"
-                  );
-                  if (addonOptionsLegacyEntry) addonOptionsLegacyEntry.remove();
-                }
-              } else {
-                // Remove options button in 88
-                for (let card of cards) {
-                  if (card.addon.id == this.extension.id) {
-                    let addonOptionsButton = card.querySelector(
-                      ".windowlistener-options-button"
-                    );
-                    if (addonOptionsButton) addonOptionsButton.remove();
-                    break;
-                  }
-                }
+            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;
-            }
+          // Remove tabmonitor
+          if (window[this.uniqueRandomID].hasTabMonitor) {
+            this.getTabMail(window).unregisterTabMonitor(this.tabMonitor);
+            window[this.uniqueRandomID].hasTabMonitor = false;
           }
         }
 
@@ -1125,20 +854,6 @@
       );
     }
 
-    // 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) {
@@ -1181,4 +896,4 @@
       this.chromeHandle = null;
     }
   }
-};
+};
\ Kein Zeilenumbruch am Dateiende.
diff -Nru quicktext-5.2/api/WindowListener/schema.json quicktext-5.16/api/WindowListener/schema.json
--- quicktext-5.2/api/WindowListener/schema.json	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/api/WindowListener/schema.json	2024-07-26 13:08:57.000000000 +0200
@@ -1,19 +1,8 @@
 [
   {
-    "namespace": "WindowListener",    
+    "namespace": "WindowListener",
     "functions": [
       {
-        "name": "registerDefaultPrefs",
-        "type": "function",
-        "parameters": [
-          {
-            "name": "aPath",
-            "type": "string",
-            "description": "Relative path to the default file."
-          }
-        ]
-      },
-      {
         "name": "registerOptionsPage",
         "type": "function",
         "parameters": [
@@ -34,7 +23,7 @@
             "type": "array",
             "items": {
               "type": "array",
-              "items" : {
+              "items": {
                 "type": "string"
               }
             },
@@ -43,12 +32,6 @@
         ]
       },
       {
-        "name": "waitForMasterPassword",
-        "type": "function",
-        "async": true,
-        "parameters": []
-      },
-      {
         "name": "openOptionsDialog",
         "type": "function",
         "parameters": [
@@ -80,29 +63,7 @@
             "description": "Path to the JavaScript file, which should be loaded into the window."
           }
         ]
-      },
-      {
-        "name": "registerStartupScript",
-        "type": "function",
-        "parameters": [
-          {
-            "name": "aPath",
-            "type": "string",
-            "description": "Path to a JavaScript file, which should be executed on add-on startup. The script will be executed after the main application window has been sucessfully loaded."
-          }
-        ]
-      },
-      {
-        "name": "registerShutdownScript",
-        "type": "function",
-        "parameters": [
-          {
-            "name": "aPath",
-            "type": "string",
-            "description": "Path to a JavaScript file, which should be executed on add-on shutdown."
-          }
-        ]
       }
     ]
   }
-]
+]
\ Kein Zeilenumbruch am Dateiende.
diff -Nru quicktext-5.2/background.js quicktext-5.16/background.js
--- quicktext-5.2/background.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/background.js	2024-07-26 13:08:57.000000000 +0200
@@ -89,3 +89,4 @@
 
   messenger.WindowListener.startListening();
 })();
+
diff -Nru quicktext-5.2/chrome/content/modules/utils.jsm quicktext-5.16/chrome/content/modules/utils.jsm
--- quicktext-5.2/chrome/content/modules/utils.jsm	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/content/modules/utils.jsm	2024-07-26 13:08:57.000000000 +0200
@@ -2,8 +2,6 @@
 
 var EXPORTED_SYMBOLS = ["quicktextUtils"]
 
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
 var quicktextUtils = {
   get dateTimeFormat() {
     if (Services.vc.compare(Services.appinfo.platformVersion, "59.0-1") >= 0) {
diff -Nru quicktext-5.2/chrome/content/modules/wzQuicktextGroup.jsm quicktext-5.16/chrome/content/modules/wzQuicktextGroup.jsm
--- quicktext-5.2/chrome/content/modules/wzQuicktextGroup.jsm	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/content/modules/wzQuicktextGroup.jsm	2024-07-26 13:08:57.000000000 +0200
@@ -2,21 +2,20 @@
 
 const kDebug        = true;
 
-function wzQuicktextGroup() {
-  this.mName = "";
-  this.mType = "";
-}
+class wzQuicktextGroup {
+  constructor() {
+    this.mName = "";
+    this.mType = "";
+  }
 
-wzQuicktextGroup.prototype = {
-  get name() { return this.mName; },
+  get name() { return this.mName; }
   set name(aName) { if (typeof aName != 'undefined') return this.mName = aName; }
-,
-  get type() { return this.mType; },
+
+  get type() { return this.mType; }
   set type(aType) { if (typeof aType != 'undefined') return this.mType = aType; }
-,
-  clone: function()
-  {
-    var newGroup = new wzQuicktextGroup();
+
+  clone() {
+    let newGroup = new wzQuicktextGroup();
     newGroup.name = this.mName;
     newGroup.type = this.mType;
 
diff -Nru quicktext-5.2/chrome/content/modules/wzQuicktext.jsm quicktext-5.16/chrome/content/modules/wzQuicktext.jsm
--- quicktext-5.2/chrome/content/modules/wzQuicktext.jsm	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/content/modules/wzQuicktext.jsm	2024-07-26 13:08:57.000000000 +0200
@@ -1,8 +1,6 @@
 var { wzQuicktextGroup } = ChromeUtils.import("chrome://quicktext/content/modules/wzQuicktextGroup.jsm");
 var { wzQuicktextTemplate } = ChromeUtils.import("chrome://quicktext/content/modules/wzQuicktextTemplate.jsm");
 var { wzQuicktextScript } = ChromeUtils.import("chrome://quicktext/content/modules/wzQuicktextScript.jsm");
-var { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 var EXPORTED_SYMBOLS = ["gQuicktext"];
 
@@ -139,8 +137,7 @@
 
     this.mSettingsLoaded = true;
 
-    var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo).QueryInterface(Components.interfaces.nsIXULRuntime);
-    this.mOS = appInfo.OS;
+    this.mOS = Services.appinfo.OS;
 
     this.mGroup = [];
     this.mTexts = [];
@@ -643,8 +640,7 @@
   {
     //MDN states, instead of checking if dir exists, just create it and
     //catch error on exist (but it does not even throw)
-    await OS.File.makeDir(aFile.parent.path);
-    await OS.File.writeAtomic(aFile.path, aData, {tmpPath: aFile.path + ".tmp"});
+    await IOUtils.writeUTF8(aFile.path, aData);
   }
 ,
   pickFile: async function(aWindow, aType, aMode, aTitle)
@@ -655,11 +651,23 @@
     switch(aMode)
     {
       case 1:
-        filePicker.init(aWindow, aTitle, filePicker.modeSave);
+        try {
+          // TB 115
+          filePicker.init(aWindow, aTitle, filePicker.modeSave);
+        } catch (ex) {
+          // TB 128
+          filePicker.init(aWindow.browsingContext, aTitle, filePicker.modeSave);
+        }
         checkFileEncoding = false;
         break;
       default:
-        filePicker.init(aWindow, aTitle, filePicker.modeOpen);
+        try {
+          // TB 115
+          filePicker.init(aWindow, aTitle, filePicker.modeOpen);
+        } catch (ex) {
+          // TB 128
+          filePicker.init(aWindow.browsingContext, aTitle, filePicker.modeOpen);
+        }
         break;
     }
 
@@ -752,7 +760,7 @@
     {
       // Only export scripts which have not been auto imported.
       if (this.mScripts[i].type == 0) {
-        buffer += "\t<script>\n\t\t<name><![CDATA["+ this.removeIllegalChars(this.mScripts[i].name) +"]]></name>\n\t\t<body><![CDATA["+ this.removeIllegalChars(this.mScripts[i].script) +"]]></body>\n\t</script>\n";
+        buffer += "\t<script>\n\t\t<name><![CDATA["+ this.removeIllegalCharsCDATA(this.mScripts[i].name) +"]]></name>\n\t\t<body><![CDATA["+ this.removeIllegalCharsCDATA(this.mScripts[i].script) +"]]></body>\n\t</script>\n";
       }
     }
     buffer += "</quicktext>";
@@ -773,15 +781,15 @@
           for (var j = 0; j < this.mTexts[i].length; j++)
           {
             var text = this.mTexts[i][j];
-            buffer += "\t\t\t<text shortcut=\""+ this.removeIllegalChars(text.shortcut) +"\" type=\""+ this.removeIllegalChars(text.type) +"\">\n\t\t\t\t<name><![CDATA["+ this.removeIllegalChars(text.name) +"]]></name>\n";
+            buffer += "\t\t\t<text shortcut=\""+ this.removeIllegalChars(text.shortcut) +"\" type=\""+ this.removeIllegalChars(text.type) +"\">\n\t\t\t\t<name><![CDATA["+ this.removeIllegalCharsCDATA(text.name) +"]]></name>\n";
             if (text.keyword != "")
-              buffer += "\t\t\t\t<keyword><![CDATA["+ this.removeIllegalChars(text.keyword) +"]]></keyword>\n";
+              buffer += "\t\t\t\t<keyword><![CDATA["+ this.removeIllegalCharsCDATA(text.keyword) +"]]></keyword>\n";
             if (text.subject != "")
-              buffer += "\t\t\t\t<subject><![CDATA["+ this.removeIllegalChars(text.subject) +"]]></subject>\n";
+              buffer += "\t\t\t\t<subject><![CDATA["+ this.removeIllegalCharsCDATA(text.subject) +"]]></subject>\n";
             if (text.text != "")
-              buffer += "\t\t\t\t<body><![CDATA["+ this.removeIllegalChars(text.text) +"]]></body>\n";
+              buffer += "\t\t\t\t<body><![CDATA["+ this.removeIllegalCharsCDATA(text.text) +"]]></body>\n";
             if (text.attachments != "")
-              buffer += "\t\t\t\t<attachments><![CDATA["+ this.removeIllegalChars(text.attachments) +"]]></attachments>\n";
+              buffer += "\t\t\t\t<attachments><![CDATA["+ this.removeIllegalCharsCDATA(text.attachments) +"]]></attachments>\n";
             
             // There seems to be no use to write dynamically gathered header informations from the last use of a template to the file
             
@@ -1014,7 +1022,16 @@
   {
     var tagElem = aElem.getElementsByTagName(aTag);
     if (tagElem.length > 0)
-      return tagElem[0].firstChild.nodeValue;
+    {
+      // can't be used anymore as sometimes there are several CDATA entries - see removeIllegalCharsCDATA
+      // return tagElem[0].firstChild.nodeValue;
+
+      var result = '';
+      for (const child of tagElem[0].childNodes) {
+        result = result + child.nodeValue;
+      }
+      return result;
+    }
 
     return "";
   }
@@ -1024,7 +1041,13 @@
     return aStr.replace(new RegExp("["+ kIllegalChars +"]", 'g'), '');
   }
 ,
-
+  removeIllegalCharsCDATA: function(aStr)
+  {
+    // https://stackoverflow.com/questions/223652/is-there-a-way-to-escape-a-cdata-end-token-in-xml
+    // replace ']]>' by ']]]]><![CDATA[>', need a regex replace to replace every occurence
+    return aStr.replace(new RegExp("["+ kIllegalChars +"]", 'g'), '').replace(/\]\]>/g, ']]]]><![CDATA[>');
+  }
+,
   /*
    * OBSERVERS
    */
diff -Nru quicktext-5.2/chrome/content/modules/wzQuicktextVar.jsm quicktext-5.16/chrome/content/modules/wzQuicktextVar.jsm
--- quicktext-5.2/chrome/content/modules/wzQuicktextVar.jsm	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/content/modules/wzQuicktextVar.jsm	2024-07-26 13:08:57.000000000 +0200
@@ -1,6 +1,5 @@
 var EXPORTED_SYMBOLS = ["wzQuicktextVar"];
 
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var { quicktextUtils } = ChromeUtils.import("chrome://quicktext/content/modules/utils.jsm");
 var { gQuicktext } = ChromeUtils.import("chrome://quicktext/content/modules/wzQuicktext.jsm");
 var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
@@ -12,65 +11,19 @@
   BANISHED_PROPERTIES: "resource:///modules/VCardUtils.jsm",
   VCardProperties: "resource:///modules/VCardUtils.jsm",
   VCardUtils: "resource:///modules/VCardUtils.jsm",
+  MsgHdrToMimeMessage: "resource:///modules/gloda/MimeMessage.jsm"
 });
 
 const kDebug          = true;
 const persistentTags  = ['COUNTER', 'ORGATT', 'ORGHEADER', 'VERSION'];
 const allowedTags     = ['ATT', 'CLIPBOARD', 'COUNTER', 'DATE', 'FILE', 'IMAGE', 'FROM', 'INPUT', 'ORGATT', 'ORGHEADER', 'SCRIPT', 'SUBJECT', 'TEXT', 'TIME', 'TO', 'URL', 'VERSION', 'SELECTION', 'HEADER'];
 
-function streamListener(aInspector)
-{
-  var newStreamListener = {
-    mAttachments: [],
-    mHeaders: [],
-
-    onStartRequest : function (aRequest, aContext)
-    {
-      this.mAttachments = [];
-      this.mHeaders = [];
-
-      var channel = aRequest.QueryInterface(Components.interfaces.nsIChannel);
-      channel.URI.QueryInterface(Components.interfaces.nsIMsgMailNewsUrl);
-      channel.URI.msgHeaderSink = this;  // adds this header sink interface to the channel
-    },
-    onStopRequest : function (aRequest, aContext, aStatusCode)
-    {
-      aInspector.exitNestedEventLoop();
-    },
-    onDataAvailable : function (aRequest, aContext, aInputStream, aOffset, aCount) {},
-    onStartHeaders: function() {},
-    onEndHeaders: function() {},
-    processHeaders: function(aHeaderNameEnumerator, aHeaderValueEnumerator, aDontCollectAddress)
-    {
-      while (aHeaderNameEnumerator.hasMore())
-        this.mHeaders.push({name:aHeaderNameEnumerator.getNext().toLowerCase(), value:aHeaderValueEnumerator.getNext()});
-    },
-    handleAttachment: function(aContentType, aUrl, aDisplayName, aUri, aIsExternalAttachment)
-    {
-      if (aContentType == "text/html") return;
-      this.mAttachments.push({contentType:aContentType, url:aUrl, displayName:aDisplayName, uri:aUri, isExternal:aIsExternalAttachment});
-    },
-    onEndAllAttachments: function() {},
-    onEndMsgDownload: function(aUrl) {},
-    onEndMsgHeaders: function(aUrl) {},
-    onMsgHasRemoteContent: function(aMsgHdr) {},
-    getSecurityInfo: function() {},
-    setSecurityInfo: function(aSecurityInfo) {},
-    getDummyMsgHeader: function() {},
-
-    QueryInterface : function(aIID)
-    {
-      if (aIID.equals(Components.interfaces.nsIStreamListener) ||
-          aIID.equals(Components.interfaces.nsIMsgHeaderSink) ||
-          aIID.equals(Components.interfaces.nsISupports))
-        return this;
-
-      throw Components.results.NS_NOINTERFACE;
-      return 0;
-    }
-  };
-
-  return newStreamListener;
+function getThunderbirdVersion() {
+  let parts = Services.appinfo.version.split(".");
+  return {
+    major: parseInt(parts[0]),
+    minor: parseInt(parts[1]),
+  }
 }
 
 function wzQuicktextVar()
@@ -328,6 +281,8 @@
       {
         if (aVariables[0] == "full")
           value.push(data[i][0] +" ("+ this.niceFileSize(data[i][1]) +")");
+        else if (aVariables[0] == "modified")
+          value.push(data[i][2])
         else
           value.push(data[i][0]);
       }
@@ -452,9 +407,9 @@
     return "";
   }
 ,
-  get_orgheader: function(aVariables)
+  get_orgheader: async function(aVariables)
   {
-    var data = this.process_orgheader(aVariables);
+    var data = await this.process_orgheader(aVariables);
 
     aVariables[0] = aVariables[0].toLowerCase();
     if (typeof data[aVariables[0]] != 'undefined')
@@ -468,9 +423,9 @@
       return "";
   }
 ,
-  get_orgatt: function(aVariables)
+  get_orgatt: async function(aVariables)
   {
-    var data = this.process_orgatt(aVariables);
+    var data = await this.process_orgatt(aVariables);
 
     if (typeof data != 'undefined')
     {
@@ -637,7 +592,7 @@
         try {
           var file = fileHandler.getFileFromURLSpec(attachment.url);
           if (file.exists())
-            this.mData['ATT'].data.push([attachment.name, file.fileSize]);
+            this.mData['ATT'].data.push([attachment.name, file.fileSize, file.lastModifiedTime]);
         }
         catch(e)
         {
@@ -660,7 +615,7 @@
       return this.mData['INPUT'].data;
 
     // There are two types of input select and text.
-    var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
+    var promptService = Services.prompt;
     if (aVariables[1] == 'select')
     {
       var checkValue = {};
@@ -741,11 +696,12 @@
         // Text templates: request clipboard content as plain text only
         if(clipboardHTMLfilled == 0)
         {
-          trans.addDataFlavor("text/unicode");
+          const textFlavor = getThunderbirdVersion().major < 115 ? "text/unicode" : "text/plain";
+          trans.addDataFlavor(textFlavor);
           clip.getData(trans, clip.kGlobalClipboard);
           var clipboard = {};
           try {
-            trans.getTransferData("text/unicode", clipboard);
+            trans.getTransferData(textFlavor, clipboard);
             if (clipboard)
             {
               clipboard = clipboard.value.QueryInterface(Components.interfaces.nsISupportsString);
@@ -939,7 +895,7 @@
     };
 
     this.mWindow.Recipients2CompFields(this.mWindow.gMsgCompose.compFields);
-    let emailAddresses = MailServices.headerParser.parseEncodedHeader(this.mWindow.gMsgCompose.compFields.to);
+    let emailAddresses = MailServices.headerParser.parseEncodedHeaderW(this.mWindow.gMsgCompose.compFields.to);
 
     if (emailAddresses.length > 0)
     {
@@ -1146,21 +1102,21 @@
     return this.mData['TIME'].data;
   }
 ,
-  process_orgheader: function(aVariables)
+  process_orgheader: async function(aVariables)
   {
     if (this.mData['ORGHEADER'] && this.mData['ORGHEADER'].checked)
       return this.mData['ORGHEADER'].data;
 
-    this.preprocess_org();
+    await this.preprocess_org();
     return this.mData['ORGHEADER'].data;
   }
 ,
-  process_orgatt: function(aVariables)
+  process_orgatt: async function(aVariables)
   {
     if (this.mData['ORGATT'] && this.mData['ORGATT'].checked)
       return this.mData['ORGATT'].data;
 
-    this.preprocess_org();
+    await this.preprocess_org();
     return this.mData['ORGATT'].data;
   }
 ,
@@ -1182,49 +1138,86 @@
     }
  }
 ,
-  preprocess_org: function()
-  {
-    this.mData['ORGHEADER'] = {};
-    this.mData['ORGHEADER'].checked = true;
-    this.mData['ORGHEADER'].data = {};
-
-    this.mData['ORGATT'] = {};
-    this.mData['ORGATT'].checked = true;
-    this.mData['ORGATT'].data = {contentType:[], url:[], displayName:[], uri:[], isExternal:[]};
-
-    var msgURI = this.mWindow.gMsgCompose.originalMsgURI;
-    if (!msgURI || msgURI == "")
-      return;
-
-    var messenger = Components.classes["@mozilla.org/messenger;1"].createInstance(Components.interfaces.nsIMessenger);
-    var mms = messenger.messageServiceFromURI(msgURI).QueryInterface(Components.interfaces.nsIMsgMessageService);
-
-    //Lazy async-to-sync implementation with ACK from Philipp Kewisch
-    //http://lists.thunderbird.net/pipermail/maildev_lists.thunderbird.net/2018-June/001205.html
-    let inspector = Components.classes["@mozilla.org/jsinspector;1"].createInstance(Components.interfaces.nsIJSInspector);
-    let listener = streamListener(inspector);
-    mms.streamMessage(msgURI, listener, null, null, true, "filter");
+preprocess_org: async function () {
 
-    //lazy async, wait for listener
-    inspector.enterNestedEventLoop(0); /* wait for async process to terminate */
+  this.mData['ORGHEADER'] = {};
+  this.mData['ORGHEADER'].checked = true;
+  this.mData['ORGHEADER'].data = {};
+
+  this.mData['ORGATT'] = {};
+  this.mData['ORGATT'].checked = true;
+  this.mData['ORGATT'].data = { contentType: [], url: [], displayName: [], uri: [], isExternal: [] };
+
+  let msgURI = this.mWindow.gMsgCompose.originalMsgURI;
+  if (!msgURI || msgURI == "") {
+    return;
+  }
+
+  let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+  let relatedMsgHdr = messenger.msgHdrFromURI(msgURI);
+  
+  let mimeMsg;
+  try {
+    mimeMsg = await new Promise(resolve => {
+      MsgHdrToMimeMessage(
+        relatedMsgHdr,
+        null,
+        (_msgHdr, mimeMsg) => {
+          if (!mimeMsg) {
+            reject();
+          } else {
+            mimeMsg.attachments = mimeMsg.allInlineAttachments;
+            resolve(mimeMsg);
+          }
+        },
+        true,
+        { examineEncryptedParts: true }
+      );
+    });
+  } catch (ex) {
+    // Something went wrong. Return null, which will inform the user that the
+    return;
+  }
+
+  if (!mimeMsg) {
+    return;
+  }
+
+  // Decode headers. This also takes care of headers, which still include
+  // encoded words and need to be RFC 2047 decoded.
+  if ("headers" in mimeMsg) {
+    for (let header of Object.keys(mimeMsg.headers)) {
+      let name = header.toLowerCase();
+      let value = mimeMsg.headers[header].map(h =>
+        MailServices.mimeConverter.decodeMimeHeader(
+          h,
+          null,
+          false /* override_charset */,
+          true /* eatContinuations */
+        )
+      );
 
-    // Store all headers in the mData-variable
-    for (var i = 0; i < listener.mHeaders.length; i++)
-    {
-      var name = listener.mHeaders[i].name;
-      if (typeof this.mData['ORGHEADER'].data[name] == 'undefined')
+      // Store header in the mData-variable
+      if (typeof this.mData['ORGHEADER'].data[name] == 'undefined') {
         this.mData['ORGHEADER'].data[name] = [];
-      this.mData['ORGHEADER'].data[name].push(listener.mHeaders[i].value);
+      }
+      this.mData['ORGHEADER'].data[name].push(value);
     }
+  }
 
-    // Store all attachments in the mData-variable
-    for (var i = 0; i < listener.mAttachments.length; i++)
-    {
-      var attachment = listener.mAttachments[i];
-      for (var fields in attachment)
-        this.mData['ORGATT'].data[fields][i] = attachment[fields];
+  if ("attachments" in mimeMsg) {
+    for (let attachment of mimeMsg.attachments) {
+      if (attachment.contentType == "text/html") return;
+
+      // Store attachments in the mData-variable
+      this.mData['ORGATT'].data.contentType = attachment.contentType;
+      this.mData['ORGATT'].data.url = attachment.url;
+      this.mData['ORGATT'].data.displayName = attachment.displayName;
+      this.mData['ORGATT'].data.uri = attachment.uri;
+      this.mData['ORGATT'].data.isExternal = attachment.isExternal;
     }
   }
+}
 ,
   escapeRegExp: function(aStr)
   {
diff -Nru quicktext-5.2/chrome/content/notifyTools/notifyTools.js quicktext-5.16/chrome/content/notifyTools/notifyTools.js
--- quicktext-5.2/chrome/content/notifyTools/notifyTools.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/content/notifyTools/notifyTools.js	2024-07-26 13:08:57.000000000 +0200
@@ -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().
 const ADDON_ID = "{8845E3B3-E8FB-40E2-95E9-EC40294818C4}";
 
 /*
@@ -8,7 +8,17 @@
  * 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,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
 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 +77,26 @@
           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 +104,61 @@
 
   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 (typeof window != "undefined" && window) {
   window.addEventListener(
-    "load",
+    "unload",
     function (event) {
-      notifyTools.enable();
-      window.addEventListener(
-        "unload",
-        function (event) {
-          notifyTools.disable();
-        },
-        false
-      );
+      notifyTools.removeAllListeners();
     },
     false
   );
diff -Nru quicktext-5.2/chrome/content/quicktext.js quicktext-5.16/chrome/content/quicktext.js
--- quicktext-5.2/chrome/content/quicktext.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/content/quicktext.js	2024-07-26 13:08:57.000000000 +0200
@@ -119,27 +119,14 @@
     this.mKeywords = {};
 
     // Update the toolbar
-    var toolbar = document.getElementById("quicktext-toolbar");
+    var toolbar = document.getElementById("quicktext-templates-toolbar");
     if (toolbar != null)
     {
 
-      //clear toolbar and store current "variables" and "other" menus (the two rightmost ones)
-      var toolbarbuttonVar = null;
-      var toolbarbuttonOther = null;
+      // Clear template toolbar.
       var length = toolbar.children.length;
-      for(var i = length-1; i >= 0; i--)
-      {
-        var element = toolbar.children[i];
-        switch(element.getAttribute("id"))
-        {
-          case 'quicktext-variables':
-            toolbarbuttonVar = element.cloneNode(true);
-            break;
-          case 'quicktext-other':
-            toolbarbuttonOther = element.cloneNode(true);
-            break;
-        }
-        toolbar.removeChild(element);
+      for(var i = length-1; i >= 0; i--) {
+        toolbar.removeChild(toolbar.children[i]);
       }
 
       //rebuild template groups (the leftmost entries)
@@ -211,16 +198,8 @@
         }
       }
 
-      //add a flex spacer to push the VAR and OTHER elements to the right 
-      var spacer = document.createXULElement("spacer");
-      spacer.setAttribute("flex", "1");
-      toolbar.appendChild(spacer);
-      toolbar.appendChild(toolbarbuttonVar);
-      toolbar.appendChild(toolbarbuttonOther);
-
-            
-      // Update the toolbar inside the toolbarpalette and the drop-down menu - if used
-      let optionalUI = ["button-quicktext", "quicktext-popup"];
+      // Update the template menu inside the context menu - if used.
+      let optionalUI = ["quicktext-popup"];
       for (let a=0; a < optionalUI.length; a++) { 
         if (document.getElementById(optionalUI[a] + "-menupopup")) {
           let rootElement = document.getElementById(optionalUI[a] + "-menupopup");
@@ -460,7 +439,7 @@
         tmpRecipientHeaders[header] = [];
         
         // Create an array of emailaddresses for this header that allready added
-        let tmpEmailAddresses = MailServices.headerParser.parseEncodedHeader(gMsgCompose.compFields[convertHeaderToParse[header]]);
+        let tmpEmailAddresses = MailServices.headerParser.parseEncodedHeaderW(gMsgCompose.compFields[convertHeaderToParse[header]]);
         let emailAddresses = [];
         for (let i = 0; i < tmpEmailAddresses.length; i++)
           emailAddresses.push(tmpEmailAddresses[i].email);
@@ -469,7 +448,7 @@
         for (var i = 0; i < recipientHeaders[header].length; i++)
         {
           // Get the mailaddresses of all the addresses
-          let insertedAddresses = MailServices.headerParser.parseEncodedHeader(recipientHeaders[header][i]);
+          let insertedAddresses = MailServices.headerParser.parseEncodedHeaderW(recipientHeaders[header][i]);
           for (var j = 0; j < insertedAddresses.length; j++)
           {
             if (insertedAddresses[j].email && !emailAddresses.includes(insertedAddresses[j].email))
@@ -715,7 +694,11 @@
 ,
   editorKeyPress: async function(e)
   {
-    if (e.code == gQuicktext.keywordKey)
+    const alternatives = {
+      "Enter" : ["NumpadEnter"]
+    }
+
+    if (e.code == gQuicktext.keywordKey || alternatives[gQuicktext.keywordKey]?.includes(e.code))
     {
       var editor = GetCurrentEditor();
       var selection = editor.selection;
diff -Nru quicktext-5.2/chrome/content/scripts/messengercompose.js quicktext-5.16/chrome/content/scripts/messengercompose.js
--- quicktext-5.2/chrome/content/scripts/messengercompose.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/content/scripts/messengercompose.js	2024-07-26 13:08:57.000000000 +0200
@@ -1,6 +1,3 @@
-// Import any needed modules.
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
 // Load an additional JavaScript file.
 Services.scriptloader.loadSubScript("chrome://quicktext/content/quicktext.js", window, "UTF-8");
 
@@ -8,23 +5,28 @@
   
   WL.injectCSS("resource://quicktext/skin/quicktext.css");
   WL.injectElements(`
-	<popup id="msgComposeContext">
-		<menuseparator id="quicktext-popupsep" hidden="true" insertafter="spellCheckSuggestionsSeparator"/>
-		<menu id="quicktext-popup" label="&quicktext.label;" hidden="true" insertafter="spellCheckSuggestionsSeparator"  class="menu-iconic quicktext-icon menuitem-iconic" >
-		  <menupopup id="quicktext-popup-menupopup"/>
-		</menu>
-	</popup>
+  <popup id="msgComposeContext">
+    <menuseparator id="quicktext-popupsep" hidden="true" insertafter="spellCheckSuggestionsSeparator" />
+    <menu id="quicktext-popup" label="&quicktext.label;" hidden="true" insertafter="spellCheckSuggestionsSeparator"
+      class="menu-iconic quicktext-icon menuitem-iconic">
+      <menupopup id="quicktext-popup-menupopup" />
+    </menu>
+  </popup>
 
   <menupopup id="menu_View_Popup">
-    <menuitem id="quicktext-view" type="checkbox" label="&quicktext.label;" oncommand="quicktext.toogleToolbar();"/>
+    <menuitem id="quicktext-view" type="checkbox" label="&quicktext.label;" oncommand="quicktext.toogleToolbar();" />
   </menupopup>
 
   <menupopup id="taskPopup">
-    <menuitem id="quicktext-settings" label="&quicktext.label;" oncommand="quicktext.openSettings();" insertafter="tasksMenuAddressBook" class="menu-iconic quicktext-icon menuitem-iconic" />
+    <menuitem id="quicktext-settings" label="&quicktext.label;" oncommand="quicktext.openSettings();"
+      insertafter="tasksMenuAddressBook" class="menu-iconic quicktext-icon menuitem-iconic" />
     <menuseparator insertafter="tasksMenuAddressBook" />
   </menupopup>
 
   <toolbar id="quicktext-toolbar" insertbefore="messageEditor">
+    <html:div id="quicktext-templates-toolbar" />
+    <spacer flex="1" />
+    <hbox>
       <button type="menu" id="quicktext-variables" label="&quicktext.variables.label;" tabindex="-1">
         <menupopup>
           <menu label="&quicktext.to.label;">
@@ -37,7 +39,8 @@
               <menuitem label="&quicktext.email.label;" oncommand="quicktext.insertVariable('TO=email');" />
               <menuitem label="&quicktext.worknumber.label;" oncommand="quicktext.insertVariable('TO=workphone');" />
               <menuitem label="&quicktext.faxnumber.label;" oncommand="quicktext.insertVariable('TO=faxnumber');" />
-              <menuitem label="&quicktext.cellularnumber.label;" oncommand="quicktext.insertVariable('TO=cellularnumber');" />
+              <menuitem label="&quicktext.cellularnumber.label;"
+                oncommand="quicktext.insertVariable('TO=cellularnumber');" />
               <menuitem label="&quicktext.jobtitle.label;" oncommand="quicktext.insertVariable('TO=jobtitle');" />
               <menuitem label="&quicktext.custom1.label;" oncommand="quicktext.insertVariable('TO=custom1');" />
               <menuitem label="&quicktext.custom2.label;" oncommand="quicktext.insertVariable('TO=custom2');" />
@@ -55,7 +58,8 @@
               <menuitem label="&quicktext.email.label;" oncommand="quicktext.insertVariable('FROM=email');" />
               <menuitem label="&quicktext.worknumber.label;" oncommand="quicktext.insertVariable('FROM=workphone');" />
               <menuitem label="&quicktext.faxnumber.label;" oncommand="quicktext.insertVariable('FROM=faxnumber');" />
-              <menuitem label="&quicktext.cellularnumber.label;" oncommand="quicktext.insertVariable('FROM=cellularnumber');" />
+              <menuitem label="&quicktext.cellularnumber.label;"
+                oncommand="quicktext.insertVariable('FROM=cellularnumber');" />
               <menuitem label="&quicktext.jobtitle.label;" oncommand="quicktext.insertVariable('FROM=jobtitle');" />
               <menuitem label="&quicktext.custom1.label;" oncommand="quicktext.insertVariable('FROM=custom1');" />
               <menuitem label="&quicktext.custom2.label;" oncommand="quicktext.insertVariable('FROM=custom2');" />
@@ -94,7 +98,8 @@
           <menuitem label="&quicktext.insertTextFromFileAsHTML.label;" oncommand="quicktext.insertContentFromFile(1);" />
         </menupopup>
       </button>
-    </toolbar>`,
+    </hbox>
+  </toolbar>`,
   ["chrome://quicktext/locale/quicktext.dtd"]);
   
   window.quicktext.load();
diff -Nru quicktext-5.2/chrome/content/scripts/messenger.js quicktext-5.16/chrome/content/scripts/messenger.js
--- quicktext-5.2/chrome/content/scripts/messenger.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/content/scripts/messenger.js	2024-07-26 13:08:57.000000000 +0200
@@ -1,5 +1,3 @@
-// Import any needed modules.
-var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var { gQuicktext } = ChromeUtils.import("chrome://quicktext/content/modules/wzQuicktext.jsm");
 
 // Load an additional JavaScript file.
diff -Nru quicktext-5.2/chrome/content/settings.js quicktext-5.16/chrome/content/settings.js
--- quicktext-5.2/chrome/content/settings.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/content/settings.js	2024-07-26 13:08:57.000000000 +0200
@@ -1,6 +1,5 @@
 var { gQuicktext } = ChromeUtils.import("chrome://quicktext/content/modules/wzQuicktext.jsm");
 var { quicktextUtils } = ChromeUtils.import("chrome://quicktext/content/modules/utils.jsm");
-var { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 var quicktext = {
   mChangesMade:         false,
@@ -21,11 +20,10 @@
       this.mLoaded = true;
 
       // add OS as attribute to outer dialog
-      document.getElementById('quicktextSettingsWindow').setAttribute("OS", OS.Constants.Sys.Name);
-      console.log("Adding attribute 'OS' = '"+ OS.Constants.Sys.Name +"' to settings dialog element.");
+      document.getElementById('quicktextSettingsWindow').setAttribute("OS", Services.appinfo.OS);
+      console.log("Adding attribute 'OS' = '"+ Services.appinfo.OS +"' to settings dialog element.");
 
-      var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo).QueryInterface(Components.interfaces.nsIXULRuntime);
-      this.mOS = appInfo.OS;
+      this.mOS = Services.appinfo.OS;
 
       gQuicktext.addObserver(this);
       var hasLoadedBefore = !(await gQuicktext.loadSettings(false));
@@ -80,8 +78,7 @@
 
     if (this.mChangesMade)
     {
-      promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
-                                .getService(Components.interfaces.nsIPromptService);
+      promptService = Services.prompt;
       if (promptService)
       {
         result = promptService.confirmEx(window,
diff -Nru quicktext-5.2/chrome/locale/ja/quicktext.dtd quicktext-5.16/chrome/locale/ja/quicktext.dtd
--- quicktext-5.2/chrome/locale/ja/quicktext.dtd	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/locale/ja/quicktext.dtd	2024-07-26 13:08:57.000000000 +0200
@@ -3,32 +3,32 @@
 
 <!ENTITY quicktext.settings.title "Quicktext ???">
 <!ENTITY quicktext.settings.label "??">
-<!ENTITY quicktext.accesskey.settings "?">
+<!ENTITY quicktext.accesskey.settings "n">
 <!ENTITY quicktext.resetcounter.label "??????????">
-<!ENTITY quicktext.accesskey.resetCounter "?">
+<!ENTITY quicktext.accesskey.resetCounter "r">
 <!ENTITY quicktext.file.label "????">
-<!ENTITY quicktext.accesskey.file "?">
+<!ENTITY quicktext.accesskey.file "f">
 <!ENTITY quicktext.goToHomepage.label "?????????">
 <!ENTITY quicktext.accesskey.goToHomepage "h">
 <!ENTITY quicktext.help.label "???">
-<!ENTITY quicktext.accesskey.help "?">
+<!ENTITY quicktext.accesskey.help "p">
 <!ENTITY quicktext.close.label "???">
-<!ENTITY quicktext.accesskey.close "?">
+<!ENTITY quicktext.accesskey.close "c">
 <!ENTITY quicktext.save.label "??">
-<!ENTITY quicktext.accesskey.save "?">
+<!ENTITY quicktext.accesskey.save "s">
 
 <!ENTITY quicktext.general.label "??">
-<!ENTITY quicktext.accesskey.general "?">
+<!ENTITY quicktext.accesskey.general "g">
 <!ENTITY quicktext.templates.label "??????">
-<!ENTITY quicktext.accesskey.templates "?">
+<!ENTITY quicktext.accesskey.templates "t">
 <!ENTITY quicktext.scripts.label "?????">
-<!ENTITY quicktext.accesskey.scripts "?">
+<!ENTITY quicktext.accesskey.scripts "s">
 
-<!ENTITY quicktext.rightClickSetting.label "??????????? Quicktext ???">
+<!ENTITY quicktext.rightClickSetting.label "??????????? Quicktext ?????">
 <!ENTITY quicktext.collapseSetting.label "????????????1?????????????????????????">
-<!ENTITY quicktext.modifierSetting.label "??????????????????????????">
+<!ENTITY quicktext.modifierSetting.label "?????????????????????????">
 <!ENTITY quicktext.shortcutTypeAdv.label "???????????????????">
-<!ENTITY quicktext.keywordKeySetting.label "????????????????????????">
+<!ENTITY quicktext.keywordKeySetting.label "??????????????">
 
 <!ENTITY quicktext.altKey.label "Alt">
 <!ENTITY quicktext.controlKey.label "Ctrl">
@@ -48,25 +48,25 @@
 <!ENTITY quicktext.sharingTemplates.label "????????">
 <!ENTITY quicktext.sharingScripts.label "???????">
 <!ENTITY quicktext.export.label "??????">
-<!ENTITY quicktext.accesskey.export "?">
-<!ENTITY quicktext.accesskeyTemplate.export "?">
-<!ENTITY quicktext.accesskeyScripts.export "?">
+<!ENTITY quicktext.accesskey.export "e">
+<!ENTITY quicktext.accesskeyTemplate.export "e">
+<!ENTITY quicktext.accesskeyScripts.export "x">
 <!ENTITY quicktext.import.label "?????">
-<!ENTITY quicktext.accesskey.import "?">
-<!ENTITY quicktext.accesskeyTemplate.import "?">
-<!ENTITY quicktext.accesskeyScripts.import "?">
+<!ENTITY quicktext.accesskey.import "i">
+<!ENTITY quicktext.accesskeyTemplate.import "i">
+<!ENTITY quicktext.accesskeyScripts.import "m">
 
 <!ENTITY quicktext.title.label "????">
 <!ENTITY quicktext.addGroup.label "???????">
-<!ENTITY quicktext.accesskey.addGroup "?">
+<!ENTITY quicktext.accesskey.addGroup "p">
 <!ENTITY quicktext.addTemplate.label "?????????">
-<!ENTITY quicktext.accesskey.addTemplate "?">
+<!ENTITY quicktext.accesskey.addTemplate "l">
 <!ENTITY quicktext.addScript.label "????????">
-<!ENTITY quicktext.accesskey.addScript "?">
-<!ENTITY quicktext.getScript.label "Community Scripts">
-<!ENTITY quicktext.accesskey.communityScripts "s">
+<!ENTITY quicktext.accesskey.addScript "a">
+<!ENTITY quicktext.getScript.label "???????????">
+<!ENTITY quicktext.accesskey.communityScripts "c">
 <!ENTITY quicktext.remove.label "??">
-<!ENTITY quicktext.accesskey.remove "?">
+<!ENTITY quicktext.accesskey.remove "d">
 <!ENTITY quicktext.shortcut.label "???????">
 
 <!ENTITY quicktext.group.label "????">
@@ -97,18 +97,18 @@
 
 <!ENTITY quicktext.other.label "???">
 <!ENTITY quicktext.clipboard.label "???????">
-<!ENTITY quicktext.header.label "Header (To, Cc, Bcc)">
-<!ENTITY quicktext.selection.label "Selection">
+<!ENTITY quicktext.header.label "???? (To, Cc, Bcc)">
+<!ENTITY quicktext.selection.label "??">
 <!ENTITY quicktext.cursor.label "???????">
 <!ENTITY quicktext.subject.label "??">
 <!ENTITY quicktext.version.label "Thunderbird ??????">
-<!ENTITY quicktext.image.label "Embedded HTML image">
-<!ENTITY quicktext.counter.label "Counter">
-<!ENTITY quicktext.input.label "Input value from user prompt">
-<!ENTITY quicktext.orgatt.label "Original attachment info">
-<!ENTITY quicktext.orgheader.label "Original header info">
-<!ENTITY quicktext.url.label "Response from URL">
-<!ENTITY quicktext.insertfile.label "Content from file (quicktext variables will be replaced)">
+<!ENTITY quicktext.image.label "????HTML??">
+<!ENTITY quicktext.counter.label "?????">
+<!ENTITY quicktext.input.label "???????????????">
+<!ENTITY quicktext.orgatt.label "?????????????">
+<!ENTITY quicktext.orgheader.label "???????????">
+<!ENTITY quicktext.url.label "URL?????">
+<!ENTITY quicktext.insertfile.label "????????quicktext????????????">
 
 <!ENTITY quicktext.attachments.label "??????">
 <!ENTITY quicktext.filename.label "???????">
diff -Nru quicktext-5.2/chrome/locale/ja/quicktext.properties quicktext-5.16/chrome/locale/ja/quicktext.properties
--- quicktext-5.2/chrome/locale/ja/quicktext.properties	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/locale/ja/quicktext.properties	2024-07-26 13:08:57.000000000 +0200
@@ -3,8 +3,8 @@
 exportFile=??????????????? (UTF-8)
 importFile=?????????????? (UTF-8)
 insertFile=??????????? (UTF-8)
-fileNotUTF8=The selected file does not seem to be UTF-8 encoded and will not loaded correctly. Please select a different file.
-insertImage=Choose image file to insert
+fileNotUTF8=??????????UTF-8?????????????????????????????????
+insertImage=?????????
 attachmentFile=?????????????????
 saveMessage=???????????????????????
 saveMessageTitle=?????
@@ -19,6 +19,6 @@
 altKey=Alt
 controlKey=Ctrl
 metaKey=Meta
-scriptNotFound=Quicktext script ?%S? not found!
-scriptError=There was an error in your Quicktext script:
-scriptLine=Line
+scriptNotFound=Quicktext????? ?%S? ????????!
+scriptError=Quicktext??????????????:
+scriptLine=?
Bin?rdateien /tmp/Jrl2_c_3Ji/quicktext-5.2/chrome/skin/button.large.png und /tmp/mA5wCxikNg/quicktext-5.16/chrome/skin/button.large.png sind verschieden.
Bin?rdateien /tmp/Jrl2_c_3Ji/quicktext-5.2/chrome/skin/button.small.png und /tmp/mA5wCxikNg/quicktext-5.16/chrome/skin/button.small.png sind verschieden.
Bin?rdateien /tmp/Jrl2_c_3Ji/quicktext-5.2/chrome/skin/logo.png und /tmp/mA5wCxikNg/quicktext-5.16/chrome/skin/logo.png sind verschieden.
diff -Nru quicktext-5.2/chrome/skin/quicktext.css quicktext-5.16/chrome/skin/quicktext.css
--- quicktext-5.2/chrome/skin/quicktext.css	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/chrome/skin/quicktext.css	2024-07-26 13:08:57.000000000 +0200
@@ -1,32 +1,5 @@
-#button-quicktext {
-  list-style-image: url("resource://quicktext/skin/button.large.png");
-  -moz-image-region: rect(0px 24px 24px 0px);
-}
-
-#button-quicktext:hover {
-  -moz-image-region: rect(24px 24px 48px 0px);
-}
-
-#button-quicktext[disabled] {
-  -moz-image-region: rect(48px 24px 72px 0px);
-}
-
-toolbar[iconsize="small"] #button-quicktext {
-  list-style-image: url("resource://quicktext/skin/button.small.png");
-  -moz-image-region: rect(0px 16px 16px 0px);
-}
-
-toolbar[iconsize="small"] #button-quicktext:hover {
-  -moz-image-region: rect(16px 16px 32px 0px);
-}
-
-toolbar[iconsize="small"] #button-quicktext[disabled] {
-  -moz-image-region: rect(32px 16px 48px 0px);
-}
-
 .quicktext-icon {
-  list-style-image: url("resource://quicktext/skin/button.small.png");
-  -moz-image-region: rect(16px 16px 32px 0px);
+  list-style-image: url("resource://quicktext/skin/icon16.png");
 }
 
 dialog[OS=WINNT] legend.insideTab {
diff -Nru quicktext-5.2/CONTRIBUTORS.md quicktext-5.16/CONTRIBUTORS.md
--- quicktext-5.2/CONTRIBUTORS.md	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/CONTRIBUTORS.md	2024-07-26 13:08:57.000000000 +0200
@@ -20,3 +20,4 @@
 ## Translators
 * Alexey Sinitsyn (ru)
 * ?v?ri (hu)
+* Ryota Murai (ja)
\ Kein Zeilenumbruch am Dateiende.
diff -Nru quicktext-5.2/debian/changelog quicktext-5.16/debian/changelog
--- quicktext-5.2/debian/changelog	2022-09-25 13:17:44.000000000 +0200
+++ quicktext-5.16/debian/changelog	2024-09-18 12:21:50.000000000 +0200
@@ -1,3 +1,42 @@
+quicktext (5.16-1~deb12u1) bookworm; urgency=medium
+
+  [ Mechtilde ]
+  * [9d327fa] Prepared rebuild for bookworm
+
+ -- Mechtilde Stehmann <mechtilde at debian.org>  Wed, 18 Sep 2024 12:21:50 +0200
+
+quicktext (5.16-1) unstable; urgency=medium
+
+  [ Mechtilde ]
+  * [df3bd73] New upstream version 5.16
+  * [9499b4d] Bumped standard version - no changes needed
+              Bumped versions of dependency
+
+ -- Mechtilde Stehmann <mechtilde at debian.org>  Sun, 01 Sep 2024 17:03:31 +0200
+
+quicktext (5.10-1) unstable; urgency=medium
+
+  [ Mechtilde ]
+  * [009df44] New upstream version 5.10
+
+ -- Mechtilde Stehmann <mechtilde at debian.org>  Wed, 13 Sep 2023 20:32:16 +0200
+
+quicktext (5.6-1~deb12u1) bookworm; urgency=medium
+
+  * Rebuild for bookworm
+    + for the updated thunderbird to 115.*
+
+ -- Mechtilde Stehmann <mechtilde at debian.org>  Thu, 10 Aug 2023 15:53:35 +0200
+
+quicktext (5.6-1) unstable; urgency=medium
+
+  [ Mechtilde ]
+  * [a859d0b] New upstream version 5.6
+  * [0e8df4f] Bumped standard version - no changes needed
+  * [9bc840e] Bumped min version of thunderbird
+
+ -- Mechtilde Stehmann <mechtilde at debian.org>  Thu, 10 Aug 2023 08:52:27 +0200
+
 quicktext (5.2-1) unstable; urgency=medium
 
   [ Mechtilde ]
diff -Nru quicktext-5.2/debian/control quicktext-5.16/debian/control
--- quicktext-5.2/debian/control	2022-09-25 13:11:21.000000000 +0200
+++ quicktext-5.16/debian/control	2024-09-18 12:11:20.000000000 +0200
@@ -5,7 +5,7 @@
 Uploaders: Mechtilde Stehmann <mechtilde at debian.org>
 Build-Depends: debhelper-compat (=13)
  , zip
-Standards-Version: 4.6.1
+Standards-Version: 4.7.0
 Rules-Requires-Root: no
 Vcs-Git: https://salsa.debian.org/webext-team/quicktext.git
 Vcs-Browser: https://salsa.debian.org/webext-team/quicktext
@@ -14,7 +14,8 @@
 Package: webext-quicktext
 Architecture: all
 Depends: ${misc:Depends}
- , thunderbird (>= 1:102.2)
+ , thunderbird (>= 1:115.1)
+ , thunderbird (<= 1:128.x)
 Description: Create templates for Thunderbird
  Quicktext is an extension for Thunderbird that lets you create templates that
  can be easily inserted into your own emails. Using Thunderbird, Quicktext is
diff -Nru quicktext-5.2/_locales/ja/messages.json quicktext-5.16/_locales/ja/messages.json
--- quicktext-5.2/_locales/ja/messages.json	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/_locales/ja/messages.json	2024-07-26 13:08:57.000000000 +0200
@@ -1,5 +1,5 @@
 {
   "extensionDescription": {
-    "message": "Adds a toolbar with unlimited number of text to quickly insert. It is also possible to use variables like [[TO=firstname]]. With settings for everything."
+    "message": "????????????????????????????????[[TO=firstname]]??????????????"
   }
 }
diff -Nru quicktext-5.2/manifest.json quicktext-5.16/manifest.json
--- quicktext-5.2/manifest.json	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.16/manifest.json	2024-07-26 13:08:57.000000000 +0200
@@ -3,12 +3,12 @@
   "applications": {
     "gecko": {
       "id": "{8845E3B3-E8FB-40E2-95E9-EC40294818C4}",
-      "strict_min_version": "100.0",
-      "strict_max_version": "102.*"
+      "strict_min_version": "115.0",
+      "strict_max_version": "128.*"
     }
   },
   "name": "Quicktext",
-  "version": "5.2",
+  "version": "5.16",
   "author": "John Bieling",
   "homepage_url": "https://github.com/jobisoft/quicktext",
   "default_locale": "en-US",
@@ -20,7 +20,7 @@
   },
   "compose_action": {
     "default_title": "Quicktext",
-    "default_label": "",    
+    "default_label": "",
     "default_icon": {
       "16": "chrome/skin/icon16.png",
       "24": "chrome/skin/icon24.png",
@@ -29,7 +29,7 @@
   }, 
   "browser_action": {
     "default_title": "Quicktext",
-    "default_label": "",    
+    "default_label": "",
     "default_icon": {
       "16": "chrome/skin/icon16.png",
       "24": "chrome/skin/icon24.png",
@@ -57,7 +57,8 @@
       "parent": {
         "scopes": ["addon_parent"],
         "paths": [["NotifyTools"]],
-        "script": "api/NotifyTools/implementation.js"
+        "script": "api/NotifyTools/implementation.js",
+        "events": ["startup"]
       }
     },
     "LegacyPrefs": {
Bin?rdateien /tmp/Jrl2_c_3Ji/quicktext-5.2/screenshoots/donate.png und /tmp/mA5wCxikNg/quicktext-5.16/screenshoots/donate.png sind verschieden.


More information about the Pkg-mozext-maintainers mailing list