xpcom.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. "use strict";
  5. module.metadata = {
  6. "stability": "unstable"
  7. };
  8. const { Cc, Ci, Cr, Cm, components: { classesByID } } = require('chrome');
  9. const { registerFactory, unregisterFactory, isCIDRegistered } =
  10. Cm.QueryInterface(Ci.nsIComponentRegistrar);
  11. const { merge } = require('../util/object');
  12. const { Class, extend, mix } = require('../core/heritage');
  13. const { uuid } = require('../util/uuid');
  14. // This is a base prototype, that provides bare bones of XPCOM. JS based
  15. // components can be easily implement by extending it.
  16. const Unknown = new function() {
  17. function hasInterface(component, iid) {
  18. return component && component.interfaces &&
  19. ( component.interfaces.some(function(id) iid.equals(Ci[id])) ||
  20. component.implements.some(function($) hasInterface($, iid)) ||
  21. hasInterface(Object.getPrototypeOf(component), iid));
  22. }
  23. return Class({
  24. /**
  25. * The `QueryInterface` method provides runtime type discovery used by XPCOM.
  26. * This method return queried instance of `this` if given `iid` is listed in
  27. * the `interfaces` property or in equivalent properties of objects in it's
  28. * prototype chain. In addition it will look up in the prototypes under
  29. * `implements` array property, this ways compositions made via `Class`
  30. * utility will carry interfaces implemented by composition components.
  31. */
  32. QueryInterface: function QueryInterface(iid) {
  33. // For some reason there are cases when `iid` is `null`. In such cases we
  34. // just return `this`. Otherwise we verify that component implements given
  35. // `iid` interface. This will be no longer necessary once Bug 748003 is
  36. // fixed.
  37. if (iid && !hasInterface(this, iid))
  38. throw Cr.NS_ERROR_NO_INTERFACE;
  39. return this;
  40. },
  41. /**
  42. * Array of `XPCOM` interfaces (as strings) implemented by this component.
  43. * All components implement `nsISupports` by default which is default value
  44. * here. Provide array of interfaces implemented by an object when
  45. * extending, to append them to this list (Please note that there is no
  46. * need to repeat interfaces implemented by super as they will be added
  47. * automatically).
  48. */
  49. interfaces: Object.freeze([ 'nsISupports' ])
  50. });
  51. }
  52. exports.Unknown = Unknown;
  53. // Base exemplar for creating instances implementing `nsIFactory` interface,
  54. // that maybe registered into runtime via `register` function. Instances of
  55. // this factory create instances of enclosed component on `createInstance`.
  56. const Factory = Class({
  57. extends: Unknown,
  58. interfaces: [ 'nsIFactory' ],
  59. /**
  60. * All the descendants will get auto generated `id` (also known as `classID`
  61. * in XPCOM world) unless one is manually provided.
  62. */
  63. get id() { throw Error('Factory must implement `id` property') },
  64. /**
  65. * XPCOM `contractID` may optionally be provided to associate this factory
  66. * with it. `contract` is a unique string that has a following format:
  67. * '@vendor.com/unique/id;1'.
  68. */
  69. contract: null,
  70. /**
  71. * Class description that is being registered. This value is intended as a
  72. * human-readable description for the given class and does not needs to be
  73. * globally unique.
  74. */
  75. description: 'Jetpack generated factory',
  76. /**
  77. * This method is required by `nsIFactory` interfaces, but as in most
  78. * implementations it does nothing interesting.
  79. */
  80. lockFactory: function lockFactory(lock) undefined,
  81. /**
  82. * If property is `true` XPCOM service / factory will be registered
  83. * automatically on creation.
  84. */
  85. register: true,
  86. /**
  87. * If property is `true` XPCOM factory will be unregistered prior to add-on
  88. * unload.
  89. */
  90. unregister: true,
  91. /**
  92. * Method is called on `Service.new(options)` passing given `options` to
  93. * it. Options is expected to have `component` property holding XPCOM
  94. * component implementation typically decedent of `Unknown` or any custom
  95. * implementation with a `new` method and optional `register`, `unregister`
  96. * flags. Unless `register` is `false` Service / Factory will be
  97. * automatically registered. Unless `unregister` is `false` component will
  98. * be automatically unregistered on add-on unload.
  99. */
  100. initialize: function initialize(options) {
  101. merge(this, {
  102. id: 'id' in options ? options.id : uuid(),
  103. register: 'register' in options ? options.register : this.register,
  104. unregister: 'unregister' in options ? options.unregister : this.unregister,
  105. contract: 'contract' in options ? options.contract : null,
  106. Component: options.Component
  107. });
  108. // If service / factory has auto registration enabled then register.
  109. if (this.register)
  110. register(this);
  111. },
  112. /**
  113. * Creates an instance of the class associated with this factory.
  114. */
  115. createInstance: function createInstance(outer, iid) {
  116. try {
  117. if (outer)
  118. throw Cr.NS_ERROR_NO_AGGREGATION;
  119. return this.create().QueryInterface(iid);
  120. }
  121. catch (error) {
  122. throw error instanceof Ci.nsIException ? error : Cr.NS_ERROR_FAILURE;
  123. }
  124. },
  125. create: function create() this.Component()
  126. });
  127. exports.Factory = Factory;
  128. // Exemplar for creating services that implement `nsIFactory` interface, that
  129. // can be registered into runtime via call to `register`. This services return
  130. // enclosed `component` on `getService`.
  131. const Service = Class({
  132. extends: Factory,
  133. initialize: function initialize(options) {
  134. this.component = options.Component();
  135. Factory.prototype.initialize.call(this, options);
  136. },
  137. description: 'Jetpack generated service',
  138. /**
  139. * Creates an instance of the class associated with this factory.
  140. */
  141. create: function create() this.component
  142. });
  143. exports.Service = Service;
  144. function isRegistered({ id }) isCIDRegistered(id)
  145. exports.isRegistered = isRegistered;
  146. /**
  147. * Registers given `component` object to be used to instantiate a particular
  148. * class identified by `component.id`, and creates an association of class
  149. * name and `component.contract` with the class.
  150. */
  151. function register(factory) {
  152. if (!(factory instanceof Factory)) {
  153. throw new Error("xpcom.register() expect a Factory instance.\n" +
  154. "Please refactor your code to new xpcom module if you" +
  155. " are repacking an addon from SDK <= 1.5:\n" +
  156. "https://addons.mozilla.org/en-US/developers/docs/sdk/latest/packages/api-utils/xpcom.html");
  157. }
  158. registerFactory(factory.id, factory.description, factory.contract, factory);
  159. if (factory.unregister)
  160. require('../system/unload').when(unregister.bind(null, factory));
  161. }
  162. exports.register = register;
  163. /**
  164. * Unregister a factory associated with a particular class identified by
  165. * `factory.classID`.
  166. */
  167. function unregister(factory) {
  168. if (isRegistered(factory))
  169. unregisterFactory(factory.id, factory);
  170. }
  171. exports.unregister = unregister;
  172. function autoRegister(path) {
  173. // TODO: This assumes that the url points to a directory
  174. // that contains subdirectories corresponding to OS/ABI and then
  175. // further subdirectories corresponding to Gecko platform version.
  176. // we should probably either behave intelligently here or allow
  177. // the caller to pass-in more options if e.g. there aren't
  178. // Gecko-specific binaries for a component (which will be the case
  179. // if only frozen interfaces are used).
  180. var runtime = require("../system/runtime");
  181. var osDirName = runtime.OS + "_" + runtime.XPCOMABI;
  182. var platformVersion = require("../system/xul-app").platformVersion.substring(0, 5);
  183. var file = Cc['@mozilla.org/file/local;1']
  184. .createInstance(Ci.nsILocalFile);
  185. file.initWithPath(path);
  186. file.append(osDirName);
  187. file.append(platformVersion);
  188. if (!(file.exists() && file.isDirectory()))
  189. throw new Error("component not available for OS/ABI " +
  190. osDirName + " and platform " + platformVersion);
  191. Cm.QueryInterface(Ci.nsIComponentRegistrar);
  192. Cm.autoRegister(file);
  193. }
  194. exports.autoRegister = autoRegister;
  195. /**
  196. * Returns registered factory that has a given `id` or `null` if not found.
  197. */
  198. function factoryByID(id) classesByID[id] || null
  199. exports.factoryByID = factoryByID;
  200. /**
  201. * Returns factory registered with a given `contract` or `null` if not found.
  202. * In contrast to `Cc[contract]` that does ignores new factory registration
  203. * with a given `contract` this will return a factory currently associated
  204. * with a `contract`.
  205. */
  206. function factoryByContract(contract) factoryByID(Cm.contractIDToCID(contract))
  207. exports.factoryByContract = factoryByContract;