1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5const cr = (function() {
6
7  /**
8   * Whether we are using a Mac or not.
9   * @type {boolean}
10   */
11  const isMac = /Mac/.test(navigator.platform);
12
13  /**
14   * Whether this is on the Windows platform or not.
15   * @type {boolean}
16   */
17  const isWindows = /Win/.test(navigator.platform);
18
19  /**
20   * Whether this is on chromeOS or not.
21   * @type {boolean}
22   */
23  const isChromeOS = /CrOS/.test(navigator.userAgent);
24
25  /**
26   * Whether this is on vanilla Linux (not chromeOS).
27   * @type {boolean}
28   */
29  const isLinux = /Linux/.test(navigator.userAgent);
30
31  /**
32   * Whether this uses the views toolkit or not.
33   * @type {boolean}
34   */
35  const isViews = isWindows || isChromeOS;
36
37  /**
38   * Builds an object structure for the provided namespace path,
39   * ensuring that names that already exist are not overwritten. For
40   * example:
41   * "a.b.c" -> a = {};a.b={};a.b.c={};
42   * @param {string} name Name of the object that this file defines.
43   * @param {*=} opt_object The object to expose at the end of the path.
44   * @param {Object=} opt_objectToExportTo The object to add the path to;
45   *     default is {@code window}.
46   * @private
47   */
48  function exportPath(name, opt_object, opt_objectToExportTo) {
49    var parts = name.split('.');
50    var cur = opt_objectToExportTo || window /* global */;
51
52    for (var part; parts.length && (part = parts.shift());) {
53      if (!parts.length && opt_object !== undefined) {
54        // last part and we have an object; use it
55        cur[part] = opt_object;
56      } else if (part in cur) {
57        cur = cur[part];
58      } else {
59        cur = cur[part] = {};
60      }
61    }
62    return cur;
63  };
64
65  // cr.Event is called CrEvent in here to prevent naming conflicts. We also
66  // store the original Event in case someone does a global alias of cr.Event.
67  const DomEvent = Event;
68
69  /**
70   * Creates a new event to be used with cr.EventTarget or DOM EventTarget
71   * objects.
72   * @param {string} type The name of the event.
73   * @param {boolean=} opt_bubbles Whether the event bubbles. Default is false.
74   * @param {boolean=} opt_preventable Whether the default action of the event
75   *     can be prevented.
76   * @constructor
77   * @extends {DomEvent}
78   */
79  function CrEvent(type, opt_bubbles, opt_preventable) {
80    var e = cr.doc.createEvent('Event');
81    e.initEvent(type, !!opt_bubbles, !!opt_preventable);
82    e.__proto__ = CrEvent.prototype;
83    return e;
84  }
85
86  CrEvent.prototype = {
87    __proto__: DomEvent.prototype
88  };
89
90  /**
91   * Fires a property change event on the target.
92   * @param {EventTarget} target The target to dispatch the event on.
93   * @param {string} propertyName The name of the property that changed.
94   * @param {*} newValue The new value for the property.
95   * @param {*} oldValue The old value for the property.
96   */
97  function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
98    var e = new CrEvent(propertyName + 'Change');
99    e.propertyName = propertyName;
100    e.newValue = newValue;
101    e.oldValue = oldValue;
102    target.dispatchEvent(e);
103  }
104
105  /**
106   * The kind of property to define in {@code defineProperty}.
107   * @enum {number}
108   */
109  const PropertyKind = {
110    /**
111     * Plain old JS property where the backing data is stored as a "private"
112     * field on the object.
113     */
114    JS: 'js',
115
116    /**
117     * The property backing data is stored as an attribute on an element.
118     */
119    ATTR: 'attr',
120
121    /**
122     * The property backing data is stored as an attribute on an element. If the
123     * element has the attribute then the value is true.
124     */
125    BOOL_ATTR: 'boolAttr'
126  };
127
128  /**
129   * Helper function for defineProperty that returns the getter to use for the
130   * property.
131   * @param {string} name
132   * @param {cr.PropertyKind} kind
133   * @return {function():*} The getter for the property.
134   */
135  function getGetter(name, kind) {
136    switch (kind) {
137      case PropertyKind.JS:
138        var privateName = name + '_';
139        return function() {
140          return this[privateName];
141        };
142      case PropertyKind.ATTR:
143        return function() {
144          return this.getAttribute(name);
145        };
146      case PropertyKind.BOOL_ATTR:
147        return function() {
148          return this.hasAttribute(name);
149        };
150    }
151  }
152
153  /**
154   * Helper function for defineProperty that returns the setter of the right
155   * kind.
156   * @param {string} name The name of the property we are defining the setter
157   *     for.
158   * @param {cr.PropertyKind} kind The kind of property we are getting the
159   *     setter for.
160   * @param {function(*):void} opt_setHook A function to run after the property
161   *     is set, but before the propertyChange event is fired.
162   * @return {function(*):void} The function to use as a setter.
163   */
164  function getSetter(name, kind, opt_setHook) {
165    switch (kind) {
166      case PropertyKind.JS:
167        var privateName = name + '_';
168        return function(value) {
169          var oldValue = this[privateName];
170          if (value !== oldValue) {
171            this[privateName] = value;
172            if (opt_setHook)
173              opt_setHook.call(this, value, oldValue);
174            dispatchPropertyChange(this, name, value, oldValue);
175          }
176        };
177
178      case PropertyKind.ATTR:
179        return function(value) {
180          var oldValue = this[name];
181          if (value !== oldValue) {
182            this.setAttribute(name, value);
183            if (opt_setHook)
184              opt_setHook.call(this, value, oldValue);
185            dispatchPropertyChange(this, name, value, oldValue);
186          }
187        };
188
189      case PropertyKind.BOOL_ATTR:
190        return function(value) {
191          var oldValue = this[name];
192          if (value !== oldValue) {
193            if (value)
194              this.setAttribute(name, name);
195            else
196              this.removeAttribute(name);
197            if (opt_setHook)
198              opt_setHook.call(this, value, oldValue);
199            dispatchPropertyChange(this, name, value, oldValue);
200          }
201        };
202    }
203  }
204
205  /**
206   * Defines a property on an object. When the setter changes the value a
207   * property change event with the type {@code name + 'Change'} is fired.
208   * @param {!Object} obj The object to define the property for.
209   * @param {string} name The name of the property.
210   * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use.
211   * @param {function(*):void} opt_setHook A function to run after the
212   *     property is set, but before the propertyChange event is fired.
213   */
214  function defineProperty(obj, name, opt_kind, opt_setHook) {
215    if (typeof obj == 'function')
216      obj = obj.prototype;
217
218    var kind = opt_kind || PropertyKind.JS;
219
220    if (!obj.__lookupGetter__(name)) {
221      obj.__defineGetter__(name, getGetter(name, kind));
222    }
223
224    if (!obj.__lookupSetter__(name)) {
225      obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
226    }
227  }
228
229  /**
230   * Counter for use with createUid
231   */
232  var uidCounter = 1;
233
234  /**
235   * @return {number} A new unique ID.
236   */
237  function createUid() {
238    return uidCounter++;
239  }
240
241  /**
242   * Returns a unique ID for the item. This mutates the item so it needs to be
243   * an object
244   * @param {!Object} item The item to get the unique ID for.
245   * @return {number} The unique ID for the item.
246   */
247  function getUid(item) {
248    if (item.hasOwnProperty('uid'))
249      return item.uid;
250    return item.uid = createUid();
251  }
252
253  /**
254   * Dispatches a simple event on an event target.
255   * @param {!EventTarget} target The event target to dispatch the event on.
256   * @param {string} type The type of the event.
257   * @param {boolean=} opt_bubbles Whether the event bubbles or not.
258   * @param {boolean=} opt_cancelable Whether the default action of the event
259   *     can be prevented.
260   * @return {boolean} If any of the listeners called {@code preventDefault}
261   *     during the dispatch this will return false.
262   */
263  function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
264    var e = new cr.Event(type, opt_bubbles, opt_cancelable);
265    return target.dispatchEvent(e);
266  }
267
268  /**
269   * @param {string} name
270   * @param {!Function} fun
271   */
272  function define(name, fun) {
273    var obj = exportPath(name);
274    var exports = fun();
275    for (var propertyName in exports) {
276      // Maybe we should check the prototype chain here? The current usage
277      // pattern is always using an object literal so we only care about own
278      // properties.
279      var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
280                                                               propertyName);
281      if (propertyDescriptor)
282        Object.defineProperty(obj, propertyName, propertyDescriptor);
283    }
284  }
285
286  /**
287   * Document used for various document related operations.
288   * @type {!Document}
289   */
290  var doc = document;
291
292
293  /**
294   * Allows you to run func in the context of a different document.
295   * @param {!Document} document The document to use.
296   * @param {function():*} func The function to call.
297   */
298  function withDoc(document, func) {
299    var oldDoc = doc;
300    doc = document;
301    try {
302      func();
303    } finally {
304      doc = oldDoc;
305    }
306  }
307
308  /**
309   * Adds a {@code getInstance} static method that always return the same
310   * instance object.
311   * @param {!Function} ctor The constructor for the class to add the static
312   *     method to.
313   */
314  function addSingletonGetter(ctor) {
315    ctor.getInstance = function() {
316      return ctor.instance_ || (ctor.instance_ = new ctor());
317    };
318  }
319
320  return {
321    addSingletonGetter: addSingletonGetter,
322    isChromeOS: isChromeOS,
323    isMac: isMac,
324    isWindows: isWindows,
325    isLinux: isLinux,
326    isViews: isViews,
327    define: define,
328    defineProperty: defineProperty,
329    PropertyKind: PropertyKind,
330    createUid: createUid,
331    getUid: getUid,
332    dispatchSimpleEvent: dispatchSimpleEvent,
333    dispatchPropertyChange: dispatchPropertyChange,
334
335    /**
336     * The document that we are currently using.
337     * @type {!Document}
338     */
339    get doc() {
340      return doc;
341    },
342    withDoc: withDoc,
343    Event: CrEvent
344  };
345})();
346