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