diff options
Diffstat (limited to '')
11 files changed, 610 insertions, 0 deletions
diff --git a/comm/mail/components/cloudfile/cloudFileAccounts.jsm b/comm/mail/components/cloudfile/cloudFileAccounts.jsm new file mode 100644 index 0000000000..3cb478f60f --- /dev/null +++ b/comm/mail/components/cloudfile/cloudFileAccounts.jsm @@ -0,0 +1,215 @@ +/* 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/. */ + +const EXPORTED_SYMBOLS = ["cloudFileAccounts"]; + +var ACCOUNT_ROOT = "mail.cloud_files.accounts."; + +var { EventEmitter } = ChromeUtils.importESModule( + "resource://gre/modules/EventEmitter.sys.mjs" +); + +var cloudFileAccounts = new (class extends EventEmitter { + get constants() { + return { + offlineErr: 0x80550014, // NS_MSG_ERROR_OFFLINE + authErr: 0x8055001e, // NS_MSG_USER_NOT_AUTHENTICATED + uploadErr: 0x8055311a, // NS_MSG_ERROR_ATTACHING_FILE + uploadWouldExceedQuota: 0x8055311b, + uploadExceedsFileLimit: 0x8055311c, + uploadCancelled: 0x8055311d, + uploadErrWithCustomMessage: 0x8055311f, + renameErr: 0x80553120, + renameErrWithCustomMessage: 0x80553121, + renameNotSupported: 0x80553122, + deleteErr: 0x80553123, + attachmentErr: 0x80553124, + accountErr: 0x80553125, + }; + } + + constructor() { + super(); + this._providers = new Map(); + this._accounts = new Map(); + this._highestOrdinal = 0; + } + + get _accountKeys() { + let accountKeySet = new Set(); + let branch = Services.prefs.getBranch(ACCOUNT_ROOT); + let children = branch.getChildList(""); + for (let child of children) { + let subbranch = child.substr(0, child.indexOf(".")); + accountKeySet.add(subbranch); + + let match = /^account(\d+)$/.exec(subbranch); + if (match) { + let ordinal = parseInt(match[1], 10); + this._highestOrdinal = Math.max(this._highestOrdinal, ordinal); + } + } + + // TODO: sort by ordinal + return accountKeySet.keys(); + } + + /** + * Ensure that we have the account key for an account. If we already have the + * key, just return it. If we have the account, get the key from it. + * + * @param aKeyOrAccount the key or the account object + * @returns the account key + */ + _ensureKey(aKeyOrAccount) { + if (typeof aKeyOrAccount == "string") { + return aKeyOrAccount; + } + if ("accountKey" in aKeyOrAccount) { + return aKeyOrAccount.accountKey; + } + throw new Error("String or cloud file account expected"); + } + + /** + * Register a cloudfile provider, e.g. from an extension. + * + * @param {object} The implementation to register + */ + registerProvider(aType, aProvider) { + if (this._providers.has(aType)) { + throw new Error(`Cloudfile provider ${aType} is already registered`); + } + this._providers.set(aType, aProvider); + this.emit("providerRegistered", aProvider); + } + + /** + * Unregister a cloudfile provider. + * + * @param {string} aType - The provider type to unregister + */ + unregisterProvider(aType) { + if (!this._providers.has(aType)) { + throw new Error(`Cloudfile provider ${aType} is not registered`); + } + + for (let account of this.getAccountsForType(aType)) { + this._accounts.delete(account.accountKey); + } + + this._providers.delete(aType); + this.emit("providerUnregistered", aType); + } + + get providers() { + return [...this._providers.values()]; + } + + getProviderForType(aType) { + return this._providers.get(aType); + } + + createAccount(aType) { + this._highestOrdinal++; + let key = "account" + this._highestOrdinal; + + try { + let provider = this.getProviderForType(aType); + let account = provider.initAccount(key); + + Services.prefs.setCharPref(ACCOUNT_ROOT + key + ".type", aType); + Services.prefs.setCharPref( + ACCOUNT_ROOT + key + ".displayName", + account.displayName + ); + + this._accounts.set(key, account); + this.emit("accountAdded", account); + return account; + } catch (e) { + for (let prefName of Services.prefs.getChildList( + `${ACCOUNT_ROOT}${key}.` + )) { + Services.prefs.clearUserPref(prefName); + } + throw e; + } + } + + removeAccount(aKeyOrAccount) { + let key = this._ensureKey(aKeyOrAccount); + let type = Services.prefs.getCharPref(ACCOUNT_ROOT + key + ".type"); + + this._accounts.delete(key); + for (let prefName of Services.prefs.getChildList( + `${ACCOUNT_ROOT}${key}.` + )) { + Services.prefs.clearUserPref(prefName); + } + + this.emit("accountDeleted", key, type); + } + + get accounts() { + let arr = []; + for (let key of this._accountKeys) { + let account = this.getAccount(key); + if (account) { + arr.push(account); + } + } + return arr; + } + + get configuredAccounts() { + return this.accounts.filter(account => account.configured); + } + + getAccount(aKey) { + if (this._accounts.has(aKey)) { + return this._accounts.get(aKey); + } + + let type = Services.prefs.getCharPref(ACCOUNT_ROOT + aKey + ".type", ""); + if (type) { + let provider = this.getProviderForType(type); + if (provider) { + let account = provider.initAccount(aKey); + this._accounts.set(aKey, account); + return account; + } + } + return null; + } + + getAccountsForType(aType) { + let result = []; + + for (let accountKey of this._accountKeys) { + let type = Services.prefs.getCharPref( + ACCOUNT_ROOT + accountKey + ".type" + ); + if (type === aType) { + result.push(this.getAccount(accountKey)); + } + } + + return result; + } + + getDisplayName(aKeyOrAccount) { + // If no display name has been set, we return the empty string. + let key = this._ensureKey(aKeyOrAccount); + return Services.prefs.getCharPref(ACCOUNT_ROOT + key + ".displayName", ""); + } + + setDisplayName(aKeyOrAccount, aDisplayName) { + let key = this._ensureKey(aKeyOrAccount); + Services.prefs.setCharPref( + ACCOUNT_ROOT + key + ".displayName", + aDisplayName + ); + } +})(); diff --git a/comm/mail/components/cloudfile/content/selectDialog.js b/comm/mail/components/cloudfile/content/selectDialog.js new file mode 100644 index 0000000000..4c49d11aa3 --- /dev/null +++ b/comm/mail/components/cloudfile/content/selectDialog.js @@ -0,0 +1,17 @@ +/* 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/. */ + +/* import-globals-from ../../../../../toolkit/components/prompts/content/selectDialog.js */ + +function cloudfileDialogOnLoad() { + let icons = propBag.getProperty("icons"); + let listItems = listBox.itemChildren; + for (let i = 0; i < listItems.length; i++) { + listItems[i].setAttribute("align", "center"); + let image = document.createElement("img"); + image.setAttribute("src", icons[i]); + image.setAttribute("alt", ""); + listItems[i].insertBefore(image, listItems[i].firstElementChild); + } +} diff --git a/comm/mail/components/cloudfile/content/selectDialog.xhtml b/comm/mail/components/cloudfile/content/selectDialog.xhtml new file mode 100644 index 0000000000..3f99fea350 --- /dev/null +++ b/comm/mail/components/cloudfile/content/selectDialog.xhtml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/cloudfileSelectDialog.css" type="text/css"?> +<!DOCTYPE window> + +<window + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="cloudfileDialogOnLoad();" +> + <dialog> + <script + type="application/javascript" + src="chrome://messenger/content/cloudfile/selectDialog.js" + /> + <script + type="application/javascript" + src="chrome://global/content/selectDialog.js" + /> + <keyset id="dialogKeys" /> + <vbox style="width: 24em; margin: 5px"> + <label id="info.txt" /> + <vbox> + <richlistbox id="list" class="theme-listbox" style="height: 8em" /> + </vbox> + </vbox> + </dialog> +</window> diff --git a/comm/mail/components/cloudfile/jar.mn b/comm/mail/components/cloudfile/jar.mn new file mode 100644 index 0000000000..ad4eeb3065 --- /dev/null +++ b/comm/mail/components/cloudfile/jar.mn @@ -0,0 +1,7 @@ +# 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/. + +messenger.jar: + content/messenger/cloudfile/selectDialog.js (content/selectDialog.js) + content/messenger/cloudfile/selectDialog.xhtml (content/selectDialog.xhtml) diff --git a/comm/mail/components/cloudfile/moz.build b/comm/mail/components/cloudfile/moz.build new file mode 100644 index 0000000000..f456f7ad85 --- /dev/null +++ b/comm/mail/components/cloudfile/moz.build @@ -0,0 +1,14 @@ +# vim: set filetype=python: +# 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/. + +EXTRA_JS_MODULES += [ + "cloudFileAccounts.jsm", +] + +JAR_MANIFESTS += ["jar.mn"] + +BROWSER_CHROME_MANIFESTS += [ + "test/browser/browser.ini", +] diff --git a/comm/mail/components/cloudfile/test/browser/browser.ini b/comm/mail/components/cloudfile/test/browser/browser.ini new file mode 100644 index 0000000000..8f4b1a954a --- /dev/null +++ b/comm/mail/components/cloudfile/test/browser/browser.ini @@ -0,0 +1,13 @@ +[DEFAULT] +head = head.js +prefs = + mail.provider.suppress_dialog_on_startup=true + mail.spotlight.firstRunDone=true + mail.winsearch.firstRunDone=true + mailnews.start_page.override_url=about:blank + mailnews.start_page.url=about:blank +subsuite = thunderbird +support-files = files/icon.svg files/management.html + +[browser_repeat_upload.js] +support-files = files/green_eggs.txt diff --git a/comm/mail/components/cloudfile/test/browser/browser_repeat_upload.js b/comm/mail/components/cloudfile/test/browser/browser_repeat_upload.js new file mode 100644 index 0000000000..7390354a8c --- /dev/null +++ b/comm/mail/components/cloudfile/test/browser/browser_repeat_upload.js @@ -0,0 +1,246 @@ +/* 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/. */ + +/* import-globals-from ../../../../base/content/mailWindowOverlay.js */ + +let { cloudFileAccounts } = ChromeUtils.import( + "resource:///modules/cloudFileAccounts.jsm" +); + +const ICON_URL = getRootDirectory(gTestPath) + "files/icon.svg"; +const MANAGEMENT_URL = getRootDirectory(gTestPath) + "files/management.html"; + +function getFileFromChromeURL(leafName) { + let ChromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( + Ci.nsIChromeRegistry + ); + + let url = Services.io.newURI( + getRootDirectory(gTestPath) + "files/" + leafName + ); + let fileURL = ChromeRegistry.convertChromeURL(url).QueryInterface( + Ci.nsIFileURL + ); + return fileURL.file; +} + +add_task(async () => { + let uploadedFiles = []; + let provider = { + type: "Mochitest", + displayName: "Mochitest", + iconURL: ICON_URL, + initAccount(accountKey) { + return { + accountKey, + type: "Mochitest", + get displayName() { + return Services.prefs.getCharPref( + `mail.cloud_files.accounts.${this.accountKey}.displayName`, + "Mochitest Account" + ); + }, + getPreviousUploads() { + return uploadedFiles; + }, + urlForFile(file) { + return "https://mochi.test/" + file.leafName; + }, + iconURL: ICON_URL, + configured: true, + managementURL: MANAGEMENT_URL, + reuseUploads: true, + }; + }, + }; + + Assert.equal( + cloudFileAccounts.configuredAccounts.length, + 0, + "Should have no cloudfile accounts starting off." + ); + + cloudFileAccounts.registerProvider(provider.type, provider); + let account = cloudFileAccounts.createAccount(provider.type); + Assert.equal( + cloudFileAccounts.configuredAccounts.length, + 1, + "Should have only the one account we created." + ); + + let composeWindowPromise = BrowserTestUtils.domWindowOpened(); + MsgNewMessage(); + let composeWindow = await composeWindowPromise; + await BrowserTestUtils.waitForEvent(composeWindow, "compose-editor-ready"); + await TestUtils.waitForCondition( + () => Services.focus.activeWindow == composeWindow + ); + let composeDocument = composeWindow.document; + + // Compose window loaded. + // Check the attach dropdown has our account as a <menuitem>. + + let toolbarButton = composeDocument.getElementById("button-attach"); + let rect = toolbarButton.getBoundingClientRect(); + EventUtils.synthesizeMouse( + toolbarButton, + rect.width - 5, + 5, + { clickCount: 1 }, + composeWindow + ); + await promiseAnimationFrame(composeWindow); + + let menu = composeDocument.getElementById( + "button-attachPopup_attachCloudMenu" + ); + ok(!BrowserTestUtils.is_hidden(menu)); + + let popupshown = BrowserTestUtils.waitForEvent(menu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(menu, { clickCount: 1 }, composeWindow); + await popupshown; + + Assert.equal( + cloudFileAccounts.configuredAccounts.length, + 1, + "Should still have one registered account." + ); + + let menuitems = menu.menupopup.children; + is(menuitems.length, 1); + is(menuitems[0].getAttribute("image"), ICON_URL); + is(menuitems[0].getAttribute("label"), "Mochitest Account\u2026"); + + composeDocument.getElementById("button-attachPopup").hidePopup(); + + // Pretend we uploaded some files before. + + uploadedFiles = [ + { + id: 1, + name: "green_eggs.txt", + path: getFileFromChromeURL("green_eggs.txt").path, + size: 30, + url: "https://mochi.test/green_eggs.txt", + serviceName: "MyCloud", + serviceIcon: "chrome://messenger/skin/icons/globe.svg", + }, + { + id: 2, + name: "ham.zip", + path: getFileFromChromeURL("ham.zip").path, + size: 1234, + url: "https://mochi.test/ham.zip", + }, + ]; + is(account.getPreviousUploads().length, 2); + + // Check the attach dropdown has our account as a <menu>. + + await new Promise(resolve => { + toolbarButton.addEventListener("popupshown", resolve, { once: true }); + EventUtils.synthesizeMouse( + toolbarButton, + rect.width - 5, + 5, + { clickCount: 1 }, + composeWindow + ); + }); + info("toolbar button menu opened"); + await promiseAnimationFrame(composeWindow); + + await new Promise(resolve => { + menu.menupopup.addEventListener("popupshown", resolve, { once: true }); + EventUtils.synthesizeMouseAtCenter(menu, { clickCount: 1 }, composeWindow); + }); + info("file link menu opened"); + await promiseAnimationFrame(composeWindow); + + menuitems = menu.menupopup.children; + is(menuitems.length, 2); + is(menuitems[0].getAttribute("image"), ICON_URL); + is(menuitems[0].getAttribute("label"), "Mochitest Account\u2026"); + is(menuitems[1].localName, "menuitem"); + is(menuitems[1].getAttribute("image"), "moz-icon://green_eggs.txt"); + is(menuitems[1].getAttribute("label"), "green_eggs.txt"); + // TODO: Enable this when we handle files that no longer exist on the filesystem. + // is(menuitems[2].localName, "menuitem"); + // is(menuitems[2].getAttribute("image"), "moz-icon://ham.zip"); + // is(menuitems[2].getAttribute("label"), "ham.zip"); + + // Select one of the previously-uploaded items and check the attachment is added. + + let bucket = composeDocument.getElementById("attachmentBucket"); + await new Promise(resolve => { + bucket.addEventListener("attachments-added", resolve, { once: true }); + menu.menupopup.activateItem(menuitems[1]); + }); + info("attachment added"); + await promiseAnimationFrame(composeWindow); + ok(toolbarButton.open === false); + + is(bucket.itemCount, 1); + let attachment = bucket.itemChildren[0]; + is(attachment.getAttribute("name"), "green_eggs.txt"); + ok(attachment.attachment.sendViaCloud); + is(attachment.attachment.cloudFileAccountKey, account.accountKey); + is( + attachment.attachment.contentLocation, + "https://mochi.test/green_eggs.txt" + ); + + is( + attachment.querySelector("img.attachmentcell-icon").src, + uploadedFiles[0].serviceIcon, + "CloudFile icon should be correct." + ); + + // Check the content of the editor for the added template. + let editor = composeWindow.GetCurrentEditor(); + let urls = editor.document.querySelectorAll( + "body > #cloudAttachmentListRoot > #cloudAttachmentList" + ); + Assert.equal(urls.length, 1, "Found 1 FileLink template in the document."); + + // Template is added asynchronously. + await TestUtils.waitForCondition(() => urls[0].querySelector("li")); + Assert.equal( + urls[0].querySelector(".cloudfile-name").textContent, + "green_eggs.txt", + "The name of the cloud file in the template should be correct." + ); + + Assert.equal( + urls[0].querySelector(".cloudfile-name").href, + "https://mochi.test/green_eggs.txt", + "The URL attached to the name of the cloud file in the template should be correct." + ); + + Assert.equal( + urls[0].querySelector(".cloudfile-service-name").textContent, + "MyCloud", + "The used service name in the template should be correct." + ); + + Assert.equal( + urls[0].querySelector(".cloudfile-service-icon").src, + "data:image/svg+xml;filename=globe.svg;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgZD0iTTggMGE4IDggMCAxIDAgOCA4IDguMDA5IDguMDA5IDAgMCAwLTgtOHptNS4xNjMgNC45NThoLTEuNTUyYTcuNyA3LjcgMCAwIDAtMS4wNTEtMi4zNzYgNi4wMyA2LjAzIDAgMCAxIDIuNjAzIDIuMzc2ek0xNCA4YTUuOTYzIDUuOTYzIDAgMCAxLS4zMzUgMS45NThoLTEuODIxQTEyLjMyNyAxMi4zMjcgMCAwIDAgMTIgOGExMi4zMjcgMTIuMzI3IDAgMCAwLS4xNTYtMS45NThoMS44MjFBNS45NjMgNS45NjMgMCAwIDEgMTQgOHptLTYgNmMtMS4wNzUgMC0yLjAzNy0xLjItMi41NjctMi45NThoNS4xMzVDMTAuMDM3IDEyLjggOS4wNzUgMTQgOCAxNHpNNS4xNzQgOS45NThhMTEuMDg0IDExLjA4NCAwIDAgMSAwLTMuOTE2aDUuNjUxQTExLjExNCAxMS4xMTQgMCAwIDEgMTEgOGExMS4xMTQgMTEuMTE0IDAgMCAxLS4xNzQgMS45NTh6TTIgOGE1Ljk2MyA1Ljk2MyAwIDAgMSAuMzM1LTEuOTU4aDEuODIxYTEyLjM2MSAxMi4zNjEgMCAwIDAgMCAzLjkxNkgyLjMzNUE1Ljk2MyA1Ljk2MyAwIDAgMSAyIDh6bTYtNmMxLjA3NSAwIDIuMDM3IDEuMiAyLjU2NyAyLjk1OEg1LjQzM0M1Ljk2MyAzLjIgNi45MjUgMiA4IDJ6bS0yLjU2LjU4MmE3LjcgNy43IDAgMCAwLTEuMDUxIDIuMzc2SDIuODM3QTYuMDMgNi4wMyAwIDAgMSA1LjQ0IDIuNTgyem0tMi42IDguNDZoMS41NDlhNy43IDcuNyAwIDAgMCAxLjA1MSAyLjM3NiA2LjAzIDYuMDMgMCAwIDEtMi42MDMtMi4zNzZ6bTcuNzIzIDIuMzc2YTcuNyA3LjcgMCAwIDAgMS4wNTEtMi4zNzZoMS41NTJhNi4wMyA2LjAzIDAgMCAxLTIuNjA2IDIuMzc2eiI+PC9wYXRoPgo8L3N2Zz4K", + "The used service icon should be correct." + ); + + // clean up + cloudFileAccounts.removeAccount(account); + cloudFileAccounts.unregisterProvider(provider.type); + Assert.equal( + cloudFileAccounts.configuredAccounts.length, + 0, + "Should leave no cloudfile accounts when done" + ); + composeWindow.close(); + + // Request focus on something in the main window so the test doesn't time + // out waiting for focus. + document.getElementById("button-appmenu").focus(); +}); diff --git a/comm/mail/components/cloudfile/test/browser/files/green_eggs.txt b/comm/mail/components/cloudfile/test/browser/files/green_eggs.txt new file mode 100644 index 0000000000..058318befb --- /dev/null +++ b/comm/mail/components/cloudfile/test/browser/files/green_eggs.txt @@ -0,0 +1 @@ +I do not like them, Sam I Am! diff --git a/comm/mail/components/cloudfile/test/browser/files/icon.svg b/comm/mail/components/cloudfile/test/browser/files/icon.svg new file mode 100644 index 0000000000..6c1a552445 --- /dev/null +++ b/comm/mail/components/cloudfile/test/browser/files/icon.svg @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"> + <circle cx="8" cy="8" r="7.5" fill="#ffffff" stroke="#00aa00" stroke-width="1.5"/> + <circle cx="5" cy="6" r="1.5" fill="#00aa00"/> + <circle cx="11" cy="6" r="1.5" fill="#00aa00"/> + <path d="M 12.83,9.30 C 12.24,11.48 10.26,13 8,13 5.75,13 3.74,11.48 3.17,9.29" fill="none" stroke="#00aa00" stroke-width="1.5"/> +</svg> diff --git a/comm/mail/components/cloudfile/test/browser/files/management.html b/comm/mail/components/cloudfile/test/browser/files/management.html new file mode 100644 index 0000000000..5a51891fd7 --- /dev/null +++ b/comm/mail/components/cloudfile/test/browser/files/management.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"/> + <title></title> +</head> +<body> + +</body> +</html> diff --git a/comm/mail/components/cloudfile/test/browser/head.js b/comm/mail/components/cloudfile/test/browser/head.js new file mode 100644 index 0000000000..3dd17de883 --- /dev/null +++ b/comm/mail/components/cloudfile/test/browser/head.js @@ -0,0 +1,48 @@ +/* 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 { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +add_setup(async function () { + let gAccount = createAccount(); + addIdentity(gAccount); + let rootFolder = gAccount.incomingServer.rootFolder; + + let about3Pane = document.getElementById("tabmail").currentAbout3Pane; + about3Pane.displayFolder(rootFolder.URI); + await new Promise(resolve => executeSoon(resolve)); +}); + +function createAccount() { + registerCleanupFunction(() => { + MailServices.accounts.accounts.forEach(cleanUpAccount); + }); + + MailServices.accounts.createLocalMailAccount(); + let account = MailServices.accounts.accounts[0]; + info(`Created account ${account.toString()}`); + + return account; +} + +function cleanUpAccount(account) { + info(`Cleaning up account ${account.toString()}`); + MailServices.accounts.removeAccount(account, true); +} + +function addIdentity(account) { + let identity = MailServices.accounts.createIdentity(); + identity.email = "mochitest@localhost"; + account.addIdentity(identity); + account.defaultIdentity = identity; + info(`Created identity ${identity.toString()}`); +} + +async function promiseAnimationFrame(win = window) { + await new Promise(win.requestAnimationFrame); + // dispatchToMainThread throws if used as the first argument of Promise. + return new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); +} |