Bug 1483638 - Update ical.js to the latest version. r=MakeMyDay
authorGeoff Lankow <geoff@darktrojan.net>
Sat, 10 Nov 2018 22:47:15 +1300
changeset 33688 8869c51e099512a60f05bb0c033677470a460052
parent 33687 b1d1536882ec74e6f43c29e692fb0911d7a08abe
child 33689 856dd9d687bd3089751b4aae08a5a2f07fd18586
push id388
push userclokep@gmail.com
push dateMon, 28 Jan 2019 20:54:56 +0000
reviewersMakeMyDay
bugs1483638
Bug 1483638 - Update ical.js to the latest version. r=MakeMyDay
calendar/base/modules/ical.js
--- a/calendar/base/modules/ical.js
+++ b/calendar/base/modules/ical.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * This is ical.js from <https://github.com/mozilla-comm/ical.js>.
  *
  * If you would like to change anything in ical.js, it is required to do so
  * upstream first.
  *
- * Current ical.js git revision: f4fb1b9564f990f7b718253f2251bb30f58c9a5a (v1.2.0)
+ * Current ical.js git revision: 3f219e04f238bcad6c964e8d93b21de49e5f5047 (v1.3.0)
  */
 
 var EXPORTED_SYMBOLS = ["ICAL", "unwrap", "unwrapSetter", "unwrapSingle", "wrapGetter"];
 
 function wrapGetter(type, val) {
     return val ? new type(val) : null;
 }
 
@@ -27,17 +27,17 @@ function unwrapSetter(type, val, innerFu
 
 function unwrapSingle(type, val) {
     if (!val || !val.wrappedJSObject) {
         return null;
     } else if (val.wrappedJSObject.innerObject instanceof type) {
         return val.wrappedJSObject.innerObject;
     } else {
         ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
-        Components.utils.reportError("Unknown " + (type.icalclass || type) + " passed at " + cal.STACK(10));
+        Cu.reportError("Unknown " + (type.icalclass || type) + " passed at " + cal.STACK(10));
         return null;
     }
 }
 
 // -- start ical.js --
 
 /* 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
@@ -75,16 +75,76 @@ ICAL.newLineChar = '\r\n';
 
 
 /**
  * Helper functions used in various places within ical.js
  * @namespace
  */
 ICAL.helpers = {
   /**
+   * Compiles a list of all referenced TZIDs in all subcomponents and
+   * removes any extra VTIMEZONE subcomponents. In addition, if any TZIDs
+   * are referenced by a component, but a VTIMEZONE does not exist,
+   * an attempt will be made to generate a VTIMEZONE using ICAL.TimezoneService.
+   *
+   * @param {ICAL.Component} vcal     The top-level VCALENDAR component.
+   * @return {ICAL.Component}         The ICAL.Component that was passed in.
+   */
+  updateTimezones: function(vcal) {
+    var allsubs, properties, vtimezones, reqTzid, i, tzid;
+
+    if (!vcal || vcal.name !== "vcalendar") {
+      //not a top-level vcalendar component
+      return vcal;
+    }
+
+    //Store vtimezone subcomponents in an object reference by tzid.
+    //Store properties from everything else in another array
+    allsubs = vcal.getAllSubcomponents();
+    properties = [];
+    vtimezones = {};
+    for (i = 0; i < allsubs.length; i++) {
+      if (allsubs[i].name === "vtimezone") {
+        tzid = allsubs[i].getFirstProperty("tzid").getFirstValue();
+        vtimezones[tzid] = allsubs[i];
+      } else {
+        properties = properties.concat(allsubs[i].getAllProperties());
+      }
+    }
+
+    //create an object with one entry for each required tz
+    reqTzid = {};
+    for (i = 0; i < properties.length; i++) {
+      if ((tzid = properties[i].getParameter("tzid"))) {
+        reqTzid[tzid] = true;
+      }
+    }
+
+    //delete any vtimezones that are not on the reqTzid list.
+    for (i in vtimezones) {
+      if (vtimezones.hasOwnProperty(i) && !reqTzid[i]) {
+        vcal.removeSubcomponent(vtimezones[i]);
+      }
+    }
+
+    //create any missing, but registered timezones
+    for (i in reqTzid) {
+      if (
+        reqTzid.hasOwnProperty(i) &&
+        !vtimezones[i] &&
+        ICAL.TimezoneService.has(i)
+      ) {
+        vcal.addSubcomponent(ICAL.TimezoneService.get(i).component);
+      }
+    }
+
+    return vcal;
+  },
+
+  /**
    * Checks if the given type is of the number type and also NaN.
    *
    * @param {Number} number     The number to check
    * @return {Boolean}          True, if the number is strictly NaN
    */
   isStrictlyNaN: function(number) {
     return typeof(number) === 'number' && isNaN(number);
   },
@@ -1068,17 +1128,17 @@ ICAL.design = (function() {
         var parts = aValue.split('T');
         return vcardValues.date.toICAL(parts[0]) +
                (parts[1] ? 'T' + vcardValues.time.toICAL(parts[1]) : '');
 
       }
     },
     timestamp: icalValues['date-time'],
     "language-tag": {
-      matches: /^[a-zA-Z0-9\-]+$/ // Could go with a more strict regex here
+      matches: /^[a-zA-Z0-9-]+$/ // Could go with a more strict regex here
     }
   });
 
   var vcardParams = {
     "type": {
       valueType: "text",
       multiValue: ","
     },
@@ -1420,17 +1480,18 @@ ICAL.stringify = (function() {
       designSetName = "vcard3";
     }
     designSet = designSet || design.getDesignSet(designSetName);
 
     for (; propIdx < propLen; propIdx++) {
       result += stringify.property(props[propIdx], designSet) + LINE_ENDING;
     }
 
-    var comps = component[2];
+    // Ignore subcomponents if none exist, e.g. in vCard.
+    var comps = component[2] || [];
     var compIdx = 0;
     var compLen = comps.length;
 
     for (; compIdx < compLen; compIdx++) {
       result += stringify.component(comps[compIdx], designSet) + LINE_ENDING;
     }
 
     result += 'END:' + name;
@@ -2913,16 +2974,32 @@ ICAL.Property = (function() {
       if (name in this.jCal[PROP_INDEX]) {
         return this.jCal[PROP_INDEX][name];
       } else {
         return undefined;
       }
     },
 
     /**
+     * Gets first parameter on the property.
+     *
+     * @param {String}        name   Property name (lowercase)
+     * @return {String}        Property value
+     */
+    getFirstParameter: function(name) {
+      var parameters = this.getParameter(name);
+
+      if (Array.isArray(parameters)) {
+        return parameters[0];
+      }
+
+      return parameters;
+    },
+
+    /**
      * Sets a parameter on the property.
      *
      * @param {String}       name     The parameter name
      * @param {Array|String} value    The parameter value
      */
     setParameter: function(name, value) {
       var lcname = name.toLowerCase();
       if (typeof value === "string" &&
@@ -5636,16 +5713,22 @@ ICAL.TimezoneService = (function() {
           if (this._pendingNormalization) {
             this._normalize();
             this._pendingNormalization = false;
           }
 
           return this._time[attr];
         },
         set: function setTimeAttr(val) {
+          // Check if isDate will be set and if was not set to normalize date.
+          // This avoids losing days when seconds, minutes and hours are zeroed
+          // what normalize will do when time is a date.
+          if (attr === "isDate" && val && !this._time.isDate) {
+            this.adjust(0, 0, 0, 0);
+          }
           this._cachedUnixTime = null;
           this._pendingNormalization = true;
           this._time[attr] = val;
 
           return val;
         }
       });
 
@@ -6205,31 +6288,31 @@ ICAL.TimezoneService = (function() {
 
   /**
    * @classdesc
    * This class represents the "recur" value type, with various calculation
    * and manipulation methods.
    *
    * @class
    * @alias ICAL.Recur
-   * @param {Object} data                       An object with members of the recurrence
-   * @param {ICAL.Recur.frequencyValues} freq   The frequency value
-   * @param {Number=} data.interval             The INTERVAL value
-   * @param {ICAL.Time.weekDay=} data.wkst      The week start value
-   * @param {ICAL.Time=} data.until             The end of the recurrence set
-   * @param {Number=} data.count                The number of occurrences
-   * @param {Array.<Number>=} data.bysecond     The seconds for the BYSECOND part
-   * @param {Array.<Number>=} data.byminute     The minutes for the BYMINUTE part
-   * @param {Array.<Number>=} data.byhour       The hours for the BYHOUR part
-   * @param {Array.<String>=} data.byday        The BYDAY values
-   * @param {Array.<Number>=} data.bymonthday   The days for the BYMONTHDAY part
-   * @param {Array.<Number>=} data.byyearday    The days for the BYYEARDAY part
-   * @param {Array.<Number>=} data.byweekno     The weeks for the BYWEEKNO part
-   * @param {Array.<Number>=} data.bymonth      The month for the BYMONTH part
-   * @param {Array.<Number>=} data.bysetpos     The positionals for the BYSETPOS part
+   * @param {Object} data                               An object with members of the recurrence
+   * @param {ICAL.Recur.frequencyValues=} data.freq     The frequency value
+   * @param {Number=} data.interval                     The INTERVAL value
+   * @param {ICAL.Time.weekDay=} data.wkst              The week start value
+   * @param {ICAL.Time=} data.until                     The end of the recurrence set
+   * @param {Number=} data.count                        The number of occurrences
+   * @param {Array.<Number>=} data.bysecond             The seconds for the BYSECOND part
+   * @param {Array.<Number>=} data.byminute             The minutes for the BYMINUTE part
+   * @param {Array.<Number>=} data.byhour               The hours for the BYHOUR part
+   * @param {Array.<String>=} data.byday                The BYDAY values
+   * @param {Array.<Number>=} data.bymonthday           The days for the BYMONTHDAY part
+   * @param {Array.<Number>=} data.byyearday            The days for the BYYEARDAY part
+   * @param {Array.<Number>=} data.byweekno             The weeks for the BYWEEKNO part
+   * @param {Array.<Number>=} data.bymonth              The month for the BYMONTH part
+   * @param {Array.<Number>=} data.bysetpos             The positionals for the BYSETPOS part
    */
   ICAL.Recur = function icalrecur(data) {
     this.wrappedJSObject = this;
     this.parts = {};
 
     if (data && typeof(data) === 'object') {
       this.fromData(data);
     }
@@ -6409,47 +6492,51 @@ ICAL.TimezoneService = (function() {
       }
 
       return next;
     },
 
     /**
      * Sets up the current instance using members from the passed data object.
      *
-     * @param {Object} data                       An object with members of the recurrence
-     * @param {ICAL.Recur.frequencyValues} freq   The frequency value
-     * @param {Number=} data.interval             The INTERVAL value
-     * @param {ICAL.Time.weekDay=} data.wkst      The week start value
-     * @param {ICAL.Time=} data.until             The end of the recurrence set
-     * @param {Number=} data.count                The number of occurrences
-     * @param {Array.<Number>=} data.bysecond     The seconds for the BYSECOND part
-     * @param {Array.<Number>=} data.byminute     The minutes for the BYMINUTE part
-     * @param {Array.<Number>=} data.byhour       The hours for the BYHOUR part
-     * @param {Array.<String>=} data.byday        The BYDAY values
-     * @param {Array.<Number>=} data.bymonthday   The days for the BYMONTHDAY part
-     * @param {Array.<Number>=} data.byyearday    The days for the BYYEARDAY part
-     * @param {Array.<Number>=} data.byweekno     The weeks for the BYWEEKNO part
-     * @param {Array.<Number>=} data.bymonth      The month for the BYMONTH part
-     * @param {Array.<Number>=} data.bysetpos     The positionals for the BYSETPOS part
+     * @param {Object} data                               An object with members of the recurrence
+     * @param {ICAL.Recur.frequencyValues=} data.freq     The frequency value
+     * @param {Number=} data.interval                     The INTERVAL value
+     * @param {ICAL.Time.weekDay=} data.wkst              The week start value
+     * @param {ICAL.Time=} data.until                     The end of the recurrence set
+     * @param {Number=} data.count                        The number of occurrences
+     * @param {Array.<Number>=} data.bysecond             The seconds for the BYSECOND part
+     * @param {Array.<Number>=} data.byminute             The minutes for the BYMINUTE part
+     * @param {Array.<Number>=} data.byhour               The hours for the BYHOUR part
+     * @param {Array.<String>=} data.byday                The BYDAY values
+     * @param {Array.<Number>=} data.bymonthday           The days for the BYMONTHDAY part
+     * @param {Array.<Number>=} data.byyearday            The days for the BYYEARDAY part
+     * @param {Array.<Number>=} data.byweekno             The weeks for the BYWEEKNO part
+     * @param {Array.<Number>=} data.bymonth              The month for the BYMONTH part
+     * @param {Array.<Number>=} data.bysetpos             The positionals for the BYSETPOS part
      */
     fromData: function(data) {
       for (var key in data) {
         var uckey = key.toUpperCase();
 
         if (uckey in partDesign) {
           if (Array.isArray(data[key])) {
             this.parts[uckey] = data[key];
           } else {
             this.parts[uckey] = [data[key]];
           }
         } else {
           this[key] = data[key];
         }
       }
 
+      if (this.interval && typeof this.interval != "number") {
+        optionDesign.INTERVAL(this.interval, this);
+      }
+
       if (this.wkst && typeof this.wkst != "number") {
         this.wkst = ICAL.Recur.icalDayToNumericDay(this.wkst);
       }
 
       if (this.until && !(this.until instanceof ICAL.Time)) {
         this.until = ICAL.Time.fromString(this.until);
       }
     },
@@ -6507,17 +6594,17 @@ ICAL.TimezoneService = (function() {
       }
       for (var k in this.parts) {
         /* istanbul ignore else */
         if (this.parts.hasOwnProperty(k)) {
           str += ";" + k + "=" + this.parts[k];
         }
       }
       if (this.until) {
-        str += ';UNTIL=' + this.until.toString();
+        str += ';UNTIL=' + this.until.toICALString();
       }
       if ('wkst' in this && this.wkst !== ICAL.Time.DEFAULT_WEEK_START) {
         str += ';WKST=' + ICAL.Recur.numericDayToIcalDay(this.wkst);
       }
       return str;
     }
   };
 
@@ -6609,24 +6696,23 @@ ICAL.TimezoneService = (function() {
       if (dict.interval < 1) {
         // 0 or negative values are not allowed, some engines seem to generate
         // it though. Assume 1 instead.
         dict.interval = 1;
       }
     },
 
     UNTIL: function(value, dict, fmtIcal) {
-      if (fmtIcal) {
-        if (value.length > 10) {
-          dict.until = ICAL.design.icalendar.value['date-time'].fromICAL(value);
-        } else {
-          dict.until = ICAL.design.icalendar.value.date.fromICAL(value);
-        }
+      if (value.length > 10) {
+        dict.until = ICAL.design.icalendar.value['date-time'].fromICAL(value);
       } else {
-        dict.until = ICAL.Time.fromString(value);
+        dict.until = ICAL.design.icalendar.value.date.fromICAL(value);
+      }
+      if (!fmtIcal) {
+        dict.until = ICAL.Time.fromString(dict.until);
       }
     },
 
     WKST: function(value, dict, fmtIcal) {
       if (VALID_DAY_NAMES.test(value)) {
         dict.wkst = ICAL.Recur.icalDayToNumericDay(value);
       } else {
         throw new Error('invalid WKST value "' + value + '"');
@@ -6663,31 +6749,31 @@ ICAL.TimezoneService = (function() {
     var data = ICAL.Recur._stringToData(string, false);
     return new ICAL.Recur(data);
   };
 
   /**
    * Creates a new {@link ICAL.Recur} instance using members from the passed
    * data object.
    *
-   * @param {Object} aData                      An object with members of the recurrence
-   * @param {ICAL.Recur.frequencyValues} freq   The frequency value
-   * @param {Number=} aData.interval            The INTERVAL value
-   * @param {ICAL.Time.weekDay=} aData.wkst     The week start value
-   * @param {ICAL.Time=} aData.until            The end of the recurrence set
-   * @param {Number=} aData.count               The number of occurrences
-   * @param {Array.<Number>=} aData.bysecond    The seconds for the BYSECOND part
-   * @param {Array.<Number>=} aData.byminute    The minutes for the BYMINUTE part
-   * @param {Array.<Number>=} aData.byhour      The hours for the BYHOUR part
-   * @param {Array.<String>=} aData.byday       The BYDAY values
-   * @param {Array.<Number>=} aData.bymonthday  The days for the BYMONTHDAY part
-   * @param {Array.<Number>=} aData.byyearday   The days for the BYYEARDAY part
-   * @param {Array.<Number>=} aData.byweekno    The weeks for the BYWEEKNO part
-   * @param {Array.<Number>=} aData.bymonth     The month for the BYMONTH part
-   * @param {Array.<Number>=} aData.bysetpos    The positionals for the BYSETPOS part
+   * @param {Object} aData                              An object with members of the recurrence
+   * @param {ICAL.Recur.frequencyValues=} aData.freq    The frequency value
+   * @param {Number=} aData.interval                    The INTERVAL value
+   * @param {ICAL.Time.weekDay=} aData.wkst             The week start value
+   * @param {ICAL.Time=} aData.until                    The end of the recurrence set
+   * @param {Number=} aData.count                       The number of occurrences
+   * @param {Array.<Number>=} aData.bysecond            The seconds for the BYSECOND part
+   * @param {Array.<Number>=} aData.byminute            The minutes for the BYMINUTE part
+   * @param {Array.<Number>=} aData.byhour              The hours for the BYHOUR part
+   * @param {Array.<String>=} aData.byday               The BYDAY values
+   * @param {Array.<Number>=} aData.bymonthday          The days for the BYMONTHDAY part
+   * @param {Array.<Number>=} aData.byyearday           The days for the BYYEARDAY part
+   * @param {Array.<Number>=} aData.byweekno            The weeks for the BYWEEKNO part
+   * @param {Array.<Number>=} aData.bymonth             The month for the BYMONTH part
+   * @param {Array.<Number>=} aData.bysetpos            The positionals for the BYSETPOS part
    */
   ICAL.Recur.fromData = function(aData) {
     return new ICAL.Recur(aData);
   };
 
   /**
    * Converts a recurrence string to a data object, suitable for the fromData
    * method.
@@ -8679,17 +8765,19 @@ ICAL.Event = (function() {
    *
    * @class
    * @alias ICAL.Event
    * @param {ICAL.Component=} component         The ICAL.Component to base this event on
    * @param {Object} options                    Options for this event
    * @param {Boolean} options.strictExceptions
    *          When true, will verify exceptions are related by their UUID
    * @param {Array<ICAL.Component|ICAL.Event>} options.exceptions
-   *          Exceptions to this event, either as components or events
+   *          Exceptions to this event, either as components or events. If not
+   *            specified exceptions will automatically be set in relation of
+   *            component's parent
    */
   function Event(component, options) {
     if (!(component instanceof ICAL.Component)) {
       options = component;
       component = null;
     }
 
     if (component) {
@@ -8703,16 +8791,22 @@ ICAL.Event = (function() {
     this.rangeExceptions = [];
 
     if (options && options.strictExceptions) {
       this.strictExceptions = options.strictExceptions;
     }
 
     if (options && options.exceptions) {
       options.exceptions.forEach(this.relateException, this);
+    } else if (this.component.parent && !this.isRecurrenceException()) {
+      this.component.parent.getAllSubcomponents('vevent').forEach(function(event) {
+        if (event.hasProperty('recurrence-id')) {
+          this.relateException(event);
+        }
+      }, this);
     }
   }
 
   Event.prototype = {
 
     THISANDFUTURE: 'THISANDFUTURE',
 
     /**
@@ -8779,17 +8873,21 @@ ICAL.Event = (function() {
 
     /**
      * Checks if this record is an exception and has the RANGE=THISANDFUTURE
      * value.
      *
      * @return {Boolean}        True, when exception is within range
      */
     modifiesFuture: function() {
-      var range = this.component.getFirstPropertyValue('range');
+      if (!this.component.hasProperty('recurrence-id')) {
+        return false;
+      }
+
+      var range = this.component.getFirstProperty('recurrence-id').getParameter('range');
       return range === this.THISANDFUTURE;
     },
 
     /**
      * Finds the range exception nearest to the given date.
      *
      * @param {ICAL.Time} time usually an occurrence time of an event
      * @return {?ICAL.Event} the related event/exception or null
@@ -9000,17 +9098,18 @@ ICAL.Event = (function() {
     },
 
     set startDate(value) {
       this._setTime('dtstart', value);
     },
 
     /**
      * The end date. This can be the result directly from the property, or the
-     * end date calculated from start date and duration.
+     * end date calculated from start date and duration. Setting the property
+     * will remove any duration properties.
      * @type {ICAL.Time}
      */
     get endDate() {
       var endDate = this._firstProp('dtend');
       if (!endDate) {
           var duration = this._firstProp('duration');
           endDate = this.startDate.clone();
           if (duration) {
@@ -9018,33 +9117,44 @@ ICAL.Event = (function() {
           } else if (endDate.isDate) {
               endDate.day += 1;
           }
       }
       return endDate;
     },
 
     set endDate(value) {
+      if (this.component.hasProperty('duration')) {
+        this.component.removeProperty('duration');
+      }
       this._setTime('dtend', value);
     },
 
     /**
      * The duration. This can be the result directly from the property, or the
-     * duration calculated from start date and end date.
+     * duration calculated from start date and end date. Setting the property
+     * will remove any `dtend` properties.
      * @type {ICAL.Duration}
-     * @readonly
      */
     get duration() {
       var duration = this._firstProp('duration');
       if (!duration) {
         return this.endDate.subtractDate(this.startDate);
       }
       return duration;
     },
 
+    set duration(value) {
+      if (this.component.hasProperty('dtend')) {
+        this.component.removeProperty('dtend');
+      }
+
+      this._setProp('duration', value);
+    },
+
     /**
      * The location of the event.
      * @type {String}
      */
     get location() {
       return this._firstProp('location');
     },