[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1043412: bookworm-pu: package quicktext/5.6



Hello,

I completed the items

[ 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

and attach debdiff against the package in stable

Am 11.08.23 um 18:53 schrieb Jonathan Wiltshire:
Control: tag -1 moreinfo

On Thu, Aug 10, 2023 at 04:13:22PM +0200, Mechtilde Stehmann wrote:
[ Checklist ]
   [] *all* changes are documented in the d/changelog
   [X] I reviewed all changes and I approve them
   [] attach debdiff against the package in (old)stable
   [] the issue is verified as fixed in unstable

Please complete these items first.

Thanks,



After this is ok, I will do some other packages for the ne version of Thunderbird.

Kind regards
--
Mechtilde Stehmann
## Debian Developer
## PGP encryption welcome
## F0E3 7F3D C87A 4998 2899  39E7 F287 7BBA 141A AD7F
diffstat for quicktext-5.2 quicktext-5.6

 api/WindowListener/CHANGELOG.md           |   27 
 api/WindowListener/implementation.js      | 1181 +++++++++++++++++++++++++++---
 api/WindowListener/schema.json            |    6 
 chrome/content/modules/wzQuicktext.jsm    |   38 
 chrome/content/modules/wzQuicktextVar.jsm |    8 
 chrome/content/quicktext.js               |   10 
 chrome/content/settings.js                |   11 
 chrome/locale/ja/quicktext.dtd            |   40 -
 debian/changelog                          |   16 
 debian/control                            |    4 
 manifest.json                             |    4 
 11 files changed, 1192 insertions(+), 153 deletions(-)

diff -Nru quicktext-5.2/api/WindowListener/CHANGELOG.md quicktext-5.6/api/WindowListener/CHANGELOG.md
--- quicktext-5.2/api/WindowListener/CHANGELOG.md	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.6/api/WindowListener/CHANGELOG.md	2023-06-25 12:48:31.000000000 +0200
@@ -1,3 +1,30 @@
+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.6/api/WindowListener/implementation.js
--- quicktext-5.2/api/WindowListener/implementation.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.6/api/WindowListener/implementation.js	2023-06-25 12:48:31.000000000 +0200
@@ -2,7 +2,7 @@
  * This file is provided by the addon-developer-support repository at
  * https://github.com/thundernest/addon-developer-support
  *
- * Version: 1.54
+ * Version: 1.60
  *
  * Author: John Bieling (john@thunderbird.net)
  *
@@ -20,17 +20,17 @@
 );
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-var WindowListener = class extends ExtensionCommon.ExtensionAPI {
-  log(msg) {
-    if (this.debug) console.log("WindowListener API: " + msg);
+function getThunderbirdVersion() {
+  let parts = Services.appinfo.version.split(".");
+  return {
+    major: parseInt(parts[0]),
+    minor: parseInt(parts[1]),
   }
+}
 
-  getThunderbirdVersion() {
-    let parts = Services.appinfo.version.split(".");
-    return {
-      major: parseInt(parts[0]),
-      minor: parseInt(parts[1]),
-    }
+var WindowListener_102 = class extends ExtensionCommon.ExtensionAPI {
+  log(msg) {
+    if (this.debug) console.log("WindowListener API: " + msg);
   }
 
   getCards(e) {
@@ -39,10 +39,10 @@
     let doc;
 
     // 78,86, and 87+ need special handholding. *Yeah*.
-    if (this.getThunderbirdVersion().major < 86) {
+    if (getThunderbirdVersion().major < 86) {
       let ownerDoc = e.document || e.target.ownerDocument;
       doc = ownerDoc.getElementById("html-view-browser").contentDocument;
-    } else if (this.getThunderbirdVersion().major < 87) {
+    } else if (getThunderbirdVersion().major < 87) {
       let ownerDoc = e.document || e.target;
       doc = ownerDoc.getElementById("html-view-browser").contentDocument;
     } else {
@@ -66,11 +66,11 @@
     let name = this.extension.manifest.name;
     let entry = icon
       ? event.target.ownerGlobal.MozXULElement.parseXULToFragment(
-          `<menuitem class="menuitem-iconic" id="${id}" image="${icon}" label="${name}" />`
-        )
+        `<menuitem class="menuitem-iconic" id="${id}" image="${icon}" label="${name}" />`
+      )
       : event.target.ownerGlobal.MozXULElement.parseXULToFragment(
-          `<menuitem id="${id}" label="${name}" />`
-        );
+        `<menuitem id="${id}" label="${name}" />`
+      );
 
     event.target.appendChild(entry);
     let noPrefsElem = event.target.querySelector('[disabled="true"]');
@@ -132,9 +132,10 @@
           // 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);
+            let optionsMenu =
+              (getThunderbirdVersion().major > 78 && getThunderbirdVersion().major < 88) ||
+              (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor < 10) ||
+              (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor == 10 && getThunderbirdVersion().revision < 2);
             if (optionsMenu) {
               // Options menu in 78.0-78.10 and 79-87
               let addonOptionsLegacyEntry = card.querySelector(
@@ -198,7 +199,7 @@
   // returns the outer browser, not the nested browser of the add-on manager
   // events must be attached to the outer browser
   getAddonManagerFromTab(tab) {
-    if (tab.browser) {
+    if (tab.browser && tab.mode.name == "contentTab") {
       let win = tab.browser.contentWindow;
       if (win && win.location.href == "about:addons") {
         return win;
@@ -209,9 +210,28 @@
   getAddonManagerFromWindow(window) {
     let tabMail = this.getTabMail(window);
     for (let tab of tabMail.tabInfo) {
-      let win = this.getAddonManagerFromTab(tab);
-      if (win) {
-        return win;
+      let managerWindow = this.getAddonManagerFromTab(tab);
+      if (managerWindow) {
+        return managerWindow;
+      }
+    }
+  }
+
+  async getAddonManagerFromWindowWaitForLoad(window) {
+    let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane");
+    
+    let tabMail = this.getTabMail(window);
+    for (let tab of tabMail.tabInfo) {
+      if (tab.browser && tab.mode.name == "contentTab") {
+        // Instead of registering a load observer, wait until its loaded. Not nice,
+        // but gets aroud a lot of edge cases.
+        while(!tab.pageLoaded) {
+          await new Promise(r => setTimeout(r, 150));
+        }
+        let managerWindow = this.getAddonManagerFromTab(tab);
+        if (managerWindow) {
+          return managerWindow;
+        }
       }
     }
   }
@@ -227,7 +247,7 @@
     )) {
       managerWindow.document.addEventListener("ViewChanged", this);
       managerWindow.document.addEventListener("update", this);
-      managerWindow.document.addEventListener("view-loaded", this);   
+      managerWindow.document.addEventListener("view-loaded", this);
       managerWindow[this.uniqueRandomID] = {};
       managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true;
     }
@@ -327,41 +347,20 @@
 
     // TabMonitor to detect opening of tabs, to setup the options button in the add-on manager.
     this.tabMonitor = {
-      onTabTitleChanged(aTab) {},
-      onTabClosing(aTab) {},
-      onTabPersist(aTab) {},
-      onTabRestored(aTab) {},
-      onTabSwitched(aNewTab, aOldTab) {
-        //self.setupAddonManager(self.getAddonManagerFromTab(aNewTab));
-      },
-      async onTabOpened(aTab) {
-        if (aTab.browser) {
-          if (!aTab.pageLoaded) {
-            // await a location change if browser is not loaded yet
-            await new Promise((resolve) => {
-              let reporterListener = {
-                QueryInterface: ChromeUtils.generateQI([
-                  "nsIWebProgressListener",
-                  "nsISupportsWeakReference",
-                ]),
-                onStateChange() {},
-                onProgressChange() {},
-                onLocationChange(
-                  /* in nsIWebProgress*/ aWebProgress,
-                  /* in nsIRequest*/ aRequest,
-                  /* in nsIURI*/ aLocation
-                ) {
-                  aTab.browser.removeProgressListener(reporterListener);
-                  resolve();
-                },
-                onStatusChange() {},
-                onSecurityChange() {},
-                onContentBlockingEvent() {},
-              };
-              aTab.browser.addProgressListener(reporterListener);
-            });
+      onTabTitleChanged(tab) { },
+      onTabClosing(tab) { },
+      onTabPersist(tab) { },
+      onTabRestored(tab) { },
+      onTabSwitched(aNewTab, aOldTab) { },
+      async onTabOpened(tab) {
+        if (tab.browser && tab.mode.name == "contentTab") {
+          let { setTimeout } = Services.wm.getMostRecentWindow("mail:3pane");
+          // Instead of registering a load observer, wait until its loaded. Not nice,
+          // but gets aroud a lot of edge cases.
+          while(!tab.pageLoaded) {
+            await new Promise(r => setTimeout(r, 150));
           }
-          self.setupAddonManager(self.getAddonManagerFromTab(aTab));
+          self.setupAddonManager(self.getAddonManagerFromTab(tab));
         }
       },
     };
@@ -380,8 +379,8 @@
         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);
@@ -421,10 +420,10 @@
               default:
                 throw new Error(
                   "Preference <" +
-                    aName +
-                    "> has an unsupported type <" +
-                    typeof aDefault +
-                    ">. Allowed are string, number and boolean."
+                  aName +
+                  "> has an unsupported type <" +
+                  typeof aDefault +
+                  ">. Allowed are string, number and boolean."
                 );
             }
           };
@@ -473,7 +472,7 @@
           if (self.debug && !this.aDocumentExistsAt(windowHref)) {
             self.error(
               "Attempt to register an injector script for non-existent window: " +
-                windowHref
+              windowHref
             );
             return;
           }
@@ -536,8 +535,8 @@
                 // delay startup until startup has been finished
                 self.log(
                   "Waiting for async startup() in <" +
-                    self.pathToStartupScript +
-                    "> to finish."
+                  self.pathToStartupScript +
+                  "> to finish."
                 );
                 if (startupJS.startup) {
                   await startupJS.startup();
@@ -578,12 +577,12 @@
                   // Special action #1: If this is the main messenger window
                   if (
                     window.location.href ==
-                      "chrome://messenger/content/messenger.xul" ||
+                    "chrome://messenger/content/messenger.xul" ||
                     window.location.href ==
-                      "chrome://messenger/content/messenger.xhtml"
+                    "chrome://messenger/content/messenger.xhtml"
                   ) {
                     if (self.pathToOptionsPage) {
-                      if (self.getThunderbirdVersion().major < 78) {
+                      if (getThunderbirdVersion().major < 78) {
                         let element_addonPrefs = window.document.getElementById(
                           self.menu_addonPrefs_id
                         );
@@ -592,16 +591,14 @@
                           self
                         );
                       } else {
-                        // Setup the options button/menu in the add-on manager, if it is already open.
-                        self.setupAddonManager(
-                          self.getAddonManagerFromWindow(window),
-                          true
-                        );
                         // Add a tabmonitor, to be able to setup the options button/menu in the add-on manager.
                         self
                           .getTabMail(window)
                           .registerTabMonitor(self.tabMonitor);
                         window[self.uniqueRandomID].hasTabMonitor = true;
+                        // Setup the options button/menu in the add-on manager, if it is already open.
+                        let managerWindow = await self.getAddonManagerFromWindowWaitForLoad(window);
+                        self.setupAddonManager(managerWindow, true);
                       }
                     }
                   }
@@ -636,7 +633,7 @@
                             if (
                               targetWindow &&
                               targetWindow.location.href ==
-                                mutation.target.getAttribute("src") &&
+                              mutation.target.getAttribute("src") &&
                               targetWindow.document.readyState == "complete"
                             ) {
                               loaded = true;
@@ -808,10 +805,10 @@
                 if (debug)
                   console.log(
                     elements[i].tagName +
-                      "#" +
-                      elements[i].id +
-                      ": insertafter " +
-                      insertAfterElement.id
+                    "#" +
+                    elements[i].id +
+                    ": insertafter " +
+                    insertAfterElement.id
                   );
                 if (
                   debug &&
@@ -820,8 +817,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 +837,10 @@
                 if (debug)
                   console.log(
                     elements[i].tagName +
-                      "#" +
-                      elements[i].id +
-                      ": insertbefore " +
-                      insertBeforeElement.id
+                    "#" +
+                    elements[i].id +
+                    ": insertbefore " +
+                    insertBeforeElement.id
                   );
                 if (
                   debug &&
@@ -852,8 +849,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 +866,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 +913,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]);
@@ -1044,7 +1041,7 @@
     if (isAppShutdown) {
       return; // the application gets unloaded anyway
     }
-    
+
     // Unload from all still open windows
     let urls = Object.keys(this.registeredWindows);
     if (urls.length > 0) {
@@ -1054,9 +1051,9 @@
           this.pathToOptionsPage &&
           (window.location.href == "chrome://messenger/content/messenger.xul" ||
             window.location.href ==
-              "chrome://messenger/content/messenger.xhtml")
+            "chrome://messenger/content/messenger.xhtml")
         ) {
-          if (this.getThunderbirdVersion().major < 78) {
+          if (getThunderbirdVersion().major < 78) {
             let element_addonPrefs = window.document.getElementById(
               this.menu_addonPrefs_id
             );
@@ -1084,9 +1081,10 @@
               managerWindow.document.removeEventListener("ViewChanged", this);
               managerWindow.document.removeEventListener("view-loaded", this);
               managerWindow.document.removeEventListener("update", this);
+              managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false;
 
               let cards = this.getCards(managerWindow);
-              if (this.getThunderbirdVersion().major < 88) {
+              if (getThunderbirdVersion().major < 88) {
                 // Remove options menu in 78-87
                 for (let card of cards) {
                   let addonOptionsLegacyEntry = card.querySelector(
@@ -1182,3 +1180,986 @@
     }
   }
 };
+
+var WindowListener_115 = class extends ExtensionCommon.ExtensionAPI {
+  log(msg) {
+    if (this.debug) console.log("WindowListener API: " + msg);
+  }
+
+  getCards(e) {
+    // This gets triggered by real events but also manually by providing the outer window.
+    // The event is attached to the outer browser, get the inner one.
+    let doc = e.document || e.target;
+    return doc.querySelectorAll("addon-card");
+  }
+
+
+  // Event handler for the addon manager, to update the state of the options button.
+  handleEvent(e) {
+    switch (e.type) {
+      case "click": {
+        e.preventDefault();
+        e.stopPropagation();
+        let WL = {};
+        WL.extension = this.extension;
+        WL.messenger = this.getMessenger(this.context);
+        let w = Services.wm.getMostRecentWindow("mail:3pane");
+        w.openDialog(
+          this.pathToOptionsPage,
+          "AddonOptions",
+          "chrome,resizable,centerscreen",
+          WL
+        );
+      }
+        break;
+
+      // update, ViewChanged and manual call for add-on manager options overlay
+      default: {
+        let cards = this.getCards(e);
+        for (let card of cards) {
+          // Setup either the options entry in the menu or the button
+          if (card.addon.id == this.extension.id) {
+            // Add-on button
+            let addonOptionsButton = card.querySelector(
+              ".windowlistener-options-button"
+            );
+            if (card.addon.isActive && !addonOptionsButton) {
+              let origAddonOptionsButton = card.querySelector(".extension-options-button")
+              origAddonOptionsButton.setAttribute("hidden", "true");
+
+              addonOptionsButton = card.ownerDocument.createElement("button");
+              addonOptionsButton.classList.add("windowlistener-options-button");
+              addonOptionsButton.classList.add("extension-options-button");
+              card.optionsButton.parentNode.insertBefore(
+                addonOptionsButton,
+                card.optionsButton
+              );
+              card
+                .querySelector(".windowlistener-options-button")
+                .addEventListener("click", this);
+            } else if (!card.addon.isActive && addonOptionsButton) {
+              addonOptionsButton.remove();
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // Some tab/add-on-manager related functions
+  getTabMail(window) {
+    return window.document.getElementById("tabmail");
+  }
+
+  // returns the outer browser, not the nested browser of the add-on manager
+  // events must be attached to the outer browser
+  getAddonManagerFromTab(tab) {
+    if (tab.browser) {
+      let win = tab.browser.contentWindow;
+      if (win && win.location.href == "about:addons") {
+        return win;
+      }
+    }
+  }
+
+  getAddonManagerFromWindow(window) {
+    let tabMail = this.getTabMail(window);
+    for (let tab of tabMail.tabInfo) {
+      let win = this.getAddonManagerFromTab(tab);
+      if (win) {
+        return win;
+      }
+    }
+  }
+
+  setupAddonManager(managerWindow, forceLoad = false) {
+    if (!managerWindow) {
+      return;
+    }
+    if (!this.pathToOptionsPage) {
+      return;
+    }
+    if (
+      managerWindow &&
+      managerWindow[this.uniqueRandomID] &&
+      managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners
+    ) {
+      return;
+    }
+
+    managerWindow.document.addEventListener("ViewChanged", this);
+    managerWindow.document.addEventListener("update", this);
+    managerWindow.document.addEventListener("view-loaded", this);
+    managerWindow[this.uniqueRandomID] = {};
+    managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true;
+    if (forceLoad) {
+      this.handleEvent(managerWindow);
+    }
+  }
+
+  getMessenger(context) {
+    let apis = ["storage", "runtime", "extension", "i18n"];
+
+    function getStorage() {
+      let localstorage = null;
+      try {
+        localstorage = context.apiCan.findAPIPath("storage");
+        localstorage.local.get = (...args) =>
+          localstorage.local.callMethodInParentProcess("get", args);
+        localstorage.local.set = (...args) =>
+          localstorage.local.callMethodInParentProcess("set", args);
+        localstorage.local.remove = (...args) =>
+          localstorage.local.callMethodInParentProcess("remove", args);
+        localstorage.local.clear = (...args) =>
+          localstorage.local.callMethodInParentProcess("clear", args);
+      } catch (e) {
+        console.info("Storage permission is missing");
+      }
+      return localstorage;
+    }
+
+    let messenger = {};
+    for (let api of apis) {
+      switch (api) {
+        case "storage":
+          XPCOMUtils.defineLazyGetter(messenger, "storage", () => getStorage());
+          break;
+
+        default:
+          XPCOMUtils.defineLazyGetter(messenger, api, () =>
+            context.apiCan.findAPIPath(api)
+          );
+      }
+    }
+    return messenger;
+  }
+
+  error(msg) {
+    if (this.debug) console.error("WindowListener API: " + msg);
+  }
+
+  // async sleep function using Promise
+  async sleep(delay) {
+    let timer = Components.classes["@mozilla.org/timer;1"].createInstance(
+      Components.interfaces.nsITimer
+    );
+    return new Promise(function (resolve, reject) {
+      let event = {
+        notify: function (timer) {
+          resolve();
+        },
+      };
+      timer.initWithCallback(
+        event,
+        delay,
+        Components.interfaces.nsITimer.TYPE_ONE_SHOT
+      );
+    });
+  }
+
+  getAPI(context) {
+    // Track if this is the background/main context
+    if (context.viewType != "background")
+      throw new Error(
+        "The WindowListener API may only be called from the background page."
+      );
+
+    this.context = context;
+
+    this.uniqueRandomID = "AddOnNS" + context.extension.instanceId;
+    this.menu_addonPrefs_id = "addonPrefs";
+
+    this.registeredWindows = {};
+    this.pathToStartupScript = null;
+    this.pathToShutdownScript = null;
+    this.pathToOptionsPage = null;
+    this.chromeHandle = null;
+    this.chromeData = null;
+    this.resourceData = null;
+    this.openWindows = [];
+    this.debug = context.extension.addonData.temporarilyInstalled;
+
+    const aomStartup = Cc[
+      "@mozilla.org/addons/addon-manager-startup;1"
+    ].getService(Ci.amIAddonManagerStartup);
+    const resProto = Cc[
+      "@mozilla.org/network/protocol;1?name=resource"
+    ].getService(Ci.nsISubstitutingProtocolHandler);
+
+    let self = this;
+
+    // TabMonitor to detect opening of tabs, to setup the options button in the add-on manager.
+    this.tabMonitor = {
+      onTabTitleChanged(aTab) { },
+      onTabClosing(aTab) { },
+      onTabPersist(aTab) { },
+      onTabRestored(aTab) { },
+      onTabSwitched(aNewTab, aOldTab) {
+        //self.setupAddonManager(self.getAddonManagerFromTab(aNewTab));
+      },
+      async onTabOpened(aTab) {
+        if (aTab.browser) {
+          if (!aTab.pageLoaded) {
+            // await a location change if browser is not loaded yet
+            await new Promise((resolve) => {
+              let reporterListener = {
+                QueryInterface: ChromeUtils.generateQI([
+                  "nsIWebProgressListener",
+                  "nsISupportsWeakReference",
+                ]),
+                onStateChange() { },
+                onProgressChange() { },
+                onLocationChange(
+                  /* in nsIWebProgress*/ aWebProgress,
+                  /* in nsIRequest*/ aRequest,
+                  /* in nsIURI*/ aLocation
+                ) {
+                  aTab.browser.removeProgressListener(reporterListener);
+                  resolve();
+                },
+                onStatusChange() { },
+                onSecurityChange() { },
+                onContentBlockingEvent() { },
+              };
+              aTab.browser.addProgressListener(reporterListener);
+            });
+          }
+          self.setupAddonManager(self.getAddonManagerFromTab(aTab));
+          self._loadIntoWindow(aTab.browser.contentWindow, false);
+        }
+
+        if (aTab.chromeBrowser) {
+          self._loadIntoWindow(aTab.chromeBrowser.contentWindow, false);
+        }
+      },
+    };
+
+    return {
+      WindowListener: {
+        async waitForMasterPassword() {
+          // Wait until master password has been entered (if needed)
+          while (!Services.logins.isLoggedIn) {
+            self.log("Waiting for master password.");
+            await self.sleep(1000);
+          }
+          self.log("Master password has been entered.");
+        },
+
+        aDocumentExistsAt(uriString) {
+          // No sane way yet to detect if about:urls exists, maybe lookup the definition?
+          if (uriString.startsWith("about:")) {
+            return true;
+          }
+
+          self.log(
+            "Checking if document at <" +
+            uriString +
+            "> used in registration actually exists."
+          );
+          try {
+            let uriObject = Services.io.newURI(uriString);
+            let content = Cu.readUTF8URI(uriObject);
+          } catch (e) {
+            Components.utils.reportError(e);
+            return false;
+          }
+          return true;
+        },
+
+        registerOptionsPage(optionsUrl) {
+          self.pathToOptionsPage = optionsUrl.startsWith("chrome://")
+            ? optionsUrl
+            : context.extension.rootURI.resolve(optionsUrl);
+        },
+
+        registerDefaultPrefs(defaultUrl) {
+          let url = context.extension.rootURI.resolve(defaultUrl);
+
+          let prefsObj = {};
+          prefsObj.Services = ChromeUtils.import(
+            "resource://gre/modules/Services.jsm"
+          ).Services;
+          prefsObj.pref = function (aName, aDefault) {
+            let defaults = Services.prefs.getDefaultBranch("");
+            switch (typeof aDefault) {
+              case "string":
+                return defaults.setStringPref(aName, aDefault);
+
+              case "number":
+                return defaults.setIntPref(aName, aDefault);
+
+              case "boolean":
+                return defaults.setBoolPref(aName, aDefault);
+
+              default:
+                throw new Error(
+                  "Preference <" +
+                  aName +
+                  "> has an unsupported type <" +
+                  typeof aDefault +
+                  ">. Allowed are string, number and boolean."
+                );
+            }
+          };
+          Services.scriptloader.loadSubScript(url, prefsObj, "UTF-8");
+        },
+
+        registerChromeUrl(data) {
+          let chromeData = [];
+          let resourceData = [];
+          for (let entry of data) {
+            if (entry[0] == "resource") resourceData.push(entry);
+            else chromeData.push(entry);
+          }
+
+          if (chromeData.length > 0) {
+            const manifestURI = Services.io.newURI(
+              "manifest.json",
+              null,
+              context.extension.rootURI
+            );
+            self.chromeHandle = aomStartup.registerChrome(
+              manifestURI,
+              chromeData
+            );
+          }
+
+          for (let res of resourceData) {
+            // [ "resource", "shortname" , "path" ]
+            let uri = Services.io.newURI(
+              res[2],
+              null,
+              context.extension.rootURI
+            );
+            resProto.setSubstitutionWithFlags(
+              res[1],
+              uri,
+              resProto.ALLOW_CONTENT_ACCESS
+            );
+          }
+
+          self.chromeData = chromeData;
+          self.resourceData = resourceData;
+        },
+
+        registerWindow(windowHref, jsFile) {
+          if (self.debug && !this.aDocumentExistsAt(windowHref)) {
+            self.error(
+              "Attempt to register an injector script for non-existent window: " +
+              windowHref
+            );
+            return;
+          }
+
+          if (!self.registeredWindows.hasOwnProperty(windowHref)) {
+            // path to JS file can either be chrome:// URL or a relative URL
+            let path = jsFile.startsWith("chrome://")
+              ? jsFile
+              : context.extension.rootURI.resolve(jsFile);
+
+            self.registeredWindows[windowHref] = path;
+          } else {
+            self.error(
+              "Window <" + windowHref + "> has already been registered"
+            );
+          }
+        },
+
+        registerStartupScript(aPath) {
+          self.pathToStartupScript = aPath.startsWith("chrome://")
+            ? aPath
+            : context.extension.rootURI.resolve(aPath);
+        },
+
+        registerShutdownScript(aPath) {
+          self.pathToShutdownScript = aPath.startsWith("chrome://")
+            ? aPath
+            : context.extension.rootURI.resolve(aPath);
+        },
+
+        openOptionsDialog(windowId) {
+          let window = context.extension.windowManager.get(windowId, context)
+            .window;
+          let WL = {};
+          WL.extension = self.extension;
+          WL.messenger = self.getMessenger(self.context);
+          window.openDialog(
+            self.pathToOptionsPage,
+            "AddonOptions",
+            "chrome,resizable,centerscreen",
+            WL
+          );
+        },
+
+        async startListening() {
+          // load the registered startup script, if one has been registered
+          // (mail3:pane may not have been fully loaded yet)
+          if (self.pathToStartupScript) {
+            let startupJS = {};
+            startupJS.WL = {};
+            startupJS.WL.extension = self.extension;
+            startupJS.WL.messenger = self.getMessenger(self.context);
+            try {
+              if (self.pathToStartupScript) {
+                Services.scriptloader.loadSubScript(
+                  self.pathToStartupScript,
+                  startupJS,
+                  "UTF-8"
+                );
+                // delay startup until startup has been finished
+                self.log(
+                  "Waiting for async startup() in <" +
+                  self.pathToStartupScript +
+                  "> to finish."
+                );
+                if (startupJS.startup) {
+                  await startupJS.startup();
+                  self.log(
+                    "startup() in <" + self.pathToStartupScript + "> finished"
+                  );
+                } else {
+                  self.log(
+                    "No startup() in <" + self.pathToStartupScript + "> found."
+                  );
+                }
+              }
+            } catch (e) {
+              Components.utils.reportError(e);
+            }
+          }
+
+          let urls = Object.keys(self.registeredWindows);
+          if (urls.length > 0) {
+            // Before registering the window listener, check which windows are already open
+            self.openWindows = [];
+            for (let window of Services.wm.getEnumerator(null)) {
+              self.openWindows.push(window);
+            }
+
+            // Register window listener for all pre-registered windows
+            ExtensionSupport.registerWindowListener(
+              "injectListener_" + self.uniqueRandomID,
+              {
+                // React on all windows and manually reduce to the registered
+                // windows, so we can do special actions when the main
+                // messenger window is opened.
+                //chromeURLs: Object.keys(self.registeredWindows),
+                async onLoadWindow(window) {
+                  // Load JS into window
+                  await self._loadIntoWindow(
+                    window,
+                    self.openWindows.includes(window)
+                  );
+                },
+
+                onUnloadWindow(window) {
+                  // Remove JS from window, window is being closed, addon is not shut down
+                  self._unloadFromWindow(window, false);
+                },
+              }
+            );
+          } else {
+            self.error("Failed to start listening, no windows registered");
+          }
+        },
+      },
+    };
+  }
+
+  _loadIntoNestedBrowsers(window, isAddonActivation) {
+    let elements = [];
+    elements = elements.concat(...window.document.getElementsByTagName("browser"));
+    elements = elements.concat(...window.document.getElementsByTagName("xul:browser"));
+    for (let element of elements) {
+      this._loadIntoWindow(element.contentWindow, isAddonActivation);
+    }
+
+  }
+
+  async _loadIntoWindow(window, isAddonActivation) {
+    const fullyLoaded = async window => {
+      for (let i = 0; i < 10; i++) {
+        await this.sleep(100);
+        if (
+          window &&
+          window.location.href != "about:blank" &&
+          window.document.readyState == "complete"
+        ) {
+          return;
+        }
+      }
+    }
+
+    await fullyLoaded(window);
+    if (!window || window.hasOwnProperty(this.uniqueRandomID)) {
+      return;
+    }
+
+    // Special action if this is the main messenger window.
+    if (window.location.href == "chrome://messenger/content/messenger.xhtml") {
+      // Add a tab monitor. The tabMonitor checks newly opened tabs and injects us.
+      this.getTabMail(window).registerTabMonitor(this.tabMonitor);
+      window[this.uniqueRandomID] = {};
+      window[this.uniqueRandomID].hasTabMonitor = true;
+
+      // Setup the options button/menu in the add-on manager, if it is already open.
+      this.setupAddonManager(this.getAddonManagerFromWindow(window), true);
+    }
+
+    // Load into nested browsers
+    this._loadIntoNestedBrowsers(window, isAddonActivation);
+
+    if (this.registeredWindows.hasOwnProperty(window.location.href)) {
+      if (!window.hasOwnProperty(this.uniqueRandomID)) {
+        window[this.uniqueRandomID] = {};
+      }
+
+      try {
+        let uniqueRandomID = this.uniqueRandomID;
+        let extension = this.extension;
+
+        // Add reference to window to add-on scope
+        window[this.uniqueRandomID].window = window;
+        window[this.uniqueRandomID].document = window.document;
+
+        // Keep track of toolbarpalettes we are injecting into
+        window[this.uniqueRandomID]._toolbarpalettes = {};
+
+        //Create WLDATA object
+        window[this.uniqueRandomID].WL = {};
+        window[this.uniqueRandomID].WL.scopeName = this.uniqueRandomID;
+
+        // Add helper function to inject CSS to WLDATA object
+        window[this.uniqueRandomID].WL.injectCSS = function (cssFile) {
+          let element;
+          let v = parseInt(Services.appinfo.version.split(".").shift());
+
+          // using createElementNS in TB78 delays the insert process and hides any security violation errors
+          if (v > 68) {
+            element = window.document.createElement("link");
+          } else {
+            let ns = window.document.documentElement.lookupNamespaceURI("html");
+            element = window.document.createElementNS(ns, "link");
+          }
+
+          element.setAttribute("wlapi_autoinjected", uniqueRandomID);
+          element.setAttribute("rel", "stylesheet");
+          element.setAttribute("href", cssFile);
+          return window.document.documentElement.appendChild(element);
+        };
+
+        // Add helper function to inject XUL to WLDATA object
+        window[this.uniqueRandomID].WL.injectElements = function (
+          xulString,
+          dtdFiles = [],
+          debug = false
+        ) {
+          let toolbarsToResolve = [];
+
+          function checkElements(stringOfIDs) {
+            let arrayOfIDs = stringOfIDs.split(",").map((e) => e.trim());
+            for (let id of arrayOfIDs) {
+              let element = window.document.getElementById(id);
+              if (element) {
+                return element;
+              }
+            }
+            return null;
+          }
+
+          function localize(entity) {
+            let msg = entity.slice("__MSG_".length, -2);
+            return extension.localeData.localizeMessage(msg);
+          }
+
+          function injectChildren(elements, container) {
+            if (debug) console.log(elements);
+
+            for (let i = 0; i < elements.length; i++) {
+              // take care of persists
+              const uri = window.document.documentURI;
+              for (const persistentNode of elements[i].querySelectorAll(
+                "[persist]"
+              )) {
+                for (const persistentAttribute of persistentNode
+                  .getAttribute("persist")
+                  .trim()
+                  .split(" ")) {
+                  if (
+                    Services.xulStore.hasValue(
+                      uri,
+                      persistentNode.id,
+                      persistentAttribute
+                    )
+                  ) {
+                    persistentNode.setAttribute(
+                      persistentAttribute,
+                      Services.xulStore.getValue(
+                        uri,
+                        persistentNode.id,
+                        persistentAttribute
+                      )
+                    );
+                  }
+                }
+              }
+
+              if (
+                elements[i].hasAttribute("insertafter") &&
+                checkElements(elements[i].getAttribute("insertafter"))
+              ) {
+                let insertAfterElement = checkElements(
+                  elements[i].getAttribute("insertafter")
+                );
+
+                if (debug)
+                  console.log(
+                    elements[i].tagName +
+                    "#" +
+                    elements[i].id +
+                    ": insertafter " +
+                    insertAfterElement.id
+                  );
+                if (
+                  debug &&
+                  elements[i].id &&
+                  window.document.getElementById(elements[i].id)
+                ) {
+                  console.error(
+                    "The id <" +
+                    elements[i].id +
+                    "> of the injected element already exists in the document!"
+                  );
+                }
+                elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
+                insertAfterElement.parentNode.insertBefore(
+                  elements[i],
+                  insertAfterElement.nextSibling
+                );
+              } else if (
+                elements[i].hasAttribute("insertbefore") &&
+                checkElements(elements[i].getAttribute("insertbefore"))
+              ) {
+                let insertBeforeElement = checkElements(
+                  elements[i].getAttribute("insertbefore")
+                );
+
+                if (debug)
+                  console.log(
+                    elements[i].tagName +
+                    "#" +
+                    elements[i].id +
+                    ": insertbefore " +
+                    insertBeforeElement.id
+                  );
+                if (
+                  debug &&
+                  elements[i].id &&
+                  window.document.getElementById(elements[i].id)
+                ) {
+                  console.error(
+                    "The id <" +
+                    elements[i].id +
+                    "> of the injected element already exists in the document!"
+                  );
+                }
+                elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
+                insertBeforeElement.parentNode.insertBefore(
+                  elements[i],
+                  insertBeforeElement
+                );
+              } else if (
+                elements[i].id &&
+                window.document.getElementById(elements[i].id)
+              ) {
+                // existing container match, dive into recursivly
+                if (debug)
+                  console.log(
+                    elements[i].tagName +
+                    "#" +
+                    elements[i].id +
+                    " is an existing container, injecting into " +
+                    elements[i].id
+                  );
+                injectChildren(
+                  Array.from(elements[i].children),
+                  window.document.getElementById(elements[i].id)
+                );
+              } else if (elements[i].localName === "toolbarpalette") {
+                // These vanish from the document but still exist via the palette property
+                if (debug) console.log(elements[i].id + " is a toolbarpalette");
+                let boxes = [
+                  ...window.document.getElementsByTagName("toolbox"),
+                ];
+                let box = boxes.find(
+                  (box) => box.palette && box.palette.id === elements[i].id
+                );
+                let palette = box ? box.palette : null;
+
+                if (!palette) {
+                  if (debug)
+                    console.log(
+                      `The palette for ${elements[i].id} could not be found, deferring to later`
+                    );
+                  continue;
+                }
+
+                if (debug)
+                  console.log(`The toolbox for ${elements[i].id} is ${box.id}`);
+
+                toolbarsToResolve.push(...box.querySelectorAll("toolbar"));
+                toolbarsToResolve.push(
+                  ...window.document.querySelectorAll(
+                    `toolbar[toolboxid="${box.id}"]`
+                  )
+                );
+                for (let child of elements[i].children) {
+                  child.setAttribute("wlapi_autoinjected", uniqueRandomID);
+                }
+                window[uniqueRandomID]._toolbarpalettes[palette.id] = palette;
+                injectChildren(Array.from(elements[i].children), palette);
+              } else {
+                // append element to the current container
+                if (debug)
+                  console.log(
+                    elements[i].tagName +
+                    "#" +
+                    elements[i].id +
+                    ": append to " +
+                    container.id
+                  );
+                elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
+                container.appendChild(elements[i]);
+              }
+            }
+          }
+
+          if (debug) console.log("Injecting into root document:");
+          let localizedXulString = xulString.replace(
+            /__MSG_(.*?)__/g,
+            localize
+          );
+          injectChildren(
+            Array.from(
+              window.MozXULElement.parseXULToFragment(
+                localizedXulString,
+                dtdFiles
+              ).children
+            ),
+            window.document.documentElement
+          );
+
+          for (let bar of toolbarsToResolve) {
+            let currentset = Services.xulStore.getValue(
+              window.location,
+              bar.id,
+              "currentset"
+            );
+            if (currentset) {
+              bar.currentSet = currentset;
+            } else if (bar.getAttribute("defaultset")) {
+              bar.currentSet = bar.getAttribute("defaultset");
+            }
+          }
+        };
+
+        // Add extension object to WLDATA object
+        window[this.uniqueRandomID].WL.extension = this.extension;
+        // Add messenger object to WLDATA object
+        window[this.uniqueRandomID].WL.messenger = this.getMessenger(
+          this.context
+        );
+        // Load script into add-on scope
+        Services.scriptloader.loadSubScript(
+          this.registeredWindows[window.location.href],
+          window[this.uniqueRandomID],
+          "UTF-8"
+        );
+        window[this.uniqueRandomID].onLoad(isAddonActivation);
+      } catch (e) {
+        Components.utils.reportError(e);
+      }
+    }
+  }
+
+  _unloadFromWindow(window, isAddonDeactivation) {
+    // Unload any contained browser elements.
+    let elements = [];
+    elements = elements.concat(...window.document.getElementsByTagName("browser"));
+    elements = elements.concat(...window.document.getElementsByTagName("xul:browser"));
+    for (let element of elements) {
+      if (element.contentWindow) {
+        this._unloadFromWindow(
+          element.contentWindow,
+          isAddonDeactivation
+        );
+      }
+    }
+
+    if (
+      window.hasOwnProperty(this.uniqueRandomID) &&
+      this.registeredWindows.hasOwnProperty(window.location.href)
+    ) {
+      // Remove this window from the list of open windows
+      this.openWindows = this.openWindows.filter((e) => e != window);
+
+      if (window[this.uniqueRandomID].onUnload) {
+        try {
+          // Call onUnload()
+          window[this.uniqueRandomID].onUnload(isAddonDeactivation);
+        } catch (e) {
+          Components.utils.reportError(e);
+        }
+      }
+
+      // Remove all auto injected objects
+      let elements = Array.from(
+        window.document.querySelectorAll(
+          '[wlapi_autoinjected="' + this.uniqueRandomID + '"]'
+        )
+      );
+      for (let element of elements) {
+        element.remove();
+      }
+
+      // Remove all autoinjected toolbarpalette items
+      for (const palette of Object.values(
+        window[this.uniqueRandomID]._toolbarpalettes
+      )) {
+        let elements = Array.from(
+          palette.querySelectorAll(
+            '[wlapi_autoinjected="' + this.uniqueRandomID + '"]'
+          )
+        );
+        for (let element of elements) {
+          element.remove();
+        }
+      }
+    }
+
+    // Remove add-on scope, if it exists
+    if (window.hasOwnProperty(this.uniqueRandomID)) {
+      delete window[this.uniqueRandomID];
+    }
+  }
+
+  onShutdown(isAppShutdown) {
+    if (isAppShutdown) {
+      return; // the application gets unloaded anyway
+    }
+
+    // Unload from all still open windows
+    let urls = Object.keys(this.registeredWindows);
+    if (urls.length > 0) {
+      for (let window of Services.wm.getEnumerator(null)) {
+        //remove our entry in the add-on options menu
+        if (window.location.href == "chrome://messenger/content/messenger.xhtml") {
+          // Remove event listener for addon manager view changes
+          let managerWindow = this.getAddonManagerFromWindow(window);
+          if (
+            managerWindow &&
+            managerWindow[this.uniqueRandomID] && 
+            managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners
+          ) {
+            managerWindow.document.removeEventListener("ViewChanged", this);
+            managerWindow.document.removeEventListener("view-loaded", this);
+            managerWindow.document.removeEventListener("update", this);
+            managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false;
+
+            let buttons = managerWindow.document.getElementsByClassName("extension-options-button");
+            for (let button of buttons) {
+              button.removeAttribute("hidden");
+            }
+            let cards = this.getCards(managerWindow);
+            // Remove options button in 88+
+            for (let card of cards) {
+              if (card.addon.id == this.extension.id) {
+                let origAddonOptionsButton = card.querySelector(".extension-options-button")
+                origAddonOptionsButton.removeAttribute("hidden");
+
+                let addonOptionsButton = card.querySelector(
+                  ".windowlistener-options-button"
+                );
+                if (addonOptionsButton) addonOptionsButton.remove();
+                break;
+              }
+            }
+          }
+
+          // Remove tabmonitor
+          if (window[this.uniqueRandomID].hasTabMonitor) {
+            this.getTabMail(window).unregisterTabMonitor(this.tabMonitor);
+            window[this.uniqueRandomID].hasTabMonitor = false;
+          }
+        }
+
+        // if it is app shutdown, it is not just an add-on deactivation
+        this._unloadFromWindow(window, !isAppShutdown);
+      }
+      // Stop listening for new windows.
+      ExtensionSupport.unregisterWindowListener(
+        "injectListener_" + this.uniqueRandomID
+      );
+    }
+
+    // Load registered shutdown script
+    let shutdownJS = {};
+    shutdownJS.extension = this.extension;
+    try {
+      if (this.pathToShutdownScript)
+        Services.scriptloader.loadSubScript(
+          this.pathToShutdownScript,
+          shutdownJS,
+          "UTF-8"
+        );
+    } catch (e) {
+      Components.utils.reportError(e);
+    }
+
+    // Extract all registered chrome content urls
+    let chromeUrls = [];
+    if (this.chromeData) {
+      for (let chromeEntry of this.chromeData) {
+        if (chromeEntry[0].toLowerCase().trim() == "content") {
+          chromeUrls.push("chrome://" + chromeEntry[1] + "/");
+        }
+      }
+    }
+
+    // Unload JSMs of this add-on
+    const rootURI = this.extension.rootURI.spec;
+    for (let module of Cu.loadedModules) {
+      if (
+        module.startsWith(rootURI) ||
+        (module.startsWith("chrome://") &&
+          chromeUrls.find((s) => module.startsWith(s)))
+      ) {
+        this.log("Unloading: " + module);
+        Cu.unload(module);
+      }
+    }
+
+    // Flush all caches
+    Services.obs.notifyObservers(null, "startupcache-invalidate");
+    this.registeredWindows = {};
+
+    if (this.resourceData) {
+      const resProto = Cc[
+        "@mozilla.org/network/protocol;1?name=resource"
+      ].getService(Ci.nsISubstitutingProtocolHandler);
+      for (let res of this.resourceData) {
+        // [ "resource", "shortname" , "path" ]
+        resProto.setSubstitution(res[1], null);
+      }
+    }
+
+    if (this.chromeHandle) {
+      this.chromeHandle.destruct();
+      this.chromeHandle = null;
+    }
+  }
+};
+
+var WindowListener = getThunderbirdVersion().major < 111
+  ? WindowListener_102
+  : WindowListener_115;
diff -Nru quicktext-5.2/api/WindowListener/schema.json quicktext-5.6/api/WindowListener/schema.json
--- quicktext-5.2/api/WindowListener/schema.json	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.6/api/WindowListener/schema.json	2023-06-25 12:48:31.000000000 +0200
@@ -1,6 +1,6 @@
 [
   {
-    "namespace": "WindowListener",    
+    "namespace": "WindowListener",
     "functions": [
       {
         "name": "registerDefaultPrefs",
@@ -34,7 +34,7 @@
             "type": "array",
             "items": {
               "type": "array",
-              "items" : {
+              "items": {
                 "type": "string"
               }
             },
@@ -105,4 +105,4 @@
       }
     ]
   }
-]
+]
\ Kein Zeilenumbruch am Dateiende.
diff -Nru quicktext-5.2/chrome/content/modules/wzQuicktext.jsm quicktext-5.6/chrome/content/modules/wzQuicktext.jsm
--- quicktext-5.2/chrome/content/modules/wzQuicktext.jsm	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.6/chrome/content/modules/wzQuicktext.jsm	2023-06-25 12:48:31.000000000 +0200
@@ -1,7 +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 +138,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 +641,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)
@@ -752,7 +749,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 +770,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 +1011,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 +1030,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.6/chrome/content/modules/wzQuicktextVar.jsm
--- quicktext-5.2/chrome/content/modules/wzQuicktextVar.jsm	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.6/chrome/content/modules/wzQuicktextVar.jsm	2023-06-25 12:48:31.000000000 +0200
@@ -328,6 +328,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]);
       }
@@ -637,7 +639,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 +662,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 = {};
@@ -939,7 +941,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)
     {
diff -Nru quicktext-5.2/chrome/content/quicktext.js quicktext-5.6/chrome/content/quicktext.js
--- quicktext-5.2/chrome/content/quicktext.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.6/chrome/content/quicktext.js	2023-06-25 12:48:31.000000000 +0200
@@ -460,7 +460,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 +469,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 +715,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/settings.js quicktext-5.6/chrome/content/settings.js
--- quicktext-5.2/chrome/content/settings.js	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.6/chrome/content/settings.js	2023-06-25 12:48:31.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.6/chrome/locale/ja/quicktext.dtd
--- quicktext-5.2/chrome/locale/ja/quicktext.dtd	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.6/chrome/locale/ja/quicktext.dtd	2023-06-25 12:48:31.000000000 +0200
@@ -3,26 +3,26 @@
 
 <!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.collapseSetting.label "グループにテンプレートが1つしか含まれていない時は、グループとして表示しない">
@@ -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.accesskey.addScript "a">
 <!ENTITY quicktext.getScript.label "Community Scripts">
-<!ENTITY quicktext.accesskey.communityScripts "s">
+<!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 "グループ">
diff -Nru quicktext-5.2/debian/changelog quicktext-5.6/debian/changelog
--- quicktext-5.2/debian/changelog	2022-09-25 13:17:44.000000000 +0200
+++ quicktext-5.6/debian/changelog	2023-08-10 15:53:35.000000000 +0200
@@ -1,3 +1,19 @@
+quicktext (5.6-1~deb12u1) bookworm; urgency=medium
+
+  * Rebuild for bookworm
+    + for the updated thunderbird to 115.*
+
+ -- Mechtilde Stehmann <mechtilde@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@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.6/debian/control
--- quicktext-5.2/debian/control	2022-09-25 13:11:21.000000000 +0200
+++ quicktext-5.6/debian/control	2023-08-10 08:47:43.000000000 +0200
@@ -5,7 +5,7 @@
 Uploaders: Mechtilde Stehmann <mechtilde@debian.org>
 Build-Depends: debhelper-compat (=13)
  , zip
-Standards-Version: 4.6.1
+Standards-Version: 4.6.2
 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,7 @@
 Package: webext-quicktext
 Architecture: all
 Depends: ${misc:Depends}
- , thunderbird (>= 1:102.2)
+ , thunderbird (>= 1:115.1)
 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/manifest.json quicktext-5.6/manifest.json
--- quicktext-5.2/manifest.json	2022-07-04 10:54:15.000000000 +0200
+++ quicktext-5.6/manifest.json	2023-06-25 12:48:31.000000000 +0200
@@ -4,11 +4,11 @@
     "gecko": {
       "id": "{8845E3B3-E8FB-40E2-95E9-EC40294818C4}",
       "strict_min_version": "100.0",
-      "strict_max_version": "102.*"
+      "strict_max_version": "115.*"
     }
   },
   "name": "Quicktext",
-  "version": "5.2",
+  "version": "5.6",
   "author": "John Bieling",
   "homepage_url": "https://github.com/jobisoft/quicktext";,
   "default_locale": "en-US",

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature


Reply to: