michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "unstable" michael@0: }; michael@0: michael@0: const { Cc, Ci, Cr, Cm, components: { classesByID } } = require('chrome'); michael@0: const { registerFactory, unregisterFactory, isCIDRegistered } = michael@0: Cm.QueryInterface(Ci.nsIComponentRegistrar); michael@0: michael@0: const { merge } = require('../util/object'); michael@0: const { Class, extend, mix } = require('../core/heritage'); michael@0: const { uuid } = require('../util/uuid'); michael@0: michael@0: // This is a base prototype, that provides bare bones of XPCOM. JS based michael@0: // components can be easily implement by extending it. michael@0: const Unknown = new function() { michael@0: function hasInterface(component, iid) { michael@0: return component && component.interfaces && michael@0: ( component.interfaces.some(function(id) iid.equals(Ci[id])) || michael@0: component.implements.some(function($) hasInterface($, iid)) || michael@0: hasInterface(Object.getPrototypeOf(component), iid)); michael@0: } michael@0: michael@0: return Class({ michael@0: /** michael@0: * The `QueryInterface` method provides runtime type discovery used by XPCOM. michael@0: * This method return queried instance of `this` if given `iid` is listed in michael@0: * the `interfaces` property or in equivalent properties of objects in it's michael@0: * prototype chain. In addition it will look up in the prototypes under michael@0: * `implements` array property, this ways compositions made via `Class` michael@0: * utility will carry interfaces implemented by composition components. michael@0: */ michael@0: QueryInterface: function QueryInterface(iid) { michael@0: // For some reason there are cases when `iid` is `null`. In such cases we michael@0: // just return `this`. Otherwise we verify that component implements given michael@0: // `iid` interface. This will be no longer necessary once Bug 748003 is michael@0: // fixed. michael@0: if (iid && !hasInterface(this, iid)) michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: michael@0: return this; michael@0: }, michael@0: /** michael@0: * Array of `XPCOM` interfaces (as strings) implemented by this component. michael@0: * All components implement `nsISupports` by default which is default value michael@0: * here. Provide array of interfaces implemented by an object when michael@0: * extending, to append them to this list (Please note that there is no michael@0: * need to repeat interfaces implemented by super as they will be added michael@0: * automatically). michael@0: */ michael@0: interfaces: Object.freeze([ 'nsISupports' ]) michael@0: }); michael@0: } michael@0: exports.Unknown = Unknown; michael@0: michael@0: // Base exemplar for creating instances implementing `nsIFactory` interface, michael@0: // that maybe registered into runtime via `register` function. Instances of michael@0: // this factory create instances of enclosed component on `createInstance`. michael@0: const Factory = Class({ michael@0: extends: Unknown, michael@0: interfaces: [ 'nsIFactory' ], michael@0: /** michael@0: * All the descendants will get auto generated `id` (also known as `classID` michael@0: * in XPCOM world) unless one is manually provided. michael@0: */ michael@0: get id() { throw Error('Factory must implement `id` property') }, michael@0: /** michael@0: * XPCOM `contractID` may optionally be provided to associate this factory michael@0: * with it. `contract` is a unique string that has a following format: michael@0: * '@vendor.com/unique/id;1'. michael@0: */ michael@0: contract: null, michael@0: /** michael@0: * Class description that is being registered. This value is intended as a michael@0: * human-readable description for the given class and does not needs to be michael@0: * globally unique. michael@0: */ michael@0: description: 'Jetpack generated factory', michael@0: /** michael@0: * This method is required by `nsIFactory` interfaces, but as in most michael@0: * implementations it does nothing interesting. michael@0: */ michael@0: lockFactory: function lockFactory(lock) undefined, michael@0: /** michael@0: * If property is `true` XPCOM service / factory will be registered michael@0: * automatically on creation. michael@0: */ michael@0: register: true, michael@0: /** michael@0: * If property is `true` XPCOM factory will be unregistered prior to add-on michael@0: * unload. michael@0: */ michael@0: unregister: true, michael@0: /** michael@0: * Method is called on `Service.new(options)` passing given `options` to michael@0: * it. Options is expected to have `component` property holding XPCOM michael@0: * component implementation typically decedent of `Unknown` or any custom michael@0: * implementation with a `new` method and optional `register`, `unregister` michael@0: * flags. Unless `register` is `false` Service / Factory will be michael@0: * automatically registered. Unless `unregister` is `false` component will michael@0: * be automatically unregistered on add-on unload. michael@0: */ michael@0: initialize: function initialize(options) { michael@0: merge(this, { michael@0: id: 'id' in options ? options.id : uuid(), michael@0: register: 'register' in options ? options.register : this.register, michael@0: unregister: 'unregister' in options ? options.unregister : this.unregister, michael@0: contract: 'contract' in options ? options.contract : null, michael@0: Component: options.Component michael@0: }); michael@0: michael@0: // If service / factory has auto registration enabled then register. michael@0: if (this.register) michael@0: register(this); michael@0: }, michael@0: /** michael@0: * Creates an instance of the class associated with this factory. michael@0: */ michael@0: createInstance: function createInstance(outer, iid) { michael@0: try { michael@0: if (outer) michael@0: throw Cr.NS_ERROR_NO_AGGREGATION; michael@0: return this.create().QueryInterface(iid); michael@0: } michael@0: catch (error) { michael@0: throw error instanceof Ci.nsIException ? error : Cr.NS_ERROR_FAILURE; michael@0: } michael@0: }, michael@0: create: function create() this.Component() michael@0: }); michael@0: exports.Factory = Factory; michael@0: michael@0: // Exemplar for creating services that implement `nsIFactory` interface, that michael@0: // can be registered into runtime via call to `register`. This services return michael@0: // enclosed `component` on `getService`. michael@0: const Service = Class({ michael@0: extends: Factory, michael@0: initialize: function initialize(options) { michael@0: this.component = options.Component(); michael@0: Factory.prototype.initialize.call(this, options); michael@0: }, michael@0: description: 'Jetpack generated service', michael@0: /** michael@0: * Creates an instance of the class associated with this factory. michael@0: */ michael@0: create: function create() this.component michael@0: }); michael@0: exports.Service = Service; michael@0: michael@0: function isRegistered({ id }) isCIDRegistered(id) michael@0: exports.isRegistered = isRegistered; michael@0: michael@0: /** michael@0: * Registers given `component` object to be used to instantiate a particular michael@0: * class identified by `component.id`, and creates an association of class michael@0: * name and `component.contract` with the class. michael@0: */ michael@0: function register(factory) { michael@0: if (!(factory instanceof Factory)) { michael@0: throw new Error("xpcom.register() expect a Factory instance.\n" + michael@0: "Please refactor your code to new xpcom module if you" + michael@0: " are repacking an addon from SDK <= 1.5:\n" + michael@0: "https://addons.mozilla.org/en-US/developers/docs/sdk/latest/packages/api-utils/xpcom.html"); michael@0: } michael@0: michael@0: registerFactory(factory.id, factory.description, factory.contract, factory); michael@0: michael@0: if (factory.unregister) michael@0: require('../system/unload').when(unregister.bind(null, factory)); michael@0: } michael@0: exports.register = register; michael@0: michael@0: /** michael@0: * Unregister a factory associated with a particular class identified by michael@0: * `factory.classID`. michael@0: */ michael@0: function unregister(factory) { michael@0: if (isRegistered(factory)) michael@0: unregisterFactory(factory.id, factory); michael@0: } michael@0: exports.unregister = unregister; michael@0: michael@0: function autoRegister(path) { michael@0: // TODO: This assumes that the url points to a directory michael@0: // that contains subdirectories corresponding to OS/ABI and then michael@0: // further subdirectories corresponding to Gecko platform version. michael@0: // we should probably either behave intelligently here or allow michael@0: // the caller to pass-in more options if e.g. there aren't michael@0: // Gecko-specific binaries for a component (which will be the case michael@0: // if only frozen interfaces are used). michael@0: michael@0: var runtime = require("../system/runtime"); michael@0: var osDirName = runtime.OS + "_" + runtime.XPCOMABI; michael@0: var platformVersion = require("../system/xul-app").platformVersion.substring(0, 5); michael@0: michael@0: var file = Cc['@mozilla.org/file/local;1'] michael@0: .createInstance(Ci.nsILocalFile); michael@0: file.initWithPath(path); michael@0: file.append(osDirName); michael@0: file.append(platformVersion); michael@0: michael@0: if (!(file.exists() && file.isDirectory())) michael@0: throw new Error("component not available for OS/ABI " + michael@0: osDirName + " and platform " + platformVersion); michael@0: michael@0: Cm.QueryInterface(Ci.nsIComponentRegistrar); michael@0: Cm.autoRegister(file); michael@0: } michael@0: exports.autoRegister = autoRegister; michael@0: michael@0: /** michael@0: * Returns registered factory that has a given `id` or `null` if not found. michael@0: */ michael@0: function factoryByID(id) classesByID[id] || null michael@0: exports.factoryByID = factoryByID; michael@0: michael@0: /** michael@0: * Returns factory registered with a given `contract` or `null` if not found. michael@0: * In contrast to `Cc[contract]` that does ignores new factory registration michael@0: * with a given `contract` this will return a factory currently associated michael@0: * with a `contract`. michael@0: */ michael@0: function factoryByContract(contract) factoryByID(Cm.contractIDToCID(contract)) michael@0: exports.factoryByContract = factoryByContract;