1/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- 2 * vim: sw=2 ts=2 sts=2 et filetype=javascript 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7/** 8 * Utilities for JavaScript components loaded by the JS component 9 * loader. 10 * 11 * Import into a JS component using 12 * 'Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");' 13 * 14 * Exposing a JS 'class' as a component using these utility methods consists 15 * of several steps: 16 * 0. Import XPCOMUtils, as described above. 17 * 1. Declare the 'class' (or multiple classes) implementing the component(s): 18 * function MyComponent() { 19 * // constructor 20 * } 21 * MyComponent.prototype = { 22 * // properties required for XPCOM registration: 23 * classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"), 24 * 25 * // [optional] custom factory (an object implementing nsIFactory). If not 26 * // provided, the default factory is used, which returns 27 * // |(new MyComponent()).QueryInterface(iid)| in its createInstance(). 28 * _xpcom_factory: { ... }, 29 * 30 * // QueryInterface implementation, e.g. using the generateQI helper 31 * QueryInterface: XPCOMUtils.generateQI( 32 * [Components.interfaces.nsIObserver, 33 * Components.interfaces.nsIMyInterface, 34 * "nsIFoo", 35 * "nsIBar" ]), 36 * 37 * // [optional] classInfo implementation, e.g. using the generateCI helper. 38 * // Will be automatically returned from QueryInterface if that was 39 * // generated with the generateQI helper. 40 * classInfo: XPCOMUtils.generateCI( 41 * {classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"), 42 * contractID: "@example.com/xxx;1", 43 * classDescription: "unique text description", 44 * interfaces: [Components.interfaces.nsIObserver, 45 * Components.interfaces.nsIMyInterface, 46 * "nsIFoo", 47 * "nsIBar"], 48 * flags: Ci.nsIClassInfo.SINGLETON}), 49 * 50 * // The following properties were used prior to Mozilla 2, but are no 51 * // longer supported. They may still be included for compatibility with 52 * // prior versions of XPCOMUtils. In Mozilla 2, this information is 53 * // included in the .manifest file which registers this JS component. 54 * classDescription: "unique text description", 55 * contractID: "@example.com/xxx;1", 56 * 57 * // [optional] an array of categories to register this component in. 58 * _xpcom_categories: [{ 59 * // Each object in the array specifies the parameters to pass to 60 * // nsICategoryManager.addCategoryEntry(). 'true' is passed for 61 * // both aPersist and aReplace params. 62 * category: "some-category", 63 * // optional, defaults to the object's classDescription 64 * entry: "entry name", 65 * // optional, defaults to the object's contractID (unless 66 * // 'service' is specified) 67 * value: "...", 68 * // optional, defaults to false. When set to true, and only if 'value' 69 * // is not specified, the concatenation of the string "service," and the 70 * // object's contractID is passed as aValue parameter of addCategoryEntry. 71 * service: true, 72 * // optional, it can be an array of applications' IDs in the form: 73 * // [ "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}", ... ] 74 * // If defined the component will be registered in this category only for 75 * // the provided applications. 76 * apps: [...] 77 * }], 78 * 79 * // ...component implementation... 80 * }; 81 * 82 * 2. Create an array of component constructors (like the one 83 * created in step 1): 84 * var components = [MyComponent]; 85 * 86 * 3. Define the NSGetFactory entry point: 87 * this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components); 88 */ 89 90 91this.EXPORTED_SYMBOLS = [ "XPCOMUtils" ]; 92 93const Cc = Components.classes; 94const Ci = Components.interfaces; 95const Cr = Components.results; 96const Cu = Components.utils; 97 98this.XPCOMUtils = { 99 /** 100 * Generate a QueryInterface implementation. The returned function must be 101 * assigned to the 'QueryInterface' property of a JS object. When invoked on 102 * that object, it checks if the given iid is listed in the |interfaces| 103 * param, and if it is, returns |this| (the object it was called on). 104 * If the JS object has a classInfo property it'll be returned for the 105 * nsIClassInfo IID, generateCI can be used to generate the classInfo 106 * property. 107 */ 108 generateQI: function XPCU_generateQI(interfaces) { 109 /* Note that Ci[Ci.x] == Ci.x for all x */ 110 let a = []; 111 if (interfaces) { 112 for (let i = 0; i < interfaces.length; i++) { 113 let iface = interfaces[i]; 114 if (Ci[iface]) { 115 a.push(Ci[iface].name); 116 } 117 } 118 } 119 return makeQI(a); 120 }, 121 122 /** 123 * Generate a ClassInfo implementation for a component. The returned object 124 * must be assigned to the 'classInfo' property of a JS object. The first and 125 * only argument should be an object that contains a number of optional 126 * properties: "interfaces", "contractID", "classDescription", "classID" and 127 * "flags". The values of the properties will be returned as the values of the 128 * various properties of the nsIClassInfo implementation. 129 */ 130 generateCI: function XPCU_generateCI(classInfo) 131 { 132 if (QueryInterface in classInfo) 133 throw Error("In generateCI, don't use a component for generating classInfo"); 134 /* Note that Ci[Ci.x] == Ci.x for all x */ 135 let _interfaces = []; 136 for (let i = 0; i < classInfo.interfaces.length; i++) { 137 let iface = classInfo.interfaces[i]; 138 if (Ci[iface]) { 139 _interfaces.push(Ci[iface]); 140 } 141 } 142 return { 143 getInterfaces: function XPCU_getInterfaces(countRef) { 144 countRef.value = _interfaces.length; 145 return _interfaces; 146 }, 147 getScriptableHelper: function XPCU_getScriptableHelper() { 148 return null; 149 }, 150 contractID: classInfo.contractID, 151 classDescription: classInfo.classDescription, 152 classID: classInfo.classID, 153 flags: classInfo.flags, 154 QueryInterface: this.generateQI([Ci.nsIClassInfo]) 155 }; 156 }, 157 158 /** 159 * Generate a NSGetFactory function given an array of components. 160 */ 161 generateNSGetFactory: function XPCU_generateNSGetFactory(componentsArray) { 162 let classes = {}; 163 for (let i = 0; i < componentsArray.length; i++) { 164 let component = componentsArray[i]; 165 if (!(component.prototype.classID instanceof Components.ID)) 166 throw Error("In generateNSGetFactory, classID missing or incorrect for component " + component); 167 168 classes[component.prototype.classID] = this._getFactory(component); 169 } 170 return function NSGetFactory(cid) { 171 let cidstring = cid.toString(); 172 if (cidstring in classes) 173 return classes[cidstring]; 174 throw Cr.NS_ERROR_FACTORY_NOT_REGISTERED; 175 } 176 }, 177 178 /** 179 * Defines a getter on a specified object that will be created upon first use. 180 * 181 * @param aObject 182 * The object to define the lazy getter on. 183 * @param aName 184 * The name of the getter to define on aObject. 185 * @param aLambda 186 * A function that returns what the getter should return. This will 187 * only ever be called once. 188 */ 189 defineLazyGetter: function XPCU_defineLazyGetter(aObject, aName, aLambda) 190 { 191 Object.defineProperty(aObject, aName, { 192 get: function () { 193 // Redefine this accessor property as a data property. 194 // Delete it first, to rule out "too much recursion" in case aObject is 195 // a proxy whose defineProperty handler might unwittingly trigger this 196 // getter again. 197 delete aObject[aName]; 198 let value = aLambda.apply(aObject); 199 Object.defineProperty(aObject, aName, { 200 value, 201 writable: true, 202 configurable: true, 203 enumerable: true 204 }); 205 return value; 206 }, 207 configurable: true, 208 enumerable: true 209 }); 210 }, 211 212 /** 213 * Defines a getter on a specified object for a service. The service will not 214 * be obtained until first use. 215 * 216 * @param aObject 217 * The object to define the lazy getter on. 218 * @param aName 219 * The name of the getter to define on aObject for the service. 220 * @param aContract 221 * The contract used to obtain the service. 222 * @param aInterfaceName 223 * The name of the interface to query the service to. 224 */ 225 defineLazyServiceGetter: function XPCU_defineLazyServiceGetter(aObject, aName, 226 aContract, 227 aInterfaceName) 228 { 229 this.defineLazyGetter(aObject, aName, function XPCU_serviceLambda() { 230 return Cc[aContract].getService(Ci[aInterfaceName]); 231 }); 232 }, 233 234 /** 235 * Defines a getter on a specified object for a module. The module will not 236 * be imported until first use. The getter allows to execute setup and 237 * teardown code (e.g. to register/unregister to services) and accepts 238 * a proxy object which acts on behalf of the module until it is imported. 239 * 240 * @param aObject 241 * The object to define the lazy getter on. 242 * @param aName 243 * The name of the getter to define on aObject for the module. 244 * @param aResource 245 * The URL used to obtain the module. 246 * @param aSymbol 247 * The name of the symbol exported by the module. 248 * This parameter is optional and defaults to aName. 249 * @param aPreLambda 250 * A function that is executed when the proxy is set up. 251 * This will only ever be called once. 252 * @param aPostLambda 253 * A function that is executed when the module has been imported to 254 * run optional teardown procedures on the proxy object. 255 * This will only ever be called once. 256 * @param aProxy 257 * An object which acts on behalf of the module to be imported until 258 * the module has been imported. 259 */ 260 defineLazyModuleGetter: function XPCU_defineLazyModuleGetter( 261 aObject, aName, aResource, aSymbol, 262 aPreLambda, aPostLambda, aProxy) 263 { 264 let proxy = aProxy || {}; 265 266 if (typeof(aPreLambda) === "function") { 267 aPreLambda.apply(proxy); 268 } 269 270 this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() { 271 var temp = {}; 272 try { 273 Cu.import(aResource, temp); 274 275 if (typeof(aPostLambda) === "function") { 276 aPostLambda.apply(proxy); 277 } 278 } catch (ex) { 279 Cu.reportError("Failed to load module " + aResource + "."); 280 throw ex; 281 } 282 return temp[aSymbol || aName]; 283 }); 284 }, 285 286 /** 287 * Defines a getter on a specified object for preference value. The 288 * preference is read the first time that the property is accessed, 289 * and is thereafter kept up-to-date using a preference observer. 290 * 291 * @param aObject 292 * The object to define the lazy getter on. 293 * @param aName 294 * The name of the getter property to define on aObject. 295 * @param aPreference 296 * The name of the preference to read. 297 * @param aDefaultValue 298 * The default value to use, if the preference is not defined. 299 */ 300 defineLazyPreferenceGetter: function XPCU_defineLazyPreferenceGetter( 301 aObject, aName, aPreference, aDefaultValue = null) 302 { 303 // Note: We need to keep a reference to this observer alive as long 304 // as aObject is alive. This means that all of our getters need to 305 // explicitly close over the variable that holds the object, and we 306 // cannot define a value in place of a getter after we read the 307 // preference. 308 let observer = { 309 QueryInterface: this.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 310 311 value: undefined, 312 313 observe(subject, topic, data) { 314 if (data == aPreference) { 315 this.value = undefined; 316 } 317 }, 318 } 319 320 let defineGetter = get => { 321 Object.defineProperty(aObject, aName, { 322 configurable: true, 323 enumerable: true, 324 get, 325 }); 326 }; 327 328 function lazyGetter() { 329 if (observer.value === undefined) { 330 observer.value = Preferences.get(aPreference, aDefaultValue); 331 } 332 return observer.value; 333 } 334 335 defineGetter(() => { 336 Services.prefs.addObserver(aPreference, observer, true); 337 338 defineGetter(lazyGetter); 339 return lazyGetter(); 340 }); 341 }, 342 343 /** 344 * Helper which iterates over a nsISimpleEnumerator. 345 * @param e The nsISimpleEnumerator to iterate over. 346 * @param i The expected interface for each element. 347 */ 348 IterSimpleEnumerator: function* XPCU_IterSimpleEnumerator(e, i) 349 { 350 while (e.hasMoreElements()) 351 yield e.getNext().QueryInterface(i); 352 }, 353 354 /** 355 * Helper which iterates over a string enumerator. 356 * @param e The string enumerator (nsIUTF8StringEnumerator or 357 * nsIStringEnumerator) over which to iterate. 358 */ 359 IterStringEnumerator: function* XPCU_IterStringEnumerator(e) 360 { 361 while (e.hasMore()) 362 yield e.getNext(); 363 }, 364 365 /** 366 * Helper which iterates over the entries in a category. 367 * @param aCategory The name of the category over which to iterate. 368 */ 369 enumerateCategoryEntries: function* XPCOMUtils_enumerateCategoryEntries(aCategory) 370 { 371 let category = this.categoryManager.enumerateCategory(aCategory); 372 for (let entry of this.IterSimpleEnumerator(category, Ci.nsISupportsCString)) { 373 yield [entry.data, this.categoryManager.getCategoryEntry(aCategory, entry.data)]; 374 } 375 }, 376 377 /** 378 * Returns an nsIFactory for |component|. 379 */ 380 _getFactory: function XPCOMUtils__getFactory(component) { 381 var factory = component.prototype._xpcom_factory; 382 if (!factory) { 383 factory = { 384 createInstance: function(outer, iid) { 385 if (outer) 386 throw Cr.NS_ERROR_NO_AGGREGATION; 387 return (new component()).QueryInterface(iid); 388 }, 389 QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) 390 } 391 } 392 return factory; 393 }, 394 395 /** 396 * Allows you to fake a relative import. Expects the global object from the 397 * module that's calling us, and the relative filename that we wish to import. 398 */ 399 importRelative: function XPCOMUtils__importRelative(that, path, scope) { 400 if (!("__URI__" in that)) 401 throw Error("importRelative may only be used from a JSM, and its first argument "+ 402 "must be that JSM's global object (hint: use this)"); 403 let uri = that.__URI__; 404 let i = uri.lastIndexOf("/"); 405 Components.utils.import(uri.substring(0, i+1) + path, scope || that); 406 }, 407 408 /** 409 * generates a singleton nsIFactory implementation that can be used as 410 * the _xpcom_factory of the component. 411 * @param aServiceConstructor 412 * Constructor function of the component. 413 */ 414 generateSingletonFactory: 415 function XPCOMUtils_generateSingletonFactory(aServiceConstructor) { 416 return { 417 _instance: null, 418 createInstance: function XPCU_SF_createInstance(aOuter, aIID) { 419 if (aOuter !== null) { 420 throw Cr.NS_ERROR_NO_AGGREGATION; 421 } 422 if (this._instance === null) { 423 this._instance = new aServiceConstructor(); 424 } 425 return this._instance.QueryInterface(aIID); 426 }, 427 lockFactory: function XPCU_SF_lockFactory(aDoLock) { 428 throw Cr.NS_ERROR_NOT_IMPLEMENTED; 429 }, 430 QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) 431 }; 432 }, 433 434 /** 435 * Defines a non-writable property on an object. 436 */ 437 defineConstant: function XPCOMUtils__defineConstant(aObj, aName, aValue) { 438 Object.defineProperty(aObj, aName, { 439 value: aValue, 440 enumerable: true, 441 writable: false 442 }); 443 }, 444}; 445 446XPCOMUtils.defineLazyModuleGetter(this, "Preferences", 447 "resource://gre/modules/Preferences.jsm"); 448XPCOMUtils.defineLazyModuleGetter(this, "Services", 449 "resource://gre/modules/Services.jsm"); 450 451XPCOMUtils.defineLazyServiceGetter(XPCOMUtils, "categoryManager", 452 "@mozilla.org/categorymanager;1", 453 "nsICategoryManager"); 454 455/** 456 * Helper for XPCOMUtils.generateQI to avoid leaks - see bug 381651#c1 457 */ 458function makeQI(interfaceNames) { 459 return function XPCOMUtils_QueryInterface(iid) { 460 if (iid.equals(Ci.nsISupports)) 461 return this; 462 if (iid.equals(Ci.nsIClassInfo) && "classInfo" in this) 463 return this.classInfo; 464 for (let i = 0; i < interfaceNames.length; i++) { 465 if (Ci[interfaceNames[i]].equals(iid)) 466 return this; 467 } 468 469 throw Cr.NS_ERROR_NO_INTERFACE; 470 }; 471} 472