summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/util
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2018-02-09 06:46:43 -0500
committerMatt A. Tobin <email@mattatobin.com>2018-02-09 06:46:43 -0500
commitac46df8daea09899ce30dc8fd70986e258c746bf (patch)
tree2750d3125fc253fd5b0671e4bd268eff1fd97296 /toolkit/jetpack/sdk/util
parent8cecf8d5208f3945b35f879bba3015bb1a11bec6 (diff)
downloadUXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.gz
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.lz
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.xz
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.zip
Move Add-on SDK source to toolkit/jetpack
Diffstat (limited to 'toolkit/jetpack/sdk/util')
-rw-r--r--toolkit/jetpack/sdk/util/array.js123
-rw-r--r--toolkit/jetpack/sdk/util/collection.js115
-rw-r--r--toolkit/jetpack/sdk/util/contract.js55
-rw-r--r--toolkit/jetpack/sdk/util/deprecate.js40
-rw-r--r--toolkit/jetpack/sdk/util/dispatcher.js54
-rw-r--r--toolkit/jetpack/sdk/util/list.js90
-rw-r--r--toolkit/jetpack/sdk/util/match-pattern.js113
-rw-r--r--toolkit/jetpack/sdk/util/object.js104
-rw-r--r--toolkit/jetpack/sdk/util/rules.js53
-rw-r--r--toolkit/jetpack/sdk/util/sequence.js593
-rw-r--r--toolkit/jetpack/sdk/util/uuid.js19
11 files changed, 1359 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/util/array.js b/toolkit/jetpack/sdk/util/array.js
new file mode 100644
index 000000000..1d61a973e
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/array.js
@@ -0,0 +1,123 @@
+/* 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"
+};
+
+/**
+ * Returns `true` if given `array` contain given `element` or `false`
+ * otherwise.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element being looked up.
+ * @returns {Boolean}
+ */
+var has = exports.has = function has(array, element) {
+ // shorter and faster equivalent of `array.indexOf(element) >= 0`
+ return !!~array.indexOf(element);
+};
+var hasAny = exports.hasAny = function hasAny(array, elements) {
+ if (arguments.length < 2)
+ return false;
+ if (!Array.isArray(elements))
+ elements = [ elements ];
+ return array.some(function (element) {
+ return has(elements, element);
+ });
+};
+
+/**
+ * Adds given `element` to the given `array` if it does not contain it yet.
+ * `true` is returned if element was added otherwise `false` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be added.
+ * @returns {Boolean}
+ */
+var add = exports.add = function add(array, element) {
+ var result;
+ if ((result = !has(array, element)))
+ array.push(element);
+
+ return result;
+};
+
+/**
+ * Removes first occurrence of the given `element` from the given `array`. If
+ * `array` does not contain given `element` `false` is returned otherwise
+ * `true` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be removed.
+ * @returns {Boolean}
+ */
+exports.remove = function remove(array, element) {
+ var result;
+ if ((result = has(array, element)))
+ array.splice(array.indexOf(element), 1);
+
+ return result;
+};
+
+/**
+ * Produces a duplicate-free version of the given `array`.
+ * @param {Array} array
+ * Source array.
+ * @returns {Array}
+ */
+function unique(array) {
+ return array.reduce(function(result, item) {
+ add(result, item);
+ return result;
+ }, []);
+};
+exports.unique = unique;
+
+/**
+ * Produce an array that contains the union: each distinct element from all
+ * of the passed-in arrays.
+ */
+function union() {
+ return unique(Array.concat.apply(null, arguments));
+};
+exports.union = union;
+
+exports.flatten = function flatten(array){
+ var flat = [];
+ for (var i = 0, l = array.length; i < l; i++) {
+ flat = flat.concat(Array.isArray(array[i]) ? flatten(array[i]) : array[i]);
+ }
+ return flat;
+};
+
+function fromIterator(iterator) {
+ let array = [];
+ if (iterator.__iterator__) {
+ for (let item of iterator)
+ array.push(item);
+ }
+ else {
+ for (let item of iterator)
+ array.push(item);
+ }
+ return array;
+}
+exports.fromIterator = fromIterator;
+
+function find(array, predicate, fallback) {
+ var index = 0;
+ var count = array.length;
+ while (index < count) {
+ var value = array[index];
+ if (predicate(value)) return value;
+ else index = index + 1;
+ }
+ return fallback;
+}
+exports.find = find;
diff --git a/toolkit/jetpack/sdk/util/collection.js b/toolkit/jetpack/sdk/util/collection.js
new file mode 100644
index 000000000..194a29470
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/collection.js
@@ -0,0 +1,115 @@
+/* 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"
+};
+
+exports.Collection = Collection;
+
+/**
+ * Adds a collection property to the given object. Setting the property to a
+ * scalar value empties the collection and adds the value. Setting it to an
+ * array empties the collection and adds all the items in the array.
+ *
+ * @param obj
+ * The property will be defined on this object.
+ * @param propName
+ * The name of the property.
+ * @param array
+ * If given, this will be used as the collection's backing array.
+ */
+exports.addCollectionProperty = function addCollProperty(obj, propName, array) {
+ array = array || [];
+ let publicIface = new Collection(array);
+
+ Object.defineProperty(obj, propName, {
+ configurable: true,
+ enumerable: true,
+
+ set: function set(itemOrItems) {
+ array.splice(0, array.length);
+ publicIface.add(itemOrItems);
+ },
+
+ get: function get() {
+ return publicIface;
+ }
+ });
+};
+
+/**
+ * A collection is ordered, like an array, but its items are unique, like a set.
+ *
+ * @param array
+ * The collection is backed by an array. If this is given, it will be
+ * used as the backing array. This way the caller can fully control the
+ * collection. Otherwise a new empty array will be used, and no one but
+ * the collection will have access to it.
+ */
+function Collection(array) {
+ array = array || [];
+
+ /**
+ * Provides iteration over the collection. Items are yielded in the order
+ * they were added.
+ */
+ this.__iterator__ = function Collection___iterator__() {
+ let items = array.slice();
+ for (let i = 0; i < items.length; i++)
+ yield items[i];
+ };
+
+ /**
+ * The number of items in the collection.
+ */
+ this.__defineGetter__("length", function Collection_get_length() {
+ return array.length;
+ });
+
+ /**
+ * Adds a single item or an array of items to the collection. Any items
+ * already contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.add = function Collection_add(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let item = items[i];
+ if (array.indexOf(item) < 0)
+ array.push(item);
+ }
+ return this;
+ };
+
+ /**
+ * Removes a single item or an array of items from the collection. Any items
+ * not contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.remove = function Collection_remove(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let idx = array.indexOf(items[i]);
+ if (idx >= 0)
+ array.splice(idx, 1);
+ }
+ return this;
+ };
+};
+
+function toArray(itemOrItems) {
+ let isArr = itemOrItems &&
+ itemOrItems.constructor &&
+ itemOrItems.constructor.name === "Array";
+ return isArr ? itemOrItems : [itemOrItems];
+}
diff --git a/toolkit/jetpack/sdk/util/contract.js b/toolkit/jetpack/sdk/util/contract.js
new file mode 100644
index 000000000..c689ea601
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/contract.js
@@ -0,0 +1,55 @@
+/* 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 { validateOptions: valid } = require("../deprecated/api-utils");
+const method = require("method/core");
+
+// Function takes property validation rules and returns function that given
+// an `options` object will return validated / normalized options back. If
+// option(s) are invalid validator will throw exception described by rules.
+// Returned will also have contain `rules` property with a given validation
+// rules and `properties` function that can be used to generate validated
+// property getter and setters can be mixed into prototype. For more details
+// see `properties` function below.
+function contract(rules) {
+ const validator = (instance, options) => {
+ return valid(options || instance || {}, rules);
+ };
+ validator.rules = rules
+ validator.properties = function(modelFor) {
+ return properties(modelFor, rules);
+ }
+ return validator;
+}
+exports.contract = contract
+
+// Function takes `modelFor` instance state model accessor functions and
+// a property validation rules and generates object with getters and setters
+// that can be mixed into prototype. Property accessors update model for the
+// given instance. If you wish to react to property updates you can always
+// override setters to put specific logic.
+function properties(modelFor, rules) {
+ let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
+ descriptor[name] = {
+ get: function() { return modelFor(this)[name] },
+ set: function(value) {
+ let change = {};
+ change[name] = value;
+ modelFor(this)[name] = valid(change, rules)[name];
+ }
+ }
+ return descriptor
+ }, {});
+ return Object.create(Object.prototype, descriptor);
+}
+exports.properties = properties;
+
+const validate = method("contract/validate");
+exports.validate = validate;
diff --git a/toolkit/jetpack/sdk/util/deprecate.js b/toolkit/jetpack/sdk/util/deprecate.js
new file mode 100644
index 000000000..40f236de5
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/deprecate.js
@@ -0,0 +1,40 @@
+/* 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 { get, format } = require("../console/traceback");
+const { get: getPref } = require("../preferences/service");
+const PREFERENCE = "devtools.errorconsole.deprecation_warnings";
+
+function deprecateUsage(msg) {
+ // Print caller stacktrace in order to help figuring out which code
+ // does use deprecated thing
+ let stack = get().slice(2);
+
+ if (getPref(PREFERENCE))
+ console.error("DEPRECATED: " + msg + "\n" + format(stack));
+}
+exports.deprecateUsage = deprecateUsage;
+
+function deprecateFunction(fun, msg) {
+ return function deprecated() {
+ deprecateUsage(msg);
+ return fun.apply(this, arguments);
+ };
+}
+exports.deprecateFunction = deprecateFunction;
+
+function deprecateEvent(fun, msg, evtTypes) {
+ return function deprecateEvent(evtType) {
+ if (evtTypes.indexOf(evtType) >= 0)
+ deprecateUsage(msg);
+ return fun.apply(this, arguments);
+ };
+}
+exports.deprecateEvent = deprecateEvent;
diff --git a/toolkit/jetpack/sdk/util/dispatcher.js b/toolkit/jetpack/sdk/util/dispatcher.js
new file mode 100644
index 000000000..67d29dfed
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/dispatcher.js
@@ -0,0 +1,54 @@
+/* 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");
+
+// Utility function that is just an enhancement over `method` to
+// allow predicate based dispatch in addition to polymorphic
+// dispatch. Unfortunately polymorphic dispatch does not quite
+// cuts it in the world of XPCOM where no types / classes exist
+// and all the XUL nodes share same type / prototype.
+// Probably this is more generic and belongs some place else, but
+// we can move it later once this will be relevant.
+var dispatcher = hint => {
+ const base = method(hint);
+ // Make a map for storing predicate, implementation mappings.
+ let implementations = new Map();
+
+ // Dispatcher function goes through `predicate, implementation`
+ // pairs to find predicate that matches first argument and
+ // returns application of arguments on the associated
+ // `implementation`. If no matching predicate is found delegates
+ // to a `base` polymorphic function.
+ let dispatch = (value, ...rest) => {
+ for (let [predicate, implementation] of implementations) {
+ if (predicate(value))
+ return implementation(value, ...rest);
+ }
+
+ return base(value, ...rest);
+ };
+
+ // Expose base API.
+ dispatch.define = base.define;
+ dispatch.implement = base.implement;
+ dispatch.toString = base.toString;
+
+ // Add a `when` function to allow extending function via
+ // predicates.
+ dispatch.when = (predicate, implementation) => {
+ if (implementations.has(predicate))
+ throw TypeError("Already implemented for the given predicate");
+ implementations.set(predicate, implementation);
+ };
+
+ return dispatch;
+};
+
+exports.dispatcher = dispatcher;
diff --git a/toolkit/jetpack/sdk/util/list.js b/toolkit/jetpack/sdk/util/list.js
new file mode 100644
index 000000000..6d7d2dea9
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/list.js
@@ -0,0 +1,90 @@
+/* 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('../core/heritage');
+const listNS = require('../core/namespace').ns();
+
+const listOptions = {
+ /**
+ * List constructor can take any number of element to populate itself.
+ * @params {Object|String|Number} element
+ * @example
+ * List(1,2,3).length == 3 // true
+ */
+ initialize: function List() {
+ listNS(this).keyValueMap = [];
+
+ for (let i = 0, ii = arguments.length; i < ii; i++)
+ addListItem(this, arguments[i]);
+ },
+ /**
+ * Number of elements in this list.
+ * @type {Number}
+ */
+ get length() {
+ return listNS(this).keyValueMap.length;
+ },
+ /**
+ * Returns a string representing this list.
+ * @returns {String}
+ */
+ toString: function toString() {
+ return 'List(' + listNS(this).keyValueMap + ')';
+ },
+ /**
+ * Custom iterator providing `List`s enumeration behavior.
+ * We cant reuse `_iterator` that is defined by `Iterable` since it provides
+ * iteration in an arbitrary order.
+ * @see https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in
+ * @param {Boolean} onKeys
+ */
+ __iterator__: function __iterator__(onKeys, onKeyValue) {
+ let array = listNS(this).keyValueMap.slice(0),
+ i = -1;
+ for (let element of array)
+ yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
+ },
+};
+listOptions[Symbol.iterator] = function iterator() {
+ return listNS(this).keyValueMap.slice(0)[Symbol.iterator]();
+};
+const List = Class(listOptions);
+exports.List = List;
+
+function addListItem(that, value) {
+ let list = listNS(that).keyValueMap,
+ index = list.indexOf(value);
+
+ if (-1 === index) {
+ try {
+ that[that.length] = value;
+ }
+ catch (e) {}
+ list.push(value);
+ }
+}
+exports.addListItem = addListItem;
+
+function removeListItem(that, element) {
+ let list = listNS(that).keyValueMap,
+ index = list.indexOf(element);
+
+ if (0 <= index) {
+ list.splice(index, 1);
+ try {
+ for (let length = list.length; index < length; index++)
+ that[index] = list[index];
+ that[list.length] = undefined;
+ }
+ catch(e){}
+ }
+}
+exports.removeListItem = removeListItem;
+
+exports.listNS = listNS;
diff --git a/toolkit/jetpack/sdk/util/match-pattern.js b/toolkit/jetpack/sdk/util/match-pattern.js
new file mode 100644
index 000000000..a0eb88b49
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/match-pattern.js
@@ -0,0 +1,113 @@
+/* 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 { URL } = require('../url');
+const cache = {};
+
+function MatchPattern(pattern) {
+ if (cache[pattern]) return cache[pattern];
+
+ if (typeof pattern.test == "function") {
+ // For compatibility with -moz-document rules, we require the RegExp's
+ // global, ignoreCase, and multiline flags to be set to false.
+ if (pattern.global) {
+ throw new Error("A RegExp match pattern cannot be set to `global` " +
+ "(i.e. //g).");
+ }
+ if (pattern.multiline) {
+ throw new Error("A RegExp match pattern cannot be set to `multiline` " +
+ "(i.e. //m).");
+ }
+
+ this.regexp = pattern;
+ }
+ else {
+ let firstWildcardPosition = pattern.indexOf("*");
+ let lastWildcardPosition = pattern.lastIndexOf("*");
+ if (firstWildcardPosition != lastWildcardPosition)
+ throw new Error("There can be at most one '*' character in a wildcard.");
+
+ if (firstWildcardPosition == 0) {
+ if (pattern.length == 1)
+ this.anyWebPage = true;
+ else if (pattern[1] != ".")
+ throw new Error("Expected a *.<domain name> string, got: " + pattern);
+ else
+ this.domain = pattern.substr(2);
+ }
+ else {
+ if (pattern.indexOf(":") == -1) {
+ throw new Error("When not using *.example.org wildcard, the string " +
+ "supplied is expected to be either an exact URL to " +
+ "match or a URL prefix. The provided string ('" +
+ pattern + "') is unlikely to match any pages.");
+ }
+
+ if (firstWildcardPosition == -1)
+ this.exactURL = pattern;
+ else if (firstWildcardPosition == pattern.length - 1)
+ this.urlPrefix = pattern.substr(0, pattern.length - 1);
+ else {
+ throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
+ "in an unexpected position. It is expected to be the " +
+ "first or the last character in the wildcard.");
+ }
+ }
+ }
+
+ cache[pattern] = this;
+}
+
+MatchPattern.prototype = {
+ test: function MatchPattern_test(urlStr) {
+ try {
+ var url = URL(urlStr);
+ }
+ catch (err) {
+ return false;
+ }
+
+ // Test the URL against a RegExp pattern. For compatibility with
+ // -moz-document rules, we require the RegExp to match the entire URL,
+ // so we not only test for a match, we also make sure the matched string
+ // is the entire URL string.
+ //
+ // Assuming most URLs don't match most match patterns, we call `test` for
+ // speed when determining whether or not the URL matches, then call `exec`
+ // for the small subset that match to make sure the entire URL matches.
+ if (this.regexp && this.regexp.test(urlStr) &&
+ this.regexp.exec(urlStr)[0] == urlStr)
+ return true;
+
+ if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
+ return true;
+
+ if (this.exactURL && this.exactURL == urlStr)
+ return true;
+
+ // Tests the urlStr against domain and check if
+ // wildcard submitted (*.domain.com), it only allows
+ // subdomains (sub.domain.com) or from the root (http://domain.com)
+ // and reject non-matching domains (otherdomain.com)
+ // bug 856913
+ if (this.domain && url.host &&
+ (url.host === this.domain ||
+ url.host.slice(-this.domain.length - 1) === "." + this.domain))
+ return true;
+
+ if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
+ return true;
+
+ return false;
+ },
+
+ toString: () => '[object MatchPattern]'
+};
+
+exports.MatchPattern = MatchPattern;
diff --git a/toolkit/jetpack/sdk/util/object.js b/toolkit/jetpack/sdk/util/object.js
new file mode 100644
index 000000000..9d202bb51
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/object.js
@@ -0,0 +1,104 @@
+/* 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 { flatten } = require('./array');
+
+/**
+ * Merges all the properties of all arguments into first argument. 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.
+ *
+ * Any argument given with "falsy" value - commonly `null` and `undefined` in
+ * case of objects - are skipped.
+ *
+ * @examples
+ * var a = { bar: 0, a: 'a' }
+ * var b = merge(a, { foo: 'foo', bar: 1 }, { foo: 'bar', name: 'b' });
+ * b === a // true
+ * b.a // 'a'
+ * b.foo // 'bar'
+ * b.bar // 1
+ * b.name // 'b'
+ */
+function merge(source) {
+ let descriptor = {};
+
+ // `Boolean` converts the first parameter to a boolean value. Any object is
+ // converted to `true` where `null` and `undefined` becames `false`. Therefore
+ // the `filter` method will keep only objects that are defined and not null.
+ Array.slice(arguments, 1).filter(Boolean).forEach(function onEach(properties) {
+ getOwnPropertyIdentifiers(properties).forEach(function(name) {
+ descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
+ });
+ });
+ return Object.defineProperties(source, descriptor);
+}
+exports.merge = merge;
+
+/**
+ * Returns an object that inherits from the first argument and contains all the
+ * properties from all following arguments.
+ * `extend(source1, source2, source3)` is equivalent of
+ * `merge(Object.create(source1), source2, source3)`.
+ */
+function extend(source) {
+ let rest = Array.slice(arguments, 1);
+ rest.unshift(Object.create(source));
+ return merge.apply(null, rest);
+}
+exports.extend = extend;
+
+function has(obj, key) {
+ return obj.hasOwnProperty(key);
+}
+exports.has = has;
+
+function each(obj, fn) {
+ for (let key in obj) has(obj, key) && fn(obj[key], key, obj);
+}
+exports.each = each;
+
+/**
+ * Like `merge`, except no property descriptors are manipulated, for use
+ * with platform objects. Identical to underscore's `extend`. Useful for
+ * merging XPCOM objects
+ */
+function safeMerge(source) {
+ Array.slice(arguments, 1).forEach(function onEach (obj) {
+ for (let prop in obj) source[prop] = obj[prop];
+ });
+ return source;
+}
+exports.safeMerge = safeMerge;
+
+/*
+ * Returns a copy of the object without omitted properties
+ */
+function omit(source, ...values) {
+ let copy = {};
+ let keys = flatten(values);
+ for (let prop in source)
+ if (!~keys.indexOf(prop))
+ copy[prop] = source[prop];
+ return copy;
+}
+exports.omit = omit;
+
+// get object's own property Symbols and/or Names, including nonEnumerables by default
+function getOwnPropertyIdentifiers(object, options = { names: true, symbols: true, nonEnumerables: true }) {
+ const symbols = !options.symbols ? [] :
+ Object.getOwnPropertySymbols(object);
+ const names = !options.names ? [] :
+ options.nonEnumerables ? Object.getOwnPropertyNames(object) :
+ Object.keys(object);
+ return [...names, ...symbols];
+}
+exports.getOwnPropertyIdentifiers = getOwnPropertyIdentifiers;
diff --git a/toolkit/jetpack/sdk/util/rules.js b/toolkit/jetpack/sdk/util/rules.js
new file mode 100644
index 000000000..98e3109b0
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/rules.js
@@ -0,0 +1,53 @@
+/* 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 { Class } = require('../core/heritage');
+const { MatchPattern } = require('./match-pattern');
+const { emit } = require('../event/core');
+const { EventTarget } = require('../event/target');
+const { List, addListItem, removeListItem } = require('./list');
+
+// Should deprecate usage of EventEmitter/compose
+const Rules = Class({
+ implements: [
+ EventTarget,
+ List
+ ],
+ add: function(...rules) {
+ return [].concat(rules).forEach(function onAdd(rule) {
+ addListItem(this, rule);
+ emit(this, 'add', rule);
+ }, this);
+ },
+ remove: function(...rules) {
+ return [].concat(rules).forEach(function onRemove(rule) {
+ removeListItem(this, rule);
+ emit(this, 'remove', rule);
+ }, this);
+ },
+ get: function(rule) {
+ let found = false;
+ for (let i in this) if (this[i] === rule) found = true;
+ return found;
+ },
+ // Returns true if uri matches atleast one stored rule
+ matchesAny: function(uri) {
+ return !!filterMatches(this, uri).length;
+ },
+ toString: () => '[object Rules]'
+});
+exports.Rules = Rules;
+
+function filterMatches(instance, uri) {
+ let matches = [];
+ for (let i in instance) {
+ if (new MatchPattern(instance[i]).test(uri)) matches.push(instance[i]);
+ }
+ return matches;
+}
diff --git a/toolkit/jetpack/sdk/util/sequence.js b/toolkit/jetpack/sdk/util/sequence.js
new file mode 100644
index 000000000..28e3de255
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/sequence.js
@@ -0,0 +1,593 @@
+/* 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"
+};
+
+// Disclamer:
+// In this module we'll have some common argument / variable names
+// to hint their type or behavior.
+//
+// - `f` stands for "function" that is intended to be side effect
+// free.
+// - `p` stands for "predicate" that is function which returns logical
+// true or false and is intended to be side effect free.
+// - `x` / `y` single item of the sequence.
+// - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies
+// type of the items in sequence, so sequence is not of the same item.
+// - `_` used for argument(s) or variable(s) who's values are ignored.
+
+const { complement, flip, identity } = require("../lang/functional");
+const { isArray, isArguments, isMap, isSet, isGenerator,
+ isString, isBoolean, isNumber } = require("../lang/type");
+
+const Sequence = function Sequence(iterator) {
+ if (!isGenerator(iterator)) {
+ throw TypeError("Expected generator argument");
+ }
+
+ this[Symbol.iterator] = iterator;
+};
+exports.Sequence = Sequence;
+
+const polymorphic = dispatch => x =>
+ x === null ? dispatch.null(null) :
+ x === void(0) ? dispatch.void(void(0)) :
+ isArray(x) ? (dispatch.array || dispatch.indexed)(x) :
+ isString(x) ? (dispatch.string || dispatch.indexed)(x) :
+ isArguments(x) ? (dispatch.arguments || dispatch.indexed)(x) :
+ isMap(x) ? dispatch.map(x) :
+ isSet(x) ? dispatch.set(x) :
+ isNumber(x) ? dispatch.number(x) :
+ isBoolean(x) ? dispatch.boolean(x) :
+ dispatch.default(x);
+
+const nogen = function*() {};
+const empty = () => new Sequence(nogen);
+exports.empty = empty;
+
+const seq = polymorphic({
+ null: empty,
+ void: empty,
+ array: identity,
+ string: identity,
+ arguments: identity,
+ map: identity,
+ set: identity,
+ default: x => x instanceof Sequence ? x : new Sequence(x)
+});
+exports.seq = seq;
+
+// Function to cast seq to string.
+const string = (...etc) => "".concat(...etc);
+exports.string = string;
+
+// Function for casting seq to plain object.
+const object = (...pairs) => {
+ let result = {};
+ for (let [key, value] of pairs)
+ result[key] = value;
+
+ return result;
+};
+exports.object = object;
+
+// Takes `getEnumerator` function that returns `nsISimpleEnumerator`
+// and creates lazy sequence of it's items. Note that function does
+// not take `nsISimpleEnumerator` itslef because that would allow
+// single iteration, which would not be consistent with rest of the
+// lazy sequences.
+const fromEnumerator = getEnumerator => seq(function* () {
+ const enumerator = getEnumerator();
+ while (enumerator.hasMoreElements())
+ yield enumerator.getNext();
+});
+exports.fromEnumerator = fromEnumerator;
+
+// Takes `object` and returns lazy sequence of own `[key, value]`
+// pairs (does not include inherited and non enumerable keys).
+const pairs = polymorphic({
+ null: empty,
+ void: empty,
+ map: identity,
+ indexed: indexed => seq(function* () {
+ const count = indexed.length;
+ let index = 0;
+ while (index < count) {
+ yield [index, indexed[index]];
+ index = index + 1;
+ }
+ }),
+ default: object => seq(function* () {
+ for (let key of Object.keys(object))
+ yield [key, object[key]];
+ })
+});
+exports.pairs = pairs;
+
+const names = polymorphic({
+ null: empty,
+ void: empty,
+ default: object => seq(function*() {
+ for (let name of Object.getOwnPropertyNames(object)) {
+ yield name;
+ }
+ })
+});
+exports.names = names;
+
+const symbols = polymorphic({
+ null: empty,
+ void: empty,
+ default: object => seq(function* () {
+ for (let symbol of Object.getOwnPropertySymbols(object)) {
+ yield symbol;
+ }
+ })
+});
+exports.symbols = symbols;
+
+const keys = polymorphic({
+ null: empty,
+ void: empty,
+ indexed: indexed => seq(function* () {
+ const count = indexed.length;
+ let index = 0;
+ while (index < count) {
+ yield index;
+ index = index + 1;
+ }
+ }),
+ map: map => seq(function* () {
+ for (let [key, _] of map)
+ yield key;
+ }),
+ default: object => seq(function* () {
+ for (let key of Object.keys(object))
+ yield key;
+ })
+});
+exports.keys = keys;
+
+
+const values = polymorphic({
+ null: empty,
+ void: empty,
+ set: identity,
+ indexed: indexed => seq(function* () {
+ const count = indexed.length;
+ let index = 0;
+ while (index < count) {
+ yield indexed[index];
+ index = index + 1;
+ }
+ }),
+ map: map => seq(function* () {
+ for (let [_, value] of map) yield value;
+ }),
+ default: object => seq(function* () {
+ for (let key of Object.keys(object)) yield object[key];
+ })
+});
+exports.values = values;
+
+
+
+// Returns a lazy sequence of `x`, `f(x)`, `f(f(x))` etc.
+// `f` must be free of side-effects. Note that returned
+// sequence is infinite so it must be consumed partially.
+//
+// Implements clojure iterate:
+// http://clojuredocs.org/clojure_core/clojure.core/iterate
+const iterate = (f, x) => seq(function* () {
+ let state = x;
+ while (true) {
+ yield state;
+ state = f(state);
+ }
+});
+exports.iterate = iterate;
+
+// Returns a lazy sequence of the items in sequence for which `p(item)`
+// returns `true`. `p` must be free of side-effects.
+//
+// Implements clojure filter:
+// http://clojuredocs.org/clojure_core/clojure.core/filter
+const filter = (p, sequence) => seq(function* () {
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence) {
+ if (p(item))
+ yield item;
+ }
+ }
+});
+exports.filter = filter;
+
+// Returns a lazy sequence consisting of the result of applying `f` to the
+// set of first items of each sequence, followed by applying f to the set
+// of second items in each sequence, until any one of the sequences is
+// exhausted. Any remaining items in other sequences are ignored. Function
+// `f` should accept number-of-sequences arguments.
+//
+// Implements clojure map:
+// http://clojuredocs.org/clojure_core/clojure.core/map
+const map = (f, ...sequences) => seq(function* () {
+ const count = sequences.length;
+ // Optimize a single sequence case
+ if (count === 1) {
+ let [sequence] = sequences;
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence)
+ yield f(item);
+ }
+ }
+ else {
+ // define args array that will be recycled on each
+ // step to aggregate arguments to be passed to `f`.
+ let args = [];
+ // define inputs to contain started generators.
+ let inputs = [];
+
+ let index = 0;
+ while (index < count) {
+ inputs[index] = sequences[index][Symbol.iterator]();
+ index = index + 1;
+ }
+
+ // Run loop yielding of applying `f` to the set of
+ // items at each step until one of the `inputs` is
+ // exhausted.
+ let done = false;
+ while (!done) {
+ let index = 0;
+ let value = void(0);
+ while (index < count && !done) {
+ ({ done, value } = inputs[index].next());
+
+ // If input is not exhausted yet store value in args.
+ if (!done) {
+ args[index] = value;
+ index = index + 1;
+ }
+ }
+
+ // If none of the inputs is exhasted yet, `args` contain items
+ // from each input so we yield application of `f` over them.
+ if (!done)
+ yield f(...args);
+ }
+ }
+});
+exports.map = map;
+
+// Returns a lazy sequence of the intermediate values of the reduction (as
+// per reduce) of sequence by `f`, starting with `initial` value if provided.
+//
+// Implements clojure reductions:
+// http://clojuredocs.org/clojure_core/clojure.core/reductions
+const reductions = (...params) => {
+ const count = params.length;
+ let hasInitial = false;
+ let f, initial, source;
+ if (count === 2) {
+ [f, source] = params;
+ }
+ else if (count === 3) {
+ [f, initial, source] = params;
+ hasInitial = true;
+ }
+ else {
+ throw Error("Invoked with wrong number of arguments: " + count);
+ }
+
+ const sequence = seq(source);
+
+ return seq(function* () {
+ let started = hasInitial;
+ let result = void(0);
+
+ // If initial is present yield it.
+ if (hasInitial)
+ yield (result = initial);
+
+ // For each item of the sequence accumulate new result.
+ for (let item of sequence) {
+ // If nothing has being yield yet set result to first
+ // item and yield it.
+ if (!started) {
+ started = true;
+ yield (result = item);
+ }
+ // Otherwise accumulate new result and yield it.
+ else {
+ yield (result = f(result, item));
+ }
+ }
+
+ // If nothing has being yield yet it's empty sequence and no
+ // `initial` was provided in which case we need to yield `f()`.
+ if (!started)
+ yield f();
+ });
+};
+exports.reductions = reductions;
+
+// `f` should be a function of 2 arguments. If `initial` is not supplied,
+// returns the result of applying `f` to the first 2 items in sequence, then
+// applying `f` to that result and the 3rd item, etc. If sequence contains no
+// items, `f` must accept no arguments as well, and reduce returns the
+// result of calling f with no arguments. If sequence has only 1 item, it
+// is returned and `f` is not called. If `initial` is supplied, returns the
+// result of applying `f` to `initial` and the first item in sequence, then
+// applying `f` to that result and the 2nd item, etc. If sequence contains no
+// items, returns `initial` and `f` is not called.
+//
+// Implements clojure reduce:
+// http://clojuredocs.org/clojure_core/clojure.core/reduce
+const reduce = (...args) => {
+ const xs = reductions(...args);
+ let x;
+ for (x of xs) void(0);
+ return x;
+};
+exports.reduce = reduce;
+
+const each = (f, sequence) => {
+ for (let x of seq(sequence)) void(f(x));
+};
+exports.each = each;
+
+
+const inc = x => x + 1;
+// Returns the number of items in the sequence. `count(null)` && `count()`
+// returns `0`. Also works on strings, arrays, Maps & Sets.
+
+// Implements clojure count:
+// http://clojuredocs.org/clojure_core/clojure.core/count
+const count = polymorphic({
+ null: _ => 0,
+ void: _ => 0,
+ indexed: indexed => indexed.length,
+ map: map => map.size,
+ set: set => set.size,
+ default: xs => reduce(inc, 0, xs)
+});
+exports.count = count;
+
+// Returns `true` if sequence has no items.
+
+// Implements clojure empty?:
+// http://clojuredocs.org/clojure_core/clojure.core/empty_q
+const isEmpty = sequence => {
+ // Treat `null` and `undefined` as empty sequences.
+ if (sequence === null || sequence === void(0))
+ return true;
+
+ // If contains any item non empty so return `false`.
+ for (let _ of sequence)
+ return false;
+
+ // If has not returned yet, there was nothing to iterate
+ // so it's empty.
+ return true;
+};
+exports.isEmpty = isEmpty;
+
+const and = (a, b) => a && b;
+
+// Returns true if `p(x)` is logical `true` for every `x` in sequence, else
+// `false`.
+//
+// Implements clojure every?:
+// http://clojuredocs.org/clojure_core/clojure.core/every_q
+const isEvery = (p, sequence) => {
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence) {
+ if (!p(item))
+ return false;
+ }
+ }
+ return true;
+};
+exports.isEvery = isEvery;
+
+// Returns the first logical true value of (p x) for any x in sequence,
+// else `null`.
+//
+// Implements clojure some:
+// http://clojuredocs.org/clojure_core/clojure.core/some
+const some = (p, sequence) => {
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence) {
+ if (p(item))
+ return true;
+ }
+ }
+ return null;
+};
+exports.some = some;
+
+// Returns a lazy sequence of the first `n` items in sequence, or all items if
+// there are fewer than `n`.
+//
+// Implements clojure take:
+// http://clojuredocs.org/clojure_core/clojure.core/take
+const take = (n, sequence) => n <= 0 ? empty() : seq(function* () {
+ let count = n;
+ for (let item of sequence) {
+ yield item;
+ count = count - 1;
+ if (count === 0) break;
+ }
+});
+exports.take = take;
+
+// Returns a lazy sequence of successive items from sequence while
+// `p(item)` returns `true`. `p` must be free of side-effects.
+//
+// Implements clojure take-while:
+// http://clojuredocs.org/clojure_core/clojure.core/take-while
+const takeWhile = (p, sequence) => seq(function* () {
+ for (let item of sequence) {
+ if (!p(item))
+ break;
+
+ yield item;
+ }
+});
+exports.takeWhile = takeWhile;
+
+// Returns a lazy sequence of all but the first `n` items in
+// sequence.
+//
+// Implements clojure drop:
+// http://clojuredocs.org/clojure_core/clojure.core/drop
+const drop = (n, sequence) => seq(function* () {
+ if (sequence !== null && sequence !== void(0)) {
+ let count = n;
+ for (let item of sequence) {
+ if (count > 0)
+ count = count - 1;
+ else
+ yield item;
+ }
+ }
+});
+exports.drop = drop;
+
+// Returns a lazy sequence of the items in sequence starting from the
+// first item for which `p(item)` returns falsy value.
+//
+// Implements clojure drop-while:
+// http://clojuredocs.org/clojure_core/clojure.core/drop-while
+const dropWhile = (p, sequence) => seq(function* () {
+ let keep = false;
+ for (let item of sequence) {
+ keep = keep || !p(item);
+ if (keep) yield item;
+ }
+});
+exports.dropWhile = dropWhile;
+
+// Returns a lazy sequence representing the concatenation of the
+// suplied sequences.
+//
+// Implements clojure conact:
+// http://clojuredocs.org/clojure_core/clojure.core/concat
+const concat = (...sequences) => seq(function* () {
+ for (let sequence of sequences)
+ for (let item of sequence)
+ yield item;
+});
+exports.concat = concat;
+
+// Returns the first item in the sequence.
+//
+// Implements clojure first:
+// http://clojuredocs.org/clojure_core/clojure.core/first
+const first = sequence => {
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence)
+ return item;
+ }
+ return null;
+};
+exports.first = first;
+
+// Returns a possibly empty sequence of the items after the first.
+//
+// Implements clojure rest:
+// http://clojuredocs.org/clojure_core/clojure.core/rest
+const rest = sequence => drop(1, sequence);
+exports.rest = rest;
+
+// Returns the value at the index. Returns `notFound` or `undefined`
+// if index is out of bounds.
+const nth = (xs, n, notFound) => {
+ if (n >= 0) {
+ if (isArray(xs) || isArguments(xs) || isString(xs)) {
+ return n < xs.length ? xs[n] : notFound;
+ }
+ else if (xs !== null && xs !== void(0)) {
+ let count = n;
+ for (let x of xs) {
+ if (count <= 0)
+ return x;
+
+ count = count - 1;
+ }
+ }
+ }
+ return notFound;
+};
+exports.nth = nth;
+
+// Return the last item in sequence, in linear time.
+// If `sequence` is an array or string or arguments
+// returns in constant time.
+// Implements clojure last:
+// http://clojuredocs.org/clojure_core/clojure.core/last
+const last = polymorphic({
+ null: _ => null,
+ void: _ => null,
+ indexed: indexed => indexed[indexed.length - 1],
+ map: xs => reduce((_, x) => x, xs),
+ set: xs => reduce((_, x) => x, xs),
+ default: xs => reduce((_, x) => x, xs)
+});
+exports.last = last;
+
+// Return a lazy sequence of all but the last `n` (default 1) items
+// from the give `xs`.
+//
+// Implements clojure drop-last:
+// http://clojuredocs.org/clojure_core/clojure.core/drop-last
+const dropLast = flip((xs, n=1) => seq(function* () {
+ let ys = [];
+ for (let x of xs) {
+ ys.push(x);
+ if (ys.length > n)
+ yield ys.shift();
+ }
+}));
+exports.dropLast = dropLast;
+
+// Returns a lazy sequence of the elements of `xs` with duplicates
+// removed
+//
+// Implements clojure distinct
+// http://clojuredocs.org/clojure_core/clojure.core/distinct
+const distinct = sequence => seq(function* () {
+ let items = new Set();
+ for (let item of sequence) {
+ if (!items.has(item)) {
+ items.add(item);
+ yield item;
+ }
+ }
+});
+exports.distinct = distinct;
+
+// Returns a lazy sequence of the items in `xs` for which
+// `p(x)` returns false. `p` must be free of side-effects.
+//
+// Implements clojure remove
+// http://clojuredocs.org/clojure_core/clojure.core/remove
+const remove = (p, xs) => filter(complement(p), xs);
+exports.remove = remove;
+
+// Returns the result of applying concat to the result of
+// `map(f, xs)`. Thus function `f` should return a sequence.
+//
+// Implements clojure mapcat
+// http://clojuredocs.org/clojure_core/clojure.core/mapcat
+const mapcat = (f, sequence) => seq(function* () {
+ const sequences = map(f, sequence);
+ for (let sequence of sequences)
+ for (let item of sequence)
+ yield item;
+});
+exports.mapcat = mapcat;
diff --git a/toolkit/jetpack/sdk/util/uuid.js b/toolkit/jetpack/sdk/util/uuid.js
new file mode 100644
index 000000000..6d0f2de53
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/uuid.js
@@ -0,0 +1,19 @@
+/* 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, components: { ID: parseUUID } } = require('chrome');
+const { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].
+ getService(Ci.nsIUUIDGenerator);
+
+// Returns `uuid`. If `id` is passed then it's parsed to `uuid` and returned
+// if not then new one is generated.
+exports.uuid = function uuid(id) {
+ return id ? parseUUID(id) : generateUUID();
+};