Bug 1886417 - remove HashedArray. r=leftmostcat

Differential Revision: https://phabricator.services.mozilla.com/D205206

--HG--
extra : rebase_source : f205ecf13436076a9c409ab1c87d26b38ef530ba
This commit is contained in:
Magnus Melin 2024-03-22 09:50:18 +02:00
Родитель 319ed56ad7
Коммит 7dc5369bf3
8 изменённых файлов: 49 добавлений и 658 удалений

Просмотреть файл

@ -7,10 +7,12 @@
/* import-globals-from item-editing/calendar-item-editing.js */
/* import-globals-from widgets/mouseoverPreviews.js */
/* globals cal */
var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs");
/**
* The tree view for a CalendarTaskTree.
*
* @implements {nsITreeView}
*/
class CalendarTaskTreeView {
/**
@ -80,12 +82,12 @@ class CalendarTaskTreeView {
* Removes an array of old items from the list, and adds an array of new items if
* they match the currently applied filter.
*
* @param {object[]} newItems - An array of new items to add.
* @param {object[]} oldItems - An array of old items to remove.
* @param {boolean} [doNotSort] - Whether to re-sort the list after modifying it.
* @param {boolean} [selectNew] - Whether to select the new tasks.
* @param {calIItemBase[]} newItems - An array of new items to add.
* @param {calIItemBase[]} oldItems - An array of old items to remove.
* @param {boolean} [doNotSort=false] - Whether to re-sort the list after modifying it.
* @param {boolean} [selectNew=false] - Whether to select the new tasks.
*/
modifyItems(newItems = [], oldItems = [], doNotSort, selectNew) {
modifyItems(newItems, oldItems, doNotSort = false, selectNew = false) {
const selItem = this.tree.currentTask;
let selIndex = this.tree.currentIndex;
let firstHash = null;
@ -93,16 +95,10 @@ class CalendarTaskTreeView {
this.tree.beginUpdateBatch();
const idiff = new cal.item.ItemDiff();
idiff.load(oldItems);
idiff.difference(newItems);
idiff.complete();
const delItems = idiff.deletedItems;
const addItems = idiff.addedItems;
const modItems = idiff.modifiedItems;
const { deletedItems, addedItems, modifiedItems } = cal.item.interDiff(oldItems, newItems);
// Find the indexes of the old items that need to be removed.
for (const item of delItems.mArray) {
for (const item of deletedItems) {
if (item.hashId in this.tree.mHash2Index) {
// The old item needs to be removed.
remIndexes.push(this.tree.mHash2Index[item.hashId]);
@ -111,7 +107,7 @@ class CalendarTaskTreeView {
}
// Modified items need to be updated.
for (const item of modItems.mArray) {
for (const item of modifiedItems) {
if (item.hashId in this.tree.mHash2Index) {
// Make sure we're using the new version of a modified item.
this.tree.mTaskArray[this.tree.mHash2Index[item.hashId]] = item;
@ -127,7 +123,7 @@ class CalendarTaskTreeView {
});
// Add the new items.
for (const item of addItems.mArray) {
for (const item of addedItems) {
if (!(item.hashId in this.tree.mHash2Index)) {
const index = this.tree.mTaskArray.length;
this.tree.mTaskArray.push(item);
@ -194,7 +190,7 @@ class CalendarTaskTreeView {
* @param {Event} event - An event.
* @param {object} [col] - A column object.
* @param {object} [row] - A row object.
* @returns {object | false} The task object related to the event or false if none found.
* @returns {?calITodo} the task object related to the event, if any.
*/
getItemFromEvent(event, col, row) {
const { col: eventColumn, row: eventRow } = this.tree.getCellAt(event.clientX, event.clientY);
@ -204,7 +200,7 @@ class CalendarTaskTreeView {
if (row) {
row.value = eventRow;
}
return eventRow > -1 && this.tree.mTaskArray[eventRow];
return eventRow > -1 ? this.tree.mTaskArray[eventRow] : null;
}
// nsITreeView Methods and Properties
@ -353,9 +349,10 @@ class CalendarTaskTreeView {
}
/**
* Called to link the task tree to the tree view. A null argument un-sets/un-links the tree.
* Called to link the task tree to the tree view.
* A null argument un-sets/un-links the tree.
*
* @param {object | null} tree
* @param {?XULTreeElement} tree
*/
setTree(tree) {
const hasOldTree = this.tree != null;
@ -484,8 +481,8 @@ class CalendarTaskTreeView {
/**
* Format a datetime object for display.
*
* @param {object} dateTime - From a todo object, not a JavaScript date.
* @returns {string} Formatted string version of the datetime ("" if invalid).
* @param {calIDateTime} dateTime - Datetime, from a calITodo object.
* @returns {string} a formatted string version of the datetime ("" if invalid).
*/
_formatDateTime(dateTime) {
return dateTime && dateTime.isValid

Просмотреть файл

@ -463,7 +463,7 @@
* Return the task object related to a given event.
*
* @param {Event} event - The event.
* @returns {object | false} The task object related to the event or false if none found.
* @returns {?calITodo} the task object related to the event, if any.
*/
getTaskFromEvent(event) {
return this.mTreeView.getItemFromEvent(event);

Просмотреть файл

@ -1,258 +0,0 @@
/* 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/. */
import { data } from "resource:///modules/calendar/utils/calDataUtils.sys.mjs";
/**
* An unsorted array of hashable items with some extra functions to quickly
* retrieve the item by its hash id.
*
* Performance Considerations:
* - Accessing items is fast
* - Adding items is fast (they are added to the end)
* - Deleting items is O(n)
* - Modifying items is fast.
*/
export const HashedArray = function () {
this.clear();
}; // even though it's defined in calUtils.sys.mjs, import needs this
HashedArray.prototype = {
mArray: null,
mHash: null,
mBatch: 0,
mFirstDirty: -1,
/**
* Returns a copy of the internal array. Note this is a shallow copy.
*/
get arrayCopy() {
return this.mArray.concat([]);
},
/**
* The function to retrieve the hashId given the item. This function can be
* overridden by implementations, in case the added items are not instances
* of calIItemBase.
*
* @param item The item to get the hashId for
* @returns The hashId of the item
*/
hashAccessor(item) {
return item.hashId;
},
/**
* Returns the item, given its index in the array
*
* @param index The index of the item to retrieve.
* @returns The retrieved item.
*/
itemByIndex(index) {
return this.mArray[index];
},
/**
* Returns the item, given its hashId
*
* @param id The hashId of the item to retrieve.
* @returns The retrieved item.
*/
itemById(id) {
if (this.mBatch > 0) {
throw new Error("Accessing Array by ID not supported in batch mode");
}
return id in this.mHash ? this.mArray[this.mHash[id]] : null;
},
/**
* Returns the index of the given item. This function is cheap performance
* wise, since it uses the hash
*
* @param item The item to search for.
* @returns The index of the item.
*/
indexOf(item) {
if (this.mBatch > 0) {
throw new Error("Accessing Array Indexes not supported in batch mode");
}
const hashId = this.hashAccessor(item);
return hashId in this.mHash ? this.mHash[hashId] : -1;
},
/**
* Remove the item with the given hashId.
*
* @param id The id of the item to be removed
*/
removeById(id) {
if (this.mBatch > 0) {
throw new Error("Remvoing by ID in batch mode is not supported"); /* TODO */
}
const index = this.mHash[id];
delete this.mHash[id];
this.mArray.splice(index, 1);
this.reindex(index);
},
/**
* Remove the item at the given index.
*
* @param index The index of the item to remove.
*/
removeByIndex(index) {
delete this.mHash[this.hashAccessor(this.mArray[index])];
this.mArray.splice(index, 1);
this.reindex(index);
},
/**
* Clear the whole array, removing all items. This also resets batch mode.
*/
clear() {
this.mHash = {};
this.mArray = [];
this.mFirstDirty = -1;
this.mBatch = 0;
},
/**
* Add the item to the array
*
* @param item The item to add.
* @returns The index of the added item.
*/
addItem(item) {
const index = this.mArray.length;
this.mArray.push(item);
this.reindex(index);
return index;
},
/**
* Modifies the item in the array. If the item is already in the array, then
* it is replaced by the passed item. Otherwise, the item is added to the
* array.
*
* @param item The item to modify.
* @returns The (new) index.
*/
modifyItem(item) {
const hashId = this.hashAccessor(item);
if (hashId in this.mHash) {
const index = this.mHash[this.hashAccessor(item)];
this.mArray[index] = item;
return index;
}
return this.addItem(item);
},
/**
* Reindexes the items in the array. This function is mostly used
* internally. All parameters are inclusive. The ranges are automatically
* swapped if from > to.
*
* @param from (optional) The index to start indexing from. If left
* out, defaults to 0.
* @param to (optional) The index to end indexing on. If left out,
* defaults to the array length.
*/
reindex(from, to) {
if (this.mArray.length == 0) {
return;
}
from = from === undefined ? 0 : from;
to = to === undefined ? this.mArray.length - 1 : to;
from = Math.min(this.mArray.length - 1, Math.max(0, from));
to = Math.min(this.mArray.length - 1, Math.max(0, to));
if (from > to) {
const tmp = from;
from = to;
to = tmp;
}
if (this.mBatch > 0) {
// No indexing in batch mode, but remember from where to index.
this.mFirstDirty = Math.min(Math.max(0, this.mFirstDirty), from);
return;
}
for (let idx = from; idx <= to; idx++) {
this.mHash[this.hashAccessor(this.mArray[idx])] = idx;
}
},
startBatch() {
this.mBatch++;
},
endBatch() {
this.mBatch = Math.max(0, this.mBatch - 1);
if (this.mBatch == 0 && this.mFirstDirty > -1) {
this.reindex(this.mFirstDirty);
this.mFirstDirty = -1;
}
},
/**
* Iterator to allow iterating the hashed array object.
*/
*[Symbol.iterator]() {
yield* this.mArray;
},
};
/**
*
* Sorted hashed array. The array always stays sorted.
* TODO: unused outside of tests. Remove?
*
* Performance Considerations:
* - Accessing items is fast
* - Adding and deleting items is O(n)
* - Modifying items is fast.
*/
export const SortedHashedArray = function (comparator) {
HashedArray.apply(this, arguments);
if (!comparator) {
throw new Error("Sorted Hashed Array needs a comparator");
}
this.mCompFunc = comparator;
};
SortedHashedArray.prototype = {
__proto__: HashedArray.prototype,
mCompFunc: null,
addItem(item) {
const newIndex = data.binaryInsert(this.mArray, item, this.mCompFunc, false);
this.reindex(newIndex);
return newIndex;
},
modifyItem(item) {
const hashId = this.hashAccessor(item);
if (hashId in this.mHash) {
const cmp = this.mCompFunc(item, this.mArray[this.mHash[hashId]]);
if (cmp == 0) {
// The item will be at the same index, we just need to replace it
this.mArray[this.mHash[hashId]] = item;
return this.mHash[hashId];
}
const oldIndex = this.mHash[hashId];
const newIndex = data.binaryInsert(this.mArray, item, this.mCompFunc, false);
this.mArray.splice(oldIndex, 1);
this.reindex(oldIndex, newIndex);
return newIndex;
}
return this.addItem(item);
},
};

Просмотреть файл

@ -29,7 +29,6 @@ EXTRA_JS_MODULES.calendar.utils += [
EXTRA_JS_MODULES.calendar += [
"calCalendarDeactivator.sys.mjs",
"calExtract.sys.mjs",
"calHashedArray.sys.mjs",
"calRecurrenceUtils.sys.mjs",
"calUtils.sys.mjs",
"Ical.sys.mjs",

Просмотреть файл

@ -2,7 +2,6 @@
* 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/. */
import { HashedArray } from "resource:///modules/calendar/calHashedArray.sys.mjs";
import { iterate } from "resource:///modules/calendar/utils/calIteratorUtils.sys.mjs";
import { data } from "resource:///modules/calendar/utils/calDataUtils.sys.mjs";
import { dtz } from "resource:///modules/calendar/utils/calDateTimeUtils.sys.mjs";
@ -15,174 +14,39 @@ import { dtz } from "resource:///modules/calendar/utils/calDateTimeUtils.sys.mjs
// including calUtils.sys.mjs under the cal.item namespace.
export var item = {
ItemDiff: (function () {
/**
* Given two sets of items, find out which items were added, changed or
* removed.
*
* The general flow is to first use load method to load the engine with
* the first set of items, then use difference to load the set of
* items to diff against. Afterwards, call the complete method to tell the
* engine that no more items are coming.
*
* You can then access the mAddedItems/mModifiedItems/mDeletedItems attributes to
* get the items that were changed during the process.
*/
function ItemDiff() {
this.reset();
/**
* Calculcate difference between items.
*
* @param {calIItemBase[]} oldItems - Old items.
* @param {calIItemBase[]} newItems - New items.
* @returns {object} interdiff
* @returns {calIItemBase[]} interdiff.deletedItems
* @returns {calIItemBase[]} interdiff.addedItems
* @returns {calIItemBase[]} interdiff.modifiedItems
*/
interDiff(oldItems, newItems) {
const addedItems = [];
const modifiedItems = [];
const initialItems = new Map(oldItems.map(item => [item.hashId, item]));
for (const item of newItems) {
if (initialItems.has(item.hashId)) {
modifiedItems.push(item);
} else {
addedItems.push(item);
}
initialItems.delete(item.hashId);
}
ItemDiff.prototype = {
STATE_INITIAL: 1,
STATE_LOADING: 2,
STATE_DIFFERING: 4,
STATE_COMPLETED: 8,
state: 1,
mInitialItems: null,
mModifiedItems: null,
mModifiedOldItems: null,
mAddedItems: null,
mDeletedItems: null,
/**
* Expect the difference engine to be in the given state.
*
* @param aState The state to be in
* @param aMethod The method name expecting the state
*/
_expectState(aState, aMethod) {
if ((this.state & aState) == 0) {
throw new Error(
"ItemDiff method " + aMethod + " called while in unexpected state " + this.state
);
}
},
/**
* Loads an array of items. This step cannot be executed
* after calling the difference methods.
*
* @param items The array of items to load
*/
load(items) {
this._expectState(this.STATE_INITIAL | this.STATE_LOADING, "load");
for (const calendarItem of items) {
this.mInitialItems[calendarItem.hashId] = calendarItem;
}
this.state = this.STATE_LOADING;
},
/**
* Calculate the difference for the array of items. This method should be
* called after all load methods and before the complete method.
*
* @param items The array of items to calculate difference with
*/
difference(items) {
this._expectState(
this.STATE_INITIAL | this.STATE_LOADING | this.STATE_DIFFERING,
"difference"
);
this.mModifiedOldItems.startBatch();
this.mModifiedItems.startBatch();
this.mAddedItems.startBatch();
for (const calendarItem of items) {
if (calendarItem.hashId in this.mInitialItems) {
const oldItem = this.mInitialItems[calendarItem.hashId];
this.mModifiedOldItems.addItem(oldItem);
this.mModifiedItems.addItem(calendarItem);
} else {
this.mAddedItems.addItem(calendarItem);
}
delete this.mInitialItems[calendarItem.hashId];
}
this.mModifiedOldItems.endBatch();
this.mModifiedItems.endBatch();
this.mAddedItems.endBatch();
this.state = this.STATE_DIFFERING;
},
/**
* Tell the engine that all load and difference calls have been made, this
* makes sure that all item states are correctly returned.
*/
complete() {
this._expectState(
this.STATE_INITIAL | this.STATE_LOADING | this.STATE_DIFFERING,
"complete"
);
this.mDeletedItems.startBatch();
for (const hashId in this.mInitialItems) {
const calendarItem = this.mInitialItems[hashId];
this.mDeletedItems.addItem(calendarItem);
}
this.mDeletedItems.endBatch();
this.mInitialItems = {};
this.state = this.STATE_COMPLETED;
},
/** @returns a HashedArray containing the new version of the modified items */
get modifiedItems() {
this._expectState(this.STATE_COMPLETED, "get modifiedItems");
return this.mModifiedItems;
},
/** @returns a HashedArray containing the old version of the modified items */
get modifiedOldItems() {
this._expectState(this.STATE_COMPLETED, "get modifiedOldItems");
return this.mModifiedOldItems;
},
/** @returns a HashedArray containing added items */
get addedItems() {
this._expectState(this.STATE_COMPLETED, "get addedItems");
return this.mAddedItems;
},
/** @returns a HashedArray containing deleted items */
get deletedItems() {
this._expectState(this.STATE_COMPLETED, "get deletedItems");
return this.mDeletedItems;
},
/** @returns the number of loaded items */
get count() {
return Object.keys(this.mInitialItems).length;
},
/**
* Resets the difference engine to its initial state.
*/
reset() {
this.mInitialItems = {};
this.mModifiedItems = new HashedArray();
this.mModifiedOldItems = new HashedArray();
this.mAddedItems = new HashedArray();
this.mDeletedItems = new HashedArray();
this.state = this.STATE_INITIAL;
},
};
return ItemDiff;
})(),
const deletedItems = [...initialItems.values()];
return { deletedItems, addedItems, modifiedItems };
},
/**
* Checks if an item is supported by a Calendar.
*
* @param aCalendar the calendar
* @param aItem the item either a task or an event
* @returns true or false
* @param {calICalendar} aCalendar - The calendar to check.
* @param {calIItemBase} aItem - The item; either a task or an event.
* @returns {boolean} true if supported.
*/
isItemSupported(aItem, aCalendar) {
if (aItem.isTodo()) {
@ -196,7 +60,7 @@ export var item = {
/*
* Checks whether a calendar supports events
*
* @param aCalendar
* @param {calICalendar} aCalendar
*/
isEventCalendar(aCalendar) {
return aCalendar.getProperty("capabilities.events.supported") !== false;

Просмотреть файл

@ -1,209 +0,0 @@
/* 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/. */
var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs");
ChromeUtils.defineESModuleGetters(this, {
CalEvent: "resource:///modules/CalEvent.sys.mjs",
HashedArray: "resource:///modules/calendar/calHashedArray.sys.mjs",
SortedHashedArray: "resource:///modules/calendar/calHashedArray.sys.mjs",
});
function run_test() {
test_array_base();
test_array_sorted();
test_hashAccessor();
}
/**
* Helper function to create an item that has a sensible hash id, with the given
* title identification.
*
* @param ident The title to identify the item.
* @returns The created item.
*/
function hashedCreateItem(ident) {
const item = new CalEvent();
item.calendar = { id: "test" };
item.id = cal.getUUID();
item.title = ident;
return item;
}
/**
* Comparator function to sort the items by their title
*
* @param a Object to compare.
* @param b Object to compare with.
* @returns 0, -1, or 1 (usual comptor meanings)
*/
function titleComptor(a, b) {
if (a.title > b.title) {
return 1;
} else if (a.title < b.title) {
return -1;
}
return 0;
}
/**
* Checks if the hashed array accessor functions work for the status of the
* items array.
*
* @param har The Hashed Array
* @param testItems The array of test items
* @param itemAccessor The accessor func to retrieve the items
* @throws Exception If the arrays are not the same.
*/
function checkConsistancy(har, testItems, itemAccessor) {
itemAccessor =
itemAccessor ||
function (item) {
return item;
};
for (const idx in testItems) {
const testItem = itemAccessor(testItems[idx]);
equal(itemAccessor(har.itemByIndex(idx)).title, testItem.title);
equal(itemAccessor(har.itemById(testItem.hashId)).title, testItem.title);
equal(har.indexOf(testItems[idx]), idx);
}
}
/**
* Man, this function is really hard to keep general enough, I'm almost tempted
* to duplicate the code. It checks if the remove and modify operations work for
* the given hashed array.
*
* @param har The Hashed Array
* @param testItems The js array with the items
* @param postprocessFunc (optional) The function to call after each
* operation, but before checking consistency.
* @param itemAccessor (optional) The function to access the item for an
* array element.
* @param itemCreator (optional) Function to create a new item for the
* array.
*/
function testRemoveModify(har, testItems, postprocessFunc, itemAccessor, itemCreator) {
postprocessFunc =
postprocessFunc ||
function (a, b) {
return [a, b];
};
itemCreator = itemCreator || (title => hashedCreateItem(title));
itemAccessor =
itemAccessor ||
function (item) {
return item;
};
// Now, delete the second item and check again
har.removeById(itemAccessor(testItems[1]).hashId);
testItems.splice(1, 1);
[har, testItems] = postprocessFunc(har, testItems);
checkConsistancy(har, testItems, itemAccessor);
// Try the same by index
har.removeByIndex(2);
testItems.splice(2, 1);
[har, testItems] = postprocessFunc(har, testItems);
checkConsistancy(har, testItems, itemAccessor);
// Try modifying an item
const newInstance = itemCreator("z-changed");
itemAccessor(newInstance).id = itemAccessor(testItems[0]).id;
testItems[0] = newInstance;
har.modifyItem(newInstance);
[har, testItems] = postprocessFunc(har, testItems);
checkConsistancy(har, testItems, itemAccessor);
}
/**
* Tests the basic HashedArray
*/
function test_array_base() {
let har, testItems;
// Test normal additions
har = new HashedArray();
testItems = ["a", "b", "c", "d"].map(hashedCreateItem);
testItems.forEach(har.addItem, har);
checkConsistancy(har, testItems);
testRemoveModify(har, testItems);
// Test adding in batch mode
har = new HashedArray();
testItems = ["e", "f", "g", "h"].map(hashedCreateItem);
har.startBatch();
testItems.forEach(har.addItem, har);
har.endBatch();
checkConsistancy(har, testItems);
testRemoveModify(har, testItems);
}
/**
* Tests the sorted SortedHashedArray
*/
function test_array_sorted() {
let har, testItems, testItemsSorted;
function sortedPostProcess(harParam, tiParam) {
tiParam = tiParam.sort(titleComptor);
return [harParam, tiParam];
}
// Test normal additions
har = new SortedHashedArray(titleComptor);
testItems = ["d", "c", "a", "b"].map(hashedCreateItem);
testItemsSorted = testItems.sort(titleComptor);
testItems.forEach(har.addItem, har);
checkConsistancy(har, testItemsSorted);
testRemoveModify(har, testItemsSorted, sortedPostProcess);
// Test adding in batch mode
har = new SortedHashedArray(titleComptor);
testItems = ["e", "f", "g", "h"].map(hashedCreateItem);
testItemsSorted = testItems.sort(titleComptor);
har.startBatch();
testItems.forEach(har.addItem, har);
har.endBatch();
checkConsistancy(har, testItemsSorted);
testRemoveModify(har, testItemsSorted, sortedPostProcess);
}
/**
* Tests SortedHashedArray with a custom hashAccessor.
*/
function test_hashAccessor() {
const comptor = (a, b) => titleComptor(a.item, b.item);
const har = new SortedHashedArray(comptor);
har.hashAccessor = function (obj) {
return obj.item.hashId;
};
function itemAccessor(obj) {
if (!obj) {
do_throw("WTF?");
}
return obj.item;
}
function itemCreator(title) {
return { item: hashedCreateItem(title) };
}
function sortedPostProcess(harParam, tiParam) {
tiParam = tiParam.sort(comptor);
return [harParam, tiParam];
}
const testItems = ["d", "c", "a", "b"].map(itemCreator);
const testItemsSorted = testItems.sort(comptor);
testItems.forEach(har.addItem, har);
checkConsistancy(har, testItemsSorted, itemAccessor);
testRemoveModify(har, testItemsSorted, sortedPostProcess, itemAccessor, itemCreator);
}

Просмотреть файл

@ -52,7 +52,6 @@ tags = oauth
[test_filter_tree_view.js]
[test_freebusy.js]
[test_freebusy_service.js]
[test_hashedarray.js]
[test_ics.js]
[test_ics_parser.js]
[test_ics_service.js]

Просмотреть файл

@ -282,7 +282,6 @@
"resource:///modules/calendar/Ical.jsm": "comm/calendar/base/modules/Ical.jsm",
"resource:///modules/calendar/calCalendarDeactivator.jsm": "comm/calendar/base/modules/calCalendarDeactivator.jsm",
"resource:///modules/calendar/calExtract.jsm": "comm/calendar/base/modules/calExtract.jsm",
"resource:///modules/calendar/calHashedArray.jsm": "comm/calendar/base/modules/calHashedArray.jsm",
"resource:///modules/calendar/calRecurrenceUtils.jsm": "comm/calendar/base/modules/calRecurrenceUtils.jsm",
"resource:///modules/calendar/calStorageHelpers.jsm": "comm/calendar/providers/storage/calStorageHelpers.jsm",
"resource:///modules/calendar/calStorageUpgrade.jsm": "comm/calendar/providers/storage/calStorageUpgrade.jsm",