/* * This file is provided by the addon-developer-support repository at * https://github.com/thundernest/addon-developer-support * * Version: 1.21 * * 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/. */ // 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 { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); function getThunderbirdVersion() { let parts = Services.appinfo.version.split("."); return { major: parseInt(parts[0]), minor: parseInt(parts[1]), revision: parts.length > 2 ? parseInt(parts[2]) : 0, } } function 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; } var BootstrapLoader_102 = class extends ExtensionCommon.ExtensionAPI { 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 (getThunderbirdVersion().major < 86) { let ownerDoc = e.document || e.target.ownerDocument; doc = ownerDoc.getElementById("html-view-browser").contentDocument; } else if (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 BL = {} BL.extension = this.extension; BL.messenger = getMessenger(this.context); let w = Services.wm.getMostRecentWindow("mail:3pane"); w.openDialog(this.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", BL); } break; // 68 add-on options menu command case "command": { let BL = {} BL.extension = this.extension; BL.messenger = getMessenger(this.context); e.target.ownerGlobal.openDialog(this.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", BL); } break; // update, ViewChanged and manual call for add-on manager options overlay default: { let cards = this.getCards(e); for (let card of cards) { // Setup either the options entry in the menu or the button if (card.addon.id == this.extension.id) { let optionsMenu = (getThunderbirdVersion().major > 78 && getThunderbirdVersion().major < 88) || (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor < 10) || (getThunderbirdVersion().major == 78 && getThunderbirdVersion().minor == 10 && getThunderbirdVersion().revision < 2); if (optionsMenu) { // Options menu in 78.0-78.10 and 79-87 let addonOptionsLegacyEntry = card.querySelector(".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(".extension-options-button2"); if (card.addon.isActive && !addonOptionsButton) { addonOptionsButton = card.ownerDocument.createElement("button"); addonOptionsButton.classList.add("extension-options-button2"); addonOptionsButton.style["min-width"] = "auto"; addonOptionsButton.style["min-height"] = "auto"; addonOptionsButton.style["width"] = "24px"; addonOptionsButton.style["height"] = "24px"; addonOptionsButton.style["margin"] = "0"; addonOptionsButton.style["margin-inline-start"] = "8px"; addonOptionsButton.style["-moz-context-properties"] = "fill"; addonOptionsButton.style["fill"] = "currentColor"; addonOptionsButton.style["background-image"] = "url('chrome://messenger/skin/icons/developer.svg')"; addonOptionsButton.style["background-repeat"] = "no-repeat"; addonOptionsButton.style["background-position"] = "center center"; addonOptionsButton.style["padding"] = "1px"; addonOptionsButton.style["display"] = "flex"; addonOptionsButton.style["justify-content"] = "flex-end"; card.optionsButton.parentNode.insertBefore( addonOptionsButton, card.optionsButton ); card.querySelector(".extension-options-button2").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 ) { 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); } } getAPI(context) { this.uniqueRandomID = "AddOnNS" + context.extension.instanceId; this.menu_addonPrefs_id = "addonPrefs"; this.pathToBootstrapScript = null; this.pathToOptionsPage = null; this.chromeHandle = null; this.chromeData = null; this.resourceData = null; this.bootstrappedObj = {}; // make the extension object and the messenger object available inside // the bootstrapped scope this.bootstrappedObj.extension = context.extension; this.bootstrappedObj.messenger = getMessenger(this.context); this.BOOTSTRAP_REASONS = { APP_STARTUP: 1, APP_SHUTDOWN: 2, ADDON_ENABLE: 3, ADDON_DISABLE: 4, ADDON_INSTALL: 5, ADDON_UNINSTALL: 6, // not supported ADDON_UPGRADE: 7, ADDON_DOWNGRADE: 8, }; 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 { BootstrapLoader: { registerOptionsPage(optionsUrl) { self.pathToOptionsPage = optionsUrl.startsWith("chrome://") ? optionsUrl : context.extension.rootURI.resolve(optionsUrl); }, openOptionsDialog(windowId) { let window = context.extension.windowManager.get(windowId, context).window let BL = {} BL.extension = self.extension; BL.messenger = getMessenger(self.context); window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", BL); }, 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; }, registerBootstrapScript: async function (aPath) { self.pathToBootstrapScript = aPath.startsWith("chrome://") ? aPath : context.extension.rootURI.resolve(aPath); // Get the addon object belonging to this extension. let addon = await AddonManager.getAddonByID(context.extension.id); //make the addon globally available in the bootstrapped scope self.bootstrappedObj.addon = addon; // add BOOTSTRAP_REASONS to scope for (let reason of Object.keys(self.BOOTSTRAP_REASONS)) { self.bootstrappedObj[reason] = self.BOOTSTRAP_REASONS[reason]; } // Load registered bootstrap scripts and execute its startup() function. try { if (self.pathToBootstrapScript) Services.scriptloader.loadSubScript(self.pathToBootstrapScript, self.bootstrappedObj, "UTF-8"); if (self.bootstrappedObj.startup) self.bootstrappedObj.startup.call(self.bootstrappedObj, self.extension.addonData, self.BOOTSTRAP_REASONS[self.extension.startupReason]); } catch (e) { Components.utils.reportError(e) } // Register window listener for main TB window if (self.pathToOptionsPage) { ExtensionSupport.registerWindowListener("injectListener_" + self.uniqueRandomID, { chromeURLs: [ "chrome://messenger/content/messenger.xul", "chrome://messenger/content/messenger.xhtml", ], async onLoadWindow(window) { if (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] = {}; 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); } }, onUnloadWindow(window) { } }); } } } }; } onShutdown(isAppShutdown) { if (isAppShutdown) { return; // the application gets unloaded anyway } //remove our entry in the add-on options menu if (this.pathToOptionsPage) { for (let window of Services.wm.getEnumerator("mail:3pane")) { if (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("update", this); managerWindow.document.removeEventListener("view-loaded", this); managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false; let cards = this.getCards(managerWindow); if (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(".extension-options-button2"); if (addonOptionsButton) addonOptionsButton.remove(); break; } } } } // Remove tabmonitor if (window[this.uniqueRandomID].hasTabMonitor) { this.getTabMail(window).unregisterTabMonitor(this.tabMonitor); window[this.uniqueRandomID].hasTabMonitor = false; } } } // Stop listening for new windows. ExtensionSupport.unregisterWindowListener("injectListener_" + this.uniqueRandomID); } // Execute registered shutdown() try { if (this.bootstrappedObj.shutdown) { this.bootstrappedObj.shutdown( this.extension.addonData, isAppShutdown ? this.BOOTSTRAP_REASONS.APP_SHUTDOWN : this.BOOTSTRAP_REASONS.ADDON_DISABLE); } } catch (e) { Components.utils.reportError(e) } 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; } // Flush all caches Services.obs.notifyObservers(null, "startupcache-invalidate"); console.log("BootstrapLoader for " + this.extension.id + " unloaded!"); } }; // Removed all extra code for backward compatibility for better maintainability. var BootstrapLoader_115 = class extends ExtensionCommon.ExtensionAPI { getCards(e) { // This gets triggered by real events but also manually by providing the outer window. // The event is attached to the outer browser, get the inner one. let doc = e.document || e.target; return doc.querySelectorAll("addon-card"); } // Event handler for the addon manager, to update the state of the options button. handleEvent(e) { switch (e.type) { case "click": { e.preventDefault(); e.stopPropagation(); let BL = {} BL.extension = this.extension; BL.messenger = getMessenger(this.context); let w = Services.wm.getMostRecentWindow("mail:3pane"); w.openDialog( this.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", BL ); } break; // update, ViewChanged and manual call for add-on manager options overlay default: { let cards = this.getCards(e); for (let card of cards) { // Setup either the options entry in the menu or the button if (card.addon.id == this.extension.id) { // Add-on button let addonOptionsButton = card.querySelector( ".windowlistener-options-button" ); if (card.addon.isActive && !addonOptionsButton) { let origAddonOptionsButton = card.querySelector(".extension-options-button") origAddonOptionsButton.setAttribute("hidden", "true"); addonOptionsButton = card.ownerDocument.createElement("button"); addonOptionsButton.classList.add("windowlistener-options-button"); addonOptionsButton.classList.add("extension-options-button"); card.optionsButton.parentNode.insertBefore( addonOptionsButton, card.optionsButton ); card .querySelector(".windowlistener-options-button") .addEventListener("click", this); } else if (!card.addon.isActive && addonOptionsButton) { addonOptionsButton.remove(); } } } } } } // Some tab/add-on-manager related functions getTabMail(window) { return window.document.getElementById("tabmail"); } // returns the outer browser, not the nested browser of the add-on manager // events must be attached to the outer browser getAddonManagerFromTab(tab) { if (tab.browser && 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 (!this.pathToOptionsPage) { return; } if ( managerWindow && managerWindow[this.uniqueRandomID] && managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners ) { return; } managerWindow.document.addEventListener("ViewChanged", this); managerWindow.document.addEventListener("update", this); managerWindow.document.addEventListener("view-loaded", this); managerWindow[this.uniqueRandomID] = {}; managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true; if (forceLoad) { this.handleEvent(managerWindow); } } getAPI(context) { this.uniqueRandomID = "AddOnNS" + context.extension.instanceId; this.menu_addonPrefs_id = "addonPrefs"; this.pathToBootstrapScript = null; this.pathToOptionsPage = null; this.chromeHandle = null; this.chromeData = null; this.resourceData = null; this.bootstrappedObj = {}; // make the extension object and the messenger object available inside // the bootstrapped scope this.bootstrappedObj.extension = context.extension; this.bootstrappedObj.messenger = getMessenger(this.context); this.BOOTSTRAP_REASONS = { APP_STARTUP: 1, APP_SHUTDOWN: 2, ADDON_ENABLE: 3, ADDON_DISABLE: 4, ADDON_INSTALL: 5, ADDON_UNINSTALL: 6, // not supported ADDON_UPGRADE: 7, ADDON_DOWNGRADE: 8, }; 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 { BootstrapLoader: { registerOptionsPage(optionsUrl) { self.pathToOptionsPage = optionsUrl.startsWith("chrome://") ? optionsUrl : context.extension.rootURI.resolve(optionsUrl); }, openOptionsDialog(windowId) { let window = context.extension.windowManager.get(windowId, context).window let BL = {} BL.extension = self.extension; BL.messenger = getMessenger(self.context); window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", BL); }, 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; }, registerBootstrapScript: async function (aPath) { self.pathToBootstrapScript = aPath.startsWith("chrome://") ? aPath : context.extension.rootURI.resolve(aPath); // Get the addon object belonging to this extension. let addon = await AddonManager.getAddonByID(context.extension.id); //make the addon globally available in the bootstrapped scope self.bootstrappedObj.addon = addon; // add BOOTSTRAP_REASONS to scope for (let reason of Object.keys(self.BOOTSTRAP_REASONS)) { self.bootstrappedObj[reason] = self.BOOTSTRAP_REASONS[reason]; } // Load registered bootstrap scripts and execute its startup() function. try { if (self.pathToBootstrapScript) Services.scriptloader.loadSubScript(self.pathToBootstrapScript, self.bootstrappedObj, "UTF-8"); if (self.bootstrappedObj.startup) self.bootstrappedObj.startup.call(self.bootstrappedObj, self.extension.addonData, self.BOOTSTRAP_REASONS[self.extension.startupReason]); } catch (e) { Components.utils.reportError(e) } // Register window listener for main TB window if (self.pathToOptionsPage) { ExtensionSupport.registerWindowListener("injectListener_" + self.uniqueRandomID, { chromeURLs: [ "chrome://messenger/content/messenger.xul", "chrome://messenger/content/messenger.xhtml", ], async onLoadWindow(window) { if (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] = {}; 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); } }, onUnloadWindow(window) { } }); } } } }; } onShutdown(isAppShutdown) { if (isAppShutdown) { return; // the application gets unloaded anyway } //remove our entry in the add-on options menu if (this.pathToOptionsPage) { for (let window of Services.wm.getEnumerator("mail:3pane")) { if (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("update", this); managerWindow.document.removeEventListener("view-loaded", this); managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = false; let cards = this.getCards(managerWindow); if (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(".extension-options-button2"); if (addonOptionsButton) addonOptionsButton.remove(); break; } } } } // Remove tabmonitor if (window[this.uniqueRandomID].hasTabMonitor) { this.getTabMail(window).unregisterTabMonitor(this.tabMonitor); window[this.uniqueRandomID].hasTabMonitor = false; } } } // Stop listening for new windows. ExtensionSupport.unregisterWindowListener("injectListener_" + this.uniqueRandomID); } // Execute registered shutdown() try { if (this.bootstrappedObj.shutdown) { this.bootstrappedObj.shutdown( this.extension.addonData, isAppShutdown ? this.BOOTSTRAP_REASONS.APP_SHUTDOWN : this.BOOTSTRAP_REASONS.ADDON_DISABLE); } } catch (e) { Components.utils.reportError(e) } 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; } // Flush all caches Services.obs.notifyObservers(null, "startupcache-invalidate"); console.log("BootstrapLoader for " + this.extension.id + " unloaded!"); } }; var BootstrapLoader = getThunderbirdVersion().major < 111 ? BootstrapLoader_102 : BootstrapLoader_115;