Bug 1857034 - Reorganize ext-mails.js into sys.mjs modules. r=mkmelin
Depends on D189667 Differential Revision: https://phabricator.services.mozilla.com/D190092 --HG-- rename : mail/components/extensions/parent/ext-mail.js => mail/components/extensions/ExtensionAccounts.sys.mjs rename : mail/components/extensions/parent/ext-mail.js => mail/components/extensions/ExtensionMessages.sys.mjs extra : moz-landing-system : lando
This commit is contained in:
Родитель
23e1d15666
Коммит
7a202b1085
|
@ -0,0 +1,221 @@
|
|||
/* 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"
|
||||
);
|
||||
|
||||
/**
|
||||
* Converts an nsIMsgAccount to a simple object
|
||||
*
|
||||
* @param {nsIMsgAccount} account
|
||||
* @returns {object}
|
||||
*/
|
||||
export function convertAccount(account, includeFolders = true) {
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
account = account.QueryInterface(Ci.nsIMsgAccount);
|
||||
let server = account.incomingServer;
|
||||
if (server.type == "im") {
|
||||
return null;
|
||||
}
|
||||
|
||||
let folders = null;
|
||||
if (includeFolders) {
|
||||
folders = traverseSubfolders(
|
||||
account.incomingServer.rootFolder,
|
||||
account.key
|
||||
).subFolders;
|
||||
}
|
||||
|
||||
return {
|
||||
id: account.key,
|
||||
name: account.incomingServer.prettyName,
|
||||
type: account.incomingServer.type,
|
||||
folders,
|
||||
identities: account.identities.map(identity =>
|
||||
convertMailIdentity(account, identity)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an nsIMsgIdentity to a simple object for use in messages.
|
||||
*
|
||||
* @param {nsIMsgAccount} account
|
||||
* @param {nsIMsgIdentity} identity
|
||||
* @returns {object}
|
||||
*/
|
||||
export function convertMailIdentity(account, identity) {
|
||||
if (!account || !identity) {
|
||||
return null;
|
||||
}
|
||||
identity = identity.QueryInterface(Ci.nsIMsgIdentity);
|
||||
return {
|
||||
accountId: account.key,
|
||||
id: identity.key,
|
||||
label: identity.label || "",
|
||||
name: identity.fullName || "",
|
||||
email: identity.email || "",
|
||||
replyTo: identity.replyTo || "",
|
||||
organization: identity.organization || "",
|
||||
composeHtml: identity.composeHtml,
|
||||
signature: identity.htmlSigText || "",
|
||||
signatureIsPlainText: !identity.htmlSigFormat,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The following functions turn nsIMsgFolder references into more human-friendly forms.
|
||||
* A folder can be referenced with the account key, and the path to the folder in that account.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a folder URI to a human-friendly path.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export function folderURIToPath(accountId, uri) {
|
||||
let server = MailServices.accounts.getAccount(accountId).incomingServer;
|
||||
let rootURI = server.rootFolder.URI;
|
||||
if (rootURI == uri) {
|
||||
return "/";
|
||||
}
|
||||
// The .URI property of an IMAP folder doesn't have %-encoded characters, but
|
||||
// may include literal % chars. Services.io.newURI(uri) applies encodeURI to
|
||||
// the returned filePath, but will not encode any literal % chars, which will
|
||||
// cause decodeURIComponent to fail (bug 1707408).
|
||||
if (server.type == "imap") {
|
||||
return uri.substring(rootURI.length);
|
||||
}
|
||||
let path = Services.io.newURI(uri).filePath;
|
||||
return path.split("/").map(decodeURIComponent).join("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a human-friendly path to a folder URI. This function does not assume
|
||||
* that the folder referenced exists.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export function folderPathToURI(accountId, path) {
|
||||
let server = MailServices.accounts.getAccount(accountId).incomingServer;
|
||||
let rootURI = server.rootFolder.URI;
|
||||
if (path == "/") {
|
||||
return rootURI;
|
||||
}
|
||||
// The .URI property of an IMAP folder doesn't have %-encoded characters.
|
||||
// If encoded here, the folder lookup service won't find the folder.
|
||||
if (server.type == "imap") {
|
||||
return rootURI + path;
|
||||
}
|
||||
return (
|
||||
rootURI +
|
||||
path
|
||||
.split("/")
|
||||
.map(p =>
|
||||
encodeURIComponent(p)
|
||||
.replace(/[!'()*]/g, c => "%" + c.charCodeAt(0).toString(16))
|
||||
// We do not encode "+" chars in folder URIs. Manually convert them
|
||||
// back to literal + chars, otherwise folder lookup will fail.
|
||||
.replaceAll("%2B", "+")
|
||||
)
|
||||
.join("/")
|
||||
);
|
||||
}
|
||||
|
||||
const folderTypeMap = new Map([
|
||||
[Ci.nsMsgFolderFlags.Inbox, "inbox"],
|
||||
[Ci.nsMsgFolderFlags.Drafts, "drafts"],
|
||||
[Ci.nsMsgFolderFlags.SentMail, "sent"],
|
||||
[Ci.nsMsgFolderFlags.Trash, "trash"],
|
||||
[Ci.nsMsgFolderFlags.Templates, "templates"],
|
||||
[Ci.nsMsgFolderFlags.Archive, "archives"],
|
||||
[Ci.nsMsgFolderFlags.Junk, "junk"],
|
||||
[Ci.nsMsgFolderFlags.Queue, "outbox"],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Converts an nsIMsgFolder to a simple object for use in API messages.
|
||||
*
|
||||
* @param {nsIMsgFolder} folder - The folder to convert.
|
||||
* @param {string} [accountId] - An optimization to avoid looking up the
|
||||
* account. The value from nsIMsgDBHdr.accountKey must not be used here.
|
||||
* @returns {MailFolder}
|
||||
* @see mail/components/extensions/schemas/folders.json
|
||||
*/
|
||||
export function convertFolder(folder, accountId) {
|
||||
if (!folder) {
|
||||
return null;
|
||||
}
|
||||
if (!accountId) {
|
||||
let server = folder.server;
|
||||
let account = MailServices.accounts.FindAccountForServer(server);
|
||||
accountId = account.key;
|
||||
}
|
||||
|
||||
let folderObject = {
|
||||
accountId,
|
||||
name: folder.prettyName,
|
||||
path: folderURIToPath(accountId, folder.URI),
|
||||
};
|
||||
|
||||
let flags = folder.flags;
|
||||
for (let [flag, typeName] of folderTypeMap.entries()) {
|
||||
if (flags & flag) {
|
||||
folderObject.type = typeName;
|
||||
// Exit the loop as soon as an entry was found.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return folderObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an nsIMsgFolder and all its subfolders to a simple object for use in
|
||||
* API messages.
|
||||
*
|
||||
* @param {nsIMsgFolder} folder - The folder to convert.
|
||||
* @param {string} [accountId] - An optimization to avoid looking up the
|
||||
* account. The value from nsIMsgDBHdr.accountKey must not be used here.
|
||||
* @returns {MailFolder}
|
||||
* @see mail/components/extensions/schemas/folders.json
|
||||
*/
|
||||
export function traverseSubfolders(folder, accountId) {
|
||||
let f = convertFolder(folder, accountId);
|
||||
f.subFolders = [];
|
||||
if (folder.hasSubFolders) {
|
||||
// Use the same order as used by Thunderbird.
|
||||
let subFolders = [...folder.subFolders].sort((a, b) =>
|
||||
a.sortOrder == b.sortOrder
|
||||
? a.name.localeCompare(b.name)
|
||||
: a.sortOrder - b.sortOrder
|
||||
);
|
||||
for (let subFolder of subFolders) {
|
||||
f.subFolders.push(
|
||||
traverseSubfolders(subFolder, accountId || f.accountId)
|
||||
);
|
||||
}
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
export class FolderManager {
|
||||
constructor(extension) {
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
convert(folder, accountId) {
|
||||
return convertFolder(folder, accountId);
|
||||
}
|
||||
|
||||
get(accountId, path) {
|
||||
return MailServices.folderLookup.getFolderForURL(
|
||||
folderPathToURI(accountId, path)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,872 @@
|
|||
/* 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 lazy = {};
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";
|
||||
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
|
||||
import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
|
||||
|
||||
import { convertFolder } from "resource:///modules/ExtensionAccounts.sys.mjs";
|
||||
|
||||
var { ExtensionError } = ExtensionUtils;
|
||||
var { MailServices } = ChromeUtils.import(
|
||||
"resource:///modules/MailServices.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"gJunkThreshold",
|
||||
"mail.adaptivefilters.junk_threshold",
|
||||
90
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"gMessagesPerPage",
|
||||
"extensions.webextensions.messagesPerPage",
|
||||
100
|
||||
);
|
||||
|
||||
/**
|
||||
* Class for cached message headers to reduce XPCOM requests and to cache msgHdr
|
||||
* of file and attachment messages.
|
||||
*/
|
||||
export class CachedMsgHeader {
|
||||
constructor(msgHdr) {
|
||||
this.mProperties = {};
|
||||
|
||||
// Properties needed by convertMessage().
|
||||
this.author = null;
|
||||
this.subject = "";
|
||||
this.recipients = null;
|
||||
this.ccList = null;
|
||||
this.bccList = null;
|
||||
this.messageId = null;
|
||||
this.date = 0;
|
||||
this.flags = 0;
|
||||
this.isRead = false;
|
||||
this.isFlagged = false;
|
||||
this.messageSize = 0;
|
||||
this.folder = null;
|
||||
|
||||
// Additional properties.
|
||||
this.accountKey = "";
|
||||
|
||||
if (msgHdr) {
|
||||
// Cache all elements which are needed by convertMessage().
|
||||
this.author = msgHdr.mime2DecodedAuthor;
|
||||
this.subject = msgHdr.mime2DecodedSubject;
|
||||
this.recipients = msgHdr.mime2DecodedRecipients;
|
||||
this.ccList = msgHdr.ccList;
|
||||
this.bccList = msgHdr.bccList;
|
||||
this.messageId = msgHdr.messageId;
|
||||
this.date = msgHdr.date;
|
||||
this.flags = msgHdr.flags;
|
||||
this.isRead = msgHdr.isRead;
|
||||
this.isFlagged = msgHdr.isFlagged;
|
||||
this.messageSize = msgHdr.messageSize;
|
||||
this.folder = msgHdr.folder;
|
||||
|
||||
this.mProperties.junkscore = msgHdr.getStringProperty("junkscore");
|
||||
this.mProperties.keywords = msgHdr.getStringProperty("keywords");
|
||||
|
||||
if (this.folder) {
|
||||
this.messageKey = msgHdr.messageKey;
|
||||
} else {
|
||||
this.mProperties.dummyMsgUrl = msgHdr.getStringProperty("dummyMsgUrl");
|
||||
this.mProperties.dummyMsgLastModifiedTime = msgHdr.getUint32Property(
|
||||
"dummyMsgLastModifiedTime"
|
||||
);
|
||||
}
|
||||
|
||||
// Also cache the additional elements.
|
||||
this.accountKey = msgHdr.accountKey;
|
||||
}
|
||||
}
|
||||
|
||||
getProperty(aProperty) {
|
||||
return this.getStringProperty(aProperty);
|
||||
}
|
||||
setProperty(aProperty, aVal) {
|
||||
return this.setStringProperty(aProperty, aVal);
|
||||
}
|
||||
getStringProperty(aProperty) {
|
||||
if (this.mProperties.hasOwnProperty(aProperty)) {
|
||||
return this.mProperties[aProperty];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
setStringProperty(aProperty, aVal) {
|
||||
this.mProperties[aProperty] = aVal;
|
||||
}
|
||||
getUint32Property(aProperty) {
|
||||
if (this.mProperties.hasOwnProperty(aProperty)) {
|
||||
return parseInt(this.mProperties[aProperty]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
setUint32Property(aProperty, aVal) {
|
||||
this.mProperties[aProperty] = aVal.toString();
|
||||
}
|
||||
markHasAttachments(hasAttachments) {}
|
||||
get mime2DecodedAuthor() {
|
||||
return this.author;
|
||||
}
|
||||
get mime2DecodedSubject() {
|
||||
return this.subject;
|
||||
}
|
||||
get mime2DecodedRecipients() {
|
||||
return this.recipients;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided dummyMsgUrl belongs to an attached message.
|
||||
*/
|
||||
function isAttachedMessageUrl(dummyMsgUrl) {
|
||||
try {
|
||||
return dummyMsgUrl && new URL(dummyMsgUrl).searchParams.has("part");
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of numeric identifiers to messages for easy reference.
|
||||
*
|
||||
* @implements {nsIFolderListener}
|
||||
* @implements {nsIMsgFolderListener}
|
||||
* @implements {nsIObserver}
|
||||
*/
|
||||
export class MessageTracker extends EventEmitter {
|
||||
constructor(windowTracker) {
|
||||
super();
|
||||
this._nextId = 1;
|
||||
this._messages = new Map();
|
||||
this._messageIds = new Map();
|
||||
this._listenerCount = 0;
|
||||
this._pendingKeyChanges = new Map();
|
||||
this._dummyMessageHeaders = new Map();
|
||||
this._windowTracker = windowTracker;
|
||||
|
||||
// nsIObserver
|
||||
Services.obs.addObserver(this, "quit-application-granted");
|
||||
Services.obs.addObserver(this, "attachment-delete-msgkey-changed");
|
||||
// nsIFolderListener
|
||||
MailServices.mailSession.AddFolderListener(
|
||||
this,
|
||||
Ci.nsIFolderListener.propertyFlagChanged |
|
||||
Ci.nsIFolderListener.intPropertyChanged
|
||||
);
|
||||
// nsIMsgFolderListener
|
||||
MailServices.mfn.addListener(
|
||||
this,
|
||||
MailServices.mfn.msgsJunkStatusChanged |
|
||||
MailServices.mfn.msgsDeleted |
|
||||
MailServices.mfn.msgsMoveCopyCompleted |
|
||||
MailServices.mfn.msgKeyChanged
|
||||
);
|
||||
|
||||
this._messageOpenListenerRegistered = false;
|
||||
try {
|
||||
this._windowTracker.addListener("MsgLoaded", this);
|
||||
this._messageOpenListenerRegistered = true;
|
||||
} catch (ex) {
|
||||
// Fails during XPCSHELL tests, which mock the WindowWatcher but do not
|
||||
// implement registerNotification.
|
||||
}
|
||||
}
|
||||
|
||||
// Event handler for MsgLoaded event.
|
||||
handleEvent(event) {
|
||||
let msgHdr = event.detail;
|
||||
// It is not possible to retrieve the dummyMsgHdr of messages opened
|
||||
// from file at a later time, track them manually.
|
||||
if (
|
||||
msgHdr &&
|
||||
!msgHdr.folder &&
|
||||
msgHdr.getStringProperty("dummyMsgUrl").startsWith("file://")
|
||||
) {
|
||||
this.getId(msgHdr);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
// nsIObserver
|
||||
Services.obs.removeObserver(this, "quit-application-granted");
|
||||
Services.obs.removeObserver(this, "attachment-delete-msgkey-changed");
|
||||
// nsIFolderListener
|
||||
MailServices.mailSession.RemoveFolderListener(this);
|
||||
// nsIMsgFolderListener
|
||||
MailServices.mfn.removeListener(this);
|
||||
if (this._messageOpenListenerRegistered) {
|
||||
this._windowTracker.removeListener("MsgLoaded", this);
|
||||
this._messageOpenListenerRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a hash for the given msgIdentifier.
|
||||
*
|
||||
* @param {*} msgIdentifier
|
||||
* @returns {string}
|
||||
*/
|
||||
getHash(msgIdentifier) {
|
||||
if (msgIdentifier.folderURI) {
|
||||
return `folderURI:${msgIdentifier.folderURI}, messageKey: ${msgIdentifier.messageKey}`;
|
||||
}
|
||||
return `dummyMsgUrl:${msgIdentifier.dummyMsgUrl}, dummyMsgLastModifiedTime: ${msgIdentifier.dummyMsgLastModifiedTime}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the provided message identifier to the given messageTracker id.
|
||||
*
|
||||
* @param {integer} id - messageTracker id of the message
|
||||
* @param {*} msgIdentifier - msgIdentifier of the message
|
||||
* @param {nsIMsgDBHdr} [msgHdr] - optional msgHdr of the message, will be
|
||||
* added to the cache if it is a dummy msgHdr (a file or attachment message)
|
||||
*/
|
||||
_set(id, msgIdentifier, msgHdr) {
|
||||
let hash = this.getHash(msgIdentifier);
|
||||
this._messageIds.set(hash, id);
|
||||
this._messages.set(id, msgIdentifier);
|
||||
// Keep track of dummy message headers, which do not have a folder property
|
||||
// and cannot be retrieved later.
|
||||
if (msgHdr && !msgHdr.folder && msgIdentifier.dummyMsgUrl) {
|
||||
this._dummyMessageHeaders.set(
|
||||
msgIdentifier.dummyMsgUrl,
|
||||
msgHdr instanceof Ci.nsIMsgDBHdr ? new CachedMsgHeader(msgHdr) : msgHdr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the messageTracker id for the given message identifier, return null
|
||||
* if not known.
|
||||
*
|
||||
* @param {*} msgIdentifier - msgIdentifier of the message
|
||||
* @returns {integer} The messageTracker id of the message.
|
||||
*/
|
||||
_get(msgIdentifier) {
|
||||
let hash = this.getHash(msgIdentifier);
|
||||
if (this._messageIds.has(hash)) {
|
||||
return this._messageIds.get(hash);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the provided message identifier from the messageTracker.
|
||||
*
|
||||
* @param {*} msgIdentifier - msgIdentifier of the message
|
||||
*/
|
||||
_remove(msgIdentifier) {
|
||||
let hash = this.getHash(msgIdentifier);
|
||||
let id = this._get(msgIdentifier);
|
||||
this._messages.delete(id);
|
||||
this._messageIds.delete(hash);
|
||||
this._dummyMessageHeaders.delete(msgIdentifier.dummyMsgUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a message in the messageTracker or adds it.
|
||||
*
|
||||
* @param {nsIMsgDBHdr} - msgHdr of the requested message
|
||||
* @returns {integer} The messageTracker id of the message.
|
||||
*/
|
||||
getId(msgHdr) {
|
||||
let msgIdentifier;
|
||||
if (msgHdr.folder) {
|
||||
msgIdentifier = {
|
||||
folderURI: msgHdr.folder.URI,
|
||||
messageKey: msgHdr.messageKey,
|
||||
};
|
||||
} else {
|
||||
// Normalize the dummyMsgUrl by sorting its parameters and striping them
|
||||
// to a minimum.
|
||||
let url = new URL(msgHdr.getStringProperty("dummyMsgUrl"));
|
||||
let parameters = Array.from(url.searchParams, p => p[0]).filter(
|
||||
p => !["group", "number", "key", "part"].includes(p)
|
||||
);
|
||||
for (let parameter of parameters) {
|
||||
url.searchParams.delete(parameter);
|
||||
}
|
||||
url.searchParams.sort();
|
||||
|
||||
msgIdentifier = {
|
||||
dummyMsgUrl: url.href,
|
||||
dummyMsgLastModifiedTime: msgHdr.getUint32Property(
|
||||
"dummyMsgLastModifiedTime"
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
let id = this._get(msgIdentifier);
|
||||
if (id) {
|
||||
return id;
|
||||
}
|
||||
id = this._nextId++;
|
||||
|
||||
this._set(id, msgIdentifier, msgHdr);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided msgIdentifier belongs to a modified file message.
|
||||
*
|
||||
* @param {*} msgIdentifier - msgIdentifier object of the message
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isModifiedFileMsg(msgIdentifier) {
|
||||
if (!msgIdentifier.dummyMsgUrl?.startsWith("file://")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
let file = Services.io
|
||||
.newURI(msgIdentifier.dummyMsgUrl)
|
||||
.QueryInterface(Ci.nsIFileURL).file;
|
||||
if (!file?.exists()) {
|
||||
throw new ExtensionError("File does not exist");
|
||||
}
|
||||
if (
|
||||
msgIdentifier.dummyMsgLastModifiedTime &&
|
||||
Math.floor(file.lastModifiedTime / 1000000) !=
|
||||
msgIdentifier.dummyMsgLastModifiedTime
|
||||
) {
|
||||
throw new ExtensionError("File has been modified");
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a message from the messageTracker. If the message no longer,
|
||||
* exists it is removed from the messageTracker.
|
||||
*
|
||||
* @param {integer} id - messageTracker id of the message
|
||||
* @returns {nsIMsgDBHdr} The identifier of the message.
|
||||
*/
|
||||
getMessage(id) {
|
||||
let msgIdentifier = this._messages.get(id);
|
||||
if (!msgIdentifier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (msgIdentifier.folderURI) {
|
||||
let folder = MailServices.folderLookup.getFolderForURL(
|
||||
msgIdentifier.folderURI
|
||||
);
|
||||
if (folder) {
|
||||
let msgHdr = folder.msgDatabase.getMsgHdrForKey(
|
||||
msgIdentifier.messageKey
|
||||
);
|
||||
if (msgHdr) {
|
||||
return msgHdr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let msgHdr = this._dummyMessageHeaders.get(msgIdentifier.dummyMsgUrl);
|
||||
if (msgHdr && !this.isModifiedFileMsg(msgIdentifier)) {
|
||||
return msgHdr;
|
||||
}
|
||||
}
|
||||
|
||||
this._remove(msgIdentifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an nsIMsgDBHdr to a simple object for use in messages.
|
||||
* This function WILL change as the API develops.
|
||||
*
|
||||
* @param {nsIMsgDBHdr} msgHdr
|
||||
* @param {ExtensionData} extension
|
||||
*
|
||||
* @returns {MessageHeader} MessageHeader object
|
||||
*
|
||||
* @see /mail/components/extensions/schemas/messages.json
|
||||
*/
|
||||
convertMessage(msgHdr, extension) {
|
||||
if (!msgHdr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let composeFields = Cc[
|
||||
"@mozilla.org/messengercompose/composefields;1"
|
||||
].createInstance(Ci.nsIMsgCompFields);
|
||||
|
||||
// Cache msgHdr to reduce XPCOM requests.
|
||||
let cachedHdr = new CachedMsgHeader(msgHdr);
|
||||
|
||||
let junkScore = parseInt(cachedHdr.getStringProperty("junkscore"), 10) || 0;
|
||||
let tags = (cachedHdr.getStringProperty("keywords") || "")
|
||||
.split(" ")
|
||||
.filter(MailServices.tags.isValidKey);
|
||||
|
||||
// Getting the size of attached messages does not work consistently. For imap://
|
||||
// and mailbox:// messages the returned size in msgHdr.messageSize is 0, and for
|
||||
// file:// messages the returned size is always the total file size
|
||||
// Be consistent here and always return 0. The user can obtain the message size
|
||||
// from the size of the associated attachment file.
|
||||
let size = isAttachedMessageUrl(cachedHdr.getStringProperty("dummyMsgUrl"))
|
||||
? 0
|
||||
: cachedHdr.messageSize;
|
||||
|
||||
let messageObject = {
|
||||
id: this.getId(cachedHdr),
|
||||
date: new Date(Math.round(cachedHdr.date / 1000)),
|
||||
author: cachedHdr.mime2DecodedAuthor,
|
||||
recipients: cachedHdr.mime2DecodedRecipients
|
||||
? composeFields.splitRecipients(cachedHdr.mime2DecodedRecipients, false)
|
||||
: [],
|
||||
ccList: cachedHdr.ccList
|
||||
? composeFields.splitRecipients(cachedHdr.ccList, false)
|
||||
: [],
|
||||
bccList: cachedHdr.bccList
|
||||
? composeFields.splitRecipients(cachedHdr.bccList, false)
|
||||
: [],
|
||||
subject: cachedHdr.mime2DecodedSubject,
|
||||
read: cachedHdr.isRead,
|
||||
new: !!(cachedHdr.flags & Ci.nsMsgMessageFlags.New),
|
||||
headersOnly: !!(cachedHdr.flags & Ci.nsMsgMessageFlags.Partial),
|
||||
flagged: !!cachedHdr.isFlagged,
|
||||
junk: junkScore >= lazy.gJunkThreshold,
|
||||
junkScore,
|
||||
headerMessageId: cachedHdr.messageId,
|
||||
size,
|
||||
tags,
|
||||
external: !cachedHdr.folder,
|
||||
};
|
||||
// convertMessage can be called without providing an extension, if the info is
|
||||
// needed for multiple extensions. The caller has to ensure that the folder info
|
||||
// is not forwarded to extensions, which do not have the required permission.
|
||||
if (
|
||||
cachedHdr.folder &&
|
||||
(!extension || extension.hasPermission("accountsRead"))
|
||||
) {
|
||||
messageObject.folder = convertFolder(cachedHdr.folder);
|
||||
}
|
||||
return messageObject;
|
||||
}
|
||||
|
||||
// nsIFolderListener
|
||||
|
||||
onFolderPropertyFlagChanged(item, property, oldFlag, newFlag) {
|
||||
let changes = {};
|
||||
switch (property) {
|
||||
case "Status":
|
||||
if ((oldFlag ^ newFlag) & Ci.nsMsgMessageFlags.Read) {
|
||||
changes.read = item.isRead;
|
||||
}
|
||||
if ((oldFlag ^ newFlag) & Ci.nsMsgMessageFlags.New) {
|
||||
changes.new = !!(newFlag & Ci.nsMsgMessageFlags.New);
|
||||
}
|
||||
break;
|
||||
case "Flagged":
|
||||
changes.flagged = item.isFlagged;
|
||||
break;
|
||||
case "Keywords":
|
||||
{
|
||||
let tags = item.getStringProperty("keywords");
|
||||
tags = tags ? tags.split(" ") : [];
|
||||
changes.tags = tags.filter(MailServices.tags.isValidKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (Object.keys(changes).length) {
|
||||
this.emit("message-updated", item, changes);
|
||||
}
|
||||
}
|
||||
|
||||
onFolderIntPropertyChanged(folder, property, oldValue, newValue) {
|
||||
switch (property) {
|
||||
case "BiffState":
|
||||
if (newValue == Ci.nsIMsgFolder.nsMsgBiffState_NewMail) {
|
||||
// The folder argument is a root folder.
|
||||
this.findNewMessages(folder);
|
||||
}
|
||||
break;
|
||||
case "NewMailReceived":
|
||||
// The folder argument is a real folder.
|
||||
this.findNewMessages(folder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all folders with new messages in the specified changedFolder and
|
||||
* returns those.
|
||||
*
|
||||
* @see MailNotificationManager._getFirstRealFolderWithNewMail()
|
||||
*/
|
||||
findNewMessages(changedFolder) {
|
||||
let folders = changedFolder.descendants;
|
||||
folders.unshift(changedFolder);
|
||||
for (let folder of folders) {
|
||||
let flags = folder.flags;
|
||||
if (
|
||||
!(flags & Ci.nsMsgFolderFlags.Inbox) &&
|
||||
flags & (Ci.nsMsgFolderFlags.SpecialUse | Ci.nsMsgFolderFlags.Virtual)
|
||||
) {
|
||||
// Do not notify if the folder is not Inbox but one of
|
||||
// Drafts|Trash|SentMail|Templates|Junk|Archive|Queue or Virtual.
|
||||
continue;
|
||||
}
|
||||
let numNewMessages = folder.getNumNewMessages(false);
|
||||
if (!numNewMessages) {
|
||||
continue;
|
||||
}
|
||||
let msgDb = folder.msgDatabase;
|
||||
let newMsgKeys = msgDb.getNewList().slice(-numNewMessages);
|
||||
if (newMsgKeys.length == 0) {
|
||||
continue;
|
||||
}
|
||||
this.emit(
|
||||
"messages-received",
|
||||
folder,
|
||||
newMsgKeys.map(key => msgDb.getMsgHdrForKey(key))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// nsIMsgFolderListener
|
||||
|
||||
msgsJunkStatusChanged(messages) {
|
||||
for (let msgHdr of messages) {
|
||||
let junkScore = parseInt(msgHdr.getStringProperty("junkscore"), 10) || 0;
|
||||
this.emit("message-updated", msgHdr, {
|
||||
junk: junkScore >= lazy.gJunkThreshold,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
msgsDeleted(deletedMsgs) {
|
||||
if (deletedMsgs.length > 0) {
|
||||
this.emit("messages-deleted", deletedMsgs);
|
||||
}
|
||||
}
|
||||
|
||||
msgsMoveCopyCompleted(move, srcMsgs, dstFolder, dstMsgs) {
|
||||
if (srcMsgs.length > 0 && dstMsgs.length > 0) {
|
||||
let emitMsg = move ? "messages-moved" : "messages-copied";
|
||||
this.emit(emitMsg, srcMsgs, dstMsgs);
|
||||
}
|
||||
}
|
||||
|
||||
msgKeyChanged(oldKey, newMsgHdr) {
|
||||
// For IMAP messages there is a delayed update of database keys and if those
|
||||
// keys change, the messageTracker needs to update its maps, otherwise wrong
|
||||
// messages will be returned. Key changes are replayed in multi-step swaps.
|
||||
let newKey = newMsgHdr.messageKey;
|
||||
|
||||
// Replay pending swaps.
|
||||
while (this._pendingKeyChanges.has(oldKey)) {
|
||||
let next = this._pendingKeyChanges.get(oldKey);
|
||||
this._pendingKeyChanges.delete(oldKey);
|
||||
oldKey = next;
|
||||
|
||||
// Check if we are left with a no-op swap and exit early.
|
||||
if (oldKey == newKey) {
|
||||
this._pendingKeyChanges.delete(oldKey);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldKey != newKey) {
|
||||
// New key swap, log the mirror swap as pending.
|
||||
this._pendingKeyChanges.set(newKey, oldKey);
|
||||
|
||||
// Swap tracker entries.
|
||||
let oldId = this._get({
|
||||
folderURI: newMsgHdr.folder.URI,
|
||||
messageKey: oldKey,
|
||||
});
|
||||
let newId = this._get({
|
||||
folderURI: newMsgHdr.folder.URI,
|
||||
messageKey: newKey,
|
||||
});
|
||||
this._set(oldId, { folderURI: newMsgHdr.folder.URI, messageKey: newKey });
|
||||
this._set(newId, { folderURI: newMsgHdr.folder.URI, messageKey: oldKey });
|
||||
}
|
||||
}
|
||||
|
||||
// nsIObserver
|
||||
|
||||
/**
|
||||
* Observer to update message tracker if a message has received a new key due
|
||||
* to attachments being removed, which we do not consider to be a new message.
|
||||
*/
|
||||
observe(subject, topic, data) {
|
||||
if (topic == "attachment-delete-msgkey-changed") {
|
||||
data = JSON.parse(data);
|
||||
|
||||
if (data && data.folderURI && data.oldMessageKey && data.newMessageKey) {
|
||||
let id = this._get({
|
||||
folderURI: data.folderURI,
|
||||
messageKey: data.oldMessageKey,
|
||||
});
|
||||
if (id) {
|
||||
// Replace tracker entries.
|
||||
this._set(id, {
|
||||
folderURI: data.folderURI,
|
||||
messageKey: data.newMessageKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (topic == "quit-application-granted") {
|
||||
this.cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience class to handle message pages.
|
||||
*/
|
||||
class MessagePage {
|
||||
constructor() {
|
||||
this.messages = [];
|
||||
this.read = false;
|
||||
this._deferredPromise = new Promise(resolve => {
|
||||
this._resolveDeferredPromise = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
get promise() {
|
||||
return this._deferredPromise;
|
||||
}
|
||||
|
||||
resolvePage() {
|
||||
this._resolveDeferredPromise(this.messages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience class to keep track of the status of message lists.
|
||||
*/
|
||||
export class MessageList {
|
||||
constructor(extension, messageTracker) {
|
||||
this.messageListId = Services.uuid.generateUUID().number.substring(1, 37);
|
||||
this.extension = extension;
|
||||
this.isDone = false;
|
||||
this.pages = [];
|
||||
this._messageTracker = messageTracker;
|
||||
this.autoPaginatorTimeout = null;
|
||||
|
||||
this.addPage();
|
||||
}
|
||||
|
||||
addPage() {
|
||||
if (this.autoPaginatorTimeout) {
|
||||
clearTimeout(this.autoPaginatorTimeout);
|
||||
this.autoPaginatorTimeout = null;
|
||||
}
|
||||
|
||||
if (this.isDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adding a page will make this.currentPage point to the new page.
|
||||
let previousPage = this.currentPage;
|
||||
|
||||
// If the current page has no messages, there is no need to add a page.
|
||||
if (previousPage && previousPage.messages.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pages.push(new MessagePage());
|
||||
// The previous page is finished and can be resolved.
|
||||
if (previousPage) {
|
||||
previousPage.resolvePage();
|
||||
}
|
||||
}
|
||||
|
||||
get currentPage() {
|
||||
return this.pages.length > 0 ? this.pages[this.pages.length - 1] : null;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.messageListId;
|
||||
}
|
||||
|
||||
addMessage(message) {
|
||||
if (this.isDone || !this.currentPage) {
|
||||
return;
|
||||
}
|
||||
if (this.currentPage.messages.length >= lazy.gMessagesPerPage) {
|
||||
this.addPage();
|
||||
}
|
||||
|
||||
this.currentPage.messages.push(
|
||||
this._messageTracker.convertMessage(message, this.extension)
|
||||
);
|
||||
|
||||
// Automatically push a new page and return the page with this message after
|
||||
// a fixed amount of time, so that small sets of search results are not held
|
||||
// back until a full page has been found or the entire search has finished.
|
||||
if (!this.autoPaginatorTimeout) {
|
||||
this.autoPaginatorTimeout = setTimeout(this.addPage.bind(this), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
done() {
|
||||
if (this.isDone) {
|
||||
return;
|
||||
}
|
||||
this.isDone = true;
|
||||
|
||||
// Resolve the current page.
|
||||
if (this.currentPage) {
|
||||
this.currentPage.resolvePage();
|
||||
}
|
||||
}
|
||||
|
||||
async getNextUnreadPage() {
|
||||
let page = this.pages.find(p => !p.read);
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let messages = await page.promise;
|
||||
page.read = true;
|
||||
|
||||
return {
|
||||
id: this.pages.find(p => !p.read) ? this.id : null,
|
||||
messages,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks lists of messages so that an extension can consume them in chunks.
|
||||
* Any WebExtensions method that could return multiple messages should instead call
|
||||
* messageListTracker.startList and return the results, which contain the first
|
||||
* chunk. Further chunks can be fetched by the extension calling
|
||||
* browser.messages.continueList. Chunk size is controlled by a pref.
|
||||
*/
|
||||
export class MessageListTracker {
|
||||
constructor(messageTracker) {
|
||||
this._contextLists = new WeakMap();
|
||||
this._messageTracker = messageTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array or enumerator of messages and returns a Promise for the first
|
||||
* page, which will resolve as soon as it is available.
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
startList(messages, extension) {
|
||||
let messageList = this.createList(extension);
|
||||
this._addMessages(messages, messageList);
|
||||
return this.getNextPage(messageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add messages to a messageList.
|
||||
*/
|
||||
async _addMessages(messages, messageList) {
|
||||
if (messageList.isDone) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(messages)) {
|
||||
messages = this._createEnumerator(messages);
|
||||
}
|
||||
while (messages.hasMoreElements()) {
|
||||
let next = messages.getNext();
|
||||
messageList.addMessage(next.QueryInterface(Ci.nsIMsgDBHdr));
|
||||
}
|
||||
messageList.done();
|
||||
}
|
||||
|
||||
_createEnumerator(array) {
|
||||
let current = 0;
|
||||
return {
|
||||
hasMoreElements() {
|
||||
return current < array.length;
|
||||
},
|
||||
getNext() {
|
||||
return array[current++];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a new messageList object.
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
createList(extension) {
|
||||
let messageList = new MessageList(extension, this._messageTracker);
|
||||
let lists = this._contextLists.get(extension);
|
||||
if (!lists) {
|
||||
lists = new Map();
|
||||
this._contextLists.set(extension, lists);
|
||||
}
|
||||
lists.set(messageList.id, messageList);
|
||||
return messageList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the messageList object for a given id.
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
getList(messageListId, extension) {
|
||||
let lists = this._contextLists.get(extension);
|
||||
let messageList = lists ? lists.get(messageListId, null) : null;
|
||||
if (!messageList) {
|
||||
throw new ExtensionError(
|
||||
`No message list for id ${messageListId}. Have you reached the end of a list?`
|
||||
);
|
||||
}
|
||||
return messageList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first/next message page of the given messageList.
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
async getNextPage(messageList) {
|
||||
let page = await messageList.getNextUnreadPage();
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the page does not have an id, the list has been retrieved completely
|
||||
// and can be removed.
|
||||
if (!page.id) {
|
||||
let lists = this._contextLists.get(messageList.extension);
|
||||
if (lists && lists.has(messageList.id)) {
|
||||
lists.delete(messageList.id);
|
||||
}
|
||||
}
|
||||
return page;
|
||||
}
|
||||
}
|
||||
|
||||
export class MessageManager {
|
||||
constructor(extension, messageTracker, messageListTracker) {
|
||||
this.extension = extension;
|
||||
this._messageTracker = messageTracker;
|
||||
this._messageListTracker = messageListTracker;
|
||||
}
|
||||
|
||||
convert(msgHdr) {
|
||||
return this._messageTracker.convertMessage(msgHdr, this.extension);
|
||||
}
|
||||
|
||||
get(id) {
|
||||
return this._messageTracker.getMessage(id);
|
||||
}
|
||||
|
||||
startMessageList(messageList) {
|
||||
return this._messageListTracker.startList(messageList, this.extension);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,9 @@ EXTRA_COMPONENTS += [
|
|||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
"ExtensionAccounts.sys.mjs",
|
||||
"ExtensionBrowsingData.sys.mjs",
|
||||
"ExtensionMessages.sys.mjs",
|
||||
"ExtensionPopups.sys.mjs",
|
||||
"ExtensionToolbarButtons.sys.mjs",
|
||||
"MailExtensionShortcuts.sys.mjs",
|
||||
|
|
|
@ -42,20 +42,12 @@ module.exports = {
|
|||
MESSAGE_WINDOW_URI: true,
|
||||
MESSAGE_PROTOCOLS: true,
|
||||
NOTIFICATION_COLLAPSE_TIME: true,
|
||||
CachedMsgHeader: true,
|
||||
ExtensionError: true,
|
||||
Tab: true,
|
||||
TabmailTab: true,
|
||||
Window: true,
|
||||
TabmailWindow: true,
|
||||
clickModifiersFromEvent: true,
|
||||
convertFolder: true,
|
||||
convertAccount: true,
|
||||
traverseSubfolders: true,
|
||||
convertMailIdentity: true,
|
||||
convertMessage: true,
|
||||
folderPathToURI: true,
|
||||
folderURIToPath: true,
|
||||
getNormalWindowReady: true,
|
||||
getRealFileForFile: true,
|
||||
getTabBrowser: true,
|
||||
|
|
|
@ -8,6 +8,10 @@ ChromeUtils.defineModuleGetter(
|
|||
"resource:///modules/MailServices.jsm"
|
||||
);
|
||||
|
||||
var { convertAccount, convertMailIdentity } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ExtensionAccounts.sys.mjs"
|
||||
);
|
||||
|
||||
/**
|
||||
* @implements {nsIObserver}
|
||||
* @implements {nsIMsgFolderListener}
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
var { ExtensionParent } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/ExtensionParent.sys.mjs"
|
||||
);
|
||||
var { XPCOMUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/XPCOMUtils.sys.mjs"
|
||||
);
|
||||
var { cloudFileAccounts } = ChromeUtils.import(
|
||||
"resource:///modules/cloudFileAccounts.jsm"
|
||||
);
|
||||
|
||||
// eslint-disable-next-line mozilla/reject-importGlobalProperties
|
||||
Cu.importGlobalProperties(["File", "FileReader"]);
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["File", "FileReader"]);
|
||||
|
||||
async function promiseFileRead(nsifile) {
|
||||
let blob = await File.createFromNsIFile(nsifile);
|
||||
|
|
|
@ -6,7 +6,7 @@ var { XPCOMUtils } = ChromeUtils.importESModule(
|
|||
"resource://gre/modules/XPCOMUtils.sys.mjs"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["IOUtils", "PathUtils"]);
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["File", "IOUtils", "PathUtils"]);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
|
@ -22,8 +22,12 @@ let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(
|
|||
Ci.nsIParserUtils
|
||||
);
|
||||
|
||||
// eslint-disable-next-line mozilla/reject-importGlobalProperties
|
||||
Cu.importGlobalProperties(["File"]);
|
||||
var { convertMessage } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ExtensionMessages.sys.mjs"
|
||||
);
|
||||
var { convertFolder, folderPathToURI } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ExtensionAccounts.sys.mjs"
|
||||
);
|
||||
|
||||
const deliveryFormats = [
|
||||
{ id: Ci.nsIMsgCompSendFormat.Auto, value: "auto" },
|
||||
|
@ -850,7 +854,7 @@ class MsgOperationObserver {
|
|||
folderURI: msgHdr.folder.URI,
|
||||
});
|
||||
if (!this.classifiedMessages.has(key)) {
|
||||
this.classifiedMessages.set(key, convertMessage(msgHdr));
|
||||
this.classifiedMessages.set(key, messageTracker.convertMessage(msgHdr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ ChromeUtils.defineModuleGetter(
|
|||
ChromeUtils.defineESModuleGetters(this, {
|
||||
DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
|
||||
});
|
||||
|
||||
var { convertFolder, folderPathToURI, folderURIToPath, traverseSubfolders } =
|
||||
ChromeUtils.importESModule("resource:///modules/ExtensionAccounts.sys.mjs");
|
||||
/**
|
||||
* Tracks folder events.
|
||||
*
|
||||
|
|
|
@ -11,6 +11,10 @@ ChromeUtils.defineESModuleGetters(this, {
|
|||
DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
|
||||
});
|
||||
|
||||
var { convertMailIdentity } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ExtensionAccounts.sys.mjs"
|
||||
);
|
||||
|
||||
function findIdentityAndAccount(identityId) {
|
||||
for (let account of MailServices.accounts.accounts) {
|
||||
for (let identity of account.identities) {
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -11,6 +11,10 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
MailServices: "resource:///modules/MailServices.jsm",
|
||||
});
|
||||
|
||||
var { convertFolder, folderPathToURI } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ExtensionAccounts.sys.mjs"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"gDynamicPaneConfig",
|
||||
|
|
|
@ -12,24 +12,26 @@ ChromeUtils.defineModuleGetter(
|
|||
"resource:///modules/MailServices.jsm"
|
||||
);
|
||||
|
||||
var { convertFolder, convertAccount, folderPathToURI, traverseSubfolders } =
|
||||
ChromeUtils.importESModule("resource:///modules/ExtensionAccounts.sys.mjs");
|
||||
|
||||
var { AppConstants } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
);
|
||||
|
||||
var { ExtensionCommon } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/ExtensionCommon.sys.mjs"
|
||||
);
|
||||
var { ExtensionParent } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/ExtensionParent.sys.mjs"
|
||||
);
|
||||
var { SelectionUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/SelectionUtils.sys.mjs"
|
||||
);
|
||||
|
||||
var { DefaultMap, ExtensionError } = ExtensionUtils;
|
||||
|
||||
var { ExtensionParent } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/ExtensionParent.sys.mjs"
|
||||
);
|
||||
var { IconDetails, StartupCache } = ExtensionParent;
|
||||
|
||||
var { ExtensionCommon } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/ExtensionCommon.sys.mjs"
|
||||
);
|
||||
var { makeWidgetId } = ExtensionCommon;
|
||||
var { DefaultMap, ExtensionError } = ExtensionUtils;
|
||||
var { IconDetails, StartupCache } = ExtensionParent;
|
||||
|
||||
const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ function getDisplayedMessages(tab) {
|
|||
function convertMessages(messages, extension) {
|
||||
let result = [];
|
||||
for (let msg of messages) {
|
||||
let hdr = convertMessage(msg, extension);
|
||||
let hdr = messageTracker.convertMessage(msg, extension);
|
||||
if (hdr) {
|
||||
result.push(hdr);
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ this.messageDisplay = class extends ExtensionAPIPersistent {
|
|||
// `event.target` is an about:message window.
|
||||
let nativeTab = event.target.tabOrWindow;
|
||||
let tab = tabManager.wrapTab(nativeTab);
|
||||
let msg = convertMessage(event.detail, extension);
|
||||
let msg = messageTracker.convertMessage(event.detail, extension);
|
||||
fire.async(tab.convert(), msg);
|
||||
},
|
||||
};
|
||||
|
@ -252,7 +252,7 @@ this.messageDisplay = class extends ExtensionAPIPersistent {
|
|||
if (messages.length != 1) {
|
||||
return null;
|
||||
}
|
||||
return convertMessage(messages[0], extension);
|
||||
return messageTracker.convertMessage(messages[0], extension);
|
||||
},
|
||||
async getDisplayedMessages(tabId) {
|
||||
let tab = await getMessageDisplayTab(tabId);
|
||||
|
|
|
@ -6,6 +6,16 @@ ChromeUtils.defineESModuleGetters(this, {
|
|||
AttachmentInfo: "resource:///modules/AttachmentInfo.sys.mjs",
|
||||
});
|
||||
|
||||
var { CachedMsgHeader } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ExtensionMessages.sys.mjs"
|
||||
);
|
||||
var { convertFolder, folderPathToURI } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ExtensionAccounts.sys.mjs"
|
||||
);
|
||||
var { XPCOMUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/XPCOMUtils.sys.mjs"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"MailServices",
|
||||
|
@ -40,9 +50,7 @@ ChromeUtils.defineModuleGetter(
|
|||
var { MailStringUtils } = ChromeUtils.import(
|
||||
"resource:///modules/MailStringUtils.jsm"
|
||||
);
|
||||
|
||||
// eslint-disable-next-line mozilla/reject-importGlobalProperties
|
||||
Cu.importGlobalProperties(["File", "IOUtils", "PathUtils"]);
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["File", "IOUtils", "PathUtils"]);
|
||||
|
||||
var { DefaultMap } = ExtensionUtils;
|
||||
|
||||
|
@ -107,7 +115,7 @@ async function convertAttachment(attachment) {
|
|||
let hdrId = attachment.headers["message-id"]?.[0];
|
||||
attachedMsgHdr.messageId = hdrId ? hdrId.replace(/^<|>$/g, "") : "";
|
||||
|
||||
rv.message = convertMessage(attachedMsgHdr);
|
||||
rv.message = messageTracker.convertMessage(attachedMsgHdr);
|
||||
}
|
||||
|
||||
return rv;
|
||||
|
@ -443,7 +451,10 @@ this.messages = class extends ExtensionAPIPersistent {
|
|||
let listener = async (event, message, properties) => {
|
||||
let { extension } = this;
|
||||
// The msgHdr could be gone after the wakeup, convert it early.
|
||||
let convertedMessage = convertMessage(message, extension);
|
||||
let convertedMessage = messageTracker.convertMessage(
|
||||
message,
|
||||
extension
|
||||
);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
|
@ -756,7 +767,10 @@ this.messages = class extends ExtensionAPIPersistent {
|
|||
if (!msgHdr) {
|
||||
throw new ExtensionError(`Message not found: ${messageId}.`);
|
||||
}
|
||||
let messageHeader = convertMessage(msgHdr, context.extension);
|
||||
let messageHeader = messageTracker.convertMessage(
|
||||
msgHdr,
|
||||
context.extension
|
||||
);
|
||||
if (messageHeader.id != messageId) {
|
||||
throw new ExtensionError(
|
||||
"Unexpected Error: Returned message does not equal requested message."
|
||||
|
@ -1503,7 +1517,7 @@ this.messages = class extends ExtensionAPIPersistent {
|
|||
if (!file.mozFullPath) {
|
||||
await IOUtils.remove(tempFile.path);
|
||||
}
|
||||
return convertMessage(msgHeader, context.extension);
|
||||
return messageTracker.convertMessage(msgHeader, context.extension);
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
throw new ExtensionError(`Error importing message: ${ex.message}`);
|
||||
|
|
Загрузка…
Ссылка в новой задаче