1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/platform/xpcom.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,229 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +module.metadata = { 1.11 + "stability": "unstable" 1.12 +}; 1.13 + 1.14 +const { Cc, Ci, Cr, Cm, components: { classesByID } } = require('chrome'); 1.15 +const { registerFactory, unregisterFactory, isCIDRegistered } = 1.16 + Cm.QueryInterface(Ci.nsIComponentRegistrar); 1.17 + 1.18 +const { merge } = require('../util/object'); 1.19 +const { Class, extend, mix } = require('../core/heritage'); 1.20 +const { uuid } = require('../util/uuid'); 1.21 + 1.22 +// This is a base prototype, that provides bare bones of XPCOM. JS based 1.23 +// components can be easily implement by extending it. 1.24 +const Unknown = new function() { 1.25 + function hasInterface(component, iid) { 1.26 + return component && component.interfaces && 1.27 + ( component.interfaces.some(function(id) iid.equals(Ci[id])) || 1.28 + component.implements.some(function($) hasInterface($, iid)) || 1.29 + hasInterface(Object.getPrototypeOf(component), iid)); 1.30 + } 1.31 + 1.32 + return Class({ 1.33 + /** 1.34 + * The `QueryInterface` method provides runtime type discovery used by XPCOM. 1.35 + * This method return queried instance of `this` if given `iid` is listed in 1.36 + * the `interfaces` property or in equivalent properties of objects in it's 1.37 + * prototype chain. In addition it will look up in the prototypes under 1.38 + * `implements` array property, this ways compositions made via `Class` 1.39 + * utility will carry interfaces implemented by composition components. 1.40 + */ 1.41 + QueryInterface: function QueryInterface(iid) { 1.42 + // For some reason there are cases when `iid` is `null`. In such cases we 1.43 + // just return `this`. Otherwise we verify that component implements given 1.44 + // `iid` interface. This will be no longer necessary once Bug 748003 is 1.45 + // fixed. 1.46 + if (iid && !hasInterface(this, iid)) 1.47 + throw Cr.NS_ERROR_NO_INTERFACE; 1.48 + 1.49 + return this; 1.50 + }, 1.51 + /** 1.52 + * Array of `XPCOM` interfaces (as strings) implemented by this component. 1.53 + * All components implement `nsISupports` by default which is default value 1.54 + * here. Provide array of interfaces implemented by an object when 1.55 + * extending, to append them to this list (Please note that there is no 1.56 + * need to repeat interfaces implemented by super as they will be added 1.57 + * automatically). 1.58 + */ 1.59 + interfaces: Object.freeze([ 'nsISupports' ]) 1.60 + }); 1.61 +} 1.62 +exports.Unknown = Unknown; 1.63 + 1.64 +// Base exemplar for creating instances implementing `nsIFactory` interface, 1.65 +// that maybe registered into runtime via `register` function. Instances of 1.66 +// this factory create instances of enclosed component on `createInstance`. 1.67 +const Factory = Class({ 1.68 + extends: Unknown, 1.69 + interfaces: [ 'nsIFactory' ], 1.70 + /** 1.71 + * All the descendants will get auto generated `id` (also known as `classID` 1.72 + * in XPCOM world) unless one is manually provided. 1.73 + */ 1.74 + get id() { throw Error('Factory must implement `id` property') }, 1.75 + /** 1.76 + * XPCOM `contractID` may optionally be provided to associate this factory 1.77 + * with it. `contract` is a unique string that has a following format: 1.78 + * '@vendor.com/unique/id;1'. 1.79 + */ 1.80 + contract: null, 1.81 + /** 1.82 + * Class description that is being registered. This value is intended as a 1.83 + * human-readable description for the given class and does not needs to be 1.84 + * globally unique. 1.85 + */ 1.86 + description: 'Jetpack generated factory', 1.87 + /** 1.88 + * This method is required by `nsIFactory` interfaces, but as in most 1.89 + * implementations it does nothing interesting. 1.90 + */ 1.91 + lockFactory: function lockFactory(lock) undefined, 1.92 + /** 1.93 + * If property is `true` XPCOM service / factory will be registered 1.94 + * automatically on creation. 1.95 + */ 1.96 + register: true, 1.97 + /** 1.98 + * If property is `true` XPCOM factory will be unregistered prior to add-on 1.99 + * unload. 1.100 + */ 1.101 + unregister: true, 1.102 + /** 1.103 + * Method is called on `Service.new(options)` passing given `options` to 1.104 + * it. Options is expected to have `component` property holding XPCOM 1.105 + * component implementation typically decedent of `Unknown` or any custom 1.106 + * implementation with a `new` method and optional `register`, `unregister` 1.107 + * flags. Unless `register` is `false` Service / Factory will be 1.108 + * automatically registered. Unless `unregister` is `false` component will 1.109 + * be automatically unregistered on add-on unload. 1.110 + */ 1.111 + initialize: function initialize(options) { 1.112 + merge(this, { 1.113 + id: 'id' in options ? options.id : uuid(), 1.114 + register: 'register' in options ? options.register : this.register, 1.115 + unregister: 'unregister' in options ? options.unregister : this.unregister, 1.116 + contract: 'contract' in options ? options.contract : null, 1.117 + Component: options.Component 1.118 + }); 1.119 + 1.120 + // If service / factory has auto registration enabled then register. 1.121 + if (this.register) 1.122 + register(this); 1.123 + }, 1.124 + /** 1.125 + * Creates an instance of the class associated with this factory. 1.126 + */ 1.127 + createInstance: function createInstance(outer, iid) { 1.128 + try { 1.129 + if (outer) 1.130 + throw Cr.NS_ERROR_NO_AGGREGATION; 1.131 + return this.create().QueryInterface(iid); 1.132 + } 1.133 + catch (error) { 1.134 + throw error instanceof Ci.nsIException ? error : Cr.NS_ERROR_FAILURE; 1.135 + } 1.136 + }, 1.137 + create: function create() this.Component() 1.138 +}); 1.139 +exports.Factory = Factory; 1.140 + 1.141 +// Exemplar for creating services that implement `nsIFactory` interface, that 1.142 +// can be registered into runtime via call to `register`. This services return 1.143 +// enclosed `component` on `getService`. 1.144 +const Service = Class({ 1.145 + extends: Factory, 1.146 + initialize: function initialize(options) { 1.147 + this.component = options.Component(); 1.148 + Factory.prototype.initialize.call(this, options); 1.149 + }, 1.150 + description: 'Jetpack generated service', 1.151 + /** 1.152 + * Creates an instance of the class associated with this factory. 1.153 + */ 1.154 + create: function create() this.component 1.155 +}); 1.156 +exports.Service = Service; 1.157 + 1.158 +function isRegistered({ id }) isCIDRegistered(id) 1.159 +exports.isRegistered = isRegistered; 1.160 + 1.161 +/** 1.162 + * Registers given `component` object to be used to instantiate a particular 1.163 + * class identified by `component.id`, and creates an association of class 1.164 + * name and `component.contract` with the class. 1.165 + */ 1.166 +function register(factory) { 1.167 + if (!(factory instanceof Factory)) { 1.168 + throw new Error("xpcom.register() expect a Factory instance.\n" + 1.169 + "Please refactor your code to new xpcom module if you" + 1.170 + " are repacking an addon from SDK <= 1.5:\n" + 1.171 + "https://addons.mozilla.org/en-US/developers/docs/sdk/latest/packages/api-utils/xpcom.html"); 1.172 + } 1.173 + 1.174 + registerFactory(factory.id, factory.description, factory.contract, factory); 1.175 + 1.176 + if (factory.unregister) 1.177 + require('../system/unload').when(unregister.bind(null, factory)); 1.178 +} 1.179 +exports.register = register; 1.180 + 1.181 +/** 1.182 + * Unregister a factory associated with a particular class identified by 1.183 + * `factory.classID`. 1.184 + */ 1.185 +function unregister(factory) { 1.186 + if (isRegistered(factory)) 1.187 + unregisterFactory(factory.id, factory); 1.188 +} 1.189 +exports.unregister = unregister; 1.190 + 1.191 +function autoRegister(path) { 1.192 + // TODO: This assumes that the url points to a directory 1.193 + // that contains subdirectories corresponding to OS/ABI and then 1.194 + // further subdirectories corresponding to Gecko platform version. 1.195 + // we should probably either behave intelligently here or allow 1.196 + // the caller to pass-in more options if e.g. there aren't 1.197 + // Gecko-specific binaries for a component (which will be the case 1.198 + // if only frozen interfaces are used). 1.199 + 1.200 + var runtime = require("../system/runtime"); 1.201 + var osDirName = runtime.OS + "_" + runtime.XPCOMABI; 1.202 + var platformVersion = require("../system/xul-app").platformVersion.substring(0, 5); 1.203 + 1.204 + var file = Cc['@mozilla.org/file/local;1'] 1.205 + .createInstance(Ci.nsILocalFile); 1.206 + file.initWithPath(path); 1.207 + file.append(osDirName); 1.208 + file.append(platformVersion); 1.209 + 1.210 + if (!(file.exists() && file.isDirectory())) 1.211 + throw new Error("component not available for OS/ABI " + 1.212 + osDirName + " and platform " + platformVersion); 1.213 + 1.214 + Cm.QueryInterface(Ci.nsIComponentRegistrar); 1.215 + Cm.autoRegister(file); 1.216 +} 1.217 +exports.autoRegister = autoRegister; 1.218 + 1.219 +/** 1.220 + * Returns registered factory that has a given `id` or `null` if not found. 1.221 + */ 1.222 +function factoryByID(id) classesByID[id] || null 1.223 +exports.factoryByID = factoryByID; 1.224 + 1.225 +/** 1.226 + * Returns factory registered with a given `contract` or `null` if not found. 1.227 + * In contrast to `Cc[contract]` that does ignores new factory registration 1.228 + * with a given `contract` this will return a factory currently associated 1.229 + * with a `contract`. 1.230 + */ 1.231 +function factoryByContract(contract) factoryByID(Cm.contractIDToCID(contract)) 1.232 +exports.factoryByContract = factoryByContract;