Bug 1590682 - Convert troublesome parts of calendar tests to real mochitest code; r=pmorris
authorGeoff Lankow <geoff@darktrojan.net>
Thu, 07 Nov 2019 15:45:08 +1300
changeset 36630 bb95517489e3a6e093a7210c83d9c99e0a812c9b
parent 36629 0402e4370a3cb4093a253970c3b396b43791e783
child 36631 4f748b7dc20dfb713f118f680cec8c2c8443862f
push id2534
push userclokep@gmail.com
push dateMon, 02 Dec 2019 19:52:51 +0000
treeherdercomm-beta@055c50840778 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspmorris
bugs1590682
Bug 1590682 - Convert troublesome parts of calendar tests to real mochitest code; r=pmorris
calendar/lightning/content/lightning-item-iframe.js
calendar/test/browser/browser_localICS.js
calendar/test/browser/browser_timezones.js
calendar/test/browser/browser_todayPane.js
calendar/test/browser/eventDialog/browser_alarmDialog.js
calendar/test/browser/eventDialog/browser_eventDialog.js
calendar/test/browser/eventDialog/browser_eventDialogModificationPrompt.js
calendar/test/browser/eventDialog/browser_eventDialogSize.js
calendar/test/browser/eventDialog/browser_utf8.js
calendar/test/browser/preferences/browser_alarmDefaultValue.js
calendar/test/browser/recurrence/browser_annual.js
calendar/test/browser/recurrence/browser_biweekly.js
calendar/test/browser/recurrence/browser_daily.js
calendar/test/browser/recurrence/browser_lastDayOfMonth.js
calendar/test/browser/recurrence/browser_weeklyN.js
calendar/test/browser/recurrence/browser_weeklyUntil.js
calendar/test/browser/recurrence/browser_weeklyWithException.js
calendar/test/browser/recurrenceRotated/browser_annual.js
calendar/test/browser/recurrenceRotated/browser_biweekly.js
calendar/test/browser/recurrenceRotated/browser_daily.js
calendar/test/browser/recurrenceRotated/browser_lastDayOfMonth.js
calendar/test/browser/recurrenceRotated/browser_weeklyN.js
calendar/test/browser/recurrenceRotated/browser_weeklyUntil.js
calendar/test/browser/recurrenceRotated/browser_weeklyWithException.js
calendar/test/browser/views/browser_dayView.js
calendar/test/browser/views/browser_monthView.js
calendar/test/browser/views/browser_multiweekView.js
calendar/test/browser/views/browser_taskView.js
calendar/test/browser/views/browser_weekView.js
calendar/test/modules/CalendarUtils.jsm
calendar/test/modules/ItemEditingHelpers.jsm
mail/test/resources/mozmill/mozmill/extension/content/stdlib/EventUtils.jsm
--- a/calendar/lightning/content/lightning-item-iframe.js
+++ b/calendar/lightning/content/lightning-item-iframe.js
@@ -3988,17 +3988,19 @@ function setAttendeeContext(aEvent) {
     document.getElementById("attendee-popup-sendemail-menuitem").removeAttribute("hidden");
     document.getElementById("attendee-popup-sendtentativeemail-menuitem").removeAttribute("hidden");
     document.getElementById("attendee-popup-first-separator").removeAttribute("hidden");
 
     // setup attendee specific menu items if appropriate otherwise hide respective menu items
     let mailto = document.getElementById("attendee-popup-emailattendee-menuitem");
     let remove = document.getElementById("attendee-popup-removeattendee-menuitem");
     let secondSeparator = document.getElementById("attendee-popup-second-separator");
-    let attId = aEvent.target.parentNode.getAttribute("attendeeid");
+    let attId =
+      aEvent.target.getAttribute("attendeeid") ||
+      aEvent.target.parentNode.getAttribute("attendeeid");
     let attendee = window.attendees.find(aAtt => aAtt.id == attId);
     if (attendee) {
       mailto.removeAttribute("hidden");
       remove.removeAttribute("hidden");
       secondSeparator.removeAttribute("hidden");
 
       mailto.setAttribute("label", attendee.toString());
       mailto.attendee = attendee;
--- a/calendar/test/browser/browser_localICS.js
+++ b/calendar/test/browser/browser_localICS.js
@@ -38,20 +38,20 @@ add_task(async function testLocalICS() {
   plan_for_modal_dialog("Calendar:NewCalendarWizard", wizard => {
     handleNewCalendarWizard(wizard, calendarName, { network: { format: "ics" } });
   });
   controller.mainMenu.click("#ltnNewCalendar");
   wait_for_modal_dialog("Calendar:NewCalendarWizard", TIMEOUT_MODAL_DIALOG);
 
   // Create new event.
   let box = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, box, (event, iframe) => {
+  await invokeEventDialog(controller, box, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
-    setData(event, iframe, { title: calendarName, calendar: calendarName });
+    await setData(event, iframe, { title: calendarName, calendar: calendarName });
 
     // save
     event.click(eventid("button-saveandclose"));
   });
 
   // Assert presence in view.
   controller.waitForElement(
     lookupEventBox(
--- a/calendar/test/browser/browser_timezones.js
+++ b/calendar/test/browser/browser_timezones.js
@@ -46,30 +46,30 @@ var TIMEZONES = [
   "Australia/Adelaide",
 ];
 
 add_task(async function testTimezones1_SetGMT() {
   Services.prefs.setStringPref("calendar.timezone.local", "Europe/London");
   await setCalendarView("day");
 });
 
-add_task(function testTimezones2_CreateEvents() {
+add_task(async function testTimezones2_CreateEvents() {
   goToDate(controller, 2009, 1, 1);
 
   // Create weekly recurring events in all TIMEZONES.
   let times = [[4, 30], [5, 0], [3, 0], [3, 0], [9, 0], [14, 0], [19, 45], [1, 30]];
   let time = new Date();
   for (let i = 0; i < TIMEZONES.length; i++) {
     let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, i + 11);
-    invokeEventDialog(controller, eventBox, (event, iframe) => {
+    await invokeEventDialog(controller, eventBox, async (event, iframe) => {
       time.setHours(times[i][0]);
       time.setMinutes(times[i][1]);
 
       // Set event data.
-      setData(event, iframe, {
+      await setData(event, iframe, {
         title: TIMEZONES[i],
         repeat: "weekly",
         repeatuntil: new Date(2009, 11, 31),
         starttime: time,
         timezone: TIMEZONES[i],
       });
 
       // save
@@ -249,21 +249,21 @@ function verify(dates, timezones, times)
     `;
   let timeLine = `
         ${DAY_VIEW}/{"class":"mainbox"}/{"class":"scrollbox"}/
         {"class":"timebar"}/{"class":"timebarboxstack"}/{"class":"topbox"}
     `;
   let allowedDifference = 3;
 
   /* Event box' time can't be deduced from it's position in                    ----------------
-       xul element tree because for each event a box is laid over whole day and  |___spacer_____|
-       a spacer is added to push the event to it's correct location.             |__event_box___|
-       But timeline can be used to retrieve the position of a particular hour    |day continues |
-       on screen and it can be compared against the position of the event.       ----------------
-    */
+     xul element tree because for each event a box is laid over whole day and  |___spacer_____|
+     a spacer is added to push the event to it's correct location.             |__event_box___|
+     But timeline can be used to retrieve the position of a particular hour    |day continues |
+     on screen and it can be compared against the position of the event.       ----------------
+  */
 
   for (let [selectedYear, selectedMonth, selectedDay, selectedTime] of datetimes()) {
     goToDate(controller, selectedYear, selectedMonth, selectedDay);
 
     // Find event with timezone tz.
     for (let tzIdx = 0; tzIdx < timezones.length; tzIdx++) {
       let [correctHour, minutes, day] = selectedTime[tzIdx];
 
--- a/calendar/test/browser/browser_todayPane.js
+++ b/calendar/test/browser/browser_todayPane.js
@@ -24,22 +24,22 @@ var { cal } = ChromeUtils.import("resour
 
 var controller = mozmill.getMail3PaneController();
 var { eid, lookup, lookupEventBox, sleep } = helpersForController(controller);
 
 add_task(async function testTodayPane() {
   createCalendar(controller, CALENDARNAME);
   await setCalendarView("day");
 
-  let createEvent = (hour, name) => {
+  let createEvent = async (hour, name) => {
     let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, hour);
-    invokeEventDialog(controller, eventBox, (event, iframe) => {
+    await invokeEventDialog(controller, eventBox, async (event, iframe) => {
       let { eid: eventid } = helpersForController(event);
 
-      setData(event, iframe, { title: name });
+      await setData(event, iframe, { title: name });
       event.click(eventid("button-saveandclose"));
     });
   };
 
   // Go to today and verify date.
   let dayPath = `${DAY_VIEW}/${LABELDAYBOX}/{"flex":"1"}`;
   controller.waitThenClick(lookup(TODAY_BUTTON));
   controller.assert(() => lookup(dayPath).getNode().mDate.icalString == getIsoDate());
@@ -51,28 +51,28 @@ add_task(async function testTodayPane() 
   let hour = new Date().getHours();
   let startHour = hour < 18 ? hour + 6 : 23;
   let view = lookup(DAY_VIEW).getNode();
 
   if (startHour < 8 || startHour > 16) {
     view.scrollToMinute(60 * startHour);
   }
 
-  createEvent(startHour, "Today's Event");
+  await createEvent(startHour, "Today's Event");
 
   // Reset view.
   view.scrollToMinute(60 * 8);
 
   // Go to tomorrow and add an event.
   viewForward(controller, 1);
-  createEvent(9, "Tomorrow's Event");
+  await createEvent(9, "Tomorrow's Event");
 
   // Go 5 days forward and add an event.
   viewForward(controller, 5);
-  createEvent(9, "Future Event");
+  await createEvent(9, "Future Event");
 
   // Go to mail tab.
   controller.click(
     lookup(`
         /id("messengerWindow")/id("navigation-toolbox")/id("tabs-toolbar")/id("tabmail-tabs")/[1]/[0]
     `)
   );
   sleep();
--- a/calendar/test/browser/eventDialog/browser_alarmDialog.js
+++ b/calendar/test/browser/eventDialog/browser_alarmDialog.js
@@ -21,32 +21,32 @@ var {
 var { setData } = ChromeUtils.import("resource://testing-common/mozmill/ItemEditingHelpers.jsm");
 var { plan_for_modal_dialog, wait_for_modal_dialog } = ChromeUtils.import(
   "resource://testing-common/mozmill/WindowHelpers.jsm"
 );
 
 var controller = mozmill.getMail3PaneController();
 var { lookupEventBox } = helpersForController(controller);
 
-add_task(function testAlarmDialog() {
+add_task(async function testAlarmDialog() {
   let now = new Date();
 
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, now.getFullYear(), now.getMonth() + 1, now.getDate());
   viewForward(controller, 1);
 
   controller.click(lookupEventBox("day", ALLDAY, undefined, 1));
   controller.mainMenu.click("#ltnNewEvent");
 
   // Create a new all-day event tomorrow.
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
-    setData(event, iframe, {
+    await setData(event, iframe, {
       allday: true,
       reminder: "1day",
     });
 
     // Prepare to dismiss the alarm.
     plan_for_modal_dialog("Calendar:AlarmWindow", alarm => {
       let { eid: alarmid } = helpersForController(alarm);
       alarm.waitThenClick(alarmid("alarm-dismiss-all-button"));
@@ -55,20 +55,20 @@ add_task(function testAlarmDialog() {
     });
 
     event.click(eventid("button-saveandclose"));
   });
   wait_for_modal_dialog("Calendar:AlarmWindow", TIMEOUT_MODAL_DIALOG);
 
   // Change the reminder duration, this resets the alarm.
   let eventBox = lookupEventBox("day", ALLDAY, undefined, 1, undefined, EVENTPATH);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
-    setData(event, iframe, { reminder: "2days" });
+    await setData(event, iframe, { reminder: "2days" });
 
     // Prepare to snooze the alarm.
     plan_for_modal_dialog("Calendar:AlarmWindow", alarm => {
       let { eid: alarmid } = helpersForController(alarm);
       let snoozeAllButton = alarmid("alarm-snooze-all-button");
       let popup = alarmid("alarm-snooze-all-popup").getNode();
       let menuitems = popup.querySelectorAll(":scope > menuitem");
 
--- a/calendar/test/browser/eventDialog/browser_eventDialog.js
+++ b/calendar/test/browser/eventDialog/browser_eventDialog.js
@@ -35,17 +35,17 @@ var { eid, lookup, lookupEventBox } = he
 
 const EVENTTITLE = "Event";
 const EVENTLOCATION = "Location";
 const EVENTDESCRIPTION = "Event Description";
 const EVENTATTENDEE = "foo@bar.com";
 const EVENTURL = "http://mozilla.org/";
 var firstDay;
 
-add_task(function testEventDialog() {
+add_task(async function testEventDialog() {
   let dateFormatter = cal.getDateFormatter();
   let now = new Date();
 
   createCalendar(controller, CALENDARNAME);
   // Since from other tests we may be elsewhere, make sure we start today.
   switchToView(controller, "day");
   goToDate(controller, now.getFullYear(), now.getMonth() + 1, now.getDate());
   viewBack(controller, 1);
@@ -73,17 +73,17 @@ add_task(function testEventDialog() {
     cal.dtz.floating
   );
   let endTime = dateFormatter.formatTime(nextHour);
 
   // Create new event on first day in view.
   controller.click(lookupEventBox("month", CANVAS_BOX, 1, 1, null));
   controller.mainMenu.click("#ltnNewEvent");
 
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
     let { eid: iframeId } = helpersForController(iframe);
     let { iframeLookup, getDateTimePicker } = helpersForEditUI(iframe);
 
     // First check all standard-values are set correctly.
     let startTimeInput = getDateTimePicker("STARTTIME");
 
     event.waitForElement(startTimeInput);
@@ -100,17 +100,17 @@ add_task(function testEventDialog() {
     let categories = cal.l10n.getAnyString("calendar", "categories", "categories2");
     // Pick 4th value in a comma-separated list.
     let category = categories.split(",")[4];
     // Calculate date to repeat until.
     let untildate = firstDay.clone();
     untildate.addDuration(cal.createDuration("P20D"));
 
     // Fill in the rest of the values.
-    setData(event, iframe, {
+    await setData(event, iframe, {
       title: EVENTTITLE,
       location: EVENTLOCATION,
       description: EVENTDESCRIPTION,
       categories: [category],
       repeat: "daily",
       repeatuntil: cal.dtz.dateTimeToJsDate(untildate),
       reminder: "5minutes",
       privacy: "private",
--- a/calendar/test/browser/eventDialog/browser_eventDialogModificationPrompt.js
+++ b/calendar/test/browser/eventDialog/browser_eventDialogModificationPrompt.js
@@ -24,78 +24,78 @@ var { setData } = ChromeUtils.import("re
 var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { eid, lookupEventBox } = helpersForController(controller);
 
 var { date1, date2, date3, data, newlines } = setupData();
 
 // Test that closing an event dialog with no changes does not prompt for save.
-add_task(function testEventDialogModificationPrompt() {
+add_task(async function testEventDialogModificationPrompt() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 1);
 
   let createbox = lookupEventBox("day", CANVAS_BOX, null, 1, 8);
   let eventbox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
 
   // Create new event.
-  invokeEventDialog(controller, createbox, (event, iframe) => {
+  await invokeEventDialog(controller, createbox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     let categories = cal.l10n.getAnyString("calendar", "categories", "categories2").split(",");
     data[0].categories.push(categories[0]);
     data[1].categories.push(categories[1], categories[2]);
 
     // Enter first set of data.
-    setData(event, iframe, data[0]);
+    await setData(event, iframe, data[0]);
 
     // save
     event.click(eventid("button-saveandclose"));
   });
 
   // Open, but change nothing.
-  invokeEventDialog(controller, eventbox, (event, iframe) => {
+  await invokeEventDialog(controller, eventbox, (event, iframe) => {
     // Escape the event window, there should be no prompt to save event.
     event.keypress(null, "VK_ESCAPE", {});
     // Wait to see if the prompt appears.
     controller.sleep(2000);
   });
 
   // Open, change all values then revert the changes.
-  invokeEventDialog(controller, eventbox, (event, iframe) => {
+  await invokeEventDialog(controller, eventbox, async (event, iframe) => {
     // Change all values.
-    setData(event, iframe, data[1]);
+    await setData(event, iframe, data[1]);
 
     // Edit all values back to original.
-    setData(event, iframe, data[0]);
+    await setData(event, iframe, data[0]);
 
     // Escape the event window, there should be no prompt to save event.
     event.keypress(null, "VK_ESCAPE", {});
     // Wait to see if the prompt appears.
     controller.sleep(2000);
   });
 
   // Delete event.
   controller.click(eventbox);
   controller.keypress(eid("day-view"), "VK_DELETE", {});
   controller.waitForElementNotPresent(eventbox);
 
   for (let i = 0; i < newlines.length; i++) {
     // test set i
-    invokeEventDialog(controller, createbox, (event, iframe) => {
+    await invokeEventDialog(controller, createbox, async (event, iframe) => {
       let { eid: eventid } = helpersForController(event);
 
-      setData(event, iframe, newlines[i]);
+      await setData(event, iframe, newlines[i]);
       event.click(eventid("button-saveandclose"));
     });
 
     // Open and close.
-    invokeEventDialog(controller, eventbox, (event, iframe) => {
-      setData(event, iframe, newlines[i]);
+    await invokeEventDialog(controller, eventbox, async (event, iframe) => {
+      await setData(event, iframe, newlines[i]);
       event.keypress(null, "VK_ESCAPE", {});
       // Wait to see if the prompt appears.
       controller.sleep(2000);
     });
 
     // Delete it.
     // XXX Somehow the event is selected at this point, this didn't use to
     // be the case and can't be reproduced manually.
--- a/calendar/test/browser/eventDialog/browser_eventDialogSize.js
+++ b/calendar/test/browser/eventDialog/browser_eventDialogSize.js
@@ -19,71 +19,71 @@ var controller = mozmill.getMail3PaneCon
 
 const SMALL_TOLERANCE = 5;
 const LARGE_TOLERANCE = 10;
 
 add_task(function setupModule(module) {
   createCalendar(controller, CALENDARNAME);
 });
 
-add_task(function testEventDialog() {
+add_task(async function testEventDialog() {
   dump("#ltnNewEvent click\n");
   controller.mainMenu.click("#ltnNewEvent");
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, (event, iframe) => {
     checkLargeEnough(event, iframe);
 
     // Much larger than necessary.
     event.window.resizeTo(640, 690);
     checkWithinTolerance(event.window.outerWidth, 640);
     checkWithinTolerance(event.window.outerHeight, 690);
     event.keypress(null, "VK_ESCAPE", {});
   });
 
   checkWithinTolerance(getPersistedValue("event", "width"), 640, LARGE_TOLERANCE);
   checkWithinTolerance(getPersistedValue("event", "height"), 690, LARGE_TOLERANCE);
 
   dump("#ltnNewEvent click\n");
   controller.mainMenu.click("#ltnNewEvent");
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, (event, iframe) => {
     checkWithinTolerance(event.window.outerWidth, 640, LARGE_TOLERANCE);
     checkWithinTolerance(event.window.outerHeight, 690, LARGE_TOLERANCE);
     checkLargeEnough(event, iframe);
 
     // Much smaller than necessary.
     event.window.resizeTo(350, 400);
     controller.assert(() => event.window.outerWidth < 640);
     controller.assert(() => event.window.outerHeight < 690);
     controller.assert(() => event.window.outerWidth > 350);
     controller.assert(() => event.window.outerHeight > 400);
     checkLargeEnough(event, iframe);
     event.keypress(null, "VK_ESCAPE", {});
   });
 
   dump("#ltnNewEvent click\n");
   controller.mainMenu.click("#ltnNewEvent");
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, (event, iframe) => {
     checkLargeEnough(event, iframe);
 
     // Much larger than necessary.
     event.window.resizeTo(640, 690);
     checkWithinTolerance(event.window.outerWidth, 640);
     checkWithinTolerance(event.window.outerHeight, 690);
     event.keypress(null, "VK_ESCAPE", {});
   });
 
   checkWithinTolerance(getPersistedValue("event", "width"), 640, LARGE_TOLERANCE);
   checkWithinTolerance(getPersistedValue("event", "height"), 690, LARGE_TOLERANCE);
 
   Assert.ok(true, "Test ran to completion");
 });
 
-add_task(function testTaskDialog() {
+add_task(async function testTaskDialog() {
   dump("#ltnNewTask click\n");
   controller.mainMenu.click("#ltnNewTask");
-  invokeEventDialog(controller, null, (task, iframe) => {
+  await invokeEventDialog(controller, null, (task, iframe) => {
     checkWithinTolerance(getPersistedValue("event", "width"), 640, LARGE_TOLERANCE);
     checkWithinTolerance(getPersistedValue("event", "height"), 690, LARGE_TOLERANCE);
 
     checkLargeEnough(task, iframe);
 
     // Much larger than necessary.
     task.window.resizeTo(650, 700);
     checkWithinTolerance(task.window.outerWidth, 650);
@@ -91,34 +91,34 @@ add_task(function testTaskDialog() {
     task.keypress(null, "VK_ESCAPE", {});
   });
 
   checkWithinTolerance(getPersistedValue("task", "width"), 650, LARGE_TOLERANCE);
   checkWithinTolerance(getPersistedValue("task", "height"), 700, LARGE_TOLERANCE);
 
   dump("#ltnNewTask click\n");
   controller.mainMenu.click("#ltnNewTask");
-  invokeEventDialog(controller, null, (task, iframe) => {
+  await invokeEventDialog(controller, null, (task, iframe) => {
     checkWithinTolerance(task.window.outerWidth, 650, LARGE_TOLERANCE);
     checkWithinTolerance(task.window.outerHeight, 700, LARGE_TOLERANCE);
     checkLargeEnough(task, iframe);
 
     // Much smaller than necessary.
     task.window.resizeTo(350, 400);
     controller.assert(() => task.window.outerWidth < 650);
     controller.assert(() => task.window.outerHeight < 700);
     controller.assert(() => task.window.outerWidth > 350);
     controller.assert(() => task.window.outerHeight > 400);
     checkLargeEnough(task, iframe);
     task.keypress(null, "VK_ESCAPE", {});
   });
 
   dump("#ltnNewTask click\n");
   controller.mainMenu.click("#ltnNewTask");
-  invokeEventDialog(controller, null, (task, iframe) => {
+  await invokeEventDialog(controller, null, (task, iframe) => {
     checkLargeEnough(task, iframe);
 
     // Much larger than necessary.
     task.window.resizeTo(650, 700);
     checkWithinTolerance(task.window.outerWidth, 650);
     checkWithinTolerance(task.window.outerHeight, 700);
     task.keypress(null, "VK_ESCAPE", {});
   });
--- a/calendar/test/browser/eventDialog/browser_utf8.js
+++ b/calendar/test/browser/eventDialog/browser_utf8.js
@@ -18,42 +18,42 @@ var { setData } = ChromeUtils.import("re
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { lookupEventBox } = helpersForController(controller);
 
 var UTF8STRING = " 💣 💥  ☣  ";
 
-add_task(function testUTF8() {
+add_task(async function testUTF8() {
   Services.prefs.setStringPref("calendar.categories.names", UTF8STRING);
   createCalendar(controller, UTF8STRING);
   switchToView(controller, "day");
 
   // Create new event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, 8);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     // Fill in name, location, description.
-    setData(event, iframe, {
+    await setData(event, iframe, {
       title: UTF8STRING,
       location: UTF8STRING,
       description: UTF8STRING,
       categories: [UTF8STRING],
     });
 
     // save
     event.click(eventid("button-saveandclose"));
   });
 
   // open
   let eventPath = `/{"tooltip":"itemTooltip","calendar":"${UTF8STRING.toLowerCase()}"}`;
   eventBox = lookupEventBox("day", EVENT_BOX, null, 1, null, eventPath);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: iframeId } = helpersForController(iframe);
 
     // Check values.
     event.assertValue(iframeId("item-title"), UTF8STRING);
     event.assertValue(iframeId("item-location"), UTF8STRING);
     event.assertValue(iframeId("item-description"), UTF8STRING);
     event.assert(() =>
       iframeId("item-categories").getNode().querySelector(`
--- a/calendar/test/browser/preferences/browser_alarmDefaultValue.js
+++ b/calendar/test/browser/preferences/browser_alarmDefaultValue.js
@@ -27,17 +27,17 @@ var { PluralForm } = ChromeUtils.import(
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const DEFVALUE = 43;
 
 var controller = mozmill.getMail3PaneController();
 
 var prefTab = null;
 
-add_task(function testDefaultAlarms() {
+add_task(async function testDefaultAlarms() {
   let localeUnitString = cal.l10n.getCalString("unitDays");
   let unitString = PluralForm.get(DEFVALUE, localeUnitString).replace("#1", DEFVALUE);
   let alarmString = (...args) => cal.l10n.getString("calendar-alarms", ...args);
   let originStringEvent = alarmString("reminderCustomOriginBeginBeforeEvent");
   let originStringTask = alarmString("reminderCustomOriginBeginBeforeTask");
   let expectedEventReminder = alarmString("reminderCustomTitle", [unitString, originStringEvent]);
   let expectedTaskReminder = alarmString("reminderCustomTitle", [unitString, originStringTask]);
 
@@ -46,17 +46,17 @@ add_task(function testDefaultAlarms() {
     `;
 
   // Configure the lightning preferences.
   openLightningPrefs(handlePrefTab, controller);
 
   // Create New Event.
   controller.keypress(null, "i", { shiftKey: false, accelKey: true });
   // Set up the event dialog controller.
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, (event, iframe) => {
     let { xpath: eventpath, eid: eventid } = helpersForController(event);
 
     // Check if the "custom" item was selected.
     event.assertDOMProperty(eventid("item-alarm"), "value", "custom");
     let reminderDetailsVisible = eventpath(detailPath);
     event.assertDOMProperty(reminderDetailsVisible, "value", expectedEventReminder);
 
     plan_for_modal_dialog("Calendar:EventDialog:Reminder", handleReminderDialog);
@@ -64,17 +64,17 @@ add_task(function testDefaultAlarms() {
     wait_for_modal_dialog("Calendar:EventDialog:Reminder");
 
     // Close the event dialog.
     event.window.close();
   });
 
   // Create New Task.
   controller.keypress(null, "d", { shiftKey: false, accelKey: true });
-  invokeEventDialog(controller, null, (task, iframe) => {
+  await invokeEventDialog(controller, null, (task, iframe) => {
     let { xpath: taskpath, eid: taskid } = helpersForController(task);
 
     // Check if the "custom" item was selected.
     task.assertDOMProperty(taskid("item-alarm"), "value", "custom");
     let reminderDetailsVisible = taskpath(detailPath);
     task.assertDOMProperty(reminderDetailsVisible, "value", expectedTaskReminder);
 
     plan_for_modal_dialog("Calendar:EventDialog:Reminder", handleReminderDialog);
--- a/calendar/test/browser/recurrence/browser_annual.js
+++ b/calendar/test/browser/recurrence/browser_annual.js
@@ -21,24 +21,24 @@ var {
 } = ChromeUtils.import("resource://testing-common/mozmill/CalendarUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { getEventBoxPath, lookup, lookupEventBox } = helpersForController(controller);
 
 const STARTYEAR = 1950;
 const EPOCH = 1970;
 
-add_task(function testAnnualRecurrence() {
+add_task(async function testAnnualRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, STARTYEAR, 1, 1);
 
   // Create yearly recurring all-day event.
   let eventBox = lookupEventBox("day", ALLDAY, null, 1, null);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     menulistSelect(eventid("item-repeat"), "yearly", event);
     event.click(eventid("button-saveandclose"));
   });
 
   let checkYears = [STARTYEAR, STARTYEAR + 1, EPOCH - 1, EPOCH, EPOCH + 1];
   for (let year of checkYears) {
--- a/calendar/test/browser/recurrence/browser_biweekly.js
+++ b/calendar/test/browser/recurrence/browser_biweekly.js
@@ -21,24 +21,24 @@ var {
   viewForward,
 } = ChromeUtils.import("resource://testing-common/mozmill/CalendarUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 
-add_task(function testBiweeklyRecurrence() {
+add_task(async function testBiweeklyRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 31);
 
   // Create biweekly event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     menulistSelect(eventid("item-repeat"), "bi.weekly", event);
     event.click(eventid("button-saveandclose"));
   });
 
   // Check day view.
   for (let i = 0; i < 4; i++) {
--- a/calendar/test/browser/recurrence/browser_daily.js
+++ b/calendar/test/browser/recurrence/browser_daily.js
@@ -23,27 +23,27 @@ var {
 } = ChromeUtils.import("resource://testing-common/mozmill/CalendarUtils.jsm");
 var { setData } = ChromeUtils.import("resource://testing-common/mozmill/ItemEditingHelpers.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 
-add_task(function testDailyRecurrence() {
+add_task(async function testDailyRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 1);
 
   // Create daily event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
-    setData(event, iframe, { repeat: "daily", repeatuntil: new Date(2009, 2, 20) });
+    await setData(event, iframe, { repeat: "daily", repeatuntil: new Date(2009, 2, 20) });
     event.click(eventid("button-saveandclose"));
   });
 
   // Check day view for 7 days.
   let daybox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
   controller.waitForElement(daybox);
 
   for (let day = 1; day <= 7; day++) {
@@ -112,17 +112,17 @@ add_task(function testDailyRecurrence() 
   switchToView(controller, "day");
   controller.assertNodeNotExist(lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH));
 
   // Go to previous day to edit event to occur only on weekdays.
   viewBack(controller, 1);
 
   eventBox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
   handleOccurrencePrompt(controller, eventBox, "modify", true);
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, (event, iframe) => {
     let { eid: eventid, sleep: eventsleep } = helpersForController(event);
 
     menulistSelect(eventid("item-repeat"), "every.weekday", event);
     eventsleep();
     event.click(eventid("button-saveandclose"));
   });
 
   // Check day view for 7 days.
--- a/calendar/test/browser/recurrence/browser_lastDayOfMonth.js
+++ b/calendar/test/browser/recurrence/browser_lastDayOfMonth.js
@@ -27,24 +27,24 @@ var { plan_for_modal_dialog, wait_for_mo
   "resource://testing-common/mozmill/WindowHelpers.jsm"
 );
 
 var controller = mozmill.getMail3PaneController();
 var { lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 
-add_task(function testLastDayOfMonthRecurrence() {
+add_task(async function testLastDayOfMonthRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2008, 1, 31); // Start with a leap year.
 
   // Create monthly recurring event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", setRecurrence);
     menulistSelect(eventid("item-repeat"), "custom", event);
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
     event.click(eventid("button-saveandclose"));
   });
--- a/calendar/test/browser/recurrence/browser_weeklyN.js
+++ b/calendar/test/browser/recurrence/browser_weeklyN.js
@@ -30,24 +30,24 @@ var { plan_for_modal_dialog, wait_for_mo
 
 var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 
-add_task(function testWeeklyNRecurrence() {
+add_task(async function testWeeklyNRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 5);
 
   // Create weekly recurring event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", setRecurrence);
     event.waitForElement(eventid("item-repeat"));
     menulistSelect(eventid("item-repeat"), "custom", event);
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
     event.click(eventid("button-saveandclose"));
--- a/calendar/test/browser/recurrence/browser_weeklyUntil.js
+++ b/calendar/test/browser/recurrence/browser_weeklyUntil.js
@@ -32,24 +32,24 @@ var { plan_for_modal_dialog, wait_for_mo
 var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { lookupEventBox } = helpersForController(controller);
 
 const ENDDATE = new Date(2009, 0, 26); // Last Monday in month.
 const HOUR = 8;
 
-add_task(function testWeeklyUntilRecurrence() {
+add_task(async function testWeeklyUntilRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 5); // Monday
 
   // Create weekly recurring event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", setRecurrence);
     event.waitForElement(eventid("item-repeat"));
     menulistSelect(eventid("item-repeat"), "custom", event);
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
     event.click(eventid("button-saveandclose"));
--- a/calendar/test/browser/recurrence/browser_weeklyWithException.js
+++ b/calendar/test/browser/recurrence/browser_weeklyWithException.js
@@ -37,49 +37,49 @@ var { plan_for_modal_dialog, wait_for_mo
 var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { lookup, lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 const STARTDATE = new Date(2009, 0, 6);
 
-add_task(function testWeeklyWithExceptionRecurrence() {
+add_task(async function testWeeklyWithExceptionRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 5);
 
   // Create weekly recurring event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     event.waitForElement(eventid("item-repeat"));
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", setRecurrence);
     menulistSelect(eventid("item-repeat"), "custom", event);
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
     event.click(eventid("button-saveandclose"));
   });
 
   // Move 5th January occurrence to 6th January.
   eventBox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
   handleOccurrencePrompt(controller, eventBox, "modify", false);
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
-    setData(event, iframe, { startdate: STARTDATE, enddate: STARTDATE });
+    await setData(event, iframe, { startdate: STARTDATE, enddate: STARTDATE });
     event.click(eventid("button-saveandclose"));
   });
 
   // Change recurrence rule.
   goToDate(controller, 2009, 1, 7);
   eventBox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
   handleOccurrencePrompt(controller, eventBox, "modify", true);
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
     let { iframeLookup } = helpersForEditUI(iframe);
 
     event.waitForElement(eventid("item-repeat"));
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", changeRecurrence);
     event.click(iframeLookup(REPEAT_DETAILS));
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
--- a/calendar/test/browser/recurrenceRotated/browser_annual.js
+++ b/calendar/test/browser/recurrenceRotated/browser_annual.js
@@ -21,28 +21,28 @@ var {
 } = ChromeUtils.import("resource://testing-common/mozmill/CalendarUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { eid, getEventBoxPath, lookup, lookupEventBox } = helpersForController(controller);
 
 const STARTYEAR = 1950;
 const EPOCH = 1970;
 
-add_task(function testAnnualRecurrence() {
+add_task(async function testAnnualRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, STARTYEAR, 1, 1);
 
   // Rotate view.
   controller.mainMenu.click("#ltnViewRotated");
   controller.waitFor(() => eid("day-view").getNode().orient == "horizontal");
 
   // Create yearly recurring all-day event.
   let eventBox = lookupEventBox("day", ALLDAY, null, 1, null);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     menulistSelect(eventid("item-repeat"), "yearly", event);
     event.click(eventid("button-saveandclose"));
   });
 
   let checkYears = [STARTYEAR, STARTYEAR + 1, EPOCH - 1, EPOCH, EPOCH + 1];
   for (let year of checkYears) {
--- a/calendar/test/browser/recurrenceRotated/browser_biweekly.js
+++ b/calendar/test/browser/recurrenceRotated/browser_biweekly.js
@@ -21,28 +21,28 @@ var {
   viewForward,
 } = ChromeUtils.import("resource://testing-common/mozmill/CalendarUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { eid, lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 
-add_task(function testBiweeklyRecurrence() {
+add_task(async function testBiweeklyRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 31);
 
   // Rotate view.
   controller.mainMenu.click("#ltnViewRotated");
   controller.waitFor(() => eid("day-view").getNode().orient == "horizontal");
 
   // Create biweekly event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     menulistSelect(eventid("item-repeat"), "bi.weekly", event);
     event.click(eventid("button-saveandclose"));
   });
 
   // Check day view.
   switchToView(controller, "day");
--- a/calendar/test/browser/recurrenceRotated/browser_daily.js
+++ b/calendar/test/browser/recurrenceRotated/browser_daily.js
@@ -23,31 +23,31 @@ var {
 } = ChromeUtils.import("resource://testing-common/mozmill/CalendarUtils.jsm");
 var { setData } = ChromeUtils.import("resource://testing-common/mozmill/ItemEditingHelpers.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { eid, lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 
-add_task(function testDailyRecurrence() {
+add_task(async function testDailyRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 1);
 
   // Rotate view.
   controller.mainMenu.click("#ltnViewRotated");
   controller.waitFor(() => eid("day-view").getNode().orient == "horizontal");
 
   // Create daily event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
-    setData(event, iframe, { repeat: "daily", repeatuntil: new Date(2009, 2, 20) });
+    await setData(event, iframe, { repeat: "daily", repeatuntil: new Date(2009, 2, 20) });
     event.click(eventid("button-saveandclose"));
   });
 
   // Check day view for 7 days.
   let daybox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
   controller.waitForElement(daybox);
 
   for (let day = 1; day <= 7; day++) {
@@ -115,17 +115,17 @@ add_task(function testDailyRecurrence() 
   switchToView(controller, "day");
   controller.assertNodeNotExist(lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH));
 
   // Go to previous day to edit event to occur only on weekdays.
   viewBack(controller, 1);
 
   eventBox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
   handleOccurrencePrompt(controller, eventBox, "modify", true);
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, (event, iframe) => {
     let { eid: eventid, sleep: eventsleep } = helpersForController(event);
 
     menulistSelect(eventid("item-repeat"), "every.weekday", event);
     eventsleep();
     event.click(eventid("button-saveandclose"));
   });
 
   // Check day view for 7 days.
--- a/calendar/test/browser/recurrenceRotated/browser_lastDayOfMonth.js
+++ b/calendar/test/browser/recurrenceRotated/browser_lastDayOfMonth.js
@@ -27,29 +27,29 @@ var { plan_for_modal_dialog, wait_for_mo
   "resource://testing-common/mozmill/WindowHelpers.jsm"
 );
 
 var controller = mozmill.getMail3PaneController();
 var { eid, lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 
-add_task(function testLastDayOfMonthRecurrence() {
+add_task(async function testLastDayOfMonthRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2008, 1, 31); // Start with a leap year.
 
   // Rotate view.
   switchToView(controller, "day");
   controller.mainMenu.click("#ltnViewRotated");
   controller.waitFor(() => eid("day-view").getNode().orient == "horizontal");
 
   // Create monthly recurring event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", setRecurrence);
     menulistSelect(eventid("item-repeat"), "custom", event);
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
     event.click(eventid("button-saveandclose"));
   });
--- a/calendar/test/browser/recurrenceRotated/browser_weeklyN.js
+++ b/calendar/test/browser/recurrenceRotated/browser_weeklyN.js
@@ -30,28 +30,28 @@ var { plan_for_modal_dialog, wait_for_mo
 
 var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { eid, lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 
-add_task(function testWeeklyNRecurrence() {
+add_task(async function testWeeklyNRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 5);
 
   // Rotate view.
   controller.mainMenu.click("#ltnViewRotated");
   controller.waitFor(() => eid("day-view").getNode().orient == "horizontal");
 
   // Create weekly recurring event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", setRecurrence);
     event.waitForElement(eventid("item-repeat"));
     menulistSelect(eventid("item-repeat"), "custom", event);
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
     event.click(eventid("button-saveandclose"));
--- a/calendar/test/browser/recurrenceRotated/browser_weeklyUntil.js
+++ b/calendar/test/browser/recurrenceRotated/browser_weeklyUntil.js
@@ -52,22 +52,22 @@ add_task(function setupModule(module) {
 
   switchToView(controller, "day");
   createCalendar(controller, CALENDARNAME);
   // Rotate view.
   controller.mainMenu.click("#ltnViewRotated");
   controller.waitFor(() => eid("day-view").getNode().orient == "horizontal");
 });
 
-add_task(function testWeeklyUntilRecurrence() {
+add_task(async function testWeeklyUntilRecurrence() {
   goToDate(controller, 2009, 1, 5); // Monday
 
   // Create weekly recurring event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", setRecurrence);
     event.waitForElement(eventid("item-repeat"));
     menulistSelect(eventid("item-repeat"), "custom", event);
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
     event.click(eventid("button-saveandclose"));
--- a/calendar/test/browser/recurrenceRotated/browser_weeklyWithException.js
+++ b/calendar/test/browser/recurrenceRotated/browser_weeklyWithException.js
@@ -37,53 +37,53 @@ var { plan_for_modal_dialog, wait_for_mo
 var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 
 var controller = mozmill.getMail3PaneController();
 var { eid, lookup, lookupEventBox } = helpersForController(controller);
 
 const HOUR = 8;
 const STARTDATE = new Date(2009, 0, 6);
 
-add_task(function testWeeklyWithExceptionRecurrence() {
+add_task(async function testWeeklyWithExceptionRecurrence() {
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 5);
 
   // Rotate view.
   controller.mainMenu.click("#ltnViewRotated");
   controller.waitFor(() => eid("day-view").getNode().orient == "horizontal");
 
   // Create weekly recurring event.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, HOUR);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     event.waitForElement(eventid("item-repeat"));
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", setRecurrence);
     menulistSelect(eventid("item-repeat"), "custom", event);
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
     event.click(eventid("button-saveandclose"));
   });
 
   // Move 5th January occurrence to 6th January.
   eventBox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
   handleOccurrencePrompt(controller, eventBox, "modify", false);
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
-    setData(event, iframe, { startdate: STARTDATE, enddate: STARTDATE });
+    await setData(event, iframe, { startdate: STARTDATE, enddate: STARTDATE });
     event.click(eventid("button-saveandclose"));
   });
 
   // Change recurrence rule.
   goToDate(controller, 2009, 1, 7);
   eventBox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
   handleOccurrencePrompt(controller, eventBox, "modify", true);
-  invokeEventDialog(controller, null, (event, iframe) => {
+  await invokeEventDialog(controller, null, (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
     let { iframeLookup } = helpersForEditUI(iframe);
 
     event.waitForElement(eventid("item-repeat"));
     plan_for_modal_dialog("Calendar:EventDialog:Recurrence", changeRecurrence);
     event.click(iframeLookup(REPEAT_DETAILS));
     wait_for_modal_dialog("Calendar:EventDialog:Recurrence", TIMEOUT_MODAL_DIALOG);
 
--- a/calendar/test/browser/views/browser_dayView.js
+++ b/calendar/test/browser/views/browser_dayView.js
@@ -28,61 +28,61 @@ var { cal } = ChromeUtils.import("resour
 
 var controller = mozmill.getMail3PaneController();
 var { lookup, lookupEventBox } = helpersForController(controller);
 
 const TITLE1 = "Day View Event";
 const TITLE2 = "Day View Event Changed";
 const DESC = "Day View Event Description";
 
-add_task(function testDayView() {
+add_task(async function testDayView() {
   let dateFormatter = cal.getDateFormatter();
 
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "day");
   goToDate(controller, 2009, 1, 1);
 
   // Verify date in view.
   let day = lookup(`${DAY_VIEW}/${LABELDAYBOX}/{"flex":"1"}`);
   controller.waitFor(() => day.getNode().mDate.icalString == "20090101");
 
   // Create event at 8 AM.
   let eventBox = lookupEventBox("day", CANVAS_BOX, null, 1, 8);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
     let { getDateTimePicker } = helpersForEditUI(iframe);
 
     let startTimeInput = getDateTimePicker("STARTTIME");
     let startDateInput = getDateTimePicker("STARTDATE");
 
     // Check that the start time is correct.
     let someDate = cal.createDateTime();
     someDate.resetTo(2009, 0, 1, 8, 0, 0, cal.dtz.floating);
     event.waitForElement(startTimeInput);
     event.assertValue(startTimeInput, dateFormatter.formatTime(someDate));
     event.assertValue(startDateInput, dateFormatter.formatDateShort(someDate));
 
     // Fill in title, description and calendar.
-    setData(event, iframe, {
+    await setData(event, iframe, {
       title: TITLE1,
       description: DESC,
       calendar: CALENDARNAME,
     });
 
     // save
     event.click(eventid("button-saveandclose"));
   });
 
   // If it was created successfully, it can be opened.
   eventBox = lookupEventBox("day", EVENT_BOX, null, 1, null, EVENTPATH);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     // Change title and save changes.
-    setData(event, iframe, { title: TITLE2 });
+    await setData(event, iframe, { title: TITLE2 });
     event.click(eventid("button-saveandclose"));
   });
 
   // Check if name was saved.
   let eventName = lookupEventBox(
     "day",
     EVENT_BOX,
     null,
--- a/calendar/test/browser/views/browser_monthView.js
+++ b/calendar/test/browser/views/browser_monthView.js
@@ -26,67 +26,67 @@ var { cal } = ChromeUtils.import("resour
 
 var controller = mozmill.getMail3PaneController();
 var { lookup, lookupEventBox } = helpersForController(controller);
 
 const TITLE1 = "Month View Event";
 const TITLE2 = "Month View Event Changed";
 const DESC = "Month View Event Description";
 
-add_task(function testMonthView() {
+add_task(async function testMonthView() {
   let dateFormatter = cal.getDateFormatter();
 
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "month");
   goToDate(controller, 2009, 1, 1);
 
   // Verify date.
   let day = lookup(`
         ${MONTH_VIEW}/{"class":"mainbox"}/{"class":"monthgrid"}/[0]/{"selected":"true"}/[0]
     `);
   controller.waitFor(() => day.getNode().mDate.icalString == "20090101");
 
   // Create event.
   // Thursday of 2009-01-01 should be the selected box in the first row with default settings.
   let hour = new Date().getHours(); // Remember time at click.
   let eventBox = lookupEventBox("month", CANVAS_BOX, 1, 5);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
     let { getDateTimePicker } = helpersForEditUI(iframe);
 
     let startTimeInput = getDateTimePicker("STARTTIME");
     let startDateInput = getDateTimePicker("STARTDATE");
 
     // Check that the start time is correct.
     // Next full hour except last hour hour of the day.
     let nextHour = hour == 23 ? hour : (hour + 1) % 24;
     let someDate = cal.dtz.now();
     someDate.resetTo(2009, 0, 1, nextHour, 0, 0, cal.dtz.floating);
     event.waitForElement(startTimeInput);
     event.assertValue(startTimeInput, dateFormatter.formatTime(someDate));
     event.assertValue(startDateInput, dateFormatter.formatDateShort(someDate));
 
     // Fill in title, description and calendar.
-    setData(event, iframe, {
+    await setData(event, iframe, {
       title: TITLE1,
       description: DESC,
       calendar: CALENDARNAME,
     });
 
     // save
     event.click(eventid("button-saveandclose"));
   });
 
   // If it was created successfully, it can be opened.
   eventBox = lookupEventBox("month", CANVAS_BOX, 1, 5, null, EVENTPATH);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     // Change title and save changes.
-    setData(event, iframe, { title: TITLE2 });
+    await setData(event, iframe, { title: TITLE2 });
     event.click(eventid("button-saveandclose"));
   });
 
   // Check if name was saved.
   let eventName = lookupEventBox(
     "month",
     CANVAS_BOX,
     1,
--- a/calendar/test/browser/views/browser_multiweekView.js
+++ b/calendar/test/browser/views/browser_multiweekView.js
@@ -26,67 +26,67 @@ var { cal } = ChromeUtils.import("resour
 
 var controller = mozmill.getMail3PaneController();
 var { lookup, lookupEventBox } = helpersForController(controller);
 
 const TITLE1 = "Multiweek View Event";
 const TITLE2 = "Multiweek View Event Changed";
 const DESC = "Multiweek View Event Description";
 
-add_task(function setupModule(module) {
+add_task(async function setupModule(module) {
   let dateFormatter = cal.getDateFormatter();
 
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "multiweek");
   goToDate(controller, 2009, 1, 1);
 
   // Verify date.
   let day = lookup(`
         ${MULTIWEEK_VIEW}/{"class":"mainbox"}/{"class":"monthgrid"}/[0]/{"selected":"true"}/[0]
     `);
   controller.waitFor(() => day.getNode().mDate.icalString == "20090101");
 
   // Create event.
   // Thursday of 2009-01-01 should be the selected box in the first row with default settings.
   let hour = new Date().getHours(); // Remember time at click.
   let eventBox = lookupEventBox("multiweek", CANVAS_BOX, 1, 5);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
     let { getDateTimePicker } = helpersForEditUI(iframe);
 
     let startTimeInput = getDateTimePicker("STARTTIME");
     let startDateInput = getDateTimePicker("STARTDATE");
 
     // Check that the start time is correct.
     // Next full hour except last hour hour of the day.
     let nextHour = hour == 23 ? hour : (hour + 1) % 24;
     let someDate = cal.dtz.now();
     someDate.resetTo(2009, 0, 1, nextHour, 0, 0, cal.dtz.floating);
     event.waitForElement(startTimeInput);
     event.assertValue(startTimeInput, dateFormatter.formatTime(someDate));
     event.assertValue(startDateInput, dateFormatter.formatDateShort(someDate));
 
     // Fill in title, description and calendar.
-    setData(event, iframe, {
+    await setData(event, iframe, {
       title: TITLE1,
       description: DESC,
       calendar: CALENDARNAME,
     });
 
     // save
     event.click(eventid("button-saveandclose"));
   });
 
   // If it was created successfully, it can be opened.
   eventBox = lookupEventBox("multiweek", CANVAS_BOX, 1, 5, null, EVENTPATH);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     // Change title and save changes.
-    setData(event, iframe, { title: TITLE2 });
+    await setData(event, iframe, { title: TITLE2 });
     event.click(eventid("button-saveandclose"));
   });
 
   // Check if name was saved.
   let eventName = lookupEventBox(
     "multiweek",
     CANVAS_BOX,
     1,
--- a/calendar/test/browser/views/browser_taskView.js
+++ b/calendar/test/browser/views/browser_taskView.js
@@ -20,17 +20,17 @@ var controller = mozmill.getMail3PaneCon
 var { eid, lookup, sleep } = helpersForController(controller);
 
 const TITLE = "Task";
 const DESCRIPTION = "1. Do A\n2. Do B";
 const PERCENTCOMPLETE = "50";
 
 // Mozmill doesn't support trees yet, therefore completed checkbox and line-through style are not
 // checked.
-add_task(function setupModule(module) {
+add_task(async function setupModule(module) {
   let CALENDARID = createCalendar(controller, CALENDARNAME);
 
   // paths
   let treeChildren = `${TASK_VIEW}/[1]/id("calendar-task-tree")/{"class":"calendar-task-treechildren"}`;
   let taskTree = TASK_VIEW + '[1]/id("calendar-task-tree")';
   let toolTip = '/id("messengerWindow")/id("calendar-popupset")/id("taskTreeTooltip")';
   let toolTipTable = toolTip + '/{"class":"tooltipBox"}/{"class":"tooltipHeaderTable"}/';
 
@@ -56,24 +56,24 @@ add_task(function setupModule(module) {
 
   // Last added task is automatically selected so verify detail window data.
   controller.assertJSProperty(eid("calendar-task-details-title"), "textContent", TITLE);
 
   // Open added task
   // Double-click on completion checkbox is ignored as opening action, so don't
   // click at immediate left where the checkbox is located.
   controller.doubleClick(lookup(treeChildren), 50, 0);
-  invokeEventDialog(controller, null, (task, iframe) => {
+  await invokeEventDialog(controller, null, async (task, iframe) => {
     let { eid: taskid } = helpersForController(task);
     let { eid: iframeId } = helpersForController(iframe);
 
     // Verify calendar.
     controller.assertValue(iframeId("item-calendar"), CALENDARNAME);
 
-    setData(task, iframe, {
+    await setData(task, iframe, {
       status: "needs-action",
       percent: PERCENTCOMPLETE,
       description: DESCRIPTION,
     });
 
     // save
     task.click(taskid("button-saveandclose"));
   });
--- a/calendar/test/browser/views/browser_weekView.js
+++ b/calendar/test/browser/views/browser_weekView.js
@@ -27,65 +27,65 @@ var { cal } = ChromeUtils.import("resour
 
 var controller = mozmill.getMail3PaneController();
 var { lookup, lookupEventBox } = helpersForController(controller);
 
 var TITLE1 = "Week View Event";
 var TITLE2 = "Week View Event Changed";
 var DESC = "Week View Event Description";
 
-add_task(function testWeekView() {
+add_task(async function testWeekView() {
   let dateFormatter = cal.getDateFormatter();
 
   createCalendar(controller, CALENDARNAME);
   switchToView(controller, "week");
   goToDate(controller, 2009, 1, 1);
 
   // Verify date.
   let day = lookup(`
         ${WEEK_VIEW}/{"class":"mainbox"}/{"class":"headerbox"}/
         {"class":"headerdaybox"}/{"selected":"true"}
     `);
   controller.waitFor(() => day.getNode().mDate.icalString == "20090101");
 
   // Create event at 8 AM.
   // Thursday of 2009-01-01 is 4th with default settings.
   let eventBox = lookupEventBox("week", CANVAS_BOX, null, 5, 8);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
     let { getDateTimePicker } = helpersForEditUI(iframe);
 
     let startTimeInput = getDateTimePicker("STARTTIME");
     let startDateInput = getDateTimePicker("STARTDATE");
 
     // Check that the start time is correct.
     event.waitForElement(startTimeInput);
     let someDate = cal.createDateTime();
     someDate.resetTo(2009, 0, 1, 8, 0, 0, cal.dtz.floating);
     event.assertValue(startTimeInput, dateFormatter.formatTime(someDate));
     event.assertValue(startDateInput, dateFormatter.formatDateShort(someDate));
 
     // Fill in title, description and calendar.
-    setData(event, iframe, {
+    await setData(event, iframe, {
       title: TITLE1,
       description: DESC,
       calendar: CALENDARNAME,
     });
 
     // save
     event.click(eventid("button-saveandclose"));
   });
 
   // If it was created successfully, it can be opened.
   eventBox = lookupEventBox("week", EVENT_BOX, null, 5, null, EVENTPATH);
-  invokeEventDialog(controller, eventBox, (event, iframe) => {
+  await invokeEventDialog(controller, eventBox, async (event, iframe) => {
     let { eid: eventid } = helpersForController(event);
 
     // Change title and save changes.
-    setData(event, iframe, { title: TITLE2 });
+    await setData(event, iframe, { title: TITLE2 });
     event.click(eventid("button-saveandclose"));
   });
 
   // Check if name was saved.
   let eventName = lookupEventBox(
     "week",
     EVENT_BOX,
     null,
--- a/calendar/test/modules/CalendarUtils.jsm
+++ b/calendar/test/modules/CalendarUtils.jsm
@@ -296,17 +296,17 @@ function goToDate(controller, year, mont
 /**
  * Opens the event dialog by clicking on the (optional) box and executing the
  * body. The event dialog must be closed in the body function.
  *
  * @param controller    Main window controller
  * @param clickBox      The box to click on, or null if no box to click on.
  * @param body          The function to execute while the event dialog is open.
  */
-function invokeEventDialog(controller, clickBox, body) {
+async function invokeEventDialog(controller, clickBox, body) {
   if (clickBox) {
     controller.waitForElement(clickBox);
     controller.doubleClick(clickBox, 1, 1);
   }
 
   controller.waitFor(
     () => {
       return mozmill.utils.getWindows("Calendar:EventDialog").length > 0;
@@ -326,17 +326,17 @@ function invokeEventDialog(controller, c
     "event-dialog did not load in time",
     10000
   );
 
   // We can't use a full mozmill controller on an iframe, but we need
   // something for helpersForController.
   let mockIframeController = { window: iframe.contentWindow };
 
-  body(eventController, mockIframeController);
+  await body(eventController, mockIframeController);
 
   // Wait for close.
   controller.waitFor(() => mozmill.utils.getWindows("Calendar:EventDialog").length == 0);
 }
 
 /**
  * Gets the path for an event box.
  *
--- a/calendar/test/modules/ItemEditingHelpers.jsm
+++ b/calendar/test/modules/ItemEditingHelpers.jsm
@@ -11,39 +11,30 @@ this.EXPORTED_SYMBOLS = [
   "PERCENT_COMPLETE_INPUT",
   "DATE_INPUT",
   "TIME_INPUT",
   "REC_DLG_ACCEPT",
   "REC_DLG_DAYS",
   "REC_DLG_UNTIL_INPUT",
   "helpersForEditUI",
   "setData",
-  "setReminderMenulist",
-  "setCategories",
-  "handleAddingAttachment",
-  "setTimezone",
 ];
 
 var elementslib = ChromeUtils.import("resource://testing-common/mozmill/elementslib.jsm");
+var { sendString, synthesizeKey, synthesizeMouseAtCenter } = ChromeUtils.import(
+  "resource://testing-common/mozmill/EventUtils.jsm"
+);
 
-var {
-  helpersForController,
-  menulistSelect,
-  SHORT_SLEEP,
-  TIMEOUT_MODAL_DIALOG,
-} = ChromeUtils.import("resource://testing-common/mozmill/CalendarUtils.jsm");
-var { mark_failure } = ChromeUtils.import(
-  "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
-);
-var { augment_controller, plan_for_modal_dialog, wait_for_modal_dialog } = ChromeUtils.import(
-  "resource://testing-common/mozmill/WindowHelpers.jsm"
+var { helpersForController, menulistSelect } = ChromeUtils.import(
+  "resource://testing-common/mozmill/CalendarUtils.jsm"
 );
 
 var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 var { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+var { BrowserTestUtils } = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
 
 // Lookup paths and path-snippets.
 // These 5 have to be used with itemEditLookup().
 var CATEGORY_LIST = `
     id("event-grid")/id("event-grid-category-color-row")/id("event-grid-category-color-td")
     /id("item-categories")/id("item-categories-popup")
 `;
 var REPEAT_DETAILS = `
@@ -85,16 +76,20 @@ var REC_DLG_DAYS = `
     {"flex":"1"}/[1]/id("period-deck")/id("period-deck-weekly-box")/[1]/id("daypicker-weekday")
 `;
 var REC_DLG_UNTIL_INPUT = `
     /id("calendar-event-dialog-recurrence")/id("recurrence-range-groupbox")/[1]/
     id("recurrence-duration")/id("recurrence-range-until-box")/id("repeat-until-date")/
     {"class":"datepicker-menulist"}/{"class":"menulist-input"}
 `;
 
+function sleep(window) {
+  return new Promise(resolve => window.setTimeout(resolve));
+}
+
 function helpersForEditUI(controller) {
   function selector(sel) {
     return sel.trim().replace(/\n(\s*)/g, "");
   }
 
   let isEvent = cal.item.isEvent(controller.window.calendarItem);
 
   let obj = {
@@ -197,415 +192,430 @@ function helpersForEditUI(controller) {
  *                      completed - Date object for tasks
  *                      percent - percent complete for tasks
  *                      freebusy - free/busy
  *                      attachment.add - url to add
  *                      attachment.remove - Label of url to remove. (without http://)
  *                      attendees.add - eMail of attendees to add, comma separated.
  *                      attendees.remove - eMail of attendees to remove, comma separated.
  */
-function setData(dialog, iframe, data) {
-  let { eid, sleep, replaceText } = helpersForController(dialog);
+async function setData(dialog, iframe, data) {
+  function replaceText(input, text) {
+    synthesizeMouseAtCenter(input.getNode(), {}, iframe.window);
+    synthesizeKey("a", { accelKey: true }, iframe.window);
+    sendString(text, iframe.window);
+  }
+
+  let { eid } = helpersForController(dialog);
   let { eid: iframeid } = helpersForController(iframe);
   let { iframeLookup, getDateTimePicker } = helpersForEditUI(iframe);
 
   let isEvent = cal.item.isEvent(iframe.window.calendarItem);
 
   let startdateInput = getDateTimePicker("STARTDATE");
   let enddateInput = getDateTimePicker("ENDDATE");
   let starttimeInput = getDateTimePicker("STARTTIME");
   let endtimeInput = getDateTimePicker("ENDTIME");
   let completeddateInput = getDateTimePicker("COMPLETEDDATE");
   let percentCompleteInput = iframeLookup(PERCENT_COMPLETE_INPUT);
   let untilDateInput = getDateTimePicker("UNTILDATE");
 
   let dateFormatter = cal.getDateFormatter();
   // Wait for input elements' values to be populated.
-  sleep();
+  await sleep(iframe.window);
 
   // title
-  if (data.title != undefined) {
+  if (data.title !== undefined) {
     let titleInput = iframeid("item-title");
     replaceText(titleInput, data.title);
   }
 
   // location
-  if (data.location != undefined) {
+  if (data.location !== undefined) {
     let locationInput = iframeid("item-location");
     replaceText(locationInput, data.location);
   }
 
   // categories
-  if (data.categories != undefined) {
-    setCategories(dialog, iframe, data.categories);
+  if (data.categories !== undefined) {
+    await setCategories(iframe.window, data.categories);
+    await sleep(iframe.window);
   }
 
   // calendar
-  if (data.calendar != undefined) {
+  if (data.calendar !== undefined) {
     menulistSelect(iframeid("item-calendar"), data.calendar, dialog);
+    await sleep(iframe.window);
   }
 
   // all-day
-  if (data.allday != undefined && isEvent) {
+  if (data.allday !== undefined && isEvent) {
     let checkbox = iframeid("event-all-day");
     if (checkbox.getNode().checked != data.allday) {
-      dialog.click(checkbox);
-      sleep();
+      synthesizeMouseAtCenter(checkbox.getNode(), {}, iframe.window);
     }
-    Assert.equal(checkbox.getNode().checked, data.allday);
   }
 
   // timezonedisplay
   if (data.timezonedisplay !== undefined) {
     let menuitem = eid("options-timezones-menuitem");
     if (menuitem.getNode().getAttribute("checked") != data.timezonedisplay) {
       dialog.click(menuitem);
     }
   }
 
   // timezone
   if (data.timezone !== undefined) {
-    setTimezone(dialog, data.timezone);
+    await setTimezone(dialog.window, iframe.window, data.timezone);
   }
 
   // startdate
-  if (data.startdate != undefined && data.startdate.constructor.name == "Date") {
+  if (data.startdate !== undefined && data.startdate.constructor.name == "Date") {
     let startdate = dateFormatter.formatDateShort(
       cal.dtz.jsDateToDateTime(data.startdate, cal.dtz.floating)
     );
 
     if (!isEvent) {
       dialog.check(iframeid("todo-has-entrydate"), true);
     }
     replaceText(startdateInput, startdate);
   }
 
   // starttime
-  if (data.starttime != undefined && data.starttime.constructor.name == "Date") {
+  if (data.starttime !== undefined && data.starttime.constructor.name == "Date") {
     let starttime = dateFormatter.formatTime(
       cal.dtz.jsDateToDateTime(data.starttime, cal.dtz.floating)
     );
     replaceText(starttimeInput, starttime);
-    sleep();
+    await sleep(iframe.window);
   }
 
   // enddate
-  if (data.enddate != undefined && data.enddate.constructor.name == "Date") {
+  if (data.enddate !== undefined && data.enddate.constructor.name == "Date") {
     let enddate = dateFormatter.formatDateShort(
       cal.dtz.jsDateToDateTime(data.enddate, cal.dtz.floating)
     );
     if (!isEvent) {
       dialog.check(iframeid("todo-has-duedate"), true);
     }
     replaceText(enddateInput, enddate);
   }
 
   // endtime
-  if (data.endtime != undefined && data.endtime.constructor.name == "Date") {
+  if (data.endtime !== undefined && data.endtime.constructor.name == "Date") {
     let endtime = dateFormatter.formatTime(
       cal.dtz.jsDateToDateTime(data.endtime, cal.dtz.floating)
     );
     replaceText(endtimeInput, endtime);
   }
 
   // recurrence
-  if (data.repeat != undefined) {
+  if (data.repeat !== undefined) {
     menulistSelect(iframeid("item-repeat"), data.repeat, dialog);
   }
-  if (data.repeatuntil != undefined && data.repeatuntil.constructor.name == "Date") {
+  if (data.repeatuntil !== undefined && data.repeatuntil.constructor.name == "Date") {
     // Only fill in date, when the Datepicker is visible.
     if (iframeid("repeat-deck").getNode().selectedIndex == 0) {
       let untildate = dateFormatter.formatDateShort(
         cal.dtz.jsDateToDateTime(data.repeatuntil, cal.dtz.floating)
       );
       replaceText(untilDateInput, untildate);
     }
   }
 
   // reminder
-  if (data.reminder != undefined) {
-    setReminderMenulist(dialog, iframeid("item-alarm").getNode(), data.reminder);
+  if (data.reminder !== undefined) {
+    await setReminderMenulist(iframe.window, data.reminder);
   }
 
   // priority
-  if (data.priority != undefined) {
+  if (data.priority !== undefined) {
     dialog.mainMenu.click(`#options-priority-${data.priority}-label`);
   }
 
   // privacy
-  if (data.privacy != undefined) {
+  if (data.privacy !== undefined) {
     dialog.click(eid("button-privacy"));
     dialog.click(eid(`event-privacy-${data.privacy}-menuitem`));
     dialog.click(eid("button-privacy"));
-    sleep();
+    await sleep(iframe.window);
   }
 
   // status
-  if (data.status != undefined) {
+  if (data.status !== undefined) {
     if (isEvent) {
       dialog.mainMenu.click(`#options-status-${data.status}-menuitem`);
     } else {
       menulistSelect(iframeid("todo-status"), data.status.toUpperCase(), dialog);
     }
   }
 
   let currentStatus = iframeid("todo-status").getNode().value;
 
   // completed on
-  if (data.completed != undefined && data.completed.constructor.name == "Date" && !isEvent) {
+  if (data.completed !== undefined && data.completed.constructor.name == "Date" && !isEvent) {
     let completeddate = dateFormatter.formatDateShort(
       cal.dtz.jsDateToDateTime(data.completed, cal.dtz.floating)
     );
     if (currentStatus == "COMPLETED") {
       replaceText(completeddateInput, completeddate);
     }
   }
 
   // percent complete
   if (
-    data.percent != undefined &&
+    data.percent !== undefined &&
     (currentStatus == "NEEDS-ACTION" ||
       currentStatus == "IN-PROCESS" ||
       currentStatus == "COMPLETED")
   ) {
     replaceText(percentCompleteInput, data.percent);
   }
 
   // free/busy
-  if (data.freebusy != undefined) {
+  if (data.freebusy !== undefined) {
     dialog.mainMenu.click(`#options-freebusy-${data.freebusy}-menuitem`);
   }
 
   // description
-  if (data.description != undefined) {
+  if (data.description !== undefined) {
     dialog.click(iframeid("event-grid-tab-description"));
     let descField = iframeLookup(DESCRIPTION_TEXTBOX);
     replaceText(descField, data.description);
   }
 
   // attachment
-  if (data.attachment != undefined) {
-    if (data.attachment.add != undefined) {
-      handleAddingAttachment(dialog, data.attachment.add);
+  if (data.attachment !== undefined) {
+    if (data.attachment.add !== undefined) {
+      await handleAddingAttachment(dialog.window, data.attachment.add);
     }
-    if (data.attachment.remove != undefined) {
+    if (data.attachment.remove !== undefined) {
       dialog.click(iframeid("event-grid-tab-attachments"));
       let attachmentBox = iframeid("attachment-link");
       let attachments = attachmentBox.getNode().children;
       for (let attachment of attachments) {
         if (attachment.tooltipText.includes(data.attachment.remove)) {
           dialog.click(new elementslib.Elem(attachment));
           dialog.keypress(attachmentBox, "VK_DELETE", {});
         }
       }
     }
   }
 
   // attendees
-  if (data.attendees != undefined) {
+  if (data.attendees !== undefined) {
     // Display attendees Tab.
     dialog.click(iframeid("event-grid-tab-attendees"));
     // Make sure no notifications are sent, since handling this dialog is
     // not working when deleting a parent of a recurring event.
     let attendeeCheckbox = iframeid("notify-attendees-checkbox");
     if (!attendeeCheckbox.getNode().disabled) {
       dialog.check(attendeeCheckbox, false);
     }
 
     // add
-    if (data.attendees.add != undefined) {
-      addAttendees(dialog, iframe, data.attendees.add);
+    if (data.attendees.add !== undefined) {
+      await addAttendees(dialog.window, iframe.window, data.attendees.add);
     }
     // delete
-    if (data.attendees.remove != undefined) {
-      deleteAttendees(dialog, iframe, data.attendees.remove);
+    if (data.attendees.remove !== undefined) {
+      await deleteAttendees(iframe.window, data.attendees.remove);
     }
   }
 
-  sleep(SHORT_SLEEP);
+  await sleep(iframe.window);
 }
 
 /**
  * Select an item in the reminder menulist.
  * Custom reminders are not supported.
  *
- * @param controller      Mozmill controller of item-Iframe:
- * @param menulist        The reminder menulist node.
+ * @param iframeWindow    The event dialog iframe.
  * @param id              Identifying string of menuitem id.
  */
-function setReminderMenulist(controller, menulist, id) {
-  let { eid } = helpersForController(controller);
+async function setReminderMenulist(iframeWindow, id) {
+  let iframeDocument = iframeWindow.document;
+  let menulist = iframeDocument.getElementById("item-alarm");
+  let menuitem = iframeDocument.getElementById(`reminder-${id}-menuitem`);
 
-  let menuitem = eid(`reminder-${id}-menuitem`);
-  menulist.click();
-  controller.click(menuitem);
-  controller.waitFor(() => {
-    return menulist.selectedItem.id == `reminder-${id}-menuitem`;
-  });
+  synthesizeMouseAtCenter(menulist, {}, iframeWindow);
+  await BrowserTestUtils.waitForEvent(menulist, "popupshown");
+  synthesizeMouseAtCenter(menuitem, {}, iframeWindow);
+  await BrowserTestUtils.waitForEvent(menulist, "popuphidden");
+  await sleep(iframeWindow);
 }
 
 /**
  * Set the categories in the event-dialog menulist-panel.
  *
- * @param dialog      Mozmill controller of event-dialog.
- * @param iframe      Controller of the iframe of the dialog.
- * @param index       Array containing the categories as strings - leave empty to clear.
+ * @param iframeWindow    The event dialog iframe.
+ * @param categories      Array containing the categories as strings - leave empty to clear.
  */
-function setCategories(dialog, iframe, categories) {
-  let { eid: iframeid } = helpersForController(iframe);
-  let { iframeLookup } = helpersForEditUI(iframe);
-  let categoryMenulist = iframeid("item-categories");
-  let categoryList = iframeLookup(CATEGORY_LIST);
-  dialog.click(categoryMenulist);
-  dialog.waitFor(() => categoryMenulist.getNode().open);
-  if (categoryMenulist.itemCount > -1 && categoryMenulist.itemCount < categories.length) {
-    mark_failure(["more categories than supported by current calendar"]);
-  } else {
-    // Iterate over categories and check if needed.
-    let listItems = categoryList.getNode().children;
-    for (let item of listItems) {
-      let set = false;
-      if (categories.includes(item.label)) {
-        set = true;
-      }
-      if (set && !item.getAttribute("checked")) {
-        item.setAttribute("checked", true);
-      } else if (!set && item.getAttribute("checked")) {
-        item.removeAttribute("checked");
-      }
+async function setCategories(iframeWindow, categories) {
+  let iframeDocument = iframeWindow.document;
+  let menulist = iframeDocument.getElementById("item-categories");
+  let menupopup = iframeDocument.getElementById("item-categories-popup");
+
+  synthesizeMouseAtCenter(menulist, {}, iframeWindow);
+  await BrowserTestUtils.waitForEvent(menupopup, "popupshown");
+
+  // Iterate over categories and check if needed.
+  for (let item of menupopup.children) {
+    if (categories.includes(item.label)) {
+      item.setAttribute("checked", "true");
+    } else {
+      item.removeAttribute("checked");
     }
   }
-  categoryList.getNode().hidePopup();
-  dialog.click(iframeid("item-title"));
-  dialog.sleep();
+
+  let hiddenPromise = BrowserTestUtils.waitForEvent(menupopup, "popuphidden");
+  menupopup.hidePopup();
+  await hiddenPromise;
 }
 
 /**
  * Add an URL attachment.
  *
- * @param controller        Mozmill window controller
- * @param url               URL to be added
+ * @param dialogWindow    The event dialog.
+ * @param url             URL to be added
  */
-function handleAddingAttachment(controller, url) {
-  let { eid } = helpersForController(controller);
-  plan_for_modal_dialog("commonDialog", attachment => {
-    let { lookup: cdlglookup, eid: cdlgid } = helpersForController(attachment);
-    attachment.waitForElement(cdlgid("loginTextbox"));
-    cdlgid("loginTextbox").getNode().value = url;
-    attachment.click(
-      cdlglookup(`
-            /id("commonDialog")/shadow/{"class":"dialog-button-box"}/{"dlgtype":"accept"}
-        `)
+async function handleAddingAttachment(dialogWindow, url) {
+  let dialogDocument = dialogWindow.document;
+
+  synthesizeMouseAtCenter(dialogDocument.getElementById("button-url"), {}, dialogWindow);
+  await BrowserTestUtils.promiseAlertDialog(undefined, undefined, attachmentWindow => {
+    let attachmentDocument = attachmentWindow.document;
+
+    attachmentDocument.getElementById("loginTextbox").value = url;
+    synthesizeMouseAtCenter(
+      attachmentDocument.documentElement.getButton("accept"),
+      {},
+      attachmentWindow
     );
   });
-  controller.click(eid("button-url"));
-
-  wait_for_modal_dialog("commonDialog", TIMEOUT_MODAL_DIALOG);
+  await sleep(dialogWindow);
 }
 
 /**
  * Add attendees to the event.
  *
- * @param dialog            The controller of the Edit Dialog.
- * @param innerFrame        The controller of the item iframe.
+ * @param dialogWindow      The event dialog.
+ * @param iframeWindow      The event dialog iframe.
  * @param attendeesString   Comma separated list of eMail-Addresses to add.
  */
-function addAttendees(dialog, innerFrame, attendeesString) {
-  let { eid: dlgid } = helpersForController(dialog);
+async function addAttendees(dialogWindow, iframeWindow, attendeesString) {
+  let dialogDocument = dialogWindow.document;
 
   let attendees = attendeesString.split(",");
   for (let attendee of attendees) {
-    let calAttendee = innerFrame.window.attendees.find(aAtt => aAtt.id == `mailto:${attendee}`);
+    let calAttendee = iframeWindow.attendees.find(aAtt => aAtt.id == `mailto:${attendee}`);
     // Only add if not already present.
     if (!calAttendee) {
-      plan_for_modal_dialog("Calendar:EventDialog:Attendees", attDialog => {
-        let { lookup: attlookup, eid: attid } = helpersForController(attDialog);
+      synthesizeMouseAtCenter(dialogDocument.getElementById("button-attendees"), {}, dialogWindow);
+      await BrowserTestUtils.promiseAlertDialog(
+        undefined,
+        "chrome://calendar/content/calendar-event-dialog-attendees.xul",
+        async attendeesWindow => {
+          await sleep(attendeesWindow);
+          let attendeesDocument = attendeesWindow.document;
 
-        let input = attid("attendees-list");
-        // As starting point is always the last entered Attendee, we have
-        // to advance to not overwrite it.
-        attDialog.waitFor(
-          () => attDialog.window.document.activeElement.getAttribute("is") == "autocomplete-input"
-        );
-        attDialog.keypress(input, "VK_TAB", {});
-        attDialog.waitFor(
-          () =>
-            attDialog.window.document.activeElement.getAttribute("is") == "autocomplete-input" &&
-            attDialog.window.document.activeElement.getAttribute("value") == null
-        );
-        attDialog.type(input, attendee);
-        attDialog.click(
-          attlookup(`
-                    /id("calendar-event-dialog-attendees-v2")/shadow/
-                    {"class":"dialog-button-box"}/{"dlgtype":"accept"}
-                `)
-        );
-      });
-      dialog.click(dlgid("button-attendees"));
-      wait_for_modal_dialog("Calendar:EventDialog:Attendees", TIMEOUT_MODAL_DIALOG);
+          // As starting point is always the last entered Attendee, we have
+          // to advance to not overwrite it.
+          await sleep(attendeesWindow);
+          Assert.equal(attendeesDocument.activeElement.getAttribute("is"), "autocomplete-input");
+          synthesizeKey("VK_TAB", {}, attendeesWindow);
+          Assert.equal(attendeesDocument.activeElement.getAttribute("is"), "autocomplete-input");
+          Assert.equal(attendeesDocument.activeElement.getAttribute("value"), null);
+          sendString(attendee, attendeesWindow);
+          synthesizeMouseAtCenter(
+            attendeesDocument.documentElement.getButton("accept"),
+            {},
+            attendeesWindow
+          );
+        }
+      );
+      await sleep(iframeWindow);
     }
   }
 }
 
 /**
  * Delete attendees from the event.
  *
- * @param dialog            The controller of the Edit Dialog.
- * @param innerFrame        The controller of the item iframe.
+ * @param iframeWindow      The event dialog iframe.
  * @param attendeesString   Comma separated list of eMail-Addresses to delete.
  */
-function deleteAttendees(event, innerFrame, attendeesString) {
-  let { eid: iframeid } = helpersForController(innerFrame);
-  let { iframeLookup } = helpersForEditUI(innerFrame);
+async function deleteAttendees(iframeWindow, attendeesString) {
+  let iframeDocument = iframeWindow.document;
+  let menupopup = iframeDocument.getElementById("attendee-popup");
 
   // Now delete the attendees.
   let attendees = attendeesString.split(",");
   for (let attendee of attendees) {
-    let attendeeToDelete = iframeLookup(`${ATTENDEES_ROW}/{"attendeeid":"mailto:${attendee}"}`);
+    let attendeeToDelete = iframeDocument.querySelector(
+      `.item-attendees-row [attendeeid="mailto:${attendee}"]`
+    );
     if (attendeeToDelete) {
-      augment_controller(event);
-      event.rightClick(attendeeToDelete);
-      event.click_menus_in_sequence(iframeid("attendee-popup").getNode(), [
-        { id: "attendee-popup-removeattendee-menuitem" },
-      ]);
+      attendeeToDelete.focus();
+      synthesizeMouseAtCenter(attendeeToDelete, { type: "contextmenu" }, iframeWindow);
+      await BrowserTestUtils.waitForEvent(menupopup, "popupshown");
+      synthesizeMouseAtCenter(
+        iframeDocument.getElementById("attendee-popup-removeattendee-menuitem"),
+        {},
+        iframeWindow
+      );
+      await BrowserTestUtils.waitForEvent(menupopup, "popuphidden");
     }
-    event.waitForElementNotPresent(attendeeToDelete);
   }
+  await sleep(iframeWindow);
 }
 
 /**
  * Set the timezone for the item
  *
- * @param event           The controller of the Edit Dialog.
+ * @param dialogWindow    The event dialog.
+ * @param iframeWindow    The event dialog iframe.
  * @param timezone        String identifying the Timezone.
  */
-function setTimezone(event, timezone) {
-  let { eid: eventid } = helpersForController(event);
-  let eventCallback = function(zone, tzcontroller) {
-    let { lookup: tzlookup, xpath: tzpath } = helpersForController(tzcontroller);
+async function setTimezone(dialogWindow, iframeWindow, timezone) {
+  let dialogDocument = dialogWindow.document;
+  let iframeDocument = iframeWindow.document;
 
-    let item = tzpath(`
-            /*[name()='dialog']/*[name()='menulist'][1]/*[name()='menupopup'][1]/
-            *[@value='${zone}']
-        `);
-    tzcontroller.waitForElement(item);
-    tzcontroller.click(item);
-    tzcontroller.click(
-      tzlookup(`
-            /id("calendar-event-dialog-timezone")/shadow/
-            {"class":"dialog-button-box"}/{"dlgtype":"accept"}
-        `)
-    );
-  };
+  let menuitem = dialogDocument.getElementById("options-timezones-menuitem");
+  let label = iframeDocument.getElementById("timezone-starttime");
+  let menupopup = iframeDocument.getElementById("timezone-popup");
+  let customMenuitem = iframeDocument.getElementById("timezone-custom-menuitem");
 
-  if (eventid("timezone-starttime").getNode().collapsed) {
-    let menuitem = eventid("options-timezones-menuitem");
-    event.click(menuitem);
+  if (!BrowserTestUtils.is_visible(label)) {
+    menuitem.click();
+    await sleep(iframeWindow);
   }
 
-  plan_for_modal_dialog("Calendar:EventDialog:Timezone", eventCallback.bind(null, timezone));
-  event.waitForElement(eventid("timezone-starttime"));
-  event.click(eventid("timezone-starttime"));
-  event.click(eventid("timezone-starttime"));
-  event.waitForElement(eventid("timezone-custom-menuitem"));
-  event.click(eventid("timezone-custom-menuitem"));
-  wait_for_modal_dialog("Calendar:EventDialog:Timezone", TIMEOUT_MODAL_DIALOG);
+  Assert.ok(BrowserTestUtils.is_visible(label));
+  synthesizeMouseAtCenter(label, {}, iframeWindow);
+  await BrowserTestUtils.waitForEvent(menupopup, "popupshown");
+  synthesizeMouseAtCenter(customMenuitem, {}, iframeWindow);
+
+  await BrowserTestUtils.promiseAlertDialog(
+    undefined,
+    "chrome://calendar/content/calendar-event-dialog-timezone.xul",
+    async timezoneWindow => {
+      let timezoneDocument = timezoneWindow.document;
+      let timezoneMenulist = timezoneDocument.getElementById("timezone-menulist");
+      let timezoneMenuitem = timezoneMenulist.querySelector(`[value="${timezone}"]`);
+
+      synthesizeMouseAtCenter(timezoneMenulist, {}, timezoneWindow);
+      await BrowserTestUtils.waitForEvent(timezoneMenulist, "popupshown");
+      timezoneMenuitem.scrollIntoView();
+      synthesizeMouseAtCenter(timezoneMenuitem, {}, timezoneWindow);
+      await BrowserTestUtils.waitForEvent(timezoneMenulist, "popuphidden");
+      await sleep(timezoneWindow);
+
+      synthesizeMouseAtCenter(
+        timezoneDocument.documentElement.getButton("accept"),
+        {},
+        timezoneWindow
+      );
+    }
+  );
+
+  await new Promise(resolve => iframeWindow.setTimeout(resolve, 500));
 }
--- a/mail/test/resources/mozmill/mozmill/extension/content/stdlib/EventUtils.jsm
+++ b/mail/test/resources/mozmill/mozmill/extension/content/stdlib/EventUtils.jsm
@@ -1,11 +1,14 @@
 // Export all available functions for Mozmill
 var EXPORTED_SYMBOLS = [
+  "sendChar",
+  "sendString",
   "synthesizeMouse",
+  "synthesizeMouseAtCenter",
   "synthesizeMouseScroll",
   "synthesizeKey",
   "synthesizeMouseExpectEvent",
 ];
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 /**
@@ -33,16 +36,71 @@ function _parseModifiers(aEvent) {
       ? Ci.nsIDOMWindowUtils.MODIFIER_META
       : Ci.nsIDOMWindowUtils.MODIFIER_CONTROL;
   }
 
   return mval;
 }
 
 /**
+ * Send the char aChar to the focused element.  This method handles casing of
+ * chars (sends the right charcode, and sends a shift key for uppercase chars).
+ * No other modifiers are handled at this point.
+ *
+ * For now this method only works for ASCII characters and emulates the shift
+ * key state on US keyboard layout.
+ */
+function sendChar(aChar, aWindow) {
+  var hasShift;
+  // Emulate US keyboard layout for the shiftKey state.
+  switch (aChar) {
+    case "!":
+    case "@":
+    case "#":
+    case "$":
+    case "%":
+    case "^":
+    case "&":
+    case "*":
+    case "(":
+    case ")":
+    case "_":
+    case "+":
+    case "{":
+    case "}":
+    case ":":
+    case '"':
+    case "|":
+    case "<":
+    case ">":
+    case "?":
+      hasShift = true;
+      break;
+    default:
+      hasShift =
+        aChar.toLowerCase() != aChar.toUpperCase() &&
+        aChar == aChar.toUpperCase();
+      break;
+  }
+  synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
+}
+
+/**
+ * Send the string aStr to the focused element.
+ *
+ * For now this method only works for ASCII characters and emulates the shift
+ * key state on US keyboard layout.
+ */
+function sendString(aStr, aWindow) {
+  for (var i = 0; i < aStr.length; ++i) {
+    sendChar(aStr.charAt(i), aWindow);
+  }
+}
+
+/**
  * Synthesize a mouse event on a target. The actual client point is determined
  * by taking the aTarget's client box and offsetting it by aOffsetX and
  * aOffsetY. This allows mouse clicks to be simulated by calling this method.
  *
  * aEvent is an object which may contain the properties:
  *   shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
  *
  * If the type is specified, an mouse event of that type is fired. Otherwise,
@@ -81,16 +139,38 @@ function synthesizeMouse(aTarget, aOffse
         modifiers
       );
       utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers);
     }
   }
 }
 
 /**
+ * Call synthesizeMouse with coordinates at the center of aTarget.
+ *
+ * aEvent is an object which may contain the properties:
+ *   shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
+ *
+ * If the type is specified, an mouse event of that type is fired. Otherwise,
+ * a mousedown followed by a mouse up is performed.
+ *
+ * aWindow is optional, and defaults to the current window object.
+ */
+function synthesizeMouseAtCenter(aTarget, aEvent, aWindow) {
+  var rect = aTarget.getBoundingClientRect();
+  return synthesizeMouse(
+    aTarget,
+    rect.width / 2,
+    rect.height / 2,
+    aEvent,
+    aWindow
+  );
+}
+
+/**
  * Synthesize a mouse scroll event on a target. The actual client point is determined
  * by taking the aTarget's client box and offsetting it by aOffsetX and
  * aOffsetY.
  *
  * aEvent is an object which may contain the properties:
  *   shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, delta, hasPixels
  *
  * If the type is specified, a mouse scroll event of that type is fired. Otherwise,