Merge lp:~adrianiainlam/messagingmenu-extension/thunderbird-68 into lp:messagingmenu-extension

Proposed by Adrian I. Lam
Status: Needs review
Proposed branch: lp:~adrianiainlam/messagingmenu-extension/thunderbird-68
Merge into: lp:messagingmenu-extension
Diff against target: 1690 lines (+1490/-65)
11 files modified
README (+13/-0)
api/WindowListener/implementation.js (+1186/-0)
api/WindowListener/schema.json (+108/-0)
background.js (+78/-0)
content/skin/indicator-messages.svg (+15/-0)
content/thunderbirdMenu.js (+11/-1)
manifest.json (+32/-0)
res/modules/LibIndicateBackend.jsm (+10/-9)
res/modules/LibMessagingMenuBackend.jsm (+7/-5)
res/modules/MessagingMenuModule.jsm (+26/-46)
res/modules/utils.jsm (+4/-4)
To merge this branch: bzr merge lp:~adrianiainlam/messagingmenu-extension/thunderbird-68
Reviewer Review Type Date Requested Status
Firefox and Thunderbird extension hackers Pending
Review via email: mp+377161@code.launchpad.net

Description of the change

Porting to Thunderbird 68. Made some minimal changes until it seems to work (Desktop notification, messaging icon turns blue, number showing in the launcher icon) in Unity in Ubuntu 18.04 with Thunderbird 68.2.2.

Didn't run the test-messaging-menu.js file since I have no idea how to do so. Also I have never touched any Thunderbird add-ons (or any other browser add-ons) so I could be doing things wrong here.

To post a comment you must log in.
161. By Adrian I. Lam

Porting to Thunderbird 78 using WindowListener API

162. By Adrian I. Lam

Port to Thunderbird 91 (still compatible with TB 78)

163. By Adrian I. Lam

Remove sender/return-path/author check.

Probably an API change has caused the author field to include
additional information. For example, I am getting

    <email address hidden> (Display Name)

And the part in the brackets causes a mismatch with the return-path.
I have decided to just remove this check completely, because writing
a regex that correctly extracts the email address, while covering all
corner cases, seems too complicated (cf. <https://stackoverflow.com/a/201378>).

Revert setting attentionForAll to true.

Unmerged revisions

163. By Adrian I. Lam

Remove sender/return-path/author check.

Probably an API change has caused the author field to include
additional information. For example, I am getting

    <email address hidden> (Display Name)

And the part in the brackets causes a mismatch with the return-path.
I have decided to just remove this check completely, because writing
a regex that correctly extracts the email address, while covering all
corner cases, seems too complicated (cf. <https://stackoverflow.com/a/201378>).

Revert setting attentionForAll to true.

162. By Adrian I. Lam

Port to Thunderbird 91 (still compatible with TB 78)

161. By Adrian I. Lam

Porting to Thunderbird 78 using WindowListener API

160. By Adrian I. Lam

Porting to Thunderbird 68.

 - Use manifest.json instead of install.rdf
   Ref: https://developer.thunderbird.net/add-ons/tb68/overlays#switch-to-json-manifest

 - Changed Components.utils.import to ChromeUtils.import
   Ref: https://developer.thunderbird.net/add-ons/tb68/changes#changed-import-of-javascript-modules

 - Changed name of mailServices.js
   Ref: https://developer.thunderbird.net/add-ons/tb68/changes#renamed-javascript-modules

Also fixed an undefined variable bug.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'README'
2--- README 1970-01-01 00:00:00 +0000
3+++ README 2021-06-05 11:50:28 +0000
4@@ -0,0 +1,13 @@
5+The code is licensed under MPL 1.1/GPL 2.0/LGPL 2.1.
6+
7+The code in api/WindowListener is taken from https://github.com/thundernest/addon-developer-support/tree/master/wrapper-apis/WindowListener and is licensed under MPL 2.0.
8+
9+The icon in content/skin/indicator-messages.svg is taken from ubuntu-mono
10+(ubuntu-themes), by Canonical Ltd., used under CC-BY-SA 3.0.
11+
12+Note: I have attempted to port this addon to Thunderbird 68, and subsequently to
13+78 and 91. However for the most part I have no idea what I'm doing. Please
14+report back if I broke anything.
15+
16+Reference: https://github.com/thundernest/addon-developer-support/wiki/Using-the-WindowListener-API-to-convert-a-Legacy-Overlay-WebExtension-into-a-MailExtension-for-Thunderbird-78
17+
18
19=== added directory 'api'
20=== added directory 'api/WindowListener'
21=== added file 'api/WindowListener/implementation.js'
22--- api/WindowListener/implementation.js 1970-01-01 00:00:00 +0000
23+++ api/WindowListener/implementation.js 2021-06-05 11:50:28 +0000
24@@ -0,0 +1,1186 @@
25+/*
26+ * This file is provided by the addon-developer-support repository at
27+ * https://github.com/thundernest/addon-developer-support
28+ *
29+ * Version: 1.56
30+ *
31+ * Author: John Bieling (john@thunderbird.net)
32+ *
33+ * This Source Code Form is subject to the terms of the Mozilla Public
34+ * License, v. 2.0. If a copy of the MPL was not distributed with this
35+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
36+ */
37+
38+// Import some things we need.
39+var { ExtensionCommon } = ChromeUtils.import(
40+ "resource://gre/modules/ExtensionCommon.jsm"
41+);
42+var { ExtensionSupport } = ChromeUtils.import(
43+ "resource:///modules/ExtensionSupport.jsm"
44+);
45+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
46+
47+var WindowListener = class extends ExtensionCommon.ExtensionAPI {
48+ log(msg) {
49+ if (this.debug) console.log("WindowListener API: " + msg);
50+ }
51+
52+ getThunderbirdVersion() {
53+ let parts = Services.appinfo.version.split(".");
54+ return {
55+ major: parseInt(parts[0]),
56+ minor: parseInt(parts[1]),
57+ revision: parts.length > 2 ? parseInt(parts[2]) : 0,
58+ }
59+ }
60+
61+ getCards(e) {
62+ // This gets triggered by real events but also manually by providing the outer window.
63+ // The event is attached to the outer browser, get the inner one.
64+ let doc;
65+
66+ // 78,86, and 87+ need special handholding. *Yeah*.
67+ if (this.getThunderbirdVersion().major < 86) {
68+ let ownerDoc = e.document || e.target.ownerDocument;
69+ doc = ownerDoc.getElementById("html-view-browser").contentDocument;
70+ } else if (this.getThunderbirdVersion().major < 87) {
71+ let ownerDoc = e.document || e.target;
72+ doc = ownerDoc.getElementById("html-view-browser").contentDocument;
73+ } else {
74+ doc = e.document || e.target;
75+ }
76+ return doc.querySelectorAll("addon-card");
77+ }
78+
79+ // Add pref entry to 68
80+ add68PrefsEntry(event) {
81+ let id = this.menu_addonPrefs_id + "_" + this.uniqueRandomID;
82+
83+ // Get the best size of the icon (16px or bigger)
84+ let iconSizes = this.extension.manifest.icons
85+ ? Object.keys(this.extension.manifest.icons)
86+ : [];
87+ iconSizes.sort((a, b) => a - b);
88+ let bestSize = iconSizes.filter((e) => parseInt(e) >= 16).shift();
89+ let icon = bestSize ? this.extension.manifest.icons[bestSize] : "";
90+
91+ let name = this.extension.manifest.name;
92+ let entry = icon
93+ ? event.target.ownerGlobal.MozXULElement.parseXULToFragment(
94+ `<menuitem class="menuitem-iconic" id="${id}" image="${icon}" label="${name}" />`
95+ )
96+ : event.target.ownerGlobal.MozXULElement.parseXULToFragment(
97+ `<menuitem id="${id}" label="${name}" />`
98+ );
99+
100+ event.target.appendChild(entry);
101+ let noPrefsElem = event.target.querySelector('[disabled="true"]');
102+ // using collapse could be undone by core, so we use display none
103+ // noPrefsElem.setAttribute("collapsed", "true");
104+ noPrefsElem.style.display = "none";
105+ event.target.ownerGlobal.document
106+ .getElementById(id)
107+ .addEventListener("command", this);
108+ }
109+
110+ // Event handler for the addon manager, to update the state of the options button.
111+ handleEvent(e) {
112+ switch (e.type) {
113+ // 68 add-on options menu showing
114+ case "popupshowing":
115+ {
116+ this.add68PrefsEntry(e);
117+ }
118+ break;
119+
120+ // 78/88 add-on options menu/button click
121+ case "click":
122+ {
123+ e.preventDefault();
124+ e.stopPropagation();
125+ let WL = {};
126+ WL.extension = this.extension;
127+ WL.messenger = this.getMessenger(this.context);
128+ let w = Services.wm.getMostRecentWindow("mail:3pane");
129+ w.openDialog(
130+ this.pathToOptionsPage,
131+ "AddonOptions",
132+ "chrome,resizable,centerscreen",
133+ WL
134+ );
135+ }
136+ break;
137+
138+ // 68 add-on options menu command
139+ case "command":
140+ {
141+ let WL = {};
142+ WL.extension = this.extension;
143+ WL.messenger = this.getMessenger(this.context);
144+ e.target.ownerGlobal.openDialog(
145+ this.pathToOptionsPage,
146+ "AddonOptions",
147+ "chrome,resizable,centerscreen",
148+ WL
149+ );
150+ }
151+ break;
152+
153+ // update, ViewChanged and manual call for add-on manager options overlay
154+ default: {
155+ let cards = this.getCards(e);
156+ for (let card of cards) {
157+ // Setup either the options entry in the menu or the button
158+ //window.document.getElementById(id).addEventListener("command", function() {window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", WL)});
159+ if (card.addon.id == this.extension.id) {
160+ let optionsMenu =
161+ (this.getThunderbirdVersion().major > 78 && this.getThunderbirdVersion().major < 88) ||
162+ (this.getThunderbirdVersion().major == 78 && this.getThunderbirdVersion().minor < 10) ||
163+ (this.getThunderbirdVersion().major == 78 && this.getThunderbirdVersion().minor == 10 && this.getThunderbirdVersion().revision < 2);
164+ if (optionsMenu) {
165+ // Options menu in 78.0-78.10 and 79-87
166+ let addonOptionsLegacyEntry = card.querySelector(
167+ ".extension-options-legacy"
168+ );
169+ if (card.addon.isActive && !addonOptionsLegacyEntry) {
170+ let addonOptionsEntry = card.querySelector(
171+ "addon-options panel-list panel-item[action='preferences']"
172+ );
173+ addonOptionsLegacyEntry = card.ownerDocument.createElement(
174+ "panel-item"
175+ );
176+ addonOptionsLegacyEntry.setAttribute(
177+ "data-l10n-id",
178+ "preferences-addon-button"
179+ );
180+ addonOptionsLegacyEntry.classList.add(
181+ "extension-options-legacy"
182+ );
183+ addonOptionsEntry.parentNode.insertBefore(
184+ addonOptionsLegacyEntry,
185+ addonOptionsEntry
186+ );
187+ card
188+ .querySelector(".extension-options-legacy")
189+ .addEventListener("click", this);
190+ } else if (!card.addon.isActive && addonOptionsLegacyEntry) {
191+ addonOptionsLegacyEntry.remove();
192+ }
193+ } else {
194+ // Add-on button in 88
195+ let addonOptionsButton = card.querySelector(
196+ ".windowlistener-options-button"
197+ );
198+ if (card.addon.isActive && !addonOptionsButton) {
199+ addonOptionsButton = card.ownerDocument.createElement("button");
200+ addonOptionsButton.classList.add("windowlistener-options-button");
201+ addonOptionsButton.classList.add("extension-options-button");
202+ card.optionsButton.parentNode.insertBefore(
203+ addonOptionsButton,
204+ card.optionsButton
205+ );
206+ card
207+ .querySelector(".windowlistener-options-button")
208+ .addEventListener("click", this);
209+ } else if (!card.addon.isActive && addonOptionsButton) {
210+ addonOptionsButton.remove();
211+ }
212+ }
213+ }
214+ }
215+ }
216+ }
217+ }
218+
219+ // Some tab/add-on-manager related functions
220+ getTabMail(window) {
221+ return window.document.getElementById("tabmail");
222+ }
223+
224+ // returns the outer browser, not the nested browser of the add-on manager
225+ // events must be attached to the outer browser
226+ getAddonManagerFromTab(tab) {
227+ if (tab.browser) {
228+ let win = tab.browser.contentWindow;
229+ if (win && win.location.href == "about:addons") {
230+ return win;
231+ }
232+ }
233+ }
234+
235+ getAddonManagerFromWindow(window) {
236+ let tabMail = this.getTabMail(window);
237+ for (let tab of tabMail.tabInfo) {
238+ let win = this.getAddonManagerFromTab(tab);
239+ if (win) {
240+ return win;
241+ }
242+ }
243+ }
244+
245+ setupAddonManager(managerWindow, forceLoad = false) {
246+ if (!managerWindow) {
247+ return;
248+ }
249+ if (!(
250+ managerWindow &&
251+ managerWindow[this.uniqueRandomID] &&
252+ managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners
253+ )) {
254+ managerWindow.document.addEventListener("ViewChanged", this);
255+ managerWindow.document.addEventListener("update", this);
256+ managerWindow.document.addEventListener("view-loaded", this);
257+ managerWindow[this.uniqueRandomID] = {};
258+ managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true;
259+ }
260+ if (forceLoad) this.handleEvent(managerWindow);
261+ }
262+
263+ getMessenger(context) {
264+ let apis = ["storage", "runtime", "extension", "i18n"];
265+
266+ function getStorage() {
267+ let localstorage = null;
268+ try {
269+ localstorage = context.apiCan.findAPIPath("storage");
270+ localstorage.local.get = (...args) =>
271+ localstorage.local.callMethodInParentProcess("get", args);
272+ localstorage.local.set = (...args) =>
273+ localstorage.local.callMethodInParentProcess("set", args);
274+ localstorage.local.remove = (...args) =>
275+ localstorage.local.callMethodInParentProcess("remove", args);
276+ localstorage.local.clear = (...args) =>
277+ localstorage.local.callMethodInParentProcess("clear", args);
278+ } catch (e) {
279+ console.info("Storage permission is missing");
280+ }
281+ return localstorage;
282+ }
283+
284+ let messenger = {};
285+ for (let api of apis) {
286+ switch (api) {
287+ case "storage":
288+ XPCOMUtils.defineLazyGetter(messenger, "storage", () => getStorage());
289+ break;
290+
291+ default:
292+ XPCOMUtils.defineLazyGetter(messenger, api, () =>
293+ context.apiCan.findAPIPath(api)
294+ );
295+ }
296+ }
297+ return messenger;
298+ }
299+
300+ error(msg) {
301+ if (this.debug) console.error("WindowListener API: " + msg);
302+ }
303+
304+ // async sleep function using Promise
305+ async sleep(delay) {
306+ let timer = Components.classes["@mozilla.org/timer;1"].createInstance(
307+ Components.interfaces.nsITimer
308+ );
309+ return new Promise(function (resolve, reject) {
310+ let event = {
311+ notify: function (timer) {
312+ resolve();
313+ },
314+ };
315+ timer.initWithCallback(
316+ event,
317+ delay,
318+ Components.interfaces.nsITimer.TYPE_ONE_SHOT
319+ );
320+ });
321+ }
322+
323+ getAPI(context) {
324+ // Track if this is the background/main context
325+ if (context.viewType != "background")
326+ throw new Error(
327+ "The WindowListener API may only be called from the background page."
328+ );
329+
330+ this.context = context;
331+
332+ this.uniqueRandomID = "AddOnNS" + context.extension.instanceId;
333+ this.menu_addonPrefs_id = "addonPrefs";
334+
335+ this.registeredWindows = {};
336+ this.pathToStartupScript = null;
337+ this.pathToShutdownScript = null;
338+ this.pathToOptionsPage = null;
339+ this.chromeHandle = null;
340+ this.chromeData = null;
341+ this.resourceData = null;
342+ this.openWindows = [];
343+ this.debug = context.extension.addonData.temporarilyInstalled;
344+
345+ const aomStartup = Cc[
346+ "@mozilla.org/addons/addon-manager-startup;1"
347+ ].getService(Ci.amIAddonManagerStartup);
348+ const resProto = Cc[
349+ "@mozilla.org/network/protocol;1?name=resource"
350+ ].getService(Ci.nsISubstitutingProtocolHandler);
351+
352+ let self = this;
353+
354+ // TabMonitor to detect opening of tabs, to setup the options button in the add-on manager.
355+ this.tabMonitor = {
356+ onTabTitleChanged(aTab) {},
357+ onTabClosing(aTab) {},
358+ onTabPersist(aTab) {},
359+ onTabRestored(aTab) {},
360+ onTabSwitched(aNewTab, aOldTab) {
361+ //self.setupAddonManager(self.getAddonManagerFromTab(aNewTab));
362+ },
363+ async onTabOpened(aTab) {
364+ if (aTab.browser) {
365+ if (!aTab.pageLoaded) {
366+ // await a location change if browser is not loaded yet
367+ await new Promise((resolve) => {
368+ let reporterListener = {
369+ QueryInterface: ChromeUtils.generateQI([
370+ "nsIWebProgressListener",
371+ "nsISupportsWeakReference",
372+ ]),
373+ onStateChange() {},
374+ onProgressChange() {},
375+ onLocationChange(
376+ /* in nsIWebProgress*/ aWebProgress,
377+ /* in nsIRequest*/ aRequest,
378+ /* in nsIURI*/ aLocation
379+ ) {
380+ aTab.browser.removeProgressListener(reporterListener);
381+ resolve();
382+ },
383+ onStatusChange() {},
384+ onSecurityChange() {},
385+ onContentBlockingEvent() {},
386+ };
387+ aTab.browser.addProgressListener(reporterListener);
388+ });
389+ }
390+ self.setupAddonManager(self.getAddonManagerFromTab(aTab));
391+ }
392+ },
393+ };
394+
395+ return {
396+ WindowListener: {
397+ async waitForMasterPassword() {
398+ // Wait until master password has been entered (if needed)
399+ while (!Services.logins.isLoggedIn) {
400+ self.log("Waiting for master password.");
401+ await self.sleep(1000);
402+ }
403+ self.log("Master password has been entered.");
404+ },
405+
406+ aDocumentExistsAt(uriString) {
407+ self.log(
408+ "Checking if document at <" +
409+ uriString +
410+ "> used in registration actually exists."
411+ );
412+ try {
413+ let uriObject = Services.io.newURI(uriString);
414+ let content = Cu.readUTF8URI(uriObject);
415+ } catch (e) {
416+ Components.utils.reportError(e);
417+ return false;
418+ }
419+ return true;
420+ },
421+
422+ registerOptionsPage(optionsUrl) {
423+ self.pathToOptionsPage = optionsUrl.startsWith("chrome://")
424+ ? optionsUrl
425+ : context.extension.rootURI.resolve(optionsUrl);
426+ },
427+
428+ registerDefaultPrefs(defaultUrl) {
429+ let url = context.extension.rootURI.resolve(defaultUrl);
430+
431+ let prefsObj = {};
432+ prefsObj.Services = ChromeUtils.import(
433+ "resource://gre/modules/Services.jsm"
434+ ).Services;
435+ prefsObj.pref = function (aName, aDefault) {
436+ let defaults = Services.prefs.getDefaultBranch("");
437+ switch (typeof aDefault) {
438+ case "string":
439+ return defaults.setStringPref(aName, aDefault);
440+
441+ case "number":
442+ return defaults.setIntPref(aName, aDefault);
443+
444+ case "boolean":
445+ return defaults.setBoolPref(aName, aDefault);
446+
447+ default:
448+ throw new Error(
449+ "Preference <" +
450+ aName +
451+ "> has an unsupported type <" +
452+ typeof aDefault +
453+ ">. Allowed are string, number and boolean."
454+ );
455+ }
456+ };
457+ Services.scriptloader.loadSubScript(url, prefsObj, "UTF-8");
458+ },
459+
460+ registerChromeUrl(data) {
461+ let chromeData = [];
462+ let resourceData = [];
463+ for (let entry of data) {
464+ if (entry[0] == "resource") resourceData.push(entry);
465+ else chromeData.push(entry);
466+ }
467+
468+ if (chromeData.length > 0) {
469+ const manifestURI = Services.io.newURI(
470+ "manifest.json",
471+ null,
472+ context.extension.rootURI
473+ );
474+ self.chromeHandle = aomStartup.registerChrome(
475+ manifestURI,
476+ chromeData
477+ );
478+ }
479+
480+ for (let res of resourceData) {
481+ // [ "resource", "shortname" , "path" ]
482+ let uri = Services.io.newURI(
483+ res[2],
484+ null,
485+ context.extension.rootURI
486+ );
487+ resProto.setSubstitutionWithFlags(
488+ res[1],
489+ uri,
490+ resProto.ALLOW_CONTENT_ACCESS
491+ );
492+ }
493+
494+ self.chromeData = chromeData;
495+ self.resourceData = resourceData;
496+ },
497+
498+ registerWindow(windowHref, jsFile) {
499+ if (self.debug && !this.aDocumentExistsAt(windowHref)) {
500+ self.error(
501+ "Attempt to register an injector script for non-existent window: " +
502+ windowHref
503+ );
504+ return;
505+ }
506+
507+ if (!self.registeredWindows.hasOwnProperty(windowHref)) {
508+ // path to JS file can either be chrome:// URL or a relative URL
509+ let path = jsFile.startsWith("chrome://")
510+ ? jsFile
511+ : context.extension.rootURI.resolve(jsFile);
512+
513+ self.registeredWindows[windowHref] = path;
514+ } else {
515+ self.error(
516+ "Window <" + windowHref + "> has already been registered"
517+ );
518+ }
519+ },
520+
521+ registerStartupScript(aPath) {
522+ self.pathToStartupScript = aPath.startsWith("chrome://")
523+ ? aPath
524+ : context.extension.rootURI.resolve(aPath);
525+ },
526+
527+ registerShutdownScript(aPath) {
528+ self.pathToShutdownScript = aPath.startsWith("chrome://")
529+ ? aPath
530+ : context.extension.rootURI.resolve(aPath);
531+ },
532+
533+ openOptionsDialog(windowId) {
534+ let window = context.extension.windowManager.get(windowId, context)
535+ .window;
536+ let WL = {};
537+ WL.extension = self.extension;
538+ WL.messenger = self.getMessenger(self.context);
539+ window.openDialog(
540+ self.pathToOptionsPage,
541+ "AddonOptions",
542+ "chrome,resizable,centerscreen",
543+ WL
544+ );
545+ },
546+
547+ async startListening() {
548+ // load the registered startup script, if one has been registered
549+ // (mail3:pane may not have been fully loaded yet)
550+ if (self.pathToStartupScript) {
551+ let startupJS = {};
552+ startupJS.WL = {};
553+ startupJS.WL.extension = self.extension;
554+ startupJS.WL.messenger = self.getMessenger(self.context);
555+ try {
556+ if (self.pathToStartupScript) {
557+ Services.scriptloader.loadSubScript(
558+ self.pathToStartupScript,
559+ startupJS,
560+ "UTF-8"
561+ );
562+ // delay startup until startup has been finished
563+ self.log(
564+ "Waiting for async startup() in <" +
565+ self.pathToStartupScript +
566+ "> to finish."
567+ );
568+ if (startupJS.startup) {
569+ await startupJS.startup();
570+ self.log(
571+ "startup() in <" + self.pathToStartupScript + "> finished"
572+ );
573+ } else {
574+ self.log(
575+ "No startup() in <" + self.pathToStartupScript + "> found."
576+ );
577+ }
578+ }
579+ } catch (e) {
580+ Components.utils.reportError(e);
581+ }
582+ }
583+
584+ let urls = Object.keys(self.registeredWindows);
585+ if (urls.length > 0) {
586+ // Before registering the window listener, check which windows are already open
587+ self.openWindows = [];
588+ for (let window of Services.wm.getEnumerator(null)) {
589+ self.openWindows.push(window);
590+ }
591+
592+ // Register window listener for all pre-registered windows
593+ ExtensionSupport.registerWindowListener(
594+ "injectListener_" + self.uniqueRandomID,
595+ {
596+ // React on all windows and manually reduce to the registered
597+ // windows, so we can do special actions when the main
598+ // messenger window is opened.
599+ //chromeURLs: Object.keys(self.registeredWindows),
600+ async onLoadWindow(window) {
601+ // Create add-on scope
602+ window[self.uniqueRandomID] = {};
603+
604+ // Special action #1: If this is the main messenger window
605+ if (
606+ window.location.href ==
607+ "chrome://messenger/content/messenger.xul" ||
608+ window.location.href ==
609+ "chrome://messenger/content/messenger.xhtml"
610+ ) {
611+ if (self.pathToOptionsPage) {
612+ if (self.getThunderbirdVersion().major < 78) {
613+ let element_addonPrefs = window.document.getElementById(
614+ self.menu_addonPrefs_id
615+ );
616+ element_addonPrefs.addEventListener(
617+ "popupshowing",
618+ self
619+ );
620+ } else {
621+ // Setup the options button/menu in the add-on manager, if it is already open.
622+ self.setupAddonManager(
623+ self.getAddonManagerFromWindow(window),
624+ true
625+ );
626+ // Add a tabmonitor, to be able to setup the options button/menu in the add-on manager.
627+ self
628+ .getTabMail(window)
629+ .registerTabMonitor(self.tabMonitor);
630+ window[self.uniqueRandomID].hasTabMonitor = true;
631+ }
632+ }
633+ }
634+
635+ // Special action #2: If this page contains browser elements
636+ let browserElements = window.document.getElementsByTagName(
637+ "browser"
638+ );
639+ if (browserElements.length > 0) {
640+ //register a MutationObserver
641+ window[
642+ self.uniqueRandomID
643+ ]._mObserver = new window.MutationObserver(function (
644+ mutations
645+ ) {
646+ mutations.forEach(async function (mutation) {
647+ if (
648+ mutation.attributeName == "src" &&
649+ self.registeredWindows.hasOwnProperty(
650+ mutation.target.getAttribute("src")
651+ )
652+ ) {
653+ // When the MutationObserver callsback, the window is still showing "about:black" and it is going
654+ // to unload and then load the new page. Any eventListener attached to the window will be removed
655+ // so we cannot listen for the load event. We have to poll manually to learn when loading has finished.
656+ // On my system it takes 70ms.
657+ let loaded = false;
658+ for (let i = 0; i < 100 && !loaded; i++) {
659+ await self.sleep(100);
660+ let targetWindow =
661+ mutation.target.contentWindow.wrappedJSObject;
662+ if (
663+ targetWindow &&
664+ targetWindow.location.href ==
665+ mutation.target.getAttribute("src") &&
666+ targetWindow.document.readyState == "complete"
667+ ) {
668+ loaded = true;
669+ break;
670+ }
671+ }
672+ if (loaded) {
673+ let targetWindow =
674+ mutation.target.contentWindow.wrappedJSObject;
675+ // Create add-on scope
676+ targetWindow[self.uniqueRandomID] = {};
677+ // Inject with isAddonActivation = false
678+ self._loadIntoWindow(targetWindow, false);
679+ }
680+ }
681+ });
682+ });
683+
684+ for (let element of browserElements) {
685+ if (
686+ self.registeredWindows.hasOwnProperty(
687+ element.getAttribute("src")
688+ )
689+ ) {
690+ let targetWindow =
691+ element.contentWindow.wrappedJSObject;
692+ // Create add-on scope
693+ targetWindow[self.uniqueRandomID] = {};
694+ // Inject with isAddonActivation = true
695+ self._loadIntoWindow(targetWindow, true);
696+ } else {
697+ // Window/Browser is not yet fully loaded, postpone injection via MutationObserver
698+ window[self.uniqueRandomID]._mObserver.observe(
699+ element,
700+ {
701+ attributes: true,
702+ childList: false,
703+ characterData: false,
704+ }
705+ );
706+ }
707+ }
708+ }
709+
710+ // Load JS into window
711+ self._loadIntoWindow(
712+ window,
713+ self.openWindows.includes(window)
714+ );
715+ },
716+
717+ onUnloadWindow(window) {
718+ // Remove JS from window, window is being closed, addon is not shut down
719+ self._unloadFromWindow(window, false);
720+ },
721+ }
722+ );
723+ } else {
724+ self.error("Failed to start listening, no windows registered");
725+ }
726+ },
727+ },
728+ };
729+ }
730+
731+ _loadIntoWindow(window, isAddonActivation) {
732+ if (
733+ window.hasOwnProperty(this.uniqueRandomID) &&
734+ this.registeredWindows.hasOwnProperty(window.location.href)
735+ ) {
736+ try {
737+ let uniqueRandomID = this.uniqueRandomID;
738+ let extension = this.extension;
739+
740+ // Add reference to window to add-on scope
741+ window[this.uniqueRandomID].window = window;
742+ window[this.uniqueRandomID].document = window.document;
743+
744+ // Keep track of toolbarpalettes we are injecting into
745+ window[this.uniqueRandomID]._toolbarpalettes = {};
746+
747+ //Create WLDATA object
748+ window[this.uniqueRandomID].WL = {};
749+ window[this.uniqueRandomID].WL.scopeName = this.uniqueRandomID;
750+
751+ // Add helper function to inject CSS to WLDATA object
752+ window[this.uniqueRandomID].WL.injectCSS = function (cssFile) {
753+ let element;
754+ let v = parseInt(Services.appinfo.version.split(".").shift());
755+
756+ // using createElementNS in TB78 delays the insert process and hides any security violation errors
757+ if (v > 68) {
758+ element = window.document.createElement("link");
759+ } else {
760+ let ns = window.document.documentElement.lookupNamespaceURI("html");
761+ element = window.document.createElementNS(ns, "link");
762+ }
763+
764+ element.setAttribute("wlapi_autoinjected", uniqueRandomID);
765+ element.setAttribute("rel", "stylesheet");
766+ element.setAttribute("href", cssFile);
767+ return window.document.documentElement.appendChild(element);
768+ };
769+
770+ // Add helper function to inject XUL to WLDATA object
771+ window[this.uniqueRandomID].WL.injectElements = function (
772+ xulString,
773+ dtdFiles = [],
774+ debug = false
775+ ) {
776+ let toolbarsToResolve = [];
777+
778+ function checkElements(stringOfIDs) {
779+ let arrayOfIDs = stringOfIDs.split(",").map((e) => e.trim());
780+ for (let id of arrayOfIDs) {
781+ let element = window.document.getElementById(id);
782+ if (element) {
783+ return element;
784+ }
785+ }
786+ return null;
787+ }
788+
789+ function localize(entity) {
790+ let msg = entity.slice("__MSG_".length, -2);
791+ return extension.localeData.localizeMessage(msg);
792+ }
793+
794+ function injectChildren(elements, container) {
795+ if (debug) console.log(elements);
796+
797+ for (let i = 0; i < elements.length; i++) {
798+ // take care of persists
799+ const uri = window.document.documentURI;
800+ for (const persistentNode of elements[i].querySelectorAll(
801+ "[persist]"
802+ )) {
803+ for (const persistentAttribute of persistentNode
804+ .getAttribute("persist")
805+ .trim()
806+ .split(" ")) {
807+ if (
808+ Services.xulStore.hasValue(
809+ uri,
810+ persistentNode.id,
811+ persistentAttribute
812+ )
813+ ) {
814+ persistentNode.setAttribute(
815+ persistentAttribute,
816+ Services.xulStore.getValue(
817+ uri,
818+ persistentNode.id,
819+ persistentAttribute
820+ )
821+ );
822+ }
823+ }
824+ }
825+
826+ if (
827+ elements[i].hasAttribute("insertafter") &&
828+ checkElements(elements[i].getAttribute("insertafter"))
829+ ) {
830+ let insertAfterElement = checkElements(
831+ elements[i].getAttribute("insertafter")
832+ );
833+
834+ if (debug)
835+ console.log(
836+ elements[i].tagName +
837+ "#" +
838+ elements[i].id +
839+ ": insertafter " +
840+ insertAfterElement.id
841+ );
842+ if (
843+ debug &&
844+ elements[i].id &&
845+ window.document.getElementById(elements[i].id)
846+ ) {
847+ console.error(
848+ "The id <" +
849+ elements[i].id +
850+ "> of the injected element already exists in the document!"
851+ );
852+ }
853+ elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
854+ insertAfterElement.parentNode.insertBefore(
855+ elements[i],
856+ insertAfterElement.nextSibling
857+ );
858+ } else if (
859+ elements[i].hasAttribute("insertbefore") &&
860+ checkElements(elements[i].getAttribute("insertbefore"))
861+ ) {
862+ let insertBeforeElement = checkElements(
863+ elements[i].getAttribute("insertbefore")
864+ );
865+
866+ if (debug)
867+ console.log(
868+ elements[i].tagName +
869+ "#" +
870+ elements[i].id +
871+ ": insertbefore " +
872+ insertBeforeElement.id
873+ );
874+ if (
875+ debug &&
876+ elements[i].id &&
877+ window.document.getElementById(elements[i].id)
878+ ) {
879+ console.error(
880+ "The id <" +
881+ elements[i].id +
882+ "> of the injected element already exists in the document!"
883+ );
884+ }
885+ elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
886+ insertBeforeElement.parentNode.insertBefore(
887+ elements[i],
888+ insertBeforeElement
889+ );
890+ } else if (
891+ elements[i].id &&
892+ window.document.getElementById(elements[i].id)
893+ ) {
894+ // existing container match, dive into recursivly
895+ if (debug)
896+ console.log(
897+ elements[i].tagName +
898+ "#" +
899+ elements[i].id +
900+ " is an existing container, injecting into " +
901+ elements[i].id
902+ );
903+ injectChildren(
904+ Array.from(elements[i].children),
905+ window.document.getElementById(elements[i].id)
906+ );
907+ } else if (elements[i].localName === "toolbarpalette") {
908+ // These vanish from the document but still exist via the palette property
909+ if (debug) console.log(elements[i].id + " is a toolbarpalette");
910+ let boxes = [
911+ ...window.document.getElementsByTagName("toolbox"),
912+ ];
913+ let box = boxes.find(
914+ (box) => box.palette && box.palette.id === elements[i].id
915+ );
916+ let palette = box ? box.palette : null;
917+
918+ if (!palette) {
919+ if (debug)
920+ console.log(
921+ `The palette for ${elements[i].id} could not be found, deferring to later`
922+ );
923+ continue;
924+ }
925+
926+ if (debug)
927+ console.log(`The toolbox for ${elements[i].id} is ${box.id}`);
928+
929+ toolbarsToResolve.push(...box.querySelectorAll("toolbar"));
930+ toolbarsToResolve.push(
931+ ...window.document.querySelectorAll(
932+ `toolbar[toolboxid="${box.id}"]`
933+ )
934+ );
935+ for (let child of elements[i].children) {
936+ child.setAttribute("wlapi_autoinjected", uniqueRandomID);
937+ }
938+ window[uniqueRandomID]._toolbarpalettes[palette.id] = palette;
939+ injectChildren(Array.from(elements[i].children), palette);
940+ } else {
941+ // append element to the current container
942+ if (debug)
943+ console.log(
944+ elements[i].tagName +
945+ "#" +
946+ elements[i].id +
947+ ": append to " +
948+ container.id
949+ );
950+ elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
951+ container.appendChild(elements[i]);
952+ }
953+ }
954+ }
955+
956+ if (debug) console.log("Injecting into root document:");
957+ let localizedXulString = xulString.replace(
958+ /__MSG_(.*?)__/g,
959+ localize
960+ );
961+ injectChildren(
962+ Array.from(
963+ window.MozXULElement.parseXULToFragment(
964+ localizedXulString,
965+ dtdFiles
966+ ).children
967+ ),
968+ window.document.documentElement
969+ );
970+
971+ for (let bar of toolbarsToResolve) {
972+ let currentset = Services.xulStore.getValue(
973+ window.location,
974+ bar.id,
975+ "currentset"
976+ );
977+ if (currentset) {
978+ bar.currentSet = currentset;
979+ } else if (bar.getAttribute("defaultset")) {
980+ bar.currentSet = bar.getAttribute("defaultset");
981+ }
982+ }
983+ };
984+
985+ // Add extension object to WLDATA object
986+ window[this.uniqueRandomID].WL.extension = this.extension;
987+ // Add messenger object to WLDATA object
988+ window[this.uniqueRandomID].WL.messenger = this.getMessenger(
989+ this.context
990+ );
991+ // Load script into add-on scope
992+ Services.scriptloader.loadSubScript(
993+ this.registeredWindows[window.location.href],
994+ window[this.uniqueRandomID],
995+ "UTF-8"
996+ );
997+ window[this.uniqueRandomID].onLoad(isAddonActivation);
998+ } catch (e) {
999+ Components.utils.reportError(e);
1000+ }
1001+ }
1002+ }
1003+
1004+ _unloadFromWindow(window, isAddonDeactivation) {
1005+ // unload any contained browser elements
1006+ if (
1007+ window.hasOwnProperty(this.uniqueRandomID) &&
1008+ window[this.uniqueRandomID].hasOwnProperty("_mObserver")
1009+ ) {
1010+ window[this.uniqueRandomID]._mObserver.disconnect();
1011+ let browserElements = window.document.getElementsByTagName("browser");
1012+ for (let element of browserElements) {
1013+ if (element.contentWindow) {
1014+ this._unloadFromWindow(
1015+ element.contentWindow.wrappedJSObject,
1016+ isAddonDeactivation
1017+ );
1018+ }
1019+ }
1020+ }
1021+
1022+ if (
1023+ window.hasOwnProperty(this.uniqueRandomID) &&
1024+ this.registeredWindows.hasOwnProperty(window.location.href)
1025+ ) {
1026+ // Remove this window from the list of open windows
1027+ this.openWindows = this.openWindows.filter((e) => e != window);
1028+
1029+ if (window[this.uniqueRandomID].onUnload) {
1030+ try {
1031+ // Call onUnload()
1032+ window[this.uniqueRandomID].onUnload(isAddonDeactivation);
1033+ } catch (e) {
1034+ Components.utils.reportError(e);
1035+ }
1036+ }
1037+
1038+ // Remove all auto injected objects
1039+ let elements = Array.from(
1040+ window.document.querySelectorAll(
1041+ '[wlapi_autoinjected="' + this.uniqueRandomID + '"]'
1042+ )
1043+ );
1044+ for (let element of elements) {
1045+ element.remove();
1046+ }
1047+
1048+ // Remove all autoinjected toolbarpalette items
1049+ for (const palette of Object.values(
1050+ window[this.uniqueRandomID]._toolbarpalettes
1051+ )) {
1052+ let elements = Array.from(
1053+ palette.querySelectorAll(
1054+ '[wlapi_autoinjected="' + this.uniqueRandomID + '"]'
1055+ )
1056+ );
1057+ for (let element of elements) {
1058+ element.remove();
1059+ }
1060+ }
1061+ }
1062+
1063+ // Remove add-on scope, if it exists
1064+ if (window.hasOwnProperty(this.uniqueRandomID)) {
1065+ delete window[this.uniqueRandomID];
1066+ }
1067+ }
1068+
1069+ onShutdown(isAppShutdown) {
1070+ if (isAppShutdown) {
1071+ return; // the application gets unloaded anyway
1072+ }
1073+
1074+ // Unload from all still open windows
1075+ let urls = Object.keys(this.registeredWindows);
1076+ if (urls.length > 0) {
1077+ for (let window of Services.wm.getEnumerator(null)) {
1078+ //remove our entry in the add-on options menu
1079+ if (
1080+ this.pathToOptionsPage &&
1081+ (window.location.href == "chrome://messenger/content/messenger.xul" ||
1082+ window.location.href ==
1083+ "chrome://messenger/content/messenger.xhtml")
1084+ ) {
1085+ if (this.getThunderbirdVersion().major < 78) {
1086+ let element_addonPrefs = window.document.getElementById(
1087+ this.menu_addonPrefs_id
1088+ );
1089+ element_addonPrefs.removeEventListener("popupshowing", this);
1090+ // Remove our entry.
1091+ let entry = window.document.getElementById(
1092+ this.menu_addonPrefs_id + "_" + this.uniqueRandomID
1093+ );
1094+ if (entry) entry.remove();
1095+ // Do we have to unhide the noPrefsElement?
1096+ if (element_addonPrefs.children.length == 1) {
1097+ let noPrefsElem = element_addonPrefs.querySelector(
1098+ '[disabled="true"]'
1099+ );
1100+ noPrefsElem.style.display = "inline";
1101+ }
1102+ } else {
1103+ // Remove event listener for addon manager view changes
1104+ let managerWindow = this.getAddonManagerFromWindow(window);
1105+ if (
1106+ managerWindow &&
1107+ managerWindow[this.uniqueRandomID] &&
1108+ managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners
1109+ ) {
1110+ managerWindow.document.removeEventListener("ViewChanged", this);
1111+ managerWindow.document.removeEventListener("view-loaded", this);
1112+ managerWindow.document.removeEventListener("update", this);
1113+
1114+ let cards = this.getCards(managerWindow);
1115+ if (this.getThunderbirdVersion().major < 88) {
1116+ // Remove options menu in 78-87
1117+ for (let card of cards) {
1118+ let addonOptionsLegacyEntry = card.querySelector(
1119+ ".extension-options-legacy"
1120+ );
1121+ if (addonOptionsLegacyEntry) addonOptionsLegacyEntry.remove();
1122+ }
1123+ } else {
1124+ // Remove options button in 88
1125+ for (let card of cards) {
1126+ if (card.addon.id == this.extension.id) {
1127+ let addonOptionsButton = card.querySelector(
1128+ ".windowlistener-options-button"
1129+ );
1130+ if (addonOptionsButton) addonOptionsButton.remove();
1131+ break;
1132+ }
1133+ }
1134+ }
1135+ }
1136+
1137+ // Remove tabmonitor
1138+ if (window[this.uniqueRandomID].hasTabMonitor) {
1139+ this.getTabMail(window).unregisterTabMonitor(this.tabMonitor);
1140+ window[this.uniqueRandomID].hasTabMonitor = false;
1141+ }
1142+ }
1143+ }
1144+
1145+ // if it is app shutdown, it is not just an add-on deactivation
1146+ this._unloadFromWindow(window, !isAppShutdown);
1147+ }
1148+ // Stop listening for new windows.
1149+ ExtensionSupport.unregisterWindowListener(
1150+ "injectListener_" + this.uniqueRandomID
1151+ );
1152+ }
1153+
1154+ // Load registered shutdown script
1155+ let shutdownJS = {};
1156+ shutdownJS.extension = this.extension;
1157+ try {
1158+ if (this.pathToShutdownScript)
1159+ Services.scriptloader.loadSubScript(
1160+ this.pathToShutdownScript,
1161+ shutdownJS,
1162+ "UTF-8"
1163+ );
1164+ } catch (e) {
1165+ Components.utils.reportError(e);
1166+ }
1167+
1168+ // Extract all registered chrome content urls
1169+ let chromeUrls = [];
1170+ if (this.chromeData) {
1171+ for (let chromeEntry of this.chromeData) {
1172+ if (chromeEntry[0].toLowerCase().trim() == "content") {
1173+ chromeUrls.push("chrome://" + chromeEntry[1] + "/");
1174+ }
1175+ }
1176+ }
1177+
1178+ // Unload JSMs of this add-on
1179+ const rootURI = this.extension.rootURI.spec;
1180+ for (let module of Cu.loadedModules) {
1181+ if (
1182+ module.startsWith(rootURI) ||
1183+ (module.startsWith("chrome://") &&
1184+ chromeUrls.find((s) => module.startsWith(s)))
1185+ ) {
1186+ this.log("Unloading: " + module);
1187+ Cu.unload(module);
1188+ }
1189+ }
1190+
1191+ // Flush all caches
1192+ Services.obs.notifyObservers(null, "startupcache-invalidate");
1193+ this.registeredWindows = {};
1194+
1195+ if (this.resourceData) {
1196+ const resProto = Cc[
1197+ "@mozilla.org/network/protocol;1?name=resource"
1198+ ].getService(Ci.nsISubstitutingProtocolHandler);
1199+ for (let res of this.resourceData) {
1200+ // [ "resource", "shortname" , "path" ]
1201+ resProto.setSubstitution(res[1], null);
1202+ }
1203+ }
1204+
1205+ if (this.chromeHandle) {
1206+ this.chromeHandle.destruct();
1207+ this.chromeHandle = null;
1208+ }
1209+ }
1210+};
1211
1212=== added file 'api/WindowListener/schema.json'
1213--- api/WindowListener/schema.json 1970-01-01 00:00:00 +0000
1214+++ api/WindowListener/schema.json 2021-06-05 11:50:28 +0000
1215@@ -0,0 +1,108 @@
1216+[
1217+ {
1218+ "namespace": "WindowListener",
1219+ "functions": [
1220+ {
1221+ "name": "registerDefaultPrefs",
1222+ "type": "function",
1223+ "parameters": [
1224+ {
1225+ "name": "aPath",
1226+ "type": "string",
1227+ "description": "Relative path to the default file."
1228+ }
1229+ ]
1230+ },
1231+ {
1232+ "name": "registerOptionsPage",
1233+ "type": "function",
1234+ "parameters": [
1235+ {
1236+ "name": "aPath",
1237+ "type": "string",
1238+ "description": "Path to the options page, which should be made accessible in the (legacy) Add-On Options menu."
1239+ }
1240+ ]
1241+ },
1242+ {
1243+ "name": "registerChromeUrl",
1244+ "type": "function",
1245+ "description": "Register folders which should be available as chrome:// urls (as defined in the legacy chrome.manifest)",
1246+ "parameters": [
1247+ {
1248+ "name": "data",
1249+ "type": "array",
1250+ "items": {
1251+ "type": "array",
1252+ "items" : {
1253+ "type": "string"
1254+ }
1255+ },
1256+ "description": "Array of manifest url definitions (content, locale, resource)"
1257+ }
1258+ ]
1259+ },
1260+ {
1261+ "name": "waitForMasterPassword",
1262+ "type": "function",
1263+ "async": true,
1264+ "parameters": []
1265+ },
1266+ {
1267+ "name": "openOptionsDialog",
1268+ "type": "function",
1269+ "parameters": [
1270+ {
1271+ "name": "windowId",
1272+ "type": "integer",
1273+ "description": "Id of the window the dialog should be opened from."
1274+ }
1275+ ]
1276+ },
1277+ {
1278+ "name": "startListening",
1279+ "type": "function",
1280+ "async": true,
1281+ "parameters": []
1282+ },
1283+ {
1284+ "name": "registerWindow",
1285+ "type": "function",
1286+ "parameters": [
1287+ {
1288+ "name": "windowHref",
1289+ "type": "string",
1290+ "description": "Url of the window, which should be listen for."
1291+ },
1292+ {
1293+ "name": "jsFile",
1294+ "type": "string",
1295+ "description": "Path to the JavaScript file, which should be loaded into the window."
1296+ }
1297+ ]
1298+ },
1299+ {
1300+ "name": "registerStartupScript",
1301+ "type": "function",
1302+ "parameters": [
1303+ {
1304+ "name": "aPath",
1305+ "type": "string",
1306+ "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."
1307+ }
1308+ ]
1309+ },
1310+ {
1311+ "name": "registerShutdownScript",
1312+ "type": "function",
1313+ "parameters": [
1314+ {
1315+ "name": "aPath",
1316+ "type": "string",
1317+ "description": "Path to a JavaScript file, which should be executed on add-on shutdown."
1318+ }
1319+ ]
1320+ }
1321+ ]
1322+ }
1323+]
1324
1325=== added file 'background.js'
1326--- background.js 1970-01-01 00:00:00 +0000
1327+++ background.js 2021-06-05 11:50:28 +0000
1328@@ -0,0 +1,78 @@
1329+(async () => {
1330+
1331+ messenger.WindowListener.registerDefaultPrefs("defaults/preferences/messagingmenu.js");
1332+
1333+ messenger.WindowListener.registerChromeUrl([
1334+ ["resource", "messagingmenu", "res/"],
1335+ ["content", "messagingmenu", "content/"],
1336+ ["locale", "messagingmenu", "af", "locale/af/"],
1337+ ["locale", "messagingmenu", "ar", "locale/ar/"],
1338+ ["locale", "messagingmenu", "ast", "locale/ast/"],
1339+ ["locale", "messagingmenu", "be", "locale/be/"],
1340+ ["locale", "messagingmenu", "bg", "locale/bg/"],
1341+ ["locale", "messagingmenu", "bn", "locale/bn/"],
1342+ ["locale", "messagingmenu", "br", "locale/br/"],
1343+ ["locale", "messagingmenu", "bs", "locale/bs/"],
1344+ ["locale", "messagingmenu", "ca", "locale/ca/"],
1345+ ["locale", "messagingmenu", "cs", "locale/cs/"],
1346+ ["locale", "messagingmenu", "cy", "locale/cy/"],
1347+ ["locale", "messagingmenu", "da", "locale/da/"],
1348+ ["locale", "messagingmenu", "de", "locale/de/"],
1349+ ["locale", "messagingmenu", "el", "locale/el/"],
1350+ ["locale", "messagingmenu", "en-GB", "locale/en-GB/"],
1351+ ["locale", "messagingmenu", "en-US", "locale/en-US/"],
1352+ ["locale", "messagingmenu", "eo", "locale/eo/"],
1353+ ["locale", "messagingmenu", "es", "locale/es/"],
1354+ ["locale", "messagingmenu", "et", "locale/et/"],
1355+ ["locale", "messagingmenu", "eu", "locale/eu/"],
1356+ ["locale", "messagingmenu", "fi", "locale/fi/"],
1357+ ["locale", "messagingmenu", "fr", "locale/fr/"],
1358+ ["locale", "messagingmenu", "gd", "locale/gd/"],
1359+ ["locale", "messagingmenu", "gl", "locale/gl/"],
1360+ ["locale", "messagingmenu", "he", "locale/he/"],
1361+ ["locale", "messagingmenu", "hi-IN", "locale/hi-IN/"],
1362+ ["locale", "messagingmenu", "hr", "locale/hr/"],
1363+ ["locale", "messagingmenu", "hu", "locale/hu/"],
1364+ ["locale", "messagingmenu", "hy-AM", "locale/hy-AM/"],
1365+ ["locale", "messagingmenu", "id", "locale/id/"],
1366+ ["locale", "messagingmenu", "is", "locale/is/"],
1367+ ["locale", "messagingmenu", "it", "locale/it/"],
1368+ ["locale", "messagingmenu", "ja", "locale/ja/"],
1369+ ["locale", "messagingmenu", "kk", "locale/kk/"],
1370+ ["locale", "messagingmenu", "kn", "locale/kn/"],
1371+ ["locale", "messagingmenu", "ko", "locale/ko/"],
1372+ ["locale", "messagingmenu", "ku", "locale/ku/"],
1373+ ["locale", "messagingmenu", "lt", "locale/lt/"],
1374+ ["locale", "messagingmenu", "lv", "locale/lv/"],
1375+ ["locale", "messagingmenu", "mk", "locale/mk/"],
1376+ ["locale", "messagingmenu", "ml", "locale/ml/"],
1377+ ["locale", "messagingmenu", "mr", "locale/mr/"],
1378+ ["locale", "messagingmenu", "nb-NO", "locale/nb-NO/"],
1379+ ["locale", "messagingmenu", "nl", "locale/nl/"],
1380+ ["locale", "messagingmenu", "nn-NO", "locale/nn-NO/"],
1381+ ["locale", "messagingmenu", "pl", "locale/pl/"],
1382+ ["locale", "messagingmenu", "pt", "locale/pt/"],
1383+ ["locale", "messagingmenu", "pt-BR", "locale/pt-BR/"],
1384+ ["locale", "messagingmenu", "ro", "locale/ro/"],
1385+ ["locale", "messagingmenu", "ru", "locale/ru/"],
1386+ ["locale", "messagingmenu", "si", "locale/si/"],
1387+ ["locale", "messagingmenu", "sk", "locale/sk/"],
1388+ ["locale", "messagingmenu", "sq", "locale/sq/"],
1389+ ["locale", "messagingmenu", "sr", "locale/sr/"],
1390+ ["locale", "messagingmenu", "sv", "locale/sv/"],
1391+ ["locale", "messagingmenu", "ta", "locale/ta/"],
1392+ ["locale", "messagingmenu", "te", "locale/te/"],
1393+ ["locale", "messagingmenu", "th", "locale/th/"],
1394+ ["locale", "messagingmenu", "tr", "locale/tr/"],
1395+ ["locale", "messagingmenu", "uk", "locale/uk/"],
1396+ ["locale", "messagingmenu", "zh-CN", "locale/zh-CN/"],
1397+ ["locale", "messagingmenu", "zh-TW", "locale/zh-TW/"]
1398+ ]);
1399+
1400+ messenger.WindowListener.registerWindow(
1401+ "chrome://messenger/content/messenger.xhtml",
1402+ "chrome://messagingmenu/content/thunderbirdMenu.js");
1403+
1404+ messenger.WindowListener.startListening();
1405+
1406+})();
1407
1408=== removed file 'build.sh'
1409=== removed file 'chrome.manifest'
1410=== removed file 'config_build.sh'
1411=== removed file 'content/prefOverlay.xul'
1412=== removed file 'content/prefs.js'
1413=== added directory 'content/skin'
1414=== added file 'content/skin/indicator-messages.svg'
1415--- content/skin/indicator-messages.svg 1970-01-01 00:00:00 +0000
1416+++ content/skin/indicator-messages.svg 2021-06-05 11:50:28 +0000
1417@@ -0,0 +1,15 @@
1418+<?xml version="1.0" encoding="UTF-8"?>
1419+<!-- Created with Inkscape (http://www.inkscape.org/) -->
1420+<svg id="svg3229" width="24" height="24" version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
1421+ <metadata id="metadata9">
1422+ <rdf:RDF>
1423+ <cc:Work rdf:about="">
1424+ <dc:format>image/svg+xml</dc:format>
1425+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
1426+ <dc:title/>
1427+ </cc:Work>
1428+ </rdf:RDF>
1429+ </metadata>
1430+ <path id="rect3170-2" d="m4.305 7.0221c-0.60941 0-1.305 0.7602-1.305 1.3142v9.2987c0 0.73098 0.7889 1.365 1.4535 1.365h15.049c0.79734 0 1.4978-0.67784 1.4978-1.3761v-9.1594c0-0.77554-0.68926-1.4646-1.4093-1.4646l-15.286 0.022122zm0.53871 1.1341 6.8438 6.8438h0.59375l6.875-6.8438 0.6875 0.6875-4.0938 4.1562 3.0938 3.1562-0.6875 0.6875-3.1562-3.1562-2.2812 2.3125h-1.4375l-2.2812-2.3125-3.1562 3.1875-0.6875-0.71875 3.125-3.1562-4.125-4.1562 0.6875-0.6875z" style="opacity:.3"/>
1431+ <path id="rect3170" d="m4.305 6.0221c-0.60941 0-1.305 0.7602-1.305 1.3142v9.2987c0 0.73098 0.7889 1.365 1.4535 1.365h15.049c0.79734 0 1.4978-0.67784 1.4978-1.3761v-9.1594c0-0.77554-0.68926-1.4646-1.4093-1.4646l-15.286 0.022122zm0.53871 1.1341 6.8438 6.8438h0.59375l6.875-6.8438 0.6875 0.6875-4.0938 4.1562 3.0938 3.1562-0.6875 0.6875-3.1562-3.1562-2.2812 2.3125h-1.4375l-2.2812-2.3125-3.1562 3.1875-0.6875-0.71875 3.125-3.1562-4.125-4.1562 0.6875-0.6875z" style="fill:#dfdbd2"/>
1432+</svg>
1433
1434=== modified file 'content/thunderbirdMenu.js'
1435--- content/thunderbirdMenu.js 2012-08-24 13:05:59 +0000
1436+++ content/thunderbirdMenu.js 2021-06-05 11:50:28 +0000
1437@@ -38,8 +38,18 @@
1438
1439 (function() {
1440 try {
1441- Components.utils.import("resource://messagingmenu/modules/MessagingMenuModule.jsm");
1442+ var { MessagingMenu } = ChromeUtils.import("resource://messagingmenu/modules/MessagingMenuModule.jsm");
1443 } catch(e) {
1444 Components.utils.reportError(e);
1445 }
1446 })();
1447+
1448+
1449+function onLoad(activatedWhileWindowOpen) {
1450+
1451+}
1452+
1453+function onUnload(deactivatedWhileWindowOpen) {
1454+
1455+}
1456+
1457
1458=== removed file 'content/thunderbirdOverlay.xul'
1459=== removed file 'install.rdf'
1460=== added file 'manifest.json'
1461--- manifest.json 1970-01-01 00:00:00 +0000
1462+++ manifest.json 2021-06-05 11:50:28 +0000
1463@@ -0,0 +1,32 @@
1464+{
1465+ "manifest_version": 2,
1466+ "applications": {
1467+ "gecko": {
1468+ "id": "messagingmenu@mozilla.com",
1469+ "strict_min_version": "78.0"
1470+ }
1471+ },
1472+ "name": "Messaging Menu and Unity Launcher integration",
1473+ "description": "This extension integrates Thunderbird into the Ubuntu Unity messaging menu and launcher",
1474+ "author": "Mike Conley, Chris Coulson, and Adrian Lam",
1475+ "version": "1.5",
1476+ "experiment_apis": {
1477+ "WindowListener": {
1478+ "schema": "api/WindowListener/schema.json",
1479+ "parent": {
1480+ "scopes": ["addon_parent"],
1481+ "paths": [["WindowListener"]],
1482+ "script": "api/WindowListener/implementation.js"
1483+ }
1484+ }
1485+ },
1486+ "background": {
1487+ "scripts": [
1488+ "background.js"
1489+ ]
1490+ },
1491+ "icons": {
1492+ "16": "content/skin/indicator-messages.svg"
1493+ }
1494+}
1495+
1496
1497=== modified file 'res/modules/LibIndicateBackend.jsm'
1498--- res/modules/LibIndicateBackend.jsm 2018-06-05 21:53:06 +0000
1499+++ res/modules/LibIndicateBackend.jsm 2021-06-05 11:50:28 +0000
1500@@ -41,15 +41,16 @@
1501
1502 var EXPORTED_SYMBOLS = [ "IndicateBackend" ];
1503
1504-Cu.import("resource://gre/modules/ctypes.jsm");
1505-Cu.import("resource://gre/modules/Services.jsm");
1506-Cu.import("resource://gre/modules/NetUtil.jsm");
1507-Cu.import("resource://gre/modules/FileUtils.jsm");
1508-Cu.import("resource://messagingmenu/modules/utils.jsm");
1509-Cu.import("resource://messagingmenu/libs/glib.jsm");
1510-Cu.import("resource://messagingmenu/libs/gobject.jsm");
1511-Cu.import("resource://messagingmenu/libs/dbusmenu.jsm");
1512-Cu.import("resource://messagingmenu/libs/indicate.jsm");
1513+var { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
1514+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
1515+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
1516+var { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
1517+var { CTypesUtils, addLogger, SimpleObjectWrapper } =
1518+ ChromeUtils.import("resource://messagingmenu/modules/utils.jsm");
1519+var { glib } = ChromeUtils.import("resource://messagingmenu/libs/glib.jsm");
1520+var { gobject } = ChromeUtils.import("resource://messagingmenu/libs/gobject.jsm");
1521+var { dbusmenu } = ChromeUtils.import("resource://messagingmenu/libs/dbusmenu.jsm");
1522+var { indicate } = ChromeUtils.import("resource://messagingmenu/libs/indicate.jsm");
1523
1524 addLogger(this, "backend.libindicate");
1525
1526
1527=== modified file 'res/modules/LibMessagingMenuBackend.jsm'
1528--- res/modules/LibMessagingMenuBackend.jsm 2012-10-15 00:08:19 +0000
1529+++ res/modules/LibMessagingMenuBackend.jsm 2021-06-05 11:50:28 +0000
1530@@ -41,11 +41,13 @@
1531
1532 var EXPORTED_SYMBOLS = [ "MessagingMenuBackend" ];
1533
1534-Cu.import("resource://gre/modules/ctypes.jsm");
1535-Cu.import("resource://messagingmenu/modules/utils.jsm");
1536-Cu.import("resource://messagingmenu/libs/glib.jsm");
1537-Cu.import("resource://messagingmenu/libs/gobject.jsm");
1538-Cu.import("resource://messagingmenu/libs/messagingmenu.jsm");
1539+var { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
1540+var { CTypesUtils, addLogger, SimpleObjectWrapper } =
1541+ ChromeUtils.import("resource://messagingmenu/modules/utils.jsm");
1542+var { glib } = ChromeUtils.import("resource://messagingmenu/libs/glib.jsm");
1543+var { gobject } = ChromeUtils.import("resource://messagingmenu/libs/gobject.jsm");
1544+var { messagingmenu } =
1545+ ChromeUtils.import("resource://messagingmenu/libs/messagingmenu.jsm");
1546
1547 addLogger(this, "backend.libmessagingmenu");
1548
1549
1550=== modified file 'res/modules/MessagingMenuModule.jsm'
1551--- res/modules/MessagingMenuModule.jsm 2018-06-05 21:53:06 +0000
1552+++ res/modules/MessagingMenuModule.jsm 2021-06-05 11:50:28 +0000
1553@@ -41,23 +41,26 @@
1554
1555 const Cc = Components.classes;
1556 const Ci = Components.interfaces;
1557-const Cu = Components.utils;
1558
1559-Cu.import("resource://gre/modules/ctypes.jsm");
1560-Cu.import("resource://gre/modules/Services.jsm");
1561-Cu.import("resource://gre/modules/FileUtils.jsm");
1562-Cu.import("resource://gre/modules/AddonManager.jsm");
1563-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
1564-Cu.import("resource:///modules/mailServices.js");
1565-Cu.import("resource:///modules/iteratorUtils.jsm");
1566-Cu.import("resource:///modules/gloda/mimemsg.js");
1567-Cu.import("resource://messagingmenu/modules/utils.jsm");
1568-Cu.import("resource://messagingmenu/libs/glib.jsm");
1569-Cu.import("resource://messagingmenu/libs/gobject.jsm");
1570-Cu.import("resource://messagingmenu/libs/gdk.jsm");
1571-Cu.import("resource://messagingmenu/libs/unity.jsm");
1572-Cu.import("resource://messagingmenu/modules/LibMessagingMenuBackend.jsm");
1573-Cu.import("resource://messagingmenu/modules/LibIndicateBackend.jsm");
1574+var { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
1575+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
1576+var { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
1577+var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
1578+var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
1579+var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
1580+var { MsgHdrToMimeMessage, MimeMessage, MimeContainer,
1581+ MimeBody, MimeUnknown, MimeMessageAttachment } =
1582+ ChromeUtils.import("resource:///modules/gloda/MimeMessage.jsm");
1583+var { CTypesUtils, addLogger, SimpleObjectWrapper } =
1584+ ChromeUtils.import("resource://messagingmenu/modules/utils.jsm");
1585+var { glib } = ChromeUtils.import("resource://messagingmenu/libs/glib.jsm");
1586+var { gobject } = ChromeUtils.import("resource://messagingmenu/libs/gobject.jsm");
1587+var { gdk } = ChromeUtils.import("resource://messagingmenu/libs/gdk.jsm");
1588+var { unity } = ChromeUtils.import("resource://messagingmenu/libs/unity.jsm");
1589+var { MessagingMenuBackend } =
1590+ ChromeUtils.import("resource://messagingmenu/modules/LibMessagingMenuBackend.jsm");
1591+var { IndicateBackend } =
1592+ ChromeUtils.import("resource://messagingmenu/modules/LibIndicateBackend.jsm");
1593
1594 addLogger(this);
1595
1596@@ -216,11 +219,10 @@
1597
1598 function hasMultipleAccounts() {
1599 let count = 0;
1600- // We don't want to just call Count() on the account nsISupportsArray, as we
1601+ // We don't want to just call Count() on the account array, as we
1602 // want to filter out accounts with "none" as the incoming server type
1603 // (eg, for Local Folders)
1604- for (let account of fixIterator(MailServices.accounts.accounts,
1605- Ci.nsIMsgAccount)) {
1606+ for (let account of MailServices.accounts.accounts) {
1607 if (account.incomingServer.type != "none") {
1608 count++
1609 }
1610@@ -411,36 +413,13 @@
1611 return aMimeMsg.has("list-id");
1612 }, "Mailing list message");
1613
1614- state.dontRequestAttentionIfTrue(function() {
1615- if (!aMimeMsg.has("return-path"))
1616- return false;
1617-
1618- let re = /.*<([^>]*)>/;
1619- let rp = aMimeMsg.get("return-path").replace(re, "$1");
1620- let from = aItemHeader.author.replace(re, "$1");
1621-
1622- return rp != from;
1623- }, "Possible automated message (Return-Path != From)");
1624-
1625- state.dontRequestAttentionIfTrue(function() {
1626- if (!aMimeMsg.has("sender"))
1627- return false;
1628-
1629- let re = /.*<([^>]*)>/;
1630- let sender = aMimeMsg.get("sender").replace(re, "$1");
1631- let from = aItemHeader.author.replace(re, "$1");
1632-
1633- return sender.toLowerCase() != from.toLowerCase();
1634- }, "Possible automated message (Sender != From)");
1635-
1636 state.requestAttentionIfTrue(function() {
1637 let recipients = aItemHeader.recipients.split(",");
1638 let re = /.*<([^>]*)>/; // Convert "Foo <bar>" in to "bar"
1639 for (let i in recipients) {
1640 let recipient = recipients[i].replace(re, "$1").toLowerCase();
1641
1642- for (let id of fixIterator(MailServices.accounts.allIdentities,
1643- Ci.nsIMsgIdentity)) {
1644+ for (let id of MailServices.accounts.allIdentities) {
1645 if (recipient.indexOf(id.email.toLowerCase()) != -1)
1646 return true;
1647 }
1648@@ -568,10 +547,10 @@
1649 this.isInbox()) {
1650 this.label = this._folder.server.prettyName;
1651 } else if (hasMultipleAccounts()) {
1652- this.label = this._folder.prettiestName +
1653+ this.label = this._folder.prettyName +
1654 " (" + this._folder.server.prettyName + ")";
1655 } else {
1656- this.label = this._folder.prettiestName;
1657+ this.label = this._folder.prettyName;
1658 }
1659 },
1660
1661@@ -788,7 +767,8 @@
1662 let inboxOnly = gPrefs.getBoolPref(kPrefInboxOnly, false);
1663 let accumulator = 0;
1664 for (let url in this.mIndicators) {
1665- if (!this.mIndicators[url].isInbox() && inboxOnly) {
1666+ let indicator = this.mIndicators[url];
1667+ if (!indicator.isInbox() && inboxOnly) {
1668 continue;
1669 }
1670
1671
1672=== modified file 'res/modules/utils.jsm'
1673--- res/modules/utils.jsm 2018-06-05 21:53:06 +0000
1674+++ res/modules/utils.jsm 2021-06-05 11:50:28 +0000
1675@@ -2,12 +2,12 @@
1676
1677 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
1678
1679-Cu.import("resource://gre/modules/Services.jsm");
1680-Cu.import("resource://gre/modules/AddonManager.jsm");
1681-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
1682+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
1683+var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
1684+var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
1685
1686 XPCOMUtils.defineLazyGetter(this, "ctypes", function() {
1687- Cu.import("resource://gre/modules/ctypes.jsm");
1688+ var { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
1689 return ctypes;
1690 });
1691

Subscribers

People subscribed via source and target branches

to all changes: