Bug 1605937 - Remove usage of Gloda in messages.query(). r=darktrojan
Differential Revision: https://phabricator.services.mozilla.com/D116311 --HG-- extra : amend_source : d45f9f96f1438eba2cdc4a4694fed7f465aba7e4
This commit is contained in:
Родитель
440de9c493
Коммит
469b35006c
|
@ -2,11 +2,6 @@
|
|||
* 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/. */
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Gloda",
|
||||
"resource:///modules/gloda/GlodaPublic.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"MailServices",
|
||||
|
@ -32,6 +27,11 @@ ChromeUtils.defineModuleGetter(
|
|||
"NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"jsmime",
|
||||
"resource:///modules/jsmime.jsm"
|
||||
);
|
||||
|
||||
// eslint-disable-next-line mozilla/reject-importGlobalProperties
|
||||
Cu.importGlobalProperties(["fetch", "File"]);
|
||||
|
@ -182,6 +182,34 @@ this.messages = class extends ExtensionAPI {
|
|||
}
|
||||
}
|
||||
|
||||
async function getMimeMessage(msgHdr) {
|
||||
// Use jsmime based MimeParser to read NNTP messages, which are not
|
||||
// supported by MsgHdrToMimeMessage. No encryption support!
|
||||
if (msgHdr.folder.server.type == "nntp") {
|
||||
try {
|
||||
let raw = await MsgHdrToRawMessage(msgHdr);
|
||||
let mimeMsg = MimeParser.extractMimeMsg(raw, {
|
||||
includeAttachments: false,
|
||||
});
|
||||
return mimeMsg;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
MsgHdrToMimeMessage(
|
||||
msgHdr,
|
||||
null,
|
||||
(_msgHdr, mimeMsg) => {
|
||||
resolve(mimeMsg);
|
||||
},
|
||||
true,
|
||||
{ examineEncryptedParts: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
messages: {
|
||||
onNewMailReceived: new EventManager({
|
||||
|
@ -228,33 +256,7 @@ this.messages = class extends ExtensionAPI {
|
|||
},
|
||||
async getFull(messageId) {
|
||||
let msgHdr = messageTracker.getMessage(messageId);
|
||||
|
||||
// Use jsmime based MimeParser to read NNTP messages, which are not
|
||||
// supported by MsgHdrToMimeMessage. No encryption support!
|
||||
if (msgHdr.folder.server.type == "nntp") {
|
||||
let mimeMsg = {};
|
||||
try {
|
||||
let raw = await MsgHdrToRawMessage(msgHdr);
|
||||
mimeMsg = MimeParser.extractMimeMsg(raw, {
|
||||
includeAttachments: false,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new ExtensionError(`Error reading message ${messageId}`);
|
||||
}
|
||||
return convertMessagePart(mimeMsg);
|
||||
}
|
||||
|
||||
let mimeMsg = await new Promise(resolve => {
|
||||
MsgHdrToMimeMessage(
|
||||
msgHdr,
|
||||
null,
|
||||
(_msgHdr, mimeMsg) => {
|
||||
resolve(mimeMsg);
|
||||
},
|
||||
true,
|
||||
{ examineEncryptedParts: true }
|
||||
);
|
||||
});
|
||||
let mimeMsg = await getMimeMessage(msgHdr);
|
||||
if (!mimeMsg) {
|
||||
throw new ExtensionError(`Error reading message ${messageId}`);
|
||||
}
|
||||
|
@ -367,99 +369,396 @@ this.messages = class extends ExtensionAPI {
|
|||
});
|
||||
},
|
||||
async query(queryInfo) {
|
||||
let query = Gloda.newQuery(Gloda.NOUN_MESSAGE);
|
||||
let composeFields = Cc[
|
||||
"@mozilla.org/messengercompose/composefields;1"
|
||||
].createInstance(Ci.nsIMsgCompFields);
|
||||
|
||||
if (queryInfo.subject) {
|
||||
query.subjectMatches(queryInfo.subject);
|
||||
const includesContent = (folder, parts, searchTerm) => {
|
||||
if (!parts || parts.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (let part of parts) {
|
||||
if (
|
||||
coerceBodyToPlaintext(folder, part).includes(searchTerm) ||
|
||||
includesContent(folder, part.parts, searchTerm)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const coerceBodyToPlaintext = (folder, part) => {
|
||||
if (!part || !part.body) {
|
||||
return "";
|
||||
}
|
||||
if (part.contentType == "text/plain") {
|
||||
return part.body;
|
||||
}
|
||||
// text/enriched gets transformed into HTML by libmime
|
||||
if (
|
||||
part.contentType == "text/html" ||
|
||||
part.contentType == "text/enriched"
|
||||
) {
|
||||
return folder.convertMsgSnippetToPlainText(part.body);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare name and email properties of the address object returned by
|
||||
* MailServices.headerParser.makeFromDisplayAddress() to be lower case.
|
||||
* Also fix the name being wrongly returned in the email property, if
|
||||
* the address was just a single name.
|
||||
*/
|
||||
const prepareAddress = displayAddr => {
|
||||
let email = displayAddr.email?.toLocaleLowerCase();
|
||||
let name = displayAddr.name?.toLocaleLowerCase();
|
||||
if (email && !name && !email.includes("@")) {
|
||||
name = email;
|
||||
email = null;
|
||||
}
|
||||
return { name, email };
|
||||
};
|
||||
|
||||
/**
|
||||
* Check multiple addresses if they match the provided search address.
|
||||
*
|
||||
* @returns A boolean indicating if search was successful.
|
||||
*/
|
||||
const searchInMultipleAddresses = (searchAddress, addresses) => {
|
||||
// Return on first positive match.
|
||||
for (let address of addresses) {
|
||||
let nameMatched =
|
||||
searchAddress.name &&
|
||||
address.name &&
|
||||
address.name.includes(searchAddress.name);
|
||||
|
||||
// Check for email match. Name match being required on top, if
|
||||
// specified.
|
||||
if (
|
||||
(nameMatched || !searchAddress.name) &&
|
||||
searchAddress.email &&
|
||||
address.email &&
|
||||
address.email == searchAddress.email
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If address match failed, name match may only be true if no
|
||||
// email has been specified.
|
||||
if (!searchAddress.email && nameMatched) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Substring match on name and exact match on email. If searchTerm
|
||||
* includes multiple addresses, all of them must match.
|
||||
*
|
||||
* @returns A boolean indicating if search was successful.
|
||||
*/
|
||||
const isAddressMatch = (searchTerm, addressObjects) => {
|
||||
let searchAddresses = MailServices.headerParser.makeFromDisplayAddress(
|
||||
searchTerm
|
||||
);
|
||||
if (!searchAddresses || searchAddresses.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare addresses.
|
||||
let addresses = [];
|
||||
for (let addressObject of addressObjects) {
|
||||
let decodedAddressString = addressObject.doRfc2047
|
||||
? jsmime.headerparser.decodeRFC2047Words(addressObject.addr)
|
||||
: addressObject.addr;
|
||||
for (let address of MailServices.headerParser.makeFromDisplayAddress(
|
||||
decodedAddressString
|
||||
)) {
|
||||
addresses.push(prepareAddress(address));
|
||||
}
|
||||
}
|
||||
if (addresses.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let success = false;
|
||||
for (let searchAddress of searchAddresses) {
|
||||
// Exit early if this search was not successfully, but all search
|
||||
// addresses have to be matched.
|
||||
if (
|
||||
!searchInMultipleAddresses(
|
||||
prepareAddress(searchAddress),
|
||||
addresses
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
};
|
||||
|
||||
const checkSearchCriteria = async (folder, msg) => {
|
||||
// Check date ranges.
|
||||
if (
|
||||
queryInfo.fromDate !== null &&
|
||||
msg.dateInSeconds * 1000 < queryInfo.fromDate.getTime()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
queryInfo.toDate !== null &&
|
||||
msg.dateInSeconds * 1000 > queryInfo.toDate.getTime()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check headerMessageId.
|
||||
if (
|
||||
queryInfo.headerMessageId &&
|
||||
msg.messageId != queryInfo.headerMessageId
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check unread.
|
||||
if (queryInfo.unread !== null && msg.isRead != !queryInfo.unread) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check flagged.
|
||||
if (
|
||||
queryInfo.flagged !== null &&
|
||||
msg.isFlagged != queryInfo.flagged
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check subject (substring match).
|
||||
if (
|
||||
queryInfo.subject &&
|
||||
!msg.mime2DecodedSubject.includes(queryInfo.subject)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check tags.
|
||||
if (requiredTags || forbiddenTags) {
|
||||
let messageTags = msg.getStringProperty("keywords").split(" ");
|
||||
if (requiredTags.length > 0) {
|
||||
if (
|
||||
queryInfo.tags.mode == "all" &&
|
||||
!requiredTags.every(tag => messageTags.includes(tag))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
queryInfo.tags.mode == "any" &&
|
||||
!requiredTags.some(tag => messageTags.includes(tag))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (forbiddenTags.length > 0) {
|
||||
if (
|
||||
queryInfo.tags.mode == "all" &&
|
||||
forbiddenTags.every(tag => messageTags.includes(tag))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
queryInfo.tags.mode == "any" &&
|
||||
forbiddenTags.some(tag => messageTags.includes(tag))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check toMe (case insensitive email address match).
|
||||
if (queryInfo.toMe !== null) {
|
||||
let recipients = [].concat(
|
||||
composeFields.splitRecipients(msg.recipients, true),
|
||||
composeFields.splitRecipients(msg.ccList, true),
|
||||
composeFields.splitRecipients(msg.bccList, true)
|
||||
);
|
||||
|
||||
if (
|
||||
queryInfo.toMe !=
|
||||
recipients.some(email =>
|
||||
identities.includes(email.toLocaleLowerCase())
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check fromMe (case insensitive email address match).
|
||||
if (queryInfo.fromMe !== null) {
|
||||
let authors = composeFields.splitRecipients(
|
||||
msg.mime2DecodedAuthor,
|
||||
true
|
||||
);
|
||||
if (
|
||||
queryInfo.fromMe !=
|
||||
authors.some(email =>
|
||||
identities.includes(email.toLocaleLowerCase())
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check author.
|
||||
if (
|
||||
queryInfo.author &&
|
||||
!isAddressMatch(queryInfo.author, [
|
||||
{ addr: msg.mime2DecodedAuthor, doRfc2047: false },
|
||||
])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check recipients.
|
||||
if (
|
||||
queryInfo.recipients &&
|
||||
!isAddressMatch(queryInfo.recipients, [
|
||||
{ addr: msg.mime2DecodedRecipients, doRfc2047: false },
|
||||
{ addr: msg.ccList, doRfc2047: true },
|
||||
{ addr: msg.bccList, doRfc2047: true },
|
||||
])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if fullText is already partially fulfilled.
|
||||
let fullTextBodySearchNeeded = false;
|
||||
if (queryInfo.fullText) {
|
||||
let subjectMatches = msg.mime2DecodedSubject.includes(
|
||||
queryInfo.fullText
|
||||
);
|
||||
let authorMatches = msg.mime2DecodedAuthor.includes(
|
||||
queryInfo.fullText
|
||||
);
|
||||
fullTextBodySearchNeeded = !(subjectMatches || authorMatches);
|
||||
}
|
||||
|
||||
// Check body.
|
||||
if (queryInfo.body || fullTextBodySearchNeeded) {
|
||||
let mimeMsg = await getMimeMessage(msg);
|
||||
if (
|
||||
queryInfo.body &&
|
||||
!includesContent(folder, [mimeMsg], queryInfo.body)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
fullTextBodySearchNeeded &&
|
||||
!includesContent(folder, [mimeMsg], queryInfo.fullText)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const searchMessages = async (
|
||||
folder,
|
||||
foundMessages,
|
||||
recursiveSearch = false
|
||||
) => {
|
||||
let messages = null;
|
||||
try {
|
||||
messages = folder.messages;
|
||||
} catch (e) {
|
||||
/* Some folders fail on message query, instead of returning empty */
|
||||
}
|
||||
|
||||
if (messages) {
|
||||
while (messages.hasMoreElements()) {
|
||||
let msg = messages.getNext();
|
||||
if (await checkSearchCriteria(folder, msg)) {
|
||||
foundMessages.push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (recursiveSearch) {
|
||||
for (let subFolder of folder.subFolders) {
|
||||
await searchMessages(subFolder, foundMessages, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Prepare case insensitive me filtering.
|
||||
let identities;
|
||||
if (queryInfo.toMe !== null || queryInfo.fromMe !== null) {
|
||||
identities = MailServices.accounts.allIdentities.map(i =>
|
||||
i.email.toLocaleLowerCase()
|
||||
);
|
||||
}
|
||||
if (queryInfo.fullText) {
|
||||
query.fulltextMatches(queryInfo.fullText);
|
||||
}
|
||||
if (queryInfo.body) {
|
||||
query.bodyMatches(queryInfo.body);
|
||||
}
|
||||
if (queryInfo.author) {
|
||||
query.authorMatches(queryInfo.author);
|
||||
}
|
||||
if (queryInfo.recipients) {
|
||||
query.recipientsMatch(queryInfo.recipients);
|
||||
}
|
||||
if (queryInfo.fromMe) {
|
||||
query.fromMe();
|
||||
}
|
||||
if (queryInfo.toMe) {
|
||||
query.toMe();
|
||||
}
|
||||
if (queryInfo.flagged !== null) {
|
||||
query.starred(queryInfo.flagged);
|
||||
|
||||
// Prepare tag filtering.
|
||||
let requiredTags;
|
||||
let forbiddenTags;
|
||||
if (queryInfo.tags) {
|
||||
let availableTags = MailServices.tags.getAllTags();
|
||||
requiredTags = availableTags.filter(
|
||||
tag =>
|
||||
tag.key in queryInfo.tags.tags && queryInfo.tags.tags[tag.key]
|
||||
);
|
||||
forbiddenTags = availableTags.filter(
|
||||
tag =>
|
||||
tag.key in queryInfo.tags.tags && !queryInfo.tags.tags[tag.key]
|
||||
);
|
||||
// If non-existing tags have been required, return immediately.
|
||||
if (
|
||||
requiredTags.length === 0 &&
|
||||
Object.values(queryInfo.tags.tags).filter(v => v).length > 0
|
||||
) {
|
||||
return messageListTracker.startList([], context.extension);
|
||||
}
|
||||
requiredTags = requiredTags.map(tag => tag.key);
|
||||
forbiddenTags = forbiddenTags.map(tag => tag.key);
|
||||
}
|
||||
|
||||
// Limit search to a given folder, or search all folders.
|
||||
let foundMessages = [];
|
||||
if (queryInfo.folder) {
|
||||
if (!context.extension.hasPermission("accountsRead")) {
|
||||
throw new ExtensionError(
|
||||
'Querying by folder requires the "accountsRead" permission'
|
||||
);
|
||||
}
|
||||
let { accountId, path } = queryInfo.folder;
|
||||
let uri = folderPathToURI(accountId, path);
|
||||
let folder = MailServices.folderLookup.getFolderForURL(uri);
|
||||
if (!folder) {
|
||||
throw new ExtensionError(`Folder not found: ${path}`);
|
||||
}
|
||||
query.folder(folder);
|
||||
}
|
||||
if (queryInfo.fromDate || queryInfo.toDate) {
|
||||
query.dateRange([queryInfo.fromDate, queryInfo.toDate]);
|
||||
}
|
||||
if (queryInfo.headerMessageId) {
|
||||
query.headerMessageID(queryInfo.headerMessageId);
|
||||
}
|
||||
let validTags;
|
||||
if (queryInfo.tags) {
|
||||
validTags = MailServices.tags
|
||||
.getAllTags()
|
||||
.filter(
|
||||
tag =>
|
||||
tag.key in queryInfo.tags.tags && queryInfo.tags.tags[tag.key]
|
||||
);
|
||||
if (validTags.length === 0) {
|
||||
// No messages will match this. Just return immediately.
|
||||
return messageListTracker.startList([], context.extension);
|
||||
}
|
||||
query.tags(...validTags);
|
||||
validTags = validTags.map(tag => tag.key);
|
||||
}
|
||||
|
||||
let collectionArray = await new Promise(resolve => {
|
||||
query.getCollection({
|
||||
onItemsAdded(items, collection) {},
|
||||
onItemsModified(items, collection) {},
|
||||
onItemsRemoved(items, collection) {},
|
||||
onQueryCompleted(collection) {
|
||||
resolve(
|
||||
collection.items
|
||||
.map(glodaMsg => glodaMsg.folderMessage)
|
||||
.filter(Boolean)
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (queryInfo.unread !== null) {
|
||||
collectionArray = collectionArray.filter(
|
||||
msg => msg.isRead == !queryInfo.unread
|
||||
let folder = MailServices.folderLookup.getFolderForURL(
|
||||
folderPathToURI(queryInfo.folder.accountId, queryInfo.folder.path)
|
||||
);
|
||||
}
|
||||
if (validTags && queryInfo.tags.mode == "all") {
|
||||
collectionArray = collectionArray.filter(msg => {
|
||||
let messageTags = msg.getStringProperty("keywords").split(" ");
|
||||
return validTags.every(tag => messageTags.includes(tag));
|
||||
});
|
||||
if (!folder) {
|
||||
throw new ExtensionError(
|
||||
`Folder not found: ${queryInfo.folder.path}`
|
||||
);
|
||||
}
|
||||
await searchMessages(
|
||||
folder,
|
||||
foundMessages,
|
||||
!!queryInfo.includeSubFolders
|
||||
);
|
||||
} else {
|
||||
for (let account of MailServices.accounts.accounts) {
|
||||
await searchMessages(
|
||||
account.incomingServer.rootFolder,
|
||||
foundMessages,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return messageListTracker.startList(
|
||||
collectionArray,
|
||||
context.extension
|
||||
);
|
||||
return messageListTracker.startList(foundMessages, context.extension);
|
||||
},
|
||||
async update(messageId, newProperties) {
|
||||
let msgHdr = messageTracker.getMessage(messageId);
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
},
|
||||
"from": {
|
||||
"$ref": "ComposeRecipient",
|
||||
"description": "*Caution*: Setting a value for `from` does not change the used identity, it overrides the FROM header. Many e-mail servers do not accept emails where the FROM header does not match the sender identity. Must be set to exactly one valid e-mail address.",
|
||||
"description": "*Caution*: Setting a value for `from` does not change the used identity, it overrides the FROM header. Many email servers do not accept emails where the FROM header does not match the sender identity. Must be set to exactly one valid email address.",
|
||||
"optional": true
|
||||
},
|
||||
"to": {
|
||||
|
|
|
@ -348,56 +348,21 @@
|
|||
"type": "object",
|
||||
"name": "queryInfo",
|
||||
"properties": {
|
||||
"subject": {
|
||||
"author": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with this value matching the subject."
|
||||
},
|
||||
"fullText": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with this value somewhere in the mail (subject, body or author)."
|
||||
"description": "Returns only messages with this value matching the author. The search value is a single email address, a name or a combination (e.g.: ``Name <user@domain.org>``). The address part of the search value (if provided) must match the author's address completely. The name part of the search value (if provided) must match the author's name partially. All matches are done case-insensitive."
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with this value in the body of the mail."
|
||||
},
|
||||
"author": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with this value matching the author."
|
||||
},
|
||||
"recipients": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with this value matching one or more recipients."
|
||||
},
|
||||
"fromMe": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with the author matching any configured identity."
|
||||
},
|
||||
"toMe": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with one or more recipients matching any configured identity."
|
||||
},
|
||||
"unread": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Returns only unread (or read if false) messages."
|
||||
},
|
||||
"flagged": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Returns only flagged (or unflagged if false) messages."
|
||||
},
|
||||
"tags": {
|
||||
"$ref": "TagsDetail",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with the specified tags. For a list of available tags, call the listTags method. Querying for messages that must *not* have a tag does not work."
|
||||
},
|
||||
"folder": {
|
||||
"$ref": "folders.MailFolder",
|
||||
"optional": true,
|
||||
|
@ -408,15 +373,55 @@
|
|||
"optional": true,
|
||||
"description": "Returns only messages with a date after this value."
|
||||
},
|
||||
"toDate": {
|
||||
"$ref": "extensionTypes.Date",
|
||||
"fromMe": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with a date before this value."
|
||||
"description": "Returns only messages with the author's address matching any configured identity."
|
||||
},
|
||||
"fullText": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with this value somewhere in the mail (subject, body or author)."
|
||||
},
|
||||
"headerMessageId": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with a Message-ID header matching this value."
|
||||
},
|
||||
"includeSubFolders": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Search the folder specified by ``queryInfo.folder`` recursively."
|
||||
},
|
||||
"recipients": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Returns only messages whose recipients match all specified addresses. The search value is a semicolon separated list of email addresses, names or combinations (e.g.: ``Name <user@domain.org>``). For a match, all specified addresses must equal a recipient's address completely and all specified names must match a recipient's name partially. All matches are done case-insensitive."
|
||||
},
|
||||
"subject": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with this value matching the subject."
|
||||
},
|
||||
"tags": {
|
||||
"$ref": "TagsDetail",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with the specified tags. For a list of available tags, call the :ref:`messages.listTags` method."
|
||||
},
|
||||
"toDate": {
|
||||
"$ref": "extensionTypes.Date",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with a date before this value."
|
||||
},
|
||||
"toMe": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Returns only messages with at least one recipient address matching any configured identity."
|
||||
},
|
||||
"unread": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Returns only unread (or read if false) messages."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,6 +102,38 @@ function createMessages(folder, makeMessagesArg) {
|
|||
}
|
||||
|
||||
let messages = createMessages.messageGenerator.makeMessages(makeMessagesArg);
|
||||
return addGeneratedMessages(folder, messages);
|
||||
}
|
||||
|
||||
class FakeGeneratedMessage {
|
||||
constructor(msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
toMessageString() {
|
||||
return this.msg;
|
||||
}
|
||||
toMboxString() {
|
||||
// A cheap hack. It works for existing uses but may not work for future uses.
|
||||
let fromAddress = this.msg.match(/From: .* <(.*@.*)>/)[0];
|
||||
let mBoxString = `From ${fromAddress}\r\n${this.msg}`;
|
||||
// Ensure a trailing empty line.
|
||||
if (!mBoxString.endsWith("\r\n")) {
|
||||
mBoxString = mBoxString + "\r\n";
|
||||
}
|
||||
return mBoxString;
|
||||
}
|
||||
}
|
||||
|
||||
async function createMessageFromFile(folder, path) {
|
||||
let message = await IOUtils.readUTF8(path);
|
||||
return addGeneratedMessages(folder, [new FakeGeneratedMessage(message)]);
|
||||
}
|
||||
|
||||
async function createMessageFromString(folder, message) {
|
||||
return addGeneratedMessages(folder, [new FakeGeneratedMessage(message)]);
|
||||
}
|
||||
|
||||
async function addGeneratedMessages(folder, messages) {
|
||||
if (folder.server.type == "imap") {
|
||||
return IMAPServer.addMessages(folder, messages);
|
||||
}
|
||||
|
@ -113,37 +145,6 @@ function createMessages(folder, makeMessagesArg) {
|
|||
folder.QueryInterface(Ci.nsIMsgLocalMailFolder);
|
||||
folder.addMessageBatch(messageStrings);
|
||||
folder.callFilterPlugins(null);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async function createMessageFromFile(folder, path) {
|
||||
let message = await IOUtils.readUTF8(path);
|
||||
return createMessageFromString(folder, message);
|
||||
}
|
||||
|
||||
async function createMessageFromString(folder, message) {
|
||||
if (folder.server.type == "imap") {
|
||||
return IMAPServer.addMessages(folder, [message]);
|
||||
}
|
||||
if (folder.server.type == "nntp") {
|
||||
return NNTPServer.addMessages(folder, [message]);
|
||||
}
|
||||
|
||||
// A cheap hack to make this acceptable to addMessageBatch. It works for
|
||||
// existing uses but may not work for future uses.
|
||||
let fromAddress = message.match(/From: .* <(.*@.*)>/)[0];
|
||||
message = `From ${fromAddress}\r\n${message}`;
|
||||
|
||||
// addMessageBatch needs a trailing empty line.
|
||||
if (!message.endsWith("\r\n")) {
|
||||
message = message + "\r\n";
|
||||
}
|
||||
|
||||
folder.QueryInterface(Ci.nsIMsgLocalMailFolder);
|
||||
folder.addMessageBatch([message]);
|
||||
folder.callFilterPlugins(null);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
Message-ID: <alternative.eml@mime.sample>
|
||||
Date: Fri, 19 May 2000 00:29:55 -0400
|
||||
To: Heinz <mueller@example.com>, Karl <friedrich@example.com>
|
||||
From: Doug Sauder <dwsauder@example.com>
|
||||
Subject: Default content-types
|
||||
Mime-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="=====================_714967308==_.ALT"
|
||||
|
||||
--=====================_714967308==_.ALT
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
I am TEXT!
|
||||
|
||||
--=====================_714967308==_.ALT
|
||||
Content-Type: text/html
|
||||
|
||||
<html><body>I <b>am</b> HTML!</body></html>
|
||||
|
||||
--=====================_714967308==_.ALT--
|
|
@ -7,232 +7,290 @@
|
|||
var { ExtensionTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/ExtensionXPCShellUtils.jsm"
|
||||
);
|
||||
var { Gloda } = ChromeUtils.import("resource:///modules/gloda/GlodaPublic.jsm");
|
||||
var { GlodaIndexer } = ChromeUtils.import(
|
||||
"resource:///modules/gloda/GlodaIndexer.jsm"
|
||||
);
|
||||
|
||||
// Create some folders and populate them.
|
||||
add_task(
|
||||
{
|
||||
skip_if: () => IS_NNTP,
|
||||
},
|
||||
async function setup() {
|
||||
GlodaIndexer._INDEX_INTERVAL = 0;
|
||||
add_task(async function setup() {
|
||||
let account = createAccount();
|
||||
let subFolders = {
|
||||
test1: await createSubfolder(account.incomingServer.rootFolder, "test1"),
|
||||
test2: await createSubfolder(account.incomingServer.rootFolder, "test2"),
|
||||
};
|
||||
await createMessages(subFolders.test1, { count: 9, age_incr: { days: 2 } });
|
||||
|
||||
let account = createAccount();
|
||||
let subFolders = {
|
||||
test1: await createSubfolder(account.incomingServer.rootFolder, "test1"),
|
||||
test2: await createSubfolder(account.incomingServer.rootFolder, "test2"),
|
||||
};
|
||||
await createMessages(subFolders.test1, { count: 9, age_incr: { days: 2 } });
|
||||
let messages = [...subFolders.test1.messages];
|
||||
// NB: Here, the messages are zero-indexed. In the test they're one-indexed.
|
||||
subFolders.test1.markMessagesRead([messages[0]], true);
|
||||
subFolders.test1.markMessagesFlagged([messages[1]], true);
|
||||
subFolders.test1.markMessagesFlagged([messages[6]], true);
|
||||
|
||||
let messages = [...subFolders.test1.messages];
|
||||
// NB: Here, the messages are zero-indexed. In the test they're one-indexed.
|
||||
subFolders.test1.markMessagesRead([messages[0]], true);
|
||||
subFolders.test1.markMessagesFlagged([messages[1]], true);
|
||||
subFolders.test1.markMessagesFlagged([messages[6]], true);
|
||||
subFolders.test1.addKeywordsToMessages(messages.slice(0, 1), "notATag");
|
||||
subFolders.test1.addKeywordsToMessages(messages.slice(2, 4), "$label2");
|
||||
subFolders.test1.addKeywordsToMessages(messages.slice(3, 6), "$label3");
|
||||
|
||||
subFolders.test1.addKeywordsToMessages(messages.slice(0, 1), "notATag");
|
||||
subFolders.test1.addKeywordsToMessages(messages.slice(2, 4), "$label2");
|
||||
subFolders.test1.addKeywordsToMessages(messages.slice(3, 6), "$label3");
|
||||
|
||||
addIdentity(account, messages[5].author.replace(/.*<(.*)>/, "$1"));
|
||||
addIdentity(account, messages[5].author.replace(/.*<(.*)>/, "$1"));
|
||||
// No recipient support for NNTP.
|
||||
if (account.incomingServer.type != "nntp") {
|
||||
addIdentity(account, messages[2].recipients.replace(/.*<(.*)>/, "$1"));
|
||||
Gloda._initMyIdentities();
|
||||
}
|
||||
|
||||
// Wait for Gloda to re-index the added messages.
|
||||
await new Promise(resolve => {
|
||||
let waiting = false;
|
||||
GlodaIndexer.addListener(function indexListener(status) {
|
||||
if (status == Gloda.kIndexerIdle && !GlodaIndexer.indexing && waiting) {
|
||||
GlodaIndexer.removeListener(indexListener);
|
||||
resolve();
|
||||
}
|
||||
await createMessages(subFolders.test2, { count: 7, age_incr: { days: 2 } });
|
||||
// Email with multipart/alternative.
|
||||
await createMessageFromFile(
|
||||
subFolders.test2,
|
||||
do_get_file("messages/alternative.eml").path
|
||||
);
|
||||
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
let [accountId] = await window.waitForMessage();
|
||||
let _account = await browser.accounts.get(accountId);
|
||||
let accountType = _account.type;
|
||||
|
||||
let messages1 = await browser.messages.list({
|
||||
accountId,
|
||||
path: "/test1",
|
||||
});
|
||||
waiting = true;
|
||||
});
|
||||
|
||||
await createMessages(subFolders.test2, { count: 7, age_incr: { days: 2 } });
|
||||
|
||||
// Wait for Gloda to re-index the added messages.
|
||||
await new Promise(resolve => {
|
||||
let waiting = false;
|
||||
GlodaIndexer.addListener(function indexListener(status) {
|
||||
if (status == Gloda.kIndexerIdle && !GlodaIndexer.indexing && waiting) {
|
||||
GlodaIndexer.removeListener(indexListener);
|
||||
resolve();
|
||||
}
|
||||
browser.test.assertEq(9, messages1.messages.length);
|
||||
let messages2 = await browser.messages.list({
|
||||
accountId,
|
||||
path: "/test2",
|
||||
});
|
||||
waiting = true;
|
||||
});
|
||||
browser.test.assertEq(8, messages2.messages.length);
|
||||
|
||||
registerCleanupFunction(() => GlodaIndexer._shutdown());
|
||||
// Check all messages are returned.
|
||||
let { messages: allMessages } = await browser.messages.query({});
|
||||
browser.test.assertEq(17, allMessages.length);
|
||||
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
let [accountId] = await window.waitForMessage();
|
||||
let folder1 = { accountId, path: "/test1" };
|
||||
let folder2 = { accountId, path: "/test2" };
|
||||
let rootFolder = { accountId, path: "/" };
|
||||
|
||||
let messages1 = await browser.messages.list({
|
||||
accountId,
|
||||
path: "/test1",
|
||||
});
|
||||
browser.test.assertEq(9, messages1.messages.length);
|
||||
let messages2 = await browser.messages.list({
|
||||
accountId,
|
||||
path: "/test2",
|
||||
});
|
||||
browser.test.assertEq(7, messages2.messages.length);
|
||||
// Query messages from test1. No messages from test2 should be returned.
|
||||
// We'll use these messages as a reference for further tests.
|
||||
let { messages: referenceMessages } = await browser.messages.query({
|
||||
folder: folder1,
|
||||
});
|
||||
browser.test.assertEq(9, referenceMessages.length);
|
||||
browser.test.assertTrue(
|
||||
referenceMessages.every(m => m.folder.path == "/test1")
|
||||
);
|
||||
|
||||
// Check all messages are returned.
|
||||
let { messages: allMessages } = await browser.messages.query({});
|
||||
browser.test.assertEq(16, allMessages.length);
|
||||
// Test includeSubFolders: Default (False).
|
||||
let { messages: searchRecursiveDefault } = await browser.messages.query({
|
||||
folder: rootFolder,
|
||||
});
|
||||
browser.test.assertEq(
|
||||
0,
|
||||
searchRecursiveDefault.length,
|
||||
"includeSubFolders: Default"
|
||||
);
|
||||
|
||||
let folder = { accountId, path: "/test1" };
|
||||
// Test includeSubFolders: True.
|
||||
let { messages: searchRecursiveTrue } = await browser.messages.query({
|
||||
folder: rootFolder,
|
||||
includeSubFolders: true,
|
||||
});
|
||||
browser.test.assertEq(
|
||||
17,
|
||||
searchRecursiveTrue.length,
|
||||
"includeSubFolders: True"
|
||||
);
|
||||
|
||||
// Query messages from test1. No messages from test2 should be returned.
|
||||
// We'll use these messages as a reference for further tests.
|
||||
let { messages: referenceMessages } = await browser.messages.query({
|
||||
folder,
|
||||
});
|
||||
browser.test.assertEq(9, referenceMessages.length);
|
||||
browser.test.assertTrue(
|
||||
referenceMessages.every(m => m.folder.path == "/test1")
|
||||
// Test includeSubFolders: False.
|
||||
let { messages: searchRecursiveFalse } = await browser.messages.query({
|
||||
folder: rootFolder,
|
||||
includeSubFolders: false,
|
||||
});
|
||||
browser.test.assertEq(
|
||||
0,
|
||||
searchRecursiveFalse.length,
|
||||
"includeSubFolders: False"
|
||||
);
|
||||
|
||||
// Dump the reference messages to the console for easier debugging.
|
||||
browser.test.log("Reference messages:");
|
||||
for (let m of referenceMessages) {
|
||||
let date = m.date.toISOString().substring(0, 10);
|
||||
let author = m.author.replace(/"(.*)".*/, "$1").padEnd(16, " ");
|
||||
// No recipient support for NNTP.
|
||||
let recipients =
|
||||
accountType == "nntp"
|
||||
? ""
|
||||
: m.recipients[0].replace(/(.*) <.*>/, "$1").padEnd(16, " ");
|
||||
browser.test.log(
|
||||
`[${m.id}] ${date} From: ${author} To: ${recipients} Subject: ${m.subject}`
|
||||
);
|
||||
}
|
||||
|
||||
let subtest = async function(queryInfo, ...expectedMessageIndices) {
|
||||
if (!queryInfo.folder) {
|
||||
queryInfo.folder = folder1;
|
||||
}
|
||||
browser.test.log("Testing " + JSON.stringify(queryInfo));
|
||||
let { messages: actualMessages } = await browser.messages.query(
|
||||
queryInfo
|
||||
);
|
||||
|
||||
// Dump the reference messages to the console for easier debugging.
|
||||
browser.test.log("Reference messages:");
|
||||
for (let m of referenceMessages) {
|
||||
let date = m.date.toISOString().substring(0, 10);
|
||||
let author = m.author.replace(/"(.*)".*/, "$1").padEnd(16, " ");
|
||||
let recipients = m.recipients[0]
|
||||
.replace(/(.*) <.*>/, "$1")
|
||||
.padEnd(16, " ");
|
||||
browser.test.log(
|
||||
`[${m.id}] ${date} From: ${author} To: ${recipients} Subject: ${m.subject}`
|
||||
);
|
||||
}
|
||||
|
||||
let subtest = async function(queryInfo, ...expectedMessageIndices) {
|
||||
browser.test.log("Testing " + JSON.stringify(queryInfo));
|
||||
queryInfo.folder = folder;
|
||||
let { messages: actualMessages } = await browser.messages.query(
|
||||
queryInfo
|
||||
);
|
||||
|
||||
browser.test.assertEq(
|
||||
expectedMessageIndices.length,
|
||||
actualMessages.length,
|
||||
"Correct number of messages"
|
||||
);
|
||||
for (let index of expectedMessageIndices) {
|
||||
// browser.test.log(`Looking for message ${index}`);
|
||||
if (!actualMessages.some(am => am.id == index)) {
|
||||
browser.test.fail(`Message ${index} was not returned`);
|
||||
browser.test.log(
|
||||
"These messages were returned: " +
|
||||
actualMessages.map(am => am.id)
|
||||
);
|
||||
}
|
||||
browser.test.assertEq(
|
||||
expectedMessageIndices.length,
|
||||
actualMessages.length,
|
||||
"Correct number of messages"
|
||||
);
|
||||
for (let index of expectedMessageIndices) {
|
||||
// browser.test.log(`Looking for message ${index}`);
|
||||
if (!actualMessages.some(am => am.id == index)) {
|
||||
browser.test.fail(`Message ${index} was not returned`);
|
||||
browser.test.log(
|
||||
"These messages were returned: " + actualMessages.map(am => am.id)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Date range query. The messages are 0 days old, 2 days old, 4 days old, etc..
|
||||
let today = new Date();
|
||||
let date1 = new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() - 5
|
||||
);
|
||||
let date2 = new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() - 11
|
||||
);
|
||||
await subtest({ fromDate: today });
|
||||
await subtest({ fromDate: date1 }, 1, 2, 3);
|
||||
await subtest({ fromDate: date2 }, 1, 2, 3, 4, 5, 6);
|
||||
await subtest({ toDate: date1 }, 4, 5, 6, 7, 8, 9);
|
||||
await subtest({ toDate: date2 }, 7, 8, 9);
|
||||
await subtest({ fromDate: date1, toDate: date2 });
|
||||
await subtest({ fromDate: date2, toDate: date1 }, 4, 5, 6);
|
||||
// Date range query. The messages are 0 days old, 2 days old, 4 days old, etc..
|
||||
let today = new Date();
|
||||
let date1 = new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() - 5
|
||||
);
|
||||
let date2 = new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() - 11
|
||||
);
|
||||
await subtest({ fromDate: today });
|
||||
await subtest({ fromDate: date1 }, 1, 2, 3);
|
||||
await subtest({ fromDate: date2 }, 1, 2, 3, 4, 5, 6);
|
||||
await subtest({ toDate: date1 }, 4, 5, 6, 7, 8, 9);
|
||||
await subtest({ toDate: date2 }, 7, 8, 9);
|
||||
await subtest({ fromDate: date1, toDate: date2 });
|
||||
await subtest({ fromDate: date2, toDate: date1 }, 4, 5, 6);
|
||||
|
||||
// Unread query. Only message 1 has been read.
|
||||
await subtest({ unread: false }, 1);
|
||||
await subtest({ unread: true }, 2, 3, 4, 5, 6, 7, 8, 9);
|
||||
// Unread query. Only message 1 has been read.
|
||||
await subtest({ unread: false }, 1);
|
||||
await subtest({ unread: true }, 2, 3, 4, 5, 6, 7, 8, 9);
|
||||
|
||||
// Flagged query. Messages 2 and 7 are flagged.
|
||||
await subtest({ flagged: true }, 2, 7);
|
||||
await subtest({ flagged: false }, 1, 3, 4, 5, 6, 8, 9);
|
||||
// Flagged query. Messages 2 and 7 are flagged.
|
||||
await subtest({ flagged: true }, 2, 7);
|
||||
await subtest({ flagged: false }, 1, 3, 4, 5, 6, 8, 9);
|
||||
|
||||
// Subject query.
|
||||
let keyword = referenceMessages[1].subject.split(" ")[1];
|
||||
await subtest({ subject: keyword }, 2);
|
||||
await subtest({ fullText: keyword }, 2);
|
||||
// Subject query.
|
||||
let keyword = referenceMessages[1].subject.split(" ")[1];
|
||||
await subtest({ subject: keyword }, 2);
|
||||
await subtest({ fullText: keyword }, 2);
|
||||
|
||||
// Author query.
|
||||
keyword = referenceMessages[2].author.replace('"', "").split(" ")[0];
|
||||
await subtest({ author: keyword }, 3);
|
||||
await subtest({ fullText: keyword }, 3);
|
||||
// Author query.
|
||||
keyword = referenceMessages[2].author.replace('"', "").split(" ")[0];
|
||||
await subtest({ author: keyword }, 3);
|
||||
await subtest({ fullText: keyword }, 3);
|
||||
|
||||
// Recipients query.
|
||||
// Recipients query.
|
||||
// No recipient support for NNTP.
|
||||
if (accountType != "nntp") {
|
||||
keyword = referenceMessages[7].recipients[0].split(" ")[0];
|
||||
await subtest({ recipients: keyword }, 8);
|
||||
await subtest({ fullText: keyword }, 8);
|
||||
await subtest({ body: keyword }, 8);
|
||||
}
|
||||
|
||||
// From Me and To Me. These use the identities added to account.
|
||||
await subtest({ fromMe: true }, 6);
|
||||
// From Me and To Me. These use the identities added to account.
|
||||
await subtest({ fromMe: true }, 6);
|
||||
// No recipient support for NNTP.
|
||||
if (accountType != "nntp") {
|
||||
await subtest({ toMe: true }, 3);
|
||||
}
|
||||
|
||||
// Tags query.
|
||||
await subtest({ tags: { mode: "any", tags: { notATag: true } } });
|
||||
await subtest({ tags: { mode: "any", tags: { $label2: true } } }, 3, 4);
|
||||
// Tags query.
|
||||
await subtest({ tags: { mode: "any", tags: { notATag: true } } });
|
||||
await subtest({ tags: { mode: "any", tags: { $label2: true } } }, 3, 4);
|
||||
await subtest(
|
||||
{ tags: { mode: "any", tags: { $label3: true } } },
|
||||
4,
|
||||
5,
|
||||
6
|
||||
);
|
||||
await subtest(
|
||||
{ tags: { mode: "any", tags: { $label2: true, $label3: true } } },
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
);
|
||||
await subtest({
|
||||
tags: { mode: "all", tags: { $label1: true, $label2: true } },
|
||||
});
|
||||
await subtest(
|
||||
{ tags: { mode: "all", tags: { $label2: true, $label3: true } } },
|
||||
4
|
||||
);
|
||||
await subtest(
|
||||
{ tags: { mode: "any", tags: { $label2: false, $label3: false } } },
|
||||
1,
|
||||
2,
|
||||
7,
|
||||
8,
|
||||
9
|
||||
);
|
||||
await subtest(
|
||||
{ tags: { mode: "all", tags: { $label2: false, $label3: false } } },
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9
|
||||
);
|
||||
|
||||
// headerMessageId query
|
||||
await subtest({ headerMessageId: "0@made.up.invalid" }, 1);
|
||||
await subtest({ headerMessageId: "7@made.up.invalid" }, 8);
|
||||
await subtest({ headerMessageId: "8@made.up.invalid" }, 9);
|
||||
await subtest({ headerMessageId: "unknown@made.up.invalid" });
|
||||
|
||||
// text in nested html part of multipart/alternative
|
||||
await subtest({ folder: folder2, body: "I am HTML!" }, 17);
|
||||
|
||||
// No recipient support for NNTP.
|
||||
if (accountType != "nntp") {
|
||||
// advanced search on recipients
|
||||
await subtest({ folder: folder2, recipients: "karl; heinz" }, 17);
|
||||
await subtest(
|
||||
{ tags: { mode: "any", tags: { $label3: true } } },
|
||||
4,
|
||||
5,
|
||||
6
|
||||
{ folder: folder2, recipients: "<friedrich@example.COM>; HEINZ" },
|
||||
17
|
||||
);
|
||||
await subtest(
|
||||
{ tags: { mode: "any", tags: { $label2: true, $label3: true } } },
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
{
|
||||
folder: folder2,
|
||||
recipients: "karl <friedrich@example.COM>; HEINZ",
|
||||
},
|
||||
17
|
||||
);
|
||||
await subtest({
|
||||
tags: { mode: "all", tags: { $label1: true, $label2: true } },
|
||||
folder: folder2,
|
||||
recipients: "Heinz <friedrich@example.COM>; Karl",
|
||||
});
|
||||
await subtest(
|
||||
{ tags: { mode: "all", tags: { $label2: true, $label3: true } } },
|
||||
4
|
||||
);
|
||||
}
|
||||
|
||||
// headerMessageId query
|
||||
await subtest({ headerMessageId: "0@made.up.invalid" }, 1);
|
||||
await subtest({ headerMessageId: "7@made.up.invalid" }, 8);
|
||||
await subtest({ headerMessageId: "8@made.up.invalid" }, 9);
|
||||
await subtest({ headerMessageId: "unknown@made.up.invalid" });
|
||||
browser.test.notifyPass("finished");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "messagesRead"],
|
||||
},
|
||||
});
|
||||
|
||||
browser.test.notifyPass("finished");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "messagesRead"],
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
extension.sendMessage(account.key);
|
||||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
}
|
||||
);
|
||||
await extension.startup();
|
||||
extension.sendMessage(account.key);
|
||||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
// Make sure any open address book database is given a chance to close.
|
||||
|
|
|
@ -7,4 +7,5 @@ tags = addrbook
|
|||
support-files = messages/**
|
||||
[test_ext_messages_id.js] # NNTP disabled (message move not supported).
|
||||
[test_ext_messages_onNewMailReceived.js]
|
||||
[test_ext_messages_query.js] # NNTP disabled (gloda fails).
|
||||
[test_ext_messages_query.js]
|
||||
support-files = messages/alternative.eml
|
||||
|
|
Загрузка…
Ссылка в новой задаче