/* eslint-disable object-shorthand */ "use strict"; // Using a closure to not leak anything but the API to the outside world. (function (exports) { var ExtensionCommon; var ExtensionSupport; if (typeof ChromeUtils.import === "function") { ExtensionCommon = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm").ExtensionCommon; ExtensionSupport = ChromeUtils.import("resource:///modules/ExtensionSupport.jsm").ExtensionSupport; } else { // TB136+ ExtensionCommon = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs").ExtensionCommon; ExtensionSupport = ChromeUtils.importESModule("resource:///modules/ExtensionSupport.sys.mjs").ExtensionSupport; } /* The old ways... // Get various parts of the WebExtension framework that we need. var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"); // You probably already know what this does. // var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); // var Services = globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services; // A helpful class for listening to windows opening and closing. var { ExtensionSupport } = ChromeUtils.import("resource:///modules/ExtensionSupport.jsm"); */ /** * This object is just what we're using to listen for toolbar clicks. The implementation * isn't what this example is about, but you might be interested as it's a common pattern. * We count the number of callbacks waiting for events so that we're only listening if we * need to be. * * An EventEmitter has the following basic functions: * * EventEmitter.on(emitterName, callback) * Registers a callback for a custom emitter. * * EventEmitter.off(emitterName, callback) * Unregisters a callback for a custom emitter. * * EventEmitter.emit(emitterName) * Emit a custom emitter, all provided parameters will be forwarded to the registered callbacks. */ let windowListener; class WindowListener extends ExtensionCommon.EventEmitter { constructor(extension) { super(); this.extension = extension; this.onNewWindowCallbackCount = 0; this.onCloseButtonClickCallbackCount = 0; this.MESSAGE_CLOSE_TYPE_DEFAULT = 0; this.MESSAGE_CLOSE_TYPE_MIN_MAIN_TRAY_CLOSE_CHILDREN = 1; this.MESSAGE_CLOSE_TYPE_MIN_ALL_TRAY = 2; this.MESSAGE_CLOSE_TYPE_MIN_MAIN_CLOSE_CHILDREN = 3; this.MESSAGE_CLOSE_TYPE_MIN_ALL = 4; this.closeType = this.MESSAGE_CLOSE_TYPE_DEFAULT; this.mainWindowId = 0; this.activeWindows = {}; } get listenerIdNewWindow() { return `window_event_listener_new_window_${this.extension.uuid}_${this.extension.instanceId}`; } get listenerIdCloseButton() { return `window_event_listener_close_button_${this.extension.uuid}_${this.extension.instanceId}`; } resetAllListeners() { } setCloseType(closeType) { // console.log("Close type change: " + closeType); if (closeType === 0) { if( this.closeType !== this.MESSAGE_CLOSE_TYPE_DEFAULT ) { this.resetAllListeners(); } this.closeType = this.MESSAGE_CLOSE_TYPE_DEFAULT; } else if (closeType === 1) { if( this.closeType !== this.MESSAGE_CLOSE_TYPE_MIN_MAIN_TRAY_CLOSE_CHILDREN ) { this.resetAllListeners(); } this.closeType = this.MESSAGE_CLOSE_TYPE_MIN_MAIN_TRAY_CLOSE_CHILDREN; } else if (closeType === 2) { if( this.closeType !== this.MESSAGE_CLOSE_TYPE_MIN_ALL_TRAY ) { this.resetAllListeners(); } this.closeType = this.MESSAGE_CLOSE_TYPE_MIN_ALL_TRAY; } else if (closeType === 3) { if( this.closeType !== this.MESSAGE_CLOSE_TYPE_MIN_MAIN_CLOSE_CHILDREN ) { this.resetAllListeners(); } this.closeType = this.MESSAGE_CLOSE_TYPE_MIN_MAIN_CLOSE_CHILDREN; } else if (closeType === 4) { if( this.closeType !== this.MESSAGE_CLOSE_TYPE_MIN_ALL ) { this.resetAllListeners(); } this.closeType = this.MESSAGE_CLOSE_TYPE_MIN_ALL; } else console.log("Unknown close type: " + closeType); } setMainWindowId(id) { this.mainWindowId = id; } forceClose(id) { if (this.activeWindows[id] !== undefined) { // console.log("Force Close clicked: " + id); this.activeWindows[id].window.close = this.activeWindows[id].close; this.activeWindows[id].window.close(); const closeWindow = this.activeWindows[id].window; delete this.activeWindows[id]; closeWindow.close(); } } addOnNewWindow(callback, context) { // Registering the callback for "new-window". this.on("new-window", callback); this.onNewWindowCallbackCount++; // console.log("New window add"); if (this.onNewWindowCallbackCount === 1) { ExtensionSupport.registerWindowListener(this.listenerIdNewWindow, { context, chromeURLs: [ "chrome://messenger/content/messenger.xhtml", "chrome://messenger/content/messenger.xul", ], onLoadWindow: function (window) { // Get current window id const id = context.extension.windowManager.getWrapper(window).id; windowListener.emit("new-window", id); // console.log("New window added: " + id); } }); } } removeOnNewWindow(callback, context) { // Un-Registering the callback for "new-window". this.off("new-window", callback); this.onNewWindowCallbackCount--; // console.log("New window remove"); if (this.onNewWindowCallbackCount === 0) { for (let window of ExtensionSupport.openWindows) { if ([ "chrome://messenger/content/messenger.xhtml", "chrome://messenger/content/messenger.xul", ].includes(window.location.href)) { // Get current window id const id = context.extension.windowManager.getWrapper(window).id; // console.log("New window removed: " + id); } } ExtensionSupport.unregisterWindowListener( this.listenerIdNewWindow ); } } addOnCloseButtonClick(callback, context) { // Registering the callback for "close-clicked". this.on( "close-clicked", callback ); this.onCloseButtonClickCallbackCount++; // console.log("New Close add"); if (this.onCloseButtonClickCallbackCount === 1) { ExtensionSupport.registerWindowListener(this.listenerIdCloseButton, { context, chromeURLs: [ "chrome://messenger/content/messenger.xhtml", "chrome://messenger/content/messenger.xul", ], onLoadWindow: function ( window ) { // Get current window id const id = context.extension.windowManager.getWrapper(window).id if (id === windowListener.mainWindowId || windowListener.closeType === windowListener.MESSAGE_CLOSE_TYPE_MIN_ALL_TRAY || windowListener.closeType === windowListener.MESSAGE_CLOSE_TYPE_MIN_ALL) { function onCloseButton(event) { // console.log("Close clicked: " + event); // console.log("Close clicked: " + id); if ( event ) event.preventDefault(); windowListener.emit("close-clicked", id, false); return true; } // Store the window data windowListener.activeWindows[id] = { window, close : window.close, listener: onCloseButton }; // Intercept the close event (triggered when clicking the close button) window.addEventListener( "close", onCloseButton, true ); // Pre TB115 close menu, now same as close button window.close = () => onCloseButton(null); // console.log("Close listener added for: " + id); } else { function onCloseButton2(event) { // console.log("Real Close clicked: " + id); windowListener.emit("close-clicked", id, true); window.close = windowListener.activeWindows[id].close; window.close(); return false; } // Store the window data windowListener.activeWindows[id] = { window, close : window.close, listener: onCloseButton2 }; // Intercept the close event (triggered when clicking the close button) window.addEventListener( "close", onCloseButton2, true ); // Pre TB115 close menu, now same as close button window.close = () => onCloseButton2(null); // console.log("Real Close listener added for: " + id); } }, }); } } removeOnCloseButtonClick(callback, context) { // Un-Registering the callback for "close-clicked". this.off( "close-clicked", callback ); this.onCloseButtonClickCallbackCount--; // console.log("Close remove"); if (this.onCloseButtonClickCallbackCount === 0) { for (let window of ExtensionSupport.openWindows) { if ([ "chrome://messenger/content/messenger.xhtml", "chrome://messenger/content/messenger.xul", ].includes(window.location.href)) { // Get current window id const id = context.extension.windowManager.getWrapper(window).id // Release the close event (triggered when clicking the close button) window.removeEventListener( "close", windowListener.activeWindows[id].listener, true ); window.close = windowListener.activeWindows[id].close; // Clean the storage delete windowListener.activeWindows[id]; // console.log("Close listener removed for: " + id); } } ExtensionSupport.unregisterWindowListener(this.listenerIdCloseButton); } } }; // This is the important part. It implements the functions and events defined // in the schema.json. The name must match what you've been using so far, // "windowEvent" in this case. class windowEvent extends ExtensionCommon.ExtensionAPI { // An alternative to defining a constructor here, is to use the onStartup // event. However, this causes the API to be instantiated directly after the // add-on has been loaded, not when the API is first used. Depends on what is // desired. constructor(extension) { super(extension); windowListener = new WindowListener(extension); } getAPI(context) { console.log("windowEvent API started"); return { // This key must match the class name. windowEvent: { setCloseType: async function (type) { windowListener.setCloseType(type); }, setMainWindowId: async function (id) { windowListener.setMainWindowId(id); }, forceClose: async function (id) { windowListener.forceClose(id); }, // An event. Most of this is boilerplate you don't need to worry about, just copy it. onNewWindow: new ExtensionCommon.EventManager({ context, name: "windowEvent.onNewWindow", // In this function we add listeners for any events we want to listen to, and return a // function that removes those listeners. To have the event fire in your extension, // call fire.async. register(fire) { function callback(event, id) { return fire.async(id); } windowListener.addOnNewWindow(callback,context); return function () { windowListener.removeOnNewWindow(callback,context); }; }, }).api(), // An event. Most of this is boilerplate you don't need to worry about, just copy it. onCloseButtonClick: new ExtensionCommon.EventManager({ context, name: "windowEvent.onCloseButtonClick", // In this function we add listeners for any events we want to listen to, and return a // function that removes those listeners. To have the event fire in your extension, // call fire.async. register(fire) { function callback(event, id, quit) { return fire.async(id, quit); } windowListener.addOnCloseButtonClick(callback,context); return function () { windowListener.removeOnCloseButtonClick(callback,context); }; }, }).api(), }, }; } onShutdown(isAppShutdown) { // This function is called if the extension is disabled or removed, or Thunderbird closes. // We usually do not have to do any cleanup, if Thunderbird is shutting down entirely if (isAppShutdown) { return; } console.log("windowEvent API closed"); } }; // Export the api by assigning in to the exports parameter of the anonymous closure // function, which is the global this. exports.windowEvent = windowEvent; })(this)