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

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial