Fix bug 678343 - Adapt to the new chrome worker code / Cannot parse ICS files / workerfactory undefined. r=mmecca,a=philipp CLOSED TREE
authorPhilipp Kewisch <mozilla@kewis.ch>
Fri, 16 Sep 2011 12:12:06 +0200
changeset 8464 cbfc2237d3f3f43b3e1200763aa32da26677acce
parent 8463 5b983ed2ca5a8568d339f2916efa3b66fdcd9bfd
child 8465 3b5a54ac2c6ae1ff467de9f59aacd5a5df898477
push id133
push usermozilla@kewis.ch
push dateFri, 16 Sep 2011 10:21:17 +0000
treeherdercomm-beta@cbfc2237d3f3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmmecca, philipp
bugs678343
Fix bug 678343 - Adapt to the new chrome worker code / Cannot parse ICS files / workerfactory undefined. r=mmecca,a=philipp CLOSED TREE
calendar/base/public/calIICSService.idl
calendar/base/src/Makefile.in
calendar/base/src/calICSService.cpp
calendar/base/src/calICSService.h
calendar/base/src/calIcsParser-worker.js
calendar/base/src/calIcsParser.js
calendar/base/src/calRecurrenceDate.cpp
--- a/calendar/base/public/calIICSService.idl
+++ b/calendar/base/public/calIICSService.idl
@@ -113,29 +113,29 @@ interface calIIcalComponent : nsISupport
     attribute calIDateTime completedTime;
     attribute calIDateTime lastModified;
 
     /**
      * The recurrence ID, a.k.a. DTSTART-of-calculated-occurrence,
      * or null if this isn't an occurrence.
      */
     attribute calIDateTime recurrenceId;
-    
+
     AUTF8String serializeToICS();
 
     /**
      * Return a string representation of this instance.
      */
     AUTF8String toString();
 
     /**
      * Serializes this component (and subcomponents) directly to an
      * input stream.  Typically used for performance to avoid
      * unnecessary conversions and XPConnect traversals.
-     * 
+     *
      * @result     an input stream which can be read to get the serialized
      *             version of this component, encoded in UTF-8.  Implements
      *             nsISeekableStream so that it can be used with
      *             nsIUploadChannel.
      */
     nsIInputStream serializeToICSStream();
 
     void addSubcomponent(in calIIcalComponent comp);
@@ -188,17 +188,17 @@ interface calIIcalProperty : nsISupports
      * @exception Any libical error will be thrown as an calIError::ICS_ error.
      */
     readonly attribute AUTF8String icalString;
 
     /**
      * Return a string representation of this instance.
      */
     AUTF8String toString();
-    
+
     /**
      * The value of the property as string.
      * The exception for properties of TEXT or X- type, those will be unescaped
      * when getting, and also expects an unescaped string when setting.
      * Datetime, numeric and other non-text types are represented as ical string
      */
     attribute AUTF8String value;
 
@@ -233,31 +233,57 @@ interface calIIcalProperty : nsISupports
     /**
      * Returns the icalcompoment this property belongs to. Please note
      * that the returned object is a raw pointer to the appropriate
      * component and is owned by libical.
      */
     [noscript,notxpcom] icalcomponentptr getIcalComponent();
 };
 
+[scriptable,uuid(41fb4cbd-9977-41c7-b179-ab567e4b9395)]
+interface calIIcsComponentParsingListener : nsISupports
+{
+    /**
+     * Called when the parsing has completed.
+     *
+     * @param rc            The result code of parsing
+     * @param rootComp      The root ical component that was parsed
+     */
+    void onParsingComplete(in nsresult rc, in calIIcalComponent rootComp);
+};
+
 [scriptable,uuid(ae4ca6c3-981b-4f66-a0ce-2f2c218ad9e3)]
 interface calIICSService : nsISupports
 {
     /**
      * Parses an ICS string and uses the passed tzProvider instance to
      * resolve timezones not contained withing the VCALENDAR.
      *
      * @param serialized     an ICS string
      * @param tzProvider     timezone provider used to resolve TZIDs
      *                       not contained within the VCALENDAR;
      *                       if null is passed, parsing falls back to
      *                       using the timezone service
      */
     calIIcalComponent parseICS(in AUTF8String serialized,
                                in calITimezoneProvider tzProvider);
 
+    /**
+     * Asynchronously parse an ICS string
+     *
+     * @param serialized     an ICS string
+     * @param tzProvider     timezone provider used to resolve TZIDs
+     *                       not contained within the VCALENDAR;
+     *                       if null is passed, parsing falls back to
+     *                       using the timezone service
+     * @param listener       The listener that notifies the root component
+     */
+    void parseICSAsync(in AUTF8String serialized,
+                       in calITimezoneProvider tzProvider,
+                       in calIIcsComponentParsingListener listener);
+
     calIIcalComponent createIcalComponent(in AUTF8String kind);
     calIIcalProperty createIcalProperty(in AUTF8String kind);
     /* I wish I could write this function atop libical!
     boolean isLegalParameterValue(in AUTF8String paramKind,
                                   in AUTF8String paramValue);
     */
 };
--- a/calendar/base/src/Makefile.in
+++ b/calendar/base/src/Makefile.in
@@ -81,17 +81,16 @@ EXTRA_SCRIPTS = \
     calAttachment.js \
     calAttendee.js \
     calCalendarManager.js \
     calCachedCalendar.js \
     calDateTimeFormatter.js \
     calEvent.js \
     calFilter.js \
     calIcsParser.js \
-    calIcsParser-worker.js \
     calIcsSerializer.js \
     calItemBase.js \
     calItipItem.js \
     calProtocolHandler.js \
     calRecurrenceInfo.js \
     calRelation.js \
     calStartupService.js \
     calTodo.js \
--- a/calendar/base/src/calICSService.cpp
+++ b/calendar/base/src/calICSService.cpp
@@ -54,18 +54,17 @@ extern "C" {
 calIcalProperty::~calIcalProperty()
 {
     if (!mParent) {
         icalproperty_free(mProperty);
     }
 }
 
 NS_IMPL_CLASSINFO(calIcalProperty, NULL, 0, CAL_ICALPROPERTY_CID)
-NS_IMPL_ISUPPORTS1(calIcalProperty, calIIcalProperty)
-NS_IMPL_CI_INTERFACE_GETTER1(calIcalProperty, calIIcalProperty)
+NS_IMPL_ISUPPORTS1_CI(calIcalProperty, calIIcalProperty)
 
 NS_IMETHODIMP_(icalproperty *)
 calIcalProperty::GetIcalProperty()
 {
     return mProperty;
 }
 
 NS_IMETHODIMP_(icalcomponent *)
@@ -94,17 +93,17 @@ calIcalProperty::ToString(nsACString& aR
 {
     return GetIcalString(aResult);
 }
 
 NS_IMETHODIMP
 calIcalProperty::GetValue(nsACString &str)
 {
     icalvalue *value = icalproperty_get_value(mProperty);
-    icalvalue_kind valuekind = icalvalue_isa(value); 
+    icalvalue_kind valuekind = icalvalue_isa(value);
 
     const char *icalstr;
     if (valuekind == ICAL_TEXT_VALUE) {
         icalstr = icalvalue_get_text(value);
     } else if (valuekind == ICAL_X_VALUE) {
         icalstr = icalvalue_get_x(value);
     } else if (valuekind == ICAL_ATTACH_VALUE) {
         icalattach *attach = icalvalue_get_attach(value);
@@ -120,17 +119,17 @@ calIcalProperty::GetValue(nsACString &st
     if (!icalstr) {
         if (icalerrno == ICAL_BADARG_ERROR) {
             str.Truncate();
             // Set string to null, because we don't have a value
             // (which is something different then an empty value)
             str.SetIsVoid(PR_TRUE);
             return NS_OK;
         }
-        
+
 #ifdef DEBUG
         fprintf(stderr, "Error getting string value: %d (%s)\n",
                 icalerrno, icalerror_strerror(icalerrno));
 #endif
         return NS_ERROR_FAILURE;
     }
 
     str.Assign(icalstr);
@@ -166,32 +165,32 @@ calIcalProperty::GetValueAsIcalString(ns
     if (!icalstr) {
         if (icalerrno == ICAL_BADARG_ERROR) {
             str.Truncate();
             // Set string to null, because we don't have a value
             // (which is something different then an empty value)
             str.SetIsVoid(PR_TRUE);
             return NS_OK;
         }
-        
+
 #ifdef DEBUG
         fprintf(stderr, "Error getting string value: %d (%s)\n",
                 icalerrno, icalerror_strerror(icalerrno));
 #endif
         return NS_ERROR_FAILURE;
     }
 
     str.Assign(icalstr);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calIcalProperty::SetValueAsIcalString(const nsACString &str)
 {
-    const char *kindstr = 
+    const char *kindstr =
         icalvalue_kind_to_string(icalproperty_kind_to_value_kind(icalproperty_isa(mProperty)));
     icalproperty_set_value_from_string(mProperty,
                                        PromiseFlatCString(str).get(),
                                        kindstr);
     return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -221,17 +220,17 @@ FindParameter(icalproperty *prop, const 
     }
     return nsnull;
 }
 
 NS_IMETHODIMP
 calIcalProperty::GetParameter(const nsACString &param, nsACString &value)
 {
     // More ridiculous parameter/X-PARAMETER handling.
-    icalparameter_kind paramkind = 
+    icalparameter_kind paramkind =
         icalparameter_string_to_kind(PromiseFlatCString(param).get());
 
     if (paramkind == ICAL_NO_PARAMETER)
         return NS_ERROR_INVALID_ARG;
 
     const char *icalstr = nsnull;
     if (paramkind == ICAL_X_PARAMETER) {
         icalparameter *icalparam = FindParameter(mProperty, param, ICAL_X_PARAMETER);
@@ -253,17 +252,17 @@ calIcalProperty::GetParameter(const nsAC
         value.Assign(icalstr);
     }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calIcalProperty::SetParameter(const nsACString &param, const nsACString &value)
 {
-    icalparameter_kind paramkind = 
+    icalparameter_kind paramkind =
         icalparameter_string_to_kind(PromiseFlatCString(param).get());
 
     if (paramkind == ICAL_NO_PARAMETER)
         return NS_ERROR_INVALID_ARG;
 
     // Because libical's support for manipulating parameters is weak, and
     // X-PARAMETERS doubly so, we walk the list looking for an existing one of
     // that name, and reset its value if found.
@@ -285,33 +284,33 @@ calIcalProperty::SetParameter(const nsAC
         // If not found, fall through to adding a new parameter below.
     } else {
         // We could try getting an existing parameter here and resetting its
         // value, but this is easier and I don't care that much about parameter
         // performance at this point.
         RemoveParameter(param);
     }
 
-    icalparameter *icalparam = 
+    icalparameter *icalparam =
         icalparameter_new_from_value_string(paramkind,
                                             PromiseFlatCString(value).get());
     if (!icalparam)
         return NS_ERROR_OUT_OF_MEMORY;
 
-    // You might ask me "why does libical not do this for us?" and I would 
+    // You might ask me "why does libical not do this for us?" and I would
     // just nod knowingly but sadly at you in return.
     //
     // You might also, if you were not too distracted by the first question,
     // ask why we have icalproperty_set_x_name but icalparameter_set_xname.
     // More nodding would ensue.
     if (paramkind == ICAL_X_PARAMETER)
         icalparameter_set_xname(icalparam, PromiseFlatCString(param).get());
     else if (paramkind == ICAL_IANA_PARAMETER)
         icalparameter_set_iana_name(icalparam, PromiseFlatCString(param).get());
-    
+
     icalproperty_add_parameter(mProperty, icalparam);
     // XXX check ical errno
     return NS_OK;
 }
 
 static nsresult
 FillParameterName(icalparameter *icalparam, nsACString &name)
 {
@@ -1127,22 +1126,22 @@ calIcalComponent::GetFirstProperty(const
     icalproperty_kind propkind =
         icalproperty_string_to_kind(PromiseFlatCString(kind).get());
 
     if (propkind == ICAL_NO_PROPERTY)
         return NS_ERROR_INVALID_ARG;
 
     icalproperty *icalprop = nsnull;
     if (propkind == ICAL_X_PROPERTY) {
-        for (icalprop = 
+        for (icalprop =
                  icalcomponent_get_first_property(mComponent, ICAL_X_PROPERTY);
              icalprop;
              icalprop = icalcomponent_get_next_property(mComponent,
                                                         ICAL_X_PROPERTY)) {
-            
+
             if (kind.Equals(icalproperty_get_x_name(icalprop)))
                 break;
         }
     } else {
         icalprop = icalcomponent_get_first_property(mComponent, propkind);
     }
 
     if (!icalprop) {
@@ -1163,22 +1162,22 @@ calIcalComponent::GetNextProperty(const 
 
     icalproperty_kind propkind =
         icalproperty_string_to_kind(PromiseFlatCString(kind).get());
 
     if (propkind == ICAL_NO_PROPERTY)
         return NS_ERROR_INVALID_ARG;
     icalproperty *icalprop = nsnull;
     if (propkind == ICAL_X_PROPERTY) {
-        for (icalprop = 
+        for (icalprop =
                  icalcomponent_get_next_property(mComponent, ICAL_X_PROPERTY);
              icalprop;
              icalprop = icalcomponent_get_next_property(mComponent,
                                                         ICAL_X_PROPERTY)) {
-            
+
             if (kind.Equals(icalproperty_get_x_name(icalprop)))
                 break;
         }
     } else {
         icalprop = icalcomponent_get_next_property(mComponent, propkind);
     }
 
     if (!icalprop) {
@@ -1263,20 +1262,71 @@ calICSService::ParseICS(const nsACString
         icalcomponent_free(ical);
         return NS_ERROR_OUT_OF_MEMORY;
     }
     NS_ADDREF(*component = comp);
     return NS_OK;
 }
 
 NS_IMETHODIMP
+calICSService::ParserWorker::Run()
+{
+    icalcomponent *ical =
+        icalparser_parse_string(PromiseFlatCString(mString).get());
+    nsresult status = NS_OK;
+    calIIcalComponent *comp = nsnull;
+
+    if (ical) {
+        comp = new calIcalComponent(ical, nsnull, mProvider);
+        if (!comp) {
+            icalcomponent_free(ical);
+            status = NS_ERROR_OUT_OF_MEMORY;
+        }
+    } else {
+        status = calIErrors::ICS_ERROR_BASE + icalerrno;
+    }
+
+    nsCOMPtr<nsIRunnable> completer = new ParserWorkerCompleter(status, comp, mListener);
+    mThread->Dispatch(completer, NS_DISPATCH_NORMAL);
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+calICSService::ParserWorker::ParserWorkerCompleter::Run()
+{
+    mListener->OnParsingComplete(mStatus, mComp);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+calICSService::ParseICSAsync(const nsACString& serialized,
+                             calITimezoneProvider *tzProvider,
+                             calIIcsComponentParsingListener *listener)
+{
+    nsresult rv;
+    NS_ENSURE_ARG_POINTER(listener);
+
+    nsCOMPtr<nsIThread> workerThread;
+    nsCOMPtr<nsIThread> currentThread;
+    rv = NS_GetCurrentThread(getter_AddRefs(currentThread));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIRunnable> worker = new ParserWorker(currentThread, serialized, tzProvider, listener);
+    NS_ENSURE_TRUE(worker, NS_ERROR_OUT_OF_MEMORY);
+    rv = NS_NewThread(getter_AddRefs(workerThread), worker);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 calICSService::CreateIcalComponent(const nsACString &kind, calIIcalComponent **comp)
 {
     NS_ENSURE_ARG_POINTER(comp);
-    icalcomponent_kind compkind = 
+    icalcomponent_kind compkind =
         icalcomponent_string_to_kind(PromiseFlatCString(kind).get());
 
     // Maybe someday I'll support X-COMPONENTs
     if (compkind == ICAL_NO_COMPONENT || compkind == ICAL_X_COMPONENT)
         return NS_ERROR_INVALID_ARG;
 
     icalcomponent *ical = icalcomponent_new(compkind);
     if (!ical)
@@ -1291,17 +1341,17 @@ calICSService::CreateIcalComponent(const
     NS_ADDREF(*comp);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calICSService::CreateIcalProperty(const nsACString &kind, calIIcalProperty **prop)
 {
     NS_ENSURE_ARG_POINTER(prop);
-    icalproperty_kind propkind = 
+    icalproperty_kind propkind =
         icalproperty_string_to_kind(PromiseFlatCString(kind).get());
 
     if (propkind == ICAL_NO_PROPERTY)
         return NS_ERROR_INVALID_ARG;
 
     icalproperty *icalprop = icalproperty_new(propkind);
     if (!icalprop)
         return NS_ERROR_OUT_OF_MEMORY; // XXX translate
--- a/calendar/base/src/calICSService.h
+++ b/calendar/base/src/calICSService.h
@@ -37,26 +37,63 @@
  * ***** END LICENSE BLOCK ***** */
 #if !defined(INCLUDED_CALICSSERVICE_H)
 #define INCLUDED_CALICSSERVICE_H
 
 #include "nsCOMPtr.h"
 #include "calIICSService.h"
 #include "calITimezoneProvider.h"
 #include "nsInterfaceHashtable.h"
+#include "nsThreadUtils.h"
 #include "calUtils.h"
 
 extern "C" {
 #include "ical.h"
 }
 
 class calICSService : public calIICSService,
                       public nsIClassInfo,
                       public cal::XpcomBase
 {
+protected:
+    class ParserWorker : public nsRunnable {
+    public:
+      ParserWorker(nsIThread *callingThread,
+                   const nsACString &icsString,
+                   calITimezoneProvider *tzProvider,
+                   calIIcsComponentParsingListener *listener) :
+        mString(icsString), mProvider(tzProvider),
+         mListener(listener), mThread(callingThread)
+      {
+      }
+
+      NS_DECL_NSIRUNNABLE
+
+    protected:
+      nsCString mString;
+      nsCOMPtr<calITimezoneProvider> mProvider;
+      nsCOMPtr<calIIcsComponentParsingListener> mListener;
+      nsCOMPtr<nsIThread> mThread;
+
+      class ParserWorkerCompleter : public nsRunnable {
+      public:
+        ParserWorkerCompleter(nsresult status,
+                              calIIcalComponent *component,
+                              calIIcsComponentParsingListener *listener) :
+          mListener(listener), mComp(component), mStatus(status)
+        {
+        }
+
+        NS_DECL_NSIRUNNABLE
+      protected:
+        nsCOMPtr<calIIcsComponentParsingListener> mListener;
+        nsCOMPtr<calIIcalComponent> mComp;
+        nsresult mStatus;
+      };
+    };
 public:
     calICSService();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSICLASSINFO
     NS_DECL_CALIICSSERVICE
 };
 
@@ -99,17 +136,17 @@ public:
     {
         mReferencedTimezones.Init();
     }
 
     // VTIMEZONE ctor
     calIcalComponent(icaltimezone * icaltz, icalcomponent * ical) : mComponent(ical), mTimezone(icaltz) {
         mReferencedTimezones.Init();
     }
-    
+
     NS_DECL_ISUPPORTS
     NS_DECL_NSICLASSINFO
     NS_DECL_CALIICALCOMPONENT
 
 protected:
     virtual ~calIcalComponent();
 
     calITimezoneProvider * getTzProvider() const {
deleted file mode 100644
--- a/calendar/base/src/calIcsParser-worker.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Calendar code.
- *
- * The Initial Developer of the Original Code is
- *   Philipp Kewisch <mozilla@kewis.ch>
- * Portions created by the Initial Developer are Copyright (C) 2010-2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/**
- * Worker to parse ics service messages
- * NOTE: Since this is a ChromeWorker, access to xpcom is limited!
- */
- 
-function onmessage(event) {
-    let icsService = XPCOM.getService("@mozilla.org/calendar/ics-service;1");
-    let rootComp = icsService.parseICS(event.data.icsString, event.data.tzProvider);
-
-    postMessage(rootComp);
-}
--- a/calendar/base/src/calIcsParser.js
+++ b/calendar/base/src/calIcsParser.js
@@ -65,184 +65,124 @@ calIcsParser.prototype = {
     getHelperForLanguage: function ip_getHelperForLanguage(language) {
         return null;
     },
 
     implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
     flags: Components.interfaces.nsIClassInfo.THREADSAFE,
 
     QueryInterface: function ip_QueryInterface(aIID) {
-        return doQueryInterface(this, calIcsParser.prototype, aIID, null, this);
+        return cal.doQueryInterface(this, calIcsParser.prototype, aIID, null, this);
     },
 
-    processIcalComponent: function ip_processIcalComponent(rootComp) {
+    processIcalComponent: function ip_processIcalComponent(rootComp, aAsyncParsing) {
         let calComp;
         // libical returns the vcalendar component if there is just one vcalendar.
         // If there are multiple vcalendars, it returns an xroot component, with
-        // those vcalendar children. We need to handle both.
+        // vcalendar children. We need to handle both cases.
         if (rootComp) {
             if (rootComp.componentType == 'VCALENDAR') {
                 calComp = rootComp;
             } else {
                 calComp = rootComp.getFirstSubcomponent('VCALENDAR');
             }
         }
 
         if (!calComp) {
-            cal.ERROR("Parser Error. Could not find 'VCALENDAR' component in: \n" + rootComp + "\nStack: \n" + cal.STACK(10));
+            cal.ERROR("Parser Error. Could not find 'VCALENDAR' component: \n" +
+                      rootComp + "\nStack: \n" + cal.STACK(10));
         }
 
-        let uid2parent = {};
-        let excItems = [];
-        let fakedParents = {};
-
-        let tzErrors = {};
-        function checkTimezone(item, dt) {
-            if (dt && cal.isPhantomTimezone(dt.timezone)) {
-                let tzid = dt.timezone.tzid;
-                let hid = item.hashId + "#" + tzid;
-                if (tzErrors[hid] === undefined) {
-                    // For now, publish errors to console and alert user.
-                    // In future, maybe make them available through an interface method
-                    // so this UI code can be removed from the parser, and caller can
-                    // choose whether to alert, or show user the problem items and ask
-                    // for fixes, or something else.
-                    let msg = (calGetString("calendar", "unknownTimezoneInItem",
-                                            [tzid, item.title, cal.getDateFormatter().formatDateTime(dt)]) +
-                               "\n" + item.icalString);
-                    cal.ERROR(msg);
-                    tzErrors[hid] = true;
-                }
-            }
-        }
+        let self = this;
+        let state = new parserState(this, aAsyncParsing);
 
         while (calComp) {
-
-            // Get unknown properties
-            this.mProperties = [ prop for (prop in cal.ical.propertyIterator(calComp))
-                                      if (prop.propertyName != "VERSION" &&
-                                          prop.propertyName != "PRODID") ];
-
-            let prodId = calComp.getFirstProperty("PRODID");
-            let isFromOldSunbird = (prodId && prodId.value == "-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN");
+            // Get unknown properties from the VCALENDAR
+            this.mProperties = this.mProperties.concat(
+                [ prop for (prop in cal.ical.propertyIterator(calComp))
+                        if (prop.propertyName != "VERSION" &&
+                            prop.propertyName != "PRODID") ]);
 
             for (let subComp in cal.ical.subcomponentIterator(calComp)) {
-                let item = null;
-                switch (subComp.componentType) {
-                    case "VEVENT":
-                        item = cal.createEvent();
-                        item.icalComponent = subComp;
-                        checkTimezone(item, item.startDate);
-                        checkTimezone(item, item.endDate);
-                        break;
-                    case "VTODO":
-                        item = cal.createTodo();
-                        item.icalComponent = subComp;
-                        checkTimezone(item, item.entryDate);
-                        checkTimezone(item, item.dueDate);
-                        // completed is defined to be in UTC
-                        break;
-                    case "VTIMEZONE":
-                        // this should already be attached to the relevant
-                        // events in the calendar, so there's no need to
-                        // do anything with it here.
-                        break;
-                    default:
-                        this.mComponents.push(subComp);
-                        break;
-                }
-
-                if (item) {
-                    // Only try to fix ICS from Sunbird 0.2 (and earlier) if it
-                    // has an EXDATE.
-                    let hasExdate = subComp.getFirstProperty("EXDATE");
-                    if (isFromOldSunbird && hasExdate) {
-                        item = fixOldSunbirdExceptions(item);
-                    }
-
-                    let rid = item.recurrenceId;
-                    if (!rid) {
-                        this.mItems.push(item);
-                        if (item.recurrenceInfo) {
-                            uid2parent[item.id] = item;
-                        }
-                    } else {
-                        excItems.push(item);
-                    }
-                }
+                state.submit(subComp);
             }
             calComp = rootComp.getNextSubcomponent("VCALENDAR");
         }
 
-        // tag "exceptions", i.e. items with rid:
-        for each (let item in excItems) {
-            let parent = uid2parent[item.id];
+        state.join(function() {
+            let fakedParents = {};
+            // tag "exceptions", i.e. items with rid:
+            for each (let item in state.excItems) {
+                let parent = state.uid2parent[item.id];
 
-            if (!parent) { // a parentless one, fake a master and override it's occurrence
-                parent = isEvent(item) ? createEvent() : createTodo();
-                parent.id = item.id;
-                parent.setProperty("DTSTART", item.recurrenceId);
-                parent.setProperty("X-MOZ-FAKED-MASTER", "1"); // this tag might be useful in the future
-                parent.recurrenceInfo = cal.createRecurrenceInfo(parent);
-                fakedParents[item.id] = true;
-                uid2parent[item.id] = parent;
-                this.mItems.push(parent);
-            }
-            if (item.id in fakedParents) {
-                let rdate = Components.classes["@mozilla.org/calendar/recurrence-date;1"]
-                                      .createInstance(Components.interfaces.calIRecurrenceDate);
-                rdate.date = item.recurrenceId;
-                parent.recurrenceInfo.appendRecurrenceItem(rdate);
-                // we'll keep the parentless-API until we switch over using itip-process for import (e.g. in dnd code)
-                this.mParentlessItems.push(item);
+                if (!parent) { // a parentless one, fake a master and override it's occurrence
+                    parent = isEvent(item) ? createEvent() : createTodo();
+                    parent.id = item.id;
+                    parent.setProperty("DTSTART", item.recurrenceId);
+                    parent.setProperty("X-MOZ-FAKED-MASTER", "1"); // this tag might be useful in the future
+                    parent.recurrenceInfo = cal.createRecurrenceInfo(parent);
+                    fakedParents[item.id] = true;
+                    state.uid2parent[item.id] = parent;
+                    state.items.push(parent);
+                }
+                if (item.id in fakedParents) {
+                    let rdate = Components.classes["@mozilla.org/calendar/recurrence-date;1"]
+                                          .createInstance(Components.interfaces.calIRecurrenceDate);
+                    rdate.date = item.recurrenceId;
+                    parent.recurrenceInfo.appendRecurrenceItem(rdate);
+                    // we'll keep the parentless-API until we switch over using itip-process for import (e.g. in dnd code)
+                    self.mParentlessItems.push(item);
+                }
+
+                parent.recurrenceInfo.modifyException(item, true);
             }
 
-            parent.recurrenceInfo.modifyException(item, true);
-        }
+            if (Object.keys(state.tzErrors).length > 0) {
+                // Use an alert rather than a prompt because problems may appear in
+                // remote subscribed calendars the user cannot change.
+                if (Components.classes["@mozilla.org/alerts-service;1"]) {
+                    let notifier = Components.classes["@mozilla.org/alerts-service;1"]
+                                             .getService(Components.interfaces.nsIAlertsService);
+                    let title = calGetString("calendar", "TimezoneErrorsAlertTitle")
+                    let text = calGetString("calendar", "TimezoneErrorsSeeConsole");
+                    notifier.showAlertNotification("", title, text, false, null, null, title);
+                }
+            }
 
-        for (let e in tzErrors) { // if any error has occurred
-            // Use an alert rather than a prompt because problems may appear in
-            // remote subscribed calendars the user cannot change.
-            if (Components.classes["@mozilla.org/alerts-service;1"]) {
-                let notifier = Components.classes["@mozilla.org/alerts-service;1"]
-                                         .getService(Components.interfaces.nsIAlertsService);
-                let title = calGetString("calendar", "TimezoneErrorsAlertTitle")
-                let text = calGetString("calendar", "TimezoneErrorsSeeConsole");
-                notifier.showAlertNotification("", title, text, false, null, null, title);
+            // We are done, push the items to the parser and notify the listener
+            self.mItems = self.mItems.concat(state.items);
+            self.mComponents = self.mComponents.concat(state.extraComponents);
+
+            if (aAsyncParsing) {
+                aAsyncParsing.onParsingComplete(Components.results.NS_OK, self);
             }
-            break;
-        }
+        });
     },
 
     parseString: function ip_parseString(aICSString, aTzProvider, aAsyncParsing) {
         if (aAsyncParsing) {
-            let this_ = this;
-            let rootComp = null;
+            let self = this;
 
-            let wf = Components.classes["@mozilla.org/threads/workerfactory;1"]
-                               .createInstance(Components.interfaces.nsIWorkerFactory);
-
-            let worker = wf.newChromeWorker("resource://calendar/calendar-js/calIcsParser-worker.js");
+            let start = new Date();
 
-            worker.onmessage = {
-                handleEvent: function(event) {
-                    let rootComp = event.data.QueryInterface(Components.interfaces.calIIcalComponent);
-                    this_.processIcalComponent(rootComp);
-                    aAsyncParsing.onParsingComplete(Components.results.NS_OK, this_);
+            // We are using two types of very similar listeners here:
+            // aAsyncParsing is a calIcsParsingListener that returns the ics
+            //   parser containing the processed items.
+            // The listener passed to parseICSAsync is a calICsComponentParsingListener
+            //   required by the ics service, that receives the parsed root component.
+            cal.getIcsService().parseICSAsync(aICSString, aTzProvider, {
+                onParsingComplete: function(rc, rootComp) {
+                    if (Components.isSuccessCode(rc)) {
+                        self.processIcalComponent(rootComp, aAsyncParsing);
+                    } else {
+                        cal.ERROR("Error Parsing ICS: " + rc);
+                        aAsyncParsing.onParsingComplete(rc, self);
+                    }
                 }
-            };
-
-            worker.onerror = {
-                handleEvent: function(error) {
-                    cal.ERROR("Error Parsing ICS: " + error.message);
-                    aAsyncParsing.onParsingComplete(Components.results.NS_ERROR_FAILURE, this_);
-                }
-            };
-
-            worker.postMessage({ icsString: aICSString, tzProvider: aTzProvider });
+            });
         } else {
             this.processIcalComponent(cal.getIcsService().parseICS(aICSString, aTzProvider));
         }
     },
 
     parseFromStream: function ip_parseFromStream(aStream, aTzProvider, aAsyncParsing) {
         // Read in the string. Note that it isn't a real string at this point,
         // because likely, the file is utf8. The multibyte chars show up as multiple
@@ -293,40 +233,145 @@ calIcsParser.prototype = {
     },
 
     getComponents: function ip_getComponents(aCount) {
         aCount.value = this.mComponents.length;
         return this.mComponents.concat([]); //clone
     }
 };
 
+/**
+ * The parser state, which helps process ical components without clogging up the
+ * event queue.
+ *
+ * @param aParser       The parser that is using this state
+ */
+function parserState(aParser) {
+    this.parser = aParser;
 
-// Helper function to deal with the busted exdates from Sunbird 0.2
-// When Sunbird 0.2 (and earlier) creates EXDATEs, they are set to
-// 00:00:00 floating rather than to the item's DTSTART. This fixes that.
-// (bug 354073)
-function fixOldSunbirdExceptions(aItem) {
-    const kCalIRecurrenceDate = Components.interfaces.calIRecurrenceDate;
+    this.extraComponents = [];
+    this.items = [];
+    this.uid2parent = {};
+    this.excItems = [];
+    this.tzErrors = {};
+}
+
+parserState.prototype = {
+    parser: null,
+    joinFunc: null,
+    threadCount: 0,
 
-    let item = aItem;
-    let ritems = aItem.recurrenceInfo.getRecurrenceItems({});
-    for each (let ritem in ritems) {
-        // EXDATEs are represented as calIRecurrenceDates, which are
-        // negative and finite.
-        if (calInstanceOf(ritem, kCalIRecurrenceDate) &&
-            ritem.isNegative &&
-            ritem.isFinite) {
-            // Only mess with the exception if its time is wrong.
-            let oldDate = aItem.startDate || aItem.entryDate;
-            if (ritem.date.compare(oldDate) != 0) {
-                let newRitem = ritem.clone();
-                // All we want from aItem is the time and timezone.
-                newRitem.date.timezone = oldDate.timezone;
-                newRitem.date.hour     = oldDate.hour;
-                newRitem.date.minute   = oldDate.minute;
-                newRitem.date.second   = oldDate.second;
-                item.recurrenceInfo.appendRecurrenceItem(newRitem);
-                item.recurrenceInfo.deleteRecurrenceItem(ritem);
+    extraComponents: null,
+    items: null,
+    uid2parent: null,
+    excItems: null,
+    tzErrors: null,
+
+    /**
+     * Checks if the timezones are missing and notifies the user via error console
+     *
+     * @param item      The item to check for
+     * @param dt        The datetime object to check with
+     */
+    checkTimezone: function checkTimezone(item, dt) {
+        if (dt && cal.isPhantomTimezone(dt.timezone)) {
+            let tzid = dt.timezone.tzid;
+            let hid = item.hashId + "#" + tzid;
+            if (!(hid in this.tzErrors)) {
+                // For now, publish errors to console and alert user.
+                // In future, maybe make them available through an interface method
+                // so this UI code can be removed from the parser, and caller can
+                // choose whether to alert, or show user the problem items and ask
+                // for fixes, or something else.
+                let msg = (calGetString("calendar", "unknownTimezoneInItem",
+                                        [tzid, item.title, cal.getDateFormatter().formatDateTime(dt)]) +
+                           "\n" + item.icalString);
+                cal.ERROR(msg);
+                this.tzErrors[hid] = true;
             }
         }
+    },
+
+    /**
+     * Submit processing of a subcomponent to the event queue
+     *
+     * @param subComp       The component to process
+     */
+    submit: function submit(subComp) {
+        let state = this;
+        let runner = {
+            run: function run() {
+                let item = null;
+                switch (subComp.componentType) {
+                    case "VEVENT":
+                        item = cal.createEvent();
+                        item.icalComponent = subComp;
+                        state.checkTimezone(item, item.startDate);
+                        state.checkTimezone(item, item.endDate);
+                        break;
+                    case "VTODO":
+                        item = cal.createTodo();
+                        item.icalComponent = subComp;
+                        state.checkTimezone(item, item.entryDate);
+                        state.checkTimezone(item, item.dueDate);
+                        // completed is defined to be in UTC
+                        break;
+                    case "VTIMEZONE":
+                        // this should already be attached to the relevant
+                        // events in the calendar, so there's no need to
+                        // do anything with it here.
+                        break;
+                    default:
+                        state.extraComponents.push(subComp);
+                        break;
+                }
+
+                if (item) {
+                    let rid = item.recurrenceId;
+                    if (!rid) {
+                        state.items.push(item);
+                        if (item.recurrenceInfo) {
+                            state.uid2parent[item.id] = item;
+                        }
+                    } else {
+                        state.excItems.push(item);
+                    }
+                }
+                state.threadCount--;
+                state.checkCompletion();
+            }
+        };
+
+        this.threadCount++;
+        if (this.listener) {
+            // If we have a listener, we are doing this asynchronously. Go ahead
+            // and use the thread manager to dispatch the above runner
+            Services.tm.currentThread.dispatch(runner, Components.interfaces.nsIEventTarget.DISPATCH_NORMAL);
+        } else {
+            // No listener means synchonous. Just run the runner instead
+            runner.run();
+        }
+    },
+
+    /**
+     * Checks if the processing of all events has completed. If a join function
+     * has been set, this function is called.
+     *
+     * @return      True, if all tasks have been completed
+     */
+    checkCompletion: function() {
+        if (this.joinFunc && this.threadCount == 0) {
+            this.joinFunc();
+            return true;
+        }
+        return false;
+    },
+
+    /**
+     * Sets a join function that is called when all tasks have been completed
+     *
+     * @param joinFunc      The join function to call
+     */
+    join: function join(joinFunc) {
+        this.joinFunc = joinFunc;
+        this.checkCompletion();
     }
-    return item;
-}
+};
--- a/calendar/base/src/calRecurrenceDate.cpp
+++ b/calendar/base/src/calRecurrenceDate.cpp
@@ -178,17 +178,17 @@ calRecurrenceDate::GetOccurrences(calIDa
 {
     NS_ENSURE_ARG_POINTER(aStartTime);
     NS_ENSURE_ARG_POINTER(aRangeStart);
 
     PRInt32 r1, r2;
 
     if (mDate) {
         if (NS_SUCCEEDED(mDate->Compare(aRangeStart, &r1)) && r1 >= 0 &&
-            (!aRangeEnd || NS_SUCCEEDED(mDate->Compare(aRangeEnd, &r2)) && r2 < 0))
+            (!aRangeEnd || (NS_SUCCEEDED(mDate->Compare(aRangeEnd, &r2)) && r2 < 0)))
         {
             calIDateTime **dates = (calIDateTime **) nsMemory::Alloc(sizeof(calIDateTime*));
             NS_ADDREF (dates[0] = mDate);
             *aDates = dates;
             *aCount = 1;
             return NS_OK;
         }
     }