addon-sdk/source/lib/sdk/deprecated/light-traits.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 module.metadata = {
     8   "stability": "deprecated"
     9 };
    11 // `var` is being used in the module in order to make it reusable in
    12 // environments in which `let` is not yet supported.
    14 // Shortcut to `Object.prototype.hasOwnProperty.call`.
    15 // owns(object, name) would be the same as
    16 // Object.prototype.hasOwnProperty.call(object, name);
    17 var owns = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
    19 /**
    20  * Whether or not given property descriptors are equivalent. They are
    21  * equivalent either if both are marked as 'conflict' or 'required' property
    22  * or if all the properties of descriptors are equal.
    23  * @param {Object} actual
    24  * @param {Object} expected
    25  */
    26 function equivalentDescriptors(actual, expected) {
    27   return (actual.conflict && expected.conflict) ||
    28          (actual.required && expected.required) ||
    29          equalDescriptors(actual, expected);
    30 }
    31 /**
    32  * Whether or not given property descriptors define equal properties.
    33  */
    34 function equalDescriptors(actual, expected) {
    35   return actual.get === expected.get &&
    36          actual.set === expected.set &&
    37          actual.value === expected.value &&
    38          !!actual.enumerable === !!expected.enumerable &&
    39          !!actual.configurable === !!expected.configurable &&
    40          !!actual.writable === !!expected.writable;
    41 }
    43 // Utilities that throwing exceptions for a properties that are marked
    44 // as "required" or "conflict" properties.
    45 function throwConflictPropertyError(name) {
    46   throw new Error("Remaining conflicting property: `" + name + "`");
    47 }
    48 function throwRequiredPropertyError(name) {
    49   throw new Error("Missing required property: `" + name + "`");
    50 }
    52 /**
    53  * Generates custom **required** property descriptor. Descriptor contains
    54  * non-standard property `required` that is equal to `true`.
    55  * @param {String} name
    56  *    property name to generate descriptor for.
    57  * @returns {Object}
    58  *    custom property descriptor
    59  */
    60 function RequiredPropertyDescriptor(name) {
    61   // Creating function by binding first argument to a property `name` on the
    62   // `throwConflictPropertyError` function. Created function is used as a
    63   // getter & setter of the created property descriptor. This way we ensure
    64   // that we throw exception late (on property access) if object with
    65   // `required` property was instantiated using built-in `Object.create`.
    66   var accessor = throwRequiredPropertyError.bind(null, name);
    67   return { get: accessor, set: accessor, required: true };
    68 }
    70 /**
    71  * Generates custom **conflicting** property descriptor. Descriptor contains
    72  * non-standard property `conflict` that is equal to `true`.
    73  * @param {String} name
    74  *    property name to generate descriptor for.
    75  * @returns {Object}
    76  *    custom property descriptor
    77  */
    78 function ConflictPropertyDescriptor(name) {
    79   // For details see `RequiredPropertyDescriptor` since idea is same.
    80   var accessor = throwConflictPropertyError.bind(null, name);
    81   return { get: accessor, set: accessor, conflict: true };
    82 }
    84 /**
    85  * Tests if property is marked as `required` property.
    86  */
    87 function isRequiredProperty(object, name) {
    88   return !!object[name].required;
    89 }
    91 /**
    92  * Tests if property is marked as `conflict` property.
    93  */
    94 function isConflictProperty(object, name) {
    95   return !!object[name].conflict;
    96 }
    98 /**
    99  * Function tests whether or not method of the `source` object with a given
   100  * `name` is inherited from `Object.prototype`.
   101  */
   102 function isBuiltInMethod(name, source) {
   103   var target = Object.prototype[name];
   105   // If methods are equal then we know it's `true`.
   106   return target == source ||
   107   // If `source` object comes form a different sandbox `==` will evaluate
   108   // to `false`, in that case we check if functions names and sources match.
   109          (String(target) === String(source) && target.name === source.name);
   110 }
   112 /**
   113  * Function overrides `toString` and `constructor` methods of a given `target`
   114  * object with a same-named methods of a given `source` if methods of `target`
   115  * object are inherited / copied from `Object.prototype`.
   116  * @see create
   117  */
   118 function overrideBuiltInMethods(target, source) {
   119   if (isBuiltInMethod("toString", target.toString)) {
   120     Object.defineProperty(target, "toString",  {
   121       value: source.toString,
   122       configurable: true,
   123       enumerable: false
   124     });
   125   }
   127   if (isBuiltInMethod("constructor", target.constructor)) {
   128     Object.defineProperty(target, "constructor", {
   129       value: source.constructor,
   130       configurable: true,
   131       enumerable: false
   132     });
   133   }
   134 }
   136 /**
   137  * Composes new trait with the same own properties as the original trait,
   138  * except that all property names appearing in the first argument are replaced
   139  * by "required" property descriptors.
   140  * @param {String[]} keys
   141  *    Array of strings property names.
   142  * @param {Object} trait
   143  *    A trait some properties of which should be excluded.
   144  * @returns {Object}
   145  * @example
   146  *    var newTrait = exclude(["name", ...], trait)
   147  */
   148 function exclude(names, trait) {
   149   var map = {};
   151   Object.keys(trait).forEach(function(name) {
   153     // If property is not excluded (the array of names does not contain it),
   154     // or it is a "required" property, copy it to the property descriptor `map`
   155     // that will be used for creation of resulting trait.
   156     if (!~names.indexOf(name) || isRequiredProperty(trait, name))
   157       map[name] = { value: trait[name], enumerable: true };
   159     // For all the `names` in the exclude name array we create required
   160     // property descriptors and copy them to the `map`.
   161     else
   162       map[name] = { value: RequiredPropertyDescriptor(name), enumerable: true };
   163   });
   165   return Object.create(Trait.prototype, map);
   166 }
   168 /**
   169  * Composes new instance of `Trait` with a properties of a given `trait`,
   170  * except that all properties whose name is an own property of `renames` will
   171  * be renamed to `renames[name]` and a `"required"` property for name will be
   172  * added instead.
   173  *
   174  * For each renamed property, a required property is generated. If
   175  * the `renames` map two properties to the same name, a conflict is generated.
   176  * If the `renames` map a property to an existing unrenamed property, a
   177  * conflict is generated.
   178  *
   179  * @param {Object} renames
   180  *    An object whose own properties serve as a mapping from old names to new
   181  *    names.
   182  * @param {Object} trait
   183  *    A new trait with renamed properties.
   184  * @returns {Object}
   185  * @example
   186  *
   187  *    // Return trait with `bar` property equal to `trait.foo` and with
   188  *    // `foo` and `baz` "required" properties.
   189  *    var renamedTrait = rename({ foo: "bar", baz: null }), trait);
   190  *
   191  *    // t1 and t2 are equivalent traits
   192  *    var t1 = rename({a: "b"}, t);
   193  *    var t2 = compose(exclude(["a"], t), { a: { required: true }, b: t[a] });
   194  */
   195 function rename(renames, trait) {
   196   var map = {};
   198   // Loop over all the properties of the given `trait` and copy them to a
   199   // property descriptor `map` that will be used for the creation of the
   200   // resulting trait.  Also, rename properties in the `map` as specified by
   201   // `renames`.
   202   Object.keys(trait).forEach(function(name) {
   203     var alias;
   205     // If the property is in the `renames` map, and it isn't a "required"
   206     // property (which should never need to be aliased because "required"
   207     // properties never conflict), then we must try to rename it.
   208     if (owns(renames, name) && !isRequiredProperty(trait, name)) {
   209       alias = renames[name];
   211       // If the `map` already has the `alias`, and it isn't a "required"
   212       // property, that means the `alias` conflicts with an existing name for a
   213       // provided trait (that can happen if >=2 properties are aliased to the
   214       // same name). In this case we mark it as a conflicting property.
   215       // Otherwise, everything is fine, and we copy property with an `alias`
   216       // name.
   217       if (owns(map, alias) && !map[alias].value.required) {
   218         map[alias] = {
   219           value: ConflictPropertyDescriptor(alias),
   220           enumerable: true
   221         };
   222       }
   223       else {
   224         map[alias] = {
   225           value: trait[name],
   226           enumerable: true
   227         };
   228       }
   230       // Regardless of whether or not the rename was successful, we check to
   231       // see if the original `name` exists in the map (such a property
   232       // could exist if previous another property was aliased to this `name`).
   233       // If it isn't, we mark it as "required", to make sure the caller
   234       // provides another value for the old name, which methods of the trait
   235       // might continue to reference.
   236       if (!owns(map, name)) {
   237         map[name] = {
   238           value: RequiredPropertyDescriptor(name),
   239           enumerable: true
   240         };
   241       }
   242     }
   244     // Otherwise, either the property isn't in the `renames` map (thus the
   245     // caller is not trying to rename it) or it is a "required" property.
   246     // Either way, we don't have to alias the property, we just have to copy it
   247     // to the map.
   248     else {
   249       // The property isn't in the map yet, so we copy it over.
   250       if (!owns(map, name)) {
   251         map[name] = { value: trait[name], enumerable: true };
   252       }
   254       // The property is already in the map (that means another property was
   255       // aliased with this `name`, which creates a conflict if the property is
   256       // not marked as "required"), so we have to mark it as a "conflict"
   257       // property.
   258       else if (!isRequiredProperty(trait, name)) {
   259         map[name] = {
   260           value: ConflictPropertyDescriptor(name),
   261           enumerable: true
   262         };
   263       }
   264     }
   265   });
   266   return Object.create(Trait.prototype, map);
   267 }
   269 /**
   270  * Composes new resolved trait, with all the same properties as the original
   271  * `trait`, except that all properties whose name is an own property of
   272  * `resolutions` will be renamed to `resolutions[name]`.
   273  *
   274  * If `resolutions[name]` is `null`, the value is mapped to a property
   275  * descriptor that is marked as a "required" property.
   276  */
   277 function resolve(resolutions, trait) {
   278     var renames = {};
   279     var exclusions = [];
   281     // Go through each mapping in `resolutions` object and distribute it either
   282     // to `renames` or `exclusions`.
   283     Object.keys(resolutions).forEach(function(name) {
   285       // If `resolutions[name]` is a truthy value then it's a mapping old -> new
   286       // so we copy it to `renames` map.
   287       if (resolutions[name])
   288         renames[name] = resolutions[name];
   290       // Otherwise it's not a mapping but an exclusion instead in which case we
   291       // add it to the `exclusions` array.
   292       else
   293         exclusions.push(name);
   294     });
   296     // First `exclude` **then** `rename` and order is important since
   297     // `exclude` and `rename` are not associative.
   298     return rename(renames, exclude(exclusions, trait));
   299 }
   301 /**
   302  * Create a Trait (a custom property descriptor map) that represents the given
   303  * `object`'s own properties. Property descriptor map is a "custom", because it
   304  * inherits from `Trait.prototype` and it's property descriptors may contain
   305  * two attributes that is not part of the ES5 specification:
   306  *
   307  *  - "required" (this property must be provided by another trait
   308  *    before an instance of this trait can be created)
   309  *  - "conflict" (when the trait is composed with another trait,
   310  *    a unique value for this property is provided by two or more traits)
   311  *
   312  * Data properties bound to the `Trait.required` singleton exported by
   313  * this module will be marked as "required" properties.
   314  *
   315  * @param {Object} object
   316  *    Map of properties to compose trait from.
   317  * @returns {Trait}
   318  *    Trait / Property descriptor map containing all the own properties of the
   319  *    given argument.
   320  */
   321 function trait(object) {
   322   var map;
   323   var trait = object;
   325   if (!(object instanceof Trait)) {
   326     // If the passed `object` is not already an instance of `Trait`, we create
   327     // a property descriptor `map` containing descriptors for the own properties
   328     // of the given `object`.  `map` is then used to create a `Trait` instance
   329     // after all properties are mapped.  Note that we can't create a trait and
   330     // then just copy properties into it since that will fail for inherited
   331     // read-only properties.
   332     map = {};
   334     // Each own property of the given `object` is mapped to a data property
   335     // whose value is a property descriptor.
   336     Object.keys(object).forEach(function (name) {
   338       // If property of an `object` is equal to a `Trait.required`, it means
   339       // that it was marked as "required" property, in which case we map it
   340       // to "required" property.
   341       if (Trait.required ==
   342           Object.getOwnPropertyDescriptor(object, name).value) {
   343         map[name] = {
   344           value: RequiredPropertyDescriptor(name),
   345           enumerable: true
   346         };
   347       }
   348       // Otherwise property is mapped to it's property descriptor.
   349       else {
   350         map[name] = {
   351           value: Object.getOwnPropertyDescriptor(object, name),
   352           enumerable: true
   353         };
   354       }
   355     });
   357     trait = Object.create(Trait.prototype, map);
   358   }
   359   return trait;
   360 }
   362 /**
   363  * Compose a property descriptor map that inherits from `Trait.prototype` and
   364  * contains property descriptors for all the own properties of the passed
   365  * traits.
   366  *
   367  * If two or more traits have own properties with the same name, the returned
   368  * trait will contain a "conflict" property for that name. Composition is a
   369  * commutative and associative operation, and the order of its arguments is
   370  * irrelevant.
   371  */
   372 function compose(trait1, trait2/*, ...*/) {
   373   // Create a new property descriptor `map` to which all the own properties
   374   // of the passed traits are copied.  This map will be used to create a `Trait`
   375   // instance that will be the result of this composition.
   376   var map = {};
   378   // Properties of each passed trait are copied to the composition.
   379   Array.prototype.forEach.call(arguments, function(trait) {
   380     // Copying each property of the given trait.
   381     Object.keys(trait).forEach(function(name) {
   383       // If `map` already owns a property with the `name` and it is not
   384       // marked "required".
   385       if (owns(map, name) && !map[name].value.required) {
   387         // If the source trait's property with the `name` is marked as
   388         // "required", we do nothing, as the requirement was already resolved
   389         // by a property in the `map` (because it already contains a
   390         // non-required property with that `name`).  But if properties are just
   391         // different, we have a name clash and we substitute it with a property
   392         // that is marked "conflict".
   393         if (!isRequiredProperty(trait, name) &&
   394             !equivalentDescriptors(map[name].value, trait[name])
   395         ) {
   396           map[name] = {
   397             value: ConflictPropertyDescriptor(name),
   398             enumerable: true
   399           };
   400         }
   401       }
   403       // Otherwise, the `map` does not have an own property with the `name`, or
   404       // it is marked "required".  Either way, the trait's property is copied to
   405       // the map (if the property of the `map` is marked "required", it is going
   406       // to be resolved by the property that is being copied).
   407       else {
   408         map[name] = { value: trait[name], enumerable: true };
   409       }
   410     });
   411   });
   413   return Object.create(Trait.prototype, map);
   414 }
   416 /**
   417  *  `defineProperties` is like `Object.defineProperties`, except that it
   418  *  ensures that:
   419  *    - An exception is thrown if any property in a given `properties` map
   420  *      is marked as "required" property and same named property is not
   421  *      found in a given `prototype`.
   422  *    - An exception is thrown if any property in a given `properties` map
   423  *      is marked as "conflict" property.
   424  * @param {Object} object
   425  *    Object to define properties on.
   426  * @param {Object} properties
   427  *    Properties descriptor map.
   428  * @returns {Object}
   429  *    `object` that was passed as a first argument.
   430  */
   431 function defineProperties(object, properties) {
   433   // Create a map into which we will copy each verified property from the given
   434   // `properties` description map. We use it to verify that none of the
   435   // provided properties is marked as a "conflict" property and that all
   436   // "required" properties are resolved by a property of an `object`, so we
   437   // can throw an exception before mutating object if that isn't the case.
   438   var verifiedProperties = {};
   440   // Coping each property from a given `properties` descriptor map to a
   441   // verified map of property descriptors.
   442   Object.keys(properties).forEach(function(name) {
   444     // If property is marked as "required" property and we don't have a same
   445     // named property in a given `object` we throw an exception. If `object`
   446     // has same named property just skip this property since required property
   447     // is was inherited and there for requirement was satisfied.
   448     if (isRequiredProperty(properties, name)) {
   449       if (!(name in object))
   450         throwRequiredPropertyError(name);
   451     }
   453     // If property is marked as "conflict" property we throw an exception.
   454     else if (isConflictProperty(properties, name)) {
   455       throwConflictPropertyError(name);
   456     }
   458     // If property is not marked neither as "required" nor "conflict" property
   459     // we copy it to verified properties map.
   460     else {
   461       verifiedProperties[name] = properties[name];
   462     }
   463   });
   465   // If no exceptions were thrown yet, we know that our verified property
   466   // descriptor map has no properties marked as "conflict" or "required",
   467   // so we just delegate to the built-in `Object.defineProperties`.
   468   return Object.defineProperties(object, verifiedProperties);
   469 }
   471 /**
   472  *  `create` is like `Object.create`, except that it ensures that:
   473  *    - An exception is thrown if any property in a given `properties` map
   474  *      is marked as "required" property and same named property is not
   475  *      found in a given `prototype`.
   476  *    - An exception is thrown if any property in a given `properties` map
   477  *      is marked as "conflict" property.
   478  * @param {Object} prototype
   479  *    prototype of the composed object
   480  * @param {Object} properties
   481  *    Properties descriptor map.
   482  * @returns {Object}
   483  *    An object that inherits form a given `prototype` and implements all the
   484  *    properties defined by a given `properties` descriptor map.
   485  */
   486 function create(prototype, properties) {
   488   // Creating an instance of the given `prototype`.
   489   var object = Object.create(prototype);
   491   // Overriding `toString`, `constructor` methods if they are just inherited
   492   // from `Object.prototype` with a same named methods of the `Trait.prototype`
   493   // that will have more relevant behavior.
   494   overrideBuiltInMethods(object, Trait.prototype);
   496   // Trying to define given `properties` on the `object`. We use our custom
   497   // `defineProperties` function instead of build-in `Object.defineProperties`
   498   // that behaves exactly the same, except that it will throw if any
   499   // property in the given `properties` descriptor is marked as "required" or
   500   // "conflict" property.
   501   return defineProperties(object, properties);
   502 }
   504 /**
   505  * Composes new trait. If two or more traits have own properties with the
   506  * same name, the new trait will contain a "conflict" property for that name.
   507  * "compose" is a commutative and associative operation, and the order of its
   508  * arguments is not significant.
   509  *
   510  * **Note:** Use `Trait.compose` instead of calling this function with more
   511  * than one argument. The multiple-argument functionality is strictly for
   512  * backward compatibility.
   513  *
   514  * @params {Object} trait
   515  *    Takes traits as an arguments
   516  * @returns {Object}
   517  *    New trait containing the combined own properties of all the traits.
   518  * @example
   519  *    var newTrait = compose(trait_1, trait_2, ..., trait_N)
   520  */
   521 function Trait(trait1, trait2) {
   523   // If the function was called with one argument, the argument should be
   524   // an object whose properties are mapped to property descriptors on a new
   525   // instance of Trait, so we delegate to the trait function.
   526   // If the function was called with more than one argument, those arguments
   527   // should be instances of Trait or plain property descriptor maps
   528   // whose properties should be mixed into a new instance of Trait,
   529   // so we delegate to the compose function.
   531   return trait2 === undefined ? trait(trait1) : compose.apply(null, arguments);
   532 }
   534 Object.freeze(Object.defineProperties(Trait.prototype, {
   535   toString: {
   536     value: function toString() {
   537       return "[object " + this.constructor.name + "]";
   538     }
   539   },
   541   /**
   542    * `create` is like `Object.create`, except that it ensures that:
   543    *    - An exception is thrown if this trait defines a property that is
   544    *      marked as required property and same named property is not
   545    *      found in a given `prototype`.
   546    *    - An exception is thrown if this trait contains property that is
   547    *      marked as "conflict" property.
   548    * @param {Object}
   549    *    prototype of the compared object
   550    * @returns {Object}
   551    *    An object with all of the properties described by the trait.
   552    */
   553   create: {
   554     value: function createTrait(prototype) {
   555       return create(undefined === prototype ? Object.prototype : prototype,
   556                     this);
   557     },
   558     enumerable: true
   559   },
   561   /**
   562    * Composes a new resolved trait, with all the same properties as the original
   563    * trait, except that all properties whose name is an own property of
   564    * `resolutions` will be renamed to the value of `resolutions[name]`. If
   565    * `resolutions[name]` is `null`, the property is marked as "required".
   566    * @param {Object} resolutions
   567    *   An object whose own properties serve as a mapping from old names to new
   568    *   names, or to `null` if the property should be excluded.
   569    * @returns {Object}
   570    *   New trait with the same own properties as the original trait but renamed.
   571    */
   572   resolve: {
   573     value: function resolveTrait(resolutions) {
   574       return resolve(resolutions, this);
   575     },
   576     enumerable: true
   577   }
   578 }));
   580 /**
   581  * @see compose
   582  */
   583 Trait.compose = Object.freeze(compose);
   584 Object.freeze(compose.prototype);
   586 /**
   587  * Constant singleton, representing placeholder for required properties.
   588  * @type {Object}
   589  */
   590 Trait.required = Object.freeze(Object.create(Object.prototype, {
   591   toString: {
   592     value: Object.freeze(function toString() {
   593       return "<Trait.required>";
   594     })
   595   }
   596 }));
   597 Object.freeze(Trait.required.toString.prototype);
   599 exports.Trait = Object.freeze(Trait);

mercurial