summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/lang
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/lang')
-rw-r--r--toolkit/jetpack/sdk/lang/functional.js47
-rw-r--r--toolkit/jetpack/sdk/lang/functional/concurrent.js110
-rw-r--r--toolkit/jetpack/sdk/lang/functional/core.js290
-rw-r--r--toolkit/jetpack/sdk/lang/functional/helpers.js29
-rw-r--r--toolkit/jetpack/sdk/lang/type.js388
-rw-r--r--toolkit/jetpack/sdk/lang/weak-set.js75
6 files changed, 939 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/lang/functional.js b/toolkit/jetpack/sdk/lang/functional.js
new file mode 100644
index 000000000..66e30edfa
--- /dev/null
+++ b/toolkit/jetpack/sdk/lang/functional.js
@@ -0,0 +1,47 @@
+/* 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/. */
+
+// Disclaimer: Some of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { defer, remit, delay, debounce,
+ throttle } = require("./functional/concurrent");
+const { method, invoke, partial, curry, compose, wrap, identity, memoize, once,
+ cache, complement, constant, when, apply, flip, field, query,
+ isInstance, chainable, is, isnt } = require("./functional/core");
+
+exports.defer = defer;
+exports.remit = remit;
+exports.delay = delay;
+exports.debounce = debounce;
+exports.throttle = throttle;
+
+exports.method = method;
+exports.invoke = invoke;
+exports.partial = partial;
+exports.curry = curry;
+exports.compose = compose;
+exports.wrap = wrap;
+exports.identity = identity;
+exports.memoize = memoize;
+exports.once = once;
+exports.cache = cache;
+exports.complement = complement;
+exports.constant = constant;
+exports.when = when;
+exports.apply = apply;
+exports.flip = flip;
+exports.field = field;
+exports.query = query;
+exports.isInstance = isInstance;
+exports.chainable = chainable;
+exports.is = is;
+exports.isnt = isnt;
diff --git a/toolkit/jetpack/sdk/lang/functional/concurrent.js b/toolkit/jetpack/sdk/lang/functional/concurrent.js
new file mode 100644
index 000000000..85e8cff46
--- /dev/null
+++ b/toolkit/jetpack/sdk/lang/functional/concurrent.js
@@ -0,0 +1,110 @@
+/* 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/. */
+
+// Disclaimer: Some of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { arity, name, derive, invoke } = require("./helpers");
+const { setTimeout, clearTimeout, setImmediate } = require("../../timers");
+
+/**
+ * Takes a function and returns a wrapped one instead, calling which will call
+ * original function in the next turn of event loop. This is basically utility
+ * to do `setImmediate(function() { ... })`, with a difference that returned
+ * function is reused, instead of creating a new one each time. This also allows
+ * to use this functions as event listeners.
+ */
+const defer = f => derive(function(...args) {
+ setImmediate(invoke, f, args, this);
+}, f);
+exports.defer = defer;
+// Exporting `remit` alias as `defer` may conflict with promises.
+exports.remit = defer;
+
+/**
+ * Much like setTimeout, invokes function after wait milliseconds. If you pass
+ * the optional arguments, they will be forwarded on to the function when it is
+ * invoked.
+ */
+const delay = function delay(f, ms, ...args) {
+ setTimeout(() => f.apply(this, args), ms);
+};
+exports.delay = delay;
+
+/**
+ * From underscore's `_.debounce`
+ * http://underscorejs.org
+ * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Underscore may be freely distributed under the MIT license.
+ */
+const debounce = function debounce (fn, wait) {
+ let timeout, args, context, timestamp, result;
+
+ let later = function () {
+ let last = Date.now() - timestamp;
+ if (last < wait) {
+ timeout = setTimeout(later, wait - last);
+ } else {
+ timeout = null;
+ result = fn.apply(context, args);
+ context = args = null;
+ }
+ };
+
+ return function (...aArgs) {
+ context = this;
+ args = aArgs;
+ timestamp = Date.now();
+ if (!timeout) {
+ timeout = setTimeout(later, wait);
+ }
+
+ return result;
+ };
+};
+exports.debounce = debounce;
+
+/**
+ * From underscore's `_.throttle`
+ * http://underscorejs.org
+ * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Underscore may be freely distributed under the MIT license.
+ */
+const throttle = function throttle (func, wait, options) {
+ let context, args, result;
+ let timeout = null;
+ let previous = 0;
+ options || (options = {});
+ let later = function() {
+ previous = options.leading === false ? 0 : Date.now();
+ timeout = null;
+ result = func.apply(context, args);
+ context = args = null;
+ };
+ return function() {
+ let now = Date.now();
+ if (!previous && options.leading === false) previous = now;
+ let remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ context = args = null;
+ } else if (!timeout && options.trailing !== false) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+};
+exports.throttle = throttle;
diff --git a/toolkit/jetpack/sdk/lang/functional/core.js b/toolkit/jetpack/sdk/lang/functional/core.js
new file mode 100644
index 000000000..0d9143364
--- /dev/null
+++ b/toolkit/jetpack/sdk/lang/functional/core.js
@@ -0,0 +1,290 @@
+/* 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/. */
+
+// Disclaimer: Some of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+}
+const { arity, name, derive, invoke } = require("./helpers");
+
+/**
+ * Takes variadic numeber of functions and returns composed one.
+ * Returned function pushes `this` pseudo-variable to the head
+ * of the passed arguments and invokes all the functions from
+ * left to right passing same arguments to them. Composite function
+ * returns return value of the right most funciton.
+ */
+const method = (...lambdas) => {
+ return function method(...args) {
+ args.unshift(this);
+ return lambdas.reduce((_, lambda) => lambda.apply(this, args),
+ void(0));
+ };
+};
+exports.method = method;
+
+/**
+ * Invokes `callee` by passing `params` as an arguments and `self` as `this`
+ * pseudo-variable. Returns value that is returned by a callee.
+ * @param {Function} callee
+ * Function to invoke.
+ * @param {Array} params
+ * Arguments to invoke function with.
+ * @param {Object} self
+ * Object to be passed as a `this` pseudo variable.
+ */
+exports.invoke = invoke;
+
+/**
+ * Takes a function and bind values to one or more arguments, returning a new
+ * function of smaller arity.
+ *
+ * @param {Function} fn
+ * The function to partial
+ *
+ * @returns The new function with binded values
+ */
+const partial = (f, ...curried) => {
+ if (typeof(f) !== "function")
+ throw new TypeError(String(f) + " is not a function");
+
+ let fn = derive(function(...args) {
+ return f.apply(this, curried.concat(args));
+ }, f);
+ fn.arity = arity(f) - curried.length;
+ return fn;
+};
+exports.partial = partial;
+
+/**
+ * Returns function with implicit currying, which will continue currying until
+ * expected number of argument is collected. Expected number of arguments is
+ * determined by `fn.length`. Using this with variadic functions is stupid,
+ * so don't do it.
+ *
+ * @examples
+ *
+ * var sum = curry(function(a, b) {
+ * return a + b
+ * })
+ * console.log(sum(2, 2)) // 4
+ * console.log(sum(2)(4)) // 6
+ */
+const curry = new function() {
+ const currier = (fn, arity, params) => {
+ // Function either continues to curry arguments or executes function
+ // if desired arguments have being collected.
+ const curried = function(...input) {
+ // Prepend all curried arguments to the given arguments.
+ if (params) input.unshift.apply(input, params);
+ // If expected number of arguments has being collected invoke fn,
+ // othrewise return curried version Otherwise continue curried.
+ return (input.length >= arity) ? fn.apply(this, input) :
+ currier(fn, arity, input);
+ };
+ curried.arity = arity - (params ? params.length : 0);
+
+ return curried;
+ };
+
+ return fn => currier(fn, arity(fn));
+};
+exports.curry = curry;
+
+/**
+ * Returns the composition of a list of functions, where each function consumes
+ * the return value of the function that follows. In math terms, composing the
+ * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ * @example
+ *
+ * var greet = function(name) { return "hi: " + name; };
+ * var exclaim = function(statement) { return statement + "!"; };
+ * var welcome = compose(exclaim, greet);
+ *
+ * welcome('moe'); // => 'hi: moe!'
+ */
+function compose(...lambdas) {
+ return function composed(...args) {
+ let index = lambdas.length;
+ while (0 <= --index)
+ args = [lambdas[index].apply(this, args)];
+
+ return args[0];
+ };
+}
+exports.compose = compose;
+
+/*
+ * Returns the first function passed as an argument to the second,
+ * allowing you to adjust arguments, run code before and after, and
+ * conditionally execute the original function.
+ * @example
+ *
+ * var hello = function(name) { return "hello: " + name; };
+ * hello = wrap(hello, function(f) {
+ * return "before, " + f("moe") + ", after";
+ * });
+ *
+ * hello(); // => 'before, hello: moe, after'
+ */
+const wrap = (f, wrapper) => derive(function wrapped(...args) {
+ return wrapper.apply(this, [f].concat(args));
+}, f);
+exports.wrap = wrap;
+
+/**
+ * Returns the same value that is used as the argument. In math: f(x) = x
+ */
+const identity = value => value;
+exports.identity = identity;
+
+/**
+ * Memoizes a given function by caching the computed result. Useful for
+ * speeding up slow-running computations. If passed an optional hashFunction,
+ * it will be used to compute the hash key for storing the result, based on
+ * the arguments to the original function. The default hashFunction just uses
+ * the first argument to the memoized function as the key.
+ */
+const memoize = (f, hasher) => {
+ let memo = Object.create(null);
+ let cache = new WeakMap();
+ hasher = hasher || identity;
+ return derive(function memoizer(...args) {
+ const key = hasher.apply(this, args);
+ const type = typeof(key);
+ if (key && (type === "object" || type === "function")) {
+ if (!cache.has(key))
+ cache.set(key, f.apply(this, args));
+ return cache.get(key);
+ }
+ else {
+ if (!(key in memo))
+ memo[key] = f.apply(this, args);
+ return memo[key];
+ }
+ }, f);
+};
+exports.memoize = memoize;
+
+/*
+ * Creates a version of the function that can only be called one time. Repeated
+ * calls to the modified function will have no effect, returning the value from
+ * the original call. Useful for initialization functions, instead of having to
+ * set a boolean flag and then check it later.
+ */
+const once = f => {
+ let ran = false, cache;
+ return derive(function(...args) {
+ return ran ? cache : (ran = true, cache = f.apply(this, args));
+ }, f);
+};
+exports.once = once;
+// export cache as once will may be conflicting with event once a lot.
+exports.cache = once;
+
+// Takes a `f` function and returns a function that takes the same
+// arguments as `f`, has the same effects, if any, and returns the
+// opposite truth value.
+const complement = f => derive(function(...args) {
+ return args.length < arity(f) ? complement(partial(f, ...args)) :
+ !f.apply(this, args);
+}, f);
+exports.complement = complement;
+
+// Constructs function that returns `x` no matter what is it
+// invoked with.
+const constant = x => _ => x;
+exports.constant = constant;
+
+// Takes `p` predicate, `consequent` function and an optional
+// `alternate` function and composes function that returns
+// application of arguments over `consequent` if application over
+// `p` is `true` otherwise returns application over `alternate`.
+// If `alternate` is not a function returns `undefined`.
+const when = (p, consequent, alternate) => {
+ if (typeof(alternate) !== "function" && alternate !== void(0))
+ throw TypeError("alternate must be a function");
+ if (typeof(consequent) !== "function")
+ throw TypeError("consequent must be a function");
+
+ return function(...args) {
+ return p.apply(this, args) ?
+ consequent.apply(this, args) :
+ alternate && alternate.apply(this, args);
+ };
+};
+exports.when = when;
+
+// Apply function that behaves as `apply` does in lisp:
+// apply(f, x, [y, z]) => f.apply(f, [x, y, z])
+// apply(f, x) => f.apply(f, [x])
+const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop()));
+exports.apply = apply;
+
+// Returns function identical to given `f` but with flipped order
+// of arguments.
+const flip = f => derive(function(...args) {
+ return f.apply(this, args.reverse());
+}, f);
+exports.flip = flip;
+
+// Takes field `name` and `target` and returns value of that field.
+// If `target` is `null` or `undefined` it would be returned back
+// instead of attempt to access it's field. Function is implicitly
+// curried, this allows accessor function generation by calling it
+// with only `name` argument.
+const field = curry((name, target) =>
+ // Note: Permisive `==` is intentional.
+ target == null ? target : target[name]);
+exports.field = field;
+
+// Takes `.` delimited string representing `path` to a nested field
+// and a `target` to get it from. For convinience function is
+// implicitly curried, there for accessors can be created by invoking
+// it with just a `path` argument.
+const query = curry((path, target) => {
+ const names = path.split(".");
+ const count = names.length;
+ let index = 0;
+ let result = target;
+ // Note: Permisive `!=` is intentional.
+ while (result != null && index < count) {
+ result = result[names[index]];
+ index = index + 1;
+ }
+ return result;
+});
+exports.query = query;
+
+// Takes `Type` (constructor function) and a `value` and returns
+// `true` if `value` is instance of the given `Type`. Function is
+// implicitly curried this allows predicate generation by calling
+// function with just first argument.
+const isInstance = curry((Type, value) => value instanceof Type);
+exports.isInstance = isInstance;
+
+/*
+ * Takes a funtion and returns a wrapped function that returns `this`
+ */
+const chainable = f => derive(function(...args) {
+ f.apply(this, args);
+ return this;
+}, f);
+exports.chainable = chainable;
+
+// Functions takes `expected` and `actual` values and returns `true` if
+// `expected === actual`. Returns curried function if called with less then
+// two arguments.
+//
+// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
+const is = curry((expected, actual) => actual === expected);
+exports.is = is;
+
+const isnt = complement(is);
+exports.isnt = isnt;
diff --git a/toolkit/jetpack/sdk/lang/functional/helpers.js b/toolkit/jetpack/sdk/lang/functional/helpers.js
new file mode 100644
index 000000000..60f4e3300
--- /dev/null
+++ b/toolkit/jetpack/sdk/lang/functional/helpers.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/. */
+
+// Disclaimer: Some of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+}
+
+const arity = f => f.arity || f.length;
+exports.arity = arity;
+
+const name = f => f.displayName || f.name;
+exports.name = name;
+
+const derive = (f, source) => {
+ f.displayName = name(source);
+ f.arity = arity(source);
+ return f;
+};
+exports.derive = derive;
+
+const invoke = (callee, params, self) => callee.apply(self, params);
+exports.invoke = invoke;
diff --git a/toolkit/jetpack/sdk/lang/type.js b/toolkit/jetpack/sdk/lang/type.js
new file mode 100644
index 000000000..b50e6be4c
--- /dev/null
+++ b/toolkit/jetpack/sdk/lang/type.js
@@ -0,0 +1,388 @@
+/* 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"
+};
+
+/**
+ * Returns `true` if `value` is `undefined`.
+ * @examples
+ * var foo; isUndefined(foo); // true
+ * isUndefined(0); // false
+ */
+function isUndefined(value) {
+ return value === undefined;
+}
+exports.isUndefined = isUndefined;
+
+/**
+ * Returns `true` if value is `null`.
+ * @examples
+ * isNull(null); // true
+ * isNull(undefined); // false
+ */
+function isNull(value) {
+ return value === null;
+}
+exports.isNull = isNull;
+
+/**
+ * Returns `true` if value is `null` or `undefined`.
+ * It's equivalent to `== null`, but resolve the ambiguity of the writer
+ * intention, makes clear that he's clearly checking both `null` and `undefined`
+ * values, and it's not a typo for `=== null`.
+ */
+function isNil(value) {
+ return value === null || value === undefined;
+}
+exports.isNil = isNil;
+
+function isBoolean(value) {
+ return typeof value === "boolean";
+}
+exports.isBoolean = isBoolean;
+/**
+ * Returns `true` if value is a string.
+ * @examples
+ * isString("moe"); // true
+ */
+function isString(value) {
+ return typeof value === "string";
+}
+exports.isString = isString;
+
+/**
+ * Returns `true` if `value` is a number.
+ * @examples
+ * isNumber(8.4 * 5); // true
+ */
+function isNumber(value) {
+ return typeof value === "number";
+}
+exports.isNumber = isNumber;
+
+/**
+ * Returns `true` if `value` is a `RegExp`.
+ * @examples
+ * isRegExp(/moe/); // true
+ */
+function isRegExp(value) {
+ return isObject(value) && instanceOf(value, RegExp);
+}
+exports.isRegExp = isRegExp;
+
+/**
+ * Returns true if `value` is a `Date`.
+ * @examples
+ * isDate(new Date()); // true
+ */
+function isDate(value) {
+ return isObject(value) && instanceOf(value, Date);
+}
+exports.isDate = isDate;
+
+/**
+ * Returns true if object is a Function.
+ * @examples
+ * isFunction(function foo(){}) // true
+ */
+function isFunction(value) {
+ return typeof value === "function";
+}
+exports.isFunction = isFunction;
+
+/**
+ * Returns `true` if `value` is an object (please note that `null` is considered
+ * to be an atom and not an object).
+ * @examples
+ * isObject({}) // true
+ * isObject(null) // false
+ */
+function isObject(value) {
+ return typeof value === "object" && value !== null;
+}
+exports.isObject = isObject;
+
+/**
+ * Detect whether a value is a generator.
+ *
+ * @param aValue
+ * The value to identify.
+ * @return A boolean indicating whether the value is a generator.
+ */
+function isGenerator(aValue) {
+ return !!(aValue && aValue.isGenerator && aValue.isGenerator());
+}
+exports.isGenerator = isGenerator;
+
+/**
+ * Returns true if `value` is an Array.
+ * @examples
+ * isArray([1, 2, 3]) // true
+ * isArray({ 0: 'foo', length: 1 }) // false
+ */
+var isArray = Array.isArray;
+exports.isArray = isArray;
+
+/**
+ * Returns `true` if `value` is an Arguments object.
+ * @examples
+ * (function(){ return isArguments(arguments); })(1, 2, 3); // true
+ * isArguments([1,2,3]); // false
+ */
+function isArguments(value) {
+ return Object.prototype.toString.call(value) === "[object Arguments]";
+}
+exports.isArguments = isArguments;
+
+var isMap = value => Object.prototype.toString.call(value) === "[object Map]"
+exports.isMap = isMap;
+
+var isSet = value => Object.prototype.toString.call(value) === "[object Set]"
+exports.isSet = isSet;
+
+/**
+ * Returns true if it is a primitive `value`. (null, undefined, number,
+ * boolean, string)
+ * @examples
+ * isPrimitive(3) // true
+ * isPrimitive('foo') // true
+ * isPrimitive({ bar: 3 }) // false
+ */
+function isPrimitive(value) {
+ return !isFunction(value) && !isObject(value);
+}
+exports.isPrimitive = isPrimitive;
+
+/**
+ * Returns `true` if given `object` is flat (it is direct decedent of
+ * `Object.prototype` or `null`).
+ * @examples
+ * isFlat({}) // true
+ * isFlat(new Type()) // false
+ */
+function isFlat(object) {
+ return isObject(object) && (isNull(Object.getPrototypeOf(object)) ||
+ isNull(Object.getPrototypeOf(
+ Object.getPrototypeOf(object))));
+}
+exports.isFlat = isFlat;
+
+/**
+ * Returns `true` if object contains no values.
+ */
+function isEmpty(object) {
+ if (isObject(object)) {
+ for (var key in object)
+ return false;
+ return true;
+ }
+ return false;
+}
+exports.isEmpty = isEmpty;
+
+/**
+ * Returns `true` if `value` is an array / flat object containing only atomic
+ * values and other flat objects.
+ */
+function isJSON(value, visited) {
+ // Adding value to array of visited values.
+ (visited || (visited = [])).push(value);
+ // If `value` is an atom return `true` cause it's valid JSON.
+ return isPrimitive(value) ||
+ // If `value` is an array of JSON values that has not been visited
+ // yet.
+ (isArray(value) && value.every(function(element) {
+ return isJSON(element, visited);
+ })) ||
+ // If `value` is a plain object containing properties with a JSON
+ // values it's a valid JSON.
+ (isFlat(value) && Object.keys(value).every(function(key) {
+ var $ = Object.getOwnPropertyDescriptor(value, key);
+ // Check every proprety of a plain object to verify that
+ // it's neither getter nor setter, but a JSON value, that
+ // has not been visited yet.
+ return ((!isObject($.value) || !~visited.indexOf($.value)) &&
+ !('get' in $) && !('set' in $) &&
+ isJSON($.value, visited));
+ }));
+}
+exports.isJSON = function (value) {
+ return isJSON(value);
+};
+
+/**
+ * Returns `true` if `value` is JSONable
+ */
+const isJSONable = (value) => {
+ try {
+ JSON.parse(JSON.stringify(value));
+ }
+ catch (e) {
+ return false;
+ }
+ return true;
+};
+exports.isJSONable = isJSONable;
+
+/**
+ * Returns if `value` is an instance of a given `Type`. This is exactly same as
+ * `value instanceof Type` with a difference that `Type` can be from a scope
+ * that has a different top level object. (Like in case where `Type` is a
+ * function from different iframe / jetpack module / sandbox).
+ */
+function instanceOf(value, Type) {
+ var isConstructorNameSame;
+ var isConstructorSourceSame;
+
+ // If `instanceof` returned `true` we know result right away.
+ var isInstanceOf = value instanceof Type;
+
+ // If `instanceof` returned `false` we do ducktype check since `Type` may be
+ // from a different sandbox. If a constructor of the `value` or a constructor
+ // of the value's prototype has same name and source we assume that it's an
+ // instance of the Type.
+ if (!isInstanceOf && value) {
+ isConstructorNameSame = value.constructor.name === Type.name;
+ isConstructorSourceSame = String(value.constructor) == String(Type);
+ isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
+ instanceOf(Object.getPrototypeOf(value), Type);
+ }
+ return isInstanceOf;
+}
+exports.instanceOf = instanceOf;
+
+/**
+ * Function returns textual representation of a value passed to it. Function
+ * takes additional `indent` argument that is used for indentation. Also
+ * optional `limit` argument may be passed to limit amount of detail returned.
+ * @param {Object} value
+ * @param {String} [indent=" "]
+ * @param {Number} [limit]
+ */
+function source(value, indent, limit, offset, visited) {
+ var result;
+ var names;
+ var nestingIndex;
+ var isCompact = !isUndefined(limit);
+
+ indent = indent || " ";
+ offset = (offset || "");
+ result = "";
+ visited = visited || [];
+
+ if (isUndefined(value)) {
+ result += "undefined";
+ }
+ else if (isNull(value)) {
+ result += "null";
+ }
+ else if (isString(value)) {
+ result += '"' + value + '"';
+ }
+ else if (isFunction(value)) {
+ value = String(value).split("\n");
+ if (isCompact && value.length > 2) {
+ value = value.splice(0, 2);
+ value.push("...}");
+ }
+ result += value.join("\n" + offset);
+ }
+ else if (isArray(value)) {
+ if ((nestingIndex = (visited.indexOf(value) + 1))) {
+ result = "#" + nestingIndex + "#";
+ }
+ else {
+ visited.push(value);
+
+ if (isCompact)
+ value = value.slice(0, limit);
+
+ result += "[\n";
+ result += value.map(function(value) {
+ return offset + indent + source(value, indent, limit, offset + indent,
+ visited);
+ }).join(",\n");
+ result += isCompact && value.length > limit ?
+ ",\n" + offset + "...]" : "\n" + offset + "]";
+ }
+ }
+ else if (isObject(value)) {
+ if ((nestingIndex = (visited.indexOf(value) + 1))) {
+ result = "#" + nestingIndex + "#"
+ }
+ else {
+ visited.push(value)
+
+ names = Object.keys(value);
+
+ result += "{ // " + value + "\n";
+ result += (isCompact ? names.slice(0, limit) : names).map(function(name) {
+ var _limit = isCompact ? limit - 1 : limit;
+ var descriptor = Object.getOwnPropertyDescriptor(value, name);
+ var result = offset + indent + "// ";
+ var accessor;
+ if (0 <= name.indexOf(" "))
+ name = '"' + name + '"';
+
+ if (descriptor.writable)
+ result += "writable ";
+ if (descriptor.configurable)
+ result += "configurable ";
+ if (descriptor.enumerable)
+ result += "enumerable ";
+
+ result += "\n";
+ if ("value" in descriptor) {
+ result += offset + indent + name + ": ";
+ result += source(descriptor.value, indent, _limit, indent + offset,
+ visited);
+ }
+ else {
+
+ if (descriptor.get) {
+ result += offset + indent + "get " + name + " ";
+ accessor = source(descriptor.get, indent, _limit, indent + offset,
+ visited);
+ result += accessor.substr(accessor.indexOf("{"));
+ }
+
+ if (descriptor.set) {
+ result += offset + indent + "set " + name + " ";
+ accessor = source(descriptor.set, indent, _limit, indent + offset,
+ visited);
+ result += accessor.substr(accessor.indexOf("{"));
+ }
+ }
+ return result;
+ }).join(",\n");
+
+ if (isCompact) {
+ if (names.length > limit && limit > 0) {
+ result += ",\n" + offset + indent + "//...";
+ }
+ }
+ else {
+ if (names.length)
+ result += ",";
+
+ result += "\n" + offset + indent + '"__proto__": ';
+ result += source(Object.getPrototypeOf(value), indent, 0,
+ offset + indent);
+ }
+
+ result += "\n" + offset + "}";
+ }
+ }
+ else {
+ result += String(value);
+ }
+ return result;
+}
+exports.source = function (value, indentation, limit) {
+ return source(value, indentation, limit);
+};
diff --git a/toolkit/jetpack/sdk/lang/weak-set.js b/toolkit/jetpack/sdk/lang/weak-set.js
new file mode 100644
index 000000000..8972602a5
--- /dev/null
+++ b/toolkit/jetpack/sdk/lang/weak-set.js
@@ -0,0 +1,75 @@
+/* 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/. */
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+"use strict";
+
+const { Cu } = require("chrome");
+
+function makeGetterFor(Type) {
+ let cache = new WeakMap();
+
+ return {
+ getFor(target) {
+ if (!cache.has(target))
+ cache.set(target, new Type());
+
+ return cache.get(target);
+ },
+ clearFor(target) {
+ return cache.delete(target)
+ }
+ }
+}
+
+var {getFor: getLookupFor, clearFor: clearLookupFor} = makeGetterFor(WeakMap);
+var {getFor: getRefsFor, clearFor: clearRefsFor} = makeGetterFor(Set);
+
+function add(target, value) {
+ if (has(target, value))
+ return;
+
+ getLookupFor(target).set(value, true);
+ getRefsFor(target).add(Cu.getWeakReference(value));
+}
+exports.add = add;
+
+function remove(target, value) {
+ getLookupFor(target).delete(value);
+}
+exports.remove = remove;
+
+function has(target, value) {
+ return getLookupFor(target).has(value);
+}
+exports.has = has;
+
+function clear(target) {
+ clearLookupFor(target);
+ clearRefsFor(target);
+}
+exports.clear = clear;
+
+function iterator(target) {
+ let refs = getRefsFor(target);
+
+ for (let ref of refs) {
+ let value = ref.get();
+
+ // If `value` is already gc'ed, it would be `null`.
+ // The `has` function is using a WeakMap as lookup table, so passing `null`
+ // would raise an exception because WeakMap accepts as value only non-null
+ // object.
+ // Plus, if `value` is already gc'ed, we do not have to take it in account
+ // during the iteration, and remove it from the references.
+ if (value !== null && has(target, value))
+ yield value;
+ else
+ refs.delete(ref);
+ }
+}
+exports.iterator = iterator;