From 334ed5c80e75027c800dba1182da9ac189b492d7 Mon Sep 17 00:00:00 2001 From: John Bieling Date: Wed, 19 Jul 2023 11:18:08 +0200 Subject: [PATCH 01/20] Make sure to await all async calls --- background.js | 149 ++++++++++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 66 deletions(-) diff --git a/background.js b/background.js index ea526fa4..402d815c 100644 --- a/background.js +++ b/background.js @@ -391,7 +391,7 @@ const SendLater = { if (success) { SLStatic.debug(`Rescheduled message ${originalMsgId}. Deleting original.`); - messenger.messages.delete([msgHdr.id], true).then(() => { + await messenger.messages.delete([msgHdr.id], true).then(() => { SLStatic.info("Deleted message", msgHdr.id); SLTools.scheduledMsgCache.delete(msgHdr.id); SLTools.unscheduledMsgCache.delete(msgHdr.id); @@ -420,7 +420,7 @@ const SendLater = { if (success) { lock[msgLockId] = true; - messenger.storage.local.set({ lock }).then(() => { + await messenger.storage.local.set({ lock }).then(() => { SLStatic.debug(`Locked message <${msgLockId}> from re-sending.`); }); if (preferences.throttleDelay) { @@ -521,7 +521,7 @@ const SendLater = { if (success) { SLStatic.info(`Scheduled next occurrence of message ` + `<${originalMsgId}>. Deleting original.`); - messenger.messages.delete([msgHdr.id], true).then(() => { + await messenger.messages.delete([msgHdr.id], true).then(() => { SLStatic.info("Deleted message", msgHdr.id); SLTools.scheduledMsgCache.delete(msgHdr.id); SLTools.unscheduledMsgCache.delete(msgHdr.id); @@ -534,7 +534,7 @@ const SendLater = { SLStatic.info( `No recurrences for message <${originalMsgId}>. Deleting original.` ); - messenger.messages.delete([msgHdr.id], true).then(() => { + await messenger.messages.delete([msgHdr.id], true).then(() => { SLStatic.info("Deleted message", msgHdr.id); SLTools.scheduledMsgCache.delete(msgHdr.id); SLTools.unscheduledMsgCache.delete(msgHdr.id); @@ -913,14 +913,12 @@ const SendLater = { // Re-save message (drops x-send-later headers by default // because they are not loaded when editing as draft). - messenger.compose.saveMessage(tab.id, {mode: "draft"}).then( - () => { - SLTools.scheduledMsgCache.delete(originalMsg.id); - SLTools.unscheduledMsgCache.add(originalMsg.id); - SendLater.updateStatusIndicator(); - } - ); - + // TODO: Not using the return value of saveMessage() ??? + await messenger.compose.saveMessage(tab.id, {mode: "draft"}); + SLTools.scheduledMsgCache.delete(originalMsg.id); + SLTools.unscheduledMsgCache.add(originalMsg.id); + SendLater.updateStatusIndicator(); + // Set popup scheduler defaults based on original message scheduleCache[window.id] = SLStatic.parseHeadersForPopupUICache(originalMsg.headers); @@ -932,14 +930,11 @@ const SendLater = { // Alert the user about what just happened if (preferences.showEditAlert) { - let draftSaveWarning = messenger.i18n.getMessage("draftSaveWarning") - SLTools.alertCheck( - null, draftSaveWarning, null, true - ).then(async (result) => { - const preferences = await SLTools.getPrefs(); - preferences.showEditAlert = result.check; - messenger.storage.local.set({ preferences }); - }); + let draftSaveWarning = messenger.i18n.getMessage("draftSaveWarning"); + let result = await SLTools.alertCheck(null, draftSaveWarning, null, true); + let preferences = await SLTools.getPrefs(); + preferences.showEditAlert = result.check; + await messenger.storage.local.set({ preferences }); } } } @@ -967,6 +962,10 @@ const SendLater = { if (SendLater.prefCache.altBinding) { SLStatic.info("Passing Ctrl+Shift+Enter along to builtin send later " + "because user bound alt+shift+enter instead."); + // TODO: Undesired use of a not-awaited .then() callback in an event + // handler. It is true that there is no code executed after this so + // technically it does not need to be awaited, but consistently using + // async/await helps to improve maintainability. SLTools.getActiveComposeTab().then(curTab => { if (curTab) messenger.compose.sendMessage(curTab.id, {mode: "sendLater"}); @@ -991,14 +990,22 @@ const SendLater = { SLStatic.debug("Opening scheduler dialog."); messenger.composeAction.openPopup(); } else if (SendLater.prefCache.sendDoesDelay) { - //Schedule with delay + // Schedule with delay. const sendDelay = SendLater.prefCache.sendDelay; SLStatic.info(`Scheduling Send Later ${sendDelay} minutes from now.`); + // TODO: Undesired use of a not-awaited .then() callback in an event + // handler. It is true that there is no code executed after this so + // technically it does not need to be awaited, but consistently using + // async/await helps to improve maintainability. SLTools.getActiveComposeTab().then(curTab => { if (curTab) SendLater.scheduleSendLater(curTab.id, {delay: sendDelay}); }); } else { + // TODO: Undesired use of a not-awaited .then() callback in an event + // handler. It is true that there is no code executed after this so + // technically it does not need to be awaited, but consistently using + // async/await helps to improve maintainability. SLTools.getActiveComposeTab().then(curTab => { if (curTab) messenger.compose.sendMessage(curTab.id, {mode: "sendNow"}); @@ -1016,12 +1023,16 @@ const SendLater = { onMessageExternalListener(message, sender, sendResponse) { switch (message.action) { case "getUUID": { + // It might improve readability to return the Promise instead of tweaking + // sendResponse() - which is intended for sync responses - to be async. SLTools.getPrefs().then((preferences) => { sendResponse(preferences.instanceUUID); }).catch(ex => SLStatic.error(ex)); return true; } case "getPreferences": { + // It might improve readability to return the Promise instead of tweaking + // sendResponse() - which is intended for sync responses - to be async. SLTools.getPrefs().then((preferences) => { for (const prop in preferences) { if (!SLStatic.prefInputIds.includes(prop)) { @@ -1034,6 +1045,8 @@ const SendLater = { } case "setPreferences": { new_prefs = message.preferences; + // It might improve readability to return the Promise instead of tweaking + // sendResponse() - which is intended for sync responses - to be async. SLTools.getPrefs().then((preferences) => { old_prefs = preferences; for (const prop in new_prefs) { @@ -1056,6 +1069,8 @@ const SendLater = { return true; } case "parseDate": { + // It might improve readability to return the Promise instead of tweaking + // sendResponse() - which is intended for sync responses - to be async. try { const date = SLStatic.convertDate(message["value"]); if (date) { @@ -1224,7 +1239,7 @@ const SendLater = { // Listen for incoming messages, and check if they are in reponse to a scheduled // message with a 'cancel-on-reply' header. - onNewMailReceivedListener(folder, messagelist) { + async onNewMailReceivedListener(folder, messagelist) { if (["sent", "trash", "templates", "archives", "junk", "outbox"].includes(folder.type)) { SLStatic.debug(`Skipping onNewMailReceived for folder type ${folder.type}`); return; @@ -1232,36 +1247,37 @@ const SendLater = { SLStatic.debug("Received messags in folder", folder, ":", messagelist); for (let rcvdHdr of messagelist.messages) { - messenger.messages.getFull(rcvdHdr.id).then((rcvdMsg) => { - SLStatic.debug("Got message", rcvdHdr, rcvdMsg); - let inReplyTo = (rcvdMsg.headers["in-reply-to"]||[])[0]; - if (inReplyTo) { - SLTools.forAllDrafts(async (draftHdr) => { - if (!SLTools.unscheduledMsgCache.has(draftHdr.id)) { - SLStatic.debug( - "Comparing", rcvdHdr, "to", draftHdr, - inReplyTo, "?=", `<${draftHdr.headerMessageId}>` + let rcvdMsg = await messenger.messages.getFull(rcvdHdr.id); + SLStatic.debug("Got message", rcvdHdr, rcvdMsg); + let inReplyTo = (rcvdMsg.headers["in-reply-to"]||[])[0]; + if (inReplyTo) { + // This does not stack handling of messages, but jumps back and forth + // and switches between them and handles them "in parallel". + SLTools.forAllDrafts(async (draftHdr) => { + if (!SLTools.unscheduledMsgCache.has(draftHdr.id)) { + SLStatic.debug( + "Comparing", rcvdHdr, "to", draftHdr, + inReplyTo, "?=", `<${draftHdr.headerMessageId}>` + ); + if (inReplyTo == `<${draftHdr.headerMessageId}>`) { + let cancelOnReply = await messenger.messages.getFull(draftHdr.id).then( + draftMsg => (draftMsg.headers["x-send-later-cancel-on-reply"]||[])[0] ); - if (inReplyTo == `<${draftHdr.headerMessageId}>`) { - let cancelOnReply = await messenger.messages.getFull(draftHdr.id).then( - draftMsg => (draftMsg.headers["x-send-later-cancel-on-reply"]||[])[0] + if (["true", "yes"].includes(cancelOnReply)) { + SLStatic.info( + `Received response to message ${inReplyTo}.`, + `Deleting scheduled draft ${draftHdr.id}` ); - if (["true", "yes"].includes(cancelOnReply)) { - SLStatic.info( - `Received response to message ${inReplyTo}.`, - `Deleting scheduled draft ${draftHdr.id}` - ); - messenger.messages.delete([draftHdr.id]).then(() => { - SLStatic.info("Deleted message", draftHdr.id); - SLTools.scheduledMsgCache.delete(draftHdr.id); - SLTools.unscheduledMsgCache.delete(draftHdr.id); - }).catch(SLStatic.error); - } + await messenger.messages.delete([draftHdr.id]).then(() => { + SLStatic.info("Deleted message", draftHdr.id); + SLTools.scheduledMsgCache.delete(draftHdr.id); + SLTools.unscheduledMsgCache.delete(draftHdr.id); + }).catch(SLStatic.error); } } - }); - } - }); + } + }); + } } }, @@ -1346,7 +1362,7 @@ const SendLater = { }; // End SendLater object -function mainLoop() { +async function mainLoop() { SLStatic.debug("Entering main loop."); try { if (SendLater.loopTimeout) { @@ -1354,10 +1370,12 @@ function mainLoop() { } } catch (ex) { SLStatic.error(ex); } - SLTools.getPrefs().then((preferences) => { + try { + let preferences = await SLTools.getPrefs() let interval = +preferences.checkTimePref || 0; - if (preferences.checkTimePref_isMilliseconds) - interval /= 60000; + if (preferences.checkTimePref_isMilliseconds) { + interval /= 60000; + } SendLater.loopMinutes = interval; @@ -1370,26 +1388,25 @@ function mainLoop() { messenger.browserAction.setTitle({title: `${extName} [${isActiveMessage}]`}); let doSequential = preferences.throttleDelay > 0; - SLTools.forAllDrafts(SendLater.possiblySendMessage, doSequential).then(() => { - SLTools.countActiveScheduledMessages().then(nActive => { - SendLater.updateStatusIndicator(nActive); - SendLater.setQuitNotificationsEnabled(preferences.askQuit, nActive); - }); + try { + await SLTools.forAllDrafts(SendLater.possiblySendMessage, doSequential) + let nActive = await SLTools.countActiveScheduledMessages(); + SendLater.updateStatusIndicator(nActive); + SendLater.setQuitNotificationsEnabled(preferences.askQuit, nActive); SendLater.previousLoop = new Date(); SendLater.loopTimeout = setTimeout(mainLoop, 60000*interval); SLStatic.debug(`Next main loop iteration in ${60*interval} seconds.`); - }).catch((err) => { + } catch(err) { SLStatic.error(err); - SLTools.countActiveScheduledMessages().then(nActive => { - SendLater.updateStatusIndicator(nActive); - SendLater.setQuitNotificationsEnabled(preferences.askQuit, nActive); - }); - + let nActive = await SLTools.countActiveScheduledMessages(); + SendLater.updateStatusIndicator(nActive); + SendLater.setQuitNotificationsEnabled(preferences.askQuit, nActive); + SendLater.previousLoop = new Date(); SendLater.loopTimeout = setTimeout(mainLoop, 60000); SLStatic.debug(`Next main loop iteration in 1 minute.`); - }); + }; } else { SendLater.setQuitNotificationsEnabled(false); let extName = messenger.i18n.getMessage("extensionName"); @@ -1402,13 +1419,13 @@ function mainLoop() { SendLater.loopTimeout = setTimeout(mainLoop, 60000); SLStatic.debug(`Next main loop iteration in 1 minute.`); } - }).catch(ex => { + } catch(ex) { SLStatic.error(ex); SendLater.previousLoop = new Date(); SendLater.loopTimeout = setTimeout(mainLoop, 60000); SLStatic.debug(`Next main loop iteration in 1 minute.`); - }); + }; } SendLater.init().then(mainLoop).catch( From 8bbfb3f26440a1b8cf9ffc03b1cf283d877c8162 Mon Sep 17 00:00:00 2001 From: John Bieling Date: Sun, 23 Jul 2023 18:08:20 +0200 Subject: [PATCH 02/20] return Promise/false in message listener --- background.js | 65 ++++++++++++++++++++------------------------------- 1 file changed, 25 insertions(+), 40 deletions(-) diff --git a/background.js b/background.js index 402d815c..df2fb0c9 100644 --- a/background.js +++ b/background.js @@ -1023,72 +1023,57 @@ const SendLater = { onMessageExternalListener(message, sender, sendResponse) { switch (message.action) { case "getUUID": { - // It might improve readability to return the Promise instead of tweaking - // sendResponse() - which is intended for sync responses - to be async. - SLTools.getPrefs().then((preferences) => { - sendResponse(preferences.instanceUUID); - }).catch(ex => SLStatic.error(ex)); - return true; + return SLTools.getPrefs() + .then(preferences => preferences.instanceUUID) + .catch(ex => SLStatic.error(ex)); } case "getPreferences": { - // It might improve readability to return the Promise instead of tweaking - // sendResponse() - which is intended for sync responses - to be async. - SLTools.getPrefs().then((preferences) => { - for (const prop in preferences) { - if (!SLStatic.prefInputIds.includes(prop)) { - delete preferences[prop]; - } - } - sendResponse(preferences); - }); - return true; + return SLTools.getPrefs() + .then(preferences => preferences.filter(prop => SLStatic.prefInputIds.includes(prop))) + .catch(ex => SLStatic.error(ex)); } case "setPreferences": { - new_prefs = message.preferences; - // It might improve readability to return the Promise instead of tweaking - // sendResponse() - which is intended for sync responses - to be async. - SLTools.getPrefs().then((preferences) => { - old_prefs = preferences; + const setAndReturnPrefs = async (old_prefs, new_prefs) => { for (const prop in new_prefs) { if (!SLStatic.prefInputIds.includes(prop)) { - throw `Property ${prop} is not a valid Send Later preference.`; + throw new Error (`Property ${prop} is not a valid Send Later preference.`); } - if (prop in old_prefs && typeof(old_prefs[prop]) != "undefined" && - typeof(new_prefs[prop]) != "undefined" && - typeof(old_prefs[prop]) != typeof(new_prefs[prop])) { - throw `Type of ${prop} is invalid: new ` + + if ( + prop in old_prefs && + typeof(old_prefs[prop]) != "undefined" && + typeof(new_prefs[prop]) != "undefined" && + typeof(old_prefs[prop]) != typeof(new_prefs[prop]) + ) { + throw new Error(`Type of ${prop} is invalid: new ` + `${typeof(new_prefs[prop])} vs. current ` + - `${typeof(old_prefs[prop])}.`; + `${typeof(old_prefs[prop])}.`); } old_prefs[prop] = new_prefs[prop]; } - messenger.storage.local.set({preferences}).then((result) => { - sendResponse(old_prefs) - }); - }); - return true; + await messenger.storage.local.set({preferences: old_prefs}); + return old_prefs; + } + return SLTools.getPrefs() + .then(old_prefs => setAndReturnPrefs(old_prefs, message.preferences)) + .catch(ex => SLStatic.error(ex)); } case "parseDate": { - // It might improve readability to return the Promise instead of tweaking - // sendResponse() - which is intended for sync responses - to be async. try { const date = SLStatic.convertDate(message["value"]); if (date) { const dateStr = SLStatic.parseableDateTimeFormat(date.getTime()); - sendResponse(dateStr); - return; + return Promise.resolve(dateStr); } } catch (ex) { SLStatic.debug("Unable to parse date/time",ex); } - sendResponse(null); - return; + break; } default: { SLStatic.warn(`Unrecognized operation <${message.action}>.`); } } - sendResponse(null); + return false; }, // Various extension components communicate with From 912f92a195ef20d7c4412fe036d6d6e8b9d8273a Mon Sep 17 00:00:00 2001 From: John Bieling Date: Sun, 23 Jul 2023 18:34:05 +0200 Subject: [PATCH 03/20] await SLTools.forAllDrafts() in onNewMailReceivedListener --- background.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/background.js b/background.js index df2fb0c9..112e75b2 100644 --- a/background.js +++ b/background.js @@ -398,7 +398,7 @@ const SendLater = { }).catch(SLStatic.error);; return; } else { - SLStatic.error("Unable to schedule next recuurrence."); + SLStatic.error("Unable to schedule next recurrence."); return; } } @@ -1222,7 +1222,7 @@ const SendLater = { return response; }, - // Listen for incoming messages, and check if they are in reponse to a scheduled + // Listen for incoming messages, and check if they are in response to a scheduled // message with a 'cancel-on-reply' header. async onNewMailReceivedListener(folder, messagelist) { if (["sent", "trash", "templates", "archives", "junk", "outbox"].includes(folder.type)) { @@ -1236,9 +1236,7 @@ const SendLater = { SLStatic.debug("Got message", rcvdHdr, rcvdMsg); let inReplyTo = (rcvdMsg.headers["in-reply-to"]||[])[0]; if (inReplyTo) { - // This does not stack handling of messages, but jumps back and forth - // and switches between them and handles them "in parallel". - SLTools.forAllDrafts(async (draftHdr) => { + await SLTools.forAllDrafts(async (draftHdr) => { if (!SLTools.unscheduledMsgCache.has(draftHdr.id)) { SLStatic.debug( "Comparing", rcvdHdr, "to", draftHdr, @@ -1373,6 +1371,8 @@ async function mainLoop() { messenger.browserAction.setTitle({title: `${extName} [${isActiveMessage}]`}); let doSequential = preferences.throttleDelay > 0; + console.debug({doSequential, throttleDelay: preferences.throttleDelay}); + try { await SLTools.forAllDrafts(SendLater.possiblySendMessage, doSequential) let nActive = await SLTools.countActiveScheduledMessages(); From 64ed5aa3eeb94df50fe6d4dc19f7f03daa86e3dd Mon Sep 17 00:00:00 2001 From: John Bieling Date: Sun, 23 Jul 2023 18:49:50 +0200 Subject: [PATCH 04/20] Add comments --- background.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/background.js b/background.js index 112e75b2..95362a09 100644 --- a/background.js +++ b/background.js @@ -1023,11 +1023,13 @@ const SendLater = { onMessageExternalListener(message, sender, sendResponse) { switch (message.action) { case "getUUID": { + // Return Promise for the instanceUUID. return SLTools.getPrefs() .then(preferences => preferences.instanceUUID) .catch(ex => SLStatic.error(ex)); } case "getPreferences": { + // Return Promise for the allowed preferences. return SLTools.getPrefs() .then(preferences => preferences.filter(prop => SLStatic.prefInputIds.includes(prop))) .catch(ex => SLStatic.error(ex)); @@ -1053,11 +1055,15 @@ const SendLater = { await messenger.storage.local.set({preferences: old_prefs}); return old_prefs; } + // Return Promise for updating the allowed preferences. return SLTools.getPrefs() .then(old_prefs => setAndReturnPrefs(old_prefs, message.preferences)) .catch(ex => SLStatic.error(ex)); } case "parseDate": { + // Return Promise for the Date. Since this is a sync operation, the + // Promise is already fulfilled. But it still has to be a Promise, or + // sendResponse() has to be used instead. Promise syntax is preferred. try { const date = SLStatic.convertDate(message["value"]); if (date) { From 029ad53b90e1b52013e47322a2a341d623cdc0c2 Mon Sep 17 00:00:00 2001 From: John Bieling Date: Sun, 23 Jul 2023 21:02:33 +0200 Subject: [PATCH 05/20] Use correct id in *scheduledMsgCache maps --- background.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/background.js b/background.js index 95362a09..45e1c693 100644 --- a/background.js +++ b/background.js @@ -898,30 +898,32 @@ const SendLater = { return; } - let originalMsg = await messenger.SL3U.findAssociatedDraft(window.id).then( - m => m ? messenger.messages.getFull(m.id) : null - ); + let originalMsg = await messenger.SL3U.findAssociatedDraft(window.id); if (originalMsg) { + let originalMsgPart = await messenger.messages.getFull(originalMsg.id); SLTools.scheduledMsgCache.delete(originalMsg.id); SLTools.unscheduledMsgCache.add(originalMsg.id); // Check if original message has x-send-later headers - if (originalMsg.headers.hasOwnProperty("x-send-later-at")) { + if (originalMsgPart.headers.hasOwnProperty("x-send-later-at")) { let { preferences, scheduleCache } = await messenger.storage.local.get( { preferences: {}, scheduleCache: {} } ); // Re-save message (drops x-send-later headers by default // because they are not loaded when editing as draft). - // TODO: Not using the return value of saveMessage() ??? - await messenger.compose.saveMessage(tab.id, {mode: "draft"}); - SLTools.scheduledMsgCache.delete(originalMsg.id); - SLTools.unscheduledMsgCache.add(originalMsg.id); + let { messages } = await messenger.compose.saveMessage(tab.id, {mode: "draft"}); + // Pick the message stored in the same folder as the original draft was stored in. + // let newMsg = messages.find(m => m.folder == originalMsg.folder); + // The saved message has a different message id then the original one. + // The new message is not used. + // console.debug({originalMsg, newMsg}); + SendLater.updateStatusIndicator(); // Set popup scheduler defaults based on original message scheduleCache[window.id] = - SLStatic.parseHeadersForPopupUICache(originalMsg.headers); + SLStatic.parseHeadersForPopupUICache(originalMsgPart.headers); SLStatic.debug( `Schedule cache item added for window ${window.id}:`, scheduleCache[window.id] From 5c0753873abab33fc6b5c42707d82ba115421012 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Wed, 26 Jul 2023 16:47:03 -0400 Subject: [PATCH 06/20] Process all drafts, not just the first 100 Fix a logic error in the loop intended to interate over all drafts. The logic error was causing the loop to only examine the first page of results from `messenger.messages.list` and therefore only the first 100 drafts in each drafts folder. Fixes #529. --- utils/tools.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/utils/tools.js b/utils/tools.js index 09ca7bed..2a959f7c 100644 --- a/utils/tools.js +++ b/utils/tools.js @@ -135,7 +135,7 @@ var SLTools = { let draftFolders = SLTools.getDraftFolders(acct); for (let folder of draftFolders) { let page = await messenger.messages.list(folder); - do { + while (true) { if (sequential) { for (let message of page.messages) { results.push(await callback(message).catch(SLStatic.error)); @@ -146,10 +146,11 @@ var SLTools = { ); results = results.concat(pageResults); } - if (page.id) { - page = await messenger.messages.continueList(page.id); + if (!page.id) { + break; } - } while (page.id); + page = await messenger.messages.continueList(page.id); + } } } if (sequential) { From 0e36c14eea4178593a2edf2cc2ae1f03b079f86d Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 14:58:20 -0400 Subject: [PATCH 07/20] await all calls to messenger.storage.local.set --- background.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/background.js b/background.js index 45e1c693..2f0eac0c 100644 --- a/background.js +++ b/background.js @@ -32,7 +32,7 @@ const SendLater = { }); if (result.check === false) { preferences.showSendNowAlert = false; - messenger.storage.local.set({ preferences }); + await messenger.storage.local.set({ preferences }); } if (!result.ok) { SLStatic.debug(`User canceled send now.`); @@ -56,7 +56,7 @@ const SendLater = { }); if (result.check === false) { preferences.showOutboxAlert = false; - messenger.storage.local.set({ preferences }); + await messenger.storage.local.set({ preferences }); } if (!result.ok) { SLStatic.debug(`User canceled send later.`); @@ -420,9 +420,8 @@ const SendLater = { if (success) { lock[msgLockId] = true; - await messenger.storage.local.set({ lock }).then(() => { - SLStatic.debug(`Locked message <${msgLockId}> from re-sending.`); - }); + await messenger.storage.local.set({ lock }); + SLStatic.debug(`Locked message <${msgLockId}> from re-sending.`); if (preferences.throttleDelay) { SLStatic.debug(`Throttling send rate: ${preferences.throttleDelay/1000}s`); await new Promise(resolve => @@ -928,7 +927,7 @@ const SendLater = { `Schedule cache item added for window ${window.id}:`, scheduleCache[window.id] ); - messenger.storage.local.set({ scheduleCache }); + await messenger.storage.local.set({ scheduleCache }); // Alert the user about what just happened if (preferences.showEditAlert) { From caf5ee3a036c4ae34b19e2b47f2c22c1b061610b Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 15:00:38 -0400 Subject: [PATCH 08/20] await calls to messenger.compose.sendMessage and scheduleSendLater --- background.js | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/background.js b/background.js index 2f0eac0c..087f7369 100644 --- a/background.js +++ b/background.js @@ -39,7 +39,7 @@ const SendLater = { return; } } - messenger.compose.sendMessage(tabId, {mode: "sendNow"}); + await messenger.compose.sendMessage(tabId, {mode: "sendNow"}); }, // Use built-in send later function (after some pre-send checks) @@ -945,7 +945,7 @@ const SendLater = { // composition windows. These events occur when the user activates // the built-in send or send later using either key combinations // (e.g. ctrl+shift+enter), or click the file menu buttons. - onUserCommandKeyListener(keyid) { + async onUserCommandKeyListener(keyid) { SLStatic.info(`Received keycode ${keyid}`); switch (keyid) { case "key_altShiftEnter": { @@ -963,14 +963,11 @@ const SendLater = { if (SendLater.prefCache.altBinding) { SLStatic.info("Passing Ctrl+Shift+Enter along to builtin send later " + "because user bound alt+shift+enter instead."); - // TODO: Undesired use of a not-awaited .then() callback in an event - // handler. It is true that there is no code executed after this so - // technically it does not need to be awaited, but consistently using - // async/await helps to improve maintainability. - SLTools.getActiveComposeTab().then(curTab => { - if (curTab) - messenger.compose.sendMessage(curTab.id, {mode: "sendLater"}); - }); + let curTab = await SLTools.getActiveComposeTab(); + if (curTab) { + await messenger.compose.sendMessage( + curTab.id, {mode: "sendLater"}); + } } else { SLStatic.info("Opening popup"); messenger.composeAction.openPopup(); @@ -994,23 +991,17 @@ const SendLater = { // Schedule with delay. const sendDelay = SendLater.prefCache.sendDelay; SLStatic.info(`Scheduling Send Later ${sendDelay} minutes from now.`); - // TODO: Undesired use of a not-awaited .then() callback in an event - // handler. It is true that there is no code executed after this so - // technically it does not need to be awaited, but consistently using - // async/await helps to improve maintainability. - SLTools.getActiveComposeTab().then(curTab => { - if (curTab) - SendLater.scheduleSendLater(curTab.id, {delay: sendDelay}); - }); + let curTab = await SLTools.getActiveComposeTab(); + if (curTab) { + await SendLater.scheduleSendLater( + curTab.id, {delay: sendDelay}); + } } else { - // TODO: Undesired use of a not-awaited .then() callback in an event - // handler. It is true that there is no code executed after this so - // technically it does not need to be awaited, but consistently using - // async/await helps to improve maintainability. - SLTools.getActiveComposeTab().then(curTab => { - if (curTab) - messenger.compose.sendMessage(curTab.id, {mode: "sendNow"}); - }); + let curTab = await SLTools.getActiveComposeTab(); + if (curTab) { + await messenger.compose.sendMessage( + curTab.id, {mode: "sendNow"}); + } } break; } From 6c63814faaeb2a2f373f40cf2459e47b2c67fafa Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 15:17:10 -0400 Subject: [PATCH 09/20] await all calls to messenger.composeAction.{set,open}Popup --- background.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/background.js b/background.js index 087f7369..f8384817 100644 --- a/background.js +++ b/background.js @@ -951,7 +951,7 @@ const SendLater = { case "key_altShiftEnter": { if (SendLater.prefCache.altBinding) { SLStatic.info("Opening popup"); - messenger.composeAction.openPopup(); + await messenger.composeAction.openPopup(); } else { SLStatic.info("Ignoring Alt+Shift+Enter on account of user preferences"); } @@ -970,14 +970,14 @@ const SendLater = { } } else { SLStatic.info("Opening popup"); - messenger.composeAction.openPopup(); + await messenger.composeAction.openPopup(); } break; } case "cmd_sendLater": { // User clicked the "Send Later" menu item, which should always // open the Send Later popup. - messenger.composeAction.openPopup(); + await messenger.composeAction.openPopup(); break; } case "cmd_sendNow": @@ -986,7 +986,7 @@ const SendLater = { { if (SendLater.prefCache.sendDoesSL) { SLStatic.debug("Opening scheduler dialog."); - messenger.composeAction.openPopup(); + await messenger.composeAction.openPopup(); } else if (SendLater.prefCache.sendDoesDelay) { // Schedule with delay. const sendDelay = SendLater.prefCache.sendDelay; @@ -1304,9 +1304,9 @@ const SendLater = { SLStatic.info(`Executing accelerator Click+${mod}: ${funcName}(${funcArgs})`); SendLater.quickSendWithUfunc(funcName, funcArgs, tab.id); } else { - messenger.composeAction.setPopup({"popup": "ui/popup.html"}); - messenger.composeAction.openPopup(); - messenger.composeAction.setPopup({"popup": null}); + await messenger.composeAction.setPopup({"popup": "ui/popup.html"}); + await messenger.composeAction.openPopup(); + await messenger.composeAction.setPopup({"popup": null}); } }, From 0a81cfdf6920b7137541c524366a77676b2241cd Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 15:28:36 -0400 Subject: [PATCH 10/20] When disabling the add-on, perform all actions sequentially When disabling the add-on because it failed to initialize properly, instead of just firing off a bunch of promises which will execute in random order based on whenever the JS engine decides to fulfill each one, execute them all sequentially. This eliminates a bunch of unnecessary nondeterminism which could cause intermittent issues we would need to debug, and it also means we will know reliably that the disable steps, some of which may depend on earlier steps, happen in order. --- background.js | 42 +++++++++++++++++++++--------------------- utils/static.js | 11 ++++++----- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/background.js b/background.js index f8384817..1fd7b200 100644 --- a/background.js +++ b/background.js @@ -1314,30 +1314,30 @@ const SendLater = { // visible, but they will be disabled and show a message indicating that the extension is // disabled. This is important for cases where the extension failed to fully intialize, so // that the user doesn't get a false impression that the extension is working. - disable() { + async disable() { SLStatic.warn("Disabling Send Later."); - SLStatic.nofail(clearTimeout, SendLater.loopTimeout); - SLStatic.nofail(SendLater.setQuitNotificationsEnabled, false); - SLStatic.nofail(messenger.browserAction.disable); - SLStatic.nofail(messenger.browserAction.setTitle, { + await SLStatic.nofail(clearTimeout, SendLater.loopTimeout); + await SLStatic.nofail(SendLater.setQuitNotificationsEnabled, false); + await SLStatic.nofail(messenger.browserAction.disable); + await SLStatic.nofail(messenger.browserAction.setTitle, { title: `${messenger.i18n.getMessage("extensionName")} [${messenger.i18n.getMessage("DisabledMessage")}]` }); - SLStatic.nofail(messenger.browserAction.setBadgeText, {text: null}); - SLStatic.nofail(messenger.composeAction.disable); - SLStatic.nofail(messenger.composeAction.setPopup, {"popup": null}); - SLStatic.nofail(messenger.messageDisplayAction.disable); - SLStatic.nofail(messenger.messageDisplayAction.setPopup, {"popup": null}); - SLStatic.nofail(messenger.windows.onCreated.removeListener, SendLater.onWindowCreatedListener); - SLStatic.nofail(messenger.SL3U.onKeyCode.removeListener, SendLater.onUserCommandKeyListener); - SLStatic.nofail(messenger.runtime.onMessageExternal.removeListener, SendLater.onMessageExternalListener); - SLStatic.nofail(messenger.runtime.onMessage.removeListener, SendLater.onRuntimeMessageListenerasync); - SLStatic.nofail(messenger.messages.onNewMailReceived.removeListener, SendLater.onNewMailReceivedListener); - SLStatic.nofail(messenger.messageDisplay.onMessageDisplayed.removeListener, SendLater.onMessageDisplayedListener); - SLStatic.nofail(messenger.commands.onCommand.removeListener, SendLater.onCommandListener); - SLStatic.nofail(messenger.composeAction.onClicked.removeListener, SendLater.clickComposeListener); - SLStatic.nofail(messenger.mailTabs.onDisplayedFolderChanged.removeListener, SendLater.displayedFolderChangedListener); - SLStatic.nofail(messenger.headerView.onHeaderRowUpdate.removeListener, SendLater.headerRowUpdateListener); - SLStatic.nofail(messenger.storage.onChanged.removeListener, SendLater.storageChangedListener); + await SLStatic.nofail(messenger.browserAction.setBadgeText, {text: null}); + await SLStatic.nofail(messenger.composeAction.disable); + await SLStatic.nofail(messenger.composeAction.setPopup, {"popup": null}); + await SLStatic.nofail(messenger.messageDisplayAction.disable); + await SLStatic.nofail(messenger.messageDisplayAction.setPopup, {"popup": null}); + await SLStatic.nofail(messenger.windows.onCreated.removeListener, SendLater.onWindowCreatedListener); + await SLStatic.nofail(messenger.SL3U.onKeyCode.removeListener, SendLater.onUserCommandKeyListener); + await SLStatic.nofail(messenger.runtime.onMessageExternal.removeListener, SendLater.onMessageExternalListener); + await SLStatic.nofail(messenger.runtime.onMessage.removeListener, SendLater.onRuntimeMessageListenerasync); + await SLStatic.nofail(messenger.messages.onNewMailReceived.removeListener, SendLater.onNewMailReceivedListener); + await SLStatic.nofail(messenger.messageDisplay.onMessageDisplayed.removeListener, SendLater.onMessageDisplayedListener); + await SLStatic.nofail(messenger.commands.onCommand.removeListener, SendLater.onCommandListener); + await SLStatic.nofail(messenger.composeAction.onClicked.removeListener, SendLater.clickComposeListener); + await SLStatic.nofail(messenger.mailTabs.onDisplayedFolderChanged.removeListener, SendLater.displayedFolderChangedListener); + await SLStatic.nofail(messenger.headerView.onHeaderRowUpdate.removeListener, SendLater.headerRowUpdateListener); + await SLStatic.nofail(messenger.storage.onChanged.removeListener, SendLater.storageChangedListener); SLStatic.warn("Disabled."); } diff --git a/utils/static.js b/utils/static.js index 3153f6b3..1a729942 100644 --- a/utils/static.js +++ b/utils/static.js @@ -70,11 +70,12 @@ var SLStatic = { // Run a function and report any errors to the console, but don't let the error // propagate to the caller. - nofail(func, ...args) { - if (func[Symbol.toStringTag] === "AsyncFunction") { - func(...args).catch(console.error); - } else { - try { func(...args) } catch (e) { console.error(e) } + async nofail(func, ...args) { + try { + await func(...args); + } + catch (e) { + console.error(e); } }, From 9a8f723e0e6164f00cc035baebe0da80280cb8d3 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 15:34:39 -0400 Subject: [PATCH 11/20] await calls to messenger.browserAction.* --- background.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/background.js b/background.js index 1fd7b200..b0eaa952 100644 --- a/background.js +++ b/background.js @@ -678,15 +678,15 @@ const SendLater = { if (nActive == undefined) nActive = await SLTools.countActiveScheduledMessages(); if (nActive) { - messenger.browserAction.setTitle({title: ( + await messenger.browserAction.setTitle({title: ( `${extName} [${messenger.i18n.getMessage("PendingMessage", [nActive])}]` )}); - messenger.browserAction.setBadgeText({text: String(nActive)}); + await messenger.browserAction.setBadgeText({text: String(nActive)}); } else { - messenger.browserAction.setTitle({title: ( + await messenger.browserAction.setTitle({title: ( `${extName} [${messenger.i18n.getMessage("IdleMessage")}]` )}); - messenger.browserAction.setBadgeText({text: null}); + await messenger.browserAction.setBadgeText({text: null}); } }, @@ -820,7 +820,7 @@ const SendLater = { SendLater.setQuitNotificationsEnabled(preferences.askQuit); - messenger.browserAction.setLabel({label: ( + await messenger.browserAction.setLabel({label: ( preferences.showStatus ? messenger.i18n.getMessage("sendlater3header.label") : "" )}); @@ -1365,8 +1365,8 @@ async function mainLoop() { // or (⌛ \u231B) (e.g. badgeText = "\u27F3") let extName = messenger.i18n.getMessage("extensionName"); let isActiveMessage = messenger.i18n.getMessage("CheckingMessage"); - messenger.browserAction.enable(); - messenger.browserAction.setTitle({title: `${extName} [${isActiveMessage}]`}); + await messenger.browserAction.enable(); + await messenger.browserAction.setTitle({title: `${extName} [${isActiveMessage}]`}); let doSequential = preferences.throttleDelay > 0; console.debug({doSequential, throttleDelay: preferences.throttleDelay}); @@ -1394,9 +1394,10 @@ async function mainLoop() { SendLater.setQuitNotificationsEnabled(false); let extName = messenger.i18n.getMessage("extensionName"); let disabledMsg = messenger.i18n.getMessage("DisabledMessage"); - messenger.browserAction.disable(); - messenger.browserAction.setTitle({title: `${extName} [${disabledMsg}]`}); - messenger.browserAction.setBadgeText({text: null}); + await messenger.browserAction.disable(); + await messenger.browserAction.setTitle( + {title: `${extName} [${disabledMsg}]`}); + await messenger.browserAction.setBadgeText({text: null}); SendLater.previousLoop = new Date(); SendLater.loopTimeout = setTimeout(mainLoop, 60000); From 72575a838106cf6bc905695f9c13b6fdcd7fc623 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 15:42:31 -0400 Subject: [PATCH 12/20] await calls to messenger.messageDisplayAction.enable --- background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/background.js b/background.js index b0eaa952..5d43085f 100644 --- a/background.js +++ b/background.js @@ -1271,7 +1271,7 @@ const SendLater = { const instanceUUID = preferences.instanceUUID; let msg = await messenger.messages.getFull(hdr.id); if (msg.headers["x-send-later-uuid"] == instanceUUID) { - messenger.messageDisplayAction.enable(tab.id); + await messenger.messageDisplayAction.enable(tab.id); } } else { SLStatic.debug("This is not a Drafts folder, so Send Later will not scan it."); From 11274167578c46dd06a354634d105b5b4019339a Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 15:44:44 -0400 Subject: [PATCH 13/20] await calls to updateStatusIndicator and setQuitNotificationsEnabled --- background.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/background.js b/background.js index 5d43085f..ed521161 100644 --- a/background.js +++ b/background.js @@ -801,7 +801,7 @@ const SendLater = { messenger.storage.onChanged.addListener(SendLater.storageChangedListener); }, - storageChangedListener(changes, areaName) { + async storageChangedListener(changes, areaName) { if (areaName === "local" && changes.preferences) { SLStatic.debug("Propagating changes from local storage"); const preferences = changes.preferences.newValue; @@ -818,7 +818,7 @@ const SendLater = { messenger.SL3U.setLogConsoleLevel(SLStatic.logConsoleLevel); - SendLater.setQuitNotificationsEnabled(preferences.askQuit); + await SendLater.setQuitNotificationsEnabled(preferences.askQuit); await messenger.browserAction.setLabel({label: ( preferences.showStatus ? messenger.i18n.getMessage("sendlater3header.label") : "" @@ -918,7 +918,7 @@ const SendLater = { // The new message is not used. // console.debug({originalMsg, newMsg}); - SendLater.updateStatusIndicator(); + await SendLater.updateStatusIndicator(); // Set popup scheduler defaults based on original message scheduleCache[window.id] = @@ -1374,8 +1374,9 @@ async function mainLoop() { try { await SLTools.forAllDrafts(SendLater.possiblySendMessage, doSequential) let nActive = await SLTools.countActiveScheduledMessages(); - SendLater.updateStatusIndicator(nActive); - SendLater.setQuitNotificationsEnabled(preferences.askQuit, nActive); + await SendLater.updateStatusIndicator(nActive); + await SendLater.setQuitNotificationsEnabled( + preferences.askQuit, nActive); SendLater.previousLoop = new Date(); SendLater.loopTimeout = setTimeout(mainLoop, 60000*interval); @@ -1383,15 +1384,16 @@ async function mainLoop() { } catch(err) { SLStatic.error(err); let nActive = await SLTools.countActiveScheduledMessages(); - SendLater.updateStatusIndicator(nActive); - SendLater.setQuitNotificationsEnabled(preferences.askQuit, nActive); + await SendLater.updateStatusIndicator(nActive); + await SendLater.setQuitNotificationsEnabled( + preferences.askQuit, nActive); SendLater.previousLoop = new Date(); SendLater.loopTimeout = setTimeout(mainLoop, 60000); SLStatic.debug(`Next main loop iteration in 1 minute.`); }; } else { - SendLater.setQuitNotificationsEnabled(false); + await SendLater.setQuitNotificationsEnabled(false); let extName = messenger.i18n.getMessage("extensionName"); let disabledMsg = messenger.i18n.getMessage("DisabledMessage"); await messenger.browserAction.disable(); From a6880109cd926dc70a4720fd03df37cedc17d980 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 16:19:08 -0400 Subject: [PATCH 14/20] Fix bug which was preventing `getPreferences` from working Send Later allows other extensions to get and set its preferences via the `getPreferences` and `setPreferences` actions. Unfortunately, the former was broken because it was attempting to use the JavaScript `filter` operator on on Object when it only works on Arrays. This is now fixed. --- background.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/background.js b/background.js index ed521161..395774a1 100644 --- a/background.js +++ b/background.js @@ -1022,9 +1022,13 @@ const SendLater = { } case "getPreferences": { // Return Promise for the allowed preferences. - return SLTools.getPrefs() - .then(preferences => preferences.filter(prop => SLStatic.prefInputIds.includes(prop))) - .catch(ex => SLStatic.error(ex)); + return SLTools.getPrefs().then((prefs) => { + prefs = Object.entries(prefs); + prefs = prefs.filter(([key, value]) => + SLStatic.prefInputIds.includes(key)); + prefs = Object.fromEntries(prefs); + return prefs; + }).catch(ex => SLStatic.error(ex)); } case "setPreferences": { const setAndReturnPrefs = async (old_prefs, new_prefs) => { From 78d9a84e67a89dc91cc326fbd1755bda0fc42e12 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 17:00:22 -0400 Subject: [PATCH 15/20] Remove unnecessary jiggering of compose action which causes issues After converting the compose action click handler to an async function, the first time the compose action button is clicked it doesn't work, and an error is logged that the popup can only be opened from a user input handler, which is weird because it _is_ being opened from a user input handler. Having said that, simplifying the code by not unnecessarily setting and clearing the popup file makes this problem go away, and this change doesn't hurt anything else, so let's go with that. --- background.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/background.js b/background.js index 395774a1..b832e1b5 100644 --- a/background.js +++ b/background.js @@ -723,6 +723,7 @@ const SendLater = { messenger.runtime.onMessage.addListener(SendLater.onRuntimeMessageListenerasync); messenger.messageDisplay.onMessageDisplayed.addListener(SendLater.onMessageDisplayedListener); messenger.commands.onCommand.addListener(SendLater.onCommandListener); + messenger.composeAction.setPopup({"popup": "ui/popup.html"}); messenger.composeAction.onClicked.addListener(SendLater.clickComposeListener); messenger.mailTabs.onDisplayedFolderChanged.addListener(SendLater.displayedFolderChangedListener); messenger.headerView.onHeaderRowUpdate.addListener( @@ -1308,9 +1309,7 @@ const SendLater = { SLStatic.info(`Executing accelerator Click+${mod}: ${funcName}(${funcArgs})`); SendLater.quickSendWithUfunc(funcName, funcArgs, tab.id); } else { - await messenger.composeAction.setPopup({"popup": "ui/popup.html"}); await messenger.composeAction.openPopup(); - await messenger.composeAction.setPopup({"popup": null}); } }, @@ -1328,7 +1327,6 @@ const SendLater = { }); await SLStatic.nofail(messenger.browserAction.setBadgeText, {text: null}); await SLStatic.nofail(messenger.composeAction.disable); - await SLStatic.nofail(messenger.composeAction.setPopup, {"popup": null}); await SLStatic.nofail(messenger.messageDisplayAction.disable); await SLStatic.nofail(messenger.messageDisplayAction.setPopup, {"popup": null}); await SLStatic.nofail(messenger.windows.onCreated.removeListener, SendLater.onWindowCreatedListener); From a987435546fcae1c865240daea5d3ea6201dcc4d Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 18:33:11 -0400 Subject: [PATCH 16/20] Fix two bugs in the Send Later column in the message list 1) I don't understand why there are two custom column handlers in the experiments directory, but it appears at some point the column handling was switched from customColumn* to legacyColumn*, which changed the argument types for `messenger.columnHandler.setColumnVisible`, but the code in `background.js` wasn't updated to reflect that change. Now it has been, so the Send Later column is now properly enabled and disabled. 2) If the Drafts folder is the currently displayed folder when Thunderbird starts up, the Send Later column is now properly enabled and made visible if it's supposed to be. --- background.js | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/background.js b/background.js index b832e1b5..9a4fcce5 100644 --- a/background.js +++ b/background.js @@ -725,7 +725,19 @@ const SendLater = { messenger.commands.onCommand.addListener(SendLater.onCommandListener); messenger.composeAction.setPopup({"popup": "ui/popup.html"}); messenger.composeAction.onClicked.addListener(SendLater.clickComposeListener); + + // Initialize drafts folder column + await messenger.columnHandler.addCustomColumn({ + name: messenger.i18n.getMessage("sendlater3header.label"), + tooltip: "", + }).catch(ex => { + SLStatic.error("columnHandler.addCustomColumn",ex); + }); messenger.mailTabs.onDisplayedFolderChanged.addListener(SendLater.displayedFolderChangedListener); + messenger.tabs.onUpdated.addListener(SendLater.tabUpdatedListener); + // We won't get events for tabs that are already loaded. + SendLater.configureAllTabs(); + messenger.headerView.onHeaderRowUpdate.addListener( SendLater.headerRowUpdateListener, messenger.i18n.getMessage("sendlater3header.label") ); @@ -772,14 +784,6 @@ const SendLater = { )}); }).catch(ex => SLStatic.error(ex)); - // Initialize drafts folder column - await messenger.columnHandler.addCustomColumn({ - name: messenger.i18n.getMessage("sendlater3header.label"), - tooltip: "", - }).catch(ex => { - SLStatic.error("columnHandler.addCustomColumn",ex); - }); - // Initialize expanded header row await messenger.headerView.addCustomHdrRow({ name: messenger.i18n.getMessage("sendlater3header.label"), @@ -832,7 +836,7 @@ const SendLater = { // folder before their preferences can fully take effect. if (!preferences.showColumn) { const columnName = messenger.i18n.getMessage("sendlater3header.label"); - messenger.columnHandler.setColumnVisible(columnName, false, true); + messenger.columnHandler.setColumnVisible(columnName, false); } } }, @@ -853,11 +857,34 @@ const SendLater = { return { text: cellText, visible }; }, + async configureAllTabs() { + SLStatic.debug("SLTABS: configureAllTabs"); + messenger.tabs.query({mailTab: true, active: true}).then(tabs => { + for (let tab of tabs) { + SLStatic.debug( + "SLTABS: Calling tabUpdatedListener from configureAllTabs"); + SendLater.tabUpdatedListener(tab.id, {}, tab); + } + }); + }, + + async tabUpdatedListener(tabId, changeInfo, tab) { + SLStatic.debug(`SLTABS: tabUpdatedListener tab.status=${tab.status} tab.mailTab=${tab.mailTab}`); + if (tab.status != "complete" || ! tab.mailTab) return; + let tabProperties = await messenger.mailTabs.get(tabId); + SLStatic.debug(`SLTABS: tabProperties.displayedFolder=${tabProperties.displayedFolder}`); + if (! tabProperties.displayedFolder) return; + await SendLater.displayedFolderChangedListener( + tab, tabProperties.displayedFolder); + }, + async displayedFolderChangedListener(tab, folder) { + SLStatic.debug("SLTABS: displayedFolderChangedListener"); const preferences = await SLTools.getPrefs(); let visible = (folder.type == "drafts") && (preferences.showColumn === true); let columnName = messenger.i18n.getMessage("sendlater3header.label"); - await messenger.columnHandler.setColumnVisible(columnName, visible, false); + await messenger.columnHandler.setColumnVisible( + columnName, visible, tab.windowId); }, // When user opens a new messagecompose window, we need to From 5f41b541d0e0093938e41b8564c25a1b320932b8 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 18:47:35 -0400 Subject: [PATCH 17/20] Add more missing awaits --- background.js | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/background.js b/background.js index 9a4fcce5..d9f59cdc 100644 --- a/background.js +++ b/background.js @@ -178,7 +178,7 @@ const SendLater = { // Optionally mark the saved message as "read" if (preferences.markDraftsRead) { for (let msg of saveProperties.messages) { - messenger.messages.update(msg.id, { read: true }) + await messenger.messages.update(msg.id, { read: true }) } } @@ -187,12 +187,12 @@ const SendLater = { if (composeDetails.relatedMessageId) { if (composeDetails.type == "reply") { console.debug("This is a reply message. Setting original 'replied'"); - messenger.SL3U.setDispositionState( + await messenger.SL3U.setDispositionState( composeDetails.relatedMessageId, "replied" ); } else if (composeDetails.type == "forward") { console.debug("This is a fwd message. Setting original 'forwarded'"); - messenger.SL3U.setDispositionState( + await messenger.SL3U.setDispositionState( composeDetails.relatedMessageId, "forwarded" ); } @@ -211,7 +211,7 @@ const SendLater = { // SLTools.unscheduledMsgCache.delete(draftId); // SLTools.scheduledMsgCache.add(draftId); // if (preferences.markDraftsRead) - // messenger.messages.update(draftId, { read: true }); + // await messenger.messages.update(draftId, { read: true }); // } // if (originalDraftMsg) // touchDraftMsg(originalDraftMsg.id); @@ -655,7 +655,8 @@ const SendLater = { SLStatic.info(`Generated new UUID: ${instance_uuid}`); } preferences.instanceUUID = instance_uuid; - messenger.SL3U.setLegacyPref("instance.uuid", "string", instance_uuid); + await messenger.SL3U.setLegacyPref( + "instance.uuid", "string", instance_uuid); } if (currentMigrationNumber < SLStatic.CURRENT_LEGACY_MIGRATION) { @@ -704,13 +705,13 @@ const SendLater = { let title = messenger.i18n.getMessage("scheduledMessagesWarningTitle") + " - " + appName; let requestWarning = messenger.i18n.getMessage("scheduledMessagesWarningQuitRequested", appName); let grantedWarning = messenger.i18n.getMessage("ScheduledMessagesWarningQuit", appName); - messenger.quitter.setQuitRequestedAlert(title, requestWarning); - messenger.quitter.setQuitGrantedAlert(title, grantedWarning); + await messenger.quitter.setQuitRequestedAlert(title, requestWarning); + await messenger.quitter.setQuitGrantedAlert(title, grantedWarning); return; } } - messenger.quitter.removeQuitRequestedObserver(); - messenger.quitter.removeQuitGrantedObserver(); + await messenger.quitter.removeQuitRequestedObserver(); + await messenger.quitter.removeQuitGrantedObserver(); }, async init() { @@ -761,28 +762,31 @@ const SendLater = { // Perform any pending preference migrations. await this.migratePreferences(); - await SLTools.getPrefs().then(async (preferences) => { + try { + let preferences = await SLTools.getPrefs(); SendLater.prefCache = preferences; SLStatic.logConsoleLevel = preferences.logConsoleLevel.toLowerCase(); SLStatic.customizeDateTime = (preferences.customizeDateTime === true); SLStatic.longDateTimeFormat = preferences.longDateTimeFormat; SLStatic.shortDateTimeFormat = preferences.shortDateTimeFormat; - messenger.SL3U.setLogConsoleLevel(SLStatic.logConsoleLevel); + await messenger.SL3U.setLogConsoleLevel(SLStatic.logConsoleLevel); - for (let pref of [ - "customizeDateTime", "longDateTimeFormat", "shortDateTimeFormat", "instanceUUID" - ]) { - messenger.columnHandler.setPreference(pref, preferences[pref]); + for (let pref of ["customizeDateTime", "longDateTimeFormat", + "shortDateTimeFormat", "instanceUUID"]) { + await messenger.columnHandler.setPreference(pref, preferences[pref]); } let nActive = await SLTools.countActiveScheduledMessages(); await SendLater.updateStatusIndicator(nActive); - await SendLater.setQuitNotificationsEnabled(preferences.askQuit, nActive); + await SendLater.setQuitNotificationsEnabled( + preferences.askQuit, nActive); - await messenger.browserAction.setLabel({label: ( - preferences.showStatus ? messenger.i18n.getMessage("sendlater3header.label") : "" - )}); - }).catch(ex => SLStatic.error(ex)); + await messenger.browserAction.setLabel( + {label: preferences.showStatus ? + messenger.i18n.getMessage("sendlater3header.label") : ""}); + } catch (ex) { + SLStatic.error(ex); + } // Initialize expanded header row await messenger.headerView.addCustomHdrRow({ @@ -821,7 +825,7 @@ const SendLater = { } } - messenger.SL3U.setLogConsoleLevel(SLStatic.logConsoleLevel); + await messenger.SL3U.setLogConsoleLevel(SLStatic.logConsoleLevel); await SendLater.setQuitNotificationsEnabled(preferences.askQuit); From 45dc4c40cfe753c1210c4de56f73cfd3c9c523a8 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 18:48:03 -0400 Subject: [PATCH 18/20] Clean up some logging Remove an unnecessary console debug log message that's two verbose. Change several console log calls to SLStatic log calls so they'll get filtered by the configured log level. --- background.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/background.js b/background.js index d9f59cdc..43b801ca 100644 --- a/background.js +++ b/background.js @@ -186,12 +186,12 @@ const SendLater = { // to show that it has been replied to or forwarded. if (composeDetails.relatedMessageId) { if (composeDetails.type == "reply") { - console.debug("This is a reply message. Setting original 'replied'"); + SLStatic.debug("This is a reply message. Setting original 'replied'"); await messenger.SL3U.setDispositionState( composeDetails.relatedMessageId, "replied" ); } else if (composeDetails.type == "forward") { - console.debug("This is a fwd message. Setting original 'forwarded'"); + SLStatic.debug("This is a fwd message. Setting original 'forwarded'"); await messenger.SL3U.setDispositionState( composeDetails.relatedMessageId, "forwarded" ); @@ -1199,7 +1199,8 @@ const SendLater = { response.schedules = await SLTools.forAllDrafts( async (draftHdr) => { if (SLTools.unscheduledMsgCache.has(draftHdr.id)) { - console.debug("Ignoring unscheduled message", draftHdr.id, draftHdr); + SLStatic.debug( + "Ignoring unscheduled message", draftHdr.id, draftHdr); return null; } return await messenger.messages.getFull(draftHdr.id).then( @@ -1402,7 +1403,6 @@ async function mainLoop() { await messenger.browserAction.setTitle({title: `${extName} [${isActiveMessage}]`}); let doSequential = preferences.throttleDelay > 0; - console.debug({doSequential, throttleDelay: preferences.throttleDelay}); try { await SLTools.forAllDrafts(SendLater.possiblySendMessage, doSequential) From eab1b04e113e2c6f932973c12f6bc1d547b1bb38 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 18:48:46 -0400 Subject: [PATCH 19/20] Delete unnecessary whitespace --- background.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/background.js b/background.js index 43b801ca..fa2f6d6d 100644 --- a/background.js +++ b/background.js @@ -871,7 +871,7 @@ const SendLater = { } }); }, - + async tabUpdatedListener(tabId, changeInfo, tab) { SLStatic.debug(`SLTABS: tabUpdatedListener tab.status=${tab.status} tab.mailTab=${tab.mailTab}`); if (tab.status != "complete" || ! tab.mailTab) return; @@ -951,7 +951,7 @@ const SendLater = { // console.debug({originalMsg, newMsg}); await SendLater.updateStatusIndicator(); - + // Set popup scheduler defaults based on original message scheduleCache[window.id] = SLStatic.parseHeadersForPopupUICache(originalMsgPart.headers); @@ -1069,7 +1069,7 @@ const SendLater = { throw new Error (`Property ${prop} is not a valid Send Later preference.`); } if ( - prop in old_prefs && + prop in old_prefs && typeof(old_prefs[prop]) != "undefined" && typeof(new_prefs[prop]) != "undefined" && typeof(old_prefs[prop]) != typeof(new_prefs[prop]) @@ -1420,7 +1420,7 @@ async function mainLoop() { await SendLater.updateStatusIndicator(nActive); await SendLater.setQuitNotificationsEnabled( preferences.askQuit, nActive); - + SendLater.previousLoop = new Date(); SendLater.loopTimeout = setTimeout(mainLoop, 60000); SLStatic.debug(`Next main loop iteration in 1 minute.`); From f042f65c8e768cb2acbefb154c925a40abd5ceda Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 7 Aug 2023 18:56:23 -0400 Subject: [PATCH 20/20] Fix expanded header row display You can't add the header row update listener before you've initialized the expanded header row! --- background.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/background.js b/background.js index fa2f6d6d..bdb43506 100644 --- a/background.js +++ b/background.js @@ -739,9 +739,16 @@ const SendLater = { // We won't get events for tabs that are already loaded. SendLater.configureAllTabs(); + // Initialize expanded header row + await messenger.headerView.addCustomHdrRow({ + name: messenger.i18n.getMessage("sendlater3header.label"), + }).catch(ex => { + SLStatic.error("headerView.addCustomHdrRow",ex); + }); messenger.headerView.onHeaderRowUpdate.addListener( SendLater.headerRowUpdateListener, messenger.i18n.getMessage("sendlater3header.label") ); + messenger.messages.onNewMailReceived.addListener(SendLater.onNewMailReceivedListener); // Set custom DB headers preference, if not already set. @@ -788,13 +795,6 @@ const SendLater = { SLStatic.error(ex); } - // Initialize expanded header row - await messenger.headerView.addCustomHdrRow({ - name: messenger.i18n.getMessage("sendlater3header.label"), - }).catch(ex => { - SLStatic.error("headerView.addCustomHdrRow",ex); - }); - // Attach to all existing msgcompose windows messenger.SL3U.hijackComposeWindowKeyBindings().catch(ex => { SLStatic.error("SL3U.hijackComposeWindowKeyBindings",ex);