From ac46df8daea09899ce30dc8fd70986e258c746bf Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 9 Feb 2018 06:46:43 -0500 Subject: Move Add-on SDK source to toolkit/jetpack --- toolkit/jetpack/sdk/lang/functional.js | 47 +++ toolkit/jetpack/sdk/lang/functional/concurrent.js | 110 ++++++ toolkit/jetpack/sdk/lang/functional/core.js | 290 ++++++++++++++++ toolkit/jetpack/sdk/lang/functional/helpers.js | 29 ++ toolkit/jetpack/sdk/lang/type.js | 388 ++++++++++++++++++++++ toolkit/jetpack/sdk/lang/weak-set.js | 75 +++++ 6 files changed, 939 insertions(+) create mode 100644 toolkit/jetpack/sdk/lang/functional.js create mode 100644 toolkit/jetpack/sdk/lang/functional/concurrent.js create mode 100644 toolkit/jetpack/sdk/lang/functional/core.js create mode 100644 toolkit/jetpack/sdk/lang/functional/helpers.js create mode 100644 toolkit/jetpack/sdk/lang/type.js create mode 100644 toolkit/jetpack/sdk/lang/weak-set.js (limited to 'toolkit/jetpack/sdk/lang') 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; -- cgit v1.2.3