Backout bug 788004 - No email send after invitation creation if offline cache is enabled [Exception... "'TypeError: aItem.calendar.canNotify is not a function' when calling method: [calIOperationListener::onOperationComplete]" CLOSED TREE
authorPhilipp Kewisch <mozilla@kewis.ch>
Mon, 18 Feb 2013 10:43:17 +0100
changeset 14872 c1d9ed13f285b0ca3750633bb8c118320e83736c
parent 14871 bce8262aabcc6bb720ed18ec46b47e1f691be5ca
child 14873 e4a72afda8abcc3d6a339d4fd78587af0a8013dc
push id867
push userbugzilla@standard8.plus.com
push dateMon, 01 Apr 2013 20:44:27 +0000
treeherdercomm-beta@797726b8d244 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs788004
Backout bug 788004 - No email send after invitation creation if offline cache is enabled [Exception... "'TypeError: aItem.calendar.canNotify is not a function' when calling method: [calIOperationListener::onOperationComplete]" CLOSED TREE
calendar/base/content/agenda-listbox.js
calendar/base/content/calendar-base-view.xml
calendar/base/content/calendar-item-editing.js
calendar/base/content/dialogs/calendar-dialog-utils.js
calendar/base/content/dialogs/calendar-event-dialog-attendees.js
calendar/base/content/dialogs/calendar-event-dialog-attendees.xml
calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
calendar/base/content/dialogs/calendar-event-dialog.js
calendar/base/content/dialogs/calendar-invitations-list.xml
calendar/base/content/dialogs/calendar-summary-dialog.js
calendar/base/modules/calItipUtils.jsm
calendar/base/modules/calRecurrenceUtils.jsm
calendar/base/modules/calUtils.jsm
calendar/base/src/calCachedCalendar.js
calendar/base/src/calCalendarManager.js
calendar/base/src/calRecurrenceInfo.js
calendar/base/src/calUtils.js
calendar/providers/composite/calCompositeCalendar.js
calendar/providers/gdata/components/calGoogleUtils.js
calendar/providers/ics/calICSCalendar.js
calendar/providers/storage/calStorageCalendar.js
calendar/providers/wcap/calWcapCalendarItems.js
--- a/calendar/base/content/agenda-listbox.js
+++ b/calendar/base/content/agenda-listbox.js
@@ -597,18 +597,17 @@ function setupContextMenu(popup) {
  * @param aEnd          (optional) The end date for the item query.
  * @param aCalendar     (optional) If specified, the single calendar from
  *                                   which the refresh will occur.
  */
 agendaListbox.refreshCalendarQuery =
 function refreshCalendarQuery(aStart, aEnd, aCalendar) {
     let pendingRefresh = this.pendingRefresh;
     if (pendingRefresh) {
-        pendingRefresh = cal.wrapInstance(pendingRefresh, Components.interfaces.calIOperation);
-        if (pendingRefresh) {
+        if (calInstanceOf(pendingRefresh, Components.interfaces.calIOperation)) {
             this.pendingRefresh = null;
             pendingRefresh.cancel(null);
         } else {
             return;
         }
     }
     if (!(aStart || aEnd || aCalendar)) {
         this.removeListItems();
--- a/calendar/base/content/calendar-base-view.xml
+++ b/calendar/base/content/calendar-base-view.xml
@@ -409,18 +409,17 @@
             return getDateFormatter().formatInterval(this.rangeStartDate, this.rangeEndDate);
         ]]></body>
       </method>
 
       <method name="popRefreshQueue">
         <body><![CDATA[
           let pendingRefresh = this.mRefreshPending;
           if (pendingRefresh) {
-              pendingRefresh = cal.wrapInstance(pendingRefresh, Components.interfaces.calIOperation);
-              if (pendingRefresh) {
+              if (calInstanceOf(pendingRefresh, Components.interfaces.calIOperation)) {
                   this.mRefreshPending = null;
                   pendingRefresh.cancel(null);
               } else {
                   if(this.mRefreshQueue.length > 0) {
                       this.relayout();
                   }
                   return;
               }
--- a/calendar/base/content/calendar-item-editing.js
+++ b/calendar/base/content/calendar-item-editing.js
@@ -311,18 +311,17 @@ function openEventDialog(calendarItem, c
 
     // the dialog will reset this to auto when it is done loading.
     window.setCursor("wait");
 
     // ask the provide if this item is an invitation. if this is the case
     // we'll open the summary dialog since the user is not allowed to change
     // the details of the item.
     var isInvitation = false;
-    calendar = cal.wrapInstance(calendar, Components.interfaces.calISchedulingSupport);
-    if (calendar) {
+    if (calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
         isInvitation = calendar.isInvitation(calendarItem);
     }
     // open the dialog modeless
     let url;
     if (isCalendarWritable(calendar)
         && (mode == "new"
             || (mode == "modify" && !isInvitation && userCanModifyItem((calendarItem))))) {
         url = "chrome://calendar/content/calendar-event-dialog.xul";
--- a/calendar/base/content/dialogs/calendar-dialog-utils.js
+++ b/calendar/base/content/dialogs/calendar-dialog-utils.js
@@ -441,19 +441,18 @@ function updateLink() {
         } catch (e) {
             // No protocol handler for the given protocol, or invalid uri
             hideOrShow(false);
             return;
         }
 
         // Only show if its either an internal protcol handler, or its external
         // and there is an external app for the scheme
-        let handlerWrappedInstance = cal.wrapInstance(handler, Components.interfaces.nsIExternalProtocolHandler);
-        hideOrShow(!handlerWrappedInstance ||
-                   handlerWrappedInstance.externalAppExistsForScheme(uri.scheme));
+        hideOrShow(!calInstanceOf(handler, Components.interfaces.nsIExternalProtocolHandler) ||
+                   handler.externalAppExistsForScheme(uri.scheme));
 
         setTimeout(function() {
           // HACK the url-link doesn't crop when setting the value in onLoad
           setElementValue("url-link", itemUrlString);
           setElementValue("url-link", itemUrlString, "href");
         }, 0);
     }
 }
--- a/calendar/base/content/dialogs/calendar-event-dialog-attendees.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog-attendees.js
@@ -611,18 +611,17 @@ function onChangeCalendar(calendar) {
     // set 'mIsReadOnly' if the calendar is read-only
     if (calendar && calendar.readOnly) {
         gIsReadOnly = true;
     }
 
     // assume we're the organizer [in case that the calendar
     // does not support the concept of identities].
     gIsInvitation = false;
-    args.item.calendar = cal.wrapInstance(args.item.calendar, Components.interfaces.calISchedulingSupport);
-    if (args.item.calendar) {
+    if (calInstanceOf(args.item.calendar, Components.interfaces.calISchedulingSupport)) {
         gIsInvitation = args.item.calendar.isInvitation(args.item);
     }
 
     if (gIsReadOnly || gIsInvitation) {
         document.getElementById("next-slot")
             .setAttribute('disabled', 'true');
         document.getElementById("previous-slot")
             .setAttribute('disabled', 'true');
--- a/calendar/base/content/dialogs/calendar-event-dialog-attendees.xml
+++ b/calendar/base/content/dialogs/calendar-event-dialog-attendees.xml
@@ -139,18 +139,19 @@
 
           this.mIsReadOnly = calendar.readOnly;
 
           // assume we're the organizer [in case that the calendar
           // does not support the concept of identities].
           var organizerID = ((organizer && organizer.id)
                              ? organizer.id
                              : calendar.getProperty("organizerId"));
-          calendar = cal.wrapInstance(calendar, Components.interfaces.calISchedulingSupport);
-          this.mIsInvitation = (calendar && calendar.isInvitation(args.item));
+
+          this.mIsInvitation = (calInstanceOf(calendar, Components.interfaces.calISchedulingSupport) &&
+                                calendar.isInvitation(args.item));
 
           var listbox =
               document.getAnonymousElementByAttribute(
                   this, "anonid", "listbox");
           var template =
               document.getAnonymousElementByAttribute(
                   this, "anonid", "item");
           template.focus();
--- a/calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
@@ -40,17 +40,19 @@ function onLoad() {
         // Split out rules and exceptions
         try {
             var rrules = splitRecurrenceRules(recinfo);
             var rules = rrules[0];
             var exceptions = rrules[1];
             // Deal with the rules
             if (rules.length > 0) {
                 // We only handle 1 rule currently
-                rule = calWrapInstance(rules[0], Components.interfaces.calIRecurrenceRule);
+                if (calInstanceOf(rules[0], Components.interfaces.calIRecurrenceRule)) {
+                    rule = rules[0];
+                }
             }
         } catch (ex) {
             Components.utils.reportError(ex);
         }
     }
     if (!rule) {
         rule = createRecurrenceRule();
         rule.type = 'DAILY';
--- a/calendar/base/content/dialogs/calendar-event-dialog.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog.js
@@ -757,18 +757,18 @@ function loadRepeat(item) {
         for each (var r in ritems) {
             if (r.isNegative) {
                 exceptions.push(r);
             } else {
                 rules.push(r);
             }
         }
         if (rules.length == 1) {
-            let rule = cal.wrapInstance(rules[0], Components.interfaces.calIRecurrenceRule);
-            if (rule) {
+            var rule = rules[0];
+            if (calInstanceOf(rule, Components.interfaces.calIRecurrenceRule)) {
                 switch (rule.type) {
                     case 'DAILY':
                         if (!checkRecurrenceRule(rule, ['BYSECOND',
                                                         'BYMINUTE',
                                                         'BYHOUR',
                                                         'BYMONTHDAY',
                                                         'BYYEARDAY',
                                                         'BYWEEKNO',
--- a/calendar/base/content/dialogs/calendar-invitations-list.xml
+++ b/calendar/base/content/dialogs/calendar-invitations-list.xml
@@ -196,19 +196,18 @@
           return (att ? att.participationStatus : null);
         ]]></body>
       </method>
 
       <method name="setCalendarItemParticipationStatus">
         <parameter name="aItem"/>
         <parameter name="aStatus"/>
         <body><![CDATA[
-          let wrappedCalendar = cal.wrapInstance(aItem.calendar, Components.interfaces.calISchedulingSupport);
-          if (wrappedCalendar) {
-              var att = wrappedCalendar.getInvitedAttendee(aItem);
+          if (calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport)) {
+              var att = aItem.calendar.getInvitedAttendee(aItem);
               if (att) {
                   var att_ = att.clone();
                   att_.participationStatus = aStatus;
 
                   // Update attendee
                   aItem.removeAttendee(att);
                   aItem.addAttendee(att_);
                   return true;
--- a/calendar/base/content/dialogs/calendar-summary-dialog.js
+++ b/calendar/base/content/dialogs/calendar-summary-dialog.js
@@ -48,20 +48,20 @@ function onLoad() {
     if (cal.isEvent(item)) {
         setDialogId(document.documentElement, "calendar-event-summary-dialog");
     } else if (cal.isToDo(item)) {
         setDialogId(document.documentElement, "calendar-task-summary-dialog");
     }
 
     window.readOnly = !(isCalendarWritable(calendar)
                         && (userCanModifyItem(item)
-                            || (cal.wrapInstance(item.calendar, Components.interfaces.calISchedulingSupport))
+                            || (calInstanceOf(item.calendar, Components.interfaces.calISchedulingSupport)
                                 && item.calendar.isInvitation(item)
                                 && userCanRespondToInvitation(item))));
-    if (!window.readOnly && cal.wrapInstance(calendar, Components.interfaces.calISchedulingSupport)) {
+    if (!window.readOnly && calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
         var attendee = calendar.getInvitedAttendee(item);
         if (attendee) {
             // if this is an unresponded invitation, preset our default alarm values:
             if (!item.getAlarms({}).length &&
                 (attendee.participationStatus == "NEEDS-ACTION")) {
                 cal.alarms.setDefaultValues(item);
             }
 
--- a/calendar/base/modules/calItipUtils.jsm
+++ b/calendar/base/modules/calItipUtils.jsm
@@ -15,18 +15,18 @@ EXPORTED_SYMBOLS = ["cal"]; // even thou
 cal.itip = {
     /**
      * Gets the sequence/revision number, either of the passed item or
      * the last received one of an attendee; see
      * <http://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04#section-7.1>.
      */
      getSequence: function cal_itip_getSequence(item) {
         let seq = null;
-        item = cal.wrapInstance(item, Components.interfaces.calIAttendee);
-        if (item) {
+
+        if (cal.calInstanceOf(item, Components.interfaces.calIAttendee)) {
             seq = item.getProperty("RECEIVED-SEQUENCE");
         } else if (item) {
             // Unless the below is standardized, we store the last original
             // REQUEST/PUBLISH SEQUENCE in X-MOZ-RECEIVED-SEQUENCE to test against it
             // when updates come in:
             seq = item.getProperty("X-MOZ-RECEIVED-SEQUENCE");
             if (seq === null) {
                 seq = item.getProperty("SEQUENCE");
@@ -50,18 +50,17 @@ cal.itip = {
     /**
      * Gets the stamp date-time, either of the passed item or
      * the last received one of an attendee; see
      * <http://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04#section-7.2>.
      */
     getStamp: function cal_itip_getStamp(item) {
         let dtstamp = null;
 
-        item = cal.wrapInstance(item, Components.interfaces.calIAttendee);
-        if (item) {
+        if (cal.calInstanceOf(item, Components.interfaces.calIAttendee)) {
             let st = item.getProperty("RECEIVED-DTSTAMP");
             if (st) {
                 dtstamp = cal.createDateTime(st);
             }
         } else if (item) {
             // Unless the below is standardized, we store the last original
             // REQUEST/PUBLISH DTSTAMP in X-MOZ-RECEIVED-DTSTAMP to test against it
             // when updates come in:
@@ -529,21 +528,21 @@ cal.itip = {
             }
 
             if (aOriginalItem.recurrenceInfo && aItem.recurrenceInfo) {
                 // check whether the two differ only in EXDATEs
                 let clonedItem = aItem.clone();
                 let exdates = [];
                 for each (let ritem in clonedItem.recurrenceInfo.getRecurrenceItems({})) {
                     if (ritem.isNegative &&
-                        cal.wrapInstance(ritem, Components.interfaces.calIRecurrenceDate) &&
+                        cal.calInstanceOf(ritem, Components.interfaces.calIRecurrenceDate) &&
                         !aOriginalItem.recurrenceInfo.getRecurrenceItems({}).some(
                             function(r) {
                                 return (r.isNegative &&
-                                        cal.wrapInstance(r, Components.interfaces.calIRecurrenceDate) &&
+                                        cal.calInstanceOf(r, Components.interfaces.calIRecurrenceDate) &&
                                         r.date.compare(ritem.date) == 0);
                             })) {
                         exdates.push(ritem);
                     }
                 }
                 if (exdates.length > 0) {
                     // check whether really only EXDATEs have been added:
                     let recInfo = clonedItem.recurrenceInfo;
@@ -742,22 +741,22 @@ cal.itip = {
 
 /** local to this module file
  * Sets the received info either on the passed attendee or item object.
  *
  * @param item either  calIAttendee or calIItemBase
  * @param itipItemItem received iTIP item
  */
 function setReceivedInfo(item, itipItemItem) {
-    item.setProperty(cal.wrapInstance(item, Components.interfaces.calIAttendee) ? "RECEIVED-SEQUENCE"
+    item.setProperty(cal.calInstanceOf(item, Components.interfaces.calIAttendee) ? "RECEIVED-SEQUENCE"
                                                                                  : "X-MOZ-RECEIVED-SEQUENCE",
                      String(cal.itip.getSequence(itipItemItem)));
     let dtstamp = cal.itip.getStamp(itipItemItem);
     if (dtstamp) {
-        item.setProperty(cal.wrapInstance(item, Components.interfaces.calIAttendee) ? "RECEIVED-DTSTAMP"
+        item.setProperty(cal.calInstanceOf(item, Components.interfaces.calIAttendee) ? "RECEIVED-DTSTAMP"
                                                                                      : "X-MOZ-RECEIVED-DTSTAMP",
                          dtstamp.getInTimezone(cal.UTC()).icalString);
     }
 }
 
 /**
  * Strips user specific data, e.g. categories and alarm settings and returns the stripped item.
  */
@@ -880,19 +879,31 @@ function createOrganizer(aCalendar) {
  * @param aMethod iTIP method
  * @param aRecipientsList an array of calIAttendee objects the message should be sent to
  * @param autoResponse an inout object whether the transport should ask before sending
  */
 function sendMessage(aItem, aMethod, aRecipientsList, autoResponse) {
     if (aRecipientsList.length == 0) {
         return;
     }
-    aItem.calendar = cal.wrapInstance(aItem.calendar, Components.interfaces.calISchedulingSupport);
-    if (aItem.calendar && aItem.calendar.canNotify(aMethod, aItem)) {
-          return; // provider will handle that
+    if (cal.calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport)) {
+        // HACK: Usage of QueryInterface kind of hackish, need to remove all
+        // calls of calInstanceOf since casting happens per-compartment.
+        //
+        // Works:
+        //   let foo = aItem.calendar;
+        //   (foo instanceof Ci.calISchedulingSupport);
+        //   cal.calInstanceOf(aItem.calendar, Ci.calISchedulingSupport) && aItem.calendar.canNotify();
+        // Fails:
+        //   let foo = aItem.calendar;
+        //   cal.calInstanceOf(aItem.calendar, Ci.calISchedulingSupport) && aItem.calendar.canNotify();
+        if (aItem.calendar.QueryInterface(Components.interfaces.calISchedulingSupport)
+                          .canNotify(aMethod, aItem)) {
+            return; //provider will handle that
+        }
     }
 
     let aTransport = aItem.calendar.getProperty("itip.transport");
     if (!aTransport) { // can only send if there's a transport for the calendar
         return;
     }
     aTransport = aTransport.QueryInterface(Components.interfaces.calIItipTransport);
 
--- a/calendar/base/modules/calRecurrenceUtils.jsm
+++ b/calendar/base/modules/calRecurrenceUtils.jsm
@@ -20,19 +20,19 @@ function recurrenceRule2String(recurrenc
     function getRString(name, args) cal.calGetString("calendar-event-dialog", name, args);
 
     // Retrieve a valid recurrence rule from the currently
     // set recurrence info. Bail out if there's more
     // than a single rule or something other than a rule.
     recurrenceInfo = recurrenceInfo.clone();
     let rrules = splitRecurrenceRules(recurrenceInfo);
     if (rrules[0].length == 1) {
-        let rule = cal.wrapInstance(rrules[0][0], Components.interfaces.calIRecurrenceRule);
+        let rule = rrules[0][0];
         // currently we don't allow for any BYxxx-rules.
-        if ((rule) &&
+        if (cal.calInstanceOf(rule, Components.interfaces.calIRecurrenceRule) &&
             !checkRecurrenceRule(rule, ['BYSECOND',
                                         'BYMINUTE',
                                         //'BYDAY',
                                         'BYHOUR',
                                         //'BYMONTHDAY',
                                         'BYYEARDAY',
                                         'BYWEEKNO',
                                         //'BYMONTH',
--- a/calendar/base/modules/calUtils.jsm
+++ b/calendar/base/modules/calUtils.jsm
@@ -165,31 +165,31 @@ let cal = {
         return serializer.serializeToString();
     },
 
     /**
      * Shortcut function to check whether an item is an invitation copy.
      */
     isInvitation: function cal_isInvitation(aItem) {
         let isInvitation = false;
-        let calendar = cal.wrapInstance(aItem.calendar, Components.interfaces.calISchedulingSupport);
-        if (calendar) {
+        let calendar = aItem.calendar;
+        if (cal.calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
             isInvitation = calendar.isInvitation(aItem);
         }
         return isInvitation;
     },
 
     /**
      * Shortcut function to check whether an item is an invitation copy and
      * has a participation status of either NEEDS-ACTION or TENTATIVE.
      *
      * @param aItem either calIAttendee or calIItemBase 
      */
     isOpenInvitation: function cal_isOpenInvitation(aItem) {
-        if (!cal.wrapInstance(aItem, Components.interfaces.calIAttendee)) {
+        if (!cal.calInstanceOf(aItem, Components.interfaces.calIAttendee)) {
             aItem = cal.getInvitedAttendee(aItem);
         }
         if (aItem) {
             switch (aItem.participationStatus) {
                 case "NEEDS-ACTION":
                 case "TENTATIVE":
                     return true;
             }
@@ -197,20 +197,20 @@ let cal = {
         return false;
     },
 
     /**
      * Shortcut function to get the invited attendee of an item.
      */
     getInvitedAttendee: function cal_getInvitedAttendee(aItem, aCalendar) {
         if (!aCalendar) {
-            aCalendar = cal.wrapInstance(aItem.calendar, Components.interfaces.calISchedulingSupport);
+            aCalendar = aItem.calendar;
         }
         let invitedAttendee = null;
-        if (aCalendar) {
+        if (cal.calInstanceOf(aCalendar, Components.interfaces.calISchedulingSupport)) {
             invitedAttendee = aCalendar.getInvitedAttendee(aItem);
         }
         return invitedAttendee;
     },
 
     // The below functions will move to some different place once the
     // unifinder tress are consolidated.
 
@@ -599,30 +599,16 @@ let cal = {
                     func(subject, topic, data);
                 }
             }
         };
         Services.obs.addObserver(observer, topic, false /* don't hold weakly */);
     },
 
     /**
-     * Wraps an instance. Replaces calInstanceOf from calUtils.js
-     *
-     * @param aObj the object under consideration
-     * @param aInterface the interface to be wrapped
-     */
-    wrapInstance: function wrapInstance(aObj, aInterface) {
-        try {
-            return aObj.QueryInterface(aInterface);
-        } catch (e) {
-            return null;
-        }
-    },
-
-    /**
      * Adds an xpcom shutdown observer.
      *
      * @param func function to execute
      */
     addShutdownObserver: function cal_addShutdownObserver(func) {
         cal.addObserver(func, "xpcom-shutdown", true /* one time */);
     },
 
--- a/calendar/base/src/calCachedCalendar.js
+++ b/calendar/base/src/calCachedCalendar.js
@@ -665,17 +665,17 @@ calCachedCalendar.prototype = {
     set superCalendar(val) {
         return (this.mSuperCalendar = val);
     },
 
     get offline() {
         return Services.io.offline;
     },
     get supportsChangeLog() {
-        return (cal.wrapInstance(this.mUncachedCalendar, Components.interfaces.calIChangeLog) != null);
+        return calInstanceOf(this.mUncachedCalendar, Components.interfaces.calIChangeLog);
     },
 
     get canRefresh() { // enable triggering sync using the reload button
         return true;
     },
 
     getProperty: function(aName) {
         switch (aName) {
--- a/calendar/base/src/calCalendarManager.js
+++ b/calendar/base/src/calCalendarManager.js
@@ -643,18 +643,17 @@ calCalendarManager.prototype = {
 
         // XXX This is a workaround for bug 351499. We should remove it once
         // we sort out the whole "delete" vs. "unsubscribe" UI thing.
         //
         // We only want to delete the contents of calendars from local
         // providers (storage and memory). Otherwise we may nuke someone's
         // calendar stored on a server when all they really wanted to do was
         // unsubscribe.
-        calendar = cal.wrapInstance(calendar, Components.interfaces.calICalendarProvider);
-        if (calendar &&
+        if (cal.calInstanceOf(calendar, Components.interfaces.calICalendarProvider) &&
             (calendar.type == "storage" || calendar.type == "memory")) {
             try {
                 calendar.deleteCalendar(calendar, null);
             } catch (e) {
                 Components.utils.reportError("error purging calendar: " + e);
             }
         }
     },
--- a/calendar/base/src/calRecurrenceInfo.js
+++ b/calendar/base/src/calRecurrenceInfo.js
@@ -310,48 +310,46 @@ calRecurrenceInfo.prototype = {
             // Go through all positive rules and get the next recurrence id
             // according to that rule. If for all rules the rid is "invalid",
             // (i.e an EXDATE removed it, or an exception moved it somewhere
             // else), then get the respective next rid.
             //
             // If in a loop at least one rid is valid (i.e not an exception, not
             // an exdate, is after aTime), then remember the lowest one.
             for (var i = 0; i < this.mPositiveRules.length; i++) {
-                let rDateInstance = cal.wrapInstance(this.mPositiveRules[i], Components.interfaces.calIRecurrenceDate);
-                let rRuleInstance = cal.wrapInstance(this.mPositiveRules[i], Components.interfaces.calIRecurrenceRule);
-                if (rDateInstance) {
+                if (calInstanceOf(this.mPositiveRules[i], Components.interfaces.calIRecurrenceDate)) {
                     // RDATEs are special. there is only one date in this rule,
                     // so no need to search anything.
-                    var rdate = rDateInstance.date;
+                    var rdate = this.mPositiveRules[i].date;
                     if (!nextOccurrences[i] && rdate.compare(aTime) > 0) {
                         // The RDATE falls into range, save it.
                         nextOccurrences[i] = rdate;
                     } else {
                         // The RDATE doesn't fall into range. This rule will
                         // always be invalid, since it can't give out a date.
                         nextOccurrences[i] = null;
                         invalidOccurrences++;
                     }
-                } else if (rRuleInstance) {
+                } else if (calInstanceOf(this.mPositiveRules[i], Components.interfaces.calIRecurrenceRule)) {
                     // RRULEs must not start searching before |startDate|, since
                     // the pattern is only valid afterwards. If an occurrence
                     // was found in a previous round, we can go ahead and start
                     // searching from that occurrence.
                     var searchStart = nextOccurrences[i] || startDate;
 
                     // Search for the next occurrence after aTime. If the last
                     // round was invalid, then in this round we need to search
                     // after nextOccurrences[i] to make sure getNextOccurrence()
                     // doesn't find the same occurrence again.
                     var searchDate =
                         (nextOccurrences[i] && nextOccurrences[i].compare(aTime) > 0 ?
                             nextOccurrences[i] :
                             aTime);
 
-                    nextOccurrences[i] = rRuleInstance
+                    nextOccurrences[i] = this.mPositiveRules[i]
                                              .getNextOccurrence(searchStart, searchDate);
                 }
 
                 // As decided in bug 734245, an EXDATE of type DATE shall also match a DTSTART of type DATE-TIME
                 let nextKey = getRidKey(nextOccurrences[i]);
                 let isInExceptionMap = nextKey && (this.mExceptionMap[nextKey.substring(0,8)] ||
                                                    this.mExceptionMap[nextKey]);
                 let isInNegMap = nextKey && (negMap[nextKey.substring(0,8)] ||
@@ -641,17 +639,17 @@ calRecurrenceInfo.prototype = {
     },
 
     restoreOccurrenceAt: function cRI_restoreOccurrenceAt(aRecurrenceId) {
         this.ensureBaseItem();
         this.ensureMutable();
         this.ensureSortedRecurrenceRules();
 
         for (var i = 0; i < this.mRecurrenceItems.length; i++) {
-            if (cal.wrapInstance(this.mRecurrenceItems[i], Components.interfaces.calIRecurrenceDate)) {
+            if (calInstanceOf(this.mRecurrenceItems[i], Components.interfaces.calIRecurrenceDate)) {
                 var rd = this.mRecurrenceItems[i].QueryInterface(Components.interfaces.calIRecurrenceDate);
                 if (rd.isNegative && rd.date.compare(aRecurrenceId) == 0) {
                     return this.deleteRecurrenceItemAt(i);
                 }
             }
         }
 
         throw Components.results.NS_ERROR_INVALID_ARG;
@@ -764,27 +762,25 @@ calRecurrenceInfo.prototype = {
         let timeDiff = aNewStartTime.getInTimezone(UTC()).subtractDate(aOldStartTime.getInTimezone(UTC()));
 
         let rdates = {};
 
         // take RDATE's and EXDATE's into account.
         const kCalIRecurrenceDate = Components.interfaces.calIRecurrenceDate;
         let ritems = this.getRecurrenceItems({});
         for each (let ritem in ritems) {
-            if (cal.wrapInstance(ritem, kCalIRecurrenceDate)) {
-                // Possible performance issue with QueryInterface
-                // as cal.wrapInstance already tries to do one
+            if (cal.calInstanceOf(ritem, kCalIRecurrenceDate)) {
                 ritem = ritem.QueryInterface(kCalIRecurrenceDate);
                 let date = ritem.date;
                 date.addDuration(timeDiff);
                 if (!ritem.isNegative) {
                     rdates[getRidKey(date)] = date;
                 }
                 ritem.date = date;
-            } else if (cal.wrapInstance(ritem, Components.interfaces.calIRecurrenceRule)) {
+            } else if (cal.calInstanceOf(ritem, Components.interfaces.calIRecurrenceRule)) {
                 ritem = ritem.QueryInterface(Components.interfaces.calIRecurrenceRule);
                 if (!ritem.isByCount) {
                     let untilDate = ritem.untilDate;
                     if (untilDate) {
                         untilDate.addDuration(timeDiff);
                         ritem.untilDate = untilDate;
                     }
                 }
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -424,33 +424,53 @@ function calInstanceOf(aObject, aInterfa
                  "with the next release. Use cal.wrapInstance() instead.\n" +
                  cal.STACK(10));
         calInstanceOf.warningIssued = true;
     }
     return (cal.wrapInstance(aObject, aInterface) != null);
 }
 
 /**
+ * (At least on branch 1.8), the js instanceof operator does not work to test
+ * interfaces on direct implementation objects, i.e. non-wrapped objects.
+ * This function falla back to using QueryInterface to check whether the interface
+ * is implemented.
+ */
+function calInstanceOf(aObject, aInterface) {
+    // We first try instanceof which is assumed to be faster than querying the object:
+    if (!(aObject instanceof aInterface)) {
+        // if the passed object in not wrapped (but a plain implementation),
+        // instanceof won't check QueryInterface.
+        try {
+            aObject.QueryInterface(aInterface);
+        } catch (exc) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
  * Determines whether or not the aObject is a calIEvent
  *
  * @param aObject  the object to test
  * @returns        true if the object is a calIEvent, false otherwise
  */
 function isEvent(aObject) {
-    return (cal.wrapInstance(aObject, Components.interfaces.calIEvent) != null);
+    return calInstanceOf(aObject, Components.interfaces.calIEvent);
 }
 
 /**
  * Determines whether or not the aObject is a calITodo
  *
  * @param aObject  the object to test
  * @returns        true if the object is a calITodo, false otherwise
  */
 function isToDo(aObject) {
-    return (cal.wrapInstance(aObject, Components.interfaces.calITodo) != null);
+    return calInstanceOf(aObject, Components.interfaces.calITodo);
 }
 
 /**
  * Normal get*Pref calls will throw if the pref is undefined.  This function
  * will get a bool, int, or string pref.  If the pref is undefined, it will
  * return aDefault.
  *
  * @param aPrefName   the (full) name of preference to get
@@ -1405,18 +1425,17 @@ function calGetProductVersion() {
  * ical component.  This should be used whenever you need to set the prodid
  * and version on a calIcalComponent object.
  *
  * @param
  *      aIcalComponent  The ical component to set the prodid and version on.
  */
 function calSetProdidVersion(aIcalComponent) {
     // Throw for an invalid parameter
-    aIcalComponent = cal.wrapInstance(aIcalComponent, Components.interfaces.calIIcalComponent);
-    if (!aIcalComponent) {
+    if (!calInstanceOf(aIcalComponent, Components.interfaces.calIIcalComponent)) {
         throw Components.results.NS_ERROR_INVALID_ARG;
     }
     // Set the prodid and version
     aIcalComponent.prodid = calGetProductId();
     aIcalComponent.version = calGetProductVersion();
 }
 
 
--- a/calendar/providers/composite/calCompositeCalendar.js
+++ b/calendar/providers/composite/calCompositeCalendar.js
@@ -314,28 +314,26 @@ calCompositeCalendar.prototype = {
     deleteProperty: function(aName) {
         return this.mDefaultCalendar.deleteProperty(aName);
     },
 
     // void addObserver( in calIObserver observer );
     mCompositeObservers: null,
     mObservers: null,
     addObserver: function (aObserver) {
-        let compositeObserver = cal.wrapInstance(aObserver, Components.interfaces.calICompositeObserver);
-        if (compositeObserver) {
-            this.mCompositeObservers.add(compositeObserver);
+        if (cal.calInstanceOf(aObserver, Components.interfaces.calICompositeObserver)) {
+            this.mCompositeObservers.add(aObserver);
         }
         this.mObservers.add(aObserver);
     },
 
     // void removeObserver( in calIObserver observer );
     removeObserver: function (aObserver) {
-        let compositeObserver = cal.wrapInstance(aObserver, Components.interfaces.calICompositeObserver);
-        if (compositeObserver) {
-            this.mCompositeObservers.remove(compositeObserver);
+        if (cal.calInstanceOf(aObserver, Components.interfaces.calICompositeObserver)) {
+            this.mCompositeObservers.remove(aObserver);
         }
         this.mObservers.remove(aObserver);
     },
 
     refresh: function cCC_refresh() {
         if (this.mStatusObserver) {
             this.mStatusObserver.startMeteors(Components.interfaces.calIStatusObserver.DETERMINED_PROGRESS, this.mCalendars.length);
         }
--- a/calendar/providers/gdata/components/calGoogleUtils.js
+++ b/calendar/providers/gdata/components/calGoogleUtils.js
@@ -579,18 +579,17 @@ function ItemToXMLEntry(aItem, aCalendar
             icalString = "DTSTART;TZID=" + startTZID
                          + ":" + aItem.startDate.icalString + kNEWLINE
                          + "DTEND;TZID=" + endTZID
                          + ":"  + aItem.endDate.icalString + kNEWLINE;
 
             // Add all recurrence items to the ical string
             for each (let ritem in recurrenceItems) {
                 let prop = ritem.icalProperty;
-                ritem = cal.wrapInstance(ritem, Components.interfaces.calIRecurrenceDate);
-                if (ritem) {
+                if (calInstanceOf(ritem, Components.interfaces.calIRecurrenceDate)) {
                     // EXDATES require special casing, since they might contain
                     // a TZID. To avoid the need for conversion of TZID strings,
                     // convert to UTC before serialization.
                     prop.valueAsDatetime = ritem.date.getInTimezone(UTC());
                 }
                 icalString += prop.icalString;
             }
 
--- a/calendar/providers/ics/calICSCalendar.js
+++ b/calendar/providers/ics/calICSCalendar.js
@@ -120,21 +120,21 @@ calICSCalendar.prototype = {
 
     get uri() { return this.mUri },
     set uri(aUri) {
         this.mUri = aUri;
         this.mMemoryCalendar.uri = this.mUri;
 
         // Use the ioservice, to create a channel, which makes finding the
         // right hooks to use easier.
-        let channel = Services.io.newChannelFromURI(this.mUri);
+        var channel = Services.io.newChannelFromURI(this.mUri);
 
-        if (cal.wrapInstance(channel, Components.interfaces.nsIHttpChannel)) {
+        if (calInstanceOf(channel, Components.interfaces.nsIHttpChannel)) {
             this.mHooks = new httpHooks(this);
-        } else if (cal.wrapInstance(channel, Components.interfaces.nsIFileChannel)) {
+        } else if (calInstanceOf(channel, Components.interfaces.nsIFileChannel)) {
             this.mHooks = new fileHooks(this);
         } else {
             this.mHooks = new dummyHooks(this);
         }
     },
 
     getProperty: function calICSCalendar_getProperty(aName) {
         switch (aName) {
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -2111,19 +2111,18 @@ calStorageCalendar.prototype = {
         return 0;
     },
 
     writeProperty: function cSC_writeProperty(item, propName, propValue) {
         try {
             this.prepareStatement(this.mInsertProperty);
             var pp = this.mInsertProperty.params;
             pp.key = propName;
-            let propDateTimeVal = cal.wrapInstance(propValue, Components.interfaces.calIDateTime);
-            if (propDateTimeVal) {
-                pp.value = propDateTimeVal.nativeTime;
+            if (calInstanceOf(propValue, Components.interfaces.calIDateTime)) {
+                pp.value = propValue.nativeTime;
             } else {
                 try {
                     pp.value = propValue;
                 } catch (e) {
                     // The storage service throws an NS_ERROR_ILLEGAL_VALUE in
                     // case pval is something complex (i.e not a string or
                     // number). Swallow this error, leaving the value empty.
                     if (e.result != Components.results.NS_ERROR_ILLEGAL_VALUE) {
--- a/calendar/providers/wcap/calWcapCalendarItems.js
+++ b/calendar/providers/wcap/calWcapCalendarItems.js
@@ -38,31 +38,29 @@ function calWcapCalendar_getRecurrencePa
     out_rrules.value = [];
     out_rdates.value = [];
     out_exrules.value = [];
     out_exdates.value = [];
     if (item.recurrenceInfo) {
         var rItems = item.recurrenceInfo.getRecurrenceItems({});
         for each (var rItem in rItems) {
             var isNeg = rItem.isNegative;
-            let rRule = cal.wrapInstance(rItem, Components.interfaces.calIRecurrenceRule);
-            let rDate = cal.wrapInstance(rItem, Components.interfaces.calIRecurrenceDate);
-            if (rRule) {
-                let rule = ("\"" + encodeURIComponent(rRule.icalProperty.valueAsIcalString) + "\"");
+            if (calInstanceOf(rItem, Components.interfaces.calIRecurrenceRule)) {
+                var rule = ("\"" + encodeURIComponent(rItem.icalProperty.valueAsIcalString) + "\"");
                 if (isNeg) {
                     out_exrules.value.push(rule);
                 } else {
                     out_rrules.value.push(rule);
                 }
-            } else if (rDate) {
+            } else if (calInstanceOf(rItem, Components.interfaces.calIRecurrenceDate)) {
                 // cs does not accept DATEs here:
                 if (isNeg) {
-                    out_exdates.value.push(getIcalUTC(ensureDateTime(rDate.date)));
+                    out_exdates.value.push(getIcalUTC(ensureDateTime(rItem.date)));
                 } else {
-                    out_rdates.value.push(getIcalUTC(ensureDateTime(rDate.date)));
+                    out_rdates.value.push(getIcalUTC(ensureDateTime(rItem.date)));
                 }
             } else {
                 this.notifyError(NS_ERROR_UNEXPECTED,
                                  "don\'t know how to handle this recurrence item: " + rItem.valueAsIcalString);
             }
         }
     }
 };
@@ -497,21 +495,20 @@ function calWcapCalendar_storeItem(bAddI
 
         // attachment urls:
         function getAttachments(item) {
             var ret = "";
             var attachments = item.attachments;
             if (attachments) {
                 var strings = [];
                 for each (var att in attachements) {
-                    let wrappedAttachment = cal.wrapInstance(att, Components.interfaces.calIAttachment);
                     if (typeof(att) == "string") {
                         strings.push(encodeURIComponent(att));
-                    } else if (wrappedAttachment && wrappedAttachment.uri) {
-                        strings.push(encodeURIComponent(wrappedAttachment.uri.spec));
+                    } else if (calInstanceOf(att, Components.interfaces.calIAttachment) && att.uri) {
+                        strings.push(encodeURIComponent(att.uri.spec));
                     } else { // xxx todo
                         logError("only URLs supported as attachment, not: " + att, this_);
                     }
                 }
                 strings.sort();
                 ret += strings.join(";");
             }
             return ret;
@@ -971,25 +968,22 @@ calWcapCalendar.prototype.parseItems = f
 
             let recStartDate = item.recurrenceStartDate;
             if (recStartDate && !recStartDate.isDate) {
                 recStartDate = null;
             }
             let recItems = item.recurrenceInfo.getRecurrenceItems({});
             for each (let recItem in recItems) {
                 // cs bug: workaround missing COUNT
-                if (cal.wrapInstance(recItem, Components.interfaces.calIRecurrenceRule)) {
-                    // Possible performance issue double QueryInterface calls
-                    recItem = recItem.QueryInterface(Components.interfaces.calIRecurrenceRule);
+                if (calInstanceOf(recItem, Components.interfaces.calIRecurrenceRule)) {
                     if (!recItem.isFinite && !recItem.isNegative) {
                         recItem.count = recurrenceBound;
                     }
                 } else if (recStartDate &&
-                           cal.wrapInstance(recItem, Components.interfaces.calIRecurrenceDate)) {
-                    recItem = recItem.QueryInterface(Components.interfaces.calIRecurrenceDate);
+                           calInstanceOf(recItem, Components.interfaces.calIRecurrenceDate)) {
                     // cs bug: always uses DATE-TIME even though the master item is all-day DATE:
                     //         get into startDate's timezone before cutting:
                     let date = recItem.date.getInTimezone(recStartDate.timezone);
                     date.isDate = true;
                     recItem.date = date;
                 }
             }