Bug 1797764 - Part 1 : Support MV3 persistent events and background restart. r=darktrojan
Differential Revision: https://phabricator.services.mozilla.com/D160520 --HG-- extra : rebase_source : 213cb0d9a0795bdd7ebc1452b81289fbd0e9bbcb
This commit is contained in:
Родитель
ed9ae86a6f
Коммит
747d6cacff
|
@ -30,7 +30,7 @@ const { ExtensionParent } = ChromeUtils.import(
|
|||
"resource://gre/modules/ExtensionParent.jsm"
|
||||
);
|
||||
|
||||
var { EventManager, ExtensionAPI, makeWidgetId } = ExtensionCommon;
|
||||
var { EventManager, ExtensionAPIPersistent, makeWidgetId } = ExtensionCommon;
|
||||
|
||||
var { IconDetails, StartupCache } = ExtensionParent;
|
||||
|
||||
|
@ -102,7 +102,7 @@ function getIconData(icons, extension) {
|
|||
return { style, legacy, realIcon };
|
||||
}
|
||||
|
||||
var ToolbarButtonAPI = class extends ExtensionAPI {
|
||||
var ToolbarButtonAPI = class extends ExtensionAPIPersistent {
|
||||
constructor(extension, global) {
|
||||
super(extension);
|
||||
this.global = global;
|
||||
|
@ -123,7 +123,10 @@ var ToolbarButtonAPI = class extends ExtensionAPI {
|
|||
this.unpaint = this.unpaint.bind(this);
|
||||
|
||||
this.widgetId = makeWidgetId(extension.id);
|
||||
this.id = `${this.widgetId}-${this.manifestName}-toolbarbutton`;
|
||||
this.id =
|
||||
this.manifestName == "action"
|
||||
? `${this.widgetId}-browserAction-toolbarbutton`
|
||||
: `${this.widgetId}-${this.manifestName}-toolbarbutton`;
|
||||
|
||||
this.eventQueue = [];
|
||||
|
||||
|
@ -442,7 +445,7 @@ var ToolbarButtonAPI = class extends ExtensionAPI {
|
|||
if (!this.lastClickInfo) {
|
||||
this.lastClickInfo = { button: 0, modifiers: [] };
|
||||
}
|
||||
this.emit("click", window);
|
||||
this.emit("click", window, this.lastClickInfo);
|
||||
}
|
||||
}
|
||||
delete this.lastClickInfo;
|
||||
|
@ -701,6 +704,35 @@ var ToolbarButtonAPI = class extends ExtensionAPI {
|
|||
return this.getContextData(this.getTargetFromDetails(details))[prop];
|
||||
}
|
||||
|
||||
PERSISTENT_EVENTS = {
|
||||
onClicked({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager, windowManager } = extension;
|
||||
|
||||
async function listener(_event, window, clickInfo) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
|
||||
// TODO: We should double-check if the tab is already being closed by the time
|
||||
// the background script got started and we converted the primed listener.
|
||||
|
||||
let win = windowManager.wrapWindow(window);
|
||||
fire.sync(tabManager.convert(win.activeTab.nativeTab), clickInfo);
|
||||
}
|
||||
this.on("click", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
this.off("click", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* WebExtension API.
|
||||
*
|
||||
|
@ -708,7 +740,6 @@ var ToolbarButtonAPI = class extends ExtensionAPI {
|
|||
*/
|
||||
getAPI(context) {
|
||||
let { extension } = context;
|
||||
let { tabManager, windowManager } = extension;
|
||||
|
||||
let action = this;
|
||||
|
||||
|
@ -716,21 +747,14 @@ var ToolbarButtonAPI = class extends ExtensionAPI {
|
|||
[this.manifestName]: {
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: `${this.manifestName}.onClicked`,
|
||||
// browserAction was renamed to action in MV3, but its module name is
|
||||
// still "browserAction" because that is the name used in ext-mail.json,
|
||||
// independently from the manifest version.
|
||||
module:
|
||||
this.manifestName == "action" ? "browserAction" : this.manifestName,
|
||||
event: "onClicked",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = (event, window) => {
|
||||
let win = windowManager.wrapWindow(window);
|
||||
fire.sync(
|
||||
tabManager.convert(win.activeTab.nativeTab),
|
||||
this.lastClickInfo
|
||||
);
|
||||
};
|
||||
action.on("click", listener);
|
||||
return () => {
|
||||
action.off("click", listener);
|
||||
};
|
||||
},
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
async enable(tabId) {
|
||||
|
|
|
@ -150,15 +150,78 @@ var accountsTracker = new (class extends EventEmitter {
|
|||
}
|
||||
})();
|
||||
|
||||
this.accounts = class extends ExtensionAPI {
|
||||
close() {
|
||||
this.accounts = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onCreated({ context, fire }) {
|
||||
async function listener(_event, key, account) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(key, account);
|
||||
}
|
||||
accountsTracker.on("account-added", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
accountsTracker.off("account-added", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onUpdated({ context, fire }) {
|
||||
async function listener(_event, key, changedValues) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(key, changedValues);
|
||||
}
|
||||
accountsTracker.on("account-updated", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
accountsTracker.off("account-updated", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onDeleted({ context, fire }) {
|
||||
async function listener(_event, key) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(key);
|
||||
}
|
||||
accountsTracker.on("account-removed", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
accountsTracker.off("account-removed", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
accountsTracker.incrementListeners();
|
||||
}
|
||||
|
||||
onShutdown() {
|
||||
accountsTracker.decrementListeners();
|
||||
}
|
||||
|
||||
getAPI(context) {
|
||||
context.callOnClose(this);
|
||||
accountsTracker.incrementListeners();
|
||||
|
||||
return {
|
||||
accounts: {
|
||||
async list(includeFolders) {
|
||||
|
@ -200,45 +263,21 @@ this.accounts = class extends ExtensionAPI {
|
|||
},
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "accounts.onCreated",
|
||||
register: fire => {
|
||||
let listener = (event, key, account) => {
|
||||
fire.sync(key, account);
|
||||
};
|
||||
|
||||
accountsTracker.on("account-added", listener);
|
||||
return () => {
|
||||
accountsTracker.off("account-added", listener);
|
||||
};
|
||||
},
|
||||
module: "accounts",
|
||||
event: "onCreated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "accounts.onUpdated",
|
||||
register: fire => {
|
||||
let listener = (event, key, changedValues) => {
|
||||
fire.sync(key, changedValues);
|
||||
};
|
||||
|
||||
accountsTracker.on("account-updated", listener);
|
||||
return () => {
|
||||
accountsTracker.off("account-updated", listener);
|
||||
};
|
||||
},
|
||||
module: "accounts",
|
||||
event: "onUpdated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onDeleted: new EventManager({
|
||||
context,
|
||||
name: "accounts.onDeleted",
|
||||
register: fire => {
|
||||
let listener = (event, key) => {
|
||||
fire.sync(key);
|
||||
};
|
||||
|
||||
accountsTracker.on("account-removed", listener);
|
||||
return () => {
|
||||
accountsTracker.off("account-removed", listener);
|
||||
};
|
||||
},
|
||||
module: "accounts",
|
||||
event: "onDeleted",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -349,6 +349,9 @@ class ExtSearchBook extends AddrBookDirectory {
|
|||
setLocalizedStringValue(aName, aValue) {}
|
||||
async search(aQuery, aSearchString, aListener) {
|
||||
try {
|
||||
if (this.fire.wakeup) {
|
||||
await this.fire.wakeup();
|
||||
}
|
||||
let { results, isCompleteResult } = await this.fire.async(
|
||||
await addressBookCache.convert(
|
||||
addressBookCache.addressBooks.get(this.UID)
|
||||
|
@ -829,15 +832,258 @@ var addressBookCache = new (class extends EventEmitter {
|
|||
}
|
||||
})();
|
||||
|
||||
this.addressBook = class extends ExtensionAPI {
|
||||
close() {
|
||||
this.addressBook = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
// addressBooks.*
|
||||
onAddressBookCreated({ context, fire }) {
|
||||
let listener = async (event, node) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
addressBookCache.on("address-book-created", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("address-book-created", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onAddressBookUpdated({ context, fire }) {
|
||||
let listener = async (event, node) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
addressBookCache.on("address-book-updated", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("address-book-updated", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onAddressBookDeleted({ context, fire }) {
|
||||
let listener = async (event, itemUID) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(itemUID);
|
||||
};
|
||||
addressBookCache.on("address-book-deleted", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("address-book-deleted", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// contacts.*
|
||||
onContactCreated({ context, fire }) {
|
||||
let listener = async (event, node) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
addressBookCache.on("contact-created", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("contact-created", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onContactUpdated({ context, fire }) {
|
||||
let listener = async (event, node, changes) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let filteredChanges = {};
|
||||
// Find changes in flat properties stored in the vCard.
|
||||
if (changes.hasOwnProperty("_vCard")) {
|
||||
let oldVCardProperties = VCardProperties.fromVCard(
|
||||
changes._vCard.oldValue
|
||||
).toPropertyMap();
|
||||
let newVCardProperties = VCardProperties.fromVCard(
|
||||
changes._vCard.newValue
|
||||
).toPropertyMap();
|
||||
for (let [name, value] of oldVCardProperties) {
|
||||
if (newVCardProperties.get(name) != value) {
|
||||
filteredChanges[name] = {
|
||||
oldValue: value,
|
||||
newValue: newVCardProperties.get(name) ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
for (let [name, value] of newVCardProperties) {
|
||||
if (
|
||||
!filteredChanges.hasOwnProperty(name) &&
|
||||
oldVCardProperties.get(name) != value
|
||||
) {
|
||||
filteredChanges[name] = {
|
||||
oldValue: oldVCardProperties.get(name) ?? null,
|
||||
newValue: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let [name, value] of Object.entries(changes)) {
|
||||
if (!filteredChanges.hasOwnProperty(name) && isCustomProperty(name)) {
|
||||
filteredChanges[name] = value;
|
||||
}
|
||||
}
|
||||
fire.sync(await addressBookCache.convert(node), filteredChanges);
|
||||
};
|
||||
addressBookCache.on("contact-updated", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("contact-updated", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onContactDeleted({ context, fire }) {
|
||||
let listener = async (event, parentUID, itemUID) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(parentUID, itemUID);
|
||||
};
|
||||
addressBookCache.on("contact-deleted", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("contact-deleted", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// mailingLists.*
|
||||
onMailingListCreated({ context, fire }) {
|
||||
let listener = async (event, node) => {
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
addressBookCache.on("mailing-list-created", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("mailing-list-created", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onMailingListUpdated({ context, fire }) {
|
||||
let listener = async (event, node) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
addressBookCache.on("mailing-list-updated", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("mailing-list-updated", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onMailingListDeleted({ context, fire }) {
|
||||
let listener = async (event, parentUID, itemUID) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(parentUID, itemUID);
|
||||
};
|
||||
addressBookCache.on("mailing-list-deleted", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("mailing-list-deleted", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onMemberAdded({ context, fire }) {
|
||||
let listener = async (event, node) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
addressBookCache.on("mailing-list-member-added", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("mailing-list-member-added", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onMemberRemoved({ context, fire }) {
|
||||
let listener = async (event, parentUID, itemUID) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(parentUID, itemUID);
|
||||
};
|
||||
addressBookCache.on("mailing-list-member-removed", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
addressBookCache.off("mailing-list-member-removed", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
addressBookCache.incrementListeners();
|
||||
}
|
||||
|
||||
onShutdown() {
|
||||
addressBookCache.decrementListeners();
|
||||
}
|
||||
|
||||
getAPI(context) {
|
||||
context.callOnClose(this);
|
||||
addressBookCache.incrementListeners();
|
||||
|
||||
return {
|
||||
addressBooks: {
|
||||
async openUI() {
|
||||
|
@ -894,47 +1140,24 @@ this.addressBook = class extends ExtensionAPI {
|
|||
await deletePromise;
|
||||
},
|
||||
|
||||
// The module name is addressBook as defined in ext-mail.json.
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "addressBooks.onCreated",
|
||||
register: fire => {
|
||||
let listener = async (event, node) => {
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
|
||||
addressBookCache.on("address-book-created", listener);
|
||||
return () => {
|
||||
addressBookCache.off("address-book-created", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onAddressBookCreated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "addressBooks.onUpdated",
|
||||
register: fire => {
|
||||
let listener = async (event, node) => {
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
|
||||
addressBookCache.on("address-book-updated", listener);
|
||||
return () => {
|
||||
addressBookCache.off("address-book-updated", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onAddressBookUpdated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onDeleted: new EventManager({
|
||||
context,
|
||||
name: "addressBooks.onDeleted",
|
||||
register: fire => {
|
||||
let listener = (event, itemUID) => {
|
||||
fire.sync(itemUID);
|
||||
};
|
||||
|
||||
addressBookCache.on("address-book-deleted", listener);
|
||||
return () => {
|
||||
addressBookCache.off("address-book-deleted", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onAddressBookDeleted",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
provider: {
|
||||
|
@ -1208,84 +1431,24 @@ this.addressBook = class extends ExtensionAPI {
|
|||
parentNode.item.deleteCards([node.item]);
|
||||
},
|
||||
|
||||
// The module name is addressBook as defined in ext-mail.json.
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "contacts.onCreated",
|
||||
register: fire => {
|
||||
let listener = async (event, node) => {
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
|
||||
addressBookCache.on("contact-created", listener);
|
||||
return () => {
|
||||
addressBookCache.off("contact-created", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onContactCreated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "contacts.onUpdated",
|
||||
register: fire => {
|
||||
let listener = async (event, node, changes) => {
|
||||
let filteredChanges = {};
|
||||
// Find changes in flat properties stored in the vCard.
|
||||
if (changes.hasOwnProperty("_vCard")) {
|
||||
let oldVCardProperties = VCardProperties.fromVCard(
|
||||
changes._vCard.oldValue
|
||||
).toPropertyMap();
|
||||
let newVCardProperties = VCardProperties.fromVCard(
|
||||
changes._vCard.newValue
|
||||
).toPropertyMap();
|
||||
for (let [name, value] of oldVCardProperties) {
|
||||
if (newVCardProperties.get(name) != value) {
|
||||
filteredChanges[name] = {
|
||||
oldValue: value,
|
||||
newValue: newVCardProperties.get(name) ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
for (let [name, value] of newVCardProperties) {
|
||||
if (
|
||||
!filteredChanges.hasOwnProperty(name) &&
|
||||
oldVCardProperties.get(name) != value
|
||||
) {
|
||||
filteredChanges[name] = {
|
||||
oldValue: oldVCardProperties.get(name) ?? null,
|
||||
newValue: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let [name, value] of Object.entries(changes)) {
|
||||
if (
|
||||
!filteredChanges.hasOwnProperty(name) &&
|
||||
isCustomProperty(name)
|
||||
) {
|
||||
filteredChanges[name] = value;
|
||||
}
|
||||
}
|
||||
fire.sync(await addressBookCache.convert(node), filteredChanges);
|
||||
};
|
||||
|
||||
addressBookCache.on("contact-updated", listener);
|
||||
return () => {
|
||||
addressBookCache.off("contact-updated", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onContactUpdated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onDeleted: new EventManager({
|
||||
context,
|
||||
name: "contacts.onDeleted",
|
||||
register: fire => {
|
||||
let listener = (event, parentUID, itemUID) => {
|
||||
fire.sync(parentUID, itemUID);
|
||||
};
|
||||
|
||||
addressBookCache.on("contact-deleted", listener);
|
||||
return () => {
|
||||
addressBookCache.off("contact-deleted", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onContactDeleted",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
},
|
||||
mailingLists: {
|
||||
|
@ -1375,75 +1538,36 @@ this.addressBook = class extends ExtensionAPI {
|
|||
node.item.deleteCards([contactNode.item]);
|
||||
},
|
||||
|
||||
// The module name is addressBook as defined in ext-mail.json.
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "mailingLists.onCreated",
|
||||
register: fire => {
|
||||
let listener = async (event, node) => {
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
|
||||
addressBookCache.on("mailing-list-created", listener);
|
||||
return () => {
|
||||
addressBookCache.off("mailing-list-created", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onMailingListCreated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "mailingLists.onUpdated",
|
||||
register: fire => {
|
||||
let listener = async (event, node) => {
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
|
||||
addressBookCache.on("mailing-list-updated", listener);
|
||||
return () => {
|
||||
addressBookCache.off("mailing-list-updated", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onMailingListUpdated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onDeleted: new EventManager({
|
||||
context,
|
||||
name: "mailingLists.onDeleted",
|
||||
register: fire => {
|
||||
let listener = (event, parentUID, itemUID) => {
|
||||
fire.sync(parentUID, itemUID);
|
||||
};
|
||||
|
||||
addressBookCache.on("mailing-list-deleted", listener);
|
||||
return () => {
|
||||
addressBookCache.off("mailing-list-deleted", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onMailingListDeleted",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onMemberAdded: new EventManager({
|
||||
context,
|
||||
name: "mailingLists.onMemberAdded",
|
||||
register: fire => {
|
||||
let listener = async (event, node) => {
|
||||
fire.sync(await addressBookCache.convert(node));
|
||||
};
|
||||
|
||||
addressBookCache.on("mailing-list-member-added", listener);
|
||||
return () => {
|
||||
addressBookCache.off("mailing-list-member-added", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onMemberAdded",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onMemberRemoved: new EventManager({
|
||||
context,
|
||||
name: "mailingLists.onMemberRemoved",
|
||||
register: fire => {
|
||||
let listener = (event, parentUID, itemUID) => {
|
||||
fire.sync(parentUID, itemUID);
|
||||
};
|
||||
|
||||
addressBookCache.on("mailing-list-member-removed", listener);
|
||||
return () => {
|
||||
addressBookCache.off("mailing-list-member-removed", listener);
|
||||
};
|
||||
},
|
||||
module: "addressBook",
|
||||
event: "onMemberRemoved",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -452,11 +452,17 @@ class CloudFileAccount {
|
|||
}
|
||||
}
|
||||
|
||||
let result;
|
||||
if (uploadId != -1) {
|
||||
result = await this.extension.emit("uploadAbort", this, uploadId, window);
|
||||
if (uploadId == -1) {
|
||||
console.error(`No upload in progress for file ${file.path}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
let result = await this.extension.emit(
|
||||
"uploadAbort",
|
||||
this,
|
||||
uploadId,
|
||||
window
|
||||
);
|
||||
if (result && result.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
@ -519,7 +525,7 @@ function convertCloudFileAccount(nativeAccount) {
|
|||
};
|
||||
}
|
||||
|
||||
this.cloudFile = class extends ExtensionAPI {
|
||||
this.cloudFile = class extends ExtensionAPIPersistent {
|
||||
get providerType() {
|
||||
return `ext-${this.extension.id}`;
|
||||
}
|
||||
|
@ -555,128 +561,201 @@ this.cloudFile = class extends ExtensionAPI {
|
|||
cloudFileAccounts.unregisterProvider(this.providerType);
|
||||
}
|
||||
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onFileUpload({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(
|
||||
_event,
|
||||
account,
|
||||
{ id, name, data },
|
||||
tab,
|
||||
relatedFileInfo
|
||||
) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
tab = tab ? tabManager.convert(tab) : null;
|
||||
account = convertCloudFileAccount(account);
|
||||
return fire.async(account, { id, name, data }, tab, relatedFileInfo);
|
||||
}
|
||||
extension.on("uploadFile", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
extension.off("uploadFile", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
onFileUploadAbort({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(_event, account, id, tab) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
tab = tab ? tabManager.convert(tab) : null;
|
||||
account = convertCloudFileAccount(account);
|
||||
return fire.async(account, id, tab);
|
||||
}
|
||||
extension.on("uploadAbort", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
extension.off("uploadAbort", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
onFileRename({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(_event, account, id, newName, tab) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
tab = tab ? tabManager.convert(tab) : null;
|
||||
account = convertCloudFileAccount(account);
|
||||
return fire.async(account, id, newName, tab);
|
||||
}
|
||||
extension.on("renameFile", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
extension.off("renameFile", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
onFileDeleted({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(_event, account, id, tab) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
tab = tab ? tabManager.convert(tab) : null;
|
||||
account = convertCloudFileAccount(account);
|
||||
return fire.async(account, id, tab);
|
||||
}
|
||||
extension.on("deleteFile", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
extension.off("deleteFile", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
onAccountAdded({ context, fire }) {
|
||||
const self = this;
|
||||
async function listener(_event, nativeAccount) {
|
||||
if (nativeAccount.type != self.providerType) {
|
||||
return null;
|
||||
}
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
return fire.async(convertCloudFileAccount(nativeAccount));
|
||||
}
|
||||
cloudFileAccounts.on("accountAdded", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
cloudFileAccounts.off("accountAdded", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
onAccountDeleted({ context, fire }) {
|
||||
const self = this;
|
||||
async function listener(_event, key, type) {
|
||||
if (self.providerType != type) {
|
||||
return null;
|
||||
}
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
return fire.async(key);
|
||||
}
|
||||
cloudFileAccounts.on("accountDeleted", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
cloudFileAccounts.off("accountDeleted", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
getAPI(context) {
|
||||
let self = this;
|
||||
let { extension } = context;
|
||||
let { tabManager } = extension;
|
||||
|
||||
return {
|
||||
cloudFile: {
|
||||
onFileUpload: new EventManager({
|
||||
context,
|
||||
name: "cloudFile.onFileUpload",
|
||||
register: fire => {
|
||||
let listener = (
|
||||
event,
|
||||
account,
|
||||
{ id, name, data },
|
||||
tab,
|
||||
relatedFileInfo
|
||||
) => {
|
||||
tab = tab ? tabManager.convert(tab) : null;
|
||||
account = convertCloudFileAccount(account);
|
||||
return fire.async(
|
||||
account,
|
||||
{ id, name, data },
|
||||
tab,
|
||||
relatedFileInfo
|
||||
);
|
||||
};
|
||||
|
||||
context.extension.on("uploadFile", listener);
|
||||
return () => {
|
||||
context.extension.off("uploadFile", listener);
|
||||
};
|
||||
},
|
||||
module: "cloudFile",
|
||||
event: "onFileUpload",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onFileUploadAbort: new EventManager({
|
||||
context,
|
||||
name: "cloudFile.onFileUploadAbort",
|
||||
register: fire => {
|
||||
let listener = (event, account, id, tab) => {
|
||||
tab = tab ? tabManager.convert(tab) : null;
|
||||
account = convertCloudFileAccount(account);
|
||||
return fire.async(account, id, tab);
|
||||
};
|
||||
|
||||
context.extension.on("uploadAbort", listener);
|
||||
return () => {
|
||||
context.extension.off("uploadAbort", listener);
|
||||
};
|
||||
},
|
||||
module: "cloudFile",
|
||||
event: "onFileUploadAbort",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onFileRename: new EventManager({
|
||||
context,
|
||||
name: "cloudFile.onFileRename",
|
||||
register: fire => {
|
||||
let listener = (event, account, id, newName, tab) => {
|
||||
tab = tab ? tabManager.convert(tab) : null;
|
||||
account = convertCloudFileAccount(account);
|
||||
return fire.async(account, id, newName, tab);
|
||||
};
|
||||
|
||||
context.extension.on("renameFile", listener);
|
||||
return () => {
|
||||
context.extension.off("renameFile", listener);
|
||||
};
|
||||
},
|
||||
module: "cloudFile",
|
||||
event: "onFileRename",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onFileDeleted: new EventManager({
|
||||
context,
|
||||
name: "cloudFile.onFileDeleted",
|
||||
register: fire => {
|
||||
let listener = (event, account, id, tab) => {
|
||||
tab = tab ? tabManager.convert(tab) : null;
|
||||
account = convertCloudFileAccount(account);
|
||||
return fire.async(account, id, tab);
|
||||
};
|
||||
|
||||
context.extension.on("deleteFile", listener);
|
||||
return () => {
|
||||
context.extension.off("deleteFile", listener);
|
||||
};
|
||||
},
|
||||
module: "cloudFile",
|
||||
event: "onFileDeleted",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onAccountAdded: new EventManager({
|
||||
context,
|
||||
name: "cloudFile.onAccountAdded",
|
||||
register: fire => {
|
||||
let listener = (event, nativeAccount) => {
|
||||
if (nativeAccount.type != this.providerType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fire.async(convertCloudFileAccount(nativeAccount));
|
||||
};
|
||||
|
||||
cloudFileAccounts.on("accountAdded", listener);
|
||||
return () => {
|
||||
cloudFileAccounts.off("accountAdded", listener);
|
||||
};
|
||||
},
|
||||
module: "cloudFile",
|
||||
event: "onAccountAdded",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onAccountDeleted: new EventManager({
|
||||
context,
|
||||
name: "cloudFile.onAccountDeleted",
|
||||
register: fire => {
|
||||
let listener = (event, key, type) => {
|
||||
if (this.providerType != type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fire.async(key);
|
||||
};
|
||||
|
||||
cloudFileAccounts.on("accountDeleted", listener);
|
||||
return () => {
|
||||
cloudFileAccounts.off("accountDeleted", listener);
|
||||
};
|
||||
},
|
||||
module: "cloudFile",
|
||||
event: "onAccountDeleted",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
async getAccount(accountId) {
|
||||
|
|
|
@ -12,7 +12,35 @@ ChromeUtils.defineModuleGetter(
|
|||
"resource:///modules/MailExtensionShortcuts.jsm"
|
||||
);
|
||||
|
||||
this.commands = class extends ExtensionAPI {
|
||||
this.commands = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onCommand({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(eventName, commandName) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let tab = tabManager.convert(tabTracker.activeTab);
|
||||
fire.async(commandName, tab);
|
||||
}
|
||||
this.on("command", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
this.off("command", listener);
|
||||
},
|
||||
convert(_fire, _context) {
|
||||
fire = _fire;
|
||||
context = _context;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
static onUninstall(extensionId) {
|
||||
return MailExtensionShortcuts.removeCommandsFromStorage(extensionId);
|
||||
}
|
||||
|
@ -39,20 +67,10 @@ this.commands = class extends ExtensionAPI {
|
|||
reset: name => this.extension.shortcuts.resetCommand(name),
|
||||
onCommand: new EventManager({
|
||||
context,
|
||||
name: "commands.onCommand",
|
||||
module: "commands",
|
||||
event: "onCommand",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = (eventName, commandName) => {
|
||||
let tab = context.extension.tabManager.convert(
|
||||
tabTracker.activeTab
|
||||
);
|
||||
fire.async(commandName, tab);
|
||||
};
|
||||
this.on("command", listener);
|
||||
return () => {
|
||||
this.off("command", listener);
|
||||
};
|
||||
},
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1145,7 +1145,256 @@ windowTracker.addCloseListener(
|
|||
var composeWindowTracker = new Set();
|
||||
windowTracker.addCloseListener(window => composeWindowTracker.delete(window));
|
||||
|
||||
this.compose = class extends ExtensionAPI {
|
||||
this.compose = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onBeforeSend({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager, windowManager } = extension;
|
||||
let listener = {
|
||||
async handler(window, details) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let win = windowManager.wrapWindow(window);
|
||||
return fire.async(
|
||||
tabManager.convert(win.activeTab.nativeTab),
|
||||
details
|
||||
);
|
||||
},
|
||||
extension,
|
||||
};
|
||||
|
||||
beforeSendEventTracker.addListener(listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
beforeSendEventTracker.removeListener(listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onAfterSend({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager, windowManager } = extension;
|
||||
let listener = {
|
||||
async onSuccess(window, mode, messages, headerMessageId) {
|
||||
let win = windowManager.wrapWindow(window);
|
||||
let tab = tabManager.convert(win.activeTab.nativeTab);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let sendInfo = { mode, messages };
|
||||
if (mode == "sendNow") {
|
||||
sendInfo.headerMessageId = headerMessageId;
|
||||
}
|
||||
return fire.async(tab, sendInfo);
|
||||
},
|
||||
async onFailure(window, mode, exception) {
|
||||
let win = windowManager.wrapWindow(window);
|
||||
let tab = tabManager.convert(win.activeTab.nativeTab);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
return fire.async(tab, {
|
||||
mode,
|
||||
messages: [],
|
||||
error: exception.message,
|
||||
});
|
||||
},
|
||||
modes: ["sendNow", "sendLater"],
|
||||
extension,
|
||||
};
|
||||
afterSaveSendEventTracker.addListener(listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
afterSaveSendEventTracker.removeListener(listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onAfterSave({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager, windowManager } = extension;
|
||||
let listener = {
|
||||
async onSuccess(window, mode, messages, headerMessageId) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let win = windowManager.wrapWindow(window);
|
||||
let saveInfo = { mode, messages };
|
||||
return fire.async(
|
||||
tabManager.convert(win.activeTab.nativeTab),
|
||||
saveInfo
|
||||
);
|
||||
},
|
||||
async onFailure(window, mode, exception) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let win = windowManager.wrapWindow(window);
|
||||
return fire.async(tabManager.convert(win.activeTab.nativeTab), {
|
||||
mode,
|
||||
messages: [],
|
||||
error: exception.message,
|
||||
});
|
||||
},
|
||||
modes: ["draft", "template"],
|
||||
extension,
|
||||
};
|
||||
afterSaveSendEventTracker.addListener(listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
afterSaveSendEventTracker.removeListener(listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onAttachmentAdded({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(event) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
for (let attachment of event.detail) {
|
||||
attachment = composeAttachmentTracker.convert(
|
||||
attachment,
|
||||
event.target.ownerGlobal
|
||||
);
|
||||
fire.async(tabManager.convert(event.target.ownerGlobal), attachment);
|
||||
}
|
||||
}
|
||||
windowTracker.addListener("attachments-added", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
windowTracker.removeListener("attachments-added", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onAttachmentRemoved({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(event) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
for (let attachment of event.detail) {
|
||||
let attachmentId = composeAttachmentTracker.getId(
|
||||
attachment,
|
||||
event.target.ownerGlobal
|
||||
);
|
||||
fire.async(
|
||||
tabManager.convert(event.target.ownerGlobal),
|
||||
attachmentId
|
||||
);
|
||||
composeAttachmentTracker.forgetAttachment(attachment);
|
||||
}
|
||||
}
|
||||
windowTracker.addListener("attachments-removed", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
windowTracker.removeListener("attachments-removed", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onIdentityChanged({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(event) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(
|
||||
tabManager.convert(event.target.ownerGlobal),
|
||||
event.target.getCurrentIdentityKey()
|
||||
);
|
||||
}
|
||||
windowTracker.addListener("compose-from-changed", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
windowTracker.removeListener("compose-from-changed", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onComposeStateChanged({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(event) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(
|
||||
tabManager.convert(event.target.ownerGlobal),
|
||||
composeStates.convert(event.detail)
|
||||
);
|
||||
}
|
||||
windowTracker.addListener("compose-state-changed", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
windowTracker.removeListener("compose-state-changed", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onActiveDictionariesChanged({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(event) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let activeDictionaries = event.detail.split(",");
|
||||
fire.async(
|
||||
tabManager.convert(event.target.ownerGlobal),
|
||||
Cc["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Ci.mozISpellCheckingEngine)
|
||||
.getDictionaryList()
|
||||
.reduce((list, dict) => {
|
||||
list[dict] = activeDictionaries.includes(dict);
|
||||
return list;
|
||||
}, {})
|
||||
);
|
||||
}
|
||||
windowTracker.addListener("active-dictionaries-changed", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
windowTracker.removeListener("active-dictionaries-changed", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
getAPI(context) {
|
||||
function getComposeTab(tabId) {
|
||||
let tab = tabManager.get(tabId);
|
||||
|
@ -1160,206 +1409,60 @@ this.compose = class extends ExtensionAPI {
|
|||
}
|
||||
|
||||
let { extension } = context;
|
||||
let { tabManager, windowManager } = extension;
|
||||
let { tabManager } = extension;
|
||||
|
||||
return {
|
||||
compose: {
|
||||
onBeforeSend: new EventManager({
|
||||
context,
|
||||
name: "compose.onBeforeSend",
|
||||
module: "compose",
|
||||
event: "onBeforeSend",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = {
|
||||
handler(window, details) {
|
||||
let win = windowManager.wrapWindow(window);
|
||||
return fire.async(
|
||||
tabManager.convert(win.activeTab.nativeTab),
|
||||
details
|
||||
);
|
||||
},
|
||||
extension,
|
||||
};
|
||||
|
||||
beforeSendEventTracker.addListener(listener);
|
||||
return () => {
|
||||
beforeSendEventTracker.removeListener(listener);
|
||||
};
|
||||
},
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onAfterSend: new EventManager({
|
||||
context,
|
||||
name: "compose.onAfterSend",
|
||||
module: "compose",
|
||||
event: "onAfterSend",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = {
|
||||
onSuccess(window, mode, messages, headerMessageId) {
|
||||
let win = windowManager.wrapWindow(window);
|
||||
let sendInfo = { mode, messages };
|
||||
if (mode == "sendNow") {
|
||||
sendInfo.headerMessageId = headerMessageId;
|
||||
}
|
||||
return fire.async(
|
||||
tabManager.convert(win.activeTab.nativeTab),
|
||||
sendInfo
|
||||
);
|
||||
},
|
||||
onFailure(window, mode, exception) {
|
||||
let win = windowManager.wrapWindow(window);
|
||||
return fire.async(tabManager.convert(win.activeTab.nativeTab), {
|
||||
mode,
|
||||
messages: [],
|
||||
error: exception.message,
|
||||
});
|
||||
},
|
||||
modes: ["sendNow", "sendLater"],
|
||||
extension,
|
||||
};
|
||||
|
||||
afterSaveSendEventTracker.addListener(listener);
|
||||
return () => {
|
||||
afterSaveSendEventTracker.removeListener(listener);
|
||||
};
|
||||
},
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onAfterSave: new EventManager({
|
||||
context,
|
||||
name: "compose.onAfterSave",
|
||||
module: "compose",
|
||||
event: "onAfterSave",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = {
|
||||
onSuccess(window, mode, messages, headerMessageId) {
|
||||
let win = windowManager.wrapWindow(window);
|
||||
let saveInfo = { mode, messages };
|
||||
return fire.async(
|
||||
tabManager.convert(win.activeTab.nativeTab),
|
||||
saveInfo
|
||||
);
|
||||
},
|
||||
onFailure(window, mode, exception) {
|
||||
let win = windowManager.wrapWindow(window);
|
||||
return fire.async(tabManager.convert(win.activeTab.nativeTab), {
|
||||
mode,
|
||||
messages: [],
|
||||
error: exception.message,
|
||||
});
|
||||
},
|
||||
modes: ["draft", "template"],
|
||||
extension,
|
||||
};
|
||||
|
||||
afterSaveSendEventTracker.addListener(listener);
|
||||
return () => {
|
||||
afterSaveSendEventTracker.removeListener(listener);
|
||||
};
|
||||
},
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onAttachmentAdded: new ExtensionCommon.EventManager({
|
||||
context,
|
||||
name: "compose.onAttachmentAdded",
|
||||
register(fire) {
|
||||
async function callback(event) {
|
||||
for (let attachment of event.detail) {
|
||||
attachment = composeAttachmentTracker.convert(
|
||||
attachment,
|
||||
event.target.ownerGlobal
|
||||
);
|
||||
fire.async(
|
||||
tabManager.convert(event.target.ownerGlobal),
|
||||
attachment
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
windowTracker.addListener("attachments-added", callback);
|
||||
return function() {
|
||||
windowTracker.removeListener("attachments-added", callback);
|
||||
};
|
||||
},
|
||||
module: "compose",
|
||||
event: "onAttachmentAdded",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onAttachmentRemoved: new ExtensionCommon.EventManager({
|
||||
context,
|
||||
name: "compose.onAttachmentRemoved",
|
||||
register(fire) {
|
||||
function callback(event) {
|
||||
for (let attachment of event.detail) {
|
||||
let attachmentId = composeAttachmentTracker.getId(
|
||||
attachment,
|
||||
event.target.ownerGlobal
|
||||
);
|
||||
fire.async(
|
||||
tabManager.convert(event.target.ownerGlobal),
|
||||
attachmentId
|
||||
);
|
||||
composeAttachmentTracker.forgetAttachment(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
windowTracker.addListener("attachments-removed", callback);
|
||||
return function() {
|
||||
windowTracker.removeListener("attachments-removed", callback);
|
||||
};
|
||||
},
|
||||
module: "compose",
|
||||
event: "onAttachmentRemoved",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onIdentityChanged: new ExtensionCommon.EventManager({
|
||||
context,
|
||||
name: "compose.onIdentityChanged",
|
||||
register(fire) {
|
||||
function callback(event) {
|
||||
fire.async(
|
||||
tabManager.convert(event.target.ownerGlobal),
|
||||
event.target.getCurrentIdentityKey()
|
||||
);
|
||||
}
|
||||
|
||||
windowTracker.addListener("compose-from-changed", callback);
|
||||
return function() {
|
||||
windowTracker.removeListener("compose-from-changed", callback);
|
||||
};
|
||||
},
|
||||
module: "compose",
|
||||
event: "onIdentityChanged",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onComposeStateChanged: new ExtensionCommon.EventManager({
|
||||
context,
|
||||
name: "compose.onComposeStateChanged",
|
||||
register(fire) {
|
||||
function callback(event) {
|
||||
fire.async(
|
||||
tabManager.convert(event.target.ownerGlobal),
|
||||
composeStates.convert(event.detail)
|
||||
);
|
||||
}
|
||||
|
||||
windowTracker.addListener("compose-state-changed", callback);
|
||||
return function() {
|
||||
windowTracker.removeListener("compose-state-changed", callback);
|
||||
};
|
||||
},
|
||||
module: "compose",
|
||||
event: "onComposeStateChanged",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onActiveDictionariesChanged: new ExtensionCommon.EventManager({
|
||||
context,
|
||||
name: "compose.onActiveDictionariesChanged",
|
||||
register(fire) {
|
||||
function callback(event) {
|
||||
let activeDictionaries = event.detail.split(",");
|
||||
fire.async(
|
||||
tabManager.convert(event.target.ownerGlobal),
|
||||
Cc["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Ci.mozISpellCheckingEngine)
|
||||
.getDictionaryList()
|
||||
.reduce((list, dict) => {
|
||||
list[dict] = activeDictionaries.includes(dict);
|
||||
return list;
|
||||
}, {})
|
||||
);
|
||||
}
|
||||
|
||||
windowTracker.addListener("active-dictionaries-changed", callback);
|
||||
return function() {
|
||||
windowTracker.removeListener(
|
||||
"active-dictionaries-changed",
|
||||
callback
|
||||
);
|
||||
};
|
||||
},
|
||||
module: "compose",
|
||||
event: "onActiveDictionariesChanged",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
async beginNew(messageId, details) {
|
||||
let type = Ci.nsIMsgCompType.New;
|
||||
|
|
|
@ -133,7 +133,13 @@ var folderTracker = new (class extends EventEmitter {
|
|||
this.emit("folder-created", convertFolder(childFolder));
|
||||
}
|
||||
folderDeleted(oldFolder) {
|
||||
this.emit("folder-deleted", convertFolder(oldFolder));
|
||||
// Deleting an account, will trigger delete notifications for its folders,
|
||||
// but the account lookup fails, so skip them.
|
||||
let server = oldFolder.server;
|
||||
let account = MailServices.accounts.FindAccountForServer(server);
|
||||
if (account) {
|
||||
this.emit("folder-deleted", convertFolder(oldFolder, account.key));
|
||||
}
|
||||
}
|
||||
folderMoveCopyCompleted(move, srcFolder, targetFolder) {
|
||||
// targetFolder is not the copied/moved folder, but its parent. Find the
|
||||
|
@ -321,91 +327,160 @@ function waitForOperation(flags, uri) {
|
|||
});
|
||||
}
|
||||
|
||||
this.folders = class extends ExtensionAPI {
|
||||
this.folders = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onCreated({ context, fire }) {
|
||||
async function listener(event, createdMailFolder) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(createdMailFolder);
|
||||
}
|
||||
folderTracker.on("folder-created", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
folderTracker.off("folder-created", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onRenamed({ context, fire }) {
|
||||
async function listener(event, originalMailFolder, renamedMailFolder) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(originalMailFolder, renamedMailFolder);
|
||||
}
|
||||
folderTracker.on("folder-renamed", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
folderTracker.off("folder-renamed", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onMoved({ context, fire }) {
|
||||
async function listener(event, srcMailFolder, dstMailFolder) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(srcMailFolder, dstMailFolder);
|
||||
}
|
||||
folderTracker.on("folder-moved", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
folderTracker.off("folder-moved", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onCopied({ context, fire }) {
|
||||
async function listener(event, srcMailFolder, dstMailFolder) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(srcMailFolder, dstMailFolder);
|
||||
}
|
||||
folderTracker.on("folder-copied", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
folderTracker.off("folder-copied", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onDeleted({ context, fire }) {
|
||||
async function listener(event, deletedMailFolder) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(deletedMailFolder);
|
||||
}
|
||||
folderTracker.on("folder-deleted", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
folderTracker.off("folder-deleted", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onFolderInfoChanged({ context, fire }) {
|
||||
async function listener(event, changedMailFolder, mailFolderInfo) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(changedMailFolder, mailFolderInfo);
|
||||
}
|
||||
folderTracker.on("folder-info-changed", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
folderTracker.off("folder-info-changed", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
getAPI(context) {
|
||||
return {
|
||||
folders: {
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "folders.onCreated",
|
||||
register: fire => {
|
||||
let listener = async (event, createdMailFolder) => {
|
||||
fire.async(createdMailFolder);
|
||||
};
|
||||
folderTracker.on("folder-created", listener);
|
||||
return () => {
|
||||
folderTracker.off("folder-created", listener);
|
||||
};
|
||||
},
|
||||
module: "folders",
|
||||
event: "onCreated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onRenamed: new EventManager({
|
||||
context,
|
||||
name: "folders.onRenamed",
|
||||
register: fire => {
|
||||
let listener = async (
|
||||
event,
|
||||
originalMailFolder,
|
||||
renamedMailFolder
|
||||
) => {
|
||||
fire.async(originalMailFolder, renamedMailFolder);
|
||||
};
|
||||
folderTracker.on("folder-renamed", listener);
|
||||
return () => {
|
||||
folderTracker.off("folder-renamed", listener);
|
||||
};
|
||||
},
|
||||
module: "folders",
|
||||
event: "onRenamed",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onMoved: new EventManager({
|
||||
context,
|
||||
name: "folders.onMoved",
|
||||
register: fire => {
|
||||
let listener = async (event, srcMailFolder, dstMailFolder) => {
|
||||
fire.async(srcMailFolder, dstMailFolder);
|
||||
};
|
||||
folderTracker.on("folder-moved", listener);
|
||||
return () => {
|
||||
folderTracker.off("folder-moved", listener);
|
||||
};
|
||||
},
|
||||
module: "folders",
|
||||
event: "onMoved",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onCopied: new EventManager({
|
||||
context,
|
||||
name: "folders.onCopied",
|
||||
register: fire => {
|
||||
let listener = async (event, srcMailFolder, dstMailFolder) => {
|
||||
fire.async(srcMailFolder, dstMailFolder);
|
||||
};
|
||||
folderTracker.on("folder-copied", listener);
|
||||
return () => {
|
||||
folderTracker.off("folder-copied", listener);
|
||||
};
|
||||
},
|
||||
module: "folders",
|
||||
event: "onCopied",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onDeleted: new EventManager({
|
||||
context,
|
||||
name: "folders.onDeleted",
|
||||
register: fire => {
|
||||
let listener = async (event, deletedMailFolder) => {
|
||||
fire.async(deletedMailFolder);
|
||||
};
|
||||
folderTracker.on("folder-deleted", listener);
|
||||
return () => {
|
||||
folderTracker.off("folder-deleted", listener);
|
||||
};
|
||||
},
|
||||
module: "folders",
|
||||
event: "onDeleted",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onFolderInfoChanged: new EventManager({
|
||||
context,
|
||||
name: "folders.onFolderInfoChanged",
|
||||
register: fire => {
|
||||
let listener = async (event, changedMailFolder, mailFolderInfo) => {
|
||||
fire.async(changedMailFolder, mailFolderInfo);
|
||||
};
|
||||
folderTracker.on("folder-info-changed", listener);
|
||||
return () => {
|
||||
folderTracker.off("folder-info-changed", listener);
|
||||
};
|
||||
},
|
||||
module: "folders",
|
||||
event: "onFolderInfoChanged",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
async create(parent, childName) {
|
||||
// The schema file allows parent to be either a MailFolder or a
|
||||
|
|
|
@ -190,15 +190,78 @@ var identitiesTracker = new (class extends EventEmitter {
|
|||
}
|
||||
})();
|
||||
|
||||
this.identities = class extends ExtensionAPI {
|
||||
close() {
|
||||
this.identities = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onCreated({ context, fire }) {
|
||||
async function listener(event, key, identity) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(key, identity);
|
||||
}
|
||||
identitiesTracker.on("account-identity-added", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
identitiesTracker.off("account-identity-added", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onUpdated({ context, fire }) {
|
||||
async function listener(event, key, changedValues) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(key, changedValues);
|
||||
}
|
||||
identitiesTracker.on("account-identity-updated", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
identitiesTracker.off("account-identity-updated", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onDeleted({ context, fire }) {
|
||||
async function listener(event, key) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(key);
|
||||
}
|
||||
identitiesTracker.on("account-identity-removed", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
identitiesTracker.off("account-identity-removed", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
identitiesTracker.incrementListeners();
|
||||
}
|
||||
|
||||
onShutdown() {
|
||||
identitiesTracker.decrementListeners();
|
||||
}
|
||||
|
||||
getAPI(context) {
|
||||
context.callOnClose(this);
|
||||
identitiesTracker.incrementListeners();
|
||||
|
||||
return {
|
||||
identities: {
|
||||
async list(accountId) {
|
||||
|
@ -278,45 +341,21 @@ this.identities = class extends ExtensionAPI {
|
|||
},
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "identities.onCreated",
|
||||
register: fire => {
|
||||
let listener = (event, key, identity) => {
|
||||
fire.sync(key, identity);
|
||||
};
|
||||
|
||||
identitiesTracker.on("account-identity-added", listener);
|
||||
return () => {
|
||||
identitiesTracker.off("account-identity-added", listener);
|
||||
};
|
||||
},
|
||||
module: "identities",
|
||||
event: "onCreated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "identities.onUpdated",
|
||||
register: fire => {
|
||||
let listener = (event, key, changedValues) => {
|
||||
fire.sync(key, changedValues);
|
||||
};
|
||||
|
||||
identitiesTracker.on("account-identity-updated", listener);
|
||||
return () => {
|
||||
identitiesTracker.off("account-identity-updated", listener);
|
||||
};
|
||||
},
|
||||
module: "identities",
|
||||
event: "onUpdated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onDeleted: new EventManager({
|
||||
context,
|
||||
name: "identities.onDeleted",
|
||||
register: fire => {
|
||||
let listener = (event, key) => {
|
||||
fire.sync(key);
|
||||
};
|
||||
|
||||
identitiesTracker.on("account-identity-removed", listener);
|
||||
return () => {
|
||||
identitiesTracker.off("account-identity-removed", listener);
|
||||
};
|
||||
},
|
||||
module: "identities",
|
||||
event: "onDeleted",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -150,7 +150,59 @@ var uiListener = new (class extends EventEmitter {
|
|||
}
|
||||
})();
|
||||
|
||||
this.mailTabs = class extends ExtensionAPI {
|
||||
this.mailTabs = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onDisplayedFolderChanged({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(event, tab, folder) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.sync(tabManager.convert(tab), convertFolder(folder));
|
||||
}
|
||||
uiListener.on("folder-changed", listener);
|
||||
uiListener.incrementListeners();
|
||||
return {
|
||||
unregister: () => {
|
||||
uiListener.off("folder-changed", listener);
|
||||
uiListener.decrementListeners();
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onSelectedMessagesChanged({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager } = extension;
|
||||
async function listener(event, tab, messages) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let page = await messageListTracker.startList(messages, extension);
|
||||
fire.sync(tabManager.convert(tab), page);
|
||||
}
|
||||
uiListener.on("messages-changed", listener);
|
||||
uiListener.incrementListeners();
|
||||
return {
|
||||
unregister: () => {
|
||||
uiListener.off("messages-changed", listener);
|
||||
uiListener.decrementListeners();
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
getAPI(context) {
|
||||
let { extension } = context;
|
||||
let { tabManager } = extension;
|
||||
|
@ -436,40 +488,16 @@ this.mailTabs = class extends ExtensionAPI {
|
|||
|
||||
onDisplayedFolderChanged: new EventManager({
|
||||
context,
|
||||
name: "mailTabs.onDisplayedFolderChanged",
|
||||
register: fire => {
|
||||
let listener = (event, tab, folder) => {
|
||||
fire.sync(tabManager.convert(tab), convertFolder(folder));
|
||||
};
|
||||
|
||||
uiListener.on("folder-changed", listener);
|
||||
uiListener.incrementListeners();
|
||||
return () => {
|
||||
uiListener.off("folder-changed", listener);
|
||||
uiListener.decrementListeners();
|
||||
};
|
||||
},
|
||||
module: "mailTabs",
|
||||
event: "onDisplayedFolderChanged",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onSelectedMessagesChanged: new EventManager({
|
||||
context,
|
||||
name: "mailTabs.onSelectedMessagesChanged",
|
||||
register: fire => {
|
||||
let listener = async (event, tab, messages) => {
|
||||
let page = await messageListTracker.startList(
|
||||
messages,
|
||||
extension
|
||||
);
|
||||
fire.sync(tabManager.convert(tab), page);
|
||||
};
|
||||
|
||||
uiListener.on("messages-changed", listener);
|
||||
uiListener.incrementListeners();
|
||||
return () => {
|
||||
uiListener.off("messages-changed", listener);
|
||||
uiListener.decrementListeners();
|
||||
};
|
||||
},
|
||||
module: "mailTabs",
|
||||
event: "onSelectedMessagesChanged",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1019,7 +1019,7 @@ MenuItem.prototype = {
|
|||
if (targetPattern) {
|
||||
let targetUrls = [];
|
||||
if (contextData.onImage || contextData.onAudio || contextData.onVideo) {
|
||||
// TODO: double check if srcUrl is always set when we need it
|
||||
// TODO: Double check if srcUrl is always set when we need it.
|
||||
targetUrls.push(contextData.srcUrl);
|
||||
}
|
||||
if (contextData.onLink) {
|
||||
|
|
|
@ -78,50 +78,81 @@ function getMsgHdr(properties) {
|
|||
return msgHdr;
|
||||
}
|
||||
|
||||
this.messageDisplay = class extends ExtensionAPI {
|
||||
this.messageDisplay = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onMessageDisplayed({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager, windowManager } = extension;
|
||||
let listener = {
|
||||
async handleEvent(event) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let win = windowManager.wrapWindow(event.target);
|
||||
let tab = tabManager.convert(win.activeTab.nativeTab);
|
||||
let msg = convertMessage(event.detail, extension);
|
||||
fire.async(tab, msg);
|
||||
},
|
||||
};
|
||||
windowTracker.addListener("MsgLoaded", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
windowTracker.removeListener("MsgLoaded", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onMessagesDisplayed({ context, fire }) {
|
||||
const { extension } = this;
|
||||
const { tabManager, windowManager } = extension;
|
||||
let listener = {
|
||||
async handleEvent(event) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let win = windowManager.wrapWindow(event.target);
|
||||
let tab = tabManager.convert(win.activeTab.nativeTab);
|
||||
getDisplayedMessages(win.activeTab, extension).then(msgs => {
|
||||
fire.async(tab, msgs);
|
||||
});
|
||||
},
|
||||
};
|
||||
windowTracker.addListener("MsgsLoaded", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
windowTracker.removeListener("MsgsLoaded", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
getAPI(context) {
|
||||
let { extension } = context;
|
||||
let { tabManager, windowManager } = extension;
|
||||
let { tabManager } = extension;
|
||||
return {
|
||||
messageDisplay: {
|
||||
onMessageDisplayed: new EventManager({
|
||||
context,
|
||||
name: "messageDisplay.onMessageDisplayed",
|
||||
register: fire => {
|
||||
let listener = {
|
||||
handleEvent(event) {
|
||||
let win = windowManager.wrapWindow(event.target);
|
||||
let tab = tabManager.convert(win.activeTab.nativeTab);
|
||||
let msg = convertMessage(event.detail, extension);
|
||||
fire.async(tab, msg);
|
||||
},
|
||||
};
|
||||
|
||||
windowTracker.addListener("MsgLoaded", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("MsgLoaded", listener);
|
||||
};
|
||||
},
|
||||
module: "messageDisplay",
|
||||
event: "onMessageDisplayed",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onMessagesDisplayed: new EventManager({
|
||||
context,
|
||||
name: "messageDisplay.onMessagesDisplayed",
|
||||
register: fire => {
|
||||
let listener = {
|
||||
handleEvent(event) {
|
||||
let win = windowManager.wrapWindow(event.target);
|
||||
let tab = tabManager.convert(win.activeTab.nativeTab);
|
||||
getDisplayedMessages(win.activeTab, extension).then(msgs => {
|
||||
fire.async(tab, msgs);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
windowTracker.addListener("MsgsLoaded", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("MsgsLoaded", listener);
|
||||
};
|
||||
},
|
||||
module: "messageDisplay",
|
||||
event: "onMessagesDisplayed",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
async getDisplayedMessage(tabId) {
|
||||
let tab = tabManager.get(tabId);
|
||||
|
|
|
@ -420,7 +420,136 @@ async function getMimeMessage(msgHdr, partName = "") {
|
|||
: mimeMsg;
|
||||
}
|
||||
|
||||
this.messages = class extends ExtensionAPI {
|
||||
this.messages = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onNewMailReceived({ context, fire }) {
|
||||
let listener = async (event, folder, newMessages) => {
|
||||
let { extension } = this;
|
||||
// The msgHdr could be gone after the wakeup, convert it early.
|
||||
let page = await messageListTracker.startList(newMessages, extension);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(convertFolder(folder), page);
|
||||
};
|
||||
messageTracker.on("messages-received", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
messageTracker.off("messages-received", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onUpdated({ context, fire }) {
|
||||
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);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(convertedMessage, properties);
|
||||
};
|
||||
messageTracker.on("message-updated", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
messageTracker.off("message-updated", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onMoved({ context, fire }) {
|
||||
let listener = async (event, srcMessages, dstMessages) => {
|
||||
let { extension } = this;
|
||||
// The msgHdr could be gone after the wakeup, convert them early.
|
||||
let srcPage = await messageListTracker.startList(
|
||||
srcMessages,
|
||||
extension
|
||||
);
|
||||
let dstPage = await messageListTracker.startList(
|
||||
dstMessages,
|
||||
extension
|
||||
);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(srcPage, dstPage);
|
||||
};
|
||||
messageTracker.on("messages-moved", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
messageTracker.off("messages-moved", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onCopied({ context, fire }) {
|
||||
let listener = async (event, srcMessages, dstMessages) => {
|
||||
let { extension } = this;
|
||||
// The msgHdr could be gone after the wakeup, convert them early.
|
||||
let srcPage = await messageListTracker.startList(
|
||||
srcMessages,
|
||||
extension
|
||||
);
|
||||
let dstPage = await messageListTracker.startList(
|
||||
dstMessages,
|
||||
extension
|
||||
);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(srcPage, dstPage);
|
||||
};
|
||||
messageTracker.on("messages-copied", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
messageTracker.off("messages-copied", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
onDeleted({ context, fire }) {
|
||||
let listener = async (event, deletedMessages) => {
|
||||
let { extension } = this;
|
||||
// The msgHdr could be gone after the wakeup, convert them early.
|
||||
let deletedPage = await messageListTracker.startList(
|
||||
deletedMessages,
|
||||
extension
|
||||
);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(deletedPage);
|
||||
};
|
||||
messageTracker.on("messages-deleted", listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
messageTracker.off("messages-deleted", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
getAPI(context) {
|
||||
function collectMessagesInFolders(messageIds) {
|
||||
let folderMap = new DefaultMap(() => new Set());
|
||||
|
@ -572,96 +701,33 @@ this.messages = class extends ExtensionAPI {
|
|||
messages: {
|
||||
onNewMailReceived: new EventManager({
|
||||
context,
|
||||
name: "messages.onNewMailReceived",
|
||||
register: fire => {
|
||||
let listener = async (event, folder, newMessages) => {
|
||||
let page = await messageListTracker.startList(
|
||||
newMessages,
|
||||
context.extension
|
||||
);
|
||||
fire.async(convertFolder(folder), page);
|
||||
};
|
||||
|
||||
messageTracker.on("messages-received", listener);
|
||||
return () => {
|
||||
messageTracker.off("messages-received", listener);
|
||||
};
|
||||
},
|
||||
module: "messages",
|
||||
event: "onNewMailReceived",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "messageDisplay.onUpdated",
|
||||
register: fire => {
|
||||
let listener = async (event, message, properties) => {
|
||||
fire.async(
|
||||
convertMessage(message, context.extension),
|
||||
properties
|
||||
);
|
||||
};
|
||||
messageTracker.on("message-updated", listener);
|
||||
return () => {
|
||||
messageTracker.off("message-updated", listener);
|
||||
};
|
||||
},
|
||||
module: "messages",
|
||||
event: "onUpdated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onMoved: new EventManager({
|
||||
context,
|
||||
name: "messageDisplay.onMoved",
|
||||
register: fire => {
|
||||
let listener = async (event, srcMessages, dstMessages) => {
|
||||
let srcPage = await messageListTracker.startList(
|
||||
srcMessages,
|
||||
context.extension
|
||||
);
|
||||
let dstPage = await messageListTracker.startList(
|
||||
dstMessages,
|
||||
context.extension
|
||||
);
|
||||
fire.async(srcPage, dstPage);
|
||||
};
|
||||
messageTracker.on("messages-moved", listener);
|
||||
return () => {
|
||||
messageTracker.off("messages-moved", listener);
|
||||
};
|
||||
},
|
||||
module: "messages",
|
||||
event: "onMoved",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onCopied: new EventManager({
|
||||
context,
|
||||
name: "messageDisplay.onCopied",
|
||||
register: fire => {
|
||||
let listener = async (event, srcMessages, dstMessages) => {
|
||||
let srcPage = await messageListTracker.startList(
|
||||
srcMessages,
|
||||
context.extension
|
||||
);
|
||||
let dstPage = await messageListTracker.startList(
|
||||
dstMessages,
|
||||
context.extension
|
||||
);
|
||||
fire.async(srcPage, dstPage);
|
||||
};
|
||||
messageTracker.on("messages-copied", listener);
|
||||
return () => {
|
||||
messageTracker.off("messages-copied", listener);
|
||||
};
|
||||
},
|
||||
module: "messages",
|
||||
event: "onCopied",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onDeleted: new EventManager({
|
||||
context,
|
||||
name: "messageDisplay.onDeleted",
|
||||
register: fire => {
|
||||
let listener = async (event, deletedMessages) => {
|
||||
let deletedPage = await messageListTracker.startList(
|
||||
deletedMessages,
|
||||
context.extension
|
||||
);
|
||||
fire.async(deletedPage);
|
||||
};
|
||||
messageTracker.on("messages-deleted", listener);
|
||||
return () => {
|
||||
messageTracker.off("messages-deleted", listener);
|
||||
};
|
||||
},
|
||||
module: "messages",
|
||||
event: "onDeleted",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
async list({ accountId, path }) {
|
||||
let uri = folderPathToURI(accountId, path);
|
||||
|
|
|
@ -91,17 +91,153 @@ const allAttrs = new Set(["favIconUrl", "title"]);
|
|||
const allProperties = new Set(["favIconUrl", "status", "title"]);
|
||||
const restricted = new Set(["url", "favIconUrl", "title"]);
|
||||
|
||||
/**
|
||||
* An EventManager for the tabs.onUpdated listener.
|
||||
*/
|
||||
class TabsUpdateFilterEventManager extends EventManager {
|
||||
constructor({ context }) {
|
||||
let { extension } = context;
|
||||
let { tabManager } = extension;
|
||||
this.tabs = class extends ExtensionAPIPersistent {
|
||||
onShutdown(isAppShutdown) {
|
||||
if (isAppShutdown) {
|
||||
return;
|
||||
}
|
||||
for (let window of Services.wm.getEnumerator("mail:3pane")) {
|
||||
let tabmail = window.document.getElementById("tabmail");
|
||||
for (let i = tabmail.tabInfo.length; i > 0; i--) {
|
||||
let nativeTabInfo = tabmail.tabInfo[i - 1];
|
||||
let uri = nativeTabInfo.browser?.browsingContext.currentURI;
|
||||
if (
|
||||
uri &&
|
||||
uri.scheme == "moz-extension" &&
|
||||
uri.host == this.extension.uuid
|
||||
) {
|
||||
tabmail.closeTab(nativeTabInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let register = (fire, filterProps) => {
|
||||
tabEventRegistrar({ tabEvent, listener }) {
|
||||
let { extension } = this;
|
||||
let { tabManager } = extension;
|
||||
return ({ context, fire }) => {
|
||||
let listener2 = async (eventName, event, ...args) => {
|
||||
if (!tabManager.canAccessTab(event.nativeTab)) {
|
||||
return;
|
||||
}
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
listener({ context, fire, event }, ...args);
|
||||
};
|
||||
tabTracker.on(tabEvent, listener2);
|
||||
return {
|
||||
unregister() {
|
||||
tabTracker.off(tabEvent, listener2);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called) (handled by tabEventRegistrar).
|
||||
|
||||
onActivated: this.tabEventRegistrar({
|
||||
tabEvent: "tab-activated",
|
||||
listener: ({ context, fire, event }) => {
|
||||
fire.async(event);
|
||||
},
|
||||
}),
|
||||
|
||||
onCreated: this.tabEventRegistrar({
|
||||
tabEvent: "tab-created",
|
||||
listener: ({ context, fire, event }) => {
|
||||
let { extension } = this;
|
||||
let { tabManager } = extension;
|
||||
fire.async(tabManager.convert(event.nativeTabInfo, event.currentTab));
|
||||
},
|
||||
}),
|
||||
|
||||
onAttached: this.tabEventRegistrar({
|
||||
tabEvent: "tab-attached",
|
||||
listener: ({ context, fire, event }) => {
|
||||
fire.async(event.tabId, {
|
||||
newWindowId: event.newWindowId,
|
||||
newPosition: event.newPosition,
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
onDetached: this.tabEventRegistrar({
|
||||
tabEvent: "tab-detached",
|
||||
listener: ({ context, fire, event }) => {
|
||||
fire.async(event.tabId, {
|
||||
oldWindowId: event.oldWindowId,
|
||||
oldPosition: event.oldPosition,
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
onRemoved: this.tabEventRegistrar({
|
||||
tabEvent: "tab-removed",
|
||||
listener: ({ context, fire, event }) => {
|
||||
fire.async(event.tabId, {
|
||||
windowId: event.windowId,
|
||||
isWindowClosing: event.isWindowClosing,
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
onMoved({ context, fire }) {
|
||||
let { tabManager } = this.extension;
|
||||
let moveListener = async event => {
|
||||
let nativeTab = event.target;
|
||||
let nativeTabInfo = event.detail.tabInfo;
|
||||
let tabmail = nativeTab.ownerDocument.getElementById("tabmail");
|
||||
if (tabManager.canAccessTab(nativeTab)) {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(tabTracker.getId(nativeTabInfo), {
|
||||
windowId: windowTracker.getId(nativeTab.ownerGlobal),
|
||||
fromIndex: event.detail.idx,
|
||||
toIndex: tabmail.tabInfo.indexOf(nativeTabInfo),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
windowTracker.addListener("TabMove", moveListener);
|
||||
return {
|
||||
unregister() {
|
||||
windowTracker.removeListener("TabMove", moveListener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
onUpdated({ context, fire }, [filterProps]) {
|
||||
let filter = { ...filterProps };
|
||||
let scheduledEvents = [];
|
||||
|
||||
if (
|
||||
filter &&
|
||||
filter.urls &&
|
||||
!this.extension.hasPermission("tabs") &&
|
||||
!this.extension.hasPermission("activeTab")
|
||||
) {
|
||||
console.error(
|
||||
'Url filtering in tabs.onUpdated requires "tabs" or "activeTab" permission.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter.urls) {
|
||||
// TODO: Consider following M-C
|
||||
// Use additional parameter { restrictSchemes: false }.
|
||||
filter.urls = new MatchPatternSet(filter.urls);
|
||||
}
|
||||
let needsModified = true;
|
||||
|
@ -113,12 +249,16 @@ class TabsUpdateFilterEventManager extends EventManager {
|
|||
filter.properties = allProperties;
|
||||
}
|
||||
|
||||
function sanitize(changeInfo) {
|
||||
function sanitize(tab, changeInfo) {
|
||||
let result = {};
|
||||
let nonempty = false;
|
||||
let hasTabs = extension.hasPermission("tabs");
|
||||
for (let prop in changeInfo) {
|
||||
if (hasTabs || !restricted.has(prop)) {
|
||||
// In practice, changeInfo contains at most one property from
|
||||
// restricted. Therefore it is not necessary to cache the value
|
||||
// of tab.hasTabPermission outside the loop.
|
||||
// Unnecessarily accessing tab.hasTabPermission can cause bugs, see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1694699#c21
|
||||
if (tab.hasTabPermission || !restricted.has(prop)) {
|
||||
nonempty = true;
|
||||
result[prop] = changeInfo[prop];
|
||||
}
|
||||
|
@ -128,6 +268,8 @@ class TabsUpdateFilterEventManager extends EventManager {
|
|||
|
||||
function getWindowID(windowId) {
|
||||
if (windowId === WindowBase.WINDOW_ID_CURRENT) {
|
||||
// TODO: Consider following M-C
|
||||
// Use windowTracker.getTopWindow(context).
|
||||
return windowTracker.getId(windowTracker.topWindow);
|
||||
}
|
||||
return windowId;
|
||||
|
@ -153,19 +295,42 @@ class TabsUpdateFilterEventManager extends EventManager {
|
|||
return true;
|
||||
}
|
||||
|
||||
let fireForTab = (tab, changed) => {
|
||||
let fireForTab = async (tab, changed) => {
|
||||
if (!matchFilters(tab, changed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let changeInfo = sanitize(changed);
|
||||
let changeInfo = sanitize(tab, changed);
|
||||
if (changeInfo) {
|
||||
fire.async(tab.id, changeInfo, tab.convert());
|
||||
let tabInfo = tab.convert();
|
||||
// TODO: Consider following M-C
|
||||
// Use tabTracker.maybeWaitForTabOpen(nativeTab).then(() => {}).
|
||||
|
||||
// Using a FIFO to keep order of events, in case the last one
|
||||
// gets through without being placed on the async callback stack.
|
||||
scheduledEvents.push([tab.id, changeInfo, tabInfo]);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
fire.async(...scheduledEvents.shift());
|
||||
}
|
||||
};
|
||||
|
||||
let listener = event => {
|
||||
/* TODO: Consider following M-C
|
||||
// Ignore any events prior to TabOpen and events that are triggered while
|
||||
// tabs are swapped between windows.
|
||||
if (event.originalTarget.initializingTab) {
|
||||
return;
|
||||
}
|
||||
if (!extension.canAccessWindow(event.originalTarget.ownerGlobal)) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
let changeInfo = {};
|
||||
let { extension } = this;
|
||||
let { tabManager } = extension;
|
||||
let tab = tabManager.getWrapper(event.detail.tabInfo);
|
||||
let changed = event.detail.changed;
|
||||
if (
|
||||
|
@ -182,6 +347,8 @@ class TabsUpdateFilterEventManager extends EventManager {
|
|||
};
|
||||
|
||||
let statusListener = ({ browser, status, url }) => {
|
||||
let { extension } = this;
|
||||
let { tabManager } = extension;
|
||||
let tabmail = browser.ownerDocument.getElementById("tabmail");
|
||||
let nativeTabInfo = tabmail.getTabForBrowser(browser);
|
||||
if (nativeTabInfo) {
|
||||
|
@ -202,60 +369,22 @@ class TabsUpdateFilterEventManager extends EventManager {
|
|||
windowTracker.addListener("status", statusListener);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (needsModified) {
|
||||
windowTracker.removeListener("TabAttrModified", listener);
|
||||
}
|
||||
if (filter.properties.has("status")) {
|
||||
windowTracker.removeListener("status", statusListener);
|
||||
}
|
||||
return {
|
||||
unregister() {
|
||||
if (needsModified) {
|
||||
windowTracker.removeListener("TabAttrModified", listener);
|
||||
}
|
||||
if (filter.properties.has("status")) {
|
||||
windowTracker.removeListener("status", statusListener);
|
||||
}
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
super({
|
||||
context,
|
||||
name: "tabs.onUpdated",
|
||||
register,
|
||||
});
|
||||
}
|
||||
|
||||
addListener(callback, filter) {
|
||||
let { extension } = this.context;
|
||||
if (
|
||||
filter &&
|
||||
filter.urls &&
|
||||
!extension.hasPermission("tabs") &&
|
||||
!extension.hasPermission("activeTab")
|
||||
) {
|
||||
console.error(
|
||||
'Url filtering in tabs.onUpdated requires "tabs" or "activeTab" permission.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return super.addListener(callback, filter);
|
||||
}
|
||||
}
|
||||
|
||||
this.tabs = class extends ExtensionAPI {
|
||||
onShutdown(isAppShutdown) {
|
||||
if (isAppShutdown) {
|
||||
return;
|
||||
}
|
||||
for (let window of Services.wm.getEnumerator("mail:3pane")) {
|
||||
let tabmail = window.document.getElementById("tabmail");
|
||||
for (let i = tabmail.tabInfo.length; i > 0; i--) {
|
||||
let nativeTabInfo = tabmail.tabInfo[i - 1];
|
||||
let uri = nativeTabInfo.browser?.browsingContext.currentURI;
|
||||
if (
|
||||
uri &&
|
||||
uri.scheme == "moz-extension" &&
|
||||
uri.host == this.extension.uuid
|
||||
) {
|
||||
tabmail.closeTab(nativeTabInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
getAPI(context) {
|
||||
let { extension } = context;
|
||||
|
@ -297,114 +426,52 @@ this.tabs = class extends ExtensionAPI {
|
|||
tabs: {
|
||||
onActivated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onActivated",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event);
|
||||
};
|
||||
|
||||
tabTracker.on("tab-activated", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-activated", listener);
|
||||
};
|
||||
},
|
||||
module: "tabs",
|
||||
event: "onActivated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onCreated",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(
|
||||
tabManager.convert(event.nativeTabInfo, event.currentTab)
|
||||
);
|
||||
};
|
||||
|
||||
tabTracker.on("tab-created", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-created", listener);
|
||||
};
|
||||
},
|
||||
module: "tabs",
|
||||
event: "onCreated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onAttached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onAttached",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {
|
||||
newWindowId: event.newWindowId,
|
||||
newPosition: event.newPosition,
|
||||
});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-attached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-attached", listener);
|
||||
};
|
||||
},
|
||||
module: "tabs",
|
||||
event: "onAttached",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onDetached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onDetached",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {
|
||||
oldWindowId: event.oldWindowId,
|
||||
oldPosition: event.oldPosition,
|
||||
});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-detached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-detached", listener);
|
||||
};
|
||||
},
|
||||
module: "tabs",
|
||||
event: "onDetached",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onRemoved",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {
|
||||
windowId: event.windowId,
|
||||
isWindowClosing: event.isWindowClosing,
|
||||
});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-removed", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-removed", listener);
|
||||
};
|
||||
},
|
||||
module: "tabs",
|
||||
event: "onRemoved",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onMoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onMoved",
|
||||
register: fire => {
|
||||
let moveListener = event => {
|
||||
let nativeTab = event.target;
|
||||
let nativeTabInfo = event.detail.tabInfo;
|
||||
let tabmail = nativeTab.ownerDocument.getElementById("tabmail");
|
||||
|
||||
fire.async(tabTracker.getId(nativeTabInfo), {
|
||||
windowId: windowTracker.getId(nativeTab.ownerGlobal),
|
||||
fromIndex: event.detail.idx,
|
||||
toIndex: tabmail.tabInfo.indexOf(nativeTabInfo),
|
||||
});
|
||||
};
|
||||
|
||||
windowTracker.addListener("TabMove", moveListener);
|
||||
return () => {
|
||||
windowTracker.removeListener("TabMove", moveListener);
|
||||
};
|
||||
},
|
||||
module: "tabs",
|
||||
event: "onMoved",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onUpdated: new TabsUpdateFilterEventManager({ context }).api(),
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
module: "tabs",
|
||||
event: "onUpdated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
async create(createProperties) {
|
||||
let window = await getNormalWindowReady(
|
||||
|
|
|
@ -52,7 +52,7 @@ class Theme {
|
|||
if (startupData && startupData.lwtData) {
|
||||
Object.assign(this, startupData);
|
||||
} else {
|
||||
// TODO: Update this part after bug 1550090
|
||||
// TODO: Update this part after bug 1550090.
|
||||
this.lwtStyles = {};
|
||||
this.lwtDarkStyles = null;
|
||||
if (darkDetails) {
|
||||
|
@ -418,8 +418,15 @@ class Theme {
|
|||
|
||||
this.theme = class extends ExtensionAPIPersistent {
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called).
|
||||
|
||||
onUpdated({ fire, context }) {
|
||||
let callback = (event, theme, windowId) => {
|
||||
let callback = async (event, theme, windowId) => {
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
if (windowId) {
|
||||
// Force access validation for incognito mode by getting the window.
|
||||
if (windowTracker.getWindow(windowId, context, false)) {
|
||||
|
@ -435,9 +442,9 @@ this.theme = class extends ExtensionAPIPersistent {
|
|||
unregister() {
|
||||
onUpdatedEmitter.off("theme-updated", callback);
|
||||
},
|
||||
convert(_fire, _context) {
|
||||
fire = _fire;
|
||||
context = _context;
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -5,41 +5,7 @@
|
|||
// The ext-* files are imported into the same scopes.
|
||||
/* import-globals-from ext-mail.js */
|
||||
|
||||
/**
|
||||
* An event manager API provider which listens for a DOM event in any browser
|
||||
* window, and calls the given listener function whenever an event is received.
|
||||
* That listener function receives a `fire` object, which it can use to dispatch
|
||||
* events to the extension, and a DOM event object.
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* The extension context which the event manager belongs to.
|
||||
* @param {string} name
|
||||
* The API name of the event manager, e.g.,"runtime.onMessage".
|
||||
* @param {string} event
|
||||
* The name of the DOM event to listen for.
|
||||
* @param {Function} listener
|
||||
* The listener function to call when a DOM event is received.
|
||||
*
|
||||
* @returns {object} An injectable api for the new event.
|
||||
*/
|
||||
function WindowEventManager(context, name, event, listener) {
|
||||
let register = fire => {
|
||||
let listener2 = (window, ...args) => {
|
||||
if (context.canAccessWindow(window)) {
|
||||
listener(fire, window, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
windowTracker.addListener(event, listener2);
|
||||
return () => {
|
||||
windowTracker.removeListener(event, listener2);
|
||||
};
|
||||
};
|
||||
|
||||
return new EventManager({ context, name, register }).api();
|
||||
}
|
||||
|
||||
this.windows = class extends ExtensionAPI {
|
||||
this.windows = class extends ExtensionAPIPersistent {
|
||||
onShutdown(isAppShutdown) {
|
||||
if (isAppShutdown) {
|
||||
return;
|
||||
|
@ -52,62 +18,124 @@ this.windows = class extends ExtensionAPI {
|
|||
}
|
||||
}
|
||||
|
||||
windowEventRegistrar({ windowEvent, listener }) {
|
||||
let { extension } = this;
|
||||
return ({ context, fire }) => {
|
||||
let listener2 = async (window, ...args) => {
|
||||
if (!extension.canAccessWindow(window)) {
|
||||
return;
|
||||
}
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
listener({ context, fire, window }, ...args);
|
||||
};
|
||||
windowTracker.addListener(windowEvent, listener2);
|
||||
return {
|
||||
unregister() {
|
||||
windowTracker.removeListener(windowEvent, listener2);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
PERSISTENT_EVENTS = {
|
||||
// For primed persistent events (deactivated background), the context is only
|
||||
// available after fire.wakeup() has fulfilled (ensuring the convert() function
|
||||
// has been called) (handled by windowEventRegistrar).
|
||||
|
||||
onCreated: this.windowEventRegistrar({
|
||||
windowEvent: "domwindowopened",
|
||||
listener: ({ context, fire, window }) => {
|
||||
fire.async(this.extension.windowManager.convert(window));
|
||||
},
|
||||
}),
|
||||
|
||||
onRemoved: this.windowEventRegistrar({
|
||||
windowEvent: "domwindowclosed",
|
||||
listener: ({ context, fire, window }) => {
|
||||
fire.async(windowTracker.getId(window));
|
||||
},
|
||||
}),
|
||||
|
||||
onFocusChanged({ context, fire }) {
|
||||
let { extension } = this;
|
||||
// Keep track of the last windowId used to fire an onFocusChanged event
|
||||
let lastOnFocusChangedWindowId;
|
||||
let scheduledEvents = [];
|
||||
|
||||
let listener = async event => {
|
||||
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
|
||||
// event when switching focus between two Thunderbird windows.
|
||||
// Note: This is not working for Linux, where we still get the -1
|
||||
await Promise.resolve();
|
||||
|
||||
let windowId = WindowBase.WINDOW_ID_NONE;
|
||||
let window = Services.focus.activeWindow;
|
||||
if (window) {
|
||||
if (!extension.canAccessWindow(window)) {
|
||||
return;
|
||||
}
|
||||
windowId = windowTracker.getId(window);
|
||||
}
|
||||
|
||||
// Using a FIFO to keep order of events, in case the last one
|
||||
// gets through without being placed on the async callback stack.
|
||||
scheduledEvents.push(windowId);
|
||||
if (fire.wakeup) {
|
||||
await fire.wakeup();
|
||||
}
|
||||
let scheduledWindowId = scheduledEvents.shift();
|
||||
|
||||
if (scheduledWindowId !== lastOnFocusChangedWindowId) {
|
||||
lastOnFocusChangedWindowId = scheduledWindowId;
|
||||
fire.async(scheduledWindowId);
|
||||
}
|
||||
};
|
||||
windowTracker.addListener("focus", listener);
|
||||
windowTracker.addListener("blur", listener);
|
||||
return {
|
||||
unregister() {
|
||||
windowTracker.removeListener("focus", listener);
|
||||
windowTracker.removeListener("blur", listener);
|
||||
},
|
||||
convert(newFire, extContext) {
|
||||
fire = newFire;
|
||||
context = extContext;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
getAPI(context) {
|
||||
const { extension } = context;
|
||||
const { windowManager } = extension;
|
||||
|
||||
return {
|
||||
windows: {
|
||||
onCreated: WindowEventManager(
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
"windows.onCreated",
|
||||
"domwindowopened",
|
||||
(fire, window) => {
|
||||
fire.async(windowManager.convert(window));
|
||||
}
|
||||
),
|
||||
module: "windows",
|
||||
event: "onCreated",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onRemoved: WindowEventManager(
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
"windows.onRemoved",
|
||||
"domwindowclosed",
|
||||
(fire, window) => {
|
||||
fire.async(windowTracker.getId(window));
|
||||
}
|
||||
),
|
||||
module: "windows",
|
||||
event: "onRemoved",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
onFocusChanged: new EventManager({
|
||||
context,
|
||||
name: "windows.onFocusChanged",
|
||||
register: fire => {
|
||||
// Keep track of the last windowId used to fire an onFocusChanged event
|
||||
let lastOnFocusChangedWindowId;
|
||||
|
||||
let listener = event => {
|
||||
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
|
||||
// event when switching focus between two Thunderbird windows.
|
||||
Promise.resolve().then(() => {
|
||||
let windowId = WindowBase.WINDOW_ID_NONE;
|
||||
let window = Services.focus.activeWindow;
|
||||
if (window) {
|
||||
if (!context.canAccessWindow(window)) {
|
||||
return;
|
||||
}
|
||||
windowId = windowTracker.getId(window);
|
||||
}
|
||||
if (windowId !== lastOnFocusChangedWindowId) {
|
||||
fire.async(windowId);
|
||||
lastOnFocusChangedWindowId = windowId;
|
||||
}
|
||||
});
|
||||
};
|
||||
windowTracker.addListener("focus", listener);
|
||||
windowTracker.addListener("blur", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("focus", listener);
|
||||
windowTracker.removeListener("blur", listener);
|
||||
};
|
||||
},
|
||||
module: "windows",
|
||||
event: "onFocusChanged",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
get(windowId, getInfo) {
|
||||
|
|
|
@ -634,10 +634,6 @@
|
|||
{
|
||||
"name": "node",
|
||||
"$ref": "ContactNode"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -15,6 +15,7 @@ tags = webextensions
|
|||
[browser_ext_addressBooksUI.js]
|
||||
tags = addrbook
|
||||
[browser_ext_browserAction.js]
|
||||
[browser_ext_browserAction_popup_click.js]
|
||||
[browser_ext_browserAction_properties.js]
|
||||
[browser_ext_cloudFile.js]
|
||||
support-files = data/cloudFile1.txt data/cloudFile2.txt
|
||||
|
@ -43,6 +44,7 @@ support-files = data/cloudFile1.txt data/cloudFile2.txt
|
|||
[browser_ext_compose_saveTemplate.js]
|
||||
[browser_ext_compose_sendMessage.js]
|
||||
[browser_ext_composeAction.js]
|
||||
[browser_ext_composeAction_popup_click.js]
|
||||
[browser_ext_composeAction_properties.js]
|
||||
[browser_ext_composeScripts.js]
|
||||
[browser_ext_contentScripts.js]
|
||||
|
@ -62,7 +64,11 @@ tags = contextmenu
|
|||
[browser_ext_message_external.js]
|
||||
support-files = messages/attachedMessageSample.eml
|
||||
[browser_ext_messageDisplay.js]
|
||||
[browser_ext_messageDisplay_headerMessageId.js]
|
||||
skip-if = true
|
||||
reason = FixMe: This is messing up msgHdr of test messages and breaks the following tests.
|
||||
[browser_ext_messageDisplayAction.js]
|
||||
[browser_ext_messageDisplayAction_popup_click.js]
|
||||
[browser_ext_messageDisplayAction_properties.js]
|
||||
[browser_ext_messageDisplayScripts.js]
|
||||
[browser_ext_quickFilter.js]
|
||||
|
@ -70,6 +76,8 @@ support-files = messages/attachedMessageSample.eml
|
|||
[browser_ext_tabs_content.js]
|
||||
[browser_ext_tabs_events.js]
|
||||
[browser_ext_tabs_query.js]
|
||||
[browser_ext_themes_onUpdated.js]
|
||||
[browser_ext_windows.js]
|
||||
[browser_ext_windows_events.js]
|
||||
[browser_ext_windows_types.js]
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ const { AddonManager } = ChromeUtils.import(
|
|||
let account;
|
||||
let messages;
|
||||
|
||||
add_task(async () => {
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
let subFolders = rootFolder.subFolders;
|
||||
|
@ -32,150 +32,53 @@ add_task(async () => {
|
|||
await BrowserTestUtils.browserLoaded(window.getMessagePaneBrowser());
|
||||
});
|
||||
|
||||
// This test clicks on the action button to open the popup.
|
||||
add_task(async function test_popup_open_with_click() {
|
||||
info("3-pane tab");
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
disable_button: true,
|
||||
window,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
use_default_popup: true,
|
||||
window,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
default_area: "tabstoolbar",
|
||||
window,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
disable_button: true,
|
||||
default_area: "tabstoolbar",
|
||||
window,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
use_default_popup: true,
|
||||
default_area: "tabstoolbar",
|
||||
window,
|
||||
});
|
||||
|
||||
info("Message window");
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
default_windows: ["messageDisplay"],
|
||||
disable_button: true,
|
||||
window: messageWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
default_windows: ["messageDisplay"],
|
||||
use_default_popup: true,
|
||||
window: messageWindow,
|
||||
});
|
||||
messageWindow.close();
|
||||
});
|
||||
|
||||
// This test uses a command from the menus API to open the popup.
|
||||
add_task(async function test_popup_open_with_menu_command() {
|
||||
info("3-pane tab");
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
use_default_popup: true,
|
||||
window,
|
||||
});
|
||||
for (let area of [null, "tabstoolbar"]) {
|
||||
let testConfig = {
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
default_area: area,
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
disable_button: true,
|
||||
window,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
window,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
default_area: "tabstoolbar",
|
||||
use_default_popup: true,
|
||||
window,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
default_area: "tabstoolbar",
|
||||
disable_button: true,
|
||||
window,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
default_area: "tabstoolbar",
|
||||
window,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
use_default_popup: true,
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
});
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
disable_button: true,
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-menu-command",
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
});
|
||||
messageWindow.close();
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
messageWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_theme_icons() {
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
/* 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 { AddonManager } = ChromeUtils.import(
|
||||
"resource://gre/modules/AddonManager.jsm"
|
||||
);
|
||||
|
||||
let account;
|
||||
let messages;
|
||||
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
let subFolders = rootFolder.subFolders;
|
||||
createMessages(subFolders[0], 10);
|
||||
messages = subFolders[0].messages;
|
||||
|
||||
// This tests selects a folder, so make sure the folder pane is visible.
|
||||
if (
|
||||
document.getElementById("folderpane_splitter").getAttribute("state") ==
|
||||
"collapsed"
|
||||
) {
|
||||
window.MsgToggleFolderPane();
|
||||
}
|
||||
if (window.IsMessagePaneCollapsed()) {
|
||||
window.MsgToggleMessagePane();
|
||||
}
|
||||
|
||||
window.gFolderTreeView.selectFolder(subFolders[0]);
|
||||
window.gFolderDisplay.selectViewIndex(0);
|
||||
await BrowserTestUtils.browserLoaded(window.getMessagePaneBrowser());
|
||||
});
|
||||
|
||||
// This test clicks on the action button to open the popup.
|
||||
add_task(async function test_popup_open_with_click() {
|
||||
info("3-pane tab");
|
||||
{
|
||||
let testConfig = {
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
default_area: "tabstoolbar",
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
default_area: "tabstoolbar",
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
default_area: "tabstoolbar",
|
||||
});
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
actionType: "browser_action",
|
||||
testType: "open-with-mouse-click",
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
messageWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
async function subtest_popup_open_with_click_MV3_event_pages(
|
||||
terminateBackground
|
||||
) {
|
||||
info("3-pane tab");
|
||||
for (let area of [null, "tabstoolbar"]) {
|
||||
let testConfig = {
|
||||
actionType: "action",
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
testType: "open-with-mouse-click",
|
||||
default_area: area,
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
actionType: "action",
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
testType: "open-with-mouse-click",
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
messageWindow.close();
|
||||
}
|
||||
}
|
||||
// This MV3 test clicks on the action button to open the popup.
|
||||
add_task(async function test_event_pages_without_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(false);
|
||||
});
|
||||
// This MV3 test clicks on the action button to open the popup (background termination).
|
||||
add_task(async function test_event_pages_with_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(true);
|
||||
});
|
|
@ -514,7 +514,7 @@ add_task(async function test_without_UI() {
|
|||
* cloudFile.onFileRename and cloudFile.onFileUploadAbort listeners with UI
|
||||
* interaction.
|
||||
*/
|
||||
add_task(async function test_compose_window() {
|
||||
add_task(async function test_compose_window_MV2() {
|
||||
let testFiles = {
|
||||
cloudFile1: new FileUtils.File(getTestFilePath("data/cloudFile1.txt")),
|
||||
cloudFile2: new FileUtils.File(getTestFilePath("data/cloudFile2.txt")),
|
||||
|
@ -760,6 +760,359 @@ add_task(async function test_compose_window() {
|
|||
composeWindow.close();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test persistent cloudFile.* events (onFileUpload, onFileDeleted, onFileRename,
|
||||
* onFileUploadAbort, onAccountAdded, onAccountDeleted) with UI interaction and
|
||||
* background terminations and background restarts.
|
||||
*/
|
||||
add_task(async function test_compose_window_MV3_event_page() {
|
||||
let testFiles = {
|
||||
cloudFile1: new FileUtils.File(getTestFilePath("data/cloudFile1.txt")),
|
||||
cloudFile2: new FileUtils.File(getTestFilePath("data/cloudFile2.txt")),
|
||||
};
|
||||
let uploads = {};
|
||||
let composeWindow;
|
||||
|
||||
async function background() {
|
||||
let abortResolveCallback;
|
||||
// Whenever the extension starts or wakes up, the eventCounter is reset and
|
||||
// allows to observe the order of events fired. In case of a wake-up, the
|
||||
// first observed event is the one that woke up the background.
|
||||
let eventCounter = 0;
|
||||
|
||||
browser.cloudFile.onFileUpload.addListener(
|
||||
async (uploadAccount, { id, name, data }, tab, relatedFileInfo) => {
|
||||
eventCounter++;
|
||||
browser.test.assertEq(
|
||||
eventCounter,
|
||||
1,
|
||||
"onFileUpload should be the wake up event"
|
||||
);
|
||||
let [
|
||||
{ cloudAccountId, composeTabId, aborting },
|
||||
] = await window.sendMessage("getEnvironment");
|
||||
browser.test.assertEq(tab.id, composeTabId);
|
||||
browser.test.assertEq(uploadAccount.id, cloudAccountId);
|
||||
browser.test.assertEq(name, "cloudFile1.txt");
|
||||
// eslint-disable-next-line mozilla/use-isInstance
|
||||
browser.test.assertTrue(data instanceof File);
|
||||
let content = await data.text();
|
||||
browser.test.assertEq(content, "you got the moves!\n");
|
||||
browser.test.assertEq(undefined, relatedFileInfo);
|
||||
|
||||
if (aborting) {
|
||||
let abortPromise = new Promise(resolve => {
|
||||
abortResolveCallback = resolve;
|
||||
});
|
||||
browser.test.sendMessage("uploadStarted", id);
|
||||
await abortPromise;
|
||||
setTimeout(() => {
|
||||
browser.test.sendMessage("uploadAborted");
|
||||
});
|
||||
return { aborted: true };
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
browser.test.sendMessage("uploadFinished", id);
|
||||
});
|
||||
return { url: "https://example.com/" + name };
|
||||
}
|
||||
);
|
||||
|
||||
browser.cloudFile.onFileRename.addListener(
|
||||
async (account, id, newName, tab) => {
|
||||
eventCounter++;
|
||||
browser.test.assertEq(
|
||||
eventCounter,
|
||||
1,
|
||||
"onFileRename should be the wake up event"
|
||||
);
|
||||
let [
|
||||
{ cloudAccountId, fileId, composeTabId },
|
||||
] = await window.sendMessage("getEnvironment");
|
||||
browser.test.assertEq(tab.id, composeTabId);
|
||||
browser.test.assertEq(account.id, cloudAccountId);
|
||||
browser.test.assertEq(id, fileId);
|
||||
browser.test.assertEq(newName, "cloudFile3.txt");
|
||||
setTimeout(() => {
|
||||
browser.test.sendMessage("renameFinished", id);
|
||||
});
|
||||
return { url: "https://example.com/" + newName };
|
||||
}
|
||||
);
|
||||
|
||||
browser.cloudFile.onFileDeleted.addListener(async (account, id, tab) => {
|
||||
eventCounter++;
|
||||
browser.test.assertEq(
|
||||
eventCounter,
|
||||
1,
|
||||
"onFileDeleted should be the wake up event"
|
||||
);
|
||||
let [{ cloudAccountId, fileId, composeTabId }] = await window.sendMessage(
|
||||
"getEnvironment"
|
||||
);
|
||||
browser.test.assertEq(tab.id, composeTabId);
|
||||
browser.test.assertEq(account.id, cloudAccountId);
|
||||
browser.test.assertEq(id, fileId);
|
||||
setTimeout(() => {
|
||||
browser.test.sendMessage("deleteFinished");
|
||||
});
|
||||
});
|
||||
|
||||
browser.cloudFile.onFileUploadAbort.addListener(
|
||||
async (account, id, tab) => {
|
||||
eventCounter++;
|
||||
browser.test.assertEq(
|
||||
eventCounter,
|
||||
2,
|
||||
"onFileUploadAbort should not be the wake up event"
|
||||
);
|
||||
let [
|
||||
{ cloudAccountId, fileId, composeTabId },
|
||||
] = await window.sendMessage("getEnvironment");
|
||||
browser.test.assertEq(tab.id, composeTabId);
|
||||
browser.test.assertEq(account.id, cloudAccountId);
|
||||
browser.test.assertEq(id, fileId);
|
||||
abortResolveCallback();
|
||||
}
|
||||
);
|
||||
|
||||
browser.cloudFile.onAccountAdded.addListener(account => {
|
||||
eventCounter++;
|
||||
browser.test.assertEq(
|
||||
eventCounter,
|
||||
1,
|
||||
"onAccountAdded should be the wake up event"
|
||||
);
|
||||
browser.test.sendMessage("accountCreated", account.id);
|
||||
});
|
||||
|
||||
browser.cloudFile.onAccountDeleted.addListener(async accountId => {
|
||||
eventCounter++;
|
||||
browser.test.assertEq(
|
||||
eventCounter,
|
||||
1,
|
||||
"onAccountDeleted should be the wake up event"
|
||||
);
|
||||
let [{ cloudAccountId }] = await window.sendMessage("getEnvironment");
|
||||
browser.test.assertEq(accountId, cloudAccountId);
|
||||
browser.test.notifyPass("finished");
|
||||
});
|
||||
|
||||
browser.runtime.onInstalled.addListener(async () => {
|
||||
eventCounter++;
|
||||
let [composeTab] = await browser.tabs.query({
|
||||
windowType: "messageCompose",
|
||||
});
|
||||
await window.sendMessage("setEnvironment", {
|
||||
composeTabId: composeTab.id,
|
||||
});
|
||||
browser.test.sendMessage("installed");
|
||||
});
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": background,
|
||||
"utils.js": await getUtilsJS(),
|
||||
},
|
||||
useAddonManager: "permanent",
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
cloud_file: {
|
||||
name: "mochitest",
|
||||
management_url: "/content/management.html",
|
||||
},
|
||||
browser_specific_settings: { gecko: { id: "cloudfile@mochi.test" } },
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
},
|
||||
});
|
||||
|
||||
function uploadFile(
|
||||
id,
|
||||
filename,
|
||||
expectedErrorStatus = Cr.NS_OK,
|
||||
expectedErrorMessage
|
||||
) {
|
||||
let cloudFileAccount = cloudFileAccounts.getAccount(id);
|
||||
|
||||
if (typeof expectedErrorStatus == "string") {
|
||||
expectedErrorStatus = cloudFileAccounts.constants[expectedErrorStatus];
|
||||
}
|
||||
|
||||
return cloudFileAccount.uploadFile(composeWindow, testFiles[filename]).then(
|
||||
upload => {
|
||||
Assert.equal(Cr.NS_OK, expectedErrorStatus);
|
||||
uploads[filename] = upload;
|
||||
},
|
||||
status => {
|
||||
Assert.equal(
|
||||
status.result,
|
||||
expectedErrorStatus,
|
||||
`Error status should be correct for ${testFiles[filename].leafName}`
|
||||
);
|
||||
Assert.equal(
|
||||
status.message,
|
||||
expectedErrorMessage,
|
||||
`Error message should be correct for ${testFiles[filename].leafName}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
function startUpload(id, filename) {
|
||||
let cloudFileAccount = cloudFileAccounts.getAccount(id);
|
||||
return cloudFileAccount
|
||||
.uploadFile(composeWindow, testFiles[filename])
|
||||
.catch(() => {});
|
||||
}
|
||||
function cancelUpload(id, filename) {
|
||||
let cloudFileAccount = cloudFileAccounts.getAccount(id);
|
||||
return cloudFileAccount.cancelFileUpload(
|
||||
composeWindow,
|
||||
testFiles[filename]
|
||||
);
|
||||
}
|
||||
function renameFile(
|
||||
id,
|
||||
uploadId,
|
||||
{ newName, newUrl },
|
||||
expectedErrorStatus = Cr.NS_OK,
|
||||
expectedErrorMessage
|
||||
) {
|
||||
let cloudFileAccount = cloudFileAccounts.getAccount(id);
|
||||
|
||||
if (typeof expectedErrorStatus == "string") {
|
||||
expectedErrorStatus = cloudFileAccounts.constants[expectedErrorStatus];
|
||||
}
|
||||
|
||||
return cloudFileAccount.renameFile(composeWindow, uploadId, newName).then(
|
||||
upload => {
|
||||
Assert.equal(Cr.NS_OK, expectedErrorStatus);
|
||||
Assert.equal(upload.name, newName, "New name should match.");
|
||||
Assert.equal(upload.url, newUrl, "New url should match.");
|
||||
},
|
||||
status => {
|
||||
Assert.equal(status.result, expectedErrorStatus);
|
||||
Assert.equal(
|
||||
status.message,
|
||||
expectedErrorMessage,
|
||||
`Error message should be correct.`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
function deleteFile(id, uploadId) {
|
||||
let cloudFileAccount = cloudFileAccounts.getAccount(id);
|
||||
return cloudFileAccount.deleteFile(composeWindow, uploadId);
|
||||
}
|
||||
|
||||
let environment = {};
|
||||
extension.onMessage("setEnvironment", data => {
|
||||
if (data.composeTabId) {
|
||||
environment.composeTabId = data.composeTabId;
|
||||
}
|
||||
extension.sendMessage();
|
||||
});
|
||||
extension.onMessage("getEnvironment", () => {
|
||||
extension.sendMessage(environment);
|
||||
});
|
||||
|
||||
let account = createAccount();
|
||||
addIdentity(account);
|
||||
|
||||
composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("installed");
|
||||
await extension.awaitMessage("background started");
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = [
|
||||
"onFileUpload",
|
||||
"onFileRename",
|
||||
"onFileDeleted",
|
||||
"onFileUploadAbort",
|
||||
"onAccountAdded",
|
||||
"onAccountDeleted",
|
||||
];
|
||||
for (let eventName of persistent_events) {
|
||||
assertPersistentListeners(extension, "cloudFile", eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Verify persistent listener, not yet primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Create account.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
cloudFileAccounts.createAccount("ext-cloudfile@mochi.test");
|
||||
await extension.awaitMessage("background started");
|
||||
environment.cloudAccountId = await extension.awaitMessage("accountCreated");
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Upload.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
uploadFile(environment.cloudAccountId, "cloudFile1");
|
||||
await extension.awaitMessage("background started");
|
||||
environment.fileId = await extension.awaitMessage("uploadFinished");
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Rename.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
renameFile(environment.cloudAccountId, environment.fileId, {
|
||||
newName: "cloudFile3.txt",
|
||||
newUrl: "https://example.com/cloudFile3.txt",
|
||||
});
|
||||
await extension.awaitMessage("background started");
|
||||
await extension.awaitMessage("renameFinished");
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Delete.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
deleteFile(environment.cloudAccountId, environment.fileId);
|
||||
await extension.awaitMessage("background started");
|
||||
await extension.awaitMessage("deleteFinished");
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Aborted upload.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
environment.aborting = true;
|
||||
startUpload(environment.cloudAccountId, "cloudFile1");
|
||||
await extension.awaitMessage("background started");
|
||||
environment.fileId = await extension.awaitMessage("uploadStarted");
|
||||
cancelUpload(environment.cloudAccountId, "cloudFile1");
|
||||
await extension.awaitMessage("uploadAborted");
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Remove account.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
cloudFileAccounts.removeAccount(environment.cloudAccountId);
|
||||
await extension.awaitMessage("background started");
|
||||
checkPersistentListeners({ primed: false });
|
||||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
composeWindow.close();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test cloudFiles without accounts and removed local files.
|
||||
*/
|
||||
|
@ -834,8 +1187,8 @@ add_task(async function test_incomplete_cloudFiles() {
|
|||
browser.compose.updateAttachment(composerTab.id, attachmentId, {
|
||||
name: "cloudFile3",
|
||||
}),
|
||||
`CloudFile Error: Account not found: account7`,
|
||||
"browser.compose.updateAttachment() should reject, if the local file does not exist."
|
||||
`CloudFile Error: Account not found: ${createdAccount.id}`,
|
||||
"browser.compose.updateAttachment() should reject, if the account does not exist."
|
||||
);
|
||||
|
||||
browser.test.notifyPass("finished");
|
||||
|
|
|
@ -124,7 +124,7 @@ async function testExecuteComposeActionWithOptions(options = {}) {
|
|||
await extension.unload();
|
||||
}
|
||||
|
||||
add_task(async function prepare_test() {
|
||||
add_setup(async () => {
|
||||
gAccount = createAccount();
|
||||
addIdentity(gAccount);
|
||||
});
|
||||
|
|
|
@ -139,7 +139,7 @@ async function testExecuteMessageDisplayActionWithOptions(msg, options = {}) {
|
|||
await extension.unload();
|
||||
}
|
||||
|
||||
add_task(async function prepare_test() {
|
||||
add_setup(async () => {
|
||||
let account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
let subFolders = rootFolder.subFolders;
|
||||
|
|
|
@ -2,189 +2,189 @@
|
|||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_user_defined_commands() {
|
||||
const testCommands = [
|
||||
// Ctrl Shortcuts
|
||||
{
|
||||
name: "toggle-ctrl-a",
|
||||
shortcut: "Ctrl+A",
|
||||
key: "A",
|
||||
// Does not work in compose window on Linux.
|
||||
skip: ["messageCompose"],
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
var testCommands = [
|
||||
// Ctrl Shortcuts
|
||||
{
|
||||
name: "toggle-ctrl-a",
|
||||
shortcut: "Ctrl+A",
|
||||
key: "A",
|
||||
// Does not work in compose window on Linux.
|
||||
skip: ["messageCompose"],
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-up",
|
||||
shortcut: "Ctrl+Up",
|
||||
key: "VK_UP",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-up",
|
||||
shortcut: "Ctrl+Up",
|
||||
key: "VK_UP",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
// Alt Shortcuts
|
||||
{
|
||||
name: "toggle-alt-a",
|
||||
shortcut: "Alt+A",
|
||||
key: "A",
|
||||
// Does not work in compose window on Mac.
|
||||
skip: ["messageCompose"],
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
},
|
||||
},
|
||||
// Alt Shortcuts
|
||||
{
|
||||
name: "toggle-alt-a",
|
||||
shortcut: "Alt+A",
|
||||
key: "A",
|
||||
// Does not work in compose window on Mac.
|
||||
skip: ["messageCompose"],
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-alt-down",
|
||||
shortcut: "Alt+Down",
|
||||
key: "VK_DOWN",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-alt-down",
|
||||
shortcut: "Alt+Down",
|
||||
key: "VK_DOWN",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
},
|
||||
// Mac Shortcuts
|
||||
{
|
||||
name: "toggle-command-shift-page-up",
|
||||
shortcutMac: "Command+Shift+PageUp",
|
||||
key: "VK_PAGE_UP",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
},
|
||||
// Mac Shortcuts
|
||||
{
|
||||
name: "toggle-command-shift-page-up",
|
||||
shortcutMac: "Command+Shift+PageUp",
|
||||
key: "VK_PAGE_UP",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-mac-control-shift+period",
|
||||
shortcut: "Ctrl+Shift+Period",
|
||||
shortcutMac: "MacCtrl+Shift+Period",
|
||||
key: "VK_PERIOD",
|
||||
modifiers: {
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-mac-control-shift+period",
|
||||
shortcut: "Ctrl+Shift+Period",
|
||||
shortcutMac: "MacCtrl+Shift+Period",
|
||||
key: "VK_PERIOD",
|
||||
modifiers: {
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
// Ctrl+Shift Shortcuts
|
||||
{
|
||||
name: "toggle-ctrl-shift-left",
|
||||
shortcut: "Ctrl+Shift+Left",
|
||||
key: "VK_LEFT",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
},
|
||||
// Ctrl+Shift Shortcuts
|
||||
{
|
||||
name: "toggle-ctrl-shift-left",
|
||||
shortcut: "Ctrl+Shift+Left",
|
||||
key: "VK_LEFT",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-shift-1",
|
||||
shortcut: "Ctrl+Shift+1",
|
||||
key: "1",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-shift-1",
|
||||
shortcut: "Ctrl+Shift+1",
|
||||
key: "1",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
// Alt+Shift Shortcuts
|
||||
{
|
||||
name: "toggle-alt-shift-1",
|
||||
shortcut: "Alt+Shift+1",
|
||||
key: "1",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
},
|
||||
// Alt+Shift Shortcuts
|
||||
{
|
||||
name: "toggle-alt-shift-1",
|
||||
shortcut: "Alt+Shift+1",
|
||||
key: "1",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-alt-shift-a",
|
||||
shortcut: "Alt+Shift+A",
|
||||
key: "A",
|
||||
// Does not work in compose window on Mac.
|
||||
skip: ["messageCompose"],
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-alt-shift-a",
|
||||
shortcut: "Alt+Shift+A",
|
||||
key: "A",
|
||||
// Does not work in compose window on Mac.
|
||||
skip: ["messageCompose"],
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-alt-shift-right",
|
||||
shortcut: "Alt+Shift+Right",
|
||||
key: "VK_RIGHT",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-alt-shift-right",
|
||||
shortcut: "Alt+Shift+Right",
|
||||
key: "VK_RIGHT",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
// Function keys
|
||||
{
|
||||
name: "function-keys-Alt+Shift+F3",
|
||||
shortcut: "Alt+Shift+F3",
|
||||
key: "VK_F3",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
},
|
||||
// Function keys
|
||||
{
|
||||
name: "function-keys-Alt+Shift+F3",
|
||||
shortcut: "Alt+Shift+F3",
|
||||
key: "VK_F3",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
{
|
||||
name: "function-keys-F2",
|
||||
shortcut: "F2",
|
||||
key: "VK_F2",
|
||||
modifiers: {
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "function-keys-F2",
|
||||
shortcut: "F2",
|
||||
key: "VK_F2",
|
||||
modifiers: {
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
},
|
||||
// Misc Shortcuts
|
||||
{
|
||||
name: "valid-command-with-unrecognized-property-name",
|
||||
shortcut: "Alt+Shift+3",
|
||||
key: "3",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
unrecognized_property: "with-a-random-value",
|
||||
},
|
||||
// Misc Shortcuts
|
||||
{
|
||||
name: "valid-command-with-unrecognized-property-name",
|
||||
shortcut: "Alt+Shift+3",
|
||||
key: "3",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
{
|
||||
name: "spaces-in-shortcut-name",
|
||||
shortcut: " Alt + Shift + 2 ",
|
||||
key: "2",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
unrecognized_property: "with-a-random-value",
|
||||
},
|
||||
{
|
||||
name: "spaces-in-shortcut-name",
|
||||
shortcut: " Alt + Shift + 2 ",
|
||||
key: "2",
|
||||
modifiers: {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-space",
|
||||
shortcut: "Ctrl+Space",
|
||||
key: "VK_SPACE",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-space",
|
||||
shortcut: "Ctrl+Space",
|
||||
key: "VK_SPACE",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-comma",
|
||||
shortcut: "Ctrl+Comma",
|
||||
key: "VK_COMMA",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-comma",
|
||||
shortcut: "Ctrl+Comma",
|
||||
key: "VK_COMMA",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-period",
|
||||
shortcut: "Ctrl+Period",
|
||||
key: "VK_PERIOD",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-period",
|
||||
shortcut: "Ctrl+Period",
|
||||
key: "VK_PERIOD",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-alt-v",
|
||||
shortcut: "Ctrl+Alt+V",
|
||||
key: "V",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
altKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "toggle-ctrl-alt-v",
|
||||
shortcut: "Ctrl+Alt+V",
|
||||
key: "V",
|
||||
modifiers: {
|
||||
accelKey: true,
|
||||
altKey: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function test_user_defined_commands() {
|
||||
let win1 = await openNewMailWindow();
|
||||
|
||||
let commands = {};
|
||||
|
@ -218,7 +218,10 @@ add_task(async function test_user_defined_commands() {
|
|||
|
||||
function background() {
|
||||
browser.commands.onCommand.addListener((commandName, activeTab) => {
|
||||
browser.test.sendMessage("oncommand", { commandName, activeTab });
|
||||
browser.test.sendMessage("oncommand event received", {
|
||||
commandName,
|
||||
activeTab,
|
||||
});
|
||||
});
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
@ -253,7 +256,7 @@ add_task(async function test_user_defined_commands() {
|
|||
continue;
|
||||
}
|
||||
EventUtils.synthesizeKey(testCommand.key, testCommand.modifiers, window);
|
||||
let message = await extension.awaitMessage("oncommand");
|
||||
let message = await extension.awaitMessage("oncommand event received");
|
||||
is(
|
||||
message.commandName,
|
||||
testCommand.name,
|
||||
|
@ -309,7 +312,7 @@ add_task(async function test_user_defined_commands() {
|
|||
"Expected keyset of window #3 to have the correct number of children"
|
||||
);
|
||||
|
||||
// Confirm that the commands are registered to both windows.
|
||||
// Confirm that the commands are registered to all windows.
|
||||
await focusWindow(win1);
|
||||
await runTest(win1, "mail");
|
||||
|
||||
|
@ -319,12 +322,221 @@ add_task(async function test_user_defined_commands() {
|
|||
await focusWindow(win3);
|
||||
await runTest(win3, "messageCompose");
|
||||
|
||||
// Mitigation for "waiting for vsync to be disabled" error.
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise(r => win3.setTimeout(r, 250));
|
||||
// Unload the extension and confirm that the keysets have been removed from all windows.
|
||||
await extension.unload();
|
||||
|
||||
keyset = win1.document.getElementById(keysetID);
|
||||
is(keyset, null, "Expected keyset to be removed from the window #1");
|
||||
|
||||
keyset = win2.document.getElementById(keysetID);
|
||||
is(keyset, null, "Expected keyset to be removed from the window #2");
|
||||
|
||||
keyset = win3.document.getElementById(keysetID);
|
||||
is(keyset, null, "Expected keyset to be removed from the window #3");
|
||||
|
||||
await BrowserTestUtils.closeWindow(win1);
|
||||
await BrowserTestUtils.closeWindow(win2);
|
||||
await BrowserTestUtils.closeWindow(win3);
|
||||
|
||||
SimpleTest.endMonitorConsole();
|
||||
await waitForConsole;
|
||||
});
|
||||
|
||||
add_task(async function test_commands_MV3_event_page() {
|
||||
let win1 = await openNewMailWindow();
|
||||
|
||||
let commands = {};
|
||||
let isMac = AppConstants.platform == "macosx";
|
||||
let totalMacOnlyCommands = 0;
|
||||
let numberNumericCommands = 4;
|
||||
|
||||
for (let testCommand of testCommands) {
|
||||
let command = {
|
||||
suggested_key: {},
|
||||
};
|
||||
|
||||
if (testCommand.shortcut) {
|
||||
command.suggested_key.default = testCommand.shortcut;
|
||||
}
|
||||
|
||||
if (testCommand.shortcutMac) {
|
||||
command.suggested_key.mac = testCommand.shortcutMac;
|
||||
}
|
||||
|
||||
if (testCommand.shortcutMac && !testCommand.shortcut) {
|
||||
totalMacOnlyCommands++;
|
||||
}
|
||||
|
||||
if (testCommand.unrecognized_property) {
|
||||
command.unrecognized_property = testCommand.unrecognized_property;
|
||||
}
|
||||
|
||||
commands[testCommand.name] = command;
|
||||
}
|
||||
|
||||
function background() {
|
||||
// Whenever the extension starts or wakes up, the eventCounter is reset and
|
||||
// allows to observe the order of events fired. In case of a wake-up, the
|
||||
// first observed event is the one that woke up the background.
|
||||
let eventCounter = 0;
|
||||
|
||||
browser.commands.onCommand.addListener(async (commandName, activeTab) => {
|
||||
browser.test.sendMessage("oncommand event received", {
|
||||
eventCount: ++eventCounter,
|
||||
commandName,
|
||||
activeTab,
|
||||
});
|
||||
});
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": background,
|
||||
"utils.js": await getUtilsJS(),
|
||||
},
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
browser_specific_settings: { gecko: { id: "cloudfile@mochi.test" } },
|
||||
commands,
|
||||
},
|
||||
});
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
let waitForConsole = new Promise(resolve => {
|
||||
SimpleTest.monitorConsole(resolve, [
|
||||
{
|
||||
message: /Reading manifest: Warning processing commands.*.unrecognized_property: An unexpected property was found/,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// Unrecognized_property in manifest triggers warning.
|
||||
ExtensionTestUtils.failOnSchemaWarnings(false);
|
||||
await extension.startup();
|
||||
ExtensionTestUtils.failOnSchemaWarnings(true);
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
// Check for persistent listener.
|
||||
assertPersistentListeners(extension, "commands", "onCommand", {
|
||||
primed: false,
|
||||
});
|
||||
|
||||
let gEventCounter = 0;
|
||||
async function runTest(window, expectedTabType) {
|
||||
// The second run will terminate the background script before each keypress,
|
||||
// verifying that the background script is waking up correctly.
|
||||
for (let terminateBackground of [false, true]) {
|
||||
for (let testCommand of testCommands) {
|
||||
if (testCommand.skip && testCommand.skip.includes(expectedTabType)) {
|
||||
continue;
|
||||
}
|
||||
if (testCommand.shortcutMac && !testCommand.shortcut && !isMac) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (terminateBackground) {
|
||||
gEventCounter = 0;
|
||||
}
|
||||
|
||||
if (terminateBackground) {
|
||||
// Terminate the background and verify the primed persistent listener.
|
||||
await extension.terminateBackground({
|
||||
disableResetIdleForTest: true,
|
||||
});
|
||||
assertPersistentListeners(extension, "commands", "onCommand", {
|
||||
primed: true,
|
||||
});
|
||||
EventUtils.synthesizeKey(
|
||||
testCommand.key,
|
||||
testCommand.modifiers,
|
||||
window
|
||||
);
|
||||
// Wait for background restart.
|
||||
await extension.awaitMessage("ready");
|
||||
} else {
|
||||
EventUtils.synthesizeKey(
|
||||
testCommand.key,
|
||||
testCommand.modifiers,
|
||||
window
|
||||
);
|
||||
}
|
||||
|
||||
let message = await extension.awaitMessage("oncommand event received");
|
||||
is(
|
||||
testCommand.name,
|
||||
message.commandName,
|
||||
`onCommand listener should fire with the correct command name`
|
||||
);
|
||||
is(
|
||||
expectedTabType,
|
||||
message.activeTab.type,
|
||||
`onCommand listener should fire with the correct tab type`
|
||||
);
|
||||
is(
|
||||
++gEventCounter,
|
||||
message.eventCount,
|
||||
`Event counter should be correct`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create another window after the extension is loaded.
|
||||
let win2 = await openNewMailWindow();
|
||||
|
||||
let totalTestCommands =
|
||||
Object.keys(testCommands).length + numberNumericCommands;
|
||||
let expectedCommandsRegistered = isMac
|
||||
? totalTestCommands
|
||||
: totalTestCommands - totalMacOnlyCommands;
|
||||
|
||||
let account = createAccount();
|
||||
addIdentity(account);
|
||||
let win3 = await openComposeWindow(account);
|
||||
// Some key combinations do not work if the TO field has focus.
|
||||
win3.document.querySelector("editor").focus();
|
||||
|
||||
// Confirm the keysets have been added to both windows.
|
||||
let keysetID = `ext-keyset-id-${makeWidgetId(extension.id)}`;
|
||||
|
||||
let keyset = win1.document.getElementById(keysetID);
|
||||
ok(keyset != null, "Expected keyset to exist");
|
||||
is(
|
||||
keyset.children.length,
|
||||
expectedCommandsRegistered,
|
||||
"Expected keyset of window #1 to have the correct number of children"
|
||||
);
|
||||
|
||||
keyset = win2.document.getElementById(keysetID);
|
||||
ok(keyset != null, "Expected keyset to exist");
|
||||
is(
|
||||
keyset.children.length,
|
||||
expectedCommandsRegistered,
|
||||
"Expected keyset of window #2 to have the correct number of children"
|
||||
);
|
||||
|
||||
keyset = win3.document.getElementById(keysetID);
|
||||
ok(keyset != null, "Expected keyset to exist");
|
||||
is(
|
||||
keyset.children.length,
|
||||
expectedCommandsRegistered,
|
||||
"Expected keyset of window #3 to have the correct number of children"
|
||||
);
|
||||
|
||||
// Confirm that the commands are registered to all windows.
|
||||
await focusWindow(win1);
|
||||
await runTest(win1, "mail");
|
||||
|
||||
await focusWindow(win2);
|
||||
await runTest(win2, "mail");
|
||||
|
||||
await focusWindow(win3);
|
||||
await runTest(win3, "messageCompose");
|
||||
|
||||
// Unload the extension and confirm that the keysets have been removed from all windows.
|
||||
await extension.unload();
|
||||
|
||||
// Confirm that the keysets have been removed from both windows after the extension is unloaded.
|
||||
keyset = win1.document.getElementById(keysetID);
|
||||
is(keyset, null, "Expected keyset to be removed from the window #1");
|
||||
|
||||
|
|
|
@ -11,115 +11,36 @@ const { AppConstants } = ChromeUtils.importESModule(
|
|||
|
||||
let account;
|
||||
|
||||
add_task(async () => {
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
addIdentity(account);
|
||||
});
|
||||
|
||||
// This test clicks on the action button to open the popup.
|
||||
add_task(async function test_popup_open_with_click() {
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
disable_button: true,
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
use_default_popup: true,
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
default_area: "formattoolbar",
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
default_area: "formattoolbar",
|
||||
disable_button: true,
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
default_area: "formattoolbar",
|
||||
use_default_popup: true,
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
composeWindow.close();
|
||||
Services.xulStore.removeDocument(
|
||||
"chrome://messenger/content/messengercompose/messengercompose.xhtml"
|
||||
);
|
||||
});
|
||||
|
||||
// This test uses a command from the menus API to open the popup.
|
||||
add_task(async function test_popup_open_with_menu_command() {
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-menu-command",
|
||||
use_default_popup: true,
|
||||
default_area: "maintoolbar",
|
||||
window: composeWindow,
|
||||
});
|
||||
for (let area of ["maintoolbar", "formattoolbar"]) {
|
||||
let testConfig = {
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-menu-command",
|
||||
default_area: area,
|
||||
window: composeWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-menu-command",
|
||||
use_default_popup: true,
|
||||
default_area: "formattoolbar",
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-menu-command",
|
||||
disable_button: true,
|
||||
default_area: "maintoolbar",
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-menu-command",
|
||||
disable_button: true,
|
||||
default_area: "formattoolbar",
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-menu-command",
|
||||
default_area: "maintoolbar",
|
||||
window: composeWindow,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-menu-command",
|
||||
default_area: "formattoolbar",
|
||||
window: composeWindow,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
}
|
||||
|
||||
composeWindow.close();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/* 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 { AddonManager } = ChromeUtils.import(
|
||||
"resource://gre/modules/AddonManager.jsm"
|
||||
);
|
||||
const { AppConstants } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
);
|
||||
|
||||
let account;
|
||||
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
addIdentity(account);
|
||||
});
|
||||
|
||||
// This test clicks on the action button to open the popup.
|
||||
add_task(async function test_popup_open_with_click() {
|
||||
for (let area of [null, "formattoolbar"]) {
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: composeWindow,
|
||||
default_area: area,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: composeWindow,
|
||||
default_area: area,
|
||||
disable_button: true,
|
||||
});
|
||||
|
||||
await run_popup_test({
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: composeWindow,
|
||||
default_area: area,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
composeWindow.close();
|
||||
Services.xulStore.removeDocument(
|
||||
"chrome://messenger/content/messengercompose/messengercompose.xhtml"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
async function subtest_popup_open_with_click_MV3_event_pages(
|
||||
terminateBackground
|
||||
) {
|
||||
for (let area of [null, "formattoolbar"]) {
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: composeWindow,
|
||||
default_area: area,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
composeWindow.close();
|
||||
Services.xulStore.removeDocument(
|
||||
"chrome://messenger/content/messengercompose/messengercompose.xhtml"
|
||||
);
|
||||
}
|
||||
}
|
||||
// This MV3 test clicks on the action button to open the popup.
|
||||
add_task(async function test_event_pages_without_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(false);
|
||||
});
|
||||
// This MV3 test clicks on the action button to open the popup (background termination).
|
||||
add_task(async function test_event_pages_with_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(true);
|
||||
});
|
|
@ -16,7 +16,8 @@ var { ExtensionSupport } = ChromeUtils.import(
|
|||
"resource:///modules/ExtensionSupport.jsm"
|
||||
);
|
||||
|
||||
addIdentity(createAccount());
|
||||
let account = createAccount();
|
||||
let defaultIdentity = addIdentity(account);
|
||||
|
||||
function findWindow(subject) {
|
||||
let windows = Array.from(Services.wm.getEnumerator("msgcompose"));
|
||||
|
@ -2112,3 +2113,161 @@ add_task(async function test_without_permission() {
|
|||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_attachment_MV3_event_pages() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, the eventCounter is reset and
|
||||
// allows to observe the order of events fired. In case of a wake-up, the
|
||||
// first observed event is the one that woke up the background.
|
||||
let eventCounter = 0;
|
||||
|
||||
browser.compose.onAttachmentAdded.addListener(async (tab, attachment) => {
|
||||
browser.test.sendMessage("attachment added", {
|
||||
eventCount: ++eventCounter,
|
||||
attachment,
|
||||
});
|
||||
});
|
||||
|
||||
browser.compose.onAttachmentRemoved.addListener(
|
||||
async (tab, attachmentId) => {
|
||||
browser.test.sendMessage("attachment removed", {
|
||||
eventCount: ++eventCounter,
|
||||
attachmentId,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "compose", "messagesRead"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "compose.attachment@mochi.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function addAttachment(ordinal) {
|
||||
let attachment = Cc[
|
||||
"@mozilla.org/messengercompose/attachment;1"
|
||||
].createInstance(Ci.nsIMsgAttachment);
|
||||
attachment.name = `${ordinal}.txt`;
|
||||
attachment.url = `data:text/plain,I'm the ${ordinal} attachment!`;
|
||||
attachment.size = attachment.url.length - 16;
|
||||
|
||||
await composeWindow.AddAttachments([attachment]);
|
||||
return attachment;
|
||||
}
|
||||
|
||||
async function removeAttachment(attachment) {
|
||||
let item = composeWindow.gAttachmentBucket.findItemForAttachment(
|
||||
attachment
|
||||
);
|
||||
await composeWindow.RemoveAttachments([item]);
|
||||
}
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = [
|
||||
"compose.onAttachmentAdded",
|
||||
"compose.onAttachmentRemoved",
|
||||
];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Trigger events without terminating the background first.
|
||||
|
||||
let rawFirstAttachment = await addAttachment("first");
|
||||
let addedFirst = await extension.awaitMessage("attachment added");
|
||||
Assert.equal(
|
||||
"first.txt",
|
||||
rawFirstAttachment.name,
|
||||
"Created attachment should be correct"
|
||||
);
|
||||
Assert.equal(
|
||||
"first.txt",
|
||||
addedFirst.attachment.name,
|
||||
"Attachment returned by onAttachmentAdded should be correct"
|
||||
);
|
||||
Assert.equal(1, addedFirst.eventCount, "Event counter should be correct");
|
||||
|
||||
await removeAttachment(rawFirstAttachment);
|
||||
|
||||
let removedFirst = await extension.awaitMessage("attachment removed");
|
||||
Assert.equal(
|
||||
addedFirst.attachment.id,
|
||||
removedFirst.attachmentId,
|
||||
"Attachment id returned by onAttachmentRemoved should be correct"
|
||||
);
|
||||
Assert.equal(2, removedFirst.eventCount, "Event counter should be correct");
|
||||
|
||||
// Terminate background and re-trigger onAttachmentAdded event.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// The listeners should be primed.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
let rawSecondAttachment = await addAttachment("second");
|
||||
let addedSecond = await extension.awaitMessage("attachment added");
|
||||
Assert.equal(
|
||||
"second.txt",
|
||||
rawSecondAttachment.name,
|
||||
"Created attachment should be correct"
|
||||
);
|
||||
Assert.equal(
|
||||
"second.txt",
|
||||
addedSecond.attachment.name,
|
||||
"Attachment returned by onAttachmentAdded should be correct"
|
||||
);
|
||||
Assert.equal(1, addedSecond.eventCount, "Event counter should be correct");
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Terminate background and re-trigger onAttachmentRemoved event.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// The listeners should be primed.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
await removeAttachment(rawSecondAttachment);
|
||||
let removedSecond = await extension.awaitMessage("attachment removed");
|
||||
Assert.equal(
|
||||
addedSecond.attachment.id,
|
||||
removedSecond.attachmentId,
|
||||
"Attachment id returned by onAttachmentRemoved should be correct"
|
||||
);
|
||||
Assert.equal(1, removedSecond.eventCount, "Event counter should be correct");
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
await extension.unload();
|
||||
composeWindow.close();
|
||||
});
|
||||
|
|
|
@ -444,6 +444,168 @@ add_task(async function testHeaders() {
|
|||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_onIdentityChanged_MV3_event_pages() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, the eventCounter is reset and
|
||||
// allows to observe the order of events fired. In case of a wake-up, the
|
||||
// first observed event is the one that woke up the background.
|
||||
let eventCounter = 0;
|
||||
|
||||
browser.compose.onIdentityChanged.addListener(async (tab, identityId) => {
|
||||
browser.test.sendMessage("identity changed", {
|
||||
eventCount: ++eventCounter,
|
||||
identityId,
|
||||
});
|
||||
});
|
||||
|
||||
browser.compose.onComposeStateChanged.addListener(async (tab, state) => {
|
||||
browser.test.sendMessage("compose state changed", {
|
||||
eventCount: ++eventCounter,
|
||||
state,
|
||||
});
|
||||
});
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "addressBooks", "compose", "messagesRead"],
|
||||
browser_specific_settings: { gecko: { id: "compose@mochi.test" } },
|
||||
},
|
||||
});
|
||||
|
||||
function changeIdentity(newIdentity) {
|
||||
let composeDocument = composeWindow.document;
|
||||
|
||||
let identityList = composeDocument.getElementById("msgIdentity");
|
||||
let identityItem = identityList.querySelector(
|
||||
`[identitykey="${newIdentity}"]`
|
||||
);
|
||||
ok(identityItem);
|
||||
identityList.selectedItem = identityItem;
|
||||
composeWindow.LoadIdentity(false);
|
||||
}
|
||||
|
||||
function setToAddr(to) {
|
||||
composeWindow.SetComposeDetails({ to });
|
||||
}
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = [
|
||||
"compose.onIdentityChanged",
|
||||
"compose.onComposeStateChanged",
|
||||
];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Trigger events without terminating the background first.
|
||||
|
||||
changeIdentity(nonDefaultIdentity.key);
|
||||
{
|
||||
let rv = await extension.awaitMessage("identity changed");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
eventCount: 1,
|
||||
identityId: nonDefaultIdentity.key,
|
||||
},
|
||||
rv,
|
||||
"The non-primed onIdentityChanged event should return the correct values"
|
||||
);
|
||||
}
|
||||
|
||||
setToAddr("user@invalid.net");
|
||||
{
|
||||
let rv = await extension.awaitMessage("compose state changed");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
eventCount: 2,
|
||||
state: {
|
||||
canSendNow: true,
|
||||
canSendLater: true,
|
||||
},
|
||||
},
|
||||
rv,
|
||||
"The non-primed onComposeStateChanged should return the correct values"
|
||||
);
|
||||
}
|
||||
|
||||
// Terminate background and re-trigger onIdentityChanged event.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// The listeners should be primed.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
changeIdentity(defaultIdentity.key);
|
||||
{
|
||||
let rv = await extension.awaitMessage("identity changed");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
eventCount: 1,
|
||||
identityId: defaultIdentity.key,
|
||||
},
|
||||
rv,
|
||||
"The primed onIdentityChanged event should return the correct values"
|
||||
);
|
||||
}
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Terminate background and re-trigger onComposeStateChanged event.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// The listeners should be primed.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
setToAddr("invalid");
|
||||
{
|
||||
let rv = await extension.awaitMessage("compose state changed");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
eventCount: 1,
|
||||
state: {
|
||||
canSendNow: false,
|
||||
canSendLater: false,
|
||||
},
|
||||
},
|
||||
rv,
|
||||
"The primed onComposeStateChanged should return the correct values"
|
||||
);
|
||||
}
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
await extension.unload();
|
||||
composeWindow.close();
|
||||
});
|
||||
|
||||
add_task(async function testCustomHeaders() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
let account = createAccount();
|
||||
let defaultIdentity = addIdentity(account);
|
||||
|
||||
add_task(async function testDictionaries() {
|
||||
add_task(async function test_dictionaries() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
function verifyDictionaries(dictionaries, expected) {
|
||||
|
@ -103,3 +103,112 @@ add_task(async function testDictionaries() {
|
|||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_onActiveDictionariesChanged_MV3_event_pages() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
browser.compose.onActiveDictionariesChanged.addListener(
|
||||
async (tab, dictionaries) => {
|
||||
// Only send the first event after background wake-up, this should be
|
||||
// the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage(
|
||||
"onActiveDictionariesChanged received",
|
||||
dictionaries
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["compose"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "compose.dictionary@xpcshell.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = ["compose.onActiveDictionariesChanged"];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function setActiveDictionaries(activeDictionaries) {
|
||||
let installedDictionaries = Cc["@mozilla.org/spellchecker/engine;1"]
|
||||
.getService(Ci.mozISpellCheckingEngine)
|
||||
.getDictionaryList();
|
||||
|
||||
for (let dict of activeDictionaries) {
|
||||
if (!installedDictionaries.includes(dict)) {
|
||||
throw new Error(`Dictionary not found: ${dict}`);
|
||||
}
|
||||
}
|
||||
|
||||
await composeWindow.ComposeChangeLanguage(activeDictionaries);
|
||||
}
|
||||
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Trigger onActiveDictionariesChanged without terminating the background first.
|
||||
|
||||
setActiveDictionaries(["en-US"]);
|
||||
let newActiveDictionary1 = await extension.awaitMessage(
|
||||
"onActiveDictionariesChanged received"
|
||||
);
|
||||
Assert.equal(
|
||||
newActiveDictionary1["en-US"],
|
||||
true,
|
||||
"Returned active dictionary should be correct"
|
||||
);
|
||||
|
||||
// Terminate background and re-trigger onActiveDictionariesChanged.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// The listeners should be primed.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
setActiveDictionaries([]);
|
||||
let newActiveDictionary2 = await extension.awaitMessage(
|
||||
"onActiveDictionariesChanged received"
|
||||
);
|
||||
Assert.equal(
|
||||
newActiveDictionary2["en-US"],
|
||||
false,
|
||||
"Returned active dictionary should be correct"
|
||||
);
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listener should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
await extension.unload();
|
||||
composeWindow.close();
|
||||
});
|
||||
|
|
|
@ -906,3 +906,105 @@ add_task(async function testMultipleListeners() {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_MV3_event_pages() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
browser.compose.onBeforeSend.addListener((tab, details) => {
|
||||
// Only send the first event after background wake-up, this should be
|
||||
// the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage("onBeforeSend received", details);
|
||||
}
|
||||
|
||||
// Let us abort, so we do not have to re-open the compose window for
|
||||
// multiple tests.
|
||||
return {
|
||||
cancel: true,
|
||||
};
|
||||
});
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["compose"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "compose.onBeforeSend@xpcshell.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = ["compose.onBeforeSend"];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function beginSend() {
|
||||
composeWindow.GenericSendMessage(Ci.nsIMsgCompDeliverMode.Now).catch(() => {
|
||||
// This test is ignoring errors thrown by GenericSendMessage, but looks
|
||||
// at didTryToSendMessage of the mocked CompleteGenericSendMessage to
|
||||
// check if onBeforeSend aborted the send process.
|
||||
});
|
||||
}
|
||||
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Trigger onBeforeSend without terminating the background first.
|
||||
|
||||
composeWindow.SetComposeDetails({ to: "first@invalid.net" });
|
||||
beginSend();
|
||||
let firstDetails = await extension.awaitMessage("onBeforeSend received");
|
||||
Assert.equal(
|
||||
"first@invalid.net",
|
||||
firstDetails.to,
|
||||
"Returned details should be correct"
|
||||
);
|
||||
|
||||
// Terminate background and re-trigger onBeforeSend.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// The listeners should be primed.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
composeWindow.SetComposeDetails({ to: "second@invalid.net" });
|
||||
beginSend();
|
||||
let secondDetails = await extension.awaitMessage("onBeforeSend received");
|
||||
Assert.equal(
|
||||
"second@invalid.net",
|
||||
secondDetails.to,
|
||||
"Returned details should be correct"
|
||||
);
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listener should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
await extension.unload();
|
||||
composeWindow.close();
|
||||
});
|
||||
|
|
|
@ -61,25 +61,27 @@ function getSmtpIdentity(senderName, smtpServer) {
|
|||
|
||||
var gServer;
|
||||
var gLocalRootFolder;
|
||||
let gPopAccount;
|
||||
let gLocalAccount;
|
||||
|
||||
add_setup(() => {
|
||||
gServer = setupServerDaemon();
|
||||
gServer.start();
|
||||
|
||||
// Test needs a non-local default account to be able to send messages.
|
||||
let popAccount = createAccount("pop3");
|
||||
let localAccount = createAccount("local");
|
||||
MailServices.accounts.defaultAccount = popAccount;
|
||||
gPopAccount = createAccount("pop3");
|
||||
gLocalAccount = createAccount("local");
|
||||
MailServices.accounts.defaultAccount = gPopAccount;
|
||||
|
||||
let identity = getSmtpIdentity(
|
||||
"identity@foo.invalid",
|
||||
getBasicSmtpServer(gServer.port)
|
||||
);
|
||||
popAccount.addIdentity(identity);
|
||||
popAccount.defaultIdentity = identity;
|
||||
gPopAccount.addIdentity(identity);
|
||||
gPopAccount.defaultIdentity = identity;
|
||||
|
||||
// Test is using the Sent folder and Outbox folder of the local account.
|
||||
gLocalRootFolder = localAccount.incomingServer.rootFolder;
|
||||
gLocalRootFolder = gLocalAccount.incomingServer.rootFolder;
|
||||
gLocalRootFolder.createSubfolder("Sent", null);
|
||||
gLocalRootFolder.createSubfolder("Drafts", null);
|
||||
gLocalRootFolder.createSubfolder("Fcc", null);
|
||||
|
@ -323,3 +325,92 @@ add_task(async function test_saveAsDraft_with_additional_fcc() {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Test onAfterSave when saving drafts for MV3
|
||||
add_task(async function test_onAfterSave_MV3_event_pages() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
browser.compose.onAfterSave.addListener((tab, saveInfo) => {
|
||||
// Only send the first event after background wake-up, this should be
|
||||
// the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage("onAfterSave received", saveInfo);
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["compose"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "compose.onAfterSave@xpcshell.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = ["compose.onAfterSave"];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let composeWindow = await openComposeWindow(gPopAccount);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Trigger onAfterSave without terminating the background first.
|
||||
|
||||
composeWindow.SetComposeDetails({ to: "first@invalid.net" });
|
||||
composeWindow.SaveAsDraft();
|
||||
let firstSaveInfo = await extension.awaitMessage("onAfterSave received");
|
||||
Assert.equal(
|
||||
"draft",
|
||||
firstSaveInfo.mode,
|
||||
"Returned SaveInfo should be correct"
|
||||
);
|
||||
|
||||
// Terminate background and re-trigger onAfterSave.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// The listeners should be primed.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
composeWindow.SetComposeDetails({ to: "second@invalid.net" });
|
||||
composeWindow.SaveAsDraft();
|
||||
let secondSaveInfo = await extension.awaitMessage("onAfterSave received");
|
||||
Assert.equal(
|
||||
"draft",
|
||||
secondSaveInfo.mode,
|
||||
"Returned SaveInfo should be correct"
|
||||
);
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listener should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
await extension.unload();
|
||||
composeWindow.close();
|
||||
});
|
||||
|
|
|
@ -61,25 +61,27 @@ function getSmtpIdentity(senderName, smtpServer) {
|
|||
|
||||
var gServer;
|
||||
var gLocalRootFolder;
|
||||
let gPopAccount;
|
||||
let gLocalAccount;
|
||||
|
||||
add_setup(() => {
|
||||
gServer = setupServerDaemon();
|
||||
gServer.start();
|
||||
|
||||
// Test needs a non-local default account to be able to send messages.
|
||||
let popAccount = createAccount("pop3");
|
||||
let localAccount = createAccount("local");
|
||||
MailServices.accounts.defaultAccount = popAccount;
|
||||
gPopAccount = createAccount("pop3");
|
||||
gLocalAccount = createAccount("local");
|
||||
MailServices.accounts.defaultAccount = gPopAccount;
|
||||
|
||||
let identity = getSmtpIdentity(
|
||||
"identity@foo.invalid",
|
||||
getBasicSmtpServer(gServer.port)
|
||||
);
|
||||
popAccount.addIdentity(identity);
|
||||
popAccount.defaultIdentity = identity;
|
||||
gPopAccount.addIdentity(identity);
|
||||
gPopAccount.defaultIdentity = identity;
|
||||
|
||||
// Test is using the Sent folder and Outbox folder of the local account.
|
||||
gLocalRootFolder = localAccount.incomingServer.rootFolder;
|
||||
gLocalRootFolder = gLocalAccount.incomingServer.rootFolder;
|
||||
gLocalRootFolder.createSubfolder("Sent", null);
|
||||
gLocalRootFolder.createSubfolder("Templates", null);
|
||||
gLocalRootFolder.createSubfolder("Fcc", null);
|
||||
|
@ -339,3 +341,92 @@ add_task(async function test_saveAsTemplate_with_additional_fcc() {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Test onAfterSave when saving templates for MV3
|
||||
add_task(async function test_onAfterSave_MV3_event_pages() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
browser.compose.onAfterSave.addListener((tab, saveInfo) => {
|
||||
// Only send the first event after background wake-up, this should be
|
||||
// the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage("onAfterSave received", saveInfo);
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["compose"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "compose.onAfterSave@xpcshell.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = ["compose.onAfterSave"];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let composeWindow = await openComposeWindow(gPopAccount);
|
||||
await focusWindow(composeWindow);
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Trigger onAfterSave without terminating the background first.
|
||||
|
||||
composeWindow.SetComposeDetails({ to: "first@invalid.net" });
|
||||
composeWindow.SaveAsTemplate();
|
||||
let firstSaveInfo = await extension.awaitMessage("onAfterSave received");
|
||||
Assert.equal(
|
||||
"template",
|
||||
firstSaveInfo.mode,
|
||||
"Returned SaveInfo should be correct"
|
||||
);
|
||||
|
||||
// Terminate background and re-trigger onAfterSave.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// The listeners should be primed.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
composeWindow.SetComposeDetails({ to: "second@invalid.net" });
|
||||
composeWindow.SaveAsTemplate();
|
||||
let secondSaveInfo = await extension.awaitMessage("onAfterSave received");
|
||||
Assert.equal(
|
||||
"template",
|
||||
secondSaveInfo.mode,
|
||||
"Returned SaveInfo should be correct"
|
||||
);
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listener should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
await extension.unload();
|
||||
composeWindow.close();
|
||||
});
|
||||
|
|
|
@ -68,25 +68,27 @@ function tracksentMessages(aSubject, aTopic, aMsgID) {
|
|||
var gServer;
|
||||
var gOutbox;
|
||||
var gSentMessages = [];
|
||||
let gPopAccount;
|
||||
let gLocalAccount;
|
||||
|
||||
add_setup(() => {
|
||||
gServer = setupServerDaemon();
|
||||
gServer.start();
|
||||
|
||||
// Test needs a non-local default account to be able to send messages.
|
||||
let popAccount = createAccount("pop3");
|
||||
let localAccount = createAccount("local");
|
||||
MailServices.accounts.defaultAccount = popAccount;
|
||||
gPopAccount = createAccount("pop3");
|
||||
gLocalAccount = createAccount("local");
|
||||
MailServices.accounts.defaultAccount = gPopAccount;
|
||||
|
||||
let identity = getSmtpIdentity(
|
||||
"identity@foo.invalid",
|
||||
getBasicSmtpServer(gServer.port)
|
||||
);
|
||||
popAccount.addIdentity(identity);
|
||||
popAccount.defaultIdentity = identity;
|
||||
gPopAccount.addIdentity(identity);
|
||||
gPopAccount.defaultIdentity = identity;
|
||||
|
||||
// Test is using the Sent folder and Outbox folder of the local account.
|
||||
let rootFolder = localAccount.incomingServer.rootFolder;
|
||||
let rootFolder = gLocalAccount.incomingServer.rootFolder;
|
||||
rootFolder.createSubfolder("Sent", null);
|
||||
MailServices.accounts.setSpecialFolders();
|
||||
gOutbox = rootFolder.getChildNamed("Outbox");
|
||||
|
@ -639,3 +641,93 @@ add_task(async function test_onComposeStateChanged() {
|
|||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
// Test onAfterSend for MV3
|
||||
add_task(async function test_onAfterSend_MV3_event_pages() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
browser.compose.onAfterSend.addListener(async (tab, sendInfo) => {
|
||||
// Only send the first event after background wake-up, this should be
|
||||
// the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage("onAfterSend received", sendInfo);
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["compose"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "compose.onAfterSend@xpcshell.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = ["compose.onAfterSend"];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Trigger onAfterSend without terminating the background first.
|
||||
|
||||
let firstComposeWindow = await openComposeWindow(gPopAccount);
|
||||
await focusWindow(firstComposeWindow);
|
||||
firstComposeWindow.SetComposeDetails({ to: "first@invalid.net" });
|
||||
firstComposeWindow.SetComposeDetails({ subject: "First message" });
|
||||
firstComposeWindow.SendMessage();
|
||||
let firstSaveInfo = await extension.awaitMessage("onAfterSend received");
|
||||
Assert.equal(
|
||||
"sendNow",
|
||||
firstSaveInfo.mode,
|
||||
"Returned SaveInfo should be correct"
|
||||
);
|
||||
|
||||
// Terminate background and re-trigger onAfterSend.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// The listeners should be primed.
|
||||
checkPersistentListeners({ primed: true });
|
||||
let secondComposeWindow = await openComposeWindow(gPopAccount);
|
||||
await focusWindow(secondComposeWindow);
|
||||
secondComposeWindow.SetComposeDetails({ to: "second@invalid.net" });
|
||||
secondComposeWindow.SetComposeDetails({ subject: "Second message" });
|
||||
secondComposeWindow.SendMessage();
|
||||
let secondSaveInfo = await extension.awaitMessage("onAfterSend received");
|
||||
Assert.equal(
|
||||
"sendNow",
|
||||
secondSaveInfo.mode,
|
||||
"Returned SaveInfo should be correct"
|
||||
);
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listener should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
let account, rootFolder, subFolders;
|
||||
|
||||
add_setup(async function() {
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
rootFolder = account.incomingServer.rootFolder;
|
||||
rootFolder.createSubfolder("test1", null);
|
||||
|
@ -915,3 +915,136 @@ add_task(async function test_setSelectedMessages() {
|
|||
tabmail.closeOtherTabs(tabmail.tabModes.folder.tabs[0]);
|
||||
window.gFolderTreeView.selectFolder(rootFolder);
|
||||
});
|
||||
|
||||
add_task(async function test_MV3_event_pages() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
for (let eventName of [
|
||||
"onDisplayedFolderChanged",
|
||||
"onSelectedMessagesChanged",
|
||||
]) {
|
||||
browser.mailTabs[eventName].addListener((...args) => {
|
||||
// Only send the first event after background wake-up, this should be
|
||||
// the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage(`${eventName} received`, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "messagesRead"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "mailtabs@mochi.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = [
|
||||
"mailTabs.onDisplayedFolderChanged",
|
||||
"mailTabs.onSelectedMessagesChanged",
|
||||
];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Select a folder.
|
||||
|
||||
{
|
||||
window.gFolderTreeView.selectFolder(subFolders.test1);
|
||||
let displayInfo = await extension.awaitMessage(
|
||||
"onDisplayedFolderChanged received"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
active: true,
|
||||
type: "mail",
|
||||
},
|
||||
{ name: "test1", path: "/test1" },
|
||||
],
|
||||
[
|
||||
{
|
||||
active: displayInfo[0].active,
|
||||
type: displayInfo[0].type,
|
||||
},
|
||||
{ name: displayInfo[1].name, path: displayInfo[1].path },
|
||||
],
|
||||
"The primed onDisplayedFolderChanged event should return the correct values"
|
||||
);
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
}
|
||||
|
||||
// Select multiple messages.
|
||||
|
||||
{
|
||||
let messages = [...subFolders.test1.messages].slice(0, 5);
|
||||
window.gFolderDisplay.selectMessages(messages);
|
||||
let displayInfo = await extension.awaitMessage(
|
||||
"onSelectedMessagesChanged received"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
[
|
||||
"Big Meeting Today",
|
||||
"Small Party Tomorrow",
|
||||
"Huge Shindig Yesterday",
|
||||
"Tiny Wedding In a Fortnight",
|
||||
"Red Document Needs Attention",
|
||||
],
|
||||
displayInfo[1].messages.map(e => e.subject),
|
||||
"The primed onSelectedMessagesChanged event should return the correct values"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
{
|
||||
active: true,
|
||||
type: "mail",
|
||||
},
|
||||
{
|
||||
active: displayInfo[0].active,
|
||||
type: displayInfo[0].type,
|
||||
},
|
||||
"The primed onSelectedMessagesChanged event should return the correct values"
|
||||
);
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
|
|
@ -266,7 +266,7 @@ function getExtensionDetails(...permissions) {
|
|||
};
|
||||
}
|
||||
|
||||
add_setup(async function() {
|
||||
add_setup(async () => {
|
||||
await Services.search.init();
|
||||
|
||||
gAccount = createAccount();
|
||||
|
|
|
@ -159,7 +159,7 @@ function getExtensionDetails(...permissions) {
|
|||
};
|
||||
}
|
||||
|
||||
add_task(async function set_up() {
|
||||
add_setup(async () => {
|
||||
await Services.search.init();
|
||||
|
||||
gAccount = createAccount();
|
||||
|
|
|
@ -717,27 +717,27 @@ add_task(async function testOpenMessagesInDefault() {
|
|||
let promisedTabs = [];
|
||||
promisedTabs.push(
|
||||
browser.messageDisplay.open({
|
||||
headerMessageId: messages1[0].headerMessageId,
|
||||
messageId: messages1[0].id,
|
||||
})
|
||||
);
|
||||
promisedTabs.push(
|
||||
browser.messageDisplay.open({
|
||||
headerMessageId: messages1[1].headerMessageId,
|
||||
messageId: messages1[1].id,
|
||||
})
|
||||
);
|
||||
promisedTabs.push(
|
||||
browser.messageDisplay.open({
|
||||
headerMessageId: messages1[2].headerMessageId,
|
||||
messageId: messages1[2].id,
|
||||
})
|
||||
);
|
||||
promisedTabs.push(
|
||||
browser.messageDisplay.open({
|
||||
headerMessageId: messages1[3].headerMessageId,
|
||||
messageId: messages1[3].id,
|
||||
})
|
||||
);
|
||||
promisedTabs.push(
|
||||
browser.messageDisplay.open({
|
||||
headerMessageId: messages1[4].headerMessageId,
|
||||
messageId: messages1[4].id,
|
||||
})
|
||||
);
|
||||
let openedTabs = await Promise.allSettled(promisedTabs);
|
||||
|
@ -772,3 +772,342 @@ add_task(async function testOpenMessagesInDefault() {
|
|||
await extension.awaitFinish();
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_MV3_event_pages_onMessageDisplayed() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
browser.messageDisplay.onMessageDisplayed.addListener((tab, message) => {
|
||||
// Only send the first event after background wake-up, this should be
|
||||
// the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage("onMessageDisplayed received", {
|
||||
tab,
|
||||
message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "messagesRead"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "onMessageDisplayed@mochi.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = ["messageDisplay.onMessageDisplayed"];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Select a message.
|
||||
|
||||
{
|
||||
window.gFolderTreeView.selectFolder(gFolder);
|
||||
window.gFolderDisplay.selectMessages([gMessages[2]]);
|
||||
|
||||
let displayInfo = await extension.awaitMessage(
|
||||
"onMessageDisplayed received"
|
||||
);
|
||||
Assert.equal(
|
||||
displayInfo.message.subject,
|
||||
"Huge Shindig Yesterday",
|
||||
"The primed onMessageDisplayed event should return the correct message."
|
||||
);
|
||||
Assert.deepEqual(
|
||||
{
|
||||
active: true,
|
||||
type: "mail",
|
||||
},
|
||||
{
|
||||
active: displayInfo.tab.active,
|
||||
type: displayInfo.tab.type,
|
||||
},
|
||||
"The primed onMessageDisplayed event should return the correct values"
|
||||
);
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
}
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Open a message in a window.
|
||||
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(gMessages[0]);
|
||||
let displayInfo = await extension.awaitMessage(
|
||||
"onMessageDisplayed received"
|
||||
);
|
||||
Assert.equal(
|
||||
displayInfo.message.subject,
|
||||
"Big Meeting Today",
|
||||
"The primed onMessageDisplayed event should return the correct message."
|
||||
);
|
||||
Assert.deepEqual(
|
||||
{
|
||||
active: true,
|
||||
type: "messageDisplay",
|
||||
},
|
||||
{
|
||||
active: displayInfo.tab.active,
|
||||
type: displayInfo.tab.type,
|
||||
},
|
||||
"The primed onMessageDisplayed event should return the correct values"
|
||||
);
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
messageWindow.close();
|
||||
}
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Open a message in a tab.
|
||||
|
||||
{
|
||||
await openMessageInTab(gMessages[1]);
|
||||
let displayInfo = await extension.awaitMessage(
|
||||
"onMessageDisplayed received"
|
||||
);
|
||||
Assert.equal(
|
||||
displayInfo.message.subject,
|
||||
"Small Party Tomorrow",
|
||||
"The primed onMessageDisplayed event should return the correct message."
|
||||
);
|
||||
Assert.deepEqual(
|
||||
{
|
||||
active: true,
|
||||
type: "messageDisplay",
|
||||
},
|
||||
{
|
||||
active: displayInfo.tab.active,
|
||||
type: displayInfo.tab.type,
|
||||
},
|
||||
"The primed onMessageDisplayed event should return the correct values"
|
||||
);
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
document.getElementById("tabmail").closeTab();
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_MV3_event_pages_onMessagesDisplayed() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
browser.messageDisplay.onMessagesDisplayed.addListener(
|
||||
(tab, messages) => {
|
||||
// Only send the first event after background wake-up, this should be
|
||||
// the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage("onMessagesDisplayed received", {
|
||||
tab,
|
||||
messages,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "messagesRead"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "onMessagesDisplayed@mochi.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = ["messageDisplay.onMessagesDisplayed"];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Select multiple messages.
|
||||
|
||||
{
|
||||
window.gFolderTreeView.selectFolder(gFolder);
|
||||
window.gFolderDisplay.selectMessages(gMessages);
|
||||
|
||||
let displayInfo = await extension.awaitMessage(
|
||||
"onMessagesDisplayed received"
|
||||
);
|
||||
Assert.equal(
|
||||
displayInfo.messages.length,
|
||||
5,
|
||||
"The primed onMessagesDisplayed event should return the correct number of messages."
|
||||
);
|
||||
Assert.deepEqual(
|
||||
[
|
||||
"Big Meeting Today",
|
||||
"Small Party Tomorrow",
|
||||
"Huge Shindig Yesterday",
|
||||
"Tiny Wedding In a Fortnight",
|
||||
"Red Document Needs Attention",
|
||||
],
|
||||
displayInfo.messages.map(e => e.subject),
|
||||
"The primed onMessagesDisplayed event should return the correct messages."
|
||||
);
|
||||
Assert.deepEqual(
|
||||
{
|
||||
active: true,
|
||||
type: "mail",
|
||||
},
|
||||
{
|
||||
active: displayInfo.tab.active,
|
||||
type: displayInfo.tab.type,
|
||||
},
|
||||
"The primed onMessagesDisplayed event should return the correct values"
|
||||
);
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
}
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Open a message in a window.
|
||||
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(gMessages[0]);
|
||||
let displayInfo = await extension.awaitMessage(
|
||||
"onMessagesDisplayed received"
|
||||
);
|
||||
Assert.equal(
|
||||
displayInfo.messages.length,
|
||||
1,
|
||||
"The primed onMessagesDisplayed event should return the correct number of messages."
|
||||
);
|
||||
Assert.equal(
|
||||
displayInfo.messages[0].subject,
|
||||
"Big Meeting Today",
|
||||
"The primed onMessagesDisplayed event should return the correct message."
|
||||
);
|
||||
Assert.deepEqual(
|
||||
{
|
||||
active: true,
|
||||
type: "messageDisplay",
|
||||
},
|
||||
{
|
||||
active: displayInfo.tab.active,
|
||||
type: displayInfo.tab.type,
|
||||
},
|
||||
"The primed onMessagesDisplayed event should return the correct values"
|
||||
);
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
messageWindow.close();
|
||||
}
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Open a message in a tab.
|
||||
|
||||
{
|
||||
await openMessageInTab(gMessages[1]);
|
||||
let displayInfo = await extension.awaitMessage(
|
||||
"onMessagesDisplayed received"
|
||||
);
|
||||
Assert.equal(
|
||||
displayInfo.messages.length,
|
||||
1,
|
||||
"The primed onMessagesDisplayed event should return the correct number of messages."
|
||||
);
|
||||
Assert.equal(
|
||||
displayInfo.messages[0].subject,
|
||||
"Small Party Tomorrow",
|
||||
"The primed onMessagesDisplayed event should return the correct message."
|
||||
);
|
||||
Assert.deepEqual(
|
||||
{
|
||||
active: true,
|
||||
type: "messageDisplay",
|
||||
},
|
||||
{
|
||||
active: displayInfo.tab.active,
|
||||
type: displayInfo.tab.type,
|
||||
},
|
||||
"The primed onMessagesDisplayed event should return the correct values"
|
||||
);
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// The listeners should be persistent, but not primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
document.getElementById("tabmail").closeTab();
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ const { AddonManager } = ChromeUtils.import(
|
|||
let account;
|
||||
let messages;
|
||||
|
||||
add_task(async () => {
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
let subFolders = rootFolder.subFolders;
|
||||
|
@ -32,132 +32,76 @@ add_task(async () => {
|
|||
await BrowserTestUtils.browserLoaded(window.getMessagePaneBrowser());
|
||||
});
|
||||
|
||||
// This test clicks on the action button to open the popup.
|
||||
add_task(async function test_popup_open_with_click() {
|
||||
info("3-pane tab");
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
disable_button: true,
|
||||
window,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
use_default_popup: true,
|
||||
window,
|
||||
});
|
||||
|
||||
info("Message tab");
|
||||
await openMessageInTab(messages.getNext());
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
disable_button: true,
|
||||
window,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
use_default_popup: true,
|
||||
window,
|
||||
});
|
||||
document.getElementById("tabmail").closeTab();
|
||||
|
||||
info("Message window");
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: messageWindow,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
disable_button: true,
|
||||
window: messageWindow,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
use_default_popup: true,
|
||||
window: messageWindow,
|
||||
});
|
||||
messageWindow.close();
|
||||
});
|
||||
|
||||
// This test uses a command from the menus API to open the popup.
|
||||
add_task(async function test_popup_open_with_menu_command() {
|
||||
info("3-pane tab");
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
window,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
use_default_popup: true,
|
||||
window,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
disable_button: true,
|
||||
window,
|
||||
});
|
||||
{
|
||||
let testConfig = {
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message tab");
|
||||
await openMessageInTab(messages.getNext());
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
window,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
use_default_popup: true,
|
||||
window,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
disable_button: true,
|
||||
window,
|
||||
});
|
||||
document.getElementById("tabmail").closeTab();
|
||||
{
|
||||
await openMessageInTab(messages.getNext());
|
||||
let testConfig = {
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
|
||||
document.getElementById("tabmail").closeTab();
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
window: messageWindow,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
use_default_popup: true,
|
||||
window: messageWindow,
|
||||
});
|
||||
await run_popup_test({
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
disable_button: true,
|
||||
window: messageWindow,
|
||||
});
|
||||
messageWindow.close();
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-menu-command",
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
|
||||
messageWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_theme_icons() {
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
/* 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/. */
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
const { AddonManager } = ChromeUtils.import(
|
||||
"resource://gre/modules/AddonManager.jsm"
|
||||
);
|
||||
|
||||
let account;
|
||||
let messages;
|
||||
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
let subFolders = rootFolder.subFolders;
|
||||
createMessages(subFolders[0], 10);
|
||||
messages = subFolders[0].messages;
|
||||
|
||||
// This tests selects a folder, so make sure the folder pane is visible.
|
||||
if (
|
||||
document.getElementById("folderpane_splitter").getAttribute("state") ==
|
||||
"collapsed"
|
||||
) {
|
||||
window.MsgToggleFolderPane();
|
||||
}
|
||||
if (window.IsMessagePaneCollapsed()) {
|
||||
window.MsgToggleMessagePane();
|
||||
}
|
||||
|
||||
window.gFolderTreeView.selectFolder(subFolders[0]);
|
||||
window.gFolderDisplay.selectViewIndex(0);
|
||||
await BrowserTestUtils.browserLoaded(window.getMessagePaneBrowser());
|
||||
});
|
||||
|
||||
// This test clicks on the action button to open the popup.
|
||||
add_task(async function test_popup_open_with_click() {
|
||||
info("3-pane tab");
|
||||
{
|
||||
let testConfig = {
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message tab");
|
||||
{
|
||||
await openMessageInTab(messages.getNext());
|
||||
let testConfig = {
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
document.getElementById("tabmail").closeTab();
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
messageWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
async function subtest_popup_open_with_click_MV3_event_pages(
|
||||
terminateBackground
|
||||
) {
|
||||
info("3-pane tab");
|
||||
{
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message tab");
|
||||
{
|
||||
await openMessageInTab(messages.getNext());
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
document.getElementById("tabmail").closeTab();
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
messageWindow.close();
|
||||
}
|
||||
}
|
||||
// This MV3 test clicks on the action button to open the popup.
|
||||
add_task(async function test_event_pages_without_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(false);
|
||||
});
|
||||
// This MV3 test clicks on the action button to open the popup (background termination).
|
||||
add_task(async function test_event_pages_with_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(true);
|
||||
});
|
|
@ -5,7 +5,7 @@
|
|||
let account, messages;
|
||||
let messagePane = document.getElementById("messagepane");
|
||||
|
||||
add_task(async () => {
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
rootFolder.createSubfolder("messageDisplayScripts", null);
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/* 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/. */
|
||||
|
||||
add_setup(async () => {
|
||||
let account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
rootFolder.createSubfolder("folder0", null);
|
||||
|
||||
let subFolders = {};
|
||||
for (let folder of rootFolder.subFolders) {
|
||||
subFolders[folder.name] = folder;
|
||||
}
|
||||
createMessages(subFolders.folder0, 5);
|
||||
});
|
||||
|
||||
add_task(async function testOpenMessagesInDefault() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": async () => {
|
||||
// Verify startup conditions.
|
||||
let accounts = await browser.accounts.list();
|
||||
browser.test.assertEq(
|
||||
1,
|
||||
accounts.length,
|
||||
`number of accounts should be correct`
|
||||
);
|
||||
|
||||
let folder0 = accounts[0].folders.find(f => f.name == "folder0");
|
||||
browser.test.assertTrue(!!folder0, "folder should exist");
|
||||
let { messages: messages1 } = await browser.messages.list(folder0);
|
||||
browser.test.assertEq(
|
||||
5,
|
||||
messages1.length,
|
||||
`number of messages should be correct`
|
||||
);
|
||||
|
||||
// Open multiple messages using their headerMessageIds.
|
||||
let promisedTabs = [];
|
||||
promisedTabs.push(
|
||||
await browser.messageDisplay.open({
|
||||
headerMessageId: messages1[0].headerMessageId,
|
||||
})
|
||||
);
|
||||
promisedTabs.push(
|
||||
await browser.messageDisplay.open({
|
||||
headerMessageId: messages1[1].headerMessageId,
|
||||
})
|
||||
);
|
||||
promisedTabs.push(
|
||||
await browser.messageDisplay.open({
|
||||
headerMessageId: messages1[2].headerMessageId,
|
||||
})
|
||||
);
|
||||
promisedTabs.push(
|
||||
await browser.messageDisplay.open({
|
||||
headerMessageId: messages1[3].headerMessageId,
|
||||
})
|
||||
);
|
||||
promisedTabs.push(
|
||||
await browser.messageDisplay.open({
|
||||
headerMessageId: messages1[4].headerMessageId,
|
||||
})
|
||||
);
|
||||
let openedTabs = await Promise.allSettled(promisedTabs);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
browser.test.assertEq(
|
||||
"fulfilled",
|
||||
openedTabs[i].status,
|
||||
`Promise for the opened message should have been fulfilled for message ${i}`
|
||||
);
|
||||
let msg = await browser.messageDisplay.getDisplayedMessage(
|
||||
openedTabs[i].value.id
|
||||
);
|
||||
browser.test.assertEq(
|
||||
messages1[i].id,
|
||||
msg.id,
|
||||
`Should see the correct message in window ${i}`
|
||||
);
|
||||
await browser.tabs.remove(openedTabs[i].value.id);
|
||||
}
|
||||
|
||||
browser.test.notifyPass();
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
},
|
||||
manifest: {
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "messagesRead", "tabs"],
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish();
|
||||
await extension.unload();
|
||||
});
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
let account, rootFolder, subFolders;
|
||||
|
||||
add_task(async () => {
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
rootFolder = account.incomingServer.rootFolder;
|
||||
subFolders = rootFolder.subFolders;
|
||||
|
|
|
@ -13,357 +13,593 @@ add_task(async () => {
|
|||
let messages = [...testFolder.messages];
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: async () => {
|
||||
let listener = {
|
||||
events: [],
|
||||
currentPromise: null,
|
||||
|
||||
pushEvent(...args) {
|
||||
browser.test.log(JSON.stringify(args));
|
||||
this.events.push(args);
|
||||
if (this.currentPromise) {
|
||||
let p = this.currentPromise;
|
||||
this.currentPromise = null;
|
||||
p.resolve(args);
|
||||
}
|
||||
},
|
||||
onCreated(...args) {
|
||||
this.pushEvent("onCreated", ...args);
|
||||
},
|
||||
onUpdated(...args) {
|
||||
this.pushEvent("onUpdated", ...args);
|
||||
},
|
||||
onActivated(...args) {
|
||||
this.pushEvent("onActivated", ...args);
|
||||
},
|
||||
onRemoved(...args) {
|
||||
this.pushEvent("onRemoved", ...args);
|
||||
},
|
||||
async nextEvent() {
|
||||
if (this.events.length == 0) {
|
||||
return new Promise(resolve => (this.currentPromise = { resolve }));
|
||||
}
|
||||
return Promise.resolve(this.events[0]);
|
||||
},
|
||||
async checkEvent(expectedEvent, ...expectedArgs) {
|
||||
await this.nextEvent();
|
||||
let [actualEvent, ...actualArgs] = this.events.shift();
|
||||
browser.test.assertEq(expectedEvent, actualEvent);
|
||||
browser.test.assertEq(expectedArgs.length, actualArgs.length);
|
||||
|
||||
for (let i = 0; i < expectedArgs.length; i++) {
|
||||
browser.test.assertEq(typeof expectedArgs[i], typeof actualArgs[i]);
|
||||
if (typeof expectedArgs[i] == "object") {
|
||||
for (let key of Object.keys(expectedArgs[i])) {
|
||||
browser.test.assertEq(expectedArgs[i][key], actualArgs[i][key]);
|
||||
}
|
||||
} else {
|
||||
browser.test.assertEq(expectedArgs[i], actualArgs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return actualArgs;
|
||||
},
|
||||
async pageLoad(tab) {
|
||||
while (true) {
|
||||
// Read the first event without consuming it.
|
||||
let [
|
||||
actualEvent,
|
||||
actualTabId,
|
||||
actualInfo,
|
||||
actualTab,
|
||||
] = await this.nextEvent();
|
||||
browser.test.assertEq("onUpdated", actualEvent);
|
||||
browser.test.assertEq(tab, actualTabId);
|
||||
|
||||
if (
|
||||
actualInfo.status == "loading" ||
|
||||
actualTab.url == "about:blank"
|
||||
) {
|
||||
// We're not interested in these events. Take them off the list.
|
||||
browser.test.log("Skipping this event.");
|
||||
this.events.shift();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
await this.checkEvent(
|
||||
"onUpdated",
|
||||
tab,
|
||||
{ status: "complete" },
|
||||
{
|
||||
id: tab,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
browser.tabs.onCreated.addListener(listener.onCreated.bind(listener));
|
||||
browser.tabs.onUpdated.addListener(listener.onUpdated.bind(listener), {
|
||||
properties: ["status"],
|
||||
});
|
||||
browser.tabs.onActivated.addListener(listener.onActivated.bind(listener));
|
||||
browser.tabs.onRemoved.addListener(listener.onRemoved.bind(listener));
|
||||
|
||||
browser.test.log(
|
||||
"Collect the ID of the initial tab (there must be only one) and window."
|
||||
);
|
||||
|
||||
let initialTabs = await browser.tabs.query({});
|
||||
browser.test.assertEq(1, initialTabs.length);
|
||||
browser.test.assertEq(0, initialTabs[0].index);
|
||||
browser.test.assertTrue(initialTabs[0].mailTab);
|
||||
browser.test.assertEq("mail", initialTabs[0].type);
|
||||
let [{ id: initialTab, windowId: initialWindow }] = initialTabs;
|
||||
|
||||
browser.test.log("Add a first content tab and wait for it to load.");
|
||||
|
||||
await browser.tabs.create({ url: browser.runtime.getURL("page1.html") });
|
||||
let [{ id: contentTab1 }] = await listener.checkEvent("onCreated", {
|
||||
index: 1,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "content",
|
||||
});
|
||||
browser.test.assertTrue(contentTab1 != initialTab);
|
||||
await listener.pageLoad(contentTab1);
|
||||
browser.test.assertEq(
|
||||
"content",
|
||||
(await browser.tabs.get(contentTab1)).type
|
||||
);
|
||||
|
||||
browser.test.log("Add a second content tab and wait for it to load.");
|
||||
|
||||
await browser.tabs.create({ url: browser.runtime.getURL("page2.html") });
|
||||
let [{ id: contentTab2 }] = await listener.checkEvent("onCreated", {
|
||||
index: 2,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "content",
|
||||
});
|
||||
browser.test.assertTrue(![initialTab, contentTab1].includes(contentTab2));
|
||||
await listener.pageLoad(contentTab2);
|
||||
browser.test.assertEq(
|
||||
"content",
|
||||
(await browser.tabs.get(contentTab2)).type
|
||||
);
|
||||
|
||||
browser.test.log("Add the calendar tab.");
|
||||
|
||||
browser.test.sendMessage("openCalendarTab");
|
||||
let [{ id: calendarTab }] = await listener.checkEvent("onCreated", {
|
||||
index: 3,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "calendar",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![initialTab, contentTab1, contentTab2].includes(calendarTab)
|
||||
);
|
||||
|
||||
browser.test.log("Add the task tab.");
|
||||
|
||||
browser.test.sendMessage("openTaskTab");
|
||||
let [{ id: taskTab }] = await listener.checkEvent("onCreated", {
|
||||
index: 4,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "tasks",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![initialTab, contentTab1, contentTab2, calendarTab].includes(taskTab)
|
||||
);
|
||||
|
||||
browser.test.log("Open a folder in a tab.");
|
||||
|
||||
browser.test.sendMessage("openFolderTab");
|
||||
let [{ id: folderTab }] = await listener.checkEvent("onCreated", {
|
||||
index: 5,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: true,
|
||||
type: "mail",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![initialTab, contentTab1, contentTab2, calendarTab, taskTab].includes(
|
||||
folderTab
|
||||
)
|
||||
);
|
||||
|
||||
browser.test.log("Open a first message in a tab.");
|
||||
|
||||
browser.test.sendMessage("openMessageTab", false);
|
||||
|
||||
// In some circumstances this onUpdated event and the onCreated event
|
||||
// happen out of order. We're not interested in the onUpdated event
|
||||
// so just throw it away.
|
||||
let unwantedEvent = await listener.nextEvent();
|
||||
if (unwantedEvent[0] == "onUpdated") {
|
||||
listener.events.shift();
|
||||
}
|
||||
|
||||
let [{ id: messageTab1 }] = await listener.checkEvent("onCreated", {
|
||||
index: 6,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "messageDisplay",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![
|
||||
initialTab,
|
||||
contentTab1,
|
||||
contentTab2,
|
||||
calendarTab,
|
||||
taskTab,
|
||||
folderTab,
|
||||
].includes(messageTab1)
|
||||
);
|
||||
await listener.pageLoad(messageTab1);
|
||||
|
||||
browser.test.log(
|
||||
"Open a second message in a tab. In the background, just because."
|
||||
);
|
||||
|
||||
browser.test.sendMessage("openMessageTab", true);
|
||||
let [{ id: messageTab2 }] = await listener.checkEvent("onCreated", {
|
||||
index: 7,
|
||||
windowId: initialWindow,
|
||||
active: false,
|
||||
mailTab: false,
|
||||
type: "messageDisplay",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![
|
||||
initialTab,
|
||||
contentTab1,
|
||||
contentTab2,
|
||||
calendarTab,
|
||||
taskTab,
|
||||
folderTab,
|
||||
messageTab1,
|
||||
].includes(messageTab2)
|
||||
);
|
||||
|
||||
browser.test.log(
|
||||
"Activate each of the tabs in a somewhat random order to test the onActivated event."
|
||||
);
|
||||
|
||||
for (let tab of [
|
||||
initialTab,
|
||||
calendarTab,
|
||||
messageTab1,
|
||||
taskTab,
|
||||
contentTab1,
|
||||
messageTab2,
|
||||
folderTab,
|
||||
contentTab2,
|
||||
]) {
|
||||
await browser.tabs.update(tab, { active: true });
|
||||
if ([messageTab1, messageTab2].includes(tab)) {
|
||||
await listener.checkEvent(
|
||||
"onUpdated",
|
||||
tab,
|
||||
{ status: "loading" },
|
||||
{
|
||||
id: tab,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
await listener.checkEvent("onActivated", {
|
||||
tabId: tab,
|
||||
windowId: initialWindow,
|
||||
});
|
||||
if ([messageTab1, messageTab2].includes(tab)) {
|
||||
await listener.pageLoad(tab);
|
||||
}
|
||||
}
|
||||
|
||||
browser.test.log(
|
||||
"Remove the first content tab. This was not active so no new tab should be activated."
|
||||
);
|
||||
|
||||
await browser.tabs.remove(contentTab1);
|
||||
await listener.checkEvent("onRemoved", contentTab1, {
|
||||
windowId: initialWindow,
|
||||
isWindowClosing: false,
|
||||
});
|
||||
|
||||
browser.test.log(
|
||||
"Remove the second content tab. This was active, and the calendar tab is after it, so that should be activated."
|
||||
);
|
||||
|
||||
await browser.tabs.remove(contentTab2);
|
||||
await listener.checkEvent("onRemoved", contentTab2, {
|
||||
windowId: initialWindow,
|
||||
isWindowClosing: false,
|
||||
});
|
||||
await listener.checkEvent("onActivated", {
|
||||
tabId: calendarTab,
|
||||
windowId: initialWindow,
|
||||
});
|
||||
|
||||
browser.test.log("Remove the remaining tabs.");
|
||||
|
||||
for (let tab of [
|
||||
taskTab,
|
||||
messageTab1,
|
||||
messageTab2,
|
||||
folderTab,
|
||||
calendarTab,
|
||||
]) {
|
||||
await browser.tabs.remove(tab);
|
||||
await listener.checkEvent("onRemoved", tab, {
|
||||
windowId: initialWindow,
|
||||
isWindowClosing: false,
|
||||
});
|
||||
}
|
||||
|
||||
await listener.checkEvent("onActivated", {
|
||||
tabId: initialTab,
|
||||
windowId: initialWindow,
|
||||
});
|
||||
|
||||
browser.test.assertEq(0, listener.events.length);
|
||||
browser.test.notifyPass("finished");
|
||||
},
|
||||
files: {
|
||||
"page1.html": "<html><body>Page 1</body></html>",
|
||||
"page2.html": "<html><body>Page 2</body></html>",
|
||||
"background.js": async () => {
|
||||
// Executes a command, but first loads a second extension with terminated
|
||||
// background and waits for it to be restarted due to the executed command.
|
||||
async function capturePrimedEvent(eventName, callback) {
|
||||
let eventPageExtensionReadyPromise = window.waitForMessage();
|
||||
browser.test.sendMessage("capturePrimedEvent", eventName);
|
||||
await eventPageExtensionReadyPromise;
|
||||
let eventPageExtensionFinishedPromise = window.waitForMessage();
|
||||
callback();
|
||||
return eventPageExtensionFinishedPromise;
|
||||
}
|
||||
|
||||
let listener = {
|
||||
events: [],
|
||||
currentPromise: null,
|
||||
|
||||
pushEvent(...args) {
|
||||
browser.test.log(JSON.stringify(args));
|
||||
this.events.push(args);
|
||||
if (this.currentPromise) {
|
||||
let p = this.currentPromise;
|
||||
this.currentPromise = null;
|
||||
p.resolve(args);
|
||||
}
|
||||
},
|
||||
onCreated(...args) {
|
||||
this.pushEvent("onCreated", ...args);
|
||||
},
|
||||
onUpdated(...args) {
|
||||
this.pushEvent("onUpdated", ...args);
|
||||
},
|
||||
onActivated(...args) {
|
||||
this.pushEvent("onActivated", ...args);
|
||||
},
|
||||
onRemoved(...args) {
|
||||
this.pushEvent("onRemoved", ...args);
|
||||
},
|
||||
async nextEvent() {
|
||||
if (this.events.length == 0) {
|
||||
return new Promise(
|
||||
resolve => (this.currentPromise = { resolve })
|
||||
);
|
||||
}
|
||||
return Promise.resolve(this.events[0]);
|
||||
},
|
||||
async checkEvent(expectedEvent, ...expectedArgs) {
|
||||
await this.nextEvent();
|
||||
let [actualEvent, ...actualArgs] = this.events.shift();
|
||||
browser.test.assertEq(expectedEvent, actualEvent);
|
||||
browser.test.assertEq(expectedArgs.length, actualArgs.length);
|
||||
for (let i = 0; i < expectedArgs.length; i++) {
|
||||
browser.test.assertEq(
|
||||
typeof expectedArgs[i],
|
||||
typeof actualArgs[i]
|
||||
);
|
||||
if (typeof expectedArgs[i] == "object") {
|
||||
for (let key of Object.keys(expectedArgs[i])) {
|
||||
browser.test.assertEq(
|
||||
expectedArgs[i][key],
|
||||
actualArgs[i][key]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
browser.test.assertEq(expectedArgs[i], actualArgs[i]);
|
||||
}
|
||||
}
|
||||
return actualArgs;
|
||||
},
|
||||
async pageLoad(tab) {
|
||||
while (true) {
|
||||
// Read the first event without consuming it.
|
||||
let [
|
||||
actualEvent,
|
||||
actualTabId,
|
||||
actualInfo,
|
||||
actualTab,
|
||||
] = await this.nextEvent();
|
||||
browser.test.assertEq("onUpdated", actualEvent);
|
||||
browser.test.assertEq(tab, actualTabId);
|
||||
|
||||
if (
|
||||
actualInfo.status == "loading" ||
|
||||
actualTab.url == "about:blank"
|
||||
) {
|
||||
// We're not interested in these events. Take them off the list.
|
||||
browser.test.log("Skipping this event.");
|
||||
this.events.shift();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
await this.checkEvent(
|
||||
"onUpdated",
|
||||
tab,
|
||||
{ status: "complete" },
|
||||
{
|
||||
id: tab,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
browser.tabs.onCreated.addListener(listener.onCreated.bind(listener));
|
||||
browser.tabs.onUpdated.addListener(listener.onUpdated.bind(listener), {
|
||||
properties: ["status"],
|
||||
});
|
||||
browser.tabs.onActivated.addListener(
|
||||
listener.onActivated.bind(listener)
|
||||
);
|
||||
browser.tabs.onRemoved.addListener(listener.onRemoved.bind(listener));
|
||||
|
||||
browser.test.log(
|
||||
"Collect the ID of the initial tab (there must be only one) and window."
|
||||
);
|
||||
|
||||
let initialTabs = await browser.tabs.query({});
|
||||
browser.test.assertEq(1, initialTabs.length);
|
||||
browser.test.assertEq(0, initialTabs[0].index);
|
||||
browser.test.assertTrue(initialTabs[0].mailTab);
|
||||
browser.test.assertEq("mail", initialTabs[0].type);
|
||||
let [{ id: initialTab, windowId: initialWindow }] = initialTabs;
|
||||
|
||||
browser.test.log("Add a first content tab and wait for it to load.");
|
||||
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
index: 1,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "content",
|
||||
},
|
||||
],
|
||||
await capturePrimedEvent("onCreated", () =>
|
||||
browser.tabs.create({
|
||||
url: browser.runtime.getURL("page1.html"),
|
||||
})
|
||||
)
|
||||
);
|
||||
let [{ id: contentTab1 }] = await listener.checkEvent("onCreated", {
|
||||
index: 1,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "content",
|
||||
});
|
||||
browser.test.assertTrue(contentTab1 != initialTab);
|
||||
await listener.pageLoad(contentTab1);
|
||||
browser.test.assertEq(
|
||||
"content",
|
||||
(await browser.tabs.get(contentTab1)).type
|
||||
);
|
||||
|
||||
browser.test.log("Add a second content tab and wait for it to load.");
|
||||
|
||||
// The external extension is looking for the onUpdated event, it either be
|
||||
// a loading or completed event. Compare with whatever the local extension
|
||||
// is getting.
|
||||
let locContentTabUpdateInfoPromise = new Promise(resolve => {
|
||||
let listener = (...args) => {
|
||||
browser.tabs.onUpdated.removeListener(listener);
|
||||
resolve(args);
|
||||
};
|
||||
browser.tabs.onUpdated.addListener(listener, {
|
||||
properties: ["status"],
|
||||
});
|
||||
});
|
||||
let primedContentTabUpdateInfo = await capturePrimedEvent(
|
||||
"onUpdated",
|
||||
() =>
|
||||
browser.tabs.create({
|
||||
url: browser.runtime.getURL("page2.html"),
|
||||
})
|
||||
);
|
||||
let [{ id: contentTab2 }] = await listener.checkEvent("onCreated", {
|
||||
index: 2,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "content",
|
||||
});
|
||||
let locContentTabUpdateInfo = await locContentTabUpdateInfoPromise;
|
||||
window.assertDeepEqual(
|
||||
locContentTabUpdateInfo,
|
||||
primedContentTabUpdateInfo,
|
||||
"primed onUpdated event and non-primed onUpdeated event should receive the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
|
||||
browser.test.assertTrue(
|
||||
![initialTab, contentTab1].includes(contentTab2)
|
||||
);
|
||||
await listener.pageLoad(contentTab2);
|
||||
browser.test.assertEq(
|
||||
"content",
|
||||
(await browser.tabs.get(contentTab2)).type
|
||||
);
|
||||
|
||||
browser.test.log("Add the calendar tab.");
|
||||
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
index: 3,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "calendar",
|
||||
},
|
||||
],
|
||||
await capturePrimedEvent("onCreated", () =>
|
||||
browser.test.sendMessage("openCalendarTab")
|
||||
)
|
||||
);
|
||||
let [{ id: calendarTab }] = await listener.checkEvent("onCreated", {
|
||||
index: 3,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "calendar",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![initialTab, contentTab1, contentTab2].includes(calendarTab)
|
||||
);
|
||||
|
||||
browser.test.log("Add the task tab.");
|
||||
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
index: 4,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "tasks",
|
||||
},
|
||||
],
|
||||
await capturePrimedEvent("onCreated", () =>
|
||||
browser.test.sendMessage("openTaskTab")
|
||||
)
|
||||
);
|
||||
let [{ id: taskTab }] = await listener.checkEvent("onCreated", {
|
||||
index: 4,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "tasks",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![initialTab, contentTab1, contentTab2, calendarTab].includes(taskTab)
|
||||
);
|
||||
|
||||
browser.test.log("Open a folder in a tab.");
|
||||
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
index: 5,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: true,
|
||||
type: "mail",
|
||||
},
|
||||
],
|
||||
await capturePrimedEvent("onCreated", () =>
|
||||
browser.test.sendMessage("openFolderTab")
|
||||
)
|
||||
);
|
||||
let [{ id: folderTab }] = await listener.checkEvent("onCreated", {
|
||||
index: 5,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: true,
|
||||
type: "mail",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![
|
||||
initialTab,
|
||||
contentTab1,
|
||||
contentTab2,
|
||||
calendarTab,
|
||||
taskTab,
|
||||
].includes(folderTab)
|
||||
);
|
||||
|
||||
browser.test.log("Open a first message in a tab.");
|
||||
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
index: 6,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "messageDisplay",
|
||||
},
|
||||
],
|
||||
await capturePrimedEvent("onCreated", () =>
|
||||
browser.test.sendMessage("openMessageTab", false)
|
||||
)
|
||||
);
|
||||
|
||||
// In some circumstances this onUpdated event and the onCreated event
|
||||
// happen out of order. We're not interested in the onUpdated event
|
||||
// so just throw it away.
|
||||
let unwantedEvent = await listener.nextEvent();
|
||||
if (unwantedEvent[0] == "onUpdated") {
|
||||
listener.events.shift();
|
||||
}
|
||||
|
||||
let [{ id: messageTab1 }] = await listener.checkEvent("onCreated", {
|
||||
index: 6,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
type: "messageDisplay",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![
|
||||
initialTab,
|
||||
contentTab1,
|
||||
contentTab2,
|
||||
calendarTab,
|
||||
taskTab,
|
||||
folderTab,
|
||||
].includes(messageTab1)
|
||||
);
|
||||
await listener.pageLoad(messageTab1);
|
||||
|
||||
browser.test.log(
|
||||
"Open a second message in a tab. In the background, just because."
|
||||
);
|
||||
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
index: 7,
|
||||
windowId: initialWindow,
|
||||
active: false,
|
||||
mailTab: false,
|
||||
type: "messageDisplay",
|
||||
},
|
||||
],
|
||||
await capturePrimedEvent("onCreated", () =>
|
||||
browser.test.sendMessage("openMessageTab", true)
|
||||
)
|
||||
);
|
||||
let [{ id: messageTab2 }] = await listener.checkEvent("onCreated", {
|
||||
index: 7,
|
||||
windowId: initialWindow,
|
||||
active: false,
|
||||
mailTab: false,
|
||||
type: "messageDisplay",
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
![
|
||||
initialTab,
|
||||
contentTab1,
|
||||
contentTab2,
|
||||
calendarTab,
|
||||
taskTab,
|
||||
folderTab,
|
||||
messageTab1,
|
||||
].includes(messageTab2)
|
||||
);
|
||||
|
||||
browser.test.log(
|
||||
"Activate each of the tabs in a somewhat random order to test the onActivated event."
|
||||
);
|
||||
|
||||
for (let tab of [
|
||||
initialTab,
|
||||
calendarTab,
|
||||
messageTab1,
|
||||
taskTab,
|
||||
contentTab1,
|
||||
messageTab2,
|
||||
folderTab,
|
||||
contentTab2,
|
||||
]) {
|
||||
window.assertDeepEqual(
|
||||
[{ tabId: tab, windowId: initialWindow }],
|
||||
await capturePrimedEvent("onActivated", () =>
|
||||
browser.tabs.update(tab, { active: true })
|
||||
)
|
||||
);
|
||||
if ([messageTab1, messageTab2].includes(tab)) {
|
||||
await listener.checkEvent(
|
||||
"onUpdated",
|
||||
tab,
|
||||
{ status: "loading" },
|
||||
{
|
||||
id: tab,
|
||||
windowId: initialWindow,
|
||||
active: true,
|
||||
mailTab: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
await listener.checkEvent("onActivated", {
|
||||
tabId: tab,
|
||||
windowId: initialWindow,
|
||||
});
|
||||
if ([messageTab1, messageTab2].includes(tab)) {
|
||||
await listener.pageLoad(tab);
|
||||
}
|
||||
}
|
||||
|
||||
browser.test.log(
|
||||
"Remove the first content tab. This was not active so no new tab should be activated."
|
||||
);
|
||||
|
||||
window.assertDeepEqual(
|
||||
[contentTab1, { windowId: initialWindow, isWindowClosing: false }],
|
||||
await capturePrimedEvent("onRemoved", () =>
|
||||
browser.tabs.remove(contentTab1)
|
||||
)
|
||||
);
|
||||
await listener.checkEvent("onRemoved", contentTab1, {
|
||||
windowId: initialWindow,
|
||||
isWindowClosing: false,
|
||||
});
|
||||
|
||||
browser.test.log(
|
||||
"Remove the second content tab. This was active, and the calendar tab is after it, so that should be activated."
|
||||
);
|
||||
|
||||
window.assertDeepEqual(
|
||||
[contentTab2, { windowId: initialWindow, isWindowClosing: false }],
|
||||
await capturePrimedEvent("onRemoved", () =>
|
||||
browser.tabs.remove(contentTab2)
|
||||
)
|
||||
);
|
||||
await listener.checkEvent("onRemoved", contentTab2, {
|
||||
windowId: initialWindow,
|
||||
isWindowClosing: false,
|
||||
});
|
||||
await listener.checkEvent("onActivated", {
|
||||
tabId: calendarTab,
|
||||
windowId: initialWindow,
|
||||
});
|
||||
|
||||
browser.test.log("Remove the remaining tabs.");
|
||||
|
||||
for (let tab of [
|
||||
taskTab,
|
||||
messageTab1,
|
||||
messageTab2,
|
||||
folderTab,
|
||||
calendarTab,
|
||||
]) {
|
||||
window.assertDeepEqual(
|
||||
[tab, { windowId: initialWindow, isWindowClosing: false }],
|
||||
await capturePrimedEvent("onRemoved", () =>
|
||||
browser.tabs.remove(tab)
|
||||
)
|
||||
);
|
||||
await listener.checkEvent("onRemoved", tab, {
|
||||
windowId: initialWindow,
|
||||
isWindowClosing: false,
|
||||
});
|
||||
}
|
||||
|
||||
await listener.checkEvent("onActivated", {
|
||||
tabId: initialTab,
|
||||
windowId: initialWindow,
|
||||
});
|
||||
|
||||
browser.test.assertEq(0, listener.events.length);
|
||||
browser.test.notifyPass("finished");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
},
|
||||
manifest: {
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["tabs"],
|
||||
},
|
||||
});
|
||||
|
||||
extension.onMessage("openCalendarTab", async () => {
|
||||
// Function to start an event page extension (MV3), which can be called whenever
|
||||
// the main test is about to trigger an event. The extension terminates its
|
||||
// background and listens for that single event, verifying it is waking up correctly.
|
||||
async function event_page_extension(eventName, actionCallback) {
|
||||
let ext = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
let eventName = browser.runtime.getManifest().description;
|
||||
|
||||
if (["onCreated", "onActivated", "onRemoved"].includes(eventName)) {
|
||||
browser.tabs[eventName].addListener(async (...args) => {
|
||||
// Only send the first event after background wake-up, this should
|
||||
// be the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage(`${eventName} received`, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (eventName == "onUpdated") {
|
||||
browser.tabs.onUpdated.addListener(
|
||||
(...args) => {
|
||||
// Only send the first event after background wake-up, this should
|
||||
// be the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage("onUpdated received", args);
|
||||
}
|
||||
},
|
||||
{
|
||||
properties: ["status"],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
},
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
description: eventName,
|
||||
background: { scripts: ["background.js"] },
|
||||
permissions: ["tabs"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: `tabs.eventpage.${eventName}@mochi.test` },
|
||||
},
|
||||
},
|
||||
});
|
||||
await ext.startup();
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "tabs", eventName, { primed: false });
|
||||
|
||||
await ext.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listener.
|
||||
assertPersistentListeners(ext, "tabs", eventName, { primed: true });
|
||||
|
||||
await actionCallback();
|
||||
let rv = await ext.awaitMessage(`${eventName} received`);
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "tabs", eventName, { primed: false });
|
||||
|
||||
await ext.unload();
|
||||
return rv;
|
||||
}
|
||||
|
||||
extension.onMessage("openCalendarTab", () => {
|
||||
let calendarTabButton = document.getElementById("calendarButton");
|
||||
EventUtils.synthesizeMouseAtCenter(calendarTabButton, { clickCount: 1 });
|
||||
EventUtils.synthesizeMouseAtCenter(calendarTabButton, {
|
||||
clickCount: 1,
|
||||
});
|
||||
});
|
||||
|
||||
extension.onMessage("openTaskTab", async () => {
|
||||
let calendarTabButton = document.getElementById("tasksButton");
|
||||
EventUtils.synthesizeMouseAtCenter(calendarTabButton, { clickCount: 1 });
|
||||
extension.onMessage("openTaskTab", () => {
|
||||
let taskTabButton = document.getElementById("tasksButton");
|
||||
EventUtils.synthesizeMouseAtCenter(taskTabButton, { clickCount: 1 });
|
||||
});
|
||||
|
||||
extension.onMessage("openFolderTab", async () => {
|
||||
extension.onMessage("openFolderTab", () => {
|
||||
tabmail.openTab("folder", { folder: rootFolder, background: false });
|
||||
});
|
||||
|
||||
extension.onMessage("openMessageTab", async background => {
|
||||
extension.onMessage("openMessageTab", background => {
|
||||
let msgHdr = messages.shift();
|
||||
tabmail.openTab("message", { msgHdr, background });
|
||||
});
|
||||
|
||||
extension.onMessage("capturePrimedEvent", async eventName => {
|
||||
let primedEventData = await event_page_extension(eventName, () => {
|
||||
// Resume execution in the main test, after the event page extension is
|
||||
// ready to capture the event with deactivated background.
|
||||
extension.sendMessage();
|
||||
});
|
||||
extension.sendMessage(...primedEventData);
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
"use strict";
|
||||
|
||||
// This test checks whether browser.theme.onUpdated works
|
||||
// when a static theme is applied
|
||||
|
||||
const ACCENT_COLOR = "#a14040";
|
||||
const TEXT_COLOR = "#fac96e";
|
||||
const BACKGROUND =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0" +
|
||||
"DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
|
||||
|
||||
add_task(async function test_on_updated() {
|
||||
const theme = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
theme: {
|
||||
images: {
|
||||
theme_frame: "image1.png",
|
||||
},
|
||||
colors: {
|
||||
frame: ACCENT_COLOR,
|
||||
tab_background_text: TEXT_COLOR,
|
||||
},
|
||||
},
|
||||
},
|
||||
files: {
|
||||
"image1.png": BACKGROUND,
|
||||
},
|
||||
});
|
||||
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
background() {
|
||||
browser.theme.onUpdated.addListener(updateInfo => {
|
||||
browser.test.sendMessage("theme-updated", updateInfo);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
info("Testing update event on static theme startup");
|
||||
let updatedPromise = extension.awaitMessage("theme-updated");
|
||||
await theme.startup();
|
||||
const { theme: receivedTheme, windowId } = await updatedPromise;
|
||||
Assert.ok(!windowId, "No window id in static theme update event");
|
||||
Assert.ok(
|
||||
receivedTheme.images.theme_frame.includes("image1.png"),
|
||||
"Theme theme_frame image should be applied"
|
||||
);
|
||||
Assert.equal(
|
||||
receivedTheme.colors.frame,
|
||||
ACCENT_COLOR,
|
||||
"Theme frame color should be applied"
|
||||
);
|
||||
Assert.equal(
|
||||
receivedTheme.colors.tab_background_text,
|
||||
TEXT_COLOR,
|
||||
"Theme tab_background_text color should be applied"
|
||||
);
|
||||
|
||||
info("Testing update event on static theme unload");
|
||||
updatedPromise = extension.awaitMessage("theme-updated");
|
||||
await theme.unload();
|
||||
const updateInfo = await updatedPromise;
|
||||
Assert.ok(!windowId, "No window id in static theme update event on unload");
|
||||
Assert.equal(
|
||||
Object.keys(updateInfo.theme),
|
||||
0,
|
||||
"unloading theme sends empty theme in update event"
|
||||
);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_on_updated_eventpage() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["extensions.eventPages.enabled", true]],
|
||||
});
|
||||
const theme = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
theme: {
|
||||
images: {
|
||||
theme_frame: "image1.png",
|
||||
},
|
||||
colors: {
|
||||
frame: ACCENT_COLOR,
|
||||
tab_background_text: TEXT_COLOR,
|
||||
},
|
||||
},
|
||||
},
|
||||
files: {
|
||||
"image1.png": BACKGROUND,
|
||||
},
|
||||
});
|
||||
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": () => {
|
||||
// Whenever the extension starts or wakes up, the eventCounter is reset
|
||||
// and allows to observe the order of events fired. In case of a wake-up,
|
||||
// the first observed event is the one that woke up the background.
|
||||
let eventCounter = 0;
|
||||
|
||||
browser.theme.onUpdated.addListener(async updateInfo => {
|
||||
browser.test.sendMessage("theme-updated", {
|
||||
eventCount: ++eventCounter,
|
||||
...updateInfo,
|
||||
});
|
||||
});
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
},
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
browser_specific_settings: { gecko: { id: "themes@mochi.test" } },
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
assertPersistentListeners(extension, "theme", "onUpdated", {
|
||||
primed: false,
|
||||
});
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
assertPersistentListeners(extension, "theme", "onUpdated", {
|
||||
primed: true,
|
||||
});
|
||||
|
||||
info("Testing update event on static theme startup");
|
||||
|
||||
await theme.startup();
|
||||
|
||||
const {
|
||||
eventCount,
|
||||
theme: receivedTheme,
|
||||
windowId,
|
||||
} = await extension.awaitMessage("theme-updated");
|
||||
Assert.equal(eventCount, 1, "Event counter should be correct");
|
||||
Assert.ok(!windowId, "No window id in static theme update event");
|
||||
Assert.ok(
|
||||
receivedTheme.images.theme_frame.includes("image1.png"),
|
||||
"Theme theme_frame image should be applied"
|
||||
);
|
||||
|
||||
await theme.unload();
|
||||
await extension.awaitMessage("theme-updated");
|
||||
|
||||
await extension.unload();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
|
@ -13,6 +13,17 @@ add_task(async () => {
|
|||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": async () => {
|
||||
// Executes a command, but first loads a second extension with terminated
|
||||
// background and waits for it to be restarted due to the executed command.
|
||||
async function capturePrimedEvent(eventName, callback) {
|
||||
let eventPageExtensionReadyPromise = window.waitForMessage();
|
||||
browser.test.sendMessage("capturePrimedEvent", eventName);
|
||||
await eventPageExtensionReadyPromise;
|
||||
let eventPageExtensionFinishedPromise = window.waitForMessage();
|
||||
callback();
|
||||
return eventPageExtensionFinishedPromise;
|
||||
}
|
||||
|
||||
let listener = {
|
||||
tabEvents: [],
|
||||
windowEvents: [],
|
||||
|
@ -102,7 +113,7 @@ add_task(async () => {
|
|||
|
||||
browser.test.log("Open a new main window (messenger.xhtml).");
|
||||
|
||||
browser.test.sendMessage("openMainWindow");
|
||||
let primedMainWindowInfo = await window.sendMessage("openMainWindow");
|
||||
let [
|
||||
{ id: mainWindow },
|
||||
] = await listener.checkEvent("windows.onCreated", { type: "normal" });
|
||||
|
@ -112,10 +123,22 @@ add_task(async () => {
|
|||
active: true,
|
||||
mailTab: true,
|
||||
});
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
id: mainWindow,
|
||||
type: "normal",
|
||||
},
|
||||
],
|
||||
primedMainWindowInfo
|
||||
);
|
||||
|
||||
browser.test.log("Open a compose window (messengercompose.xhtml).");
|
||||
|
||||
await browser.compose.beginNew();
|
||||
let primedComposeWindowInfo = await capturePrimedEvent(
|
||||
"onCreated",
|
||||
() => browser.compose.beginNew()
|
||||
);
|
||||
let [{ id: composeWindow }] = await listener.checkEvent(
|
||||
"windows.onCreated",
|
||||
{
|
||||
|
@ -128,10 +151,21 @@ add_task(async () => {
|
|||
active: true,
|
||||
mailTab: false,
|
||||
});
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
id: composeWindow,
|
||||
type: "messageCompose",
|
||||
},
|
||||
],
|
||||
primedComposeWindowInfo
|
||||
);
|
||||
|
||||
browser.test.log("Open a message in a window (messageWindow.xhtml).");
|
||||
|
||||
await window.sendMessage("openDisplayWindow");
|
||||
let primedDisplayWindowInfo = await window.sendMessage(
|
||||
"openDisplayWindow"
|
||||
);
|
||||
let [{ id: displayWindow }] = await listener.checkEvent(
|
||||
"windows.onCreated",
|
||||
{
|
||||
|
@ -144,15 +178,26 @@ add_task(async () => {
|
|||
active: true,
|
||||
mailTab: false,
|
||||
});
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
id: displayWindow,
|
||||
type: "messageDisplay",
|
||||
},
|
||||
],
|
||||
primedDisplayWindowInfo
|
||||
);
|
||||
|
||||
browser.test.log("Open a page in a popup window.");
|
||||
await browser.windows.create({
|
||||
url: "test.html",
|
||||
type: "popup",
|
||||
width: 800,
|
||||
height: 500,
|
||||
});
|
||||
|
||||
let primedPopupWindowInfo = await capturePrimedEvent("onCreated", () =>
|
||||
browser.windows.create({
|
||||
url: "test.html",
|
||||
type: "popup",
|
||||
width: 800,
|
||||
height: 500,
|
||||
})
|
||||
);
|
||||
let [{ id: popupWindow }] = await listener.checkEvent(
|
||||
"windows.onCreated",
|
||||
{
|
||||
|
@ -167,45 +212,94 @@ add_task(async () => {
|
|||
active: true,
|
||||
mailTab: false,
|
||||
});
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
id: popupWindow,
|
||||
type: "popup",
|
||||
width: 800,
|
||||
height: 500,
|
||||
},
|
||||
],
|
||||
primedPopupWindowInfo
|
||||
);
|
||||
|
||||
browser.test.log("Pause to lets windows load properly.");
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise(resolve => setTimeout(resolve, 2500));
|
||||
|
||||
browser.test.log("Change focused window.");
|
||||
|
||||
let focusInfoPromise = new Promise(resolve => {
|
||||
let listener = windowId => {
|
||||
browser.windows.onFocusChanged.removeListener(listener);
|
||||
resolve(windowId);
|
||||
};
|
||||
browser.windows.onFocusChanged.addListener(listener);
|
||||
});
|
||||
let [primedFocusInfo] = await capturePrimedEvent("onFocusChanged", () =>
|
||||
browser.windows.update(composeWindow, { focused: true })
|
||||
);
|
||||
let focusInfo = await focusInfoPromise;
|
||||
let platformInfo = await browser.runtime.getPlatformInfo();
|
||||
|
||||
let expectedWindow = ["mac", "win"].includes(platformInfo.os)
|
||||
? composeWindow
|
||||
: browser.windows.WINDOW_ID_NONE;
|
||||
window.assertDeepEqual(expectedWindow, primedFocusInfo);
|
||||
window.assertDeepEqual(expectedWindow, focusInfo);
|
||||
|
||||
browser.test.log("Close the new main window.");
|
||||
|
||||
await browser.windows.remove(mainWindow);
|
||||
let primedMainWindowRemoveInfo = await capturePrimedEvent(
|
||||
"onRemoved",
|
||||
() => browser.windows.remove(mainWindow)
|
||||
);
|
||||
await listener.checkEvent("windows.onRemoved", mainWindow);
|
||||
await listener.checkEvent("tabs.onRemoved", mainTab, {
|
||||
windowId: mainWindow,
|
||||
isWindowClosing: true,
|
||||
});
|
||||
window.assertDeepEqual([mainWindow], primedMainWindowRemoveInfo);
|
||||
|
||||
browser.test.log("Close the compose window.");
|
||||
|
||||
await browser.windows.remove(composeWindow);
|
||||
let primedComposWindowRemoveInfo = await capturePrimedEvent(
|
||||
"onRemoved",
|
||||
() => browser.windows.remove(composeWindow)
|
||||
);
|
||||
await listener.checkEvent("windows.onRemoved", composeWindow);
|
||||
await listener.checkEvent("tabs.onRemoved", composeTab, {
|
||||
windowId: composeWindow,
|
||||
isWindowClosing: true,
|
||||
});
|
||||
window.assertDeepEqual([composeWindow], primedComposWindowRemoveInfo);
|
||||
|
||||
browser.test.log("Close the message window.");
|
||||
|
||||
await browser.windows.remove(displayWindow);
|
||||
let primedDisplayWindowRemoveInfo = await capturePrimedEvent(
|
||||
"onRemoved",
|
||||
() => browser.windows.remove(displayWindow)
|
||||
);
|
||||
await listener.checkEvent("windows.onRemoved", displayWindow);
|
||||
await listener.checkEvent("tabs.onRemoved", displayTab, {
|
||||
windowId: displayWindow,
|
||||
isWindowClosing: true,
|
||||
});
|
||||
window.assertDeepEqual([displayWindow], primedDisplayWindowRemoveInfo);
|
||||
|
||||
browser.test.log("Close the popup window.");
|
||||
await browser.windows.remove(popupWindow);
|
||||
|
||||
let primedPopupWindowRemoveInfo = await capturePrimedEvent(
|
||||
"onRemoved",
|
||||
() => browser.windows.remove(popupWindow)
|
||||
);
|
||||
await listener.checkEvent("windows.onRemoved", popupWindow);
|
||||
await listener.checkEvent("tabs.onRemoved", popupTab, {
|
||||
windowId: popupWindow,
|
||||
isWindowClosing: true,
|
||||
});
|
||||
window.assertDeepEqual([popupWindow], primedPopupWindowRemoveInfo);
|
||||
|
||||
let finalWindows = await browser.windows.getAll({ populate: true });
|
||||
browser.test.assertEq(1, finalWindows.length);
|
||||
|
@ -225,15 +319,86 @@ add_task(async () => {
|
|||
},
|
||||
});
|
||||
|
||||
// Function to start an event page extension (MV3), which can be called whenever
|
||||
// the main test is about to trigger an event. The extension terminates its
|
||||
// background and listens for that single event, verifying it is waking up correctly.
|
||||
async function event_page_extension(eventName, actionCallback) {
|
||||
let ext = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
let eventName = browser.runtime.getManifest().description;
|
||||
|
||||
if (
|
||||
["onCreated", "onFocusChanged", "onRemoved"].includes(eventName)
|
||||
) {
|
||||
browser.windows[eventName].addListener(async (...args) => {
|
||||
// Only send the first event after background wake-up, this should
|
||||
// be the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage(`${eventName} received`, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
},
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
description: eventName,
|
||||
background: { scripts: ["background.js"] },
|
||||
browser_specific_settings: {
|
||||
gecko: { id: `windows.eventpage.${eventName}@mochi.test` },
|
||||
},
|
||||
},
|
||||
});
|
||||
await ext.startup();
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "windows", eventName, { primed: false });
|
||||
|
||||
await ext.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listener.
|
||||
assertPersistentListeners(ext, "windows", eventName, { primed: true });
|
||||
|
||||
await actionCallback();
|
||||
let rv = await ext.awaitMessage(`${eventName} received`);
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "windows", eventName, { primed: false });
|
||||
|
||||
await ext.unload();
|
||||
return rv;
|
||||
}
|
||||
|
||||
extension.onMessage("openMainWindow", async () => {
|
||||
let primedEventData = await event_page_extension("onCreated", () => {
|
||||
return window.MsgOpenNewWindowForFolder(testFolder.URI);
|
||||
});
|
||||
extension.sendMessage(...primedEventData);
|
||||
});
|
||||
|
||||
extension.onMessage("openDisplayWindow", async () => {
|
||||
let primedEventData = await event_page_extension("onCreated", () => {
|
||||
return openMessageInWindow([...testFolder.messages][0]);
|
||||
});
|
||||
extension.sendMessage(...primedEventData);
|
||||
});
|
||||
|
||||
extension.onMessage("capturePrimedEvent", async eventName => {
|
||||
let primedEventData = await event_page_extension(eventName, () => {
|
||||
// Resume execution of the main test, after the event page extension has
|
||||
// primed its event listeners.
|
||||
extension.sendMessage();
|
||||
});
|
||||
extension.sendMessage(...primedEventData);
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
await extension.awaitMessage("openMainWindow");
|
||||
window.MsgOpenNewWindowForFolder(testFolder.URI);
|
||||
|
||||
await extension.awaitMessage("openDisplayWindow");
|
||||
await openMessageInWindow([...testFolder.messages][0]);
|
||||
extension.sendMessage("continue");
|
||||
|
||||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
});
|
||||
|
|
|
@ -16,6 +16,9 @@ var { ExtensionCommon } = ChromeUtils.import(
|
|||
);
|
||||
var { makeWidgetId } = ExtensionCommon;
|
||||
|
||||
// Persistent Listener test functionality
|
||||
var { assertPersistentListeners } = ExtensionTestUtils.testAssertions;
|
||||
|
||||
// There are shutdown issues for which multiple rejections are left uncaught.
|
||||
// This bug should be fixed, but for the moment this directory is whitelisted.
|
||||
//
|
||||
|
@ -125,8 +128,20 @@ function createAccount(type = "none") {
|
|||
}
|
||||
|
||||
function cleanUpAccount(account) {
|
||||
info(`Cleaning up account ${account.toString()}`);
|
||||
let serverKey = account.incomingServer.key;
|
||||
let serverType = account.incomingServer.type;
|
||||
info(
|
||||
`Cleaning up ${serverType} account ${account.key} and server ${serverKey}`
|
||||
);
|
||||
MailServices.accounts.removeAccount(account, true);
|
||||
|
||||
try {
|
||||
let server = MailServices.accounts.getIncomingServer(serverKey);
|
||||
if (server) {
|
||||
info(`Cleaning up leftover ${serverType} server ${serverKey}`);
|
||||
MailServices.accounts.removeIncomingServer(server, false);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function addIdentity(account, email = "mochitest@localhost") {
|
||||
|
@ -752,6 +767,9 @@ async function run_popup_test(configData) {
|
|||
configData.apiName = configData.actionType.replace(/_([a-z])/g, function(g) {
|
||||
return g[1].toUpperCase();
|
||||
});
|
||||
configData.moduleName =
|
||||
configData.actionType == "action" ? "browserAction" : configData.apiName;
|
||||
|
||||
let backend_script = configData.backend_script;
|
||||
|
||||
let extensionDetails = {
|
||||
|
@ -795,7 +813,8 @@ async function run_popup_test(configData) {
|
|||
},
|
||||
},
|
||||
manifest: {
|
||||
applications: {
|
||||
manifest_version: configData.manifest_version || 2,
|
||||
browser_specific_settings: {
|
||||
gecko: {
|
||||
id: `${configData.actionType}@mochi.test`,
|
||||
},
|
||||
|
@ -816,7 +835,7 @@ async function run_popup_test(configData) {
|
|||
await new Promise(resolve => win.setTimeout(resolve));
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
let buttonId = `${configData.actionType}_mochi_test-${configData.apiName}-toolbarbutton`;
|
||||
let buttonId = `${configData.actionType}_mochi_test-${configData.moduleName}-toolbarbutton`;
|
||||
let toolbarId;
|
||||
switch (configData.actionType) {
|
||||
case "compose_action":
|
||||
|
@ -825,6 +844,7 @@ async function run_popup_test(configData) {
|
|||
toolbarId = "FormatToolbar";
|
||||
}
|
||||
break;
|
||||
case "action":
|
||||
case "browser_action":
|
||||
toolbarId = "mail-bar3";
|
||||
if (configData.default_area == "tabstoolbar") {
|
||||
|
@ -874,6 +894,38 @@ async function run_popup_test(configData) {
|
|||
let label = button.querySelector(".toolbarbutton-text");
|
||||
is(label.value, "This is a test", "Correct label");
|
||||
|
||||
if (
|
||||
!configData.use_default_popup &&
|
||||
configData?.manifest_version == 3
|
||||
) {
|
||||
assertPersistentListeners(
|
||||
extension,
|
||||
configData.moduleName,
|
||||
"onClicked",
|
||||
{
|
||||
primed: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
if (configData.terminateBackground) {
|
||||
await extension.terminateBackground({
|
||||
disableResetIdleForTest: true,
|
||||
});
|
||||
if (
|
||||
!configData.use_default_popup &&
|
||||
configData?.manifest_version == 3
|
||||
) {
|
||||
assertPersistentListeners(
|
||||
extension,
|
||||
configData.moduleName,
|
||||
"onClicked",
|
||||
{
|
||||
primed: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let clickedPromise;
|
||||
if (!configData.disable_button) {
|
||||
clickedPromise = extension.awaitMessage("actionButtonClicked");
|
||||
|
@ -883,13 +935,49 @@ async function run_popup_test(configData) {
|
|||
// We're testing that nothing happens. Give it time to potentially happen.
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise(resolve => win.setTimeout(resolve, 500));
|
||||
// In case the background was terminated, it should not restart.
|
||||
// If it does, we will get an extra "ready" message and fail.
|
||||
// Listeners should still be primed.
|
||||
if (
|
||||
configData.terminateBackground &&
|
||||
configData?.manifest_version == 3
|
||||
) {
|
||||
assertPersistentListeners(
|
||||
extension,
|
||||
configData.moduleName,
|
||||
"onClicked",
|
||||
{
|
||||
primed: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await clickedPromise;
|
||||
let hasFiredBefore = await clickedPromise;
|
||||
await promiseAnimationFrame(win);
|
||||
await new Promise(resolve => win.setTimeout(resolve));
|
||||
is(win.document.getElementById(buttonId), button);
|
||||
label = button.querySelector(".toolbarbutton-text");
|
||||
is(label.value, "New title", "Correct label");
|
||||
|
||||
if (configData.terminateBackground) {
|
||||
// The onClicked event should have restarted the background script.
|
||||
await extension.awaitMessage("ready");
|
||||
// Could be undefined, but it must not be true
|
||||
is(false, !!hasFiredBefore);
|
||||
}
|
||||
if (
|
||||
!configData.use_default_popup &&
|
||||
configData?.manifest_version == 3
|
||||
) {
|
||||
assertPersistentListeners(
|
||||
extension,
|
||||
configData.moduleName,
|
||||
"onClicked",
|
||||
{
|
||||
primed: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the open state of the action button.
|
||||
|
@ -903,6 +991,7 @@ async function run_popup_test(configData) {
|
|||
await new Promise(resolve => win.setTimeout(resolve));
|
||||
|
||||
ok(!win.document.getElementById(buttonId), "Button destroyed");
|
||||
|
||||
ok(
|
||||
!Services.xulStore
|
||||
.getValue(win.location.href, toolbarId, "currentset")
|
||||
|
@ -936,6 +1025,7 @@ async function run_popup_test(configData) {
|
|||
} else {
|
||||
// Without popup.
|
||||
extensionDetails.files["background.js"] = async function() {
|
||||
let hasFiredBefore = false;
|
||||
browser.test.log("nopopup background script ran");
|
||||
browser[window.apiName].onClicked.addListener(async (tab, info) => {
|
||||
browser.test.assertEq("object", typeof tab);
|
||||
|
@ -946,7 +1036,8 @@ async function run_popup_test(configData) {
|
|||
browser.test.log(`Tab ID is ${tab.id}`);
|
||||
await browser[window.apiName].setTitle({ title: "New title" });
|
||||
await new Promise(resolve => window.setTimeout(resolve));
|
||||
browser.test.sendMessage("actionButtonClicked");
|
||||
browser.test.sendMessage("actionButtonClicked", hasFiredBefore);
|
||||
hasFiredBefore = true;
|
||||
});
|
||||
browser.test.sendMessage("ready");
|
||||
};
|
||||
|
@ -957,7 +1048,7 @@ async function run_popup_test(configData) {
|
|||
extensionDetails.manifest.permissions = ["menus"];
|
||||
backend_script = async function(extension, configData) {
|
||||
let win = configData.window;
|
||||
let buttonId = `${configData.actionType}_mochi_test-${configData.apiName}-toolbarbutton`;
|
||||
let buttonId = `${configData.actionType}_mochi_test-${configData.moduleName}-toolbarbutton`;
|
||||
let menuId = "toolbar-context-menu";
|
||||
if (
|
||||
configData.actionType == "compose_action" &&
|
||||
|
|
|
@ -4,36 +4,75 @@
|
|||
|
||||
// Functions for extensions to use, so that we avoid repeating ourselves.
|
||||
|
||||
function assertDeepEqual(expected, actual) {
|
||||
function assertDeepEqual(
|
||||
expected,
|
||||
actual,
|
||||
description = "Values should be equal",
|
||||
options = {}
|
||||
) {
|
||||
let ok;
|
||||
let strict = !!options?.strict;
|
||||
try {
|
||||
ok = assertDeepEqualNested(expected, actual, strict);
|
||||
} catch (e) {
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) {
|
||||
browser.test.fail(
|
||||
`Deep equal test. \n Expected value: ${JSON.stringify(
|
||||
expected
|
||||
)} \n Actual value: ${JSON.stringify(actual)},
|
||||
${description}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertDeepEqualNested(expected, actual, strict) {
|
||||
if (expected === null) {
|
||||
browser.test.assertTrue(actual === null);
|
||||
return;
|
||||
return actual === null;
|
||||
}
|
||||
|
||||
if (["boolean", "number", "string"].includes(typeof expected)) {
|
||||
browser.test.assertEq(typeof expected, typeof actual);
|
||||
browser.test.assertEq(expected, actual);
|
||||
return;
|
||||
return typeof expected == typeof actual && expected == actual;
|
||||
}
|
||||
|
||||
if (Array.isArray(expected)) {
|
||||
browser.test.assertTrue(Array.isArray(actual));
|
||||
browser.test.assertEq(expected.length, actual.length);
|
||||
let ok = 0;
|
||||
let all = 0;
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
assertDeepEqual(expected[i], actual[i]);
|
||||
all++;
|
||||
if (assertDeepEqualNested(expected[i], actual[i], strict)) {
|
||||
ok++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
return (
|
||||
Array.isArray(actual) && expected.length == actual.length && all == ok
|
||||
);
|
||||
}
|
||||
|
||||
let expectedKeys = Object.keys(expected);
|
||||
let actualKeys = Object.keys(actual);
|
||||
// Ignore any extra keys on the actual object.
|
||||
browser.test.assertTrue(expectedKeys.length <= actualKeys.length);
|
||||
// Ignore any extra keys on the actual object in non-strict mode (default).
|
||||
let lengthOk = strict
|
||||
? expectedKeys.length == actualKeys.length
|
||||
: expectedKeys.length <= actualKeys.length;
|
||||
browser.test.assertTrue(lengthOk);
|
||||
|
||||
let ok = 0;
|
||||
let all = 0;
|
||||
for (let key of expectedKeys) {
|
||||
all++;
|
||||
browser.test.assertTrue(actualKeys.includes(key), `Key ${key} exists`);
|
||||
assertDeepEqual(expected[key], actual[key]);
|
||||
if (assertDeepEqualNested(expected[key], actual[key], strict)) {
|
||||
ok++;
|
||||
}
|
||||
}
|
||||
return all == ok && lengthOk;
|
||||
}
|
||||
|
||||
function waitForMessage() {
|
||||
|
|
|
@ -21,6 +21,9 @@ var { PromiseTestUtils } = ChromeUtils.import(
|
|||
"resource://testing-common/mailnews/PromiseTestUtils.jsm"
|
||||
);
|
||||
|
||||
// Persistent Listener test functionality
|
||||
var { assertPersistentListeners } = ExtensionTestUtils.testAssertions;
|
||||
|
||||
ExtensionTestUtils.init(this);
|
||||
|
||||
var IS_IMAP = false;
|
||||
|
@ -76,8 +79,20 @@ function createAccount(type = "none") {
|
|||
}
|
||||
|
||||
function cleanUpAccount(account) {
|
||||
info(`Cleaning up account ${account.toString()}`);
|
||||
let serverKey = account.incomingServer.key;
|
||||
let serverType = account.incomingServer.type;
|
||||
info(
|
||||
`Cleaning up ${serverType} account ${account.key} and server ${serverKey}`
|
||||
);
|
||||
MailServices.accounts.removeAccount(account, true);
|
||||
|
||||
try {
|
||||
let server = MailServices.accounts.getIncomingServer(serverKey);
|
||||
if (server) {
|
||||
info(`Cleaning up leftover ${serverType} server ${serverKey}`);
|
||||
MailServices.accounts.removeIncomingServer(server, false);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
|
|
|
@ -859,7 +859,7 @@ add_task(async function test_accounts_events() {
|
|||
identity: "user@invalidImap",
|
||||
});
|
||||
let localAccountKey = await window.sendMessage("createAccount", {
|
||||
type: "local",
|
||||
type: "none",
|
||||
identity: "user@invalidLocal",
|
||||
});
|
||||
let popAccountKey = await window.sendMessage("createAccount", {
|
||||
|
@ -869,9 +869,9 @@ add_task(async function test_accounts_events() {
|
|||
|
||||
// Update account identities.
|
||||
let accounts = await browser.accounts.list();
|
||||
let imapAccount = accounts.find(a => a.type == "imap");
|
||||
let localAccount = accounts.find(a => a.type == "none");
|
||||
let popAccount = accounts.find(a => a.type == "pop3");
|
||||
let imapAccount = accounts.find(a => a.id == imapAccountKey);
|
||||
let localAccount = accounts.find(a => a.id == localAccountKey);
|
||||
let popAccount = accounts.find(a => a.id == popAccountKey);
|
||||
|
||||
let id1 = await browser.identities.create(imapAccount.id, {
|
||||
composeHtml: true,
|
||||
|
@ -954,7 +954,7 @@ add_task(async function test_accounts_events() {
|
|||
id: "account8",
|
||||
type: "none",
|
||||
identities: [],
|
||||
name: "Local Folders",
|
||||
name: "account8user on localhost",
|
||||
folders: null,
|
||||
},
|
||||
},
|
||||
|
@ -1077,7 +1077,7 @@ add_task(async function test_accounts_events() {
|
|||
});
|
||||
extension.onMessage("removeAccount", details => {
|
||||
let account = MailServices.accounts.getAccount(details.accountKey);
|
||||
MailServices.accounts.removeAccount(account, true);
|
||||
cleanUpAccount(account);
|
||||
extension.sendMessage();
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var { ExtensionTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/ExtensionXPCShellUtils.jsm"
|
||||
);
|
||||
|
||||
var { AddonTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/AddonTestUtils.jsm"
|
||||
);
|
||||
|
||||
ExtensionTestUtils.mockAppInfo();
|
||||
AddonTestUtils.maybeInit(this);
|
||||
|
||||
add_task(async function test_accounts_MV3_event_pages() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, the eventCounter is reset and
|
||||
// allows to observe the order of events fired. In case of a wake-up, the
|
||||
// first observed event is the one that woke up the background.
|
||||
let eventCounter = 0;
|
||||
|
||||
for (let eventName of ["onCreated", "onUpdated", "onDeleted"]) {
|
||||
browser.accounts[eventName].addListener(async (...args) => {
|
||||
browser.test.sendMessage(`${eventName} event received`, {
|
||||
eventCount: ++eventCounter,
|
||||
args,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "accountsIdentities"],
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = [
|
||||
"accounts.onCreated",
|
||||
"accounts.onUpdated",
|
||||
"accounts.onDeleted",
|
||||
];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let testData = [
|
||||
{
|
||||
type: "imap",
|
||||
identity: "user@invalidImap",
|
||||
expectedUpdate: true,
|
||||
expectedName: accountKey => `Mail for ${accountKey}user@localhost`,
|
||||
expectedType: "imap",
|
||||
updatedName: "Test1",
|
||||
},
|
||||
{
|
||||
type: "pop3",
|
||||
identity: "user@invalidPop",
|
||||
expectedUpdate: false,
|
||||
expectedName: accountKey => `${accountKey}user on localhost`,
|
||||
expectedType: "pop3",
|
||||
updatedName: "Test2",
|
||||
},
|
||||
{
|
||||
type: "none",
|
||||
identity: "user@invalidLocal",
|
||||
expectedUpdate: false,
|
||||
expectedName: accountKey => `${accountKey}user on localhost`,
|
||||
expectedType: "none",
|
||||
updatedName: "Test3",
|
||||
},
|
||||
{
|
||||
type: "local",
|
||||
identity: "user@invalidLocal",
|
||||
expectedUpdate: false,
|
||||
expectedName: accountKey => "Local Folders",
|
||||
expectedType: "none",
|
||||
updatedName: "Test4",
|
||||
},
|
||||
];
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
|
||||
// Verify persistent listener, not yet primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// Create.
|
||||
|
||||
for (let details of testData) {
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
let account = createAccount(details.type);
|
||||
details.account = account;
|
||||
|
||||
{
|
||||
let rv = await extension.awaitMessage("onCreated event received");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
eventCount: 1,
|
||||
args: [
|
||||
details.account.key,
|
||||
{
|
||||
id: details.account.key,
|
||||
name: details.expectedName(account.key),
|
||||
type: details.expectedType,
|
||||
folders: null,
|
||||
identities: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
rv,
|
||||
"The primed onCreated event should return the correct values"
|
||||
);
|
||||
}
|
||||
|
||||
if (details.expectedUpdate) {
|
||||
let rv = await extension.awaitMessage("onUpdated event received");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
eventCount: 2,
|
||||
args: [
|
||||
details.account.key,
|
||||
{ id: details.account.key, name: "Mail for user@localhost" },
|
||||
],
|
||||
},
|
||||
rv,
|
||||
"The non-primed onUpdated event should return the correct values"
|
||||
);
|
||||
}
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listener should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
}
|
||||
|
||||
// Update.
|
||||
|
||||
for (let details of testData) {
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
let account = MailServices.accounts.getAccount(details.account.key);
|
||||
account.incomingServer.prettyName = details.updatedName;
|
||||
let rv = await extension.awaitMessage("onUpdated event received");
|
||||
|
||||
Assert.deepEqual(
|
||||
{
|
||||
eventCount: 1,
|
||||
args: [
|
||||
details.account.key,
|
||||
{
|
||||
id: details.account.key,
|
||||
name: details.updatedName,
|
||||
},
|
||||
],
|
||||
},
|
||||
rv,
|
||||
"The primed onUpdated event should return the correct values"
|
||||
);
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listener should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
}
|
||||
|
||||
// Delete.
|
||||
|
||||
for (let details of testData) {
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
cleanUpAccount(details.account);
|
||||
let rv = await extension.awaitMessage("onDeleted event received");
|
||||
|
||||
Assert.deepEqual(
|
||||
{
|
||||
eventCount: 1,
|
||||
args: [details.account.key],
|
||||
},
|
||||
rv,
|
||||
"The primed onDeleted event should return the correct values"
|
||||
);
|
||||
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listener should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
|
@ -17,8 +17,22 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
AddrBookUtils: "resource:///modules/AddrBookUtils.jsm",
|
||||
});
|
||||
|
||||
add_task(async function setup() {
|
||||
var { AddonTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/AddonTestUtils.jsm"
|
||||
);
|
||||
|
||||
ExtensionTestUtils.mockAppInfo();
|
||||
AddonTestUtils.maybeInit(this);
|
||||
|
||||
add_setup(async () => {
|
||||
Services.prefs.setIntPref("ldap_2.servers.osx.dirType", -1);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
// Make sure any open database is given a chance to close.
|
||||
Services.startup.advanceShutdownPhase(
|
||||
Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_addressBooks() {
|
||||
|
@ -999,6 +1013,428 @@ add_task(async function test_addressBooks() {
|
|||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_addressBooks_MV3_event_pages() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
// Create and register event listener.
|
||||
for (let event of [
|
||||
"addressBooks.onCreated",
|
||||
"addressBooks.onUpdated",
|
||||
"addressBooks.onDeleted",
|
||||
"contacts.onCreated",
|
||||
"contacts.onUpdated",
|
||||
"contacts.onDeleted",
|
||||
"mailingLists.onCreated",
|
||||
"mailingLists.onUpdated",
|
||||
"mailingLists.onDeleted",
|
||||
"mailingLists.onMemberAdded",
|
||||
"mailingLists.onMemberRemoved",
|
||||
]) {
|
||||
let [apiName, eventName] = event.split(".");
|
||||
browser[apiName][eventName].addListener((...args) => {
|
||||
// Only send the first event after background wake-up, this should be
|
||||
// the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage(`${apiName}.${eventName} received`, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["addressBooks"],
|
||||
browser_specific_settings: { gecko: { id: "addressbook@xpcshell.test" } },
|
||||
},
|
||||
});
|
||||
|
||||
let parent = MailServices.ab.getDirectory("jsaddrbook://abook.sqlite");
|
||||
function findContact(id) {
|
||||
for (let child of parent.childCards) {
|
||||
if (child.UID == id) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function findMailingList(id) {
|
||||
for (let list of parent.childNodes) {
|
||||
if (list.UID == id) {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function outsideEvent(action, ...args) {
|
||||
switch (action) {
|
||||
case "createAddressBook": {
|
||||
let dirPrefId = MailServices.ab.newAddressBook(
|
||||
"external add",
|
||||
"",
|
||||
Ci.nsIAbManager.JS_DIRECTORY_TYPE
|
||||
);
|
||||
let book = MailServices.ab.getDirectoryFromId(dirPrefId);
|
||||
return [book, dirPrefId];
|
||||
}
|
||||
case "updateAddressBook": {
|
||||
let book = MailServices.ab.getDirectoryFromId(args[0]);
|
||||
book.dirName = "external edit";
|
||||
return [];
|
||||
}
|
||||
case "deleteAddressBook": {
|
||||
let book = MailServices.ab.getDirectoryFromId(args[0]);
|
||||
MailServices.ab.deleteAddressBook(book.URI);
|
||||
return [];
|
||||
}
|
||||
case "createContact": {
|
||||
let contact = new AddrBookCard();
|
||||
contact.firstName = "external";
|
||||
contact.lastName = "add";
|
||||
contact.primaryEmail = "test@invalid";
|
||||
|
||||
let newContact = parent.addCard(contact);
|
||||
return [parent.UID, newContact.UID];
|
||||
}
|
||||
case "updateContact": {
|
||||
let contact = findContact(args[0]);
|
||||
if (contact) {
|
||||
contact.firstName = "external";
|
||||
contact.lastName = "edit";
|
||||
parent.modifyCard(contact);
|
||||
return [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "deleteContact": {
|
||||
let contact = findContact(args[0]);
|
||||
if (contact) {
|
||||
parent.deleteCards([contact]);
|
||||
return [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "createMailingList": {
|
||||
let list = Cc[
|
||||
"@mozilla.org/addressbook/directoryproperty;1"
|
||||
].createInstance(Ci.nsIAbDirectory);
|
||||
list.isMailList = true;
|
||||
list.dirName = "external add";
|
||||
|
||||
let newList = parent.addMailList(list);
|
||||
return [parent.UID, newList.UID];
|
||||
}
|
||||
case "updateMailingList": {
|
||||
let list = findMailingList(args[0]);
|
||||
if (list) {
|
||||
list.dirName = "external edit";
|
||||
list.editMailListToDatabase(null);
|
||||
return [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "deleteMailingList": {
|
||||
let list = findMailingList(args[0]);
|
||||
if (list) {
|
||||
parent.deleteDirectory(list);
|
||||
return [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "addMailingListMember": {
|
||||
let list = findMailingList(args[0]);
|
||||
let contact = findContact(args[1]);
|
||||
|
||||
if (list && contact) {
|
||||
list.addCard(contact);
|
||||
equal(1, list.childCards.length);
|
||||
return [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "removeMailingListMember": {
|
||||
let list = findMailingList(args[0]);
|
||||
let contact = findContact(args[1]);
|
||||
|
||||
if (list && contact) {
|
||||
list.deleteCards([contact]);
|
||||
equal(0, list.childCards.length);
|
||||
ok(findContact(args[1]), "Contact was not removed");
|
||||
return [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`Message "${action}" passed to handler didn't do anything.`
|
||||
);
|
||||
}
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = [
|
||||
"addressBook.onAddressBookCreated",
|
||||
"addressBook.onAddressBookUpdated",
|
||||
"addressBook.onAddressBookDeleted",
|
||||
"addressBook.onContactCreated",
|
||||
"addressBook.onContactUpdated",
|
||||
"addressBook.onContactDeleted",
|
||||
"addressBook.onMailingListCreated",
|
||||
"addressBook.onMailingListUpdated",
|
||||
"addressBook.onMailingListDeleted",
|
||||
"addressBook.onMemberAdded",
|
||||
"addressBook.onMemberRemoved",
|
||||
];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// addressBooks.onCreated.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
let [newBook, dirPrefId] = outsideEvent("createAddressBook");
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
id: newBook.UID,
|
||||
type: "addressBook",
|
||||
name: "external add",
|
||||
readOnly: false,
|
||||
remote: false,
|
||||
},
|
||||
],
|
||||
await extension.awaitMessage("addressBooks.onCreated received"),
|
||||
"The primed addressBooks.onCreated event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// addressBooks.onUpdated.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
outsideEvent("updateAddressBook", dirPrefId);
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
id: newBook.UID,
|
||||
type: "addressBook",
|
||||
name: "external edit",
|
||||
readOnly: false,
|
||||
remote: false,
|
||||
},
|
||||
],
|
||||
await extension.awaitMessage("addressBooks.onUpdated received"),
|
||||
"The primed addressBooks.onUpdated event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// addressBooks.onDeleted.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
outsideEvent("deleteAddressBook", dirPrefId);
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
Assert.deepEqual(
|
||||
[newBook.UID],
|
||||
await extension.awaitMessage("addressBooks.onDeleted received"),
|
||||
"The primed addressBooks.onDeleted event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// contacts.onCreated.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
let [parentId1, contactId] = outsideEvent("createContact");
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
let [createdNode] = await extension.awaitMessage(
|
||||
"contacts.onCreated received"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
{
|
||||
type: "contact",
|
||||
parentId: parentId1,
|
||||
id: contactId,
|
||||
},
|
||||
{
|
||||
type: createdNode.type,
|
||||
parentId: createdNode.parentId,
|
||||
id: createdNode.id,
|
||||
},
|
||||
"The primed contacts.onCreated event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// contacts.onUpdated.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
outsideEvent("updateContact", contactId);
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
let [updatedNode, changedProperties] = await extension.awaitMessage(
|
||||
"contacts.onUpdated received"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{ type: "contact", parentId: parentId1, id: contactId },
|
||||
{ LastName: { oldValue: "add", newValue: "edit" } },
|
||||
],
|
||||
[
|
||||
{
|
||||
type: updatedNode.type,
|
||||
parentId: updatedNode.parentId,
|
||||
id: updatedNode.id,
|
||||
},
|
||||
changedProperties,
|
||||
],
|
||||
"The primed contacts.onUpdated event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// mailingLists.onCreated.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
let [parentId2, listId] = outsideEvent("createMailingList");
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
type: "mailingList",
|
||||
parentId: parentId2,
|
||||
id: listId,
|
||||
name: "external add",
|
||||
nickName: "",
|
||||
description: "",
|
||||
readOnly: false,
|
||||
remote: false,
|
||||
},
|
||||
],
|
||||
await extension.awaitMessage("mailingLists.onCreated received"),
|
||||
"The primed mailingLists.onCreated event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// mailingList.onUpdated.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
outsideEvent("updateMailingList", listId);
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
type: "mailingList",
|
||||
parentId: parentId2,
|
||||
id: listId,
|
||||
name: "external edit",
|
||||
nickName: "",
|
||||
description: "",
|
||||
readOnly: false,
|
||||
remote: false,
|
||||
},
|
||||
],
|
||||
await extension.awaitMessage("mailingLists.onUpdated received"),
|
||||
"The primed mailingLists.onUpdated event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// mailingList.onMemberAdded.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
outsideEvent("addMailingListMember", listId, contactId);
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
let [addedNode] = await extension.awaitMessage(
|
||||
"mailingLists.onMemberAdded received"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
{ type: "contact", parentId: listId, id: contactId },
|
||||
{ type: addedNode.type, parentId: addedNode.parentId, id: addedNode.id },
|
||||
"The primed mailingLists.onMemberAdded event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// mailingList.onMemberRemoved.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
outsideEvent("removeMailingListMember", listId, contactId);
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
Assert.deepEqual(
|
||||
[listId, contactId],
|
||||
await extension.awaitMessage("mailingLists.onMemberRemoved received"),
|
||||
"The primed mailingLists.onMemberRemoved event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// mailingList.onDeleted.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
outsideEvent("deleteMailingList", listId);
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
Assert.deepEqual(
|
||||
[parentId2, listId],
|
||||
await extension.awaitMessage("mailingLists.onDeleted received"),
|
||||
"The primed mailingLists.onDeleted event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
// contacts.onDeleted.
|
||||
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
checkPersistentListeners({ primed: true });
|
||||
outsideEvent("deleteContact", contactId);
|
||||
// The event should have restarted the background.
|
||||
await extension.awaitMessage("background started");
|
||||
Assert.deepEqual(
|
||||
[parentId1, contactId],
|
||||
await extension.awaitMessage("contacts.onDeleted received"),
|
||||
"The primed contacts.onDeleted event should return the correct values"
|
||||
);
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
await extension.unload();
|
||||
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
||||
|
||||
add_task(async function test_photos() {
|
||||
async function background() {
|
||||
let events = [];
|
||||
|
@ -1605,10 +2041,3 @@ add_task(async function test_photos() {
|
|||
await extension.awaitFinish("addressBooksPhotos");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
// Make sure any open database is given a chance to close.
|
||||
Services.startup.advanceShutdownPhase(
|
||||
Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
|
||||
);
|
||||
});
|
||||
|
|
|
@ -11,8 +11,16 @@ var { LDAPServer } = ChromeUtils.import(
|
|||
"resource://testing-common/LDAPServer.jsm"
|
||||
);
|
||||
|
||||
add_task(async function setup() {
|
||||
add_setup(async () => {
|
||||
Services.prefs.setIntPref("ldap_2.servers.osx.dirType", -1);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
LDAPServer.close();
|
||||
// Make sure any open database is given a chance to close.
|
||||
Services.startup.advanceShutdownPhase(
|
||||
Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_quickSearch() {
|
||||
|
@ -145,10 +153,6 @@ add_task(async function test_quickSearch_types() {
|
|||
Ci.nsIAbManager.LDAP_DIRECTORY_TYPE
|
||||
);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
LDAPServer.close();
|
||||
});
|
||||
|
||||
async function background() {
|
||||
function checkCards(cards, expectedNames) {
|
||||
browser.test.assertEq(expectedNames.length, cards.length);
|
||||
|
@ -232,11 +236,3 @@ add_task(async function test_quickSearch_types() {
|
|||
await extension.awaitFinish("addressBooks");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
LDAPServer.close();
|
||||
// Make sure any open database is given a chance to close.
|
||||
Services.startup.advanceShutdownPhase(
|
||||
Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
|
||||
);
|
||||
});
|
||||
|
|
|
@ -8,9 +8,16 @@ var { ExtensionTestUtils } = ChromeUtils.import(
|
|||
"resource://testing-common/ExtensionXPCShellUtils.jsm"
|
||||
);
|
||||
|
||||
add_task(async function setup() {
|
||||
add_setup(async () => {
|
||||
Services.prefs.setIntPref("ldap_2.servers.osx.dirType", -1);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
// Make sure any open database is given a chance to close.
|
||||
Services.startup.advanceShutdownPhase(
|
||||
Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
|
||||
);
|
||||
});
|
||||
|
||||
let historyAB = MailServices.ab.getDirectory("jsaddrbook://history.sqlite");
|
||||
|
||||
let contact1 = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
|
||||
|
@ -139,10 +146,3 @@ add_task(async function test_addressBooks_readonly() {
|
|||
await extension.awaitFinish("addressBooks");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
// Make sure any open database is given a chance to close.
|
||||
Services.startup.advanceShutdownPhase(
|
||||
Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
|
||||
);
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ var { LDAPServer } = ChromeUtils.import(
|
|||
"resource://testing-common/LDAPServer.jsm"
|
||||
);
|
||||
|
||||
add_task(async function setup() {
|
||||
add_setup(async () => {
|
||||
// If nsIAbLDAPDirectory doesn't exist in our build options, someone has
|
||||
// specified --disable-ldap.
|
||||
if (!("nsIAbLDAPDirectory" in Ci)) {
|
||||
|
@ -28,8 +28,12 @@ add_task(async function setup() {
|
|||
Ci.nsIAbManager.LDAP_DIRECTORY_TYPE
|
||||
);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
registerCleanupFunction(() => {
|
||||
LDAPServer.close();
|
||||
// Make sure any open database is given a chance to close.
|
||||
Services.startup.advanceShutdownPhase(
|
||||
Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -95,10 +99,3 @@ add_task(async function test_addressBooks_remote() {
|
|||
await extension.awaitFinish("addressBooks");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
// Make sure any open database is given a chance to close.
|
||||
Services.startup.advanceShutdownPhase(
|
||||
Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,443 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var { ExtensionTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/ExtensionXPCShellUtils.jsm"
|
||||
);
|
||||
|
||||
var { AddonTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/AddonTestUtils.jsm"
|
||||
);
|
||||
|
||||
ExtensionTestUtils.mockAppInfo();
|
||||
AddonTestUtils.maybeInit(this);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
// Remove the temporary MozillaMailnews folder, which is not deleted in time when
|
||||
// the cleanupFunction registered by AddonTestUtils.maybeInit() checks for left over
|
||||
// files in the temp folder.
|
||||
// Note: PathUtils.tempDir points to the system temp folder, which is different.
|
||||
let path = PathUtils.join(
|
||||
Services.dirsvc.get("TmpD", Ci.nsIFile).path,
|
||||
"MozillaMailnews"
|
||||
);
|
||||
await IOUtils.remove(path, { recursive: true });
|
||||
});
|
||||
|
||||
// Test events and persistent events for Manifest V3 for onCreated, onRenamed,
|
||||
// onMoved, onCopied and onDeleted.
|
||||
add_task(
|
||||
{
|
||||
skip_if: () => IS_NNTP,
|
||||
},
|
||||
async function test_folders_MV3_event_pages() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
addIdentity(account, "id1@invalid");
|
||||
|
||||
let files = {
|
||||
"background.js": () => {
|
||||
for (let eventName of [
|
||||
"onCreated",
|
||||
"onDeleted",
|
||||
"onCopied",
|
||||
"onRenamed",
|
||||
"onMoved",
|
||||
"onFolderInfoChanged",
|
||||
]) {
|
||||
browser.folders[eventName].addListener(async (...args) => {
|
||||
browser.test.log(`${eventName} received: ${JSON.stringify(args)}`);
|
||||
browser.test.sendMessage(`${eventName} received`, args);
|
||||
});
|
||||
}
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead"],
|
||||
},
|
||||
});
|
||||
|
||||
// Function to start an event page extension (MV3), which can be called whenever
|
||||
// the main test is about to trigger an event. The extension terminates its
|
||||
// background and listens for that single event, verifying it is waking up correctly.
|
||||
async function event_page_extension(eventName, actionCallback) {
|
||||
let ext = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
let _eventName = browser.runtime.getManifest().description;
|
||||
|
||||
browser.folders[_eventName].addListener(async (...args) => {
|
||||
// Only send the first event after background wake-up, this should
|
||||
// be the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage(`${_eventName} received`, args);
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
},
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
description: eventName,
|
||||
background: { scripts: ["background.js"] },
|
||||
permissions: ["accountsRead"],
|
||||
},
|
||||
});
|
||||
await ext.startup();
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "folders", eventName, { primed: false });
|
||||
|
||||
await ext.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listener.
|
||||
assertPersistentListeners(ext, "folders", eventName, { primed: true });
|
||||
|
||||
await actionCallback();
|
||||
let rv = await ext.awaitMessage(`${eventName} received`);
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "folders", eventName, { primed: false });
|
||||
|
||||
await ext.unload();
|
||||
return rv;
|
||||
}
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("background started");
|
||||
|
||||
// Create a test folder before terminating the background script, to make sure
|
||||
// everything is sane.
|
||||
|
||||
rootFolder.createSubfolder("TestFolder", null);
|
||||
await extension.awaitMessage("onCreated received");
|
||||
if (IS_IMAP) {
|
||||
// IMAP creates a default Trash folder on the fly.
|
||||
await extension.awaitMessage("onCreated received");
|
||||
}
|
||||
|
||||
// Create SubFolder1.
|
||||
|
||||
{
|
||||
rootFolder.createSubfolder("SubFolder1", null);
|
||||
let createData = await extension.awaitMessage("onCreated received");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder1",
|
||||
path: "/SubFolder1",
|
||||
},
|
||||
],
|
||||
createData,
|
||||
"The onCreated event should return the correct values"
|
||||
);
|
||||
|
||||
// Collect all onFolderInfoChanged events. Order is not fixed.
|
||||
let changeEvents = [];
|
||||
let expectedChanges = [];
|
||||
changeEvents.push(
|
||||
await extension.awaitMessage("onFolderInfoChanged received")
|
||||
);
|
||||
changeEvents.push(
|
||||
await extension.awaitMessage("onFolderInfoChanged received")
|
||||
);
|
||||
expectedChanges.push([
|
||||
{ accountId: "account1", name: "TestFolder", path: "/TestFolder" },
|
||||
{ totalMessageCount: 0, unreadMessageCount: 0 },
|
||||
]);
|
||||
expectedChanges.push([
|
||||
{ accountId: "account1", name: "SubFolder1", path: "/SubFolder1" },
|
||||
{ totalMessageCount: 0, unreadMessageCount: 0 },
|
||||
]);
|
||||
if (IS_IMAP) {
|
||||
changeEvents.push(
|
||||
await extension.awaitMessage("onFolderInfoChanged received")
|
||||
);
|
||||
changeEvents.push(
|
||||
await extension.awaitMessage("onFolderInfoChanged received")
|
||||
);
|
||||
expectedChanges.push([
|
||||
{
|
||||
accountId: "account1",
|
||||
name: "Trash",
|
||||
path: "/Trash",
|
||||
type: "trash",
|
||||
},
|
||||
{ totalMessageCount: 0, unreadMessageCount: 0 },
|
||||
]);
|
||||
expectedChanges.push([
|
||||
{ accountId: "account1", name: "Junk", path: "", type: "junk" },
|
||||
{ totalMessageCount: 0, unreadMessageCount: 0 },
|
||||
]);
|
||||
}
|
||||
Assert.deepEqual(
|
||||
changeEvents.sort((a, b) => a[0].name > b[0].name),
|
||||
expectedChanges.sort((a, b) => a[0].name > b[0].name),
|
||||
"The onFolderInfoChanged event should return the correct values"
|
||||
);
|
||||
}
|
||||
|
||||
// Create SubFolder2 (used for primed onFolderInfoChanged).
|
||||
|
||||
{
|
||||
let primedChangeData = await event_page_extension(
|
||||
"onFolderInfoChanged",
|
||||
() => {
|
||||
rootFolder.createSubfolder("SubFolder2", null);
|
||||
}
|
||||
);
|
||||
let changeData = await extension.awaitMessage(
|
||||
"onFolderInfoChanged received"
|
||||
);
|
||||
let createData = await extension.awaitMessage("onCreated received");
|
||||
Assert.deepEqual(
|
||||
changeData,
|
||||
primedChangeData,
|
||||
"The primed onFolderInfoChanged event should return the correct values"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder2",
|
||||
path: "/SubFolder2",
|
||||
},
|
||||
],
|
||||
createData,
|
||||
"The onCreated event should return the correct values"
|
||||
);
|
||||
}
|
||||
|
||||
// Rename.
|
||||
|
||||
{
|
||||
let primedRenameData = await event_page_extension("onRenamed", () => {
|
||||
rootFolder.getChildNamed("SubFolder2").rename("SubFolder3", null);
|
||||
});
|
||||
let renameData = await extension.awaitMessage("onRenamed received");
|
||||
Assert.deepEqual(
|
||||
primedRenameData,
|
||||
renameData,
|
||||
"The primed onRenamed event should return the correct values"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder2",
|
||||
path: "/SubFolder2",
|
||||
},
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder3",
|
||||
},
|
||||
],
|
||||
renameData,
|
||||
"The onRenamed event should return the correct values"
|
||||
);
|
||||
|
||||
if (IS_IMAP) {
|
||||
// IMAP fires an additional delete and create event.
|
||||
let deleteData = await extension.awaitMessage("onDeleted received");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder2",
|
||||
path: "/SubFolder2",
|
||||
},
|
||||
],
|
||||
deleteData,
|
||||
"The onDeleted event should return the correct MailFolder values."
|
||||
);
|
||||
let createData = await extension.awaitMessage("onCreated received");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "/SubFolder3", // FIXME: There should be no leading slash, no
|
||||
path: "//SubFolder3", // other test tested this so far.
|
||||
},
|
||||
],
|
||||
createData,
|
||||
"The onCreated event should return the correct MailFolder values."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy.
|
||||
|
||||
{
|
||||
let primedCopyData = await event_page_extension("onCopied", () => {
|
||||
MailServices.copy.copyFolder(
|
||||
rootFolder.getChildNamed("SubFolder3"),
|
||||
rootFolder.getChildNamed("SubFolder1"),
|
||||
false,
|
||||
null,
|
||||
null
|
||||
);
|
||||
});
|
||||
let copyData = await extension.awaitMessage("onCopied received");
|
||||
Assert.deepEqual(
|
||||
primedCopyData,
|
||||
copyData,
|
||||
"The primed onCopied event should return the correct values"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder3",
|
||||
},
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder1/SubFolder3",
|
||||
},
|
||||
],
|
||||
copyData,
|
||||
"The onCopied event should return the correct values"
|
||||
);
|
||||
|
||||
if (IS_IMAP) {
|
||||
// IMAP fires an additional create event.
|
||||
let createData = await extension.awaitMessage("onCreated received");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder1/SubFolder3",
|
||||
},
|
||||
],
|
||||
createData,
|
||||
"The onCreated event should return the correct MailFolder values."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Move.
|
||||
|
||||
{
|
||||
let primedMoveData = await event_page_extension("onMoved", () => {
|
||||
MailServices.copy.copyFolder(
|
||||
rootFolder.getChildNamed("SubFolder1").getChildNamed("SubFolder3"),
|
||||
rootFolder.getChildNamed("SubFolder3"),
|
||||
true,
|
||||
null,
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
let moveData = await extension.awaitMessage("onMoved received");
|
||||
Assert.deepEqual(
|
||||
primedMoveData,
|
||||
moveData,
|
||||
"The primed onMoved event should return the correct values"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder1/SubFolder3",
|
||||
},
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder3/SubFolder3",
|
||||
},
|
||||
],
|
||||
moveData,
|
||||
"The onMoved event should return the correct values"
|
||||
);
|
||||
|
||||
if (IS_IMAP) {
|
||||
// IMAP fires additional rename and delete events.
|
||||
let renameData = await extension.awaitMessage("onRenamed received");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder1/SubFolder3",
|
||||
},
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder3/SubFolder3",
|
||||
},
|
||||
],
|
||||
renameData,
|
||||
"The onRenamed event should return the correct MailFolder values."
|
||||
);
|
||||
let deleteData = await extension.awaitMessage("onDeleted received");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder1/SubFolder3",
|
||||
},
|
||||
],
|
||||
deleteData,
|
||||
"The onDeleted event should return the correct MailFolder values."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete.
|
||||
|
||||
{
|
||||
let primedDeleteData = await event_page_extension("onDeleted", () => {
|
||||
let subFolder1 = rootFolder.getChildNamed("SubFolder3");
|
||||
subFolder1.propagateDelete(
|
||||
subFolder1.getChildNamed("SubFolder3"),
|
||||
true,
|
||||
null
|
||||
);
|
||||
});
|
||||
let deleteData = await extension.awaitMessage("onDeleted received");
|
||||
Assert.deepEqual(
|
||||
primedDeleteData,
|
||||
deleteData,
|
||||
"The primed onDeleted event should return the correct values"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
[
|
||||
{
|
||||
accountId: account.key,
|
||||
name: "SubFolder3",
|
||||
path: "/SubFolder3/SubFolder3",
|
||||
},
|
||||
],
|
||||
deleteData,
|
||||
"The onDeleted event should return the correct values"
|
||||
);
|
||||
}
|
||||
await extension.awaitMessage("onFolderInfoChanged received");
|
||||
|
||||
await extension.unload();
|
||||
cleanUpAccount(account);
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
}
|
||||
);
|
|
@ -0,0 +1,146 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var { ExtensionTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/ExtensionXPCShellUtils.jsm"
|
||||
);
|
||||
|
||||
var { AddonTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/AddonTestUtils.jsm"
|
||||
);
|
||||
|
||||
ExtensionTestUtils.mockAppInfo();
|
||||
AddonTestUtils.maybeInit(this);
|
||||
|
||||
add_task(async function test_identities_MV3_event_pages() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let account1 = createAccount();
|
||||
addIdentity(account1, "id1@invalid");
|
||||
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
|
||||
for (let eventName of ["onCreated", "onUpdated", "onDeleted"]) {
|
||||
browser.identities[eventName].addListener((...args) => {
|
||||
// Only send the first event after background wake-up, this should be the
|
||||
// only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage(`${eventName} received`, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
files,
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "accountsIdentities"],
|
||||
browser_specific_settings: { gecko: { id: "identities@xpcshell.test" } },
|
||||
},
|
||||
});
|
||||
|
||||
function checkPersistentListeners({ primed }) {
|
||||
// A persistent event is referenced by its moduleName as defined in
|
||||
// ext-mails.json, not by its actual namespace.
|
||||
const persistent_events = [
|
||||
"identities.onCreated",
|
||||
"identities.onUpdated",
|
||||
"identities.onDeleted",
|
||||
];
|
||||
|
||||
for (let event of persistent_events) {
|
||||
let [moduleName, eventName] = event.split(".");
|
||||
assertPersistentListeners(extension, moduleName, eventName, {
|
||||
primed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await extension.startup();
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// Verify persistent listener, not yet primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Create.
|
||||
|
||||
let id2 = addIdentity(account1, "id2@invalid");
|
||||
let createData = await extension.awaitMessage("onCreated received");
|
||||
Assert.deepEqual(
|
||||
[
|
||||
"id2",
|
||||
{
|
||||
accountId: "account1",
|
||||
id: "id2",
|
||||
label: "",
|
||||
name: "",
|
||||
email: "id2@invalid",
|
||||
replyTo: "",
|
||||
organization: "",
|
||||
composeHtml: true,
|
||||
signature: "",
|
||||
signatureIsPlainText: true,
|
||||
},
|
||||
],
|
||||
createData,
|
||||
"The primed onCreated event should return the correct values"
|
||||
);
|
||||
|
||||
await extension.awaitMessage("background started");
|
||||
// Verify persistent listener, not yet primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Update
|
||||
|
||||
id2.fullName = "Updated Name";
|
||||
let updateData = await extension.awaitMessage("onUpdated received");
|
||||
Assert.deepEqual(
|
||||
["id2", { name: "Updated Name", accountId: "account1", id: "id2" }],
|
||||
updateData,
|
||||
"The primed onUpdated event should return the correct values"
|
||||
);
|
||||
await extension.awaitMessage("background started");
|
||||
// Verify persistent listener, not yet primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
await extension.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listeners.
|
||||
checkPersistentListeners({ primed: true });
|
||||
|
||||
// Delete
|
||||
|
||||
account1.removeIdentity(id2);
|
||||
let deleteData = await extension.awaitMessage("onDeleted received");
|
||||
Assert.deepEqual(
|
||||
["id2"],
|
||||
deleteData,
|
||||
"The primed onDeleted event should return the correct values"
|
||||
);
|
||||
// The background should have been restarted.
|
||||
await extension.awaitMessage("background started");
|
||||
// The listener should no longer be primed.
|
||||
checkPersistentListeners({ primed: false });
|
||||
|
||||
await extension.unload();
|
||||
cleanUpAccount(account1);
|
||||
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
|
@ -13,6 +13,76 @@ var { TestUtils } = ChromeUtils.importESModule(
|
|||
var { ExtensionsUI } = ChromeUtils.import(
|
||||
"resource:///modules/ExtensionsUI.jsm"
|
||||
);
|
||||
var { AddonTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/AddonTestUtils.jsm"
|
||||
);
|
||||
|
||||
ExtensionTestUtils.mockAppInfo();
|
||||
AddonTestUtils.maybeInit(this);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
// Remove the temporary MozillaMailnews folder, which is not deleted in time when
|
||||
// the cleanupFunction registered by AddonTestUtils.maybeInit() checks for left over
|
||||
// files in the temp folder.
|
||||
// Note: PathUtils.tempDir points to the system temp folder, which is different.
|
||||
let path = PathUtils.join(
|
||||
Services.dirsvc.get("TmpD", Ci.nsIFile).path,
|
||||
"MozillaMailnews"
|
||||
);
|
||||
await IOUtils.remove(path, { recursive: true });
|
||||
});
|
||||
|
||||
// Function to start an event page extension (MV3), which can be called whenever
|
||||
// the main test is about to trigger an event. The extension terminates its
|
||||
// background and listens for that single event, verifying it is waking up correctly.
|
||||
async function event_page_extension(eventName, actionCallback) {
|
||||
let ext = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
let _eventName = browser.runtime.getManifest().description;
|
||||
|
||||
browser.messages[_eventName].addListener(async (...args) => {
|
||||
// Only send the first event after background wake-up, this should
|
||||
// be the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage(`${_eventName} received`, args);
|
||||
}
|
||||
});
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
},
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
description: eventName,
|
||||
background: { scripts: ["background.js"] },
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "event_page_extension@mochi.test" },
|
||||
},
|
||||
permissions: ["accountsRead", "messagesRead", "messagesMove"],
|
||||
},
|
||||
});
|
||||
await ext.startup();
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "messages", eventName, { primed: false });
|
||||
|
||||
await ext.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listener.
|
||||
assertPersistentListeners(ext, "messages", eventName, { primed: true });
|
||||
|
||||
await actionCallback();
|
||||
let rv = await ext.awaitMessage(`${eventName} received`);
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "messages", eventName, { primed: false });
|
||||
|
||||
await ext.unload();
|
||||
return rv;
|
||||
}
|
||||
|
||||
let account, rootFolder, subFolders;
|
||||
add_task(
|
||||
|
@ -136,8 +206,19 @@ add_task(
|
|||
skip_if: () => IS_NNTP,
|
||||
},
|
||||
async function test_update() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
async function capturePrimedEvent(eventName, callback) {
|
||||
let eventPageExtensionReadyPromise = window.waitForMessage();
|
||||
browser.test.sendMessage("capturePrimedEvent", eventName);
|
||||
await eventPageExtensionReadyPromise;
|
||||
let eventPageExtensionFinishedPromise = window.waitForMessage();
|
||||
callback();
|
||||
return eventPageExtensionFinishedPromise;
|
||||
}
|
||||
|
||||
function newUpdatePromise(numberOfEventsToCollapse = 1) {
|
||||
return new Promise(resolve => {
|
||||
let seenEvents = {};
|
||||
|
@ -178,32 +259,64 @@ add_task(
|
|||
|
||||
// Test that setting flagged works.
|
||||
let updatePromise = newUpdatePromise();
|
||||
await browser.messages.update(message.id, { flagged: true });
|
||||
let primedUpdatedInfo = await capturePrimedEvent("onUpdated", () =>
|
||||
browser.messages.update(message.id, { flagged: true })
|
||||
);
|
||||
let updateInfo = await updatePromise;
|
||||
window.assertDeepEqual(
|
||||
[updateInfo.msg, updateInfo.props],
|
||||
primedUpdatedInfo,
|
||||
"The primed and non-primed onUpdated events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
browser.test.assertEq(message.id, updateInfo.msg.id);
|
||||
window.assertDeepEqual({ flagged: true }, updateInfo.props);
|
||||
await window.sendMessage("flagged");
|
||||
|
||||
// Test that setting read works.
|
||||
updatePromise = newUpdatePromise();
|
||||
await browser.messages.update(message.id, { read: true });
|
||||
primedUpdatedInfo = await capturePrimedEvent("onUpdated", () =>
|
||||
browser.messages.update(message.id, { read: true })
|
||||
);
|
||||
updateInfo = await updatePromise;
|
||||
window.assertDeepEqual(
|
||||
[updateInfo.msg, updateInfo.props],
|
||||
primedUpdatedInfo,
|
||||
"The primed and non-primed onUpdated events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
browser.test.assertEq(message.id, updateInfo.msg.id);
|
||||
window.assertDeepEqual({ read: true }, updateInfo.props);
|
||||
await window.sendMessage("read");
|
||||
|
||||
// Test that setting junk works.
|
||||
updatePromise = newUpdatePromise();
|
||||
await browser.messages.update(message.id, { junk: true });
|
||||
primedUpdatedInfo = await capturePrimedEvent("onUpdated", () =>
|
||||
browser.messages.update(message.id, { junk: true })
|
||||
);
|
||||
updateInfo = await updatePromise;
|
||||
window.assertDeepEqual(
|
||||
[updateInfo.msg, updateInfo.props],
|
||||
primedUpdatedInfo,
|
||||
"The primed and non-primed onUpdated events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
browser.test.assertEq(message.id, updateInfo.msg.id);
|
||||
window.assertDeepEqual({ junk: true }, updateInfo.props);
|
||||
await window.sendMessage("junk");
|
||||
|
||||
// Test that setting one tag works.
|
||||
updatePromise = newUpdatePromise();
|
||||
await browser.messages.update(message.id, { tags: [tags[0].key] });
|
||||
primedUpdatedInfo = await capturePrimedEvent("onUpdated", () =>
|
||||
browser.messages.update(message.id, { tags: [tags[0].key] })
|
||||
);
|
||||
updateInfo = await updatePromise;
|
||||
window.assertDeepEqual(
|
||||
[updateInfo.msg, updateInfo.props],
|
||||
primedUpdatedInfo,
|
||||
"The primed and non-primed onUpdated events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
browser.test.assertEq(message.id, updateInfo.msg.id);
|
||||
window.assertDeepEqual({ tags: [tags[0].key] }, updateInfo.props);
|
||||
await window.sendMessage("tags1");
|
||||
|
@ -288,6 +401,9 @@ add_task(
|
|||
manifest: {
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
permissions: ["accountsRead", "messagesRead"],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "messages.update@mochi.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -296,91 +412,110 @@ add_task(
|
|||
ok(!message.isRead);
|
||||
equal(message.getStringProperty("keywords"), "testkeyword");
|
||||
|
||||
extension.onMessage("capturePrimedEvent", async eventName => {
|
||||
let primedEventData = await event_page_extension(eventName, () => {
|
||||
// Resume execution in the main test, after the event page extension is
|
||||
// ready to capture the event with deactivated background.
|
||||
extension.sendMessage();
|
||||
});
|
||||
extension.sendMessage(...primedEventData);
|
||||
});
|
||||
|
||||
extension.onMessage("flagged", async () => {
|
||||
await TestUtils.waitForCondition(() => message.isFlagged);
|
||||
extension.sendMessage();
|
||||
});
|
||||
|
||||
extension.onMessage("read", async () => {
|
||||
await TestUtils.waitForCondition(() => message.isRead);
|
||||
extension.sendMessage();
|
||||
});
|
||||
|
||||
extension.onMessage("junk", async () => {
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("junkscore") == 100
|
||||
);
|
||||
extension.sendMessage();
|
||||
});
|
||||
|
||||
extension.onMessage("tags1", async () => {
|
||||
if (IS_IMAP) {
|
||||
// Only IMAP sets the junk/nonjunk keyword.
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") == "testkeyword junk $label1"
|
||||
);
|
||||
} else {
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("keywords") == "testkeyword $label1"
|
||||
);
|
||||
}
|
||||
extension.sendMessage();
|
||||
});
|
||||
|
||||
extension.onMessage("tags2", async () => {
|
||||
if (IS_IMAP) {
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") ==
|
||||
"testkeyword junk $label2 $label3"
|
||||
);
|
||||
} else {
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") ==
|
||||
"testkeyword $label2 $label3"
|
||||
);
|
||||
}
|
||||
extension.sendMessage();
|
||||
});
|
||||
|
||||
extension.onMessage("empty", async () => {
|
||||
await TestUtils.waitForCondition(() => message.isFlagged);
|
||||
await TestUtils.waitForCondition(() => message.isRead);
|
||||
if (IS_IMAP) {
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") ==
|
||||
"testkeyword junk $label2 $label3"
|
||||
);
|
||||
} else {
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") ==
|
||||
"testkeyword $label2 $label3"
|
||||
);
|
||||
}
|
||||
extension.sendMessage();
|
||||
});
|
||||
|
||||
extension.onMessage("clear", async () => {
|
||||
await TestUtils.waitForCondition(() => !message.isFlagged);
|
||||
await TestUtils.waitForCondition(() => !message.isRead);
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("junkscore") == 0
|
||||
);
|
||||
if (IS_IMAP) {
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("keywords") == "testkeyword nonjunk"
|
||||
);
|
||||
} else {
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("keywords") == "testkeyword"
|
||||
);
|
||||
}
|
||||
extension.sendMessage();
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
extension.sendMessage({
|
||||
folder: { accountId: account.key, path: "/test0" },
|
||||
size: message.messageSize,
|
||||
});
|
||||
|
||||
await extension.awaitMessage("flagged");
|
||||
await TestUtils.waitForCondition(() => message.isFlagged);
|
||||
extension.sendMessage();
|
||||
|
||||
await extension.awaitMessage("read");
|
||||
await TestUtils.waitForCondition(() => message.isRead);
|
||||
extension.sendMessage();
|
||||
|
||||
await extension.awaitMessage("junk");
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("junkscore") == 100
|
||||
);
|
||||
extension.sendMessage();
|
||||
|
||||
await extension.awaitMessage("tags1");
|
||||
if (IS_IMAP) {
|
||||
// Only IMAP sets the junk/nonjunk keyword.
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") == "testkeyword junk $label1"
|
||||
);
|
||||
} else {
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("keywords") == "testkeyword $label1"
|
||||
);
|
||||
}
|
||||
extension.sendMessage();
|
||||
|
||||
await extension.awaitMessage("tags2");
|
||||
if (IS_IMAP) {
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") ==
|
||||
"testkeyword junk $label2 $label3"
|
||||
);
|
||||
} else {
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") == "testkeyword $label2 $label3"
|
||||
);
|
||||
}
|
||||
extension.sendMessage();
|
||||
|
||||
await extension.awaitMessage("empty");
|
||||
await TestUtils.waitForCondition(() => message.isFlagged);
|
||||
await TestUtils.waitForCondition(() => message.isRead);
|
||||
if (IS_IMAP) {
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") ==
|
||||
"testkeyword junk $label2 $label3"
|
||||
);
|
||||
} else {
|
||||
await TestUtils.waitForCondition(
|
||||
() =>
|
||||
message.getStringProperty("keywords") == "testkeyword $label2 $label3"
|
||||
);
|
||||
}
|
||||
extension.sendMessage();
|
||||
|
||||
await extension.awaitMessage("clear");
|
||||
await TestUtils.waitForCondition(() => !message.isFlagged);
|
||||
await TestUtils.waitForCondition(() => !message.isRead);
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("junkscore") == 0
|
||||
);
|
||||
if (IS_IMAP) {
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("keywords") == "testkeyword nonjunk"
|
||||
);
|
||||
} else {
|
||||
await TestUtils.waitForCondition(
|
||||
() => message.getStringProperty("keywords") == "testkeyword"
|
||||
);
|
||||
}
|
||||
extension.sendMessage();
|
||||
|
||||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -389,15 +524,24 @@ add_task(
|
|||
skip_if: () => IS_NNTP,
|
||||
},
|
||||
async function test_move_copy_delete() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
async function capturePrimedEvent(eventName, callback) {
|
||||
let eventPageExtensionReadyPromise = window.waitForMessage();
|
||||
browser.test.sendMessage("capturePrimedEvent", eventName);
|
||||
await eventPageExtensionReadyPromise;
|
||||
let eventPageExtensionFinishedPromise = window.waitForMessage();
|
||||
callback();
|
||||
return eventPageExtensionFinishedPromise;
|
||||
}
|
||||
|
||||
async function checkMessagesInFolder(expectedKeys, folder) {
|
||||
let expectedSubjects = expectedKeys.map(k => messages[k].subject);
|
||||
browser.test.log("expected: " + expectedSubjects);
|
||||
let { messages: actualMessages } = await browser.messages.list(
|
||||
folder
|
||||
);
|
||||
browser.test.log("actual: " + actualMessages.map(m => m.subject));
|
||||
|
||||
browser.test.assertEq(expectedSubjects.length, actualMessages.length);
|
||||
for (let m of actualMessages) {
|
||||
|
@ -517,7 +661,18 @@ add_task(
|
|||
|
||||
// Move one message to another folder.
|
||||
let movePromise = newMovePromise();
|
||||
await browser.messages.move([messages.Red.id], testFolder2);
|
||||
let primedMoveInfo = await capturePrimedEvent("onMoved", () =>
|
||||
browser.messages.move([messages.Red.id], testFolder2)
|
||||
);
|
||||
window.assertDeepEqual(
|
||||
await movePromise,
|
||||
{
|
||||
srcMsgs: primedMoveInfo[0].messages,
|
||||
dstMsgs: primedMoveInfo[1].messages,
|
||||
},
|
||||
"The primed and non-primed onMoved events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
await checkEventInformation(
|
||||
movePromise,
|
||||
["Red"],
|
||||
|
@ -533,7 +688,18 @@ add_task(
|
|||
|
||||
// And back again.
|
||||
movePromise = newMovePromise();
|
||||
await browser.messages.move([messages.Red.id], testFolder1);
|
||||
primedMoveInfo = await capturePrimedEvent("onMoved", () =>
|
||||
browser.messages.move([messages.Red.id], testFolder1)
|
||||
);
|
||||
window.assertDeepEqual(
|
||||
await movePromise,
|
||||
{
|
||||
srcMsgs: primedMoveInfo[0].messages,
|
||||
dstMsgs: primedMoveInfo[1].messages,
|
||||
},
|
||||
"The primed and non-primed onMoved events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
await checkEventInformation(
|
||||
movePromise,
|
||||
["Red"],
|
||||
|
@ -549,9 +715,20 @@ add_task(
|
|||
|
||||
// Move two messages to another folder.
|
||||
movePromise = newMovePromise();
|
||||
await browser.messages.move(
|
||||
[messages.Green.id, messages.My.id],
|
||||
testFolder2
|
||||
primedMoveInfo = await capturePrimedEvent("onMoved", () =>
|
||||
browser.messages.move(
|
||||
[messages.Green.id, messages.My.id],
|
||||
testFolder2
|
||||
)
|
||||
);
|
||||
window.assertDeepEqual(
|
||||
await movePromise,
|
||||
{
|
||||
srcMsgs: primedMoveInfo[0].messages,
|
||||
dstMsgs: primedMoveInfo[1].messages,
|
||||
},
|
||||
"The primed and non-primed onMoved events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
await checkEventInformation(
|
||||
movePromise,
|
||||
|
@ -565,7 +742,18 @@ add_task(
|
|||
|
||||
// Move one back again.
|
||||
movePromise = newMovePromise();
|
||||
await browser.messages.move([messages.My.id], testFolder1);
|
||||
primedMoveInfo = await capturePrimedEvent("onMoved", () =>
|
||||
browser.messages.move([messages.My.id], testFolder1)
|
||||
);
|
||||
window.assertDeepEqual(
|
||||
await movePromise,
|
||||
{
|
||||
srcMsgs: primedMoveInfo[0].messages,
|
||||
dstMsgs: primedMoveInfo[1].messages,
|
||||
},
|
||||
"The primed and non-primed onMoved events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
await checkEventInformation(movePromise, ["My"], messages, testFolder1);
|
||||
await checkMessagesInFolder(
|
||||
["Red", "Blue", "My", "Happy"],
|
||||
|
@ -646,7 +834,18 @@ add_task(
|
|||
|
||||
// Copy one message to another folder.
|
||||
let copyPromise = newCopyPromise();
|
||||
await browser.messages.copy([messages.Happy.id], testFolder2);
|
||||
let primedCopyInfo = await capturePrimedEvent("onCopied", () =>
|
||||
browser.messages.copy([messages.Happy.id], testFolder2)
|
||||
);
|
||||
window.assertDeepEqual(
|
||||
await copyPromise,
|
||||
{
|
||||
srcMsgs: primedCopyInfo[0].messages,
|
||||
dstMsgs: primedCopyInfo[1].messages,
|
||||
},
|
||||
"The primed and non-primed onCopied events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
await checkEventInformation(
|
||||
copyPromise,
|
||||
["Happy"],
|
||||
|
@ -670,9 +869,22 @@ add_task(
|
|||
|
||||
// Delete the copied message.
|
||||
let deletePromise = newDeletePromise();
|
||||
await browser.messages.delete([folder2Messages[0].id], true);
|
||||
let primedDeleteLog = await capturePrimedEvent("onDeleted", () =>
|
||||
browser.messages.delete([folder2Messages[0].id], true)
|
||||
);
|
||||
// Check if the delete information is correct.
|
||||
let deleteLog = await deletePromise;
|
||||
window.assertDeepEqual(
|
||||
[
|
||||
{
|
||||
id: null,
|
||||
messages: deleteLog,
|
||||
},
|
||||
],
|
||||
primedDeleteLog,
|
||||
"The primed and non-primed onDeleted events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
browser.test.assertEq(1, deleteLog.length);
|
||||
browser.test.assertEq(folder2Messages[0].id, deleteLog[0].id);
|
||||
// Check if the message was deleted.
|
||||
|
@ -686,8 +898,18 @@ add_task(
|
|||
|
||||
// Move a message to the trash.
|
||||
movePromise = newMovePromise();
|
||||
browser.test.log("this is the other failing bit");
|
||||
await browser.messages.move([messages.Green.id], trashFolder);
|
||||
primedMoveInfo = await capturePrimedEvent("onMoved", () =>
|
||||
browser.messages.move([messages.Green.id], trashFolder)
|
||||
);
|
||||
window.assertDeepEqual(
|
||||
await movePromise,
|
||||
{
|
||||
srcMsgs: primedMoveInfo[0].messages,
|
||||
dstMsgs: primedMoveInfo[1].messages,
|
||||
},
|
||||
"The primed and non-primed onMoved events should return the same values",
|
||||
{ strict: true }
|
||||
);
|
||||
await checkEventInformation(
|
||||
movePromise,
|
||||
["Green"],
|
||||
|
@ -723,17 +945,30 @@ add_task(
|
|||
"messagesRead",
|
||||
"messagesDelete",
|
||||
],
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "messages.move@mochi.test" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Services.prefs.setIntPref("extensions.webextensions.messagesPerPage", 1000);
|
||||
|
||||
extension.onMessage("capturePrimedEvent", async eventName => {
|
||||
let primedEventData = await event_page_extension(eventName, () => {
|
||||
// Resume execution in the main test, after the event page extension is
|
||||
// ready to capture the event with deactivated background.
|
||||
extension.sendMessage();
|
||||
});
|
||||
extension.sendMessage(...primedEventData);
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
extension.sendMessage(account.key);
|
||||
await extension.awaitFinish("finished");
|
||||
await extension.unload();
|
||||
|
||||
Services.prefs.clearUserPref("extensions.webextensions.messagesPerPage");
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -767,6 +1002,9 @@ add_task(
|
|||
files,
|
||||
manifest: {
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "messages.delete@mochi.test" },
|
||||
},
|
||||
permissions: ["accountsRead", "messagesMove", "messagesRead"],
|
||||
},
|
||||
});
|
||||
|
@ -782,7 +1020,7 @@ add_task(
|
|||
{
|
||||
skip_if: () => IS_NNTP,
|
||||
},
|
||||
async function test_move_anc_copy_without_permission() {
|
||||
async function test_move_and_copy_without_permission() {
|
||||
let files = {
|
||||
"background.js": async () => {
|
||||
let [accountId] = await window.waitForMessage();
|
||||
|
@ -816,6 +1054,9 @@ add_task(
|
|||
files,
|
||||
manifest: {
|
||||
background: { scripts: ["utils.js", "background.js"] },
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "messages.move@mochi.test" },
|
||||
},
|
||||
permissions: ["messagesRead", "accountsRead"],
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,8 +5,80 @@
|
|||
var { ExtensionTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/ExtensionXPCShellUtils.jsm"
|
||||
);
|
||||
var { AddonTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/AddonTestUtils.jsm"
|
||||
);
|
||||
|
||||
ExtensionTestUtils.mockAppInfo();
|
||||
AddonTestUtils.maybeInit(this);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
// Remove the temporary MozillaMailnews folder, which is not deleted in time when
|
||||
// the cleanupFunction registered by AddonTestUtils.maybeInit() checks for left over
|
||||
// files in the temp folder.
|
||||
// Note: PathUtils.tempDir points to the system temp folder, which is different.
|
||||
let path = PathUtils.join(
|
||||
Services.dirsvc.get("TmpD", Ci.nsIFile).path,
|
||||
"MozillaMailnews"
|
||||
);
|
||||
await IOUtils.remove(path, { recursive: true });
|
||||
});
|
||||
|
||||
// Function to start an event page extension (MV3), which can be called whenever
|
||||
// the main test is about to trigger an event. The extension terminates its
|
||||
// background and listens for that single event, verifying it is waking up correctly.
|
||||
async function event_page_extension(eventName, actionCallback) {
|
||||
let ext = ExtensionTestUtils.loadExtension({
|
||||
files: {
|
||||
"background.js": async () => {
|
||||
// Whenever the extension starts or wakes up, hasFired is set to false. In
|
||||
// case of a wake-up, the first fired event is the one that woke up the background.
|
||||
let hasFired = false;
|
||||
let _eventName = browser.runtime.getManifest().description;
|
||||
|
||||
browser.messages[_eventName].addListener(async (...args) => {
|
||||
// Only send the first event after background wake-up, this should
|
||||
// be the only one expected.
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
browser.test.sendMessage(`${_eventName} received`, args);
|
||||
}
|
||||
});
|
||||
browser.test.sendMessage("background started");
|
||||
},
|
||||
},
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
description: eventName,
|
||||
background: { scripts: ["background.js"] },
|
||||
browser_specific_settings: {
|
||||
gecko: { id: "event_page_extension@mochi.test" },
|
||||
},
|
||||
permissions: ["accountsRead", "messagesRead", "messagesMove"],
|
||||
},
|
||||
});
|
||||
await ext.startup();
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "messages", eventName, { primed: false });
|
||||
|
||||
await ext.terminateBackground({ disableResetIdleForTest: true });
|
||||
// Verify the primed persistent listener.
|
||||
assertPersistentListeners(ext, "messages", eventName, { primed: true });
|
||||
|
||||
await actionCallback();
|
||||
let rv = await ext.awaitMessage(`${eventName} received`);
|
||||
await ext.awaitMessage("background started");
|
||||
// The listener should be persistent, but not primed.
|
||||
assertPersistentListeners(ext, "messages", eventName, { primed: false });
|
||||
|
||||
await ext.unload();
|
||||
return rv;
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let account = createAccount();
|
||||
let inbox = await createSubfolder(account.incomingServer.rootFolder, "test1");
|
||||
|
||||
|
@ -17,7 +89,10 @@ add_task(async function() {
|
|||
{ accountId: "account1", name: "test1", path: "/test1" },
|
||||
folder
|
||||
);
|
||||
browser.test.sendMessage("newMessages", messageList.messages);
|
||||
browser.test.sendMessage("onNewMailReceived event received", [
|
||||
folder,
|
||||
messageList,
|
||||
]);
|
||||
});
|
||||
},
|
||||
"utils.js": await getUtilsJS(),
|
||||
|
@ -40,22 +115,37 @@ add_task(async function() {
|
|||
inbox.biffState = Ci.nsIMsgFolder.nsMsgBiffState_NewMail;
|
||||
|
||||
let inboxMessages = [...inbox.messages];
|
||||
let newMessages = await extension.awaitMessage("newMessages");
|
||||
equal(newMessages.length, 1);
|
||||
equal(newMessages[0].subject, inboxMessages[0].subject);
|
||||
let newMessages = await extension.awaitMessage(
|
||||
"onNewMailReceived event received"
|
||||
);
|
||||
equal(newMessages[1].messages.length, 1);
|
||||
equal(newMessages[1].messages[0].subject, inboxMessages[0].subject);
|
||||
|
||||
// Create 2 more new messages.
|
||||
|
||||
await createMessages(inbox, 2);
|
||||
inbox.hasNewMessages = true;
|
||||
inbox.setNumNewMessages(2);
|
||||
inbox.biffState = Ci.nsIMsgFolder.nsMsgBiffState_NewMail;
|
||||
let primedOnNewMailReceivedEventData = await event_page_extension(
|
||||
"onNewMailReceived",
|
||||
async () => {
|
||||
await createMessages(inbox, 2);
|
||||
inbox.hasNewMessages = true;
|
||||
inbox.setNumNewMessages(2);
|
||||
inbox.biffState = Ci.nsIMsgFolder.nsMsgBiffState_NewMail;
|
||||
}
|
||||
);
|
||||
|
||||
inboxMessages = [...inbox.messages];
|
||||
newMessages = await extension.awaitMessage("newMessages");
|
||||
equal(newMessages.length, 2);
|
||||
equal(newMessages[0].subject, inboxMessages[1].subject);
|
||||
equal(newMessages[1].subject, inboxMessages[2].subject);
|
||||
newMessages = await extension.awaitMessage(
|
||||
"onNewMailReceived event received"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
primedOnNewMailReceivedEventData,
|
||||
newMessages,
|
||||
"The primed and non-primed onNewMailReceived events should return the same values"
|
||||
);
|
||||
equal(newMessages[1].messages.length, 2);
|
||||
equal(newMessages[1].messages[0].subject, inboxMessages[1].subject);
|
||||
equal(newMessages[1].messages[1].subject, inboxMessages[2].subject);
|
||||
|
||||
await extension.unload();
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
||||
|
|
|
@ -8,8 +8,7 @@ var { ExtensionTestUtils } = ChromeUtils.import(
|
|||
"resource://testing-common/ExtensionXPCShellUtils.jsm"
|
||||
);
|
||||
|
||||
// Create some folders and populate them.
|
||||
add_task(async function setup() {
|
||||
add_task(async function test_query() {
|
||||
let account = createAccount();
|
||||
|
||||
let textAttachment = {
|
||||
|
|
|
@ -6,6 +6,8 @@ tags = local webextensions
|
|||
|
||||
[include:xpcshell.ini]
|
||||
[test_ext_accounts.js]
|
||||
[test_ext_accounts_mv3.js]
|
||||
[test_ext_identities_mv3.js]
|
||||
[test_ext_addressBook.js]
|
||||
support-files = images/**
|
||||
tags = addrbook
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[test_ext_experiments.js]
|
||||
tags = addrbook
|
||||
[test_ext_folders.js] # NNTP disabled (no support for folder operations).
|
||||
[test_ext_folders_mv3.js] # NNTP disabled (no support for folder operations).
|
||||
[test_ext_messages.js] # NNTP disabled (no support for Trash folder).
|
||||
[test_ext_messages_attachments.js] # IMAP disabled (doesn't work with test server).
|
||||
support-files = messages/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче