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