Bug 1440587 - Move calProviderUtils to the cal.provider namespace - manual provider changes. r=MakeMyDay
authorPhilipp Kewisch <mozilla@kewis.ch>
Thu, 22 Feb 2018 21:48:52 +0100
changeset 30553 96aefde64d3b8eb4dc3843cf4c5c3747f00c02eb
parent 30552 54bc3fdbf248c62c8e40ecca1f8128a39bf2ac17
child 30554 7f803e1812568168252d97d586ea3e66bab610dc
push id2172
push usermozilla@kewis.ch
push dateSun, 15 Apr 2018 05:33:14 +0000
treeherdercomm-beta@33a67b0129b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMakeMyDay
bugs1440587
Bug 1440587 - Move calProviderUtils to the cal.provider namespace - manual provider changes. r=MakeMyDay MozReview-Commit-ID: 2JU8zMeWKiy
calendar/.eslintrc.js
calendar/base/modules/calProviderUtils.jsm
calendar/base/modules/calUtils.jsm
calendar/base/modules/calUtilsCompat.jsm
calendar/base/src/calCachedCalendar.js
calendar/base/src/calCalendarManager.js
calendar/providers/caldav/calDavCalendar.js
calendar/providers/gdata/components/calGoogleCalendar.js
calendar/providers/gdata/modules/calUtilsShim.jsm
calendar/providers/gdata/modules/gdataSession.jsm
calendar/providers/gdata/modules/gdataUtils.jsm
calendar/providers/ics/calICSCalendar.js
calendar/providers/memory/calMemoryCalendar.js
calendar/providers/storage/calStorageCalendar.js
calendar/providers/storage/calStorageUpgrade.jsm
calendar/test/unit/test_freebusy_service.js
calendar/test/unit/test_gdata_provider.js
calendar/test/unit/test_rfc3339_parser.js
--- a/calendar/.eslintrc.js
+++ b/calendar/.eslintrc.js
@@ -482,16 +482,17 @@ module.exports = {
         "no-else-return": 0,
     },
     "overrides": [{
         files: [
             "base/modules/calEmailUtils.jsm",
             "base/modules/calItipUtils.jsm",
             "base/modules/calUnifinderUtils.jsm",
             "base/modules/calL10NUtils.jsm",
+            "base/modules/calProviderUtils.jsm",
         ],
         rules: {
             "require-jsdoc": [2, { require: { ClassDeclaration: true } }],
 
             "valid-jsdoc": [2, {
                 prefer: { returns: "return" },
                 preferType: {
                     "boolean": "Boolean",
--- a/calendar/base/modules/calProviderUtils.jsm
+++ b/calendar/base/modules/calProviderUtils.jsm
@@ -1,722 +1,772 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource:///modules/mailServices.js");
-ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calAuthUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "cal", "resource://calendar/modules/calUtils.jsm", "cal");
+
 /*
  * Provider helper code
  */
 
-this.EXPORTED_SYMBOLS = ["cal"]; // even though it's defined in calUtils.jsm, import needs this
+this.EXPORTED_SYMBOLS = ["calprovider"]; /* exported calprovider */
+
+var calprovider = {
+    /**
+     * Prepare HTTP channel with standard request headers and upload data/content-type if needed.
+     *
+     * @param {nsIURI} aUri                                     Channel Uri, will only be used for a
+     *                                                            new channel.
+     * @param {nsIInputStream|String} aUploadData               Data to be uploaded, if any. This
+     *                                                            may be a nsIInputStream or string
+     *                                                            data. In the latter case the
+     *                                                            string will be converted to an
+     *                                                            input stream.
+     * @param {String} aContentType                             Value for Content-Type header, if any
+     * @param {nsIInterfaceRequestor} aNotificationCallbacks    Calendar using channel
+     * @param {?nsIChannel} aExisting                           An existing channel to modify (optional)
+     * @return {nsIChannel}                                     The prepared channel
+     */
+    prepHttpChannel: function(aUri, aUploadData, aContentType, aNotificationCallbacks, aExisting=null) {
+        let channel = aExisting || Services.io.newChannelFromURI2(aUri,
+                                                                  null,
+                                                                  Services.scriptSecurityManager.getSystemPrincipal(),
+                                                                  null,
+                                                                  Components.interfaces.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                                                                  Components.interfaces.nsIContentPolicy.TYPE_OTHER);
+        let httpchannel = channel.QueryInterface(Components.interfaces.nsIHttpChannel);
+
+        httpchannel.setRequestHeader("Accept", "text/xml", false);
+        httpchannel.setRequestHeader("Accept-Charset", "utf-8,*;q=0.1", false);
+        httpchannel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
+        httpchannel.notificationCallbacks = aNotificationCallbacks;
+
+        if (aUploadData) {
+            httpchannel = httpchannel.QueryInterface(Components.interfaces.nsIUploadChannel);
+            let stream;
+            if (aUploadData instanceof Components.interfaces.nsIInputStream) {
+                // Make sure the stream is reset
+                stream = aUploadData.QueryInterface(Components.interfaces.nsISeekableStream);
+                stream.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0);
+            } else {
+                // Otherwise its something that should be a string, convert it.
+                let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+                                          .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+                converter.charset = "UTF-8";
+                stream = converter.convertToInputStream(aUploadData.toString());
+            }
+
+            httpchannel.setUploadStream(stream, aContentType, -1);
+        }
+
+        return httpchannel;
+    },
+
+    /**
+     * Send prepared HTTP request asynchronously
+     *
+     * @param {nsIStreamLoader} aStreamLoader       Stream loader for request
+     * @param {nsIChannel} aChannel                 Channel for request
+     * @param {nsIStreamLoaderObserver} aListener   Listener for method completion
+     */
+    sendHttpRequest: function(aStreamLoader, aChannel, aListener) {
+        aStreamLoader.init(aListener);
+        aChannel.asyncOpen(aStreamLoader, aChannel);
+    },
+
+    /**
+     * Shortcut to create an nsIStreamLoader
+     *
+     * @return {nsIStreamLoader}        A fresh streamloader
+     */
+    createStreamLoader: function() {
+        return Components.classes["@mozilla.org/network/stream-loader;1"]
+                         .createInstance(Components.interfaces.nsIStreamLoader);
+    },
+
+    /**
+     * Convert a byte array to a string
+     *
+     * @param {octet[]} aResult         The bytes to convert
+     * @param {Number} aResultLength    The number of bytes
+     * @param {String} aCharset         The character set of the bytes, defaults to utf-8
+     * @param {Boolean} aThrow          If true, the function will raise an exception on error
+     * @return {?String}                The string result, or null on error
+     */
+    convertByteArray: function(aResult, aResultLength, aCharset, aThrow) {
+        try {
+            let resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+                                            .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+            resultConverter.charset = aCharset || "UTF-8";
+            return resultConverter.convertFromByteArray(aResult, aResultLength);
+        } catch (e) {
+            if (aThrow) {
+                throw e;
+            }
+        }
+        return null;
+    },
 
-/**
- * Prepare HTTP channel with standard request headers and upload
- * data/content-type if needed
- *
- * @param arUri                      Channel Uri, will only be used for a new
- *                                     channel.
- * @param aUploadData                Data to be uploaded, if any. This may be a
- *                                     nsIInputStream or string data. In the
- *                                     latter case the string will be converted
- *                                     to an input stream.
- * @param aContentType               Value for Content-Type header, if any
- * @param aNotificationCallbacks     Calendar using channel
- * @param aExisting                  An existing channel to modify (optional)
- */
-cal.prepHttpChannel = function(aUri, aUploadData, aContentType, aNotificationCallbacks, aExisting) {
-    let channel = aExisting || Services.io.newChannelFromURI2(aUri,
-                                                              null,
-                                                              Services.scriptSecurityManager.getSystemPrincipal(),
-                                                              null,
-                                                              Components.interfaces.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
-                                                              Components.interfaces.nsIContentPolicy.TYPE_OTHER);
-    let httpchannel = channel.QueryInterface(Components.interfaces.nsIHttpChannel);
+    /**
+     * getInterface method for providers. This should be called in the context of
+     * the respective provider, i.e
+     *
+     * return cal.provider.InterfaceRequestor_getInterface.apply(this, arguments);
+     *
+     * or
+     * ...
+     * getInterface: cal.provider.InterfaceRequestor_getInterface,
+     * ...
+     *
+     * NOTE: If the server only provides one realm for all calendars, be sure that
+     * the |this| object implements calICalendar. In this case the calendar name
+     * will be appended to the realm. If you need that feature disabled, see the
+     * capabilities section of calICalendar.idl
+     *
+     * @param {nsIIDRef} aIID       The interface ID to return
+     * @return {nsISupports}        The requested interface
+     */
+    InterfaceRequestor_getInterface: function(aIID) {
+        try {
+            // Try to query the this object for the requested interface but don't
+            // throw if it fails since that borks the network code.
+            return this.QueryInterface(aIID);
+        } catch (e) {
+            // Support Auth Prompt Interfaces
+            if (aIID.equals(Components.interfaces.nsIAuthPrompt2)) {
+                if (!this.calAuthPrompt) {
+                    this.calAuthPrompt = new cal.auth.Prompt();
+                }
+                return this.calAuthPrompt;
+            } else if (aIID.equals(Components.interfaces.nsIAuthPromptProvider) ||
+                       aIID.equals(Components.interfaces.nsIPrompt)) {
+                return Services.ww.getNewPrompter(null);
+            } else if (aIID.equals(Components.interfaces.nsIBadCertListener2)) {
+                if (!this.badCertHandler) {
+                    this.badCertHandler = new cal.provider.BadCertHandler(this);
+                }
+                return this.badCertHandler;
+            } else {
+                Components.returnCode = e;
+            }
+        }
+        return null;
+    },
+
+    /**
+     * Bad Certificate Handler for Network Requests. Shows the Network Exception
+     * Dialog if a certificate Problem occurs.
+     */
+    BadCertHandler: class {
+        QueryInterface(iid) {
+            return cal.generateClassQI(this, iid, [Components.interfaces.nsIBadCertListener2]);
+        }
 
-    httpchannel.setRequestHeader("Accept", "text/xml", false);
-    httpchannel.setRequestHeader("Accept-Charset", "utf-8,*;q=0.1", false);
-    httpchannel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
-    httpchannel.notificationCallbacks = aNotificationCallbacks;
+        constructor(thisProvider) {
+            this.thisProvider = thisProvider;
+            this.timer = null;
+        }
+
+        notifyCertProblem(socketInfo, status, targetSite) {
+            // Unfortunately we can't pass js objects using the window watcher, so
+            // we'll just take the first available calendar window. We also need to
+            // do this on a timer so that the modal window doesn't block the
+            // network request.
+            let calWindow = cal.window.getCalendarWindow();
 
-    if (aUploadData) {
-        httpchannel = httpchannel.QueryInterface(Components.interfaces.nsIUploadChannel);
-        let stream;
-        if (aUploadData instanceof Components.interfaces.nsIInputStream) {
-            // Make sure the stream is reset
-            stream = aUploadData.QueryInterface(Components.interfaces.nsISeekableStream);
-            stream.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0);
-        } else {
-            // Otherwise its something that should be a string, convert it.
-            let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
-                                      .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
-            converter.charset = "UTF-8";
-            stream = converter.convertToInputStream(aUploadData.toString());
+            let timerCallback = {
+                thisProvider: this.thisProvider,
+                notify: function(timer) {
+                    let params = {
+                        exceptionAdded: false,
+                        sslStatus: status,
+                        prefetchCert: true,
+                        location: targetSite
+                    };
+                    calWindow.openDialog("chrome://pippki/content/exceptionDialog.xul",
+                                         "",
+                                         "chrome,centerscreen,modal",
+                                         params);
+                    if (this.thisProvider.canRefresh &&
+                        params.exceptionAdded) {
+                        // Refresh the provider if the
+                        // exception certificate was added
+                        this.thisProvider.refresh();
+                    }
+                }
+            };
+            this.timer = Components.classes["@mozilla.org/timer;1"]
+                                   .createInstance(Components.interfaces.nsITimer);
+            this.timer.initWithCallback(
+                timerCallback,
+                0,
+                Components.interfaces.nsITimer.TYPE_ONE_SHOT
+            );
+            return true;
+        }
+    },
+
+    /**
+     * Freebusy interval implementation. All parameters are optional.
+     *
+     * @param aCalId         The calendar id to set up with.
+     * @param aFreeBusyType  The type from calIFreeBusyInterval.
+     * @param aStart         The start of the interval.
+     * @param aEnd           The end of the interval.
+     * @return               The fresh calIFreeBusyInterval.
+     */
+    FreeBusyInterval: class {
+        QueryInterface(iid) {
+            return cal.generateClassQI(this, iid, [Components.interfaces.calIFreeBusyInterval]);
         }
 
-        httpchannel.setUploadStream(stream, aContentType, -1);
-    }
+        constructor(aCalId, aFreeBusyType, aStart, aEnd) {
+            this.calId = aCalId;
+            this.interval = Components.classes["@mozilla.org/calendar/period;1"]
+                                      .createInstance(Components.interfaces.calIPeriod);
+            this.interval.start = aStart;
+            this.interval.end = aEnd;
 
-    return httpchannel;
-};
+            this.freeBusyType = aFreeBusyType || Components.interfaces.calIFreeBusyInterval.UNKNOWN;
+        }
+    },
 
-/**
- * calSendHttpRequest; send prepared HTTP request
- *
- * @param aStreamLoader     streamLoader for request
- * @param aChannel          channel for request
- * @param aListener         listener for method completion
- */
-cal.sendHttpRequest = function(aStreamLoader, aChannel, aListener) {
-    aStreamLoader.init(aListener);
-    aChannel.asyncOpen(aStreamLoader, aChannel);
-};
+    /**
+     * Gets the iTIP/iMIP transport if the passed calendar has configured email.
+     *
+     * @param {calICalendar} aCalendar      The calendar to get the transport for
+     * @return {?calIItipTransport}         The email transport, or null if no identity configured
+     */
+    getImipTransport: function(aCalendar) {
+        // assure an identity is configured for the calendar
+        if (aCalendar && aCalendar.getProperty("imip.identity")) {
+            return Components.classes["@mozilla.org/calendar/itip-transport;1?type=email"]
+                             .getService(Components.interfaces.calIItipTransport);
+        }
+        return null;
+    },
 
-cal.createStreamLoader = function() {
-    return Components.classes["@mozilla.org/network/stream-loader;1"]
-                     .createInstance(Components.interfaces.nsIStreamLoader);
-};
+    /**
+     * Gets the configured identity and account of a particular calendar instance, or null.
+     *
+     * @param {calICalendar} aCalendar      Calendar instance
+     * @param {?Object} outAccount          Optional out value for account
+     * @return {nsIMsgIdentity}             The configured identity
+     */
+    getEmailIdentityOfCalendar: function(aCalendar, outAccount) {
+        cal.ASSERT(aCalendar, "no calendar!", Components.results.NS_ERROR_INVALID_ARG);
+        let key = aCalendar.getProperty("imip.identity.key");
+        if (key === null) { // take default account/identity:
+            let findIdentity = function(account) {
+                if (account && account.identities.length) {
+                    return account.defaultIdentity ||
+                           account.identities.queryElementAt(0, Components.interfaces.nsIMsgIdentity);
+                }
+                return null;
+            };
 
-cal.convertByteArray = function(aResult, aResultLength, aCharset, aThrow) {
-    try {
-        let resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
-                                        .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
-        resultConverter.charset = aCharset || "UTF-8";
-        return resultConverter.convertFromByteArray(aResult, aResultLength);
-    } catch (e) {
-        if (aThrow) {
-            throw e;
-        }
-    }
-    return null;
-};
+            let foundAccount = (function() {
+                try {
+                    return MailServices.accounts.defaultAccount;
+                } catch (e) {
+                    return null;
+                }
+            })();
+            let foundIdentity = findIdentity(foundAccount);
+
+            if (!foundAccount || !foundIdentity) {
+                let accounts = MailServices.accounts.accounts;
+                for (let account of fixIterator(accounts, Components.interfaces.nsIMsgAccount)) {
+                    let identity = findIdentity(account);
+
+                    if (account && identity) {
+                        foundAccount = account;
+                        foundIdentity = identity;
+                        break;
+                    }
+                }
+            }
 
-/**
- * getInterface method for providers. This should be called in the context of
- * the respective provider, i.e
- *
- * return cal.InterfaceRequestor_getInterface.apply(this, arguments);
- *
- * or
- * ...
- * getInterface: cal.InterfaceRequestor_getInterface,
- * ...
- *
- * NOTE: If the server only provides one realm for all calendars, be sure that
- * the |this| object implements calICalendar. In this case the calendar name
- * will be appended to the realm. If you need that feature disabled, see the
- * capabilities section of calICalendar.idl
- *
- * @param aIID      The interface ID to return
- */
-cal.InterfaceRequestor_getInterface = function(aIID) {
-    try {
-        // Try to query the this object for the requested interface but don't
-        // throw if it fails since that borks the network code.
-        return this.QueryInterface(aIID);
-    } catch (e) {
-        // Support Auth Prompt Interfaces
-        if (aIID.equals(Components.interfaces.nsIAuthPrompt2)) {
-            if (!this.calAuthPrompt) {
-                this.calAuthPrompt = new cal.auth.Prompt();
+            if (outAccount) {
+                outAccount.value = foundIdentity ? foundAccount : null;
+            }
+            return foundIdentity;
+        } else {
+            if (key.length == 0) { // i.e. "None"
+                return null;
             }
-            return this.calAuthPrompt;
-        } else if (aIID.equals(Components.interfaces.nsIAuthPromptProvider) ||
-                   aIID.equals(Components.interfaces.nsIPrompt)) {
-            return Services.ww.getNewPrompter(null);
-        } else if (aIID.equals(Components.interfaces.nsIBadCertListener2)) {
-            if (!this.badCertHandler) {
-                this.badCertHandler = new cal.BadCertHandler(this);
+            let identity = null;
+            cal.email.iterateIdentities((identity_, account) => {
+                if (identity_.key == key) {
+                    identity = identity_;
+                    if (outAccount) {
+                        outAccount.value = account;
+                    }
+                }
+                return (identity_.key != key);
+            });
+
+            if (!identity) {
+                // dangling identity:
+                cal.WARN("Calendar " + (aCalendar.uri ? aCalendar.uri.spec : aCalendar.id) +
+                         " has a dangling E-Mail identity configured.");
             }
-            return this.badCertHandler;
-        } else {
-            Components.returnCode = e;
+            return identity;
         }
-    }
-    return null;
-};
+    },
 
-/**
- * Bad Certificate Handler for Network Requests. Shows the Network Exception
- * Dialog if a certificate Problem occurs.
- */
-cal.BadCertHandler = function(thisProvider) {
-    this.thisProvider = thisProvider;
-};
-cal.BadCertHandler.prototype = {
-    QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIBadCertListener2]),
-    timer: null,
+    /**
+     * Opens the calendar conflict dialog
+     *
+     * @param {String} aMode        The conflict mode, either "modify" or "delete"
+     * @param {calIItemBase} aItem  The item to raise a conflict for
+     * @return {Boolean}            True, if the item should be overwritten
+     */
+    promptOverwrite: function(aMode, aItem) {
+        let window = cal.window.getCalendarWindow();
+        let args = {
+            item: aItem,
+            mode: aMode,
+            overwrite: false
+        };
 
-    notifyCertProblem: function(socketInfo, status, targetSite) {
-        // Unfortunately we can't pass js objects using the window watcher, so
-        // we'll just take the first available calendar window. We also need to
-        // do this on a timer so that the modal window doesn't block the
-        // network request.
-        let calWindow = cal.window.getCalendarWindow();
+        window.openDialog("chrome://calendar/content/calendar-conflicts-dialog.xul",
+                          "calendarConflictsDialog",
+                          "chrome,titlebar,modal",
+                          args);
+
+        return args.overwrite;
+    },
 
-        let timerCallback = {
-            thisProvider: this.thisProvider,
-            notify: function(timer) {
-                let params = {
-                    exceptionAdded: false,
-                    sslStatus: status,
-                    prefetchCert: true,
-                    location: targetSite
-                };
-                calWindow.openDialog("chrome://pippki/content/exceptionDialog.xul",
-                                     "",
-                                     "chrome,centerscreen,modal",
-                                     params);
-                if (this.thisProvider.canRefresh &&
-                    params.exceptionAdded) {
-                    // Refresh the provider if the
-                    // exception certificate was added
-                    this.thisProvider.refresh();
+    /**
+     * Gets the calendar directory, defaults to <profile-dir>/calendar-data
+     *
+     * @return {nsIFile}        The calendar-data directory as nsIFile
+     */
+    getCalendarDirectory: function() {
+        if (calprovider.getCalendarDirectory.mDir === undefined) {
+            let dir = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile);
+            dir.append("calendar-data");
+            if (!dir.exists()) {
+                try {
+                    dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+                } catch (exc) {
+                    cal.ASSERT(false, exc);
+                    throw exc;
                 }
             }
-        };
-        this.timer = Components.classes["@mozilla.org/timer;1"]
-                               .createInstance(Components.interfaces.nsITimer);
-        this.timer.initWithCallback(
-            timerCallback,
-            0,
-            Components.interfaces.nsITimer.TYPE_ONE_SHOT
-        );
-        return true;
-    }
-};
+            calprovider.getCalendarDirectory.mDir = dir;
+        }
+        return calprovider.getCalendarDirectory.mDir.clone();
+    },
+
+    /**
+     * Base prototype to be used implementing a provider.
+     *
+     * @see e.g. providers/gdata
+     */
+    BaseClass: class {
+        /**
+         * The transient proeprties that are not pesisted to storage
+         */
+        static get mTransientProperties() {
+            return {
+                "cache.uncachedCalendar": true,
+                "currentStatus": true,
+                "itip.transport": true,
+                "imip.identity": true,
+                "imip.account": true,
+                "imip.identity.disabled": true,
+                "organizerId": true,
+                "organizerCN": true
+            };
+        }
+
+        QueryInterface(iid) {
+            return cal.generateClassQI(this, iid, [
+                Components.interfaces.calICalendar,
+                Components.interfaces.calISchedulingSupport
+            ]);
+        }
 
-/**
- * Freebusy interval implementation. All parameters are optional.
- *
- * @param aCalId         The calendar id to set up with.
- * @param aFreeBusyType  The type from calIFreeBusyInterval.
- * @param aStart         The start of the interval.
- * @param aEnd           The end of the interval.
- * @return               The fresh calIFreeBusyInterval.
- */
-cal.FreeBusyInterval = function(aCalId, aFreeBusyType, aStart, aEnd) {
-    this.calId = aCalId;
-    this.interval = Components.classes["@mozilla.org/calendar/period;1"]
-                              .createInstance(Components.interfaces.calIPeriod);
-    this.interval.start = aStart;
-    this.interval.end = aEnd;
+        /**
+         * Initialize the base class, this should be migrated to an ES6 constructor once all
+         * subclasses are also es6 classes. Call this from the constructor.
+         */
+        initProviderBase() {
+            this.wrappedJSObject = this;
+            this.mID = null;
+            this.mUri = null;
+            this.mACLEntry = null;
+            this.mBatchCount = 0;
+            this.transientProperties = false;
+            this.mObservers = new cal.data.ObserverSet(Components.interfaces.calIObserver);
+            this.mProperties = {};
+            this.mProperties.currentStatus = Components.results.NS_OK;
+        }
+
+        /**
+         * Returns the calIObservers for this calendar
+         */
+        get observers() {
+            return this.mObservers;
+        }
 
-    this.freeBusyType = aFreeBusyType || Components.interfaces.calIFreeBusyInterval.UNKNOWN;
-};
-cal.FreeBusyInterval.prototype = {
-    QueryInterface: XPCOMUtils.generateQI([Components.interfaces.calIFreeBusyInterval]),
-    calId: null,
-    interval: null,
-    freeBusyType: Components.interfaces.calIFreeBusyInterval.UNKNOWN
-};
+        // attribute AUTF8String id;
+        get id() {
+            return this.mID;
+        }
+        set id(aValue) {
+            if (this.mID) {
+                throw Components.results.NS_ERROR_ALREADY_INITIALIZED;
+            }
+            this.mID = aValue;
+
+            let calMgr = cal.getCalendarManager();
+
+            // make all properties persistent that have been set so far:
+            for (let aName in this.mProperties) {
+                if (!this.constructor.mTransientProperties[aName]) {
+                    let value = this.mProperties[aName];
+                    if (value !== null) {
+                        calMgr.setCalendarPref_(this, aName, value);
+                    }
+                }
+            }
+
+            return aValue;
+        }
 
-/**
- * Gets the iTIP/iMIP transport if the passed calendar has configured email.
- */
-cal.getImipTransport = function(aCalendar) {
-    // assure an identity is configured for the calendar
-    return (aCalendar && aCalendar.getProperty("imip.identity")
-            ? Components.classes["@mozilla.org/calendar/itip-transport;1?type=email"]
-                        .getService(Components.interfaces.calIItipTransport)
-            : null);
-};
+        // attribute AUTF8String name;
+        get name() {
+            return this.getProperty("name");
+        }
+        set name(aValue) {
+            return this.setProperty("name", aValue);
+        }
+
+        // readonly attribute calICalendarACLManager aclManager;
+        get aclManager() {
+            const defaultACLProviderClass = "@mozilla.org/calendar/acl-manager;1?type=default";
+            let providerClass = this.getProperty("aclManagerClass");
+            if (!providerClass || !Components.classes[providerClass]) {
+                providerClass = defaultACLProviderClass;
+            }
+            return Components.classes[providerClass].getService(Components.interfaces.calICalendarACLManager);
+        }
+
+        // readonly attribute calICalendarACLEntry aclEntry;
+        get aclEntry() {
+            return this.mACLEntry;
+        }
+
+        // attribute calICalendar superCalendar;
+        get superCalendar() {
+            // If we have a superCalendar, check this calendar for a superCalendar.
+            // This will make sure the topmost calendar is returned
+            return (this.mSuperCalendar ? this.mSuperCalendar.superCalendar : this);
+        }
+        set superCalendar(val) {
+            return (this.mSuperCalendar = val);
+        }
+
+        // attribute nsIURI uri;
+        get uri() {
+            return this.mUri;
+        }
+        set uri(aValue) {
+            return (this.mUri = aValue);
+        }
 
-/**
- * Gets the configured identity and account of a particular calendar instance, or null.
- *
- * @param aCalendar     Calendar instance
- * @param outAccount    Optional out value for account
- * @return              The configured identity
- */
-cal.getEmailIdentityOfCalendar = function(aCalendar, outAccount) {
-    cal.ASSERT(aCalendar, "no calendar!", Components.results.NS_ERROR_INVALID_ARG);
-    let key = aCalendar.getProperty("imip.identity.key");
-    if (key === null) { // take default account/identity:
-        let findIdentity = function(account) {
-            if (account && account.identities.length) {
-                return account.defaultIdentity ||
-                       account.identities.queryElementAt(0, Components.interfaces.nsIMsgIdentity);
+        // attribute boolean readOnly;
+        get readOnly() {
+            return this.getProperty("readOnly");
+        }
+        set readOnly(aValue) {
+            return this.setProperty("readOnly", aValue);
+        }
+
+        // readonly attribute boolean canRefresh;
+        get canRefresh() {
+            return false;
+        }
+
+        // void startBatch();
+        startBatch() {
+            if (this.mBatchCount++ == 0) {
+                this.mObservers.notify("onStartBatch");
             }
-            return null;
-        };
+        }
 
-        let foundAccount = (function() {
-            try {
-                return MailServices.accounts.defaultAccount;
-            } catch (e) {
-                return null;
+        // void endBatch();
+        endBatch() {
+            if (this.mBatchCount > 0) {
+                if (--this.mBatchCount == 0) {
+                    this.mObservers.notify("onEndBatch");
+                }
+            } else {
+                cal.ASSERT(this.mBatchCount > 0, "unexepcted endBatch!");
             }
-        })();
-        let foundIdentity = findIdentity(foundAccount);
+        }
 
-        if (!foundAccount || !foundIdentity) {
-            let accounts = MailServices.accounts.accounts;
-            for (let account of fixIterator(accounts, Components.interfaces.nsIMsgAccount)) {
-                let identity = findIdentity(account);
-
-                if (account && identity) {
-                    foundAccount = account;
-                    foundIdentity = identity;
-                    break;
+        /**
+         * Notifies the given listener for onOperationComplete, ignoring (but logging) any
+         * exceptions that occur. If no listener is passed the function is a no-op.
+         *
+         * @param {?calIOperationListener} aListener        The listener to notify
+         * @param {Number} aStatus                          A Components.results result
+         * @param {Number} aOperationType                   The operation type component
+         * @param {String} aId                              The item id
+         * @param {*} aDetail                               The item detail for the listener
+         */
+        notifyPureOperationComplete(aListener, aStatus, aOperationType, aId, aDetail) {
+            if (aListener) {
+                try {
+                    aListener.onOperationComplete(this.superCalendar, aStatus, aOperationType, aId, aDetail);
+                } catch (exc) {
+                    cal.ERROR(exc);
                 }
             }
         }
 
-        if (outAccount) {
-            outAccount.value = foundIdentity ? foundAccount : null;
-        }
-        return foundIdentity;
-    } else {
-        if (key.length == 0) { // i.e. "None"
-            return null;
-        }
-        let identity = null;
-        cal.email.iterateIdentities((identity_, account) => {
-            if (identity_.key == key) {
-                identity = identity_;
-                if (outAccount) {
-                    outAccount.value = account;
-                }
-            }
-            return (identity_.key != key);
-        });
-
-        if (!identity) {
-            // dangling identity:
-            cal.WARN("Calendar " + (aCalendar.uri ? aCalendar.uri.spec : aCalendar.id) +
-                     " has a dangling E-Mail identity configured.");
-        }
-        return identity;
-    }
-};
+        /**
+         * Notifies the given listener for onOperationComplete, also setting various calendar status
+         * variables and notifying about the error.
+         *
+         * @param {?calIOperationListener} aListener        The listener to notify
+         * @param {Number} aStatus                          A Components.results result
+         * @param {Number} aOperationType                   The operation type component
+         * @param {String} aId                              The item id
+         * @param {*} aDetail                               The item detail for the listener
+         * @param {String} aExtraMessage                    An extra message to pass to notifyError
+         */
+        notifyOperationComplete(aListener, aStatus, aOperationType, aId, aDetail, aExtraMessage) {
+            this.notifyPureOperationComplete(aListener, aStatus, aOperationType, aId, aDetail);
 
-cal.promptOverwrite = function(aMode, aItem) {
-    let window = cal.window.getCalendarWindow();
-    let args = {
-        item: aItem,
-        mode: aMode,
-        overwrite: false
-    };
-
-    window.openDialog("chrome://calendar/content/calendar-conflicts-dialog.xul",
-                      "calendarConflictsDialog",
-                      "chrome,titlebar,modal",
-                      args);
-
-    return args.overwrite;
-};
-
-/**
- * Gets the calendar directory, defaults to <profile-dir>/calendar-data
- */
-cal.getCalendarDirectory = function() {
-    if (cal.getCalendarDirectory.mDir === undefined) {
-        let dir = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile);
-        dir.append("calendar-data");
-        if (!dir.exists()) {
-            try {
-                dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
-            } catch (exc) {
-                cal.ASSERT(false, exc);
-                throw exc;
+            if (aStatus == Components.interfaces.calIErrors.OPERATION_CANCELLED) {
+                return; // cancellation doesn't change current status, no notification
             }
-        }
-        cal.getCalendarDirectory.mDir = dir;
-    }
-    return cal.getCalendarDirectory.mDir.clone();
-};
-
-/**
- * Base prototype to be used implementing a provider.
- *
- * @see e.g. providers/gdata
- */
-cal.ProviderBase = function() {
-    cal.ASSERT("This prototype should only be inherited!");
-};
-cal.ProviderBase.mTransientProperties = {
-    "cache.uncachedCalendar": true,
-    "currentStatus": true,
-    "itip.transport": true,
-    "imip.identity": true,
-    "imip.account": true,
-    "imip.identity.disabled": true,
-    "organizerId": true,
-    "organizerCN": true
-};
-cal.ProviderBase.prototype = {
-    QueryInterface: XPCOMUtils.generateQI([
-        Components.interfaces.calICalendar,
-        Components.interfaces.calISchedulingSupport
-    ]),
-
-    mID: null,
-    mUri: null,
-    mACLEntry: null,
-    mObservers: null,
-    mProperties: null,
-
-    initProviderBase: function() {
-        this.wrappedJSObject = this;
-        this.mObservers = new cal.data.ObserverSet(Components.interfaces.calIObserver);
-        this.mProperties = {};
-        this.mProperties.currentStatus = Components.results.NS_OK;
-    },
-
-    get observers() {
-        return this.mObservers;
-    },
-
-    // attribute AUTF8String id;
-    get id() {
-        return this.mID;
-    },
-    set id(aValue) {
-        if (this.mID) {
-            throw Components.results.NS_ERROR_ALREADY_INITIALIZED;
-        }
-        this.mID = aValue;
-
-        let calMgr = cal.getCalendarManager();
-
-        // make all properties persistent that have been set so far:
-        for (let aName in this.mProperties) {
-            if (!cal.ProviderBase.mTransientProperties[aName]) {
-                let value = this.mProperties[aName];
-                if (value !== null) {
-                    calMgr.setCalendarPref_(this, aName, value);
+            if (Components.isSuccessCode(aStatus)) {
+                this.setProperty("currentStatus", aStatus);
+            } else {
+                if (aDetail instanceof Components.interfaces.nsIException) {
+                    this.notifyError(aDetail); // will set currentStatus
+                } else {
+                    this.notifyError(aStatus, aDetail); // will set currentStatus
                 }
+                this.notifyError(aOperationType == Components.interfaces.calIOperationListener.GET
+                                 ? Components.interfaces.calIErrors.READ_FAILED
+                                 : Components.interfaces.calIErrors.MODIFICATION_FAILED,
+                                 aExtraMessage || "");
             }
         }
 
-        let takeOverIfNotPresent = (oldPref, newPref, dontDeleteOldPref) => {
-            let val = calMgr.getCalendarPref_(this, oldPref);
-            if (val !== null) {
-                if (!dontDeleteOldPref) {
-                    calMgr.deleteCalendarPref_(this, oldPref);
+        /**
+         * Notify observers using the onError notification with a readable error message
+         *
+         * @param {Number|nsIException} aErrNo      The error number from Components.results, or
+         *                                            the exception which contains the error number
+         * @param {?String} aMessage                The message to show for the error
+         */
+        notifyError(aErrNo, aMessage=null) {
+            if (aErrNo == Components.interfaces.calIErrors.OPERATION_CANCELLED) {
+                return; // cancellation doesn't change current status, no notification
+            }
+            if (aErrNo instanceof Components.interfaces.nsIException) {
+                if (!aMessage) {
+                    aMessage = aErrNo.message;
+                }
+                aErrNo = aErrNo.result;
+            }
+            this.setProperty("currentStatus", aErrNo);
+            this.observers.notify("onError", [this.superCalendar, aErrNo, aMessage]);
+        }
+
+        // nsIVariant getProperty(in AUTF8String aName);
+        getProperty(aName) {
+            switch (aName) {
+                case "itip.transport": // iTIP/iMIP default:
+                    return calprovider.getImipTransport(this);
+                case "itip.notify-replies": // iTIP/iMIP default:
+                    return Preferences.get("calendar.itip.notify-replies", false);
+                // temporary hack to get the uncached calendar instance:
+                case "cache.uncachedCalendar":
+                    return this;
+            }
+
+            let ret = this.mProperties[aName];
+            if (ret === undefined) {
+                ret = null;
+                switch (aName) {
+                    case "imip.identity": // we want to cache the identity object a little, because
+                                          // it is heavily used by the invitation checks
+                        ret = calprovider.getEmailIdentityOfCalendar(this);
+                        break;
+                    case "imip.account": {
+                        let outAccount = {};
+                        if (calprovider.getEmailIdentityOfCalendar(this, outAccount)) {
+                            ret = outAccount.value;
+                        }
+                        break;
+                    }
+                    case "organizerId": { // itip/imip default: derived out of imip.identity
+                        let identity = this.getProperty("imip.identity");
+                        ret = (identity
+                               ? ("mailto:" + identity.QueryInterface(Components.interfaces.nsIMsgIdentity).email)
+                               : null);
+                        break;
+                    }
+                    case "organizerCN": { // itip/imip default: derived out of imip.identity
+                        let identity = this.getProperty("imip.identity");
+                        ret = (identity
+                               ? identity.QueryInterface(Components.interfaces.nsIMsgIdentity).fullName
+                               : null);
+                        break;
+                    }
+                }
+                if ((ret === null) &&
+                    !this.constructor.mTransientProperties[aName] &&
+                    !this.transientProperties) {
+                    if (this.id) {
+                        ret = cal.getCalendarManager().getCalendarPref_(this, aName);
+                    }
+                    switch (aName) {
+                        case "suppressAlarms":
+                            if (this.getProperty("capabilities.alarms.popup.supported") === false) {
+                                // If popup alarms are not supported,
+                                // automatically suppress alarms
+                                ret = true;
+                            }
+                            break;
+                    }
                 }
-                if (calMgr.getCalendarPref_(this, newPref) === null) {
-                    calMgr.setCalendarPref_(this, newPref, val);
+                this.mProperties[aName] = ret;
+            }
+            return ret;
+        }
+
+        // void setProperty(in AUTF8String aName, in nsIVariant aValue);
+        setProperty(aName, aValue) {
+            let oldValue = this.getProperty(aName);
+            if (oldValue != aValue) {
+                this.mProperties[aName] = aValue;
+                switch (aName) {
+                    case "imip.identity.key": // invalidate identity and account object if key is set:
+                        delete this.mProperties["imip.identity"];
+                        delete this.mProperties["imip.account"];
+                        delete this.mProperties.organizerId;
+                        delete this.mProperties.organizerCN;
+                        break;
+                }
+                if (!this.transientProperties &&
+                    !this.constructor.mTransientProperties[aName] &&
+                    this.id) {
+                    cal.getCalendarManager().setCalendarPref_(this, aName, aValue);
+                }
+                this.mObservers.notify("onPropertyChanged",
+                                       [this.superCalendar, aName, aValue, oldValue]);
+            }
+            return aValue;
+        }
+
+        // void deleteProperty(in AUTF8String aName);
+        deleteProperty(aName) {
+            this.mObservers.notify("onPropertyDeleting", [this.superCalendar, aName]);
+            delete this.mProperties[aName];
+            cal.getCalendarManager().deleteCalendarPref_(this, aName);
+        }
+
+        // calIOperation refresh
+        refresh() {
+            return null;
+        }
+
+        // void addObserver( in calIObserver observer );
+        addObserver(aObserver) {
+            this.mObservers.add(aObserver);
+        }
+
+        // void removeObserver( in calIObserver observer );
+        removeObserver(aObserver) {
+            this.mObservers.delete(aObserver);
+        }
+
+        // calISchedulingSupport: Implementation corresponding to our iTIP/iMIP support
+        isInvitation(aItem) {
+            if (!this.mACLEntry || !this.mACLEntry.hasAccessControl) {
+                // No ACL support - fallback to the old method
+                let id = this.getProperty("organizerId");
+                if (id) {
+                    let org = aItem.organizer;
+                    if (!org || !org.id || (org.id.toLowerCase() == id.toLowerCase())) {
+                        return false;
+                    }
+                    return (aItem.getAttendeeById(id) != null);
+                }
+                return false;
+            }
+
+            let org = aItem.organizer;
+            if (!org || !org.id) {
+                // HACK
+                // if we don't have an organizer, this is perhaps because it's an exception
+                // to a recurring event. We check the parent item.
+                if (aItem.parentItem) {
+                    org = aItem.parentItem.organizer;
+                    if (!org || !org.id) {
+                        return false;
+                    }
+                } else {
+                    return false;
                 }
             }
-        };
-
-        // takeover lightning calendar visibility from 0.5:
-        takeOverIfNotPresent("lightning-main-in-composite", "calendar-main-in-composite");
-        takeOverIfNotPresent("lightning-main-default", "calendar-main-default");
-
-        return aValue;
-    },
-
-    // attribute AUTF8String name;
-    get name() {
-        return this.getProperty("name");
-    },
-    set name(aValue) {
-        return this.setProperty("name", aValue);
-    },
-
-    // readonly attribute calICalendarACLManager aclManager;
-    get aclManager() {
-        const defaultACLProviderClass = "@mozilla.org/calendar/acl-manager;1?type=default";
-        let providerClass = this.getProperty("aclManagerClass");
-        if (!providerClass || !Components.classes[providerClass]) {
-            providerClass = defaultACLProviderClass;
-        }
-        return Components.classes[providerClass].getService(Components.interfaces.calICalendarACLManager);
-    },
-
-    // readonly attribute calICalendarACLEntry aclEntry;
-    get aclEntry() {
-        return this.mACLEntry;
-    },
-
-    // attribute calICalendar superCalendar;
-    get superCalendar() {
-        // If we have a superCalendar, check this calendar for a superCalendar.
-        // This will make sure the topmost calendar is returned
-        return (this.mSuperCalendar ? this.mSuperCalendar.superCalendar : this);
-    },
-    set superCalendar(val) {
-        return (this.mSuperCalendar = val);
-    },
-
-    // attribute nsIURI uri;
-    get uri() {
-        return this.mUri;
-    },
-    set uri(aValue) {
-        return (this.mUri = aValue);
-    },
-
-    // attribute boolean readOnly;
-    get readOnly() {
-        return this.getProperty("readOnly");
-    },
-    set readOnly(aValue) {
-        return this.setProperty("readOnly", aValue);
-    },
-
-    // readonly attribute boolean canRefresh;
-    get canRefresh() {
-        return false;
-    },
-
-    // void startBatch();
-    mBatchCount: 0,
-    startBatch: function() {
-        if (this.mBatchCount++ == 0) {
-            this.mObservers.notify("onStartBatch");
-        }
-    },
-
-    endBatch: function() {
-        if (this.mBatchCount > 0) {
-            if (--this.mBatchCount == 0) {
-                this.mObservers.notify("onEndBatch");
-            }
-        } else {
-            cal.ASSERT(this.mBatchCount > 0, "unexepcted endBatch!");
-        }
-    },
 
-    notifyPureOperationComplete: function(aListener, aStatus, aOperationType, aId, aDetail) {
-        if (aListener) {
-            try {
-                aListener.onOperationComplete(this.superCalendar, aStatus, aOperationType, aId, aDetail);
-            } catch (exc) {
-                cal.ERROR(exc);
-            }
-        }
-    },
-
-    notifyOperationComplete: function(aListener, aStatus, aOperationType, aId, aDetail, aExtraMessage) {
-        this.notifyPureOperationComplete(aListener, aStatus, aOperationType, aId, aDetail);
-
-        if (aStatus == Components.interfaces.calIErrors.OPERATION_CANCELLED) {
-            return; // cancellation doesn't change current status, no notification
-        }
-        if (Components.isSuccessCode(aStatus)) {
-            this.setProperty("currentStatus", aStatus);
-        } else {
-            if (aDetail instanceof Components.interfaces.nsIException) {
-                this.notifyError(aDetail); // will set currentStatus
-            } else {
-                this.notifyError(aStatus, aDetail); // will set currentStatus
-            }
-            this.notifyError(aOperationType == Components.interfaces.calIOperationListener.GET
-                             ? Components.interfaces.calIErrors.READ_FAILED
-                             : Components.interfaces.calIErrors.MODIFICATION_FAILED,
-                             aExtraMessage || "");
-        }
-    },
-
-    // for convenience also callable with just an exception
-    notifyError: function(aErrNo, aMessage) {
-        if (aErrNo == Components.interfaces.calIErrors.OPERATION_CANCELLED) {
-            return; // cancellation doesn't change current status, no notification
-        }
-        if (aErrNo instanceof Components.interfaces.nsIException) {
-            if (!aMessage) {
-                aMessage = aErrNo.message;
-            }
-            aErrNo = aErrNo.result;
-        }
-        this.setProperty("currentStatus", aErrNo);
-        this.observers.notify("onError", [this.superCalendar, aErrNo, aMessage]);
-    },
+            // We check if :
+            // - the organizer of the event is NOT within the owner's identities of this calendar
+            // - if the one of the owner's identities of this calendar is in the attendees
+            let ownerIdentities = this.mACLEntry.getOwnerIdentities({});
+            for (let i = 0; i < ownerIdentities.length; i++) {
+                let identity = "mailto:" + ownerIdentities[i].email.toLowerCase();
+                if (org.id.toLowerCase() == identity) {
+                    return false;
+                }
 
-    mTransientPropertiesMode: false,
-    get transientProperties() {
-        return this.mTransientPropertiesMode;
-    },
-    set transientProperties(value) {
-        return (this.mTransientPropertiesMode = value);
-    },
-
-    // nsIVariant getProperty(in AUTF8String aName);
-    getProperty: function(aName) {
-        switch (aName) {
-            case "itip.transport": // iTIP/iMIP default:
-                return cal.getImipTransport(this);
-            case "itip.notify-replies": // iTIP/iMIP default:
-                return Preferences.get("calendar.itip.notify-replies", false);
-            // temporary hack to get the uncached calendar instance:
-            case "cache.uncachedCalendar":
-                return this;
-        }
-
-        let ret = this.mProperties[aName];
-        if (ret === undefined) {
-            ret = null;
-            switch (aName) {
-                case "imip.identity": // we want to cache the identity object a little, because
-                                      // it is heavily used by the invitation checks
-                    ret = cal.getEmailIdentityOfCalendar(this);
-                    break;
-                case "imip.account": {
-                    let outAccount = {};
-                    if (cal.getEmailIdentityOfCalendar(this, outAccount)) {
-                        ret = outAccount.value;
-                    }
-                    break;
-                }
-                case "organizerId": { // itip/imip default: derived out of imip.identity
-                    let identity = this.getProperty("imip.identity");
-                    ret = (identity
-                           ? ("mailto:" + identity.QueryInterface(Components.interfaces.nsIMsgIdentity).email)
-                           : null);
-                    break;
-                }
-                case "organizerCN": { // itip/imip default: derived out of imip.identity
-                    let identity = this.getProperty("imip.identity");
-                    ret = (identity
-                           ? identity.QueryInterface(Components.interfaces.nsIMsgIdentity).fullName
-                           : null);
-                    break;
+                if (aItem.getAttendeeById(identity) != null) {
+                    return true;
                 }
             }
-            if ((ret === null) &&
-                !cal.ProviderBase.mTransientProperties[aName] &&
-                !this.transientProperties) {
-                if (this.id) {
-                    ret = cal.getCalendarManager().getCalendarPref_(this, aName);
-                }
-                switch (aName) {
-                    case "suppressAlarms":
-                        if (this.getProperty("capabilities.alarms.popup.supported") === false) {
-                            // If popup alarms are not supported,
-                            // automatically suppress alarms
-                            ret = true;
-                        }
-                        break;
-                }
-            }
-            this.mProperties[aName] = ret;
-        }
-//         cal.LOG("getProperty(\"" + aName + "\"): " + ret);
-        return ret;
-    },
 
-    // void setProperty(in AUTF8String aName, in nsIVariant aValue);
-    setProperty: function(aName, aValue) {
-        let oldValue = this.getProperty(aName);
-        if (oldValue != aValue) {
-            this.mProperties[aName] = aValue;
-            switch (aName) {
-                case "imip.identity.key": // invalidate identity and account object if key is set:
-                    delete this.mProperties["imip.identity"];
-                    delete this.mProperties["imip.account"];
-                    delete this.mProperties.organizerId;
-                    delete this.mProperties.organizerCN;
-                    break;
-            }
-            if (!this.transientProperties &&
-                !cal.ProviderBase.mTransientProperties[aName] &&
-                this.id) {
-                cal.getCalendarManager().setCalendarPref_(this, aName, aValue);
-            }
-            this.mObservers.notify("onPropertyChanged",
-                                   [this.superCalendar, aName, aValue, oldValue]);
-        }
-        return aValue;
-    },
-
-    // void deleteProperty(in AUTF8String aName);
-    deleteProperty: function(aName) {
-        this.mObservers.notify("onPropertyDeleting", [this.superCalendar, aName]);
-        delete this.mProperties[aName];
-        cal.getCalendarManager().deleteCalendarPref_(this, aName);
-    },
-
-    // calIOperation refresh
-    refresh: function() {
-        return null;
-    },
-
-    // void addObserver( in calIObserver observer );
-    addObserver: function(aObserver) {
-        this.mObservers.add(aObserver);
-    },
-
-    // void removeObserver( in calIObserver observer );
-    removeObserver: function(aObserver) {
-        this.mObservers.delete(aObserver);
-    },
-
-    // calISchedulingSupport: Implementation corresponding to our iTIP/iMIP support
-    isInvitation: function(aItem) {
-        if (!this.mACLEntry || !this.mACLEntry.hasAccessControl) {
-            // No ACL support - fallback to the old method
-            let id = this.getProperty("organizerId");
-            if (id) {
-                let org = aItem.organizer;
-                if (!org || !org.id || (org.id.toLowerCase() == id.toLowerCase())) {
-                    return false;
-                }
-                return (aItem.getAttendeeById(id) != null);
-            }
             return false;
         }
 
-        let org = aItem.organizer;
-        if (!org || !org.id) {
-            // HACK
-            // if we don't have an organizer, this is perhaps because it's an exception
-            // to a recurring event. We check the parent item.
-            if (aItem.parentItem) {
-                org = aItem.parentItem.organizer;
-                if (!org || !org.id) {
-                    return false;
+        // calIAttendee getInvitedAttendee(in calIItemBase aItem);
+        getInvitedAttendee(aItem) {
+            let id = this.getProperty("organizerId");
+            let attendee = (id ? aItem.getAttendeeById(id) : null);
+
+            if (!attendee && this.mACLEntry && this.mACLEntry.hasAccessControl) {
+                let ownerIdentities = this.mACLEntry.getOwnerIdentities({});
+                if (ownerIdentities.length > 0) {
+                    let identity;
+                    for (let i = 0; !attendee && i < ownerIdentities.length; i++) {
+                        identity = "mailto:" + ownerIdentities[i].email.toLowerCase();
+                        attendee = aItem.getAttendeeById(identity);
+                    }
                 }
-            } else {
-                return false;
-            }
-        }
-
-        // We check if :
-        // - the organizer of the event is NOT within the owner's identities of this calendar
-        // - if the one of the owner's identities of this calendar is in the attendees
-        let ownerIdentities = this.mACLEntry.getOwnerIdentities({});
-        for (let i = 0; i < ownerIdentities.length; i++) {
-            let identity = "mailto:" + ownerIdentities[i].email.toLowerCase();
-            if (org.id.toLowerCase() == identity) {
-                return false;
             }
 
-            if (aItem.getAttendeeById(identity) != null) {
-                return true;
-            }
+            return attendee;
         }
 
-        return false;
-    },
-
-    getInvitedAttendee: function(aItem) {
-        let id = this.getProperty("organizerId");
-        let attendee = (id ? aItem.getAttendeeById(id) : null);
-
-        if (!attendee && this.mACLEntry && this.mACLEntry.hasAccessControl) {
-            let ownerIdentities = this.mACLEntry.getOwnerIdentities({});
-            if (ownerIdentities.length > 0) {
-                let identity;
-                for (let i = 0; !attendee && i < ownerIdentities.length; i++) {
-                    identity = "mailto:" + ownerIdentities[i].email.toLowerCase();
-                    attendee = aItem.getAttendeeById(identity);
-                }
-            }
+        // boolean canNotify(in AUTF8String aMethod, in calIItemBase aItem);
+        canNotify(aMethod, aItem) {
+            return false; // use outbound iTIP for all
         }
-
-        return attendee;
-    },
-
-    canNotify: function(aMethod, aItem) {
-        return false; // use outbound iTIP for all
     }
 };
--- a/calendar/base/modules/calUtils.jsm
+++ b/calendar/base/modules/calUtils.jsm
@@ -144,16 +144,38 @@ var cal = {
             let rescode = aCritical === true ? Components.results.NS_ERROR_UNEXPECTED : aCritical;
             throw new Components.Exception(string, rescode);
         } else {
             Components.utils.reportError(string);
         }
     },
 
     /**
+     * Generates a QueryInterface method on the given global. To be used as follows:
+     *
+     *     class calThing {
+     *       QueryInterface(aIID) { return cal.generateClassQI(this, aIID, [Ci.calIThing]); }
+     *
+     *       ...
+     *     }
+     *
+     * The function is cached, once this is called QueryInterface is replaced with
+     * XPCOMUtils.generateQI()'s result.
+     *
+     * @param {Object} aGlobal      The object to define the method on
+     * @param {nsIIDRef} aIID       The IID to query for
+     * @param {nsIIDRef[]}          The interfaces that this object implements
+     * @return {nsQIResult}         The object queried for aIID
+     */
+    generateClassQI: function(aGlobal, aIID, aInterfaces) {
+        Object.defineProperty(aGlobal, "QueryInterface", { value: XPCOMUtils.generateQI(aInterfaces) });
+        return aGlobal.QueryInterface(aIID);
+    },
+
+    /**
      * Loads an array of calendar scripts into the passed scope.
      *
      * @param scriptNames an array of calendar script names
      * @param scope       scope to load into
      * @param baseDir     base dir; defaults to calendar-js/
      */
     loadScripts: function(scriptNames, scope, baseDir) {
         if (!baseDir) {
@@ -367,16 +389,17 @@ XPCOMUtils.defineLazyPreferenceGetter(ca
 XPCOMUtils.defineLazyModuleGetter(cal, "acl", "resource://calendar/modules/calACLUtils.jsm", "calacl");
 XPCOMUtils.defineLazyModuleGetter(cal, "category", "resource://calendar/modules/calCategoryUtils.jsm", "calcategory");
 XPCOMUtils.defineLazyModuleGetter(cal, "data", "resource://calendar/modules/calDataUtils.jsm", "caldata");
 XPCOMUtils.defineLazyModuleGetter(cal, "dtz", "resource://calendar/modules/calDateTimeUtils.jsm", "caldtz");
 XPCOMUtils.defineLazyModuleGetter(cal, "email", "resource://calendar/modules/calEmailUtils.jsm", "calemail");
 XPCOMUtils.defineLazyModuleGetter(cal, "item", "resource://calendar/modules/calItemUtils.jsm", "calitem");
 XPCOMUtils.defineLazyModuleGetter(cal, "itip", "resource://calendar/modules/calItipUtils.jsm", "calitip");
 XPCOMUtils.defineLazyModuleGetter(cal, "l10n", "resource://calendar/modules/calL10NUtils.jsm", "call10n");
+XPCOMUtils.defineLazyModuleGetter(cal, "provider", "resource://calendar/modules/calProviderUtils.jsm", "calprovider");
 XPCOMUtils.defineLazyModuleGetter(cal, "unifinder", "resource://calendar/modules/calUnifinderUtils.jsm", "calunifinder");
 XPCOMUtils.defineLazyModuleGetter(cal, "view", "resource://calendar/modules/calViewUtils.jsm", "calview");
 XPCOMUtils.defineLazyModuleGetter(cal, "window", "resource://calendar/modules/calWindowUtils.jsm", "calwindow");
 
 /**
  * Returns a function that provides access to the given service.
  *
  * @param cid           The contract id to create
--- a/calendar/base/modules/calUtilsCompat.jsm
+++ b/calendar/base/modules/calUtilsCompat.jsm
@@ -81,16 +81,27 @@ var migrations = {
     itip: {
         getPublishLikeItemCopy: "getPublishLikeItemCopy",
         isInvitation: "isInvitation",
         isOpenInvitation: "isOpenInvitation",
         resolveDelegation: "resolveDelegation",
         getInvitedAttendee: "getInvitedAttendee",
         getAttendeesBySender: "getAttendeesBySender"
     },
+    provider: {
+        prepHttpChannel: "prepHttpChannel",
+        sendHttpRequest: "sendHttpRequest",
+        createStreamLoader: "createStreamLoader",
+        convertByteArray: "convertByteArray",
+        InterfaceRequestor_getInterface: "InterfaceRequestor_getInterface",
+        getImipTransport: "getImipTransport",
+        getEmailIdentityOfCalendar: "getEmailIdentityOfCalendar",
+        promptOverwrite: "promptOverwrite",
+        getCalendarDirectory: "getCalendarDirectory"
+    },
     unifinder: {
         sortEntryComparer: "sortEntryComparer",
         getItemSortKey:  "getItemSortKey",
         // compareNative*, compareNumber, sortEntry, sortEntryItem, sortEntryKey and
         // getSortTypeForSortKey are no longer available. There is a new
         // cal.unifinder.sortItems though that should do everything necessary.
     },
     view: {
@@ -159,9 +170,36 @@ function injectCalUtilsCompat(global) {
     // calGetString is special, as the argument order and kind has changed as well
     global.calGetString = function(aBundleName, aStringName, aParams, aComponent="calendar") {
         Deprecated.warning("calUtils' cal.calGetString() has changed to cal.l10n.get*String()" +
                            " and the parameter order has changed",
                            "https://bugzilla.mozilla.org/show_bug.cgi?id=905097",
                            Components.stack.caller);
         return cal.l10n.getAnyString(aComponent, aBundleName, aStringName, aParams);
     };
+
+    global.ProviderBase = class extends global.provider.BaseClass {
+        initProviderBase() {
+            Deprecated.warning("calProviderUtils' cal.ProviderBase() has changed to cal.provider.BaseClass()",
+                               "https://bugzilla.mozilla.org/show_bug.cgi?id=905097",
+                               Components.stack.caller);
+            super.initProviderBase();
+        }
+    };
+
+    global.BadCertHandler = class extends global.provider.BadCertHandler {
+        constructor() {
+            Deprecated.warning("calProviderUtils' cal.BadCertHandler() has changed to cal.provider.BadCertHandler()",
+                               "https://bugzilla.mozilla.org/show_bug.cgi?id=905097",
+                               Components.stack.caller);
+            super();
+        }
+    };
+
+    global.FreeBusyInterval = class extends global.provider.FreeBusyInterval {
+        constructor() {
+            Deprecated.warning("calProviderUtils' cal.FreeBusyInterval() has changed to cal.provider.FreeBusyInterval()",
+                               "https://bugzilla.mozilla.org/show_bug.cgi?id=905097",
+                               Components.stack.caller);
+            super();
+        }
+    };
 }
--- a/calendar/base/src/calCachedCalendar.js
+++ b/calendar/base/src/calCachedCalendar.js
@@ -1,14 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 
 var calICalendar = Components.interfaces.calICalendar;
 var cICL = Components.interfaces.calIChangeLog;
 var cIOL = Components.interfaces.calIOperationListener;
 
--- a/calendar/base/src/calCalendarManager.js
+++ b/calendar/base/src/calCalendarManager.js
@@ -1,17 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 
 var REGISTRY_BRANCH = "calendar.registry.";
 var DB_SCHEMA_VERSION = 10;
 var MAX_INT = Math.pow(2, 31) - 1;
 var MIN_INT = -MAX_INT;
 
 function calCalendarManager() {
     this.wrappedJSObject = this;
--- a/calendar/providers/caldav/calDavCalendar.js
+++ b/calendar/providers/caldav/calDavCalendar.js
@@ -7,17 +7,16 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 
 ChromeUtils.import("resource:///modules/OAuth2.jsm");
 
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calXMLUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calIteratorUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calAuthUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calAsyncUtils.jsm");
 
 //
 // calDavCalendar.js
 //
 
 var xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n';
--- a/calendar/providers/gdata/components/calGoogleCalendar.js
+++ b/calendar/providers/gdata/components/calGoogleCalendar.js
@@ -3,17 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.import("resource://calendar/modules/calAsyncUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 
 ChromeUtils.import("resource://gdata-provider/modules/calUtilsShim.jsm");
 ChromeUtils.import("resource://gdata-provider/modules/gdataLogging.jsm");
 ChromeUtils.import("resource://gdata-provider/modules/gdataRequest.jsm");
 ChromeUtils.import("resource://gdata-provider/modules/gdataSession.jsm");
 ChromeUtils.import("resource://gdata-provider/modules/gdataUtils.jsm");
 
--- a/calendar/providers/gdata/modules/calUtilsShim.jsm
+++ b/calendar/providers/gdata/modules/calUtilsShim.jsm
@@ -92,8 +92,20 @@ if (typeof cal.itip.isInvitation == "und
 
 if (typeof cal.l10n == "undefined") {
     cal.l10n = {
         getAnyString: function(aComponent, aBundle, aString, aParams) {
             return cal.calGetString(aBundle, aString, aParams, aComponent);
         }
     };
 }
+
+if (typeof cal.provider == "undefined") {
+    cal.provider = {
+        BaseClass: cal.ProviderBase,
+        prepHttpChannel: (...args) => cal.prepHttpChannel(...args),
+        sendHttpRequest: (...args) => cal.sendHttpRequest(...args),
+        createStreamLoader: (...args) => cal.createStreamLoader(...args),
+        InterfaceRequestor_getInterface: (...args) => cal.InterfaceRequestor_getInterface(...args),
+        convertByteArray: (...args) => cal.convertByteArray(...args),
+        promptOverwrite: (...args) => cal.promptOverwrite(...args)
+    };
+}
--- a/calendar/providers/gdata/modules/gdataSession.jsm
+++ b/calendar/providers/gdata/modules/gdataSession.jsm
@@ -11,17 +11,16 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 
 ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
 
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 
 ChromeUtils.import("resource://gdata-provider/modules/calUtilsShim.jsm");
 
 var cIFBI = Components.interfaces.calIFreeBusyInterval;
 var nIPM = Components.interfaces.nsIPermissionManager;
 
 var NOTIFY_TIMEOUT = 60 * 1000;
 
--- a/calendar/providers/gdata/modules/gdataUtils.jsm
+++ b/calendar/providers/gdata/modules/gdataUtils.jsm
@@ -7,17 +7,16 @@ ChromeUtils.import("resource://gdata-pro
 ChromeUtils.import("resource://gdata-provider/modules/timezoneMap.jsm");
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
 
 ChromeUtils.import("resource://calendar/modules/calAsyncUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 
 ChromeUtils.import("resource://gdata-provider/modules/calUtilsShim.jsm");
 
 var cIE = Components.interfaces.calIErrors;
 
 var FOUR_WEEKS_IN_MINUTES = 40320;
 
 var EXPORTED_SYMBOLS = [
--- a/calendar/providers/ics/calICSCalendar.js
+++ b/calendar/providers/ics/calICSCalendar.js
@@ -3,17 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calXMLUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 
 //
 // calICSCalendar.js
 //
 // This is a non-sync ics file. It reads the file pointer to by uri when set,
 // then writes it on updates. External changes to the file will be
 // ignored and overwritten.
 //
--- a/calendar/providers/memory/calMemoryCalendar.js
+++ b/calendar/providers/memory/calMemoryCalendar.js
@@ -1,16 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calIteratorUtils.jsm");
 
 //
 // calMemoryCalendar.js
 //
 
 var cICL = Components.interfaces.calIChangeLog;
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -2,17 +2,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calAlarmUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calStorageUpgrade.jsm");
 ChromeUtils.import("resource://calendar/modules/calStorageHelpers.jsm");
 
 var USECS_PER_SECOND = 1000000;
 var kCalICalendar = Components.interfaces.calICalendar;
 var cICL = Components.interfaces.calIChangeLog;
 
 //
--- a/calendar/providers/storage/calStorageUpgrade.jsm
+++ b/calendar/providers/storage/calStorageUpgrade.jsm
@@ -65,17 +65,16 @@
  * If this documentation isn't sufficient to make upgrading understandable,
  * please file a bug.
  */
 
 /* exported upgradeDB */
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calStorageHelpers.jsm");
 
 // The current database version. Be sure to increment this when you create a new
 // updater.
 var DB_SCHEMA_VERSION = 22;
 
 var EXPORTED_SYMBOLS = ["DB_SCHEMA_VERSION", "getSql", "getAllSql", "getSqlTable", "upgradeDB", "backupDB"];
 
--- a/calendar/test/unit/test_freebusy_service.js
+++ b/calendar/test/unit/test_freebusy_service.js
@@ -1,14 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 
 var cIFI = Components.interfaces.calIFreeBusyInterval;
 var freebusy = Components.classes["@mozilla.org/calendar/freebusy-service;1"]
                          .getService(Components.interfaces.calIFreeBusyService);
 
 function run_test() {
     do_calendar_startup(really_run_test);
 }
--- a/calendar/test/unit/test_gdata_provider.js
+++ b/calendar/test/unit/test_gdata_provider.js
@@ -20,17 +20,16 @@
 ChromeUtils.import("resource://testing-common/httpd.js");
 ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.import("resource://gdata-provider/modules/gdataSession.jsm");
 ChromeUtils.import("resource://gdata-provider/modules/gdataUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calAsyncUtils.jsm");
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
 ChromeUtils.import("resource://testing-common/MockRegistrar.jsm");
 
 var gServer;
 
 var MockConflictPrompt = {
     _origFunc: null,
     overwrite: false,
     register: function() {
--- a/calendar/test/unit/test_rfc3339_parser.js
+++ b/calendar/test/unit/test_rfc3339_parser.js
@@ -1,14 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-ChromeUtils.import("resource://calendar/modules/calProviderUtils.jsm");
-
 function run_test() {
     do_calendar_startup(really_run_test);
 }
 
 function really_run_test() {
     // Check if the RFC 3339 date and timezone are properly parsed to the
     // expected result and if the result is properly mapped back into the RFC
     // 3339 date.