/* eslint-disable object-shorthand */ // 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"); // ChromeUtils.import() works in experiments for core resource urls as it did // in legacy add-ons. However, chrome:// urls that point to add-on resources no // longer work, as the "chrome.manifest" file is no longer supported, which // defined the root path for each add-on. Instead, ChromeUtils.import() needs // a url generated by // // let url = context.extension.rootURI.resolve("path/to/file.jsm") // // Instead of taking the extension object from the context, you may generate // the extension object from a given add-on ID as shown in the example below. // This allows to import a JSM without context, for example inside another JSM. // var { ExtensionParent } = ChromeUtils.import( "resource://gre/modules/ExtensionParent.jsm" ); var extension = ExtensionParent.GlobalManager.getExtension( "systray-x@Ximi1970" ); var { windowEvent } = ChromeUtils.import( extension.rootURI.resolve("modules/windowEvent.jsm") ); // This is the important part. It implements the functions and events defined in schema.json. // The variable must have the same name you've been using so far, "myapi" in this case. var windowEvent = class extends ExtensionCommon.ExtensionAPI { getAPI(context) { console.log("windowEvent API started"); // To be notified of the extension going away, call callOnClose with any object that has a // close function, such as this one. context.callOnClose(this); return { // Again, this key must have the same name. windowEvent: { // 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) { return fire.async(); } windowListener.addOnCloseButton(callback); return function () { windowListener.removeOnCloseButton(callback); }; }, }).api(), }, }; } close() { // This function is called if the extension is disabled or removed, or Thunderbird closes. // We registered it with callOnClose, above. console.log("windowEvent API closed"); // Unload the JSM we imported above. This will cause Thunderbird to forget about the JSM, and // load it afresh next time `import` is called. (If you don't call `unload`, Thunderbird will // remember this version of the module and continue to use it, even if your extension receives // an update.) You should *always* unload JSMs provided by your extension. Cu.unload(extension.getURL("modules/windowEvent.jsm")); } }; // A helpful class for listening to windows opening and closing. // (This file had a lowercase E in Thunderbird 65 and earlier.) 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. var windowListener = new (class extends ExtensionCommon.EventEmitter { constructor() { super(); this.callbackCount = 0; this.callbackOnCloseButtonCount = 0; } addOnCloseButton(callback) { if (this.callbackOnCloseButtonCount == 0) { this.on("close-clicked", callback); this.callbackOnCloseButtonCount++; ExtensionSupport.registerWindowListener("closeButtonListener", { chromeURLs: [ "chrome://messenger/content/messenger.xhtml", "chrome://messenger/content/messenger.xul", ], onLoadWindow: function (window) { window.addEventListener("close", windowListener.onCloseButton, true); windowListener.hijackTitlebarCloseButton(window); }, }); } } removeOnCloseButton(callback) { if (this.callbackOnCloseButtonCount == 1) { this.off("close-clicked", callback); this.callbackOnCloseButtonCount--; for (let window of ExtensionSupport.openWindows) { if ( [ "chrome://messenger/content/messenger.xhtml", "chrome://messenger/content/messenger.xul", ].includes(window.location.href) ) { window.removeEventListener( "close", windowListener.onCloseButton, true ); } } ExtensionSupport.unregisterWindowListener("closeButtonListener"); } } onCloseButton(event) { windowListener.emit("close-clicked"); if (event) event.preventDefault(); return true; } hijackTitlebarCloseButton(window) { if ( windowListener.replaceCommand(window, "titlebar-close", function () { return windowListener.onCloseButton(null); }) ) { console.log("replaced command= " + "titlebar-close"); } } replaceCommand(window, eltId, gotHidden) { let elt = window.document.getElementById(eltId); if (!elt) { console.log("Element '" + eltId + "' not found. Command not replaced."); return false; } let prevent = null; if (elt.command) { prevent = { event: "click", func: function (e) { e.preventDefault(); }, }; } else if (elt.getAttribute("oncommand")) { prevent = { event: "command", func: function (e) { e.stopPropagation(); }, }; } else { console.warn("Could not replace oncommand on " + eltId); return false; } let callback = function (event) { if (event.target.id === eltId) { console.debug(prevent.event + " on " + eltId); if (gotHidden()) prevent.func(event); } }; /* We put listeners on the "titlebar" parent node, because: - we can hardly short-circuit command/oncommand (probably because they are registered first) - we'd have otherwise to alter "oncommand"/"command" attribute and use Function(), which do not pass review nowadays. */ elt.parentNode.addEventListener(prevent.event, callback, true); return true; } })();