diff options
Diffstat (limited to 'comm/mail/components/extensions/test/browser/browser_ext_compose_onBeforeSend.js')
-rw-r--r-- | comm/mail/components/extensions/test/browser/browser_ext_compose_onBeforeSend.js | 1010 |
1 files changed, 1010 insertions, 0 deletions
diff --git a/comm/mail/components/extensions/test/browser/browser_ext_compose_onBeforeSend.js b/comm/mail/components/extensions/test/browser/browser_ext_compose_onBeforeSend.js new file mode 100644 index 0000000000..285c4df33f --- /dev/null +++ b/comm/mail/components/extensions/test/browser/browser_ext_compose_onBeforeSend.js @@ -0,0 +1,1010 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { ExtensionSupport } = ChromeUtils.import( + "resource:///modules/ExtensionSupport.jsm" +); + +let account = createAccount(); +let defaultIdentity = addIdentity(account); +let nonDefaultIdentity = addIdentity(account, "nondefault@invalid"); + +// A local outbox is needed so we can use "send later". +let localAccount = createAccount("local"); +let outbox = localAccount.incomingServer.rootFolder.getChildNamed("outbox"); + +function messagesInOutbox(count) { + info(`Checking for ${count} messages in outbox`); + + count -= [...outbox.messages].length; + if (count <= 0) { + return Promise.resolve(); + } + + info(`Waiting for ${count} messages in outbox`); + return new Promise(resolve => { + MailServices.mfn.addListener( + { + msgAdded(msgHdr) { + if (--count == 0) { + MailServices.mfn.removeListener(this); + resolve(); + } + }, + }, + MailServices.mfn.msgAdded + ); + }); +} + +add_task(async function testCancel() { + let files = { + "background.js": async () => { + async function beginSend(sendExpected, lockExpected) { + await window.sendMessage("beginSend"); + return checkIfSent(sendExpected, lockExpected); + } + + function checkIfSent(sendExpected, lockExpected = null) { + return window.sendMessage("checkIfSent", sendExpected, lockExpected); + } + + function checkWindow(expected) { + return window.sendMessage("checkWindow", expected); + } + + // Open a compose window with a message. The message will never send + // because we removed the sending function, so we can attempt to send + // it over and over. + + let createdWindowPromise = window.waitForEvent("windows.onCreated"); + await browser.compose.beginNew({ + to: ["test@test.invalid"], + subject: "Test", + }); + let [createdWindow] = await createdWindowPromise; + browser.test.assertEq("messageCompose", createdWindow.type); + + await checkWindow({ to: ["test@test.invalid"], subject: "Test" }); + + let [tab] = await browser.tabs.query({ windowId: createdWindow.id }); + + // Send the message. No listeners exist, so sending should continue. + + await beginSend(true); + + // Add a non-cancelling listener. Sending should continue. + + let listener1 = tab => { + listener1.tab = tab; + return {}; + }; + browser.compose.onBeforeSend.addListener(listener1); + await beginSend(true); + browser.test.assertEq(tab.id, listener1.tab.id, "listener1 was fired"); + browser.compose.onBeforeSend.removeListener(listener1); + delete listener1.tab; + + // Add a cancelling listener. Sending should not continue. + + let listener2 = tab => { + listener2.tab = tab; + return { cancel: true }; + }; + browser.compose.onBeforeSend.addListener(listener2); + await beginSend(false, false); + browser.test.assertEq(tab.id, listener2.tab.id, "listener2 was fired"); + browser.compose.onBeforeSend.removeListener(listener2); + delete listener2.tab; + await beginSend(true); // Removing the listener worked. + + // Add a listener returning a Promise. Resolve the Promise to unblock. + // Sending should continue. + + let listener3 = tab => { + listener3.tab = tab; + return new Promise(resolve => { + listener3.resolve = resolve; + }); + }; + browser.compose.onBeforeSend.addListener(listener3); + await beginSend(false, true); + browser.test.assertEq(tab.id, listener3.tab.id, "listener3 was fired"); + listener3.resolve({ cancel: false }); + await checkIfSent(true); + browser.compose.onBeforeSend.removeListener(listener3); + delete listener3.tab; + + // Add a listener returning a Promise. Resolve the Promise to cancel. + // Sending should not continue. + + let listener4 = tab => { + listener4.tab = tab; + return new Promise(resolve => { + listener4.resolve = resolve; + }); + }; + browser.compose.onBeforeSend.addListener(listener4); + await beginSend(false, true); + browser.test.assertEq(tab.id, listener4.tab.id, "listener4 was fired"); + listener4.resolve({ cancel: true }); + await checkIfSent(false, false); + browser.compose.onBeforeSend.removeListener(listener4); + delete listener4.tab; + await beginSend(true); // Removing the listener worked. + + // Clean up. + + let removedWindowPromise = window.waitForEvent("windows.onRemoved"); + browser.windows.remove(createdWindow.id); + await removedWindowPromise; + + browser.test.assertTrue( + !listener1.tab, + "listener1 was not fired after removal" + ); + browser.test.assertTrue( + !listener2.tab, + "listener2 was not fired after removal" + ); + browser.test.assertTrue( + !listener3.tab, + "listener3 was not fired after removal" + ); + browser.test.assertTrue( + !listener4.tab, + "listener4 was not fired after removal" + ); + + browser.test.notifyPass("finished"); + }, + "utils.js": await getUtilsJS(), + }; + let extension = ExtensionTestUtils.loadExtension({ + files, + manifest: { + background: { scripts: ["utils.js", "background.js"] }, + permissions: ["compose"], + }, + }); + + // We can't allow sending to actually happen, this is a test. For every + // compose window that opens, replace the function which does the actual + // sending with one that only records when it has been called. + let didTryToSendMessage = false; + let windowListenerRemoved = false; + ExtensionSupport.registerWindowListener("mochitest", { + chromeURLs: [ + "chrome://messenger/content/messengercompose/messengercompose.xhtml", + ], + onLoadWindow(window) { + window.CompleteGenericSendMessage = function (msgType) { + didTryToSendMessage = true; + Services.obs.notifyObservers( + { + composeWindow: window, + }, + "mail:composeSendProgressStop" + ); + }; + }, + }); + registerCleanupFunction(() => { + if (!windowListenerRemoved) { + ExtensionSupport.unregisterWindowListener("mochitest"); + } + }); + + extension.onMessage("beginSend", async () => { + let composeWindows = [...Services.wm.getEnumerator("msgcompose")]; + is(composeWindows.length, 1); + + composeWindows[0] + .GenericSendMessage(Ci.nsIMsgCompDeliverMode.Now) + .catch(() => { + // This test is ignoring errors thrown by GenericSendMessage, but looks + // at didTryToSendMessage of the mocked CompleteGenericSendMessage to + // check if onBeforeSend aborted the send process. + }); + extension.sendMessage(); + }); + + extension.onMessage("checkIfSent", async (sendExpected, lockExpected) => { + // Wait a moment to see if send happens asynchronously. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 500)); + is(didTryToSendMessage, sendExpected, "did try to send a message"); + + if (lockExpected !== null) { + let composeWindows = [...Services.wm.getEnumerator("msgcompose")]; + is(composeWindows.length, 1); + is(composeWindows[0].gWindowLocked, lockExpected, "window is locked"); + } + + didTryToSendMessage = false; + extension.sendMessage(); + }); + + extension.onMessage("checkWindow", async expected => { + await checkComposeHeaders(expected); + extension.sendMessage(); + }); + + await extension.startup(); + await extension.awaitFinish("finished"); + await extension.unload(); + + ExtensionSupport.unregisterWindowListener("mochitest"); + windowListenerRemoved = true; +}); + +add_task(async function testChangeDetails() { + let files = { + "background.js": async () => { + function beginSend() { + return window.sendMessage("beginSend"); + } + + function checkWindow(expected) { + return window.sendMessage("checkWindow", expected); + } + + let accounts = await browser.accounts.list(); + // If this test is run alone, the order of accounts is different compared + // to running all tests. We need the account with the 2 added identities. + let account = accounts.find(a => a.identities.length == 2); + let [defaultIdentity, nonDefaultIdentity] = account.identities; + + // Add a listener that changes the headers and body. Sending should + // continue and the headers should change. This is largely the same code + // as tested in browser_ext_compose_details.js, so just test that the + // changes happen. + + let createdWindowPromise = window.waitForEvent("windows.onCreated"); + await browser.compose.beginNew({ + to: ["test@test.invalid"], + subject: "Test", + body: "Original body.", + }); + let [createdWindow] = await createdWindowPromise; + browser.test.assertEq("messageCompose", createdWindow.type); + + await checkWindow({ + to: ["test@test.invalid"], + subject: "Test", + body: "Original body.", + }); + + let [tab] = await browser.tabs.query({ windowId: createdWindow.id }); + + let listener5 = (tab, details) => { + listener5.tab = tab; + listener5.details = details; + return { + details: { + identityId: nonDefaultIdentity.id, + to: ["to@test5.invalid"], + cc: ["cc@test5.invalid"], + subject: "Changed by listener5", + body: "New body from listener5.", + }, + }; + }; + browser.compose.onBeforeSend.addListener(listener5); + await beginSend(); + browser.test.assertEq(tab.id, listener5.tab.id, "listener5 was fired"); + browser.test.assertEq(defaultIdentity.id, listener5.details.identityId); + browser.test.assertEq(1, listener5.details.to.length); + browser.test.assertEq( + "test@test.invalid", + listener5.details.to[0], + "listener5 recipient correct" + ); + browser.test.assertEq( + "Test", + listener5.details.subject, + "listener5 subject correct" + ); + browser.compose.onBeforeSend.removeListener(listener5); + delete listener5.tab; + + // Do the same thing, but this time with a Promise. + + createdWindowPromise = window.waitForEvent("windows.onCreated"); + await browser.compose.beginNew({ + to: ["test@test.invalid"], + subject: "Test", + body: "Original body.", + }); + [createdWindow] = await createdWindowPromise; + browser.test.assertEq("messageCompose", createdWindow.type); + + await checkWindow({ + to: ["test@test.invalid"], + subject: "Test", + body: "Original body.", + }); + + [tab] = await browser.tabs.query({ windowId: createdWindow.id }); + + let listener6 = (tab, details) => { + listener6.tab = tab; + listener6.details = details; + return new Promise(resolve => { + listener6.resolve = resolve; + }); + }; + browser.compose.onBeforeSend.addListener(listener6); + await beginSend(); + browser.test.assertEq(tab.id, listener6.tab.id, "listener6 was fired"); + browser.test.assertEq(defaultIdentity.id, listener6.details.identityId); + browser.test.assertEq(1, listener6.details.to.length); + browser.test.assertEq( + "test@test.invalid", + listener6.details.to[0], + "listener6 recipient correct" + ); + browser.test.assertEq( + "Test", + listener6.details.subject, + "listener6 subject correct" + ); + listener6.resolve({ + details: { + identityId: nonDefaultIdentity.id, + to: ["to@test6.invalid"], + cc: ["cc@test6.invalid"], + subject: "Changed by listener6", + body: "New body from listener6.", + }, + }); + browser.compose.onBeforeSend.removeListener(listener6); + delete listener6.tab; + + browser.test.assertTrue( + !listener5.tab, + "listener5 was not fired after removal" + ); + browser.test.assertTrue( + !listener6.tab, + "listener6 was not fired after removal" + ); + + browser.test.notifyPass("finished"); + }, + "utils.js": await getUtilsJS(), + }; + let extension = ExtensionTestUtils.loadExtension({ + files, + manifest: { + background: { scripts: ["utils.js", "background.js"] }, + permissions: ["accountsRead", "compose"], + }, + }); + + extension.onMessage("beginSend", async () => { + let composeWindows = [...Services.wm.getEnumerator("msgcompose")]; + is(composeWindows.length, 1); + + composeWindows[0] + .GenericSendMessage(Ci.nsIMsgCompDeliverMode.Later) + .catch(() => { + // This test is ignoring errors thrown by GenericSendMessage, but looks + // at didTryToSendMessage of the mocked CompleteGenericSendMessage to + // check if onBeforeSend aborted the send process. + }); + extension.sendMessage(); + }); + + extension.onMessage("checkWindow", async expected => { + await checkComposeHeaders(expected); + + let composeWindow = Services.wm.getMostRecentWindow("msgcompose"); + let body = composeWindow + .GetCurrentEditor() + .outputToString("text/plain", Ci.nsIDocumentEncoder.OutputRaw); + is(body, expected.body); + + extension.sendMessage(); + }); + + await extension.startup(); + await extension.awaitFinish("finished"); + await extension.unload(); + + await messagesInOutbox(2); + + let outboxMessages = [...outbox.messages]; + ok(outboxMessages.length > 0); + let sentMessage5 = outboxMessages.shift(); + is(sentMessage5.author, "nondefault@invalid", "author was changed"); + is(sentMessage5.subject, "Changed by listener5", "subject was changed"); + is(sentMessage5.recipients, "to@test5.invalid", "to was changed"); + is(sentMessage5.ccList, "cc@test5.invalid", "cc was changed"); + + await new Promise(resolve => { + window.MsgHdrToMimeMessage(sentMessage5, null, (msgHdr, mimeMessage) => { + is( + // Fold Windows line-endings \r\n to \n. + mimeMessage.parts[0].body.replace(/\r/g, ""), + "New body from listener5.\n" + ); + resolve(); + }); + }); + + ok(outboxMessages.length > 0); + let sentMessage6 = outboxMessages.shift(); + is(sentMessage6.author, "nondefault@invalid", "author was changed"); + is(sentMessage6.subject, "Changed by listener6", "subject was changed"); + is(sentMessage6.recipients, "to@test6.invalid", "to was changed"); + is(sentMessage6.ccList, "cc@test6.invalid", "cc was changed"); + + await new Promise(resolve => { + window.MsgHdrToMimeMessage(sentMessage6, null, (msgHdr, mimeMessage) => { + is( + // Fold Windows line-endings \r\n to \n. + mimeMessage.parts[0].body.replace(/\r/g, ""), + "New body from listener6.\n" + ); + resolve(); + }); + }); + + ok(outboxMessages.length == 0); + + await new Promise(resolve => { + outbox.deleteMessages( + [sentMessage5, sentMessage6], + null, + true, + false, + { OnStopCopy: resolve }, + false + ); + }); +}); + +add_task(async function testChangeAttachments() { + let files = { + "background.js": async () => { + // Add a listener that changes attachments. Sending should continue and + // the attachments should change. + + let tab = await browser.compose.beginNew({ + to: ["test@test.invalid"], + subject: "Test", + body: "Original body.", + attachments: [ + { file: new File(["remove"], "remove.txt") }, + { file: new File(["change"], "change.txt") }, + ], + }); + + let listener12 = async (tab, details) => { + let attachments = await browser.compose.listAttachments(tab.id); + browser.test.assertEq("remove.txt", attachments[0].name); + browser.test.assertEq("change.txt", attachments[1].name); + + await browser.compose.removeAttachment(tab.id, attachments[0].id); + await browser.compose.updateAttachment(tab.id, attachments[1].id, { + name: "changed.txt", + }); + await browser.compose.addAttachment(tab.id, { + file: new File(["added"], "added.txt"), + }); + + attachments = await browser.compose.listAttachments(tab.id); + browser.test.assertEq("changed.txt", attachments[0].name); + browser.test.assertEq("added.txt", attachments[1].name); + + listener12.tab = tab; + }; + browser.compose.onBeforeSend.addListener(listener12); + + await window.sendMessage("beginSend"); + browser.test.assertEq(tab.id, listener12.tab.id, "listener12 completed"); + browser.compose.onBeforeSend.removeListener(listener12); + + browser.test.notifyPass("finished"); + }, + "utils.js": await getUtilsJS(), + }; + let extension = ExtensionTestUtils.loadExtension({ + files, + manifest: { + background: { scripts: ["utils.js", "background.js"] }, + permissions: ["accountsRead", "compose"], + }, + }); + + extension.onMessage("beginSend", async () => { + let composeWindows = [...Services.wm.getEnumerator("msgcompose")]; + is(composeWindows.length, 1); + + let sendPromise = BrowserTestUtils.waitForEvent( + composeWindows[0], + "aftersend" + ); + composeWindows[0] + .GenericSendMessage(Ci.nsIMsgCompDeliverMode.Later) + .catch(() => { + // This test is ignoring errors thrown by GenericSendMessage, but looks + // at didTryToSendMessage of the mocked CompleteGenericSendMessage to + // check if onBeforeSend aborted the send process. + }); + await sendPromise; + extension.sendMessage(); + }); + + await extension.startup(); + await extension.awaitFinish("finished"); + await extension.unload(); + + await messagesInOutbox(1); + + let outboxMessages = [...outbox.messages]; + ok(outboxMessages.length > 0); + let sentMessage12 = outboxMessages.shift(); + + await new Promise(resolve => { + window.MsgHdrToMimeMessage(sentMessage12, null, (msgHdr, mimeMessage) => { + Assert.equal(mimeMessage.parts.length, 1); + Assert.equal(mimeMessage.parts[0].parts.length, 3); + Assert.equal(mimeMessage.parts[0].parts[1].name, "changed.txt"); + Assert.equal(mimeMessage.parts[0].parts[2].name, "added.txt"); + resolve(); + }); + }); + + ok(outboxMessages.length == 0); + + await new Promise(resolve => { + outbox.deleteMessages( + [sentMessage12], + null, + true, + false, + { OnStopCopy: resolve }, + false + ); + }); +}); + +add_task(async function testListExpansion() { + let files = { + "background.js": async () => { + function beginSend() { + return window.sendMessage("beginSend"); + } + + function checkWindow(expected) { + return window.sendMessage("checkWindow", expected); + } + + let addressBook = await browser.addressBooks.create({ + name: "Baker Street", + }); + let contacts = { + sherlock: await browser.contacts.create(addressBook, { + DisplayName: "Sherlock Holmes", + PrimaryEmail: "sherlock@bakerstreet.invalid", + }), + john: await browser.contacts.create(addressBook, { + DisplayName: "John Watson", + PrimaryEmail: "john@bakerstreet.invalid", + }), + }; + let list = await browser.mailingLists.create(addressBook, { + name: "Holmes and Watson", + description: "Tenants221B", + }); + await browser.mailingLists.addMember(list, contacts.sherlock); + await browser.mailingLists.addMember(list, contacts.john); + + // Add a listener that changes the headers. Sending should continue and + // the headers should change. The mailing list should be expanded in both + // the To: and Bcc: headers. + + let createdWindowPromise = window.waitForEvent("windows.onCreated"); + await browser.compose.beginNew({ + to: [{ id: list, type: "mailingList" }], + subject: "Test", + }); + let [createdWindow] = await createdWindowPromise; + browser.test.assertEq("messageCompose", createdWindow.type); + + await checkWindow({ + to: ["Holmes and Watson <Tenants221B>"], + subject: "Test", + }); + + let [tab] = await browser.tabs.query({ windowId: createdWindow.id }); + + let listener7 = (tab, details) => { + listener7.tab = tab; + listener7.details = details; + return { + details: { + bcc: details.to, + subject: "Changed by listener7", + }, + }; + }; + browser.compose.onBeforeSend.addListener(listener7); + await beginSend(); + browser.test.assertEq(tab.id, listener7.tab.id, "listener7 was fired"); + browser.test.assertEq(1, listener7.details.to.length); + browser.test.assertEq( + "Holmes and Watson <Tenants221B>", + listener7.details.to[0], + "listener7 recipient correct" + ); + browser.test.assertEq( + "Test", + listener7.details.subject, + "listener7 subject correct" + ); + browser.compose.onBeforeSend.removeListener(listener7); + + // Return nothing from the listener. The mailing list should be expanded. + + createdWindowPromise = window.waitForEvent("windows.onCreated"); + await browser.compose.beginNew({ + to: [{ id: list, type: "mailingList" }], + subject: "Test", + }); + [createdWindow] = await createdWindowPromise; + browser.test.assertEq("messageCompose", createdWindow.type); + + await checkWindow({ + to: ["Holmes and Watson <Tenants221B>"], + subject: "Test", + }); + + [tab] = await browser.tabs.query({ windowId: createdWindow.id }); + + let listener8 = (tab, details) => { + listener8.tab = tab; + listener8.details = details; + }; + browser.compose.onBeforeSend.addListener(listener8); + await beginSend(); + browser.test.assertEq(tab.id, listener8.tab.id, "listener8 was fired"); + browser.test.assertEq(1, listener8.details.to.length); + browser.test.assertEq( + "Holmes and Watson <Tenants221B>", + listener8.details.to[0], + "listener8 recipient correct" + ); + browser.test.assertEq( + "Test", + listener8.details.subject, + "listener8 subject correct" + ); + browser.compose.onBeforeSend.removeListener(listener8); + + await browser.addressBooks.delete(addressBook); + browser.test.notifyPass("finished"); + }, + "utils.js": await getUtilsJS(), + }; + let extension = ExtensionTestUtils.loadExtension({ + files, + manifest: { + background: { scripts: ["utils.js", "background.js"] }, + permissions: ["addressBooks", "compose"], + }, + }); + + extension.onMessage("beginSend", async () => { + let composeWindows = [...Services.wm.getEnumerator("msgcompose")]; + is(composeWindows.length, 1); + + composeWindows[0] + .GenericSendMessage(Ci.nsIMsgCompDeliverMode.Later) + .catch(() => { + // This test is ignoring errors thrown by GenericSendMessage, but looks + // at didTryToSendMessage of the mocked CompleteGenericSendMessage to + // check if onBeforeSend aborted the send process. + }); + extension.sendMessage(); + }); + + extension.onMessage("checkWindow", async expected => { + await checkComposeHeaders(expected); + extension.sendMessage(); + }); + + await extension.startup(); + await extension.awaitFinish("finished"); + await extension.unload(); + + await messagesInOutbox(2); + + let outboxMessages = [...outbox.messages]; + ok(outboxMessages.length > 0); + let sentMessage7 = outboxMessages.shift(); + is(sentMessage7.subject, "Changed by listener7", "subject was changed"); + is( + sentMessage7.recipients, + "Sherlock Holmes <sherlock@bakerstreet.invalid>, John Watson <john@bakerstreet.invalid>", + "list in unchanged field was expanded" + ); + is( + sentMessage7.bccList, + "Sherlock Holmes <sherlock@bakerstreet.invalid>, John Watson <john@bakerstreet.invalid>", + "list in changed field was expanded" + ); + + ok(outboxMessages.length > 0); + let sentMessage8 = outboxMessages.shift(); + is(sentMessage8.subject, "Test", "subject was not changed"); + is( + sentMessage8.recipients, + "Sherlock Holmes <sherlock@bakerstreet.invalid>, John Watson <john@bakerstreet.invalid>", + "list in unchanged field was expanded" + ); + + ok(outboxMessages.length == 0); + + await new Promise(resolve => { + outbox.deleteMessages( + [sentMessage7, sentMessage8], + null, + true, + false, + { OnStopCopy: resolve }, + false + ); + }); +}); + +add_task(async function testMultipleListeners() { + let extensionA = ExtensionTestUtils.loadExtension({ + background: async () => { + let listener9 = (tab, details) => { + browser.test.log("listener9 was fired"); + browser.test.sendMessage("listener9", details); + browser.compose.onBeforeSend.removeListener(listener9); + return { + details: { + to: ["recipient2@invalid"], + subject: "Changed by listener9", + }, + }; + }; + browser.compose.onBeforeSend.addListener(listener9); + + await browser.compose.beginNew({ + to: "recipient1@invalid", + subject: "Initial subject", + }); + browser.test.sendMessage("ready"); + }, + manifest: { permissions: ["compose"] }, + }); + + let extensionB = ExtensionTestUtils.loadExtension({ + background: async () => { + let listener10 = (tab, details) => { + browser.test.log("listener10 was fired"); + browser.test.sendMessage("listener10", details); + browser.compose.onBeforeSend.removeListener(listener10); + return { + details: { + to: ["recipient3@invalid"], + subject: "Changed by listener10", + }, + }; + }; + browser.compose.onBeforeSend.addListener(listener10); + + let listener11 = (tab, details) => { + browser.test.log("listener11 was fired"); + browser.test.sendMessage("listener11", details); + browser.compose.onBeforeSend.removeListener(listener11); + return { + details: { + to: ["recipient4@invalid"], + subject: "Changed by listener11", + }, + }; + }; + browser.compose.onBeforeSend.addListener(listener11); + browser.test.sendMessage("ready"); + }, + manifest: { permissions: ["compose"] }, + }); + + await extensionA.startup(); + await extensionB.startup(); + + await extensionA.awaitMessage("ready"); + await extensionB.awaitMessage("ready"); + + let composeWindows = [...Services.wm.getEnumerator("msgcompose")]; + Assert.equal(composeWindows.length, 1); + Assert.equal(composeWindows[0].document.readyState, "complete"); + composeWindows[0] + .GenericSendMessage(Ci.nsIMsgCompDeliverMode.Later) + .catch(() => { + // This test is ignoring errors thrown by GenericSendMessage, but looks + // at didTryToSendMessage of the mocked CompleteGenericSendMessage to + // check if onBeforeSend aborted the send process. + }); + + let listener9Details = await extensionA.awaitMessage("listener9"); + Assert.equal(listener9Details.to.length, 1); + Assert.equal( + listener9Details.to[0], + "recipient1@invalid", + "listener9 recipient correct" + ); + Assert.equal( + listener9Details.subject, + "Initial subject", + "listener9 subject correct" + ); + + let listener10Details = await extensionB.awaitMessage("listener10"); + Assert.equal(listener10Details.to.length, 1); + Assert.equal( + listener10Details.to[0], + "recipient2@invalid", + "listener10 recipient correct" + ); + Assert.equal( + listener10Details.subject, + "Changed by listener9", + "listener10 subject correct" + ); + + let listener11Details = await extensionB.awaitMessage("listener11"); + Assert.equal(listener11Details.to.length, 1); + Assert.equal( + listener11Details.to[0], + "recipient3@invalid", + "listener11 recipient correct" + ); + Assert.equal( + listener11Details.subject, + "Changed by listener10", + "listener11 subject correct" + ); + + await extensionA.unload(); + await extensionB.unload(); + + await messagesInOutbox(1); + + let outboxMessages = [...outbox.messages]; + Assert.ok(outboxMessages.length > 0); + let sentMessage = outboxMessages.shift(); + Assert.equal( + sentMessage.subject, + "Changed by listener11", + "subject was changed" + ); + Assert.equal( + sentMessage.recipients, + "recipient4@invalid", + "recipient was changed" + ); + + Assert.ok(outboxMessages.length == 0); + + await new Promise(resolve => { + outbox.deleteMessages( + [sentMessage], + null, + true, + false, + { OnStopCopy: resolve }, + false + ); + }); +}); + +add_task(async function test_MV3_event_pages() { + let files = { + "background.js": async () => { + // Whenever the extension starts or wakes up, hasFired is set to false. In + // case of a wake-up, the first fired event is the one that woke up the background. + let hasFired = false; + + browser.compose.onBeforeSend.addListener((tab, details) => { + // Only send the first event after background wake-up, this should be + // the only one expected. + if (!hasFired) { + hasFired = true; + browser.test.sendMessage("onBeforeSend received", details); + } + + // Let us abort, so we do not have to re-open the compose window for + // multiple tests. + return { + cancel: true, + }; + }); + + browser.test.sendMessage("background started"); + }, + "utils.js": await getUtilsJS(), + }; + let extension = ExtensionTestUtils.loadExtension({ + files, + manifest: { + manifest_version: 3, + background: { scripts: ["utils.js", "background.js"] }, + permissions: ["compose"], + browser_specific_settings: { + gecko: { id: "compose.onBeforeSend@xpcshell.test" }, + }, + }, + }); + + function checkPersistentListeners({ primed }) { + // A persistent event is referenced by its moduleName as defined in + // ext-mails.json, not by its actual namespace. + const persistent_events = ["compose.onBeforeSend"]; + + for (let event of persistent_events) { + let [moduleName, eventName] = event.split("."); + assertPersistentListeners(extension, moduleName, eventName, { + primed, + }); + } + } + + function beginSend() { + composeWindow.GenericSendMessage(Ci.nsIMsgCompDeliverMode.Now).catch(() => { + // This test is ignoring errors thrown by GenericSendMessage, but looks + // at didTryToSendMessage of the mocked CompleteGenericSendMessage to + // check if onBeforeSend aborted the send process. + }); + } + + let composeWindow = await openComposeWindow(account); + await focusWindow(composeWindow); + + await extension.startup(); + await extension.awaitMessage("background started"); + // The listeners should be persistent, but not primed. + checkPersistentListeners({ primed: false }); + + // Trigger onBeforeSend without terminating the background first. + + composeWindow.SetComposeDetails({ to: "first@invalid.net" }); + beginSend(); + let firstDetails = await extension.awaitMessage("onBeforeSend received"); + Assert.equal( + "first@invalid.net", + firstDetails.to, + "Returned details should be correct" + ); + + // Terminate background and re-trigger onBeforeSend. + + await extension.terminateBackground({ disableResetIdleForTest: true }); + // The listeners should be primed. + checkPersistentListeners({ primed: true }); + + composeWindow.SetComposeDetails({ to: "second@invalid.net" }); + beginSend(); + let secondDetails = await extension.awaitMessage("onBeforeSend received"); + Assert.equal( + "second@invalid.net", + secondDetails.to, + "Returned details should be correct" + ); + + // The background should have been restarted. + await extension.awaitMessage("background started"); + // The listener should no longer be primed. + checkPersistentListeners({ primed: false }); + + await extension.unload(); + composeWindow.close(); +}); |