aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.12/lib/sdk/core/heritage.js
blob: 1c62e6c2b482ccef2a444bce1cab99e3031eea18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/* vim:set ts=2 sw=2 sts=2 expandtab */
/* 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"
};

var getPrototypeOf = Object.getPrototypeOf;
var getNames = Object.getOwnPropertyNames;
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
var create = Object.create;
var freeze = Object.freeze;
var unbind = Function.call.bind(Function.bind, Function.call);

// This shortcut makes sure that we do perform desired operations, even if
// associated methods have being overridden on the used object.
var owns = unbind(Object.prototype.hasOwnProperty);
var apply = unbind(Function.prototype.apply);
var slice = Array.slice || unbind(Array.prototype.slice);
var reduce = Array.reduce || unbind(Array.prototype.reduce);
var map = Array.map || unbind(Array.prototype.map);
var concat = Array.concat || unbind(Array.prototype.concat);

// Utility function to get own properties descriptor map.
function getOwnPropertyDescriptors(object) {
  return reduce(getNames(object), function(descriptor, name) {
    descriptor[name] = getOwnPropertyDescriptor(object, name);
    return descriptor;
  }, {});
}

/**
 * Takes `source` object as an argument and returns identical object
 * with the difference that all own properties will be non-enumerable
 */
function obscure(source) {
  var descriptor = reduce(getNames(source), function(descriptor, name) {
    var property = getOwnPropertyDescriptor(source, name);
    property.enumerable = false;
    descriptor[name] = property;
    return descriptor;
  }, {});
  return create(getPrototypeOf(source), descriptor);
}
exports.obscure = obscure;

/**
 * Takes arbitrary number of source objects and returns fresh one, that
 * inherits from the same prototype as a first argument and implements all
 * own properties of all argument objects. If two or more argument objects
 * have own properties with the same name, the property is overridden, with
 * precedence from right to left, implying, that properties of the object on
 * the left are overridden by a same named property of the object on the right.
 */
var mix = function(source) {
  var descriptor = reduce(slice(arguments), function(descriptor, source) {
    return reduce(getNames(source), function(descriptor, name) {
      descriptor[name] = getOwnPropertyDescriptor(source, name);
      return descriptor;
    }, descriptor);
  }, {});

  return create(getPrototypeOf(source), descriptor);
};
exports.mix = mix;

/**
 * Returns a frozen object with that inherits from the given `prototype` and
 * implements all own properties of the given `properties` object.
 */
function extend(prototype, properties) {
  return freeze(create(prototype, getOwnPropertyDescriptors(properties)));
}
exports.extend = extend;

/**
 * Returns a constructor function with a proper `prototype` setup. Returned
 * constructor's `prototype` inherits from a given `options.extends` or
 * `Class.prototype` if omitted and implements all the properties of the
 * given `option`. If `options.implemens` array is passed, it's elements
 * will be mixed into prototype as well. Also, `options.extends` can be
 * a function or a prototype. If function than it's prototype is used as
 * an ancestor of the prototype, if it's an object that it's used directly.
 * Also `options.implements` may contain functions or objects, in case of
 * functions their prototypes are used for mixing.
 */
var Class = new function() {
  function prototypeOf(input) {
    return typeof(input) === 'function' ? input.prototype : input;
  }
  var none = freeze([]);

  return function Class(options) {
    // Create descriptor with normalized `options.extends` and
    // `options.implements`.
    var descriptor = {
      // Normalize extends property of `options.extends` to a prototype object
      // in case it's constructor. If property is missing that fallback to
      // `Type.prototype`.
      extends: owns(options, 'extends') ?
               prototypeOf(options.extends) : Class.prototype,
      // Normalize `options.implements` to make sure that it's array of
      // prototype objects instead of constructor functions.
      implements: owns(options, 'implements') ?
                  freeze(map(options.implements, prototypeOf)) : none
    };

    // Create array of property descriptors who's properties will be defined
    // on the resulting prototype. Note: Using reflection `concat` instead of
    // method as it may be overridden.
    var descriptors = concat(descriptor.implements, options, descriptor);
    // Create `prototype` that inherits from given ancestor passed as
    // `options.extends`, falling back to `Type.prototype`, implementing all
    // properties of given `options.implements` and `options` itself.
    var prototype = extend(descriptor.extends, mix.apply(mix, descriptors));

    // Note: we use reflection `apply` in the constructor instead of method
    // call since later may be overridden.
    function constructor() {
      return apply(prototype.constructor, create(prototype), arguments);
    }
    constructor.prototype = prototype;
    return freeze(constructor);
  };
}
Class.prototype = extend(null, obscure({
  constructor: function constructor() {
    this.initialize.apply(this, arguments);
    return this;
  },
  initialize: function initialize() {
    // Do your initialization logic here
  },
  // Copy useful properties from `Object.prototype`.
  toString: Object.prototype.toString,
  toLocaleString: Object.prototype.toLocaleString,
  toSource: Object.prototype.toSource,
  valueOf: Object.prototype.valueOf,
  isPrototypeOf: Object.prototype.isPrototypeOf
}));
exports.Class = freeze(Class);