diff --git a/Makefile b/Makefile
index 9e3ca29..1e193cb 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,8 @@
PREFIX := /usr
DATADIR := $(PREFIX)/share
+# the profile dir is copied and left unmodified by web-ext
+PROFILE := /home/xavier/.thunderbird/6ujxy9ck.default-release-2/
# UUID below is for Thunderbird
TB_EXT_DIR := $(DATADIR)/mozilla/extensions/{3550f703-e582-4d05-9a08-453d09bdfdc6}
@@ -17,3 +19,8 @@ clean:
install: all
install -d $(DESTDIR)$(TB_EXT_DIR)
install --mode=644 *.xpi $(DESTDIR)$(TB_EXT_DIR)
+
+run:
+ web-ext run --verbose --firefox=/usr/bin/thunderbird --firefox-profile=$(PROFILE) -s xul-ext/
+lint:
+ web-ext lint -s xul-ext/
\ No newline at end of file
diff --git a/xul-ext/api/windowListener/implementation.js b/xul-ext/api/windowListener/implementation.js
new file mode 100644
index 0000000..1e5050d
--- /dev/null
+++ b/xul-ext/api/windowListener/implementation.js
@@ -0,0 +1,1194 @@
+/*
+ * This file is provided by the addon-developer-support repository at
+ * https://github.com/thundernest/addon-developer-support
+ *
+ * Version: 1.57
+ *
+ * Author: John Bieling (john@thunderbird.net)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// Import some things 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 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]),
+ revision: parts.length > 2 ? parseInt(parts[2]) : 0,
+ }
+ }
+
+ getCards(e) {
+ // This gets triggered by real events but also manually by providing the outer window.
+ // The event is attached to the outer browser, get the inner one.
+ let doc;
+
+ // 78,86, and 87+ need special handholding. *Yeah*.
+ if (this.getThunderbirdVersion().major < 86) {
+ 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;
+ }
+ 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
+ );
+ }
+ 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
+ //window.document.getElementById(id).addEventListener("command", function() {window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", WL)});
+ if (card.addon.id == this.extension.id) {
+ let optionsMenu =
+ (this.getThunderbirdVersion().major > 78 && this.getThunderbirdVersion().major < 88) ||
+ (this.getThunderbirdVersion().major == 78 && this.getThunderbirdVersion().minor < 10) ||
+ (this.getThunderbirdVersion().major == 78 && this.getThunderbirdVersion().minor == 10 && this.getThunderbirdVersion().revision < 2);
+ 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"
+ );
+ 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();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // 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 && tab.mode.name == "contentTab") {
+ 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 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;
+ }
+ }
+ }
+ }
+
+ setupAddonManager(managerWindow, forceLoad = false) {
+ if (!managerWindow) {
+ 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;
+ }
+ 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(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(tab));
+ }
+ },
+ };
+
+ 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) {
+ 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);
+ if (windowHref == "chrome://messenger/content/commonDialog.xhtml") {
+ // @XN-BUGFIX
+ // there is a bug somewhere with aDocumentExistsAt and this very specific window
+ // the existence test fails for chrome://global/content/commonDialog.xhtml
+ // and succeed for chrome://messenger/content/commonDialog.xhtml
+ // BUT this commonDialog from messenger fails to _loadIntoWindow(window, isAddonActivation) and does not activate
+ // WORKAROUD : set xhtml namespace to global for this url when it's supposed to be in messenger
+ self.log(
+ "reverting chrome commonDialog from messenger to global"
+ );
+ windowHref = "chrome://global/content/commonDialog.xhtml";
+ }
+
+ 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) {
+ // 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 {
+ // 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);
+ }
+ }
+ }
+
+ // 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(
+ 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");
+ }
+ },
+ },
+ };
+ }
+
+ _loadIntoWindow(window, isAddonActivation) {
+ if (
+ window.hasOwnProperty(this.uniqueRandomID) &&
+ this.registeredWindows.hasOwnProperty(window.location.href)
+ ) {
+ 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
+ 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
+ );
+ }
+ }
+ }
+
+ 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 (
+ 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";
+ }
+ } 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;
+ }
+ }
+ }
+ }
+
+ // 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;
+ }
+ }
+};
diff --git a/xul-ext/api/windowListener/schema.json b/xul-ext/api/windowListener/schema.json
new file mode 100644
index 0000000..03963a5
--- /dev/null
+++ b/xul-ext/api/windowListener/schema.json
@@ -0,0 +1,108 @@
+[
+ {
+ "namespace": "WindowListener",
+ "functions": [
+ {
+ "name": "registerDefaultPrefs",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "aPath",
+ "type": "string",
+ "description": "Relative path to the default file."
+ }
+ ]
+ },
+ {
+ "name": "registerOptionsPage",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "aPath",
+ "type": "string",
+ "description": "Path to the options page, which should be made accessible in the (legacy) Add-On Options menu."
+ }
+ ]
+ },
+ {
+ "name": "registerChromeUrl",
+ "type": "function",
+ "description": "Register folders which should be available as chrome:// urls (as defined in the legacy chrome.manifest)",
+ "parameters": [
+ {
+ "name": "data",
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "description": "Array of manifest url definitions (content, locale, resource)"
+ }
+ ]
+ },
+ {
+ "name": "waitForMasterPassword",
+ "type": "function",
+ "async": true,
+ "parameters": []
+ },
+ {
+ "name": "openOptionsDialog",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "windowId",
+ "type": "integer",
+ "description": "Id of the window the dialog should be opened from."
+ }
+ ]
+ },
+ {
+ "name": "startListening",
+ "type": "function",
+ "async": true,
+ "parameters": []
+ },
+ {
+ "name": "registerWindow",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "windowHref",
+ "type": "string",
+ "description": "Url of the window, which should be listen for."
+ },
+ {
+ "name": "jsFile",
+ "type": "string",
+ "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."
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/xul-ext/background.js b/xul-ext/background.js
new file mode 100644
index 0000000..3cd5673
--- /dev/null
+++ b/xul-ext/background.js
@@ -0,0 +1,63 @@
+/**
+ * commented out locales are not complete enough on Transifex
+ */
+const locales = [
+ 'cs',
+ //'da',
+ 'de',
+ //'el',
+ 'en-US',
+ 'es-AR',
+ 'fi',
+ 'fr',
+ //'hi',
+ 'hu',
+ //'it',
+ //'ja',
+ //'ko-KR',
+ 'nl',
+ //'pl',
+ //'pt-BR',
+ //'pt-PT',
+ //'ro',
+ 'ru',
+ 'sl',
+ //'sv-SE',
+ 'tr',
+ 'ul',
+ 'zh-CN'
+];
+(async () => {
+ // https://github.com/thundernest/addon-developer-support/wiki/WindowListener-API:-Getting-Started#windowlistenerregisterdefaultprefs
+ messenger.WindowListener.registerDefaultPrefs("defaults/preferences/prefs.js")
+ /**
+ * https://github.com/thundernest/addon-developer-support/wiki/WindowListener-API:-Getting-Started#windowlistenerregisterchromeurl
+ * Register the content, resource and locale entries from your legacy chrome.manifest via a call to registerChromeUrl().
+ */
+ messenger.WindowListener.registerChromeUrl([
+ ["content", "keefox", "chrome/content/"],
+ ["resource", "keefox", /*"classic/1.0", */ "chrome/skin/"],
+ ["resource", "kfmod", "modules/"],
+ ...locales.map((localeId) =>
+ ["locale", "keefox", localeId, `chrome/locale/${localeId}/`])
+ ]);
+ //xul-ext/chrome/skin
+ // https://github.com/thundernest/addon-developer-support/wiki/WindowListener-API:-Getting-Started#windowlistenerregisteroptionspage
+ messenger.WindowListener.registerOptionsPage("chrome://keefox/content/options.xhtml");
+
+ //
+ messenger.WindowListener.registerWindow(
+ "chrome://messenger/content/messenger.xhtml",
+ "chrome://keefox/content/scripts/messengerPanel.js");
+
+ messenger.WindowListener.registerWindow(
+ "chrome://messenger/content/commonDialog.xhtml",
+ "chrome://keefox/content/scripts/KFcommonDialog.js");
+
+ /*
+ messenger.WindowListener.registerWindow(
+ "chrome://global/content/win.xul",
+ "chrome://keefox/content/scripts/install.js");*/
+
+ await messenger.WindowListener.startListening();
+})()
\ No newline at end of file
diff --git a/xul-ext/chrome.manifest b/xul-ext/chrome.manifest
index 9a3b2fd..c6bd3f3 100644
--- a/xul-ext/chrome.manifest
+++ b/xul-ext/chrome.manifest
@@ -11,7 +11,7 @@ overlay chrome://messenger/content/messenger.xul chrome://keefox/content/panel.x
overlay chrome://gdata-provider/content/browserRequest.xul chrome://keefox/content/gdata-provider.xul
overlay chrome://messenger/content/browserRequest.xul chrome://keefox/content/gdata-provider.xul
-skin keefox classic/1.0 chrome/skin/
+resource keefox classic/1.0 chrome/skin/
resource kfmod modules/
# locales
diff --git a/xul-ext/chrome/content/KFcommonDialog.js b/xul-ext/chrome/content/KFcommonDialog.js
index 36aab27..59b16eb 100644
--- a/xul-ext/chrome/content/KFcommonDialog.js
+++ b/xul-ext/chrome/content/KFcommonDialog.js
@@ -39,6 +39,7 @@ if (!Cu)
const { keefox_org } = ChromeUtils.import("resource://kfmod/KF.js");
const { keeFoxLoginInfo } = ChromeUtils.import("resource://kfmod/kfDataModel.js");
+// var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var keeFoxDialogManager = {
scriptLoader : Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
@@ -47,9 +48,7 @@ var keeFoxDialogManager = {
__promptBundle : null, // String bundle for L10N
get _promptBundle() {
if (!this.__promptBundle) {
- var bunService = Components.classes["@mozilla.org/intl/stringbundle;1"].
- getService(Components.interfaces.nsIStringBundleService);
- this.__promptBundle = bunService.createBundle(
+ this.__promptBundle = Services.strings.createBundle(
"chrome://global/locale/prompts.properties");
if (!this.__promptBundle)
throw "Prompt string bundle not present!";
@@ -60,9 +59,7 @@ var keeFoxDialogManager = {
__cdBundle : null, // String bundle for L10N
get _cdBundle() {
if (!this.__cdBundle) {
- var bunService = Components.classes["@mozilla.org/intl/stringbundle;1"].
- getService(Components.interfaces.nsIStringBundleService);
- this.__cdBundle = bunService.createBundle(
+ this.__cdBundle = Services.strings.createBundle(
"chrome://global/locale/commonDialogs.properties");
if (!this.__cdBundle)
throw "Common Dialogs string bundle not present!";
@@ -73,9 +70,7 @@ var keeFoxDialogManager = {
__messengerBundle : null, // string bundle for thunderbird l10n
get _messengerBundle() {
if (!this.__messengerBundle) {
- var bunService = Components.classes["@mozilla.org/intl/stringbundle;1"].
- getService(Components.interfaces.nsIStringBundleService);
- this.__messengerBundle = bunService.createBundle(
+ this.__messengerBundle = Services.strings.createBundle(
"chrome://messenger/locale/messenger.properties");
if (!this.__messengerBundle)
throw "Messenger string bundle not present!";
@@ -86,9 +81,7 @@ var keeFoxDialogManager = {
__localMsgsBundle : null, // string bundle for thunderbird l10n
get _localMsgsBundle() {
if (!this.__localMsgsBundle) {
- var bunService = Components.classes["@mozilla.org/intl/stringbundle;1"].
- getService(Components.interfaces.nsIStringBundleService);
- this.__localMsgsBundle = bunService.createBundle(
+ this.__localMsgsBundle = Services.strings.createBundle(
"chrome://messenger/locale/localMsgs.properties");
if (!this.__localMsgsBundle)
throw "localMsgs string bundle not present!";
@@ -99,9 +92,7 @@ var keeFoxDialogManager = {
__imapMsgsBundle : null, // string bundle for thunderbird l10n
get _imapMsgsBundle() {
if (!this.__imapMsgsBundle) {
- var bunService = Components.classes["@mozilla.org/intl/stringbundle;1"].
- getService(Components.interfaces.nsIStringBundleService);
- this.__imapMsgsBundle = bunService.createBundle(
+ this.__imapMsgsBundle = Services.strings.createBundle(
"chrome://messenger/locale/imapMsgs.properties");
if (!this.__imapMsgsBundle)
throw "imapMsgs string bundle not present!";
@@ -128,9 +119,7 @@ var keeFoxDialogManager = {
__newsBundle : null, // string bundle for thunderbird l10n
get _newsBundle() {
if (!this.__newsBundle) {
- var bunService = Components.classes["@mozilla.org/intl/stringbundle;1"].
- getService(Components.interfaces.nsIStringBundleService);
- this.__newsBundle = bunService.createBundle(
+ this.__newsBundle = Services.strings.createBundle(
"chrome://messenger/locale/news.properties");
if (!this.__newsBundle)
throw "news string bundle not present!";
@@ -141,9 +130,7 @@ var keeFoxDialogManager = {
__composeBundle : null, // string bundle for thunderbird l10n
get _composeBundle() {
if (!this.__composeBundle) {
- var bunService = Components.classes["@mozilla.org/intl/stringbundle;1"].
- getService(Components.interfaces.nsIStringBundleService);
- this.__composeBundle = bunService.createBundle(
+ this.__composeBundle = Services.strings.createBundle(
"chrome://messenger/locale/messengercompose/composeMsgs.properties");
if (!this.__composeBundle)
throw "Compose Message string bundle not present!";
@@ -155,7 +142,7 @@ var keeFoxDialogManager = {
.getService(Components.interfaces.nsIXULAppInfo),
dialogInit : function(e) {
- window.removeEventListener("load", keeFoxDialogManager.dialogInit);
+ //window.removeEventListener("load", keeFoxDialogManager.dialogInit);
try
{
document.addEventListener("dialogaccept", event => {
@@ -283,112 +270,139 @@ var keeFoxDialogManager = {
{
let regexChars = /[\[\{\(\)\*\+\?\.\\\^\$\|]/g;
protocols[aDialogType] = aDialogType.split("-")[0];
- titles[aDialogType] = aStringBundle.GetStringFromName(aTitlePropertyName);
- prompts[aDialogType] = aStringBundle.GetStringFromName(aPromptPropertyName);
- prompts[aDialogType] = prompts[aDialogType].replace(regexChars, "\\$&");
- aHostPlaceholder = aHostPlaceholder.replace(regexChars, "\\$&");
- // use null as a flag to indicate that there was only one
- // placeholder and hostIsFirst and secondIsUserName are not applicable
- hostIsFirst[aDialogType] = null;
- if (aUserPlaceholder != null)
- {
- aUserPlaceholder = aUserPlaceholder.replace(regexChars, "\\$&");
- hostIsFirst[aDialogType] = prompts[aDialogType].indexOf(aHostPlaceholder) <
- prompts[aDialogType].indexOf(aUserPlaceholder);
- secondIsUserName[aDialogType] = true;
- }
- if (aRealmPlaceholder != null)
- {
- aRealmPlaceholder = aRealmPlaceholder.replace(regexChars, "\\$&");
- hostIsFirst[aDialogType] = prompts[aDialogType].indexOf(aHostPlaceholder) <
- prompts[aDialogType].indexOf(aRealmPlaceholder);
- secondIsUserName[aDialogType] = false;
- }
- prompts[aDialogType] = prompts[aDialogType].replace(aHostPlaceholder, "([^\\s]+)");
- if (aUserPlaceholder != null)
- {
- prompts[aDialogType] = prompts[aDialogType].replace(aUserPlaceholder, "([^\\s]+)");
+ // dirty fix
+ // use a big try/catch here to avoid errors on null string resources
+ try {
+ titles[aDialogType] = aStringBundle.GetStringFromName(aTitlePropertyName);
+ prompts[aDialogType] = aStringBundle.GetStringFromName(aPromptPropertyName);
+ titles[aDialogType] = titles[aDialogType].replace(regexChars, "\\$&");
+ prompts[aDialogType] = prompts[aDialogType].replace(regexChars, "\\$&");
+ aHostPlaceholder = aHostPlaceholder.replace(regexChars, "\\$&");
+ // use null as a flag to indicate that there was only one
+ // placeholder and hostIsFirst and secondIsUserName are not applicable
+ hostIsFirst[aDialogType] = null;
+ if (aUserPlaceholder != null)
+ {
+ aUserPlaceholder = aUserPlaceholder.replace(regexChars, "\\$&");
+ hostIsFirst[aDialogType] = prompts[aDialogType].indexOf(aHostPlaceholder) <
+ prompts[aDialogType].indexOf(aUserPlaceholder);
+ secondIsUserName[aDialogType] = true;
+ }
+ if (aRealmPlaceholder != null)
+ {
+ aRealmPlaceholder = aRealmPlaceholder.replace(regexChars, "\\$&");
+ hostIsFirst[aDialogType] = prompts[aDialogType].indexOf(aHostPlaceholder) <
+ prompts[aDialogType].indexOf(aRealmPlaceholder);
+ secondIsUserName[aDialogType] = false;
+ }
+ titles[aDialogType] = titles[aDialogType].replace(aHostPlaceholder, "([^\\s]+)");
+ prompts[aDialogType] = prompts[aDialogType].replace(aHostPlaceholder, "([^\\s]+)");
+ if (aUserPlaceholder != null)
+ {
+ titles[aDialogType] = titles[aDialogType].replace(aUserPlaceholder, "([^\\s]+)");
+ prompts[aDialogType] = prompts[aDialogType].replace(aUserPlaceholder, "([^\\s]+)");
+ }
+ if (aRealmPlaceholder != null)
+ {
+ prompts[aDialogType] = prompts[aDialogType].replace(aRealmPlaceholder, "([^\\s]+)");
+ }
+ extractUserFromHost[aDialogType] = aExtractUserFromHost;
}
- if (aRealmPlaceholder != null)
- {
- prompts[aDialogType] = prompts[aDialogType].replace(aRealmPlaceholder, "([^\\s]+)");
+ catch (resourceError) {
+ keefox_org._KFLog.error(`LoadDialogData failed for ${aDialogType},${aTitlePropertyName},${aPromptPropertyName}`, resourceError);
}
- extractUserFromHost[aDialogType] = aExtractUserFromHost;
}
- }
-
- LoadDialogData(this._composeBundle, "smtp", "smtpEnterPasswordPromptTitle",
+ };
+ LoadDialogData(this._composeBundle, "smtp", "smtpEnterPasswordPrompt",
"smtpEnterPasswordPromptWithUsername", "%1$S", null, "%2$S");
+
+ // SMTP alt
+ // title "Password Required for Outgoing (SMTP) Server %1$S"
+ // prompt "Enter your password for %2$S on %1$S:"
+ // see omni.ja/chrome/en-US/locale/en-US/messenger/messengercompose/composeMsgs.properties
+ LoadDialogData(this._composeBundle,
+ "smtp2",
+ "smtpEnterPasswordPromptTitleWithHostname",
+ "smtpEnterPasswordPromptWithUsername",
+ "%1$S",
+ null,
+ "%2$S");
+
var imapEnterPasswordPromptTitle = this._imapBundleUsesStrings
- ? "imapEnterPasswordPromptTitle" : "5051";
+ ? "imapEnterPasswordPromptTitleWithUsername" : "5051";
var imapEnterPasswordPrompt = this._imapBundleUsesStrings
- ? "imapEnterPasswordPrompt" : "5047";
- var isPreTB40ImapStrings = true;
- try {
+ ? "imapEnterServerPasswordPrompt" : "5047";
+ var isPreTB40ImapStrings = false;
+ /*try {
// Take a peek at imapEnterPasswordPrompt
this._imapMsgsBundle.GetStringFromName(imapEnterPasswordPrompt);
} catch (e) {
// The string identifier changed again in TB 40
imapEnterPasswordPrompt = "imapEnterServerPasswordPrompt";
isPreTB40ImapStrings = false;
- }
+ }*/
// The prompt changed from using one parameter to using two in TB40
LoadDialogData(this._imapMsgsBundle, "imap",
imapEnterPasswordPromptTitle, imapEnterPasswordPrompt,
isPreTB40ImapStrings ? "%S" : "%2$S", null,
isPreTB40ImapStrings ? null : "%1$S", isPreTB40ImapStrings);
- LoadDialogData(this._localMsgsBundle, "pop3", "pop3EnterPasswordPromptTitle",
- "pop3EnterPasswordPrompt", "%2$S", null, "%1$S");
+
+ LoadDialogData(this._localMsgsBundle, "pop3", "pop3EnterPasswordPromptTitleWithUsername",
+ "pop3EnterPasswordPromptTitleWithUsername", "%2$S", null, "%1$S");
+
LoadDialogData(this._newsBundle, "nntp-1", "enterUserPassTitle",
"enterUserPassServer", "%S");
+
LoadDialogData(this._newsBundle, "nntp-2", "enterUserPassTitle",
"enterUserPassGroup", "%2$S", "%1$S");
+
LoadDialogData(this._messengerBundle, "mail", "passwordTitle",
"passwordPrompt", "%2$S", null, "%1$S");
- for (let type in titles)
- {
- if (Dialog.args.title == titles[type])
- {
- // some types have the same title, so we have more checking to do
- let regEx = new RegExp(prompts[type]);
- let matches = Dialog.args.text.match(regEx);
- if (!matches)
- {
- continue;
- }
- if (hostIsFirst[type] === null) {
- // there is only one parameter, so nothing is first
- if (matches.length == 2) {
- if (extractUserFromHost[type])
- {
- // user and host are separated by @ character
- let lastAtSym = matches[1].lastIndexOf("@");
- username = matches[1].substring(0, lastAtSym);
- host = protocols[type] + "://" +
- matches[1].substring(lastAtSym + 1, matches[1].length);
- } else {
- host = protocols[type] + "://" + matches[1];
- }
- break;
+ const matchings = Object.keys(titles).filter((type) => {
+ // refactor the title identification with a regEx because some have placeholders like $S
+ let reTitle = new RegExp(titles[type]);
+ return reTitle.test(Dialog.args.title);
+ }).map((type) => {
+ let regEx = new RegExp(prompts[type]);
+ let matches = Dialog.args.text.match(regEx);
+ return {
+ type,
+ matches
+ };
+ });
+
+ matchings.forEach((m) => {
+ const type = m.type;
+ const matches = m.matches;
+ if (hostIsFirst[type] === null) {
+ // there is only one parameter, so nothing is first
+ if (matches.length == 2) {
+ if (extractUserFromHost[type])
+ {
+ // user and host are separated by @ character
+ let lastAtSym = matches[1].lastIndexOf("@");
+ username = matches[1].substring(0, lastAtSym);
+ host = protocols[type] + "://" +
+ matches[1].substring(lastAtSym + 1, matches[1].length);
+ } else {
+ host = protocols[type] + "://" + matches[1];
}
- } else
- {
- if (matches.length == 3) {
- if (hostIsFirst[type]) {
- host = protocols[type] + "://" + matches[1];
- username = matches[2];
- } else {
- host = protocols[type] + "://" + matches[2];
- username = matches[1];
- }
- break;
+ }
+ } else {
+ if (matches.length == 3) {
+ if (hostIsFirst[type]) {
+ host = protocols[type] + "://" + matches[1];
+ username = matches[2];
+ } else {
+ host = protocols[type] + "://" + matches[2];
+ username = matches[1];
}
}
}
- }
+ });
} // end if Thunderbird
-
+
if (host.length < 1)
{
// e.g. en-US:
@@ -560,24 +574,23 @@ var keeFoxDialogManager = {
this.mustAutoSubmit = mustAutoSubmit;
/* add ui elements to dialog */
-
- var row = document.createElement("row");
+
+ var row = document.createElement("div");
row.setAttribute("id","keefox-autoauth-row");
- row.setAttribute("flex", "1");
+ row.setAttribute("class","dialogRow");
// spacer to take up first column in layout
- var spacer = document.createElement("spacer");
- spacer.setAttribute("flex", "1");
+ var spacer = document.createElement("div");
row.appendChild(spacer);
// this box displays labels and also the list of entries when fetched
- var box = document.createElement("hbox");
+ var box = document.createXULElement("hbox");
box.setAttribute("id","keefox-autoauth-box");
box.setAttribute("align", "center");
box.setAttribute("flex", "1");
box.setAttribute("pack", "start");
- var loadingPasswords = document.createElement("description");
+ var loadingPasswords = document.createXULElement("description");
loadingPasswords.setAttribute("id","keefox-autoauth-description");
loadingPasswords.setAttribute("align", "start");
loadingPasswords.setAttribute("flex", "1");
@@ -585,7 +598,7 @@ var keeFoxDialogManager = {
row.appendChild(box);
// button to lauch KeePass
- var launchKeePassButton = document.createElement("button");
+ var launchKeePassButton = document.createXULElement("button");
launchKeePassButton.setAttribute("id", "keefox-launch-kp-button");
launchKeePassButton.setAttribute("label", keefox_org.locale.$STR("launchKeePass.label"));
launchKeePassButton.addEventListener("command", function (event) { keefox_org.launchKeePass(''); }, false);
@@ -597,6 +610,10 @@ var keeFoxDialogManager = {
}
},
+ /**
+ * This is where we send the identification request to keefox RPC
+ * Assuming that the validation token has been stored
+ */
updateDialog : function()
{
// check to make sure prepareFill was called
@@ -761,9 +778,9 @@ var keeFoxDialogManager = {
if (showList) {
var box = dialogFindLoginStorage.document.getElementById("keefox-autoauth-box");
- var list = dialogFindLoginStorage.document.createElement("menulist");
+ var list = dialogFindLoginStorage.document.createXULElement("menulist");
list.setAttribute("id","autoauth-list");
- var popup = dialogFindLoginStorage.document.createElement("menupopup");
+ var popup = dialogFindLoginStorage.document.createXULElement("menupopup");
var done = false;
for (var i = 0; i < matchedLogins.length; i++) {
@@ -779,7 +796,7 @@ var keeFoxDialogManager = {
});
for (var i = 0; i < matchedLogins.length; i++){
- var item = dialogFindLoginStorage.document.createElement("menuitem");
+ var item = dialogFindLoginStorage.document.createXULElement("menuitem");
item.setAttribute("label", keefox_org.locale.$STRF("matchedLogin.label",
[matchedLogins[i].username, matchedLogins[i].host]));
item.setAttribute("tooltiptext", keefox_org.locale.$STRF("matchedLogin.tip",
@@ -928,4 +945,5 @@ KPRPCConnectionObserver.prototype = {
keeFoxDialogManager.Logger = keefox_org._KFLog;
keeFoxDialogManager.scriptLoader.loadSubScript(
"chrome://keefox/content/shared/uriUtils.js", keeFoxDialogManager);
-window.addEventListener("load", keeFoxDialogManager.dialogInit, false);
+//window.addEventListener("load", keeFoxDialogManager.dialogInit, false);
+
diff --git a/xul-ext/chrome/content/UninstallHelper.js b/xul-ext/chrome/content/UninstallHelper.js
index c725cf0..9a0a496 100644
--- a/xul-ext/chrome/content/UninstallHelper.js
+++ b/xul-ext/chrome/content/UninstallHelper.js
@@ -214,14 +214,12 @@ keefox_win.UninstallHelper.prototype =
extraLabel.setAttribute("control", "keefox-uninstall-helper-feedback-extra");
container.appendChild(extraLabel);
- // FIXME: textbox multiline="true" is removed in TB68
- // https://developer.thunderbird.net/add-ons/updates/tb68#less-than-textbox-multiline-true-greater-than
- let extra = this.doc.createElement('textbox');
+ let extra = this.doc.createElementNS("http://www.w3.org/1999/xhtml",'textarea');
extra.setAttribute("id","keefox-uninstall-helper-feedback-extra");
- extra.setAttribute("multiline","true");
extra.addEventListener("keypress", pnCountNoteChars, false);
container.appendChild(extra);
+
if (!varients.smallScreen && varients.whyLocation == 2)
attachWhy.call(this);
diff --git a/xul-ext/chrome/content/context.js b/xul-ext/chrome/content/context.js
index 04d5b0a..72026b9 100644
--- a/xul-ext/chrome/content/context.js
+++ b/xul-ext/chrome/content/context.js
@@ -135,7 +135,7 @@ keefox_win.context = {
var tempButton = i == 0 ?
container :
- this._currentWindow.document.createElement("menuitem");
+ this._currentWindow.document.createXULElement("menuitem");
tempButton.setAttribute("label", keefox_org.locale.$STRF("matchedLogin.label"
, [usernameDisplayValue, login.title]));
tempButton.setAttribute("class", "menuitem-iconic");
diff --git a/xul-ext/chrome/content/context.xul b/xul-ext/chrome/content/context.xhtml
similarity index 97%
rename from xul-ext/chrome/content/context.xul
rename to xul-ext/chrome/content/context.xhtml
index f505848..a86e876 100644
--- a/xul-ext/chrome/content/context.xul
+++ b/xul-ext/chrome/content/context.xhtml
@@ -56,7 +56,7 @@
diff --git a/xul-ext/chrome/content/famsOptions.js b/xul-ext/chrome/content/famsOptions.js
index 6f84b13..9e9b00c 100644
--- a/xul-ext/chrome/content/famsOptions.js
+++ b/xul-ext/chrome/content/famsOptions.js
@@ -62,6 +62,10 @@ function onLoad()
event.preventDefault();
}
});
+ document.addEventListener("dialogcancel", event => {
+ event.preventDefault(); // Prevent the dialog closing.
+ return;
+ });
go();
}
@@ -91,7 +95,7 @@ function go() {
function renderAllMessageGroups()
{
var msgGroupContainer = window.document.getElementById("msgGroupContainer");
- var famsDescription = window.document.createElement("description");
+ var famsDescription = window.document.createXULElement("description");
famsDescription.textContent = FAMS.locale.internationaliseString(config.description);
msgGroupContainer.appendChild(famsDescription);
@@ -108,11 +112,11 @@ function renderAllMessageGroups()
function renderDownloadOptions()
{
var msgGroupContainer = window.document.getElementById("msgGroupContainer");
- var downloadSliderComplete = window.document.createElement("hbox");
- var downloadLabelExplanation = window.document.createElement("label");
- var downloadLabelNote = window.document.createElement("description");
- var downloadSlider = window.document.createElement("scale");
- var downloadLabel = window.document.createElement("label");
+ var downloadSliderComplete = window.document.createXULElement("hbox");
+ var downloadLabelExplanation = window.document.createXULElement("label");
+ var downloadLabelNote = window.document.createXULElement("description");
+ var downloadSlider = window.document.createXULElement("scale");
+ var downloadLabel = window.document.createXULElement("label");
downloadLabelExplanation.setAttribute("value", FAMS.getLocalisedString("Options-Download-Freq.label"));
downloadLabelNote.textContent = FAMS.getLocalisedString("Options-Download-Freq.desc", config.name);
@@ -142,14 +146,14 @@ function renderMessageGroup(msgGroupIndex)
var msgGroup = config.messageGroups[msgGroupIndex];
var msgGroupContainer = window.document.getElementById("msgGroupContainer");
- var singleMsgGroupContainer = window.document.createElement("vbox");
- var desc = window.document.createElement("description");
- var appearanceLabelNote = window.document.createElement("description");
- var enabledCheckbox = window.document.createElement("checkbox");
- var appearanceSliderComplete = window.document.createElement("hbox");
- var appearanceLabelExplanation = window.document.createElement("label");
- var appearanceSlider = window.document.createElement("scale");
- var appearanceLabel = window.document.createElement("label");
+ var singleMsgGroupContainer = window.document.createXULElement("vbox");
+ var desc = window.document.createXULElement("description");
+ var appearanceLabelNote = window.document.createXULElement("description");
+ var enabledCheckbox = window.document.createXULElement("checkbox");
+ var appearanceSliderComplete = window.document.createXULElement("hbox");
+ var appearanceLabelExplanation = window.document.createXULElement("label");
+ var appearanceSlider = window.document.createXULElement("scale");
+ var appearanceLabel = window.document.createXULElement("label");
//var seeAllButton = window.document.createElement("button");
desc.textContent = FAMS.locale.internationaliseString(msgGroup.description);
diff --git a/xul-ext/chrome/content/famsOptions.xul b/xul-ext/chrome/content/famsOptions.xhtml
similarity index 98%
rename from xul-ext/chrome/content/famsOptions.xul
rename to xul-ext/chrome/content/famsOptions.xhtml
index 7738c19..b4d8b1f 100644
--- a/xul-ext/chrome/content/famsOptions.xul
+++ b/xul-ext/chrome/content/famsOptions.xhtml
@@ -26,7 +26,6 @@