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