summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/core
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/core')
-rw-r--r--toolkit/jetpack/sdk/core/disposable.js186
-rw-r--r--toolkit/jetpack/sdk/core/heritage.js184
-rw-r--r--toolkit/jetpack/sdk/core/namespace.js43
-rw-r--r--toolkit/jetpack/sdk/core/observer.js89
-rw-r--r--toolkit/jetpack/sdk/core/promise.js118
-rw-r--r--toolkit/jetpack/sdk/core/reference.js29
6 files changed, 649 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/core/disposable.js b/toolkit/jetpack/sdk/core/disposable.js
new file mode 100644
index 000000000..19f7eaa9f
--- /dev/null
+++ b/toolkit/jetpack/sdk/core/disposable.js
@@ -0,0 +1,186 @@
+/* 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": "experimental"
+};
+
+const { Class } = require("./heritage");
+const { Observer, subscribe, unsubscribe, observe } = require("./observer");
+const { isWeak } = require("./reference");
+const SDKWeakSet = require("../lang/weak-set");
+
+const method = require("../../method/core");
+
+const unloadSubject = require('@loader/unload');
+const addonUnloadTopic = "sdk:loader:destroy";
+
+const uninstall = method("disposable/uninstall");
+exports.uninstall = uninstall;
+
+const shutdown = method("disposable/shutdown");
+exports.shutdown = shutdown;
+
+const disable = method("disposable/disable");
+exports.disable = disable;
+
+const upgrade = method("disposable/upgrade");
+exports.upgrade = upgrade;
+
+const downgrade = method("disposable/downgrade");
+exports.downgrade = downgrade;
+
+const unload = method("disposable/unload");
+exports.unload = unload;
+
+const dispose = method("disposable/dispose");
+exports.dispose = dispose;
+dispose.define(Object, object => object.dispose());
+
+const setup = method("disposable/setup");
+exports.setup = setup;
+setup.define(Object, (object, ...args) => object.setup(...args));
+
+// DisposablesUnloadObserver is the class which subscribe the
+// Observer Service to be notified when the add-on loader is
+// unloading to be able to dispose all the existent disposables.
+const DisposablesUnloadObserver = Class({
+ implements: [Observer],
+ initialize: function(...args) {
+ // Set of the non-weak disposables registered to be disposed.
+ this.disposables = new Set();
+ // Target of the weak disposables registered to be disposed
+ // (and tracked on this target using the SDK weak-set module).
+ this.weakDisposables = {};
+ },
+ subscribe(disposable) {
+ if (isWeak(disposable)) {
+ SDKWeakSet.add(this.weakDisposables, disposable);
+ } else {
+ this.disposables.add(disposable);
+ }
+ },
+ unsubscribe(disposable) {
+ if (isWeak(disposable)) {
+ SDKWeakSet.remove(this.weakDisposables, disposable);
+ } else {
+ this.disposables.delete(disposable);
+ }
+ },
+ tryUnloadDisposable(disposable) {
+ try {
+ if (disposable) {
+ unload(disposable);
+ }
+ } catch(e) {
+ console.error("Error unloading a",
+ isWeak(disposable) ? "weak disposable" : "disposable",
+ disposable, e);
+ }
+ },
+ unloadAll() {
+ // Remove all the subscribed disposables.
+ for (let disposable of this.disposables) {
+ this.tryUnloadDisposable(disposable);
+ }
+
+ this.disposables.clear();
+
+ // Remove all the subscribed weak disposables.
+ for (let disposable of SDKWeakSet.iterator(this.weakDisposables)) {
+ this.tryUnloadDisposable(disposable);
+ }
+
+ SDKWeakSet.clear(this.weakDisposables);
+ }
+});
+const disposablesUnloadObserver = new DisposablesUnloadObserver();
+
+// The DisposablesUnloadObserver instance is the only object which subscribes
+// the Observer Service directly, it observes add-on unload notifications in
+// order to trigger `unload` on all its subscribed disposables.
+observe.define(DisposablesUnloadObserver, (obj, subject, topic, data) => {
+ const isUnloadTopic = topic === addonUnloadTopic;
+ const isUnloadSubject = subject.wrappedJSObject === unloadSubject;
+ if (isUnloadTopic && isUnloadSubject) {
+ unsubscribe(disposablesUnloadObserver, addonUnloadTopic);
+ disposablesUnloadObserver.unloadAll();
+ }
+});
+
+subscribe(disposablesUnloadObserver, addonUnloadTopic, false);
+
+// Set's up disposable instance.
+const setupDisposable = disposable => {
+ disposablesUnloadObserver.subscribe(disposable);
+};
+exports.setupDisposable = setupDisposable;
+
+// Tears down disposable instance.
+const disposeDisposable = disposable => {
+ disposablesUnloadObserver.unsubscribe(disposable);
+};
+exports.disposeDisposable = disposeDisposable;
+
+// Base type that takes care of disposing it's instances on add-on unload.
+// Also makes sure to remove unload listener if it's already being disposed.
+const Disposable = Class({
+ initialize: function(...args) {
+ // First setup instance before initializing it's disposal. If instance
+ // fails to initialize then there is no instance to be disposed at the
+ // unload.
+ setup(this, ...args);
+ setupDisposable(this);
+ },
+ destroy: function(reason) {
+ // Destroying disposable removes unload handler so that attempt to dispose
+ // won't be made at unload & delegates to dispose.
+ disposeDisposable(this);
+ unload(this, reason);
+ },
+ setup: function() {
+ // Implement your initialize logic here.
+ },
+ dispose: function() {
+ // Implement your cleanup logic here.
+ }
+});
+exports.Disposable = Disposable;
+
+const unloaders = {
+ destroy: dispose,
+ uninstall: uninstall,
+ shutdown: shutdown,
+ disable: disable,
+ upgrade: upgrade,
+ downgrade: downgrade
+};
+
+const unloaded = new WeakMap();
+unload.define(Disposable, (disposable, reason) => {
+ if (!unloaded.get(disposable)) {
+ unloaded.set(disposable, true);
+ // Pick an unload handler associated with an unload
+ // reason (falling back to destroy if not found) and
+ // delegate unloading to it.
+ const unload = unloaders[reason] || unloaders.destroy;
+ unload(disposable);
+ }
+});
+
+// If add-on is disabled manually, it's being upgraded, downgraded
+// or uninstalled `dispose` is invoked to undo any changes that
+// has being done by it in this session.
+disable.define(Disposable, dispose);
+downgrade.define(Disposable, dispose);
+upgrade.define(Disposable, dispose);
+uninstall.define(Disposable, dispose);
+
+// If application is shut down no dispose is invoked as undo-ing
+// changes made by instance is likely to just waste of resources &
+// increase shutdown time. Although specefic components may choose
+// to implement shutdown handler that does something better.
+shutdown.define(Disposable, disposable => {});
diff --git a/toolkit/jetpack/sdk/core/heritage.js b/toolkit/jetpack/sdk/core/heritage.js
new file mode 100644
index 000000000..fc87ba1f5
--- /dev/null
+++ b/toolkit/jetpack/sdk/core/heritage.js
@@ -0,0 +1,184 @@
+/* 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"
+};
+
+var getPrototypeOf = Object.getPrototypeOf;
+var getNames = x => [...Object.getOwnPropertyNames(x),
+ ...Object.getOwnPropertySymbols(x)];
+var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+var create = Object.create;
+var freeze = Object.freeze;
+var unbind = Function.call.bind(Function.bind, Function.call);
+
+// This shortcut makes sure that we do perform desired operations, even if
+// associated methods have being overridden on the used object.
+var owns = unbind(Object.prototype.hasOwnProperty);
+var apply = unbind(Function.prototype.apply);
+var slice = Array.slice || unbind(Array.prototype.slice);
+var reduce = Array.reduce || unbind(Array.prototype.reduce);
+var map = Array.map || unbind(Array.prototype.map);
+var concat = Array.concat || unbind(Array.prototype.concat);
+
+// Utility function to get own properties descriptor map.
+function getOwnPropertyDescriptors(object) {
+ return reduce(getNames(object), function(descriptor, name) {
+ descriptor[name] = getOwnPropertyDescriptor(object, name);
+ return descriptor;
+ }, {});
+}
+
+function isDataProperty(property) {
+ var value = property.value;
+ var type = typeof(property.value);
+ return "value" in property &&
+ (type !== "object" || value === null) &&
+ type !== "function";
+}
+
+function getDataProperties(object) {
+ var properties = getOwnPropertyDescriptors(object);
+ return getNames(properties).reduce(function(result, name) {
+ var property = properties[name];
+ if (isDataProperty(property)) {
+ result[name] = {
+ value: property.value,
+ writable: true,
+ configurable: true,
+ enumerable: false
+ };
+ }
+ return result;
+ }, {})
+}
+
+/**
+ * Takes `source` object as an argument and returns identical object
+ * with the difference that all own properties will be non-enumerable
+ */
+function obscure(source) {
+ var descriptor = reduce(getNames(source), function(descriptor, name) {
+ var property = getOwnPropertyDescriptor(source, name);
+ property.enumerable = false;
+ descriptor[name] = property;
+ return descriptor;
+ }, {});
+ return create(getPrototypeOf(source), descriptor);
+}
+exports.obscure = obscure;
+
+/**
+ * Takes arbitrary number of source objects and returns fresh one, that
+ * inherits from the same prototype as a first argument and implements all
+ * own properties of all argument objects. If two or more argument objects
+ * have own properties with the same name, the property is overridden, with
+ * precedence from right to left, implying, that properties of the object on
+ * the left are overridden by a same named property of the object on the right.
+ */
+var mix = function(source) {
+ var descriptor = reduce(slice(arguments), function(descriptor, source) {
+ return reduce(getNames(source), function(descriptor, name) {
+ descriptor[name] = getOwnPropertyDescriptor(source, name);
+ return descriptor;
+ }, descriptor);
+ }, {});
+
+ return create(getPrototypeOf(source), descriptor);
+};
+exports.mix = mix;
+
+/**
+ * Returns a frozen object with that inherits from the given `prototype` and
+ * implements all own properties of the given `properties` object.
+ */
+function extend(prototype, properties) {
+ return create(prototype, getOwnPropertyDescriptors(properties));
+}
+exports.extend = extend;
+
+/**
+ * Returns a constructor function with a proper `prototype` setup. Returned
+ * constructor's `prototype` inherits from a given `options.extends` or
+ * `Class.prototype` if omitted and implements all the properties of the
+ * given `option`. If `options.implemens` array is passed, it's elements
+ * will be mixed into prototype as well. Also, `options.extends` can be
+ * a function or a prototype. If function than it's prototype is used as
+ * an ancestor of the prototype, if it's an object that it's used directly.
+ * Also `options.implements` may contain functions or objects, in case of
+ * functions their prototypes are used for mixing.
+ */
+var Class = new function() {
+ function prototypeOf(input) {
+ return typeof(input) === 'function' ? input.prototype : input;
+ }
+ var none = freeze([]);
+
+ return function Class(options) {
+ // Create descriptor with normalized `options.extends` and
+ // `options.implements`.
+ var descriptor = {
+ // Normalize extends property of `options.extends` to a prototype object
+ // in case it's constructor. If property is missing that fallback to
+ // `Type.prototype`.
+ extends: owns(options, 'extends') ?
+ prototypeOf(options.extends) : Class.prototype,
+ // Normalize `options.implements` to make sure that it's array of
+ // prototype objects instead of constructor functions.
+ implements: owns(options, 'implements') ?
+ freeze(map(options.implements, prototypeOf)) : none
+ };
+
+ // Create array of property descriptors who's properties will be defined
+ // on the resulting prototype. Note: Using reflection `concat` instead of
+ // method as it may be overridden.
+ var descriptors = concat(descriptor.implements, options, descriptor, {
+ constructor: constructor
+ });
+
+ // Note: we use reflection `apply` in the constructor instead of method
+ // call since later may be overridden.
+ function constructor() {
+ var instance = create(prototype, attributes);
+ if (initialize) apply(initialize, instance, arguments);
+ return instance;
+ }
+ // Create `prototype` that inherits from given ancestor passed as
+ // `options.extends`, falling back to `Type.prototype`, implementing all
+ // properties of given `options.implements` and `options` itself.
+ var prototype = extend(descriptor.extends, mix.apply(mix, descriptors));
+ var initialize = prototype.initialize;
+
+ // Combine ancestor attributes with prototype's attributes so that
+ // ancestors attributes also become initializeable.
+ var attributes = mix(descriptor.extends.constructor.attributes || {},
+ getDataProperties(prototype));
+
+ constructor.attributes = attributes;
+ Object.defineProperty(constructor, 'prototype', {
+ configurable: false,
+ writable: false,
+ value: prototype
+ });
+ return constructor;
+ };
+}
+Class.prototype = extend(null, obscure({
+ constructor: function constructor() {
+ this.initialize.apply(this, arguments);
+ return this;
+ },
+ initialize: function initialize() {
+ // Do your initialization logic here
+ },
+ // Copy useful properties from `Object.prototype`.
+ toString: Object.prototype.toString,
+ toLocaleString: Object.prototype.toLocaleString,
+ toSource: Object.prototype.toSource,
+ valueOf: Object.prototype.valueOf,
+ isPrototypeOf: Object.prototype.isPrototypeOf
+}));
+exports.Class = freeze(Class);
diff --git a/toolkit/jetpack/sdk/core/namespace.js b/toolkit/jetpack/sdk/core/namespace.js
new file mode 100644
index 000000000..3ceb73b72
--- /dev/null
+++ b/toolkit/jetpack/sdk/core/namespace.js
@@ -0,0 +1,43 @@
+/* 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 create = Object.create;
+const prototypeOf = Object.getPrototypeOf;
+
+/**
+ * Returns a new namespace, function that may can be used to access an
+ * namespaced object of the argument argument. Namespaced object are associated
+ * with owner objects via weak references. Namespaced objects inherit from the
+ * owners ancestor namespaced object. If owner's ancestor is `null` then
+ * namespaced object inherits from given `prototype`. Namespaces can be used
+ * to define internal APIs that can be shared via enclosing `namespace`
+ * function.
+ * @examples
+ * const internals = ns();
+ * internals(object).secret = secret;
+ */
+function ns() {
+ const map = new WeakMap();
+ return function namespace(target) {
+ if (!target) // If `target` is not an object return `target` itself.
+ return target;
+ // If target has no namespaced object yet, create one that inherits from
+ // the target prototype's namespaced object.
+ if (!map.has(target))
+ map.set(target, create(namespace(prototypeOf(target) || null)));
+
+ return map.get(target);
+ };
+};
+
+// `Namespace` is a e4x function in the scope, so we export the function also as
+// `ns` as alias to avoid clashing.
+exports.ns = ns;
+exports.Namespace = ns;
diff --git a/toolkit/jetpack/sdk/core/observer.js b/toolkit/jetpack/sdk/core/observer.js
new file mode 100644
index 000000000..7e11bf8f9
--- /dev/null
+++ b/toolkit/jetpack/sdk/core/observer.js
@@ -0,0 +1,89 @@
+/* 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": "experimental"
+};
+
+
+const { Cc, Ci, Cr, Cu } = require("chrome");
+const { Class } = require("./heritage");
+const { isWeak } = require("./reference");
+const method = require("../../method/core");
+
+const observerService = Cc['@mozilla.org/observer-service;1'].
+ getService(Ci.nsIObserverService);
+
+const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
+const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
+
+// This is a method that will be invoked when notification observer
+// subscribed to occurs.
+const observe = method("observer/observe");
+exports.observe = observe;
+
+// Method to subscribe to the observer notification.
+const subscribe = method("observe/subscribe");
+exports.subscribe = subscribe;
+
+
+// Method to unsubscribe from the observer notifications.
+const unsubscribe = method("observer/unsubscribe");
+exports.unsubscribe = unsubscribe;
+
+
+// This is wrapper class that takes a `delegate` and produces
+// instance of `nsIObserver` which will delegate to a given
+// object when observer notification occurs.
+const ObserverDelegee = Class({
+ initialize: function(delegate) {
+ this.delegate = delegate;
+ },
+ QueryInterface: function(iid) {
+ if (!iid.equals(Ci.nsIObserver) &&
+ !iid.equals(Ci.nsISupportsWeakReference) &&
+ !iid.equals(Ci.nsISupports))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ },
+ observe: function(subject, topic, data) {
+ observe(this.delegate, subject, topic, data);
+ }
+});
+
+
+// Class that can be either mixed in or inherited from in
+// order to subscribe / unsubscribe for observer notifications.
+const Observer = Class({});
+exports.Observer = Observer;
+
+// Weak maps that associates instance of `ObserverDelegee` with
+// an actual observer. It ensures that `ObserverDelegee` instance
+// won't be GC-ed until given `observer` is.
+const subscribers = new WeakMap();
+
+// Implementation of `subscribe` for `Observer` type just registers
+// observer for an observer service. If `isWeak(observer)` is `true`
+// observer service won't hold strong reference to a given `observer`.
+subscribe.define(Observer, (observer, topic) => {
+ if (!subscribers.has(observer)) {
+ const delegee = new ObserverDelegee(observer);
+ subscribers.set(observer, delegee);
+ addObserver(delegee, topic, isWeak(observer));
+ }
+});
+
+// Unsubscribes `observer` from observer notifications for the
+// given `topic`.
+unsubscribe.define(Observer, (observer, topic) => {
+ const delegee = subscribers.get(observer);
+ if (delegee) {
+ subscribers.delete(observer);
+ removeObserver(delegee, topic);
+ }
+});
diff --git a/toolkit/jetpack/sdk/core/promise.js b/toolkit/jetpack/sdk/core/promise.js
new file mode 100644
index 000000000..f4bd7b0f5
--- /dev/null
+++ b/toolkit/jetpack/sdk/core/promise.js
@@ -0,0 +1,118 @@
+/* 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';
+
+/*
+ * Uses `Promise.jsm` as a core implementation, with additional sugar
+ * from previous implementation, with inspiration from `Q` and `when`
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm
+ * https://github.com/cujojs/when
+ * https://github.com/kriskowal/q
+ */
+const PROMISE_URI = 'resource://gre/modules/Promise.jsm';
+
+getEnvironment.call(this, function ({ require, exports, module, Cu }) {
+
+const Promise = Cu.import(PROMISE_URI, {}).Promise;
+const { Debugging, defer, resolve, all, reject, race } = Promise;
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+var promised = (function() {
+ // Note: Define shortcuts and utility functions here in order to avoid
+ // slower property accesses and unnecessary closure creations on each
+ // call of this popular function.
+
+ var call = Function.call;
+ var concat = Array.prototype.concat;
+
+ // Utility function that does following:
+ // execute([ f, self, args...]) => f.apply(self, args)
+ function execute (args) {
+ return call.apply(call, args);
+ }
+
+ // Utility function that takes promise of `a` array and maybe promise `b`
+ // as arguments and returns promise for `a.concat(b)`.
+ function promisedConcat(promises, unknown) {
+ return promises.then(function (values) {
+ return resolve(unknown)
+ .then(value => values.concat([value]));
+ });
+ }
+
+ return function promised(f, prototype) {
+ /**
+ Returns a wrapped `f`, which when called returns a promise that resolves to
+ `f(...)` passing all the given arguments to it, which by the way may be
+ promises. Optionally second `prototype` argument may be provided to be used
+ a prototype for a returned promise.
+
+ ## Example
+
+ var promise = promised(Array)(1, promise(2), promise(3))
+ promise.then(console.log) // => [ 1, 2, 3 ]
+ **/
+
+ return function promised(...args) {
+ // create array of [ f, this, args... ]
+ return [f, this, ...args].
+ // reduce it via `promisedConcat` to get promised array of fulfillments
+ reduce(promisedConcat, resolve([], prototype)).
+ // finally map that to promise of `f.apply(this, args...)`
+ then(execute);
+ };
+ };
+})();
+
+exports.promised = promised;
+exports.all = all;
+exports.defer = defer;
+exports.resolve = resolve;
+exports.reject = reject;
+exports.race = race;
+exports.Promise = Promise;
+exports.Debugging = Debugging;
+});
+
+function getEnvironment (callback) {
+ let Cu, _exports, _module, _require;
+
+ // CommonJS / SDK
+ if (typeof(require) === 'function') {
+ Cu = require('chrome').Cu;
+ _exports = exports;
+ _module = module;
+ _require = require;
+ }
+ // JSM
+ else if (String(this).indexOf('BackstagePass') >= 0) {
+ Cu = this['Components'].utils;
+ _exports = this.Promise = {};
+ _module = { uri: __URI__, id: 'promise/core' };
+ _require = uri => {
+ let imports = {};
+ Cu.import(uri, imports);
+ return imports;
+ };
+ this.EXPORTED_SYMBOLS = ['Promise'];
+ // mozIJSSubScriptLoader.loadSubscript
+ } else if (~String(this).indexOf('Sandbox')) {
+ Cu = this['Components'].utils;
+ _exports = this;
+ _module = { id: 'promise/core' };
+ _require = uri => {};
+ }
+
+ callback({
+ Cu: Cu,
+ exports: _exports,
+ module: _module,
+ require: _require
+ });
+}
+
diff --git a/toolkit/jetpack/sdk/core/reference.js b/toolkit/jetpack/sdk/core/reference.js
new file mode 100644
index 000000000..04549cd0f
--- /dev/null
+++ b/toolkit/jetpack/sdk/core/reference.js
@@ -0,0 +1,29 @@
+/* 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": "experimental"
+};
+
+const method = require("../../method/core");
+const { Class } = require("./heritage");
+
+// Object that inherit or mix WeakRefence inn will register
+// weak observes for system notifications.
+const WeakReference = Class({});
+exports.WeakReference = WeakReference;
+
+
+// If `isWeak(object)` is `true` observer installed
+// for such `object` will be weak, meaning that it will
+// be GC-ed if nothing else but observer is observing it.
+// By default everything except `WeakReference` will return
+// `false`.
+const isWeak = method("reference/weak?");
+exports.isWeak = isWeak;
+
+isWeak.define(Object, _ => false);
+isWeak.define(WeakReference, _ => true);