Bug 1693873 - Part 2: Convert deleteItem(), getItemOfflineFlag() and deleteOfflineItem() to Promises. r=darktrojan
authorLasana Murray <lasana@thunderbird.net>
Fri, 26 Nov 2021 12:36:31 +0200
changeset 34401 245a295b4e97b00a9776646167d2abbba0a5e529
parent 34400 f0f15025a61d85a836db435d4266468a0e8ba9c3
child 34402 8654b90256407145d59b6a439323404645f999cd
push id19411
push usermkmelin@iki.fi
push dateFri, 26 Nov 2021 10:39:40 +0000
treeherdercomm-central@8c44f53caece [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdarktrojan
bugs1693873
Bug 1693873 - Part 2: Convert deleteItem(), getItemOfflineFlag() and deleteOfflineItem() to Promises. r=darktrojan Differential Revision: https://phabricator.services.mozilla.com/D131437
calendar/base/modules/utils/calAsyncUtils.jsm
calendar/base/modules/utils/calItipUtils.jsm
calendar/base/public/calICalendar.idl
calendar/base/public/calIChangeLog.idl
calendar/base/src/CalTransactionManager.jsm
calendar/base/src/calCachedCalendar.js
calendar/providers/caldav/CalDavCalendar.jsm
calendar/providers/caldav/modules/CalDavRequestHandlers.jsm
calendar/providers/composite/CalCompositeCalendar.jsm
calendar/providers/ics/CalICSCalendar.jsm
calendar/providers/memory/CalMemoryCalendar.jsm
calendar/providers/storage/CalStorageCalendar.jsm
calendar/test/unit/providers/head.js
calendar/test/unit/test_alarmservice.js
calendar/test/unit/test_calmgr.js
calendar/test/unit/test_deleted_items.js
calendar/test/unit/test_itip_message_sender.js
calendar/test/unit/test_providers.js
--- a/calendar/base/modules/utils/calAsyncUtils.jsm
+++ b/calendar/base/modules/utils/calAsyncUtils.jsm
@@ -25,37 +25,37 @@ var promisifyProxyHandler = {
     return deferred.promise;
   },
   get(target, name) {
     switch (name) {
       // calICalendar methods
       case "adoptItem":
       case "addItem":
       case "modifyItem":
-      case "deleteItem":
       case "getItems":
         return (...args) => this.promiseOperation(target, name, args);
       // calIOfflineStorage methods
       case "addOfflineItem":
       case "modifyOfflineItem":
-      case "deleteOfflineItem":
-      case "getOfflineItemFlag":
       case "resetItemOfflineFlag": {
         let offline = target.QueryInterface(Ci.calIOfflineStorage);
         return (...args) => this.promiseOperation(offline, name, args);
       }
 
       // Special getAllItems shortcut
       case "getAllItems":
         return () =>
           this.promiseOperation(target, "getItems", [cIC.ITEM_FILTER_ALL_ITEMS, 0, null, null]);
       case "proxyTarget":
         return target;
       case "getItem":
-        return id => target.getItem(id);
+      case "getOfflineItemFlag":
+      case "deleteItem":
+      case "deleteOfflineItem":
+        return arg => target[name](arg);
       default:
         return target[name];
     }
   },
 };
 
 var calasync = {
   /**
--- a/calendar/base/modules/utils/calItipUtils.jsm
+++ b/calendar/base/modules/utils/calItipUtils.jsm
@@ -1678,22 +1678,56 @@ ItipItemFinder.prototype = {
                     operations.push((opListener, partStat, extResponse) =>
                       newItem.calendar.modifyItem(newItem, item, opListener)
                     );
                   }
                   newItem.recurrenceInfo.removeOccurrenceAt(rid);
                 } else if (item.recurrenceId && item.recurrenceId.compare(rid) == 0) {
                   // parentless occurrence to be deleted (future)
                   operations.push((opListener, partStat, extResponse) =>
-                    item.calendar.deleteItem(item, opListener)
+                    item.calendar.deleteItem(item).then(
+                      () =>
+                        opListener.onComplete(
+                          item.calendar,
+                          Cr.NS_OK,
+                          Ci.calIOperationListener.DELETE,
+                          item.id,
+                          item
+                        ),
+                      e =>
+                        opListener.onOperationComplete(
+                          item.calendar,
+                          e.result,
+                          Ci.calIOperationListener.DELETE,
+                          item.id,
+                          e
+                        )
+                    )
                   );
                 }
               } else {
                 operations.push((opListener, partStat, extResponse) =>
-                  item.calendar.deleteItem(item, opListener)
+                  item.calendar.deleteItem(item).then(
+                    () =>
+                      opListener.onComplete(
+                        item.calendar,
+                        Cr.NS_OK,
+                        Ci.calIOperationListener.DELETE,
+                        item.id,
+                        item
+                      ),
+                    e =>
+                      opListener.onOperationComplete(
+                        item.calendar,
+                        e.result,
+                        Ci.calIOperationListener.DELETE,
+                        item.id,
+                        e
+                      )
+                  )
                 );
               }
             }
           }
           break;
         }
         default:
           rc = Cr.NS_ERROR_NOT_IMPLEMENTED;
--- a/calendar/base/public/calICalendar.idl
+++ b/calendar/base/public/calICalendar.idl
@@ -370,36 +370,23 @@ interface calICalendar : nsISupports
    * - aDetail: the calIItemBase corresponding to the newly-updated
    *            immutable version of the modified item
    */
   calIOperation modifyItem(in calIItemBase aNewItem,
                            in calIItemBase aOldItem,
                            in calIOperationListener aListener);
 
   /**
-   * deleteItem takes an item that is to be deleted.  The item is
-   * expected to have an ID that already exists in the calendar; if it
-   * doesn't, or there is no id, onOperationComplete is called with
-   * a status of NS_ERROR_XXXXX.
-   *
-   * @param aItem       item to delete
-   * @param aListener   where to call back the results
-   * @return            optional operation handle to track the operation
+   * Deletes an item. The item is expected to have an ID that already exists in
+   * the calendar.
    *
-   * The results of the operation are reported through an
-   * onOperationComplete call on the listener, with the following
-   * parameters:
-   *
-   * - aOperationType: calIOperationListener::DELETE
-   * - aId: the ID of the deleted item
-   * - aDetail: the calIItemBase corresponding to the immutable version
-   *            of the deleted item
+   * @param aItem            item to delete
+   * @return {Promise<void>} optional operation handle to track the operation
    */
-  calIOperation deleteItem(in calIItemBase aItem,
-                           in calIOperationListener aListener);
+  Promise deleteItem(in calIItemBase aItem);
 
   /**
    * Get a single event.  The event will be typed as one of the subclasses
    * of calIItemBase (whichever concrete type is most appropriate).
    *
    * @param aId                           UID of the event
    * @return {Promise<calIItemBase|null>} A Promise that is resolved with the item if found.
    */
--- a/calendar/base/public/calIChangeLog.idl
+++ b/calendar/base/public/calIChangeLog.idl
@@ -28,28 +28,28 @@ interface calIOfflineStorage : calICalen
      * @param aListener   where to call back the results
      */
     void modifyOfflineItem(in calIItemBase aItem, in calIOperationListener aListener);
 
     /**
      * Mark the item of which the id is passed as parameter as deleted.
      *
      * @param aItem       the item to delete
-     * @param aListener   where to call back the results
+     * @return {Promise<void>}
      */
-    void deleteOfflineItem(in calIItemBase aItem, in calIOperationListener aListener);
+    Promise deleteOfflineItem(in calIItemBase aItem);
 
     /**
      * Retrieves the offline flag for the given item. The flag is returned using the
      * detail parameter of the onOperationComplete function in calIOperationLIstener.
      *
      * @param aItem       the item to reset
-     * @param aListener   where to call back the results
+     * @return {Promise<number>}
      */
-    void getItemOfflineFlag(in calIItemBase aItem, in calIOperationListener aListener);
+    Promise getItemOfflineFlag(in calIItemBase aItem);
 
     /**
      * Remove any offline flag from the item record.
      *
      * @param aItem       the item to reset
      * @param aListener   where to call back the results
      */
     void resetItemOfflineFlag(in calIItemBase aItem, in calIOperationListener aListener);
--- a/calendar/base/src/CalTransactionManager.jsm
+++ b/calendar/base/src/CalTransactionManager.jsm
@@ -98,16 +98,34 @@ CalTransaction.prototype = {
   mCalendar: null,
   mItem: null,
   mOldItem: null,
   mOldCalendar: null,
   mListener: null,
   mIsDoTransaction: false,
   mExtResponse: null,
 
+  _onError(opType, item, error) {
+    if (this.mListener) {
+      this.mListener.onOperationComplete(item.calendar, error.result, opType, item.id, error);
+    }
+  },
+
+  _onSuccess(opType, item) {
+    cal.itip.checkAndSend(
+      opType,
+      item,
+      this.mIsDoTransaction ? this.mOldItem : this.mItem,
+      this.mExtResponse
+    );
+    if (this.mListener) {
+      this.mListener.onOperationComplete(item.calendar, Cr.NS_OK, opType, item.id, item);
+    }
+  },
+
   onOperationComplete(aCalendar, aStatus, aOperationType, aId, aDetail) {
     if (Components.isSuccessCode(aStatus)) {
       cal.itip.checkAndSend(
         aOperationType,
         aDetail,
         this.mIsDoTransaction ? this.mOldItem : this.mItem,
         this.mExtResponse
       );
@@ -148,48 +166,62 @@ CalTransaction.prototype = {
             this
           );
         } else {
           let self = this;
           let addListener = {
             onOperationComplete(aCalendar, aStatus, aOperationType, aId, aDetail) {
               self.onOperationComplete(...arguments);
               if (Components.isSuccessCode(aStatus)) {
-                self.mOldItem.calendar.deleteItem(self.mOldItem, self);
+                self.mOldItem.calendar.deleteItem(self.mOldItem).then(
+                  () => self._onSuccess(Ci.calIOperationListener.DELETE, self.mOldItem),
+                  e => self._onError(Ci.calIOperationListener.DELETE, self.mOldItem, e)
+                );
               }
             },
           };
 
           this.mOldCalendar = this.mOldItem.calendar;
           this.mCalendar.addItem(this.mItem, addListener);
         }
         break;
       case "delete":
-        this.mCalendar.deleteItem(this.mItem, this);
+        this.mCalendar.deleteItem(this.mItem).then(
+          () => this._onSuccess(Ci.calIOperationListener.DELETE, this.mItem),
+          e => this._onError(Ci.calIOperationListener.DELETE, this.mItem, e)
+        );
+
         break;
       default:
         throw new Components.Exception("Invalid action specified", Cr.NS_ERROR_ILLEGAL_VALUE);
     }
   },
 
   undoTransaction() {
     this.mIsDoTransaction = false;
     switch (this.mAction) {
       case "add":
-        this.mCalendar.deleteItem(this.mItem, this);
+        this.mCalendar.deleteItem(this.mItem).then(
+          () => this._onSuccess(Ci.calIOperationListener.DELETE, this.mItem),
+          e => this._onError(Ci.calIOperationListener.DELETE, this.mItem, e)
+        );
+
         break;
       case "modify":
         if (this.mOldItem.calendar.id == this.mItem.calendar.id) {
           this.mCalendar.modifyItem(
             cal.itip.prepareSequence(this.mOldItem, this.mItem),
             this.mItem,
             this
           );
         } else {
-          this.mCalendar.deleteItem(this.mItem, this);
+          this.mCalendar.deleteItem(this.mItem).then(
+            () => this._onSuccess(Ci.calIOperationListener.DELETE, this.mItem),
+            e => this._onError(Ci.calIOperationListener.DELETE, e)
+          );
           this.mOldCalendar.addItem(this.mOldItem, this);
         }
         break;
       case "delete":
         this.mCalendar.addItem(this.mItem, this);
         break;
       default:
         throw new Components.Exception("Invalid action specified", Cr.NS_ERROR_ILLEGAL_VALUE);
--- a/calendar/base/src/calCachedCalendar.js
+++ b/calendar/base/src/calCachedCalendar.js
@@ -428,49 +428,49 @@ calCachedCalendar.prototype = {
                       item.lastModifiedTime.compare(completeListener.modifiedTimes[item.id]) < 0
                     ) {
                       // The item on the server has been modified, ask to overwrite
                       cal.WARN(
                         "[calCachedCalendar] Item '" +
                           item.title +
                           "' at the server seems to be modified recently."
                       );
-                      self.promptOverwrite("modify", item, null, null);
+                      self.promptOverwrite("modify", item, null);
                     } else {
                       // Our item is newer, just modify the item
                       self.modifyOfflineItem(item, null, null);
                     }
                   } else {
                     // The item has been deleted from the server, ask if it should be added again
                     cal.WARN(
                       "[calCachedCalendar] Item '" +
                         item.title +
                         "' has been deleted from the server"
                     );
-                    if (cal.provider.promptOverwrite("modify", item, null, null)) {
+                    if (cal.provider.promptOverwrite("modify", item, null)) {
                       self.adoptOfflineItem(item.clone(), null);
                     }
                   }
                   break;
                 case cICL.OFFLINE_FLAG_DELETED_RECORD:
                   if (item.id in completeListener.modifiedTimes) {
                     // The item seems to exist on the server...
                     if (
                       item.lastModifiedTime.compare(completeListener.modifiedTimes[item.id]) < 0
                     ) {
                       // ...and has been modified on the server. Ask to overwrite
                       cal.WARN(
                         "[calCachedCalendar] Item '" +
                           item.title +
                           "' at the server seems to be modified recently."
                       );
-                      self.promptOverwrite("delete", item, null, null);
+                      self.promptOverwrite("delete", item, null);
                     } else {
                       // ...and has not been modified. Delete it now.
-                      self.deleteOfflineItem(item, null);
+                      self.deleteOfflineItem(item);
                     }
                   } else {
                     // Item has already been deleted from the server, no need to change anything.
                   }
                   break;
               }
             },
             () => {
@@ -504,23 +504,23 @@ calCachedCalendar.prototype = {
     } else if (!this.getProperty("disabled") && this.getProperty("refreshInterval") != "0") {
       // Going online (start replaying changes to the remote calendar).
       // Don't do this if the calendar is disabled or set to manual updates only.
       this.refresh();
     }
   },
 
   // aOldItem is already in the cache
-  promptOverwrite(aMethod, aItem, aListener, aOldItem) {
-    let overwrite = cal.provider.promptOverwrite(aMethod, aItem, aListener, aOldItem);
+  async promptOverwrite(aMethod, aItem, aOldItem) {
+    let overwrite = cal.provider.promptOverwrite(aMethod, aItem);
     if (overwrite) {
       if (aMethod == "modify") {
-        this.modifyOfflineItem(aItem, aOldItem, aListener);
+        this.modifyOfflineItem(aItem, aOldItem, null);
       } else {
-        this.deleteOfflineItem(aItem, aListener);
+        await this.deleteOfflineItem(aItem);
       }
     }
   },
 
   /*
    * Asynchronously performs playback operations of items added, modified, or deleted offline
    *
    * @param aCallback         (optional) The function to be called when playback is complete.
@@ -810,34 +810,16 @@ calCachedCalendar.prototype = {
       },
     };
     this.mCachedCalendar.adoptItem(item, opListener);
   },
 
   modifyItem(newItem, oldItem, listener) {
     let self = this;
 
-    // First of all, we should find out if the item to modify is
-    // already an offline item or not.
-    let flagListener = {
-      onGetResult(calendar, status, itemType, detail, items) {},
-      onOperationComplete(calendar, status, opType, id, offline_flag) {
-        if (
-          offline_flag == cICL.OFFLINE_FLAG_CREATED_RECORD ||
-          offline_flag == cICL.OFFLINE_FLAG_MODIFIED_RECORD
-        ) {
-          // The item is already offline, just modify it in the cache
-          self.modifyOfflineItem(newItem, oldItem, listener);
-        } else {
-          // Not an offline item, attempt to modify using provider
-          self.mUncachedCalendar.modifyItem(newItem, oldItem, cacheListener);
-        }
-      },
-    };
-
     /* Forwarding add/modify/delete to the cached calendar using the calIObserver
      * callbacks would be advantageous, because the uncached provider could implement
      * a true push mechanism firing without being triggered from within the program.
      * But this would mean the uncached provider fires on the passed
      * calIOperationListener, e.g. *before* it fires on calIObservers
      * (because that order is undefined). Firing onOperationComplete before onAddItem et al
      * would result in this facade firing onOperationComplete even though the modification
      * hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
@@ -872,23 +854,35 @@ calCachedCalendar.prototype = {
           // This happens on error, forward the error through the listener
           listener.onOperationComplete(self, status, opType, id, detail);
         }
 
         return Promise.resolve();
       },
     };
 
+    // First of all, we should find out if the item to modify is
+    // already an offline item or not.
     if (this.offline) {
       // If we are offline, don't even try to modify the item
       this.modifyOfflineItem(newItem, oldItem, listener);
     } else {
-      // Otherwise, get the item flags, the listener will further
-      // process the item.
-      this.mCachedCalendar.getItemOfflineFlag(oldItem, flagListener);
+      // Otherwise, get the item flags and further process the item.
+      this.mCachedCalendar.getItemOfflineFlag(oldItem).then(offline_flag => {
+        if (
+          offline_flag == cICL.OFFLINE_FLAG_CREATED_RECORD ||
+          offline_flag == cICL.OFFLINE_FLAG_MODIFIED_RECORD
+        ) {
+          // The item is already offline, just modify it in the cache
+          self.modifyOfflineItem(newItem, oldItem, listener);
+        } else {
+          // Not an offline item, attempt to modify using provider
+          self.mUncachedCalendar.modifyItem(newItem, oldItem, cacheListener);
+        }
+      });
     }
   },
 
   modifyOfflineItem(newItem, oldItem, listener) {
     let self = this;
     let opListener = {
       onGetResult(calendar, status, itemType, detail, items) {
         cal.ASSERT(false, "unexpected!");
@@ -905,91 +899,71 @@ calCachedCalendar.prototype = {
           listener.onOperationComplete(self, status, opType, id, detail);
         }
       },
     };
 
     this.mCachedCalendar.modifyItem(newItem, oldItem, opListener);
   },
 
-  deleteItem(item, listener) {
+  async deleteItem(item) {
     let self = this;
 
     // First of all, we should find out if the item to delete is
     // already an offline item or not.
-    let flagListener = {
-      onGetResult(calendar, status, itemType, detail, items) {},
-      onOperationComplete(calendar, status, opType, id, offline_flag) {
-        if (
-          offline_flag == cICL.OFFLINE_FLAG_CREATED_RECORD ||
-          offline_flag == cICL.OFFLINE_FLAG_MODIFIED_RECORD
-        ) {
-          // The item is already offline, just mark it deleted it in
-          // the cache
-          self.deleteOfflineItem(item, listener);
-        } else {
+    if (this.offline) {
+      // If we are offline, don't even try to delete the item
+      await this.deleteOfflineItem(item);
+    } else {
+      // Otherwise, get the item flags, the listener will further
+      // process the item.
+      let offline_flag = await this.mCachedCalendar.getItemOfflineFlag(item);
+      if (
+        offline_flag == cICL.OFFLINE_FLAG_CREATED_RECORD ||
+        offline_flag == cICL.OFFLINE_FLAG_MODIFIED_RECORD
+      ) {
+        // The item is already offline, just mark it deleted it in
+        // the cache
+        await self.deleteOfflineItem(item);
+      } else {
+        try {
           // Not an offline item, attempt to delete using provider
-          self.mUncachedCalendar.deleteItem(item, cacheListener);
-        }
-      },
-    };
-    // Forwarding add/modify/delete to the cached calendar using the calIObserver
-    // callbacks would be advantageous, because the uncached provider could implement
-    // a true push mechanism firing without being triggered from within the program.
-    // But this would mean the uncached provider fires on the passed
-    // calIOperationListener, e.g. *before* it fires on calIObservers
-    // (because that order is undefined). Firing onOperationComplete before onAddItem et al
-    // would result in this facade firing onOperationComplete even though the modification
-    // hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
-    // Result is that we currently stick to firing onOperationComplete if the cached calendar
-    // has performed the modification, see below:
-    let cacheListener = {
-      onGetResult(calendar, status, itemType, detail, items) {},
-      onOperationComplete(calendar, status, opType, id, detail) {
-        if (isUnavailableCode(status)) {
-          // The item couldn't be deleted at the (remote) location,
-          // this is like being offline. Mark the item deleted in the
-          // cache instead.
-          cal.LOG(
-            "[calCachedCalendar] Calendar " +
-              calendar.name +
-              " is unavailable, deleting item offline"
-          );
-          self.deleteOfflineItem(item, listener);
-        } else if (Components.isSuccessCode(status)) {
+          await self.mUncachedCalendar.deleteItem(item);
+
           // On success, delete the item from the cache
-          self.mCachedCalendar.deleteItem(item, listener);
+          await self.mCachedCalendar.deleteItem(item);
 
           // Also, remove any meta data associated with the item
           try {
             let storage = self.mCachedCalendar.QueryInterface(Ci.calISyncWriteCalendar);
             storage.deleteMetaData(item.id);
           } catch (e) {
             cal.LOG("[calCachedCalendar] Offline storage doesn't support metadata");
           }
-        } else if (listener) {
-          // This happens on error, forward the error through the listener
-          listener.onOperationComplete(self, status, opType, id, detail);
+        } catch (e) {
+          if (isUnavailableCode(e.result)) {
+            // The item couldn't be deleted at the (remote) location,
+            // this is like being offline. Mark the item deleted in the
+            // cache instead.
+            cal.LOG(
+              "[calCachedCalendar] Calendar " +
+                item.calendar.name +
+                " is unavailable, deleting item offline"
+            );
+            await self.deleteOfflineItem(item);
+          }
         }
-      },
-    };
-
-    if (this.offline) {
-      // If we are offline, don't even try to delete the item
-      this.deleteOfflineItem(item, listener);
-    } else {
-      // Otherwise, get the item flags, the listener will further
-      // process the item.
-      this.mCachedCalendar.getItemOfflineFlag(item, flagListener);
+      }
     }
   },
-  deleteOfflineItem(item, listener) {
+
+  async deleteOfflineItem(item) {
     /* We do not delete the item from the cache, as we will need it when reconciling the cache content and the server content. */
     let storage = this.mCachedCalendar.QueryInterface(Ci.calIOfflineStorage);
-    storage.deleteOfflineItem(item, listener);
+    return storage.deleteOfflineItem(item);
   },
 };
 (function() {
   function defineForwards(proto, targetName, functions, getters, gettersAndSetters) {
     function defineForwardGetter(attr) {
       proto.__defineGetter__(attr, function() {
         return this[targetName][attr];
       });
--- a/calendar/providers/caldav/CalDavCalendar.jsm
+++ b/calendar/providers/caldav/CalDavCalendar.jsm
@@ -746,133 +746,130 @@ CalDavCalendar.prototype = {
       }
     );
   },
 
   /**
    * deleteItem(); required by calICalendar.idl
    * the actual deletion is done in doDeleteItem()
    *
-   * @param aItem       item to delete
-   * @param aListener   listener for method completion
+   * @param {calIItemBase} item The item to delete
+   *
+   * @returns {Promise<void>}
    */
-  deleteItem(aItem, aListener) {
-    return this.doDeleteItem(aItem, aListener, false, null, null);
+  async deleteItem(item, aListener) {
+    return this.doDeleteItem(item, false, null, null);
   },
 
   /**
    * Deletes item from CalDAV store.
    *
-   * @param aItem       item to delete
-   * @param aListener   listener for method completion
-   * @param aIgnoreEtag ignore item etag
-   * @param aFromInbox  delete from inbox rather than calendar
-   * @param aUri        uri of item to delete
-   * */
-  doDeleteItem(aItem, aListener, aIgnoreEtag, aFromInbox, aUri) {
-    let notifyListener = (status, detail, pure = false, report = true) => {
+   * @param {calIItemBase}  item       Item to delete.
+   * @param {boolean}       ignoreEtag Ignore item etag.
+   * @param {boolean}       fromInbox  Delete from inbox rather than calendar.
+   * @param {string}        uri        Uri of item to delete.
+   *
+   * @return {Promise<void>}
+   */
+  async doDeleteItem(item, ignoreEtag, fromInbox, uri) {
+    let onError = async (status, detail) => {
       // Still need to visually notify for uncached calendars.
-      if (!this.isCached && !Components.isSuccessCode(status)) {
+      if (!this.isCached) {
         this.reportDavError(Ci.calIErrors.DAV_REMOVE_ERROR, status, detail);
       }
-
-      let method = pure ? "notifyPureOperationComplete" : "notifyOperationComplete";
-      this[method](aListener, status, cIOL.DELETE, aItem.id, detail);
+      this.notifyOperationComplete(null, status, cIOL.DELETE, null, detail);
+      return Promise.reject(new Components.Exception(detail, status));
     };
 
-    if (aItem.id == null) {
-      notifyListener(Cr.NS_ERROR_FAILURE, "ID doesn't exist for deleteItem");
-      return;
+    if (item.id == null) {
+      return onError(Cr.NS_ERROR_FAILURE, "ID doesn't exist for deleteItem");
     }
 
     let eventUri;
-    if (aUri) {
-      eventUri = aUri;
-    } else if (aFromInbox || this.mItemInfoCache[aItem.id].isInboxItem) {
-      eventUri = this.makeUri(this.mItemInfoCache[aItem.id].locationPath, this.mInboxUrl);
+    if (uri) {
+      eventUri = uri;
+    } else if (fromInbox || this.mItemInfoCache[item.id].isInboxItem) {
+      eventUri = this.makeUri(this.mItemInfoCache[item.id].locationPath, this.mInboxUrl);
     } else {
-      eventUri = this.makeUri(this.mItemInfoCache[aItem.id].locationPath);
+      eventUri = this.makeUri(this.mItemInfoCache[item.id].locationPath);
     }
 
     if (eventUri.pathQueryRef == this.calendarUri.pathQueryRef) {
-      notifyListener(
+      return onError(
         Cr.NS_ERROR_FAILURE,
         "eventUri and calendarUri paths are the same, will not go on to delete entire calendar"
       );
-      return;
     }
 
     if (this.verboseLogging()) {
       cal.LOG("CalDAV: Deleting " + eventUri.spec);
     }
 
-    let sendEtag = aIgnoreEtag ? null : this.mItemInfoCache[aItem.id].etag;
+    let sendEtag = ignoreEtag ? null : this.mItemInfoCache[item.id].etag;
     let request = new CalDavDeleteItemRequest(this.session, this, eventUri, sendEtag);
 
-    request.commit().then(
-      response => {
-        if (response.ok) {
-          if (!aFromInbox) {
-            let decodedPath = this.ensureDecodedPath(eventUri.pathQueryRef);
-            delete this.mHrefIndex[decodedPath];
-            delete this.mItemInfoCache[aItem.id];
-            cal.LOG("CalDAV: Item deleted successfully from calendar " + this.name);
+    let response;
+    try {
+      response = await request.commit();
+    } catch (e) {
+      return onError(Cr.NS_ERROR_NOT_AVAILABLE, "Error preparing http channel");
+    }
+
+    if (response.ok) {
+      if (!fromInbox) {
+        let decodedPath = this.ensureDecodedPath(eventUri.pathQueryRef);
+        delete this.mHrefIndex[decodedPath];
+        delete this.mItemInfoCache[item.id];
+        cal.LOG("CalDAV: Item deleted successfully from calendar " + this.name);
 
-            if (this.isCached) {
-              notifyListener(Cr.NS_OK, aItem);
-            } else {
-              // If the calendar is not cached, we need to remove
-              // the item from our memory calendar now. The
-              // listeners will be notified there.
-              this.mOfflineStorage.deleteItem(aItem, aListener);
-            }
-          }
-        } else if (response.conflict) {
-          // item has either been modified or deleted by someone else check to see which
-          cal.LOG("CalDAV: Item has been modified on server, checking if it has been deleted");
-          let headrequest = new CalDavGenericRequest(this.session, this, "HEAD", eventUri);
+        if (this.isCached) {
+          this.notifyOperationComplete(null, Cr.NS_OK, cIOL.DELETE, null, null);
+          return null;
+        }
+        // If the calendar is not cached, we need to remove
+        // the item from our memory calendar now. The
+        // listeners will be notified there.
+        return this.mOfflineStorage.deleteItem(item);
+      }
+      return null;
+    } else if (response.conflict) {
+      // item has either been modified or deleted by someone else check to see which
+      cal.LOG("CalDAV: Item has been modified on server, checking if it has been deleted");
+      let headRequest = new CalDavGenericRequest(this.session, this, "HEAD", eventUri);
+      let headResponse = await headRequest.commit();
 
-          return headrequest.commit().then(headresponse => {
-            if (headresponse.notFound) {
-              // Nothing to do. Someone else has already deleted it
-              notifyListener(Cr.NS_OK, aItem, true);
-            } else if (headresponse.serverError) {
-              notifyListener(
-                Cr.NS_ERROR_NOT_AVAILABLE,
-                "Server Replied with " + headresponse.status,
-                true
-              );
-            } else if (headresponse.status) {
-              // The item still exists. We need to ask the user if he
-              // really wants to delete the item. Remember, we only
-              // made this request since the actual delete gave 409/412
-              this.promptOverwrite(CALDAV_DELETE_ITEM, aItem, aListener, null);
-            }
-          });
-        } else if (response.serverError) {
-          notifyListener(Cr.NS_ERROR_NOT_AVAILABLE, "Server Replied with " + response.status);
-        } else if (response.status) {
-          cal.ERROR(
-            "CalDAV: Unexpected status deleting item from " +
-              this.name +
-              ": " +
-              response.status +
-              "\n" +
-              "uri: " +
-              eventUri.spec
-          );
-
-          notifyListener(Cr.NS_ERROR_FAILURE, "Server Replied with " + response.status);
-        }
+      if (headResponse.notFound) {
+        // Nothing to do. Someone else has already deleted it
+        this.notifyPureOperationComplete(null, Cr.NS_OK, cIOL.DELETE, null, null);
         return null;
-      },
-      () => {
-        notifyListener(Cr.NS_ERROR_NOT_AVAILABLE, "Error preparing http channel");
+      } else if (headResponse.serverError) {
+        return onError(Cr.NS_ERROR_NOT_AVAILABLE, "Server Replied with " + headResponse.status);
+      } else if (headResponse.status) {
+        // The item still exists. We need to ask the user if he
+        // really wants to delete the item. Remember, we only
+        // made this request since the actual delete gave 409/412
+        let item = await this.getItem(item.id);
+        return cal.provider.promptOverwrite(CALDAV_DELETE_ITEM, item)
+          ? this.doDeleteItem(item, true, false, null)
+          : null;
       }
-    );
+    } else if (response.serverError) {
+      return onError(Cr.NS_ERROR_NOT_AVAILABLE, "Server Replied with " + response.status);
+    } else if (response.status) {
+      cal.ERROR(
+        "CalDAV: Unexpected status deleting item from " +
+          this.name +
+          ": " +
+          response.status +
+          "\n" +
+          "uri: " +
+          eventUri.spec
+      );
+    }
+    return onError(Cr.NS_ERROR_FAILURE, "Server Replied with status " + response.status);
   },
 
   /**
    * Add an item to the target calendar
    *
    * @param path      Item path MUST NOT BE ENCODED
    * @param calData   iCalendar string representation of the item
    * @param aUri      Base URI of the request
@@ -1009,28 +1006,26 @@ CalDavCalendar.prototype = {
   },
 
   /**
    * Deletes an item from the target calendar
    *
    * @param path Path of the item to delete, must not be encoded
    */
   async deleteTargetCalendarItem(path) {
-    let pcal = cal.async.promisifyCalendar(this.mOfflineStorage);
-
     let foundItem = await this.mOfflineStorage.getItem(this.mHrefIndex[path]);
     let wasInboxItem = this.mItemInfoCache[foundItem.id].isInboxItem;
     if ((wasInboxItem && this.isInbox(path)) || (wasInboxItem === false && !this.isInbox(path))) {
       cal.LOG("CalDAV: deleting item: " + path + ", uid: " + foundItem.id);
       delete this.mHrefIndex[path];
       delete this.mItemInfoCache[foundItem.id];
       if (this.isCached) {
         this.mOfflineStorage.deleteMetaData(foundItem.id);
       }
-      await pcal.deleteItem(foundItem);
+      await this.mOfflineStorage.deleteItem(foundItem);
     }
   },
 
   /**
    * Perform tasks required after updating items in the calendar such as
    * notifying the observers and listeners
    *
    * @param aChangeLogListener    Change log listener
--- a/calendar/providers/caldav/modules/CalDavRequestHandlers.jsm
+++ b/calendar/providers/caldav/modules/CalDavRequestHandlers.jsm
@@ -189,28 +189,27 @@ class CalDavEtagsHandler extends XMLResp
       for (let path in this.calendar.mHrefIndex) {
         if (path in this.itemsReported || path.substr(0, this.baseUri.length) == this.baseUri) {
           // If the item is also on the server, check the next.
           continue;
         }
         // If an item has been deleted from the server, delete it here too.
         // Since the target calendar's operations are synchronous, we can
         // safely set variables from this function.
-        let pcal = cal.async.promisifyCalendar(this.calendar.mOfflineStorage);
         let foundItem = await this.calendar.mOfflineStorage.getItem(this.calendar.mHrefIndex[path]);
 
         if (foundItem) {
           let wasInboxItem = this.calendar.mItemInfoCache[foundItem.id].isInboxItem;
           if (
             (wasInboxItem && this.calendar.isInbox(this.baseUri.spec)) ||
             (wasInboxItem === false && !this.calendar.isInbox(this.baseUri.spec))
           ) {
             cal.LOG("Deleting local href: " + path);
             delete this.calendar.mHrefIndex[path];
-            await pcal.deleteItem(foundItem);
+            await this.calendar.mOfflineStorage.deleteItem(foundItem);
             needsRefresh = true;
           }
         }
       }
     } finally {
       this.calendar.superCalendar.endBatch();
     }
 
--- a/calendar/providers/composite/CalCompositeCalendar.jsm
+++ b/calendar/providers/composite/CalCompositeCalendar.jsm
@@ -309,22 +309,22 @@ CalCompositeCalendar.prototype = {
   // void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
   modifyItem(aNewItem, aOldItem, aListener) {
     cal.ASSERT(aNewItem.calendar, "Composite can't modify item with null calendar", true);
     cal.ASSERT(aNewItem.calendar != this, "Composite can't modify item with this calendar", true);
 
     return aNewItem.calendar.modifyItem(aNewItem, aOldItem, aListener);
   },
 
-  // void deleteItem( in string id, in calIOperationListener aListener );
-  deleteItem(aItem, aListener) {
+  // Promise<void> deleteItem(in calIItemBase aItem);
+  async deleteItem(aItem) {
     cal.ASSERT(aItem.calendar, "Composite can't delete item with null calendar", true);
     cal.ASSERT(aItem.calendar != this, "Composite can't delete item with this calendar", true);
 
-    return aItem.calendar.deleteItem(aItem, aListener);
+    return aItem.calendar.deleteItem(aItem);
   },
 
   // void addItem( in calIItemBase aItem, in calIOperationListener aListener );
   addItem(aItem, aListener) {
     return this.mDefaultCalendar.addItem(aItem, aListener);
   },
 
   // Promise<calIItemBase|null> getItem(in string aId);
--- a/calendar/providers/ics/CalICSCalendar.jsm
+++ b/calendar/providers/ics/CalICSCalendar.jsm
@@ -473,26 +473,35 @@ CalICSCalendar.prototype = {
       oldItem: aOldItem,
       newItem: aNewItem,
       listener: aListener,
     });
     this.processQueue();
     this.endBatch();
   },
 
-  deleteItem(aItem, aListener) {
-    if (this.readOnly) {
-      throw calIErrors.CAL_IS_READONLY;
-    }
-    this.queue.push({
-      action: "delete",
-      item: aItem,
-      listener: aListener,
+  /**
+   * Delete the provided item.
+   *
+   * @param {calIItemBase} item
+   * @returns {Promise<void>}
+   */
+  async deleteItem(item) {
+    return new Promise((resolve, reject) => {
+      if (this.readOnly) {
+        reject(calIErrors.CAL_IS_READONLY);
+        return;
+      }
+      this.queue.push({
+        action: "delete",
+        item,
+        listener: resolve,
+      });
+      this.processQueue();
     });
-    this.processQueue();
   },
 
   // Promise<calIItemBase|null> getItem(in string id);
   async getItem(aId, aListener) {
     return new Promise(resolve => {
       this.queue.push({
         action: "get_item",
         id: aId,
@@ -541,17 +550,17 @@ CalICSCalendar.prototype = {
           writeICS = true;
           break;
         case "modify":
           this.mMemoryCalendar.modifyItem(a.newItem, a.oldItem, new modListener(a));
           this.mModificationActions.push(a);
           writeICS = true;
           break;
         case "delete":
-          this.mMemoryCalendar.deleteItem(a.item, new modListener(a));
+          this.mMemoryCalendar.deleteItem(a.item);
           this.mModificationActions.push(a);
           writeICS = true;
           break;
         case "get_item":
           this.mMemoryCalendar.getItem(a.id).then(a.listener);
           break;
         case "get_items":
           this.mMemoryCalendar.getItems(
@@ -591,20 +600,22 @@ CalICSCalendar.prototype = {
   lock() {
     this.locked = true;
   },
 
   unlock(errCode) {
     cal.ASSERT(this.locked, "unexpected!");
 
     this.mModificationActions.forEach(action => {
-      let args = action.opCompleteArgs;
-      cal.ASSERT(args, "missing onOperationComplete call!");
       let listener = action.listener;
-      if (listener) {
+      if (typeof listener == "function") {
+        listener();
+      } else if (listener) {
+        let args = action.opCompleteArgs;
+        cal.ASSERT(args, "missing onOperationComplete call!");
         if (Components.isSuccessCode(args[1]) && errCode && !Components.isSuccessCode(errCode)) {
           listener.onOperationComplete(args[0], errCode, args[2], args[3], null);
         } else {
           listener.onOperationComplete(...args);
         }
       }
     });
     this.mModificationActions = [];
--- a/calendar/providers/memory/CalMemoryCalendar.jsm
+++ b/calendar/providers/memory/CalMemoryCalendar.jsm
@@ -276,68 +276,54 @@ CalMemoryCalendar.prototype = {
       modifiedItem
     );
 
     // notify observers
     this.mObservers.notify("onModifyItem", [modifiedItem, aOldItem]);
     return null;
   },
 
-  // void deleteItem( in calIItemBase aItem, in calIOperationListener aListener );
-  deleteItem(aItem, aListener) {
-    if (this.readOnly) {
+  // Promise<void> deleteItem(in calIItemBase item);
+  async deleteItem(item) {
+    let onError = async (message, exception) => {
       this.notifyOperationComplete(
-        aListener,
-        Ci.calIErrors.CAL_IS_READONLY,
+        null,
+        exception,
         Ci.calIOperationListener.DELETE,
-        aItem.id,
-        "Calendar is readonly"
+        item.id,
+        message
       );
-      return;
+      return Promise.reject(new Components.Exception(message, exception));
+    };
+
+    if (this.readOnly) {
+      return onError("Calendar is readonly", Ci.calIErrors.CAL_IS_READONLY);
     }
-    if (aItem.id == null) {
-      this.notifyOperationComplete(
-        aListener,
-        Cr.NS_ERROR_FAILURE,
-        Ci.calIOperationListener.DELETE,
-        aItem.id,
-        "ID is null in deleteItem"
-      );
-      return;
+
+    if (item.id == null) {
+      return onError("ID is null in deleteItem", Cr.NS_ERROR_FAILURE);
     }
 
     let oldItem;
     if (this.relaxedMode) {
-      oldItem = aItem;
+      oldItem = item;
     } else {
-      oldItem = this.mItems[aItem.id];
-      if (oldItem.generation != aItem.generation) {
-        this.notifyOperationComplete(
-          aListener,
-          Cr.NS_ERROR_FAILURE,
-          Ci.calIOperationListener.DELETE,
-          aItem.id,
-          "generation mismatch in deleteItem"
-        );
-        return;
+      oldItem = this.mItems[item.id];
+      if (oldItem.generation != item.generation) {
+        return onError("generation mismatch in deleteItem", Cr.NS_ERROR_FAILURE);
       }
     }
 
-    delete this.mItems[aItem.id];
-    this.mMetaData.delete(aItem.id);
+    delete this.mItems[item.id];
+    this.mMetaData.delete(item.id);
 
-    this.notifyOperationComplete(
-      aListener,
-      Cr.NS_OK,
-      Ci.calIOperationListener.DELETE,
-      aItem.id,
-      aItem
-    );
+    this.notifyOperationComplete(null, Cr.NS_OK, Ci.calIOperationListener.DELETE, item.id, item);
     // notify observers
     this.mObservers.notify("onDeleteItem", [oldItem]);
+    return null;
   },
 
   // Promise<calIItemBase|null> getItem(in string id);
   async getItem(aId) {
     return this.mItems[aId] || null;
   },
 
   // void getItems( in unsigned long aItemFilter, in unsigned long aCount,
@@ -504,25 +490,18 @@ CalMemoryCalendar.prototype = {
         this.notifyOperationComplete(aListener, Cr.NS_OK, Ci.calIOperationListener.GET, null, null);
       }
     );
   },
 
   //
   // calIOfflineStorage interface
   //
-  addOfflineItem(aItem, aListener) {
+  async addOfflineItem(aItem) {
     this.mOfflineFlags[aItem.id] = cICL.OFFLINE_FLAG_CREATED_RECORD;
-    this.notifyOperationComplete(
-      aListener,
-      Cr.NS_OK,
-      Ci.calIOperationListener.ADD,
-      aItem.id,
-      aItem
-    );
   },
 
   modifyOfflineItem(aItem, aListener) {
     let oldFlag = this.mOfflineFlags[aItem.id];
     if (
       oldFlag != cICL.OFFLINE_FLAG_CREATED_RECORD &&
       oldFlag != cICL.OFFLINE_FLAG_DELETED_RECORD
     ) {
@@ -533,39 +512,31 @@ CalMemoryCalendar.prototype = {
       aListener,
       Cr.NS_OK,
       Ci.calIOperationListener.MODIFY,
       aItem.id,
       aItem
     );
   },
 
-  deleteOfflineItem(aItem, aListener) {
+  async deleteOfflineItem(aItem) {
     let oldFlag = this.mOfflineFlags[aItem.id];
     if (oldFlag == cICL.OFFLINE_FLAG_CREATED_RECORD) {
       delete this.mItems[aItem.id];
       delete this.mOfflineFlags[aItem.id];
     } else {
       this.mOfflineFlags[aItem.id] = cICL.OFFLINE_FLAG_DELETED_RECORD;
     }
 
-    this.notifyOperationComplete(
-      aListener,
-      Cr.NS_OK,
-      Ci.calIOperationListener.DELETE,
-      aItem.id,
-      aItem
-    );
     // notify observers
     this.observers.notify("onDeleteItem", [aItem]);
   },
 
-  getItemOfflineFlag(aItem, aListener) {
-    let flag = aItem && aItem.id in this.mOfflineFlags ? this.mOfflineFlags[aItem.id] : null;
-    this.notifyOperationComplete(aListener, Cr.NS_OK, Ci.calIOperationListener.GET, null, flag);
+  async getItemOfflineFlag(aItem) {
+    return aItem && aItem.id in this.mOfflineFlags ? this.mOfflineFlags[aItem.id] : null;
   },
 
   resetItemOfflineFlag(aItem, aListener) {
     delete this.mOfflineFlags[aItem.id];
     this.notifyOperationComplete(
       aListener,
       Cr.NS_OK,
       Ci.calIOperationListener.MODIFY,
--- a/calendar/providers/storage/CalStorageCalendar.jsm
+++ b/calendar/providers/storage/CalStorageCalendar.jsm
@@ -245,27 +245,21 @@ CalStorageCalendar.prototype = {
 
     // notify observers
     this.observers.notify("onAddItem", [aItem]);
   },
 
   // void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
   // Actually uses doModifyItem
   modifyItem(aNewItem, aOldItem, aListener) {
-    let self = this;
-
     // HACK Just modifying the item would clear the offline flag, we need to
     // retrieve the flag and pass it to the real modify function.
-    let offlineJournalFlagListener = {
-      onGetResult(calendar, status, opType, id, detail) {},
-      onOperationComplete(opcalendar, status, opType, id, offlineFlag) {
-        self.doModifyItem(aNewItem, aOldItem, aListener, offlineFlag);
-      },
-    };
-    this.getItemOfflineFlag(aOldItem, offlineJournalFlagListener);
+    this.getItemOfflineFlag(aOldItem).then(offlineFlag =>
+      this.doModifyItem(aNewItem, aOldItem, aListener, offlineFlag)
+    );
   },
 
   async doModifyItem(aNewItem, aOldItem, aListener, offlineFlag) {
     let oldOfflineFlag = offlineFlag;
     if (this.readOnly) {
       this.notifyOperationComplete(
         aListener,
         Ci.calIErrors.CAL_IS_READONLY,
@@ -363,58 +357,51 @@ CalStorageCalendar.prototype = {
       modifiedItem
     );
 
     // notify observers
     this.observers.notify("onModifyItem", [modifiedItem, aOldItem]);
     return null;
   },
 
-  // void deleteItem( in string id, in calIOperationListener aListener );
-  async deleteItem(aItem, aListener) {
-    if (this.readOnly) {
+  // Promise<void> deleteItem(in calIItemBase item)
+  async deleteItem(item) {
+    let onError = async (message, exception) => {
       this.notifyOperationComplete(
-        aListener,
-        Ci.calIErrors.CAL_IS_READONLY,
-        Ci.calIOperationListener.DELETE,
         null,
-        "Calendar is readonly"
+        exception,
+        Ci.calIOperationListener.DELETE,
+        item.id,
+        message
       );
-      return;
-    }
-    if (aItem.parentItem != aItem) {
-      aItem.parentItem.recurrenceInfo.removeExceptionFor(aItem.recurrenceId);
-      // xxx todo: would we want to support this case? Removing an occurrence currently results
-      //           in a modifyItem(parent)
-      return;
+      return Promise.reject(new Components.Exception(message, exception));
+    };
+
+    if (this.readOnly) {
+      return onError("Calendar is readonly", Ci.calIErrors.CAL_IS_READONLY);
     }
 
-    if (aItem.id == null) {
-      this.notifyOperationComplete(
-        aListener,
-        Cr.NS_ERROR_FAILURE,
-        Ci.calIOperationListener.DELETE,
-        null,
-        "ID is null for deleteItem"
-      );
-      return;
+    if (item.parentItem != item) {
+      item.parentItem.recurrenceInfo.removeExceptionFor(item.recurrenceId);
+      // xxx todo: would we want to support this case? Removing an occurrence currently results
+      //           in a modifyItem(parent)
+      return null;
     }
 
-    await this.mItemModel.deleteItemById(aItem.id);
+    if (item.id == null) {
+      return onError("ID is null for deleteItem", Cr.NS_ERROR_FAILURE);
+    }
 
-    this.notifyOperationComplete(
-      aListener,
-      Cr.NS_OK,
-      Ci.calIOperationListener.DELETE,
-      aItem.id,
-      aItem
-    );
+    await this.mItemModel.deleteItemById(item.id);
+
+    this.notifyOperationComplete(null, Cr.NS_OK, Ci.calIOperationListener.DELETE, item.id, item);
 
     // notify observers
-    this.observers.notify("onDeleteItem", [aItem]);
+    this.observers.notify("onDeleteItem", [item]);
+    return null;
   },
 
   // Promise<calIItemBase|null> getItem(in string id);
   async getItem(aId) {
     return this.mItemModel.getItemById(aId);
   },
 
   // void getItems( in unsigned long aItemFilter, in unsigned long aCount,
@@ -453,50 +440,27 @@ CalStorageCalendar.prototype = {
     await this.mItemModel.getItems(query, (items, queuedItemsIID) => {
       aListener.onGetResult(this.superCalendar, Cr.NS_OK, queuedItemsIID, null, items);
     });
 
     // and finish
     this.notifyOperationComplete(aListener, Cr.NS_OK, Ci.calIOperationListener.GET, null, null);
   },
 
-  async getItemOfflineFlag(aItem, aListener) {
-    let flag = null;
-    if (aItem) {
-      try {
-        flag = await this.mOfflineModel.getItemOfflineFlag(aItem);
-      } catch (ex) {
-        aListener.onOperationComplete(
-          this,
-          ex.result,
-          Ci.calIOperationListener.GET,
-          aItem.id,
-          aItem
-        );
-        return;
-      }
-    }
-
+  async getItemOfflineFlag(aItem) {
     // It is possible that aItem can be null, flag provided should be null in this case
-    aListener.onOperationComplete(this, Cr.NS_OK, Ci.calIOperationListener.GET, aItem, flag);
+    return aItem ? this.mOfflineModel.getItemOfflineFlag(aItem) : null;
   },
 
   //
   // calIOfflineStorage interface
   //
-  async addOfflineItem(aItem, aListener) {
+  async addOfflineItem(aItem) {
     let newOfflineJournalFlag = cICL.OFFLINE_FLAG_CREATED_RECORD;
     await this.mOfflineModel.setOfflineJournalFlag(aItem, newOfflineJournalFlag);
-    this.notifyOperationComplete(
-      aListener,
-      Cr.NS_OK,
-      Ci.calIOperationListener.ADD,
-      aItem.id,
-      aItem
-    );
   },
 
   modifyOfflineItem(aItem, aListener) {
     let self = this;
     let opListener = {
       QueryInterface: ChromeUtils.generateQI(["calIOperationListener"]),
       onGetResult(calendar, status, itemType, detail, items) {},
       async onOperationComplete(calendar, status, opType, id, oldOfflineJournalFlag) {
@@ -516,45 +480,31 @@ CalStorageCalendar.prototype = {
           aItem.id,
           aItem
         );
       },
     };
     this.getItemOfflineFlag(aItem, opListener);
   },
 
-  deleteOfflineItem(aItem, aListener) {
-    let self = this;
-    let opListener = {
-      QueryInterface: ChromeUtils.generateQI(["calIOperationListener"]),
-      onGetResult(calendar, status, itemType, detail, items) {},
-      async onOperationComplete(calendar, status, opType, id, oldOfflineJournalFlag) {
-        if (oldOfflineJournalFlag) {
-          // Delete item if flag is c
-          if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_CREATED_RECORD) {
-            await self.mItemModel.deleteItemById(aItem.id);
-          } else if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_MODIFIED_RECORD) {
-            await self.mOfflineModel.setOfflineJournalFlag(aItem, cICL.OFFLINE_FLAG_DELETED_RECORD);
-          }
-        } else {
-          await self.mOfflineModel.setOfflineJournalFlag(aItem, cICL.OFFLINE_FLAG_DELETED_RECORD);
-        }
+  async deleteOfflineItem(aItem) {
+    let oldOfflineJournalFlag = await this.getItemOfflineFlag(aItem);
+    if (oldOfflineJournalFlag) {
+      // Delete item if flag is set
+      if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_CREATED_RECORD) {
+        await this.mItemModel.deleteItemById(aItem.id);
+      } else if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_MODIFIED_RECORD) {
+        await this.mOfflineModel.setOfflineJournalFlag(aItem, cICL.OFFLINE_FLAG_DELETED_RECORD);
+      }
+    } else {
+      await this.mOfflineModel.setOfflineJournalFlag(aItem, cICL.OFFLINE_FLAG_DELETED_RECORD);
+    }
 
-        self.notifyOperationComplete(
-          aListener,
-          Cr.NS_OK,
-          Ci.calIOperationListener.DELETE,
-          aItem.id,
-          aItem
-        );
-        // notify observers
-        self.observers.notify("onDeleteItem", [aItem]);
-      },
-    };
-    this.getItemOfflineFlag(aItem, opListener);
+    // notify observers
+    this.observers.notify("onDeleteItem", [aItem]);
   },
 
   async resetItemOfflineFlag(aItem, aListener) {
     await this.mOfflineModel.setOfflineJournalFlag(aItem, null);
     this.notifyOperationComplete(
       aListener,
       Cr.NS_OK,
       Ci.calIOperationListener.MODIFY,
--- a/calendar/test/unit/providers/head.js
+++ b/calendar/test/unit/providers/head.js
@@ -142,11 +142,11 @@ async function runModifyItem(calendar) {
  * Deletes the event from runAddItem.
  *
  * @param {calICalendar} calendar
  */
 async function runDeleteItem(calendar) {
   let event = await calendar.getItem("6b7dd6f6-d6f0-4e93-a953-bb5473c4c47a");
 
   calendarObserver._onDeleteItemPromise = PromiseUtils.defer();
-  calendar.deleteItem(event, null);
+  calendar.deleteItem(event);
   await calendarObserver._onDeleteItemPromise.promise;
 }
--- a/calendar/test/unit/test_alarmservice.js
+++ b/calendar/test/unit/test_alarmservice.js
@@ -407,18 +407,18 @@ function doDeleteItemTest(aCalendar) {
   item2.title = "doDeleteItemTest item2 Test 1";
   aCalendar.addItem(item, null);
   aCalendar.addItem(item2, null);
   alarmObserver.expectResult(aCalendar, item, alarm, EXPECT_FIRED);
   alarmObserver.expectResult(aCalendar, item2, alarm2, EXPECT_TIMER);
   alarmObserver.checkExpected();
 
   // item deletion should clear the fired alarm and timer
-  aCalendar.deleteItem(item, null);
-  aCalendar.deleteItem(item2, null);
+  aCalendar.deleteItem(item);
+  aCalendar.deleteItem(item2);
   alarmObserver.expectResult(aCalendar, item, alarm, EXPECT_NONE);
   alarmObserver.expectResult(aCalendar, item2, alarm2, EXPECT_NONE);
   alarmObserver.checkExpected("doDeleteItemTest, cleared fired alarm and timer");
 }
 
 function doAcknowledgeTest(aCalendar) {
   alarmObserver.clear();
   let item, alarm;
@@ -563,17 +563,17 @@ function test_notificationTimers() {
     alarmObserver.service.removeFiredNotificationTimer(item);
     // Should have three notification timers.
     matchTimers(alarmObserver.service.mNotificationTimerMap[item.calendar.id][item.hashId], [
       3600,
       5400,
       7320,
     ]);
 
-    memory.deleteItem(item, null);
+    memory.deleteItem(item);
     equal(
       alarmObserver.service.mNotificationTimerMap[item.calendar.id],
       undefined,
       "notification timers should be removed"
     );
 
     Services.prefs.clearUserPref("calendar.notifications.times");
   });
--- a/calendar/test/unit/test_calmgr.js
+++ b/calendar/test/unit/test_calmgr.js
@@ -266,35 +266,35 @@ add_test(function test_calobserver() {
   // Modify the item
   let newItem = item.clone();
   newItem.title = "title";
   memory.modifyItem(newItem, item, null);
   checkCounters(0, 1, 0);
 
   // Delete the item
   newItem.generation++; // circumvent generation checks for easier code
-  memory.deleteItem(newItem, null);
+  memory.deleteItem(newItem);
   checkCounters(0, 0, 1);
 
   // Now check the same for adding the item to a calendar only observed by the
   // calendar manager. The calcounters should still be 0, but the calendar
   // manager counter should have an item added, modified and deleted
   memory2.addItem(item, null);
   memory2.modifyItem(newItem, item, null);
-  memory2.deleteItem(newItem, null);
+  memory2.deleteItem(newItem);
   checkCounters(0, 0, 0, 1, 1, 1);
 
   // Remove observers
   memory.removeObserver(calobs);
   calmgr.removeCalendarObserver(allobs);
 
   // Make sure removing it actually worked
   memory.addItem(item, null);
   memory.modifyItem(newItem, item, null);
-  memory.deleteItem(newItem, null);
+  memory.deleteItem(newItem);
   checkCounters(0, 0, 0);
 
   // We are done now, start the next test
   run_next_test();
 });
 
 add_test(function test_removeModes() {
   function checkCounts(modes, shouldDelete, expectCount, extraFlags = 0) {
--- a/calendar/test/unit/test_deleted_items.js
+++ b/calendar/test/unit/test_deleted_items.js
@@ -57,17 +57,17 @@ add_task(async function test_deleted_ite
   futureDate.timezone = cal.dtz.defaultTimezone;
   let useFutureDate = false;
   let oldNowFunction = cal.dtz.now;
   cal.dtz.now = function() {
     return (useFutureDate ? futureDate : referenceDate).clone();
   };
 
   // Deleting an item should trigger it being marked for deletion.
-  await check_delmgr_call(() => memory.deleteItem(item, null));
+  await check_delmgr_call(() => memory.deleteItem(item));
 
   // Now check if it was deleted at our reference date.
   let deltime = delmgr.getDeletedDate(item.id);
   notEqual(deltime, null);
   equal(deltime.compare(referenceDate), 0);
 
   // The same with the calendar.
   deltime = delmgr.getDeletedDate(item.id, memory.id);
@@ -84,16 +84,16 @@ add_task(async function test_deleted_ite
   equal(delmgr.getDeletedDate(item.id, memory.id), null);
 
   // Start over with our past time.
   useFutureDate = false;
 
   // Add, delete, add. Item should no longer be deleted.
   await check_delmgr_call(() => memory.addItem(item, null));
   equal(delmgr.getDeletedDate(item.id), null);
-  await check_delmgr_call(() => memory.deleteItem(item, null));
+  await check_delmgr_call(() => memory.deleteItem(item));
   equal(delmgr.getDeletedDate(item.id).compare(referenceDate), 0);
   await check_delmgr_call(() => memory.addItem(item, null));
   equal(delmgr.getDeletedDate(item.id), null);
 
   // Revert now function, in case more tests are written.
   cal.dtz.now = oldNowFunction;
 });
--- a/calendar/test/unit/test_itip_message_sender.js
+++ b/calendar/test/unit/test_itip_message_sender.js
@@ -120,26 +120,26 @@ add_task(async function testParticipatio
     "TENTATIVE",
     "invited attendee participation status is 'TENTATIVE'"
   );
 
   await calendar.deleteItem(modifiedItem);
 });
 
 /**
- * Test deleting and event queues a "CANCEL" message.
+ * Test deleting an event queues a "CANCEL" message.
  */
 add_task(async function testEventDeleted() {
   let item = new CalEvent(icalString);
   let savedItem = await calendar.addItem(item);
 
-  let deletedItem = await calendar.deleteItem(savedItem);
-  let invitedAttendee = deletedItem.getAttendeeById(calendarOrganizerId);
+  await calendar.deleteItem(savedItem);
+  let invitedAttendee = savedItem.getAttendeeById(calendarOrganizerId);
   let sender = new CalItipMessageSender(null, invitedAttendee);
-  let result = sender.detectChanges(Ci.calIOperationListener.DELETE, deletedItem);
+  let result = sender.detectChanges(Ci.calIOperationListener.DELETE, savedItem);
   Assert.equal(result, 1, "result indicates 1 pending message queued");
   Assert.equal(sender.pendingMessageCount, 1, "pendingMessageCount is 1");
 
   let [msg] = sender.pendingMessages;
   Assert.equal(msg.method, "REPLY", "message method is 'REPLY'");
   Assert.equal(msg.recipients.length, 1, "message has 1 recipient");
 
   let [recipient] = msg.recipients;
--- a/calendar/test/unit/test_providers.js
+++ b/calendar/test/unit/test_providers.js
@@ -347,22 +347,18 @@ add_task(async function testMetaData() {
     let values = aCalendar.getAllMetaDataValues();
     equal(values.length, 2);
     equal(ids.length, 2);
     ok(ids[0] == "item1" || ids[1] == "item1");
     ok(ids[0] == "item2" || ids[1] == "item2");
     ok(values[0] == "meta1" || values[1] == "meta1");
     ok(values[0] == "meta2" || values[1] == "meta2");
 
-    await new Promise(resolve => {
-      aCalendar.deleteItem(event1, {
-        onGetResult: (calendar, aStatus, aItemType, aDetail, aItems) => {},
-        onOperationComplete: resolve,
-      });
-    });
+    await aCalendar.deleteItem(event1);
+
     equal(aCalendar.getMetaData("item1"), null);
     ids = aCalendar.getAllMetaDataIds();
     values = aCalendar.getAllMetaDataValues();
     equal(values.length, 1);
     equal(ids.length, 1);
     ok(ids[0] == "item2");
     ok(values[0] == "meta2");