Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
6
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
7
var { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm");
8
9
// Usually the backend loader gets loaded via profile-after-change, but in case
10
// a calendar component hooks in earlier, its very likely it will use calUtils.
11
// Getting the service here will load if its not already loaded
12
Cc["@mozilla.org/calendar/backend-loader;1"].getService();
13
14
// The calendar console instance
15
var gCalendarConsole = new ConsoleAPI({
16
prefix: "Lightning",
17
consoleID: "calendar",
18
maxLogLevel: Services.prefs.getBoolPref("calendar.debug.log", false) ? "all" : "warn"
19
});
20
21
this.EXPORTED_SYMBOLS = ["cal"];
22
var cal = {
23
// These functions exist to reduce boilerplate code for creating instances
24
// as well as getting services and other (cached) objects.
25
createEvent: _instance("@mozilla.org/calendar/event;1",
26
Ci.calIEvent,
27
"icalString"),
28
createTodo: _instance("@mozilla.org/calendar/todo;1",
29
Ci.calITodo,
30
"icalString"),
31
createDateTime: _instance("@mozilla.org/calendar/datetime;1",
32
Ci.calIDateTime,
33
"icalString"),
34
createDuration: _instance("@mozilla.org/calendar/duration;1",
35
Ci.calIDuration,
36
"icalString"),
37
createAttendee: _instance("@mozilla.org/calendar/attendee;1",
38
Ci.calIAttendee,
39
"icalString"),
40
createAttachment: _instance("@mozilla.org/calendar/attachment;1",
41
Ci.calIAttachment,
42
"icalString"),
43
createAlarm: _instance("@mozilla.org/calendar/alarm;1",
44
Ci.calIAlarm,
45
"icalString"),
46
createRelation: _instance("@mozilla.org/calendar/relation;1",
47
Ci.calIRelation,
48
"icalString"),
49
createRecurrenceDate: _instance("@mozilla.org/calendar/recurrence-date;1",
50
Ci.calIRecurrenceDate,
51
"icalString"),
52
createRecurrenceRule: _instance("@mozilla.org/calendar/recurrence-rule;1",
53
Ci.calIRecurrenceRule,
54
"icalString"),
55
createRecurrenceInfo: _instance("@mozilla.org/calendar/recurrence-info;1",
56
Ci.calIRecurrenceInfo,
57
"item"),
58
59
getCalendarManager: _service("@mozilla.org/calendar/manager;1",
60
Ci.calICalendarManager),
61
getIcsService: _service("@mozilla.org/calendar/ics-service;1",
62
Ci.calIICSService),
63
getTimezoneService: _service("@mozilla.org/calendar/timezone-service;1",
64
Ci.calITimezoneService),
65
getCalendarSearchService: _service("@mozilla.org/calendar/calendarsearch-service;1",
66
Ci.calICalendarSearchProvider),
67
getFreeBusyService: _service("@mozilla.org/calendar/freebusy-service;1",
68
Ci.calIFreeBusyService),
69
getWeekInfoService: _service("@mozilla.org/calendar/weekinfo-service;1",
70
Ci.calIWeekInfoService),
71
getDateFormatter: _service("@mozilla.org/calendar/datetime-formatter;1",
72
Ci.calIDateTimeFormatter),
73
getDragService: _service("@mozilla.org/widget/dragservice;1",
74
Ci.nsIDragService),
75
76
/**
77
* The calendar console instance
78
*/
79
console: gCalendarConsole,
80
81
/**
82
* Logs a calendar message to the console. Needs calendar.debug.log enabled to show messages.
83
* Shortcut to cal.console.log()
84
*/
85
LOG: gCalendarConsole.log,
86
LOGverbose: gCalendarConsole.debug,
87
88
/**
89
* Logs a calendar warning to the console. Shortcut to cal.console.warn()
90
*/
91
WARN: gCalendarConsole.warn,
92
93
/**
94
* Logs a calendar error to the console. Shortcut to cal.console.error()
95
*/
96
ERROR: gCalendarConsole.error,
97
98
/**
99
* Uses the prompt service to display an error message. Use this sparingly,
100
* as it interrupts the user.
101
*
102
* @param aMsg The message to be shown
103
* @param aWindow The window to show the message in, or null for any window.
104
*/
105
showError: function(aMsg, aWindow=null) {
106
Services.prompt.alert(aWindow, cal.l10n.getCalString("genericErrorTitle"), aMsg);
107
},
108
109
/**
110
* Returns a string describing the current js-stack with filename and line
111
* numbers.
112
*
113
* @param aDepth (optional) The number of frames to include. Defaults to 5.
114
* @param aSkip (optional) Number of frames to skip
115
*/
116
STACK: function(aDepth=10, aSkip=0) {
117
let stack = "";
118
let frame = Components.stack.caller;
119
for (let i = 1; i <= aDepth + aSkip && frame; i++) {
120
if (i > aSkip) {
121
stack += `${i}: [${frame.filename}:${frame.lineNumber}] ${frame.name}\n`;
122
}
123
frame = frame.caller;
124
}
125
return stack;
126
},
127
128
/**
129
* Logs a message and the current js-stack, if aCondition fails
130
*
131
* @param aCondition the condition to test for
132
* @param aMessage the message to report in the case the assert fails
133
* @param aCritical if true, throw an error to stop current code execution
134
* if false, code flow will continue
135
* may be a result code
136
*/
137
ASSERT: function(aCondition, aMessage, aCritical=false) {
138
if (aCondition) {
139
return;
140
}
141
142
let string = `Assert failed: ${aMessage}\n ${cal.STACK(0, 1)}`;
143
if (aCritical) {
144
let rescode = aCritical === true ? Cr.NS_ERROR_UNEXPECTED : aCritical;
145
throw new Components.Exception(string, rescode);
146
} else {
147
Cu.reportError(string);
148
}
149
},
150
151
/**
152
* Generates a QueryInterface method on the given global. To be used as follows:
153
*
154
* class calThing {
155
* QueryInterface(aIID) { return cal.generateClassQI(this, aIID, [Ci.calIThing]); }
156
*
157
* ...
158
* }
159
*
160
* The function is cached, once this is called QueryInterface is replaced with
161
* cal.generateQI()'s result.
162
*
163
* @param {Object} aGlobal The object to define the method on
164
* @param {nsIIDRef} aIID The IID to query for
165
* @param {nsIIDRef[]} aInterfaces The interfaces that this object implements
166
* @return {nsQIResult} The object queried for aIID
167
*/
168
generateClassQI: function(aGlobal, aIID, aInterfaces) {
169
const generatedQI = aInterfaces.length > 1
170
? cal.generateQI(aInterfaces)
171
: ChromeUtils.generateQI(aInterfaces);
172
Object.defineProperty(aGlobal, "QueryInterface", { value: generatedQI });
173
return aGlobal.QueryInterface(aIID);
174
},
175
176
177
/**
178
* Generates the QueryInterface function. This is a replacement for XPCOMUtils.generateQI, which
179
* is being replaced. Unfortunately Lightning's code depends on some of its classes providing
180
* nsIClassInfo, which causes xpconnect/xpcom to make all methods available, e.g. for an event
181
* both calIItemBase and calIEvent.
182
*
183
* @param {Array<String|nsIIDRef>} aInterfaces The interfaces to generate QI for.
184
* @return {Function} The QueryInterface function
185
*/
186
generateQI: function(aInterfaces) {
187
if (aInterfaces.length == 1) {
188
cal.WARN("When generating QI for one interface, please use ChromeUtils.generateQI", cal.STACK(10));
189
return ChromeUtils.generateQI(aInterfaces);
190
} else {
191
/* Note that Ci[Ci.x] == Ci.x for all x */
192
let names = [];
193
if (aInterfaces) {
194
for (let i = 0; i < aInterfaces.length; i++) {
195
let iface = aInterfaces[i];
196
let name = (iface && iface.name) || String(iface);
197
if (name in Ci) {
198
names.push(name);
199
}
200
}
201
}
202
return makeQI(names);
203
}
204
},
205
206
/**
207
* Generate a ClassInfo implementation for a component. The returned object
208
* must be assigned to the 'classInfo' property of a JS object. The first and
209
* only argument should be an object that contains a number of optional
210
* properties: "interfaces", "contractID", "classDescription", "classID" and
211
* "flags". The values of the properties will be returned as the values of the
212
* various properties of the nsIClassInfo implementation.
213
*/
214
generateCI: function(classInfo) {
215
if ("QueryInterface" in classInfo) {
216
throw Error("In generateCI, don't use a component for generating classInfo");
217
}
218
/* Note that Ci[Ci.x] == Ci.x for all x */
219
let _interfaces = [];
220
for (let i = 0; i < classInfo.interfaces.length; i++) {
221
let iface = classInfo.interfaces[i];
222
if (Ci[iface]) {
223
_interfaces.push(Ci[iface]);
224
}
225
}
226
return {
227
get interfaces() {
228
return [Ci.nsIClassInfo, Ci.nsISupports].concat(_interfaces);
229
},
230
getScriptableHelper: function() {
231
return null;
232
},
233
contractID: classInfo.contractID,
234
classDescription: classInfo.classDescription,
235
classID: classInfo.classID,
236
flags: classInfo.flags,
237
QueryInterface: ChromeUtils.generateQI([Ci.nsIClassInfo])
238
};
239
},
240
241
/**
242
* Schedules execution of the passed function to the current thread's queue.
243
*/
244
postPone: function(func) {
245
if (this.threadingEnabled) {
246
Services.tm.currentThread.dispatch({ run: func },
247
Ci.nsIEventTarget.DISPATCH_NORMAL);
248
} else {
249
func();
250
}
251
},
252
253
/**
254
* Create an adapter for the given interface. If passed, methods will be
255
* added to the template object, otherwise a new object will be returned.
256
*
257
* @param iface The interface to adapt, either using
258
* Components.interfaces or the name as a string.
259
* @param template (optional) A template object to extend
260
* @return If passed the adapted template object, otherwise a
261
* clean adapter.
262
*
263
* Currently supported interfaces are:
264
* - calIObserver
265
* - calICalendarManagerObserver
266
* - calIOperationListener
267
* - calICompositeObserver
268
*/
269
createAdapter: function(iface, template) {
270
let methods;
271
let adapter = template || {};
272
switch (iface.name || iface) {
273
case "calIObserver":
274
methods = [
275
"onStartBatch", "onEndBatch", "onLoad", "onAddItem",
276
"onModifyItem", "onDeleteItem", "onError",
277
"onPropertyChanged", "onPropertyDeleting"
278
];
279
break;
280
case "calICalendarManagerObserver":
281
methods = [
282
"onCalendarRegistered", "onCalendarUnregistering",
283
"onCalendarDeleting"
284
];
285
break;
286
case "calIOperationListener":
287
methods = ["onGetResult", "onOperationComplete"];
288
break;
289
case "calICompositeObserver":
290
methods = [
291
"onCalendarAdded", "onCalendarRemoved",
292
"onDefaultCalendarChanged"
293
];
294
break;
295
default:
296
methods = [];
297
break;
298
}
299
300
for (let method of methods) {
301
if (!(method in template)) {
302
adapter[method] = function() {};
303
}
304
}
305
adapter.QueryInterface = ChromeUtils.generateQI([iface]);
306
307
return adapter;
308
},
309
310
/**
311
* Make a UUID, without enclosing brackets, e.g. 0d3950fd-22e5-4508-91ba-0489bdac513f
312
*
313
* @return {String} The generated UUID
314
*/
315
getUUID: function() {
316
let uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
317
// generate uuids without braces to avoid problems with
318
// CalDAV servers that don't support filenames with {}
319
return uuidGen.generateUUID().toString().replace(/[{}]/g, "");
320
},
321
322
/**
323
* Adds an observer listening for the topic.
324
*
325
* @param func function to execute on topic
326
* @param topic topic to listen for
327
* @param oneTime whether to listen only once
328
*/
329
addObserver: function(func, topic, oneTime) {
330
let observer = { // nsIObserver:
331
observe: function(subject, topic_, data) {
332
if (topic == topic_) {
333
if (oneTime) {
334
Services.obs.removeObserver(this, topic);
335
}
336
func(subject, topic, data);
337
}
338
}
339
};
340
Services.obs.addObserver(observer, topic);
341
},
342
343
/**
344
* Wraps an instance, making sure the xpcom wrapped object is used.
345
*
346
* @param aObj the object under consideration
347
* @param aInterface the interface to be wrapped
348
*
349
* Use this function to QueryInterface the object to a particular interface.
350
* You may only expect the return value to be wrapped, not the original passed object.
351
* For example:
352
* // BAD USAGE:
353
* if (cal.wrapInstance(foo, Ci.nsIBar)) {
354
* foo.barMethod();
355
* }
356
* // GOOD USAGE:
357
* foo = cal.wrapInstance(foo, Ci.nsIBar);
358
* if (foo) {
359
* foo.barMethod();
360
* }
361
*
362
*/
363
wrapInstance: function(aObj, aInterface) {
364
if (!aObj) {
365
return null;
366
}
367
368
try {
369
return aObj.QueryInterface(aInterface);
370
} catch (e) {
371
return null;
372
}
373
},
374
375
/**
376
* Tries to get rid of wrappers, if this is not possible then return the
377
* passed object.
378
*
379
* @param aObj The object under consideration
380
* @return The possibly unwrapped object.
381
*/
382
unwrapInstance: function(aObj) {
383
return aObj && aObj.wrappedJSObject ? aObj.wrappedJSObject : aObj;
384
},
385
386
/**
387
* Adds an xpcom shutdown observer.
388
*
389
* @param func function to execute
390
*/
391
addShutdownObserver: function(func) {
392
cal.addObserver(func, "xpcom-shutdown", true /* one time */);
393
},
394
395
/**
396
* Due to wrapped js objects, some objects may have cyclic references.
397
* You can register properties of objects to be cleaned up on xpcom-shutdown.
398
*
399
* @param obj object
400
* @param prop property to be deleted on shutdown
401
* (if null, |object| will be deleted)
402
*/
403
registerForShutdownCleanup: shutdownCleanup
404
};
405
406
/**
407
* Update the logging preferences for the calendar console based on the state of verbose logging and
408
* normal calendar logging.
409
*/
410
function updateLogPreferences() {
411
if (cal.verboseLogEnabled) {
412
gCalendarConsole.maxLogLevel = "all";
413
} else if (cal.debugLogEnabled) {
414
gCalendarConsole.maxLogLevel = "log";
415
} else {
416
gCalendarConsole.maxLogLevel = "warn";
417
}
418
}
419
420
// Preferences
421
XPCOMUtils.defineLazyPreferenceGetter(cal, "debugLogEnabled", "calendar.debug.log", false, updateLogPreferences);
422
XPCOMUtils.defineLazyPreferenceGetter(cal, "verboseLogEnabled", "calendar.debug.log.verbose", false, updateLogPreferences);
423
XPCOMUtils.defineLazyPreferenceGetter(cal, "threadingEnabled", "calendar.threading.disabled", false);
424
425
// Sub-modules for calUtils
426
XPCOMUtils.defineLazyModuleGetter(cal, "acl", "resource://calendar/modules/utils/calACLUtils.jsm", "calacl");
427
XPCOMUtils.defineLazyModuleGetter(cal, "alarms", "resource://calendar/modules/utils/calAlarmUtils.jsm", "calalarms");
428
XPCOMUtils.defineLazyModuleGetter(cal, "async", "resource://calendar/modules/utils/calAsyncUtils.jsm", "calasync");
429
XPCOMUtils.defineLazyModuleGetter(cal, "auth", "resource://calendar/modules/utils/calAuthUtils.jsm", "calauth");
430
XPCOMUtils.defineLazyModuleGetter(cal, "category", "resource://calendar/modules/utils/calCategoryUtils.jsm", "calcategory");
431
XPCOMUtils.defineLazyModuleGetter(cal, "data", "resource://calendar/modules/utils/calDataUtils.jsm", "caldata");
432
XPCOMUtils.defineLazyModuleGetter(cal, "dtz", "resource://calendar/modules/utils/calDateTimeUtils.jsm", "caldtz");
433
XPCOMUtils.defineLazyModuleGetter(cal, "email", "resource://calendar/modules/utils/calEmailUtils.jsm", "calemail");
434
XPCOMUtils.defineLazyModuleGetter(cal, "item", "resource://calendar/modules/utils/calItemUtils.jsm", "calitem");
435
XPCOMUtils.defineLazyModuleGetter(cal, "iterate", "resource://calendar/modules/utils/calIteratorUtils.jsm", "caliterate");
436
XPCOMUtils.defineLazyModuleGetter(cal, "itip", "resource://calendar/modules/utils/calItipUtils.jsm", "calitip");
437
XPCOMUtils.defineLazyModuleGetter(cal, "l10n", "resource://calendar/modules/utils/calL10NUtils.jsm", "call10n");
438
XPCOMUtils.defineLazyModuleGetter(cal, "print", "resource://calendar/modules/utils/calPrintUtils.jsm", "calprint");
439
XPCOMUtils.defineLazyModuleGetter(cal, "provider", "resource://calendar/modules/utils/calProviderUtils.jsm", "calprovider");
440
XPCOMUtils.defineLazyModuleGetter(cal, "unifinder", "resource://calendar/modules/utils/calUnifinderUtils.jsm", "calunifinder");
441
XPCOMUtils.defineLazyModuleGetter(cal, "view", "resource://calendar/modules/utils/calViewUtils.jsm", "calview");
442
XPCOMUtils.defineLazyModuleGetter(cal, "window", "resource://calendar/modules/utils/calWindowUtils.jsm", "calwindow");
443
XPCOMUtils.defineLazyModuleGetter(cal, "xml", "resource://calendar/modules/utils/calXMLUtils.jsm", "calxml");
444
445
/**
446
* Returns a function that provides access to the given service.
447
*
448
* @param cid The contract id to create
449
* @param iid The interface id to create with
450
* @return {function} A function that returns the given service
451
*/
452
function _service(cid, iid) {
453
return function() {
454
return Cc[cid].getService(iid);
455
};
456
}
457
458
/**
459
* Returns a function that creates an instance of the given component and
460
* optionally initializes it using the property name passed.
461
*
462
* @param cid The contract id to create
463
* @param iid The interface id to create with
464
* @param prop The property name used for initialization
465
* @return {function} A function that creates the given instance, which takes an
466
* initialization value.
467
*/
468
function _instance(cid, iid, prop) {
469
return function(propval) {
470
let thing = Cc[cid].createInstance(iid);
471
if (propval) {
472
thing[prop] = propval;
473
}
474
return thing;
475
};
476
}
477
478
// will be used to clean up global objects on shutdown
479
// some objects have cyclic references due to wrappers
480
function shutdownCleanup(obj, prop) {
481
if (!shutdownCleanup.mEntries) {
482
shutdownCleanup.mEntries = [];
483
cal.addShutdownObserver(() => {
484
for (let entry of shutdownCleanup.mEntries) {
485
if (entry.mProp) {
486
delete entry.mObj[entry.mProp];
487
} else {
488
delete entry.mObj;
489
}
490
}
491
delete shutdownCleanup.mEntries;
492
});
493
}
494
shutdownCleanup.mEntries.push({ mObj: obj, mProp: prop });
495
}
496
497
/**
498
* This is the makeQI function from XPCOMUtils.jsm, it is separate to avoid leaks
499
*
500
* @param {Array<String|nsIIDRef>} aInterfaces The interfaces to make QI for.
501
* @return {Function} The QueryInterface function.
502
*/
503
function makeQI(aInterfaces) {
504
return function(iid) {
505
if (iid.equals(Ci.nsISupports)) {
506
return this;
507
}
508
if (iid.equals(Ci.nsIClassInfo) && "classInfo" in this) {
509
return this.classInfo;
510
}
511
for (let i = 0; i < aInterfaces.length; i++) {
512
if (Ci[aInterfaces[i]].equals(iid)) {
513
return this;
514
}
515
}
516
517
throw Cr.NS_ERROR_NO_INTERFACE;
518
};
519
}
520
521
// Backwards compatibility for bug 905097. Please remove with Thunderbird 61.
522
var { injectCalUtilsCompat } = ChromeUtils.import("resource://calendar/modules/calUtilsCompat.jsm");
523
injectCalUtilsCompat(cal);