summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/core/heritage.js
blob: fc87ba1f5dd43b689fafe73985b5285f3ea18bf2 (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/* 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 = x => [...Object.getOwnPropertyNames(x),
                     ...Object.getOwnPropertySymbols(x)];
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;
  }, {});
}

function isDataProperty(property) {
  var value = property.value;
  var type = typeof(property.value);
  return "value" in property &&
         (type !== "object" || value === null) &&
         type !== "function";
}

function getDataProperties(object) {
  var properties = getOwnPropertyDescriptors(object);
  return getNames(properties).reduce(function(result, name) {
    var property = properties[name];
    if (isDataProperty(property)) {
      result[name] = {
        value: property.value,
        writable: true,
        configurable: true,
        enumerable: false
      };
    }
    return result;
  }, {})
}

/**
 * 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 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, {
      constructor: constructor
    });

    // Note: we use reflection `apply` in the constructor instead of method
    // call since later may be overridden.
    function constructor() {
      var instance = create(prototype, attributes);
      if (initialize) apply(initialize, instance, arguments);
      return instance;
    }
    // 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));
    var initialize = prototype.initialize;

    // Combine ancestor attributes with prototype's attributes so that
    // ancestors attributes also become initializeable.
    var attributes = mix(descriptor.extends.constructor.attributes || {},
                         getDataProperties(prototype));

    constructor.attributes = attributes;
    Object.defineProperty(constructor, 'prototype', {
      configurable: false,
      writable: false,
      value: prototype
    });
    return 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);