Version in base suite: 5.2-1 Base version: quicktext_5.2-1 Target version: quicktext_5.16-1~deb12u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/q/quicktext/quicktext_5.2-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/q/quicktext/quicktext_5.16-1~deb12u1.dsc /srv/release.debian.org/tmp/3ADuyrwlZg/quicktext-5.16/chrome/skin/button.large.png |binary /srv/release.debian.org/tmp/3ADuyrwlZg/quicktext-5.16/chrome/skin/button.small.png |binary /srv/release.debian.org/tmp/3ADuyrwlZg/quicktext-5.16/chrome/skin/logo.png |binary /srv/release.debian.org/tmp/3ADuyrwlZg/quicktext-5.16/screenshoots/donate.png |binary quicktext-5.16/CONTRIBUTORS.md | 1 quicktext-5.16/_locales/ja/messages.json | 2 quicktext-5.16/api/LegacyPrefs/README.md | 35 quicktext-5.16/api/LegacyPrefs/implementation.js | 183 +- quicktext-5.16/api/LegacyPrefs/schema.json | 22 quicktext-5.16/api/NotifyTools/README.md | 62 quicktext-5.16/api/NotifyTools/implementation.js | 157 +- quicktext-5.16/api/NotifyTools/schema.json | 4 quicktext-5.16/api/WindowListener/CHANGELOG.md | 35 quicktext-5.16/api/WindowListener/implementation.js | 673 ++-------- quicktext-5.16/api/WindowListener/schema.json | 45 quicktext-5.16/background.js | 1 quicktext-5.16/chrome/content/modules/utils.jsm | 2 quicktext-5.16/chrome/content/modules/wzQuicktext.jsm | 55 quicktext-5.16/chrome/content/modules/wzQuicktextGroup.jsm | 23 quicktext-5.16/chrome/content/modules/wzQuicktextVar.jsm | 199 +- quicktext-5.16/chrome/content/notifyTools/notifyTools.js | 102 + quicktext-5.16/chrome/content/quicktext.js | 43 quicktext-5.16/chrome/content/scripts/messenger.js | 2 quicktext-5.16/chrome/content/scripts/messengercompose.js | 33 quicktext-5.16/chrome/content/settings.js | 11 quicktext-5.16/chrome/locale/ja/quicktext.dtd | 66 quicktext-5.16/chrome/locale/ja/quicktext.properties | 10 quicktext-5.16/chrome/skin/quicktext.css | 29 quicktext-5.16/debian/changelog | 39 quicktext-5.16/debian/control | 5 quicktext-5.16/manifest.json | 13 31 files changed, 860 insertions(+), 992 deletions(-) diff -Nru quicktext-5.2/CONTRIBUTORS.md quicktext-5.16/CONTRIBUTORS.md --- quicktext-5.2/CONTRIBUTORS.md 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/CONTRIBUTORS.md 2024-07-26 11:08:57.000000000 +0000 @@ -20,3 +20,4 @@ ## Translators * Alexey Sinitsyn (ru) * Óvári (hu) +* Ryota Murai (ja) \ No newline at end of file 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 08:54:15.000000000 +0000 +++ quicktext-5.16/_locales/ja/messages.json 2024-07-26 11:08:57.000000000 +0000 @@ -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/api/LegacyPrefs/README.md quicktext-5.16/api/LegacyPrefs/README.md --- quicktext-5.2/api/LegacyPrefs/README.md 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/api/LegacyPrefs/README.md 2024-07-26 11:08:57.000000000 +0000 @@ -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/implementation.js quicktext-5.16/api/LegacyPrefs/implementation.js --- quicktext-5.2/api/LegacyPrefs/implementation.js 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/api/LegacyPrefs/implementation.js 2024-07-26 11:08:57.000000000 +0000 @@ -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@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/schema.json quicktext-5.16/api/LegacyPrefs/schema.json --- quicktext-5.2/api/LegacyPrefs/schema.json 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/api/LegacyPrefs/schema.json 2024-07-26 11:08:57.000000000 +0000 @@ -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/README.md quicktext-5.16/api/NotifyTools/README.md --- quicktext-5.2/api/NotifyTools/README.md 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/api/NotifyTools/README.md 2024-07-26 11:08:57.000000000 +0000 @@ -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()`. \ No newline at end of file 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 08:54:15.000000000 +0000 +++ quicktext-5.16/api/NotifyTools/implementation.js 2024-07-26 11:08:57.000000000 +0000 @@ -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@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/schema.json quicktext-5.16/api/NotifyTools/schema.json --- quicktext-5.2/api/NotifyTools/schema.json 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/api/NotifyTools/schema.json 2024-07-26 11:08:57.000000000 +0000 @@ -1,6 +1,6 @@ [ { - "namespace": "NotifyTools", + "namespace": "NotifyTools", "events": [ { "name": "onNotifyBackground", @@ -31,4 +31,4 @@ } ] } -] +] \ No newline at end of file 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 08:54:15.000000000 +0000 +++ quicktext-5.16/api/WindowListener/CHANGELOG.md 2024-07-26 11:08:57.000000000 +0000 @@ -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 08:54:15.000000000 +0000 +++ quicktext-5.16/api/WindowListener/implementation.js 2024-07-26 11:08:57.000000000 +0000 @@ -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@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( - `` - ) - : event.target.ownerGlobal.MozXULElement.parseXULToFragment( - `` - ); - - 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; } } -}; +}; \ No newline at end of file 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 08:54:15.000000000 +0000 +++ quicktext-5.16/api/WindowListener/schema.json 2024-07-26 11:08:57.000000000 +0000 @@ -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." - } - ] } ] } -] +] \ No newline at end of file diff -Nru quicktext-5.2/background.js quicktext-5.16/background.js --- quicktext-5.2/background.js 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/background.js 2024-07-26 11:08:57.000000000 +0000 @@ -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 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/content/modules/utils.jsm 2024-07-26 11:08:57.000000000 +0000 @@ -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/wzQuicktext.jsm quicktext-5.16/chrome/content/modules/wzQuicktext.jsm --- quicktext-5.2/chrome/content/modules/wzQuicktext.jsm 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/content/modules/wzQuicktext.jsm 2024-07-26 11:08:57.000000000 +0000 @@ -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\n"; + buffer += "\t\n"; } } buffer += ""; @@ -773,15 +781,15 @@ for (var j = 0; j < this.mTexts[i].length; j++) { var text = this.mTexts[i][j]; - buffer += "\t\t\t\n\t\t\t\t\n"; + buffer += "\t\t\t\n\t\t\t\t\n"; if (text.keyword != "") - buffer += "\t\t\t\t\n"; + buffer += "\t\t\t\t\n"; if (text.subject != "") - buffer += "\t\t\t\t\n"; + buffer += "\t\t\t\t\n"; if (text.text != "") - buffer += "\t\t\t\t\n"; + buffer += "\t\t\t\t\n"; if (text.attachments != "") - buffer += "\t\t\t\t\n"; + buffer += "\t\t\t\t\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 ']]]]>', need a regex replace to replace every occurence + return aStr.replace(new RegExp("["+ kIllegalChars +"]", 'g'), '').replace(/\]\]>/g, ']]]]>'); + } +, /* * OBSERVERS */ 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 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/content/modules/wzQuicktextGroup.jsm 2024-07-26 11:08:57.000000000 +0000 @@ -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/wzQuicktextVar.jsm quicktext-5.16/chrome/content/modules/wzQuicktextVar.jsm --- quicktext-5.2/chrome/content/modules/wzQuicktextVar.jsm 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/content/modules/wzQuicktextVar.jsm 2024-07-26 11:08:57.000000000 +0000 @@ -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 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/content/notifyTools/notifyTools.js 2024-07-26 11:08:57.000000000 +0000 @@ -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 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/content/quicktext.js 2024-07-26 11:08:57.000000000 +0000 @@ -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/messenger.js quicktext-5.16/chrome/content/scripts/messenger.js --- quicktext-5.2/chrome/content/scripts/messenger.js 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/content/scripts/messenger.js 2024-07-26 11:08:57.000000000 +0000 @@ -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/scripts/messengercompose.js quicktext-5.16/chrome/content/scripts/messengercompose.js --- quicktext-5.2/chrome/content/scripts/messengercompose.js 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/content/scripts/messengercompose.js 2024-07-26 11:08:57.000000000 +0000 @@ -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(` - - + + - + - + + + + - `, + + `, ["chrome://quicktext/locale/quicktext.dtd"]); window.quicktext.load(); 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 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/content/settings.js 2024-07-26 11:08:57.000000000 +0000 @@ -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 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/locale/ja/quicktext.dtd 2024-07-26 11:08:57.000000000 +0000 @@ -3,32 +3,32 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -48,25 +48,25 @@ - - - + + + - - - + + + - + - + - - - + + + - + @@ -97,18 +97,18 @@ - - + + - - - - - - - + + + + + + + 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 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/locale/ja/quicktext.properties 2024-07-26 11:08:57.000000000 +0000 @@ -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=行 Binary files /srv/release.debian.org/tmp/HoU5pw_W9_/quicktext-5.2/chrome/skin/button.large.png and /srv/release.debian.org/tmp/3ADuyrwlZg/quicktext-5.16/chrome/skin/button.large.png differ Binary files /srv/release.debian.org/tmp/HoU5pw_W9_/quicktext-5.2/chrome/skin/button.small.png and /srv/release.debian.org/tmp/3ADuyrwlZg/quicktext-5.16/chrome/skin/button.small.png differ Binary files /srv/release.debian.org/tmp/HoU5pw_W9_/quicktext-5.2/chrome/skin/logo.png and /srv/release.debian.org/tmp/3ADuyrwlZg/quicktext-5.16/chrome/skin/logo.png differ 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 08:54:15.000000000 +0000 +++ quicktext-5.16/chrome/skin/quicktext.css 2024-07-26 11:08:57.000000000 +0000 @@ -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/debian/changelog quicktext-5.16/debian/changelog --- quicktext-5.2/debian/changelog 2022-09-25 11:17:44.000000000 +0000 +++ quicktext-5.16/debian/changelog 2024-09-18 10:21:50.000000000 +0000 @@ -1,3 +1,42 @@ +quicktext (5.16-1~deb12u1) bookworm; urgency=medium + + [ Mechtilde ] + * [9d327fa] Prepared rebuild for bookworm + + -- Mechtilde Stehmann 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 Sun, 01 Sep 2024 17:03:31 +0200 + +quicktext (5.10-1) unstable; urgency=medium + + [ Mechtilde ] + * [009df44] New upstream version 5.10 + + -- Mechtilde Stehmann 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 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 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 11:11:21.000000000 +0000 +++ quicktext-5.16/debian/control 2024-09-18 10:11:20.000000000 +0000 @@ -5,7 +5,7 @@ Uploaders: Mechtilde Stehmann 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/manifest.json quicktext-5.16/manifest.json --- quicktext-5.2/manifest.json 2022-07-04 08:54:15.000000000 +0000 +++ quicktext-5.16/manifest.json 2024-07-26 11:08:57.000000000 +0000 @@ -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": { Binary files /srv/release.debian.org/tmp/HoU5pw_W9_/quicktext-5.2/screenshoots/donate.png and /srv/release.debian.org/tmp/3ADuyrwlZg/quicktext-5.16/screenshoots/donate.png differ