diff options
Diffstat (limited to 'devtools/client/shared/vendor/seamless-immutable.js')
-rw-r--r-- | devtools/client/shared/vendor/seamless-immutable.js | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/devtools/client/shared/vendor/seamless-immutable.js b/devtools/client/shared/vendor/seamless-immutable.js new file mode 100644 index 000000000..ef893f384 --- /dev/null +++ b/devtools/client/shared/vendor/seamless-immutable.js @@ -0,0 +1,392 @@ +(function(){ + "use strict"; + + function addPropertyTo(target, methodName, value) { + Object.defineProperty(target, methodName, { + enumerable: false, + configurable: false, + writable: false, + value: value + }); + } + + function banProperty(target, methodName) { + addPropertyTo(target, methodName, function() { + throw new ImmutableError("The " + methodName + + " method cannot be invoked on an Immutable data structure."); + }); + } + + var immutabilityTag = "__immutable_invariants_hold"; + + function addImmutabilityTag(target) { + addPropertyTo(target, immutabilityTag, true); + } + + function isImmutable(target) { + if (typeof target === "object") { + return target === null || target.hasOwnProperty(immutabilityTag); + } else { + // In JavaScript, only objects are even potentially mutable. + // strings, numbers, null, and undefined are all naturally immutable. + return true; + } + } + + function isMergableObject(target) { + return target !== null && typeof target === "object" && !(target instanceof Array) && !(target instanceof Date); + } + + var mutatingObjectMethods = [ + "setPrototypeOf" + ]; + + var nonMutatingObjectMethods = [ + "keys" + ]; + + var mutatingArrayMethods = mutatingObjectMethods.concat([ + "push", "pop", "sort", "splice", "shift", "unshift", "reverse" + ]); + + var nonMutatingArrayMethods = nonMutatingObjectMethods.concat([ + "map", "filter", "slice", "concat", "reduce", "reduceRight" + ]); + + function ImmutableError(message) { + var err = new Error(message); + err.__proto__ = ImmutableError; + + return err; + } + ImmutableError.prototype = Error.prototype; + + function makeImmutable(obj, bannedMethods) { + // Tag it so we can quickly tell it's immutable later. + addImmutabilityTag(obj); + + if ("development" === "development") { + // Make all mutating methods throw exceptions. + for (var index in bannedMethods) { + if (bannedMethods.hasOwnProperty(index)) { + banProperty(obj, bannedMethods[index]); + } + } + + // Freeze it and return it. + Object.freeze(obj); + } + + return obj; + } + + function makeMethodReturnImmutable(obj, methodName) { + var currentMethod = obj[methodName]; + + addPropertyTo(obj, methodName, function() { + return Immutable(currentMethod.apply(obj, arguments)); + }); + } + + function makeImmutableArray(array) { + // Don't change their implementations, but wrap these functions to make sure + // they always return an immutable value. + for (var index in nonMutatingArrayMethods) { + if (nonMutatingArrayMethods.hasOwnProperty(index)) { + var methodName = nonMutatingArrayMethods[index]; + makeMethodReturnImmutable(array, methodName); + } + } + + addPropertyTo(array, "flatMap", flatMap); + addPropertyTo(array, "asObject", asObject); + addPropertyTo(array, "asMutable", asMutableArray); + + for(var i = 0, length = array.length; i < length; i++) { + array[i] = Immutable(array[i]); + } + + return makeImmutable(array, mutatingArrayMethods); + } + + /** + * Effectively performs a map() over the elements in the array, using the + * provided iterator, except that whenever the iterator returns an array, that + * array's elements are added to the final result instead of the array itself. + * + * @param {function} iterator - The iterator function that will be invoked on each element in the array. It will receive three arguments: the current value, the current index, and the current object. + */ + function flatMap(iterator) { + // Calling .flatMap() with no arguments is a no-op. Don't bother cloning. + if (arguments.length === 0) { + return this; + } + + var result = [], + length = this.length, + index; + + for (index = 0; index < length; index++) { + var iteratorResult = iterator(this[index], index, this); + + if (iteratorResult instanceof Array) { + // Concatenate Array results into the return value we're building up. + result.push.apply(result, iteratorResult); + } else { + // Handle non-Array results the same way map() does. + result.push(iteratorResult); + } + } + + return makeImmutableArray(result); + } + + /** + * Returns an Immutable copy of the object without the given keys included. + * + * @param {array} keysToRemove - A list of strings representing the keys to exclude in the return value. Instead of providing a single array, this method can also be called by passing multiple strings as separate arguments. + */ + function without(keysToRemove) { + // Calling .without() with no arguments is a no-op. Don't bother cloning. + if (arguments.length === 0) { + return this; + } + + // If we weren't given an array, use the arguments list. + if (!(keysToRemove instanceof Array)) { + keysToRemove = Array.prototype.slice.call(arguments); + } + + var result = this.instantiateEmptyObject(); + + for (var key in this) { + if (this.hasOwnProperty(key) && (keysToRemove.indexOf(key) === -1)) { + result[key] = this[key]; + } + } + + return makeImmutableObject(result, + {instantiateEmptyObject: this.instantiateEmptyObject}); + } + + function asMutableArray(opts) { + var result = [], i, length; + + if(opts && opts.deep) { + for(i = 0, length = this.length; i < length; i++) { + result.push( asDeepMutable(this[i]) ); + } + } else { + for(i = 0, length = this.length; i < length; i++) { + result.push(this[i]); + } + } + + return result; + } + + /** + * Effectively performs a [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) over the elements in the array, expecting that the iterator function + * will return an array of two elements - the first representing a key, the other + * a value. Then returns an Immutable Object constructed of those keys and values. + * + * @param {function} iterator - A function which should return an array of two elements - the first representing the desired key, the other the desired value. + */ + function asObject(iterator) { + // If no iterator was provided, assume the identity function + // (suggesting this array is already a list of key/value pairs.) + if (typeof iterator !== "function") { + iterator = function(value) { return value; }; + } + + var result = {}, + length = this.length, + index; + + for (index = 0; index < length; index++) { + var pair = iterator(this[index], index, this), + key = pair[0], + value = pair[1]; + + result[key] = value; + } + + return makeImmutableObject(result); + } + + function asDeepMutable(obj) { + if(!obj || !obj.hasOwnProperty(immutabilityTag) || obj instanceof Date) { return obj; } + return obj.asMutable({deep: true}); + } + + function quickCopy(src, dest) { + for (var key in src) { + if (src.hasOwnProperty(key)) { + dest[key] = src[key]; + } + } + + return dest; + } + + /** + * Returns an Immutable Object containing the properties and values of both + * this object and the provided object, prioritizing the provided object's + * values whenever the same key is present in both objects. + * + * @param {object} other - The other object to merge. Multiple objects can be passed as an array. In such a case, the later an object appears in that list, the higher its priority. + * @param {object} config - Optional config object that contains settings. Supported settings are: {deep: true} for deep merge and {merger: mergerFunc} where mergerFunc is a function + * that takes a property from both objects. If anything is returned it overrides the normal merge behaviour. + */ + function merge(other, config) { + // Calling .merge() with no arguments is a no-op. Don't bother cloning. + if (arguments.length === 0) { + return this; + } + + if (other === null || (typeof other !== "object")) { + throw new TypeError("Immutable#merge can only be invoked with objects or arrays, not " + JSON.stringify(other)); + } + + var anyChanges = false, + result = quickCopy(this, this.instantiateEmptyObject()), // A shallow clone of this object. + receivedArray = (other instanceof Array), + deep = config && config.deep, + merger = config && config.merger, + key; + + // Use the given key to extract a value from the given object, then place + // that value in the result object under the same key. If that resulted + // in a change from this object's value at that key, set anyChanges = true. + function addToResult(currentObj, otherObj, key) { + var immutableValue = Immutable(otherObj[key]); + var mergerResult = merger && merger(currentObj[key], immutableValue, config); + if (merger && mergerResult && mergerResult === currentObj[key]) return; + + anyChanges = anyChanges || + mergerResult !== undefined || + (!currentObj.hasOwnProperty(key) || + ((immutableValue !== currentObj[key]) && + // Avoid false positives due to (NaN !== NaN) evaluating to true + (immutableValue === immutableValue))); + + if (mergerResult) { + result[key] = mergerResult; + } else if (deep && isMergableObject(currentObj[key]) && isMergableObject(immutableValue)) { + result[key] = currentObj[key].merge(immutableValue, config); + } else { + result[key] = immutableValue; + } + } + + // Achieve prioritization by overriding previous values that get in the way. + if (!receivedArray) { + // The most common use case: just merge one object into the existing one. + for (key in other) { + if (other.hasOwnProperty(key)) { + addToResult(this, other, key); + } + } + } else { + // We also accept an Array + for (var index=0; index < other.length; index++) { + var otherFromArray = other[index]; + + for (key in otherFromArray) { + if (otherFromArray.hasOwnProperty(key)) { + addToResult(this, otherFromArray, key); + } + } + } + } + + if (anyChanges) { + return makeImmutableObject(result, + {instantiateEmptyObject: this.instantiateEmptyObject}); + } else { + return this; + } + } + + function asMutableObject(opts) { + var result = this.instantiateEmptyObject(), key; + + if(opts && opts.deep) { + for (key in this) { + if (this.hasOwnProperty(key)) { + result[key] = asDeepMutable(this[key]); + } + } + } else { + for (key in this) { + if (this.hasOwnProperty(key)) { + result[key] = this[key]; + } + } + } + + return result; + } + + // Creates plain object to be used for cloning + function instantiatePlainObject() { + return {}; + } + + // Finalizes an object with immutable methods, freezes it, and returns it. + function makeImmutableObject(obj, options) { + var instantiateEmptyObject = + (options && options.instantiateEmptyObject) ? + options.instantiateEmptyObject : instantiatePlainObject; + + addPropertyTo(obj, "merge", merge); + addPropertyTo(obj, "without", without); + addPropertyTo(obj, "asMutable", asMutableObject); + addPropertyTo(obj, "instantiateEmptyObject", instantiateEmptyObject); + + return makeImmutable(obj, mutatingObjectMethods); + } + + function Immutable(obj, options) { + if (isImmutable(obj)) { + return obj; + } else if (obj instanceof Array) { + return makeImmutableArray(obj.slice()); + } else if (obj instanceof Date) { + return makeImmutable(new Date(obj.getTime())); + } else { + // Don't freeze the object we were given; make a clone and use that. + var prototype = options && options.prototype; + var instantiateEmptyObject = + (!prototype || prototype === Object.prototype) ? + instantiatePlainObject : (function() { return Object.create(prototype); }); + var clone = instantiateEmptyObject(); + + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + clone[key] = Immutable(obj[key]); + } + } + + return makeImmutableObject(clone, + {instantiateEmptyObject: instantiateEmptyObject}); + } + } + + // Export the library + Immutable.isImmutable = isImmutable; + Immutable.ImmutableError = ImmutableError; + + Object.freeze(Immutable); + + /* istanbul ignore if */ + if (typeof module === "object") { + module.exports = Immutable; + } else if (typeof exports === "object") { + exports.Immutable = Immutable; + } else if (typeof window === "object") { + window.Immutable = Immutable; + } else if (typeof global === "object") { + global.Immutable = Immutable; + } +})(); |