1// Copyright (c) 2012 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
5/**
6 * The global object.
7 * @type {!Object}
8 * @const
9 */
10var global = this;
11
12/** Platform, package, object property, and Event support. **/
13this.cr = (function() {
14  'use strict';
15
16  /**
17   * Tags the html element with an attribute that allows touch-specific css
18   * rules.
19   * TODO(rbyers): make Chrome always touch-optimized. http://crbug.com/105380
20   */
21  function enableTouchOptimizedCss() {
22    if (cr.isTouchOptimized)
23      doc.documentElement.setAttribute('touch-optimized', '');
24  }
25
26  /**
27   * Builds an object structure for the provided namespace path,
28   * ensuring that names that already exist are not overwritten. For
29   * example:
30   * "a.b.c" -> a = {};a.b={};a.b.c={};
31   * @param {string} name Name of the object that this file defines.
32   * @param {*=} opt_object The object to expose at the end of the path.
33   * @param {Object=} opt_objectToExportTo The object to add the path to;
34   *     default is {@code global}.
35   * @private
36   */
37  function exportPath(name, opt_object, opt_objectToExportTo) {
38    var parts = name.split('.');
39    var cur = opt_objectToExportTo || global;
40
41    for (var part; parts.length && (part = parts.shift());) {
42      if (!parts.length && opt_object !== undefined) {
43        // last part and we have an object; use it
44        cur[part] = opt_object;
45      } else if (part in cur) {
46        cur = cur[part];
47      } else {
48        cur = cur[part] = {};
49      }
50    }
51    return cur;
52  };
53
54  /**
55   * Fires a property change event on the target.
56   * @param {EventTarget} target The target to dispatch the event on.
57   * @param {string} propertyName The name of the property that changed.
58   * @param {*} newValue The new value for the property.
59   * @param {*} oldValue The old value for the property.
60   */
61  function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
62    var e = new cr.Event(propertyName + 'Change');
63    e.propertyName = propertyName;
64    e.newValue = newValue;
65    e.oldValue = oldValue;
66    target.dispatchEvent(e);
67  }
68
69  /**
70   * Converts a camelCase javascript property name to a hyphenated-lower-case
71   * attribute name.
72   * @param {string} jsName The javascript camelCase property name.
73   * @return {string} The equivalent hyphenated-lower-case attribute name.
74   */
75  function getAttributeName(jsName) {
76    return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
77  }
78
79  /**
80   * The kind of property to define in {@code defineProperty}.
81   * @enum {number}
82   * @const
83   */
84  var PropertyKind = {
85    /**
86     * Plain old JS property where the backing data is stored as a "private"
87     * field on the object.
88     */
89    JS: 'js',
90
91    /**
92     * The property backing data is stored as an attribute on an element.
93     */
94    ATTR: 'attr',
95
96    /**
97     * The property backing data is stored as an attribute on an element. If the
98     * element has the attribute then the value is true.
99     */
100    BOOL_ATTR: 'boolAttr'
101  };
102
103  /**
104   * Helper function for defineProperty that returns the getter to use for the
105   * property.
106   * @param {string} name The name of the property.
107   * @param {cr.PropertyKind} kind The kind of the property.
108   * @return {function():*} The getter for the property.
109   */
110  function getGetter(name, kind) {
111    switch (kind) {
112      case PropertyKind.JS:
113        var privateName = name + '_';
114        return function() {
115          return this[privateName];
116        };
117      case PropertyKind.ATTR:
118        var attributeName = getAttributeName(name);
119        return function() {
120          return this.getAttribute(attributeName);
121        };
122      case PropertyKind.BOOL_ATTR:
123        var attributeName = getAttributeName(name);
124        return function() {
125          return this.hasAttribute(attributeName);
126        };
127    }
128  }
129
130  /**
131   * Helper function for defineProperty that returns the setter of the right
132   * kind.
133   * @param {string} name The name of the property we are defining the setter
134   *     for.
135   * @param {cr.PropertyKind} kind The kind of property we are getting the
136   *     setter for.
137   * @param {function(*):void} opt_setHook A function to run after the property
138   *     is set, but before the propertyChange event is fired.
139   * @return {function(*):void} The function to use as a setter.
140   */
141  function getSetter(name, kind, opt_setHook) {
142    switch (kind) {
143      case PropertyKind.JS:
144        var privateName = name + '_';
145        return function(value) {
146          var oldValue = this[privateName];
147          if (value !== oldValue) {
148            this[privateName] = value;
149            if (opt_setHook)
150              opt_setHook.call(this, value, oldValue);
151            dispatchPropertyChange(this, name, value, oldValue);
152          }
153        };
154
155      case PropertyKind.ATTR:
156        var attributeName = getAttributeName(name);
157        return function(value) {
158          var oldValue = this[attributeName];
159          if (value !== oldValue) {
160            if (value == undefined)
161              this.removeAttribute(attributeName);
162            else
163              this.setAttribute(attributeName, value);
164            if (opt_setHook)
165              opt_setHook.call(this, value, oldValue);
166            dispatchPropertyChange(this, name, value, oldValue);
167          }
168        };
169
170      case PropertyKind.BOOL_ATTR:
171        var attributeName = getAttributeName(name);
172        return function(value) {
173          var oldValue = this[attributeName];
174          if (value !== oldValue) {
175            if (value)
176              this.setAttribute(attributeName, name);
177            else
178              this.removeAttribute(attributeName);
179            if (opt_setHook)
180              opt_setHook.call(this, value, oldValue);
181            dispatchPropertyChange(this, name, value, oldValue);
182          }
183        };
184    }
185  }
186
187  /**
188   * Defines a property on an object. When the setter changes the value a
189   * property change event with the type {@code name + 'Change'} is fired.
190   * @param {!Object} obj The object to define the property for.
191   * @param {string} name The name of the property.
192   * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use.
193   * @param {function(*):void} opt_setHook A function to run after the
194   *     property is set, but before the propertyChange event is fired.
195   */
196  function defineProperty(obj, name, opt_kind, opt_setHook) {
197    if (typeof obj == 'function')
198      obj = obj.prototype;
199
200    var kind = opt_kind || PropertyKind.JS;
201
202    if (!obj.__lookupGetter__(name))
203      obj.__defineGetter__(name, getGetter(name, kind));
204
205    if (!obj.__lookupSetter__(name))
206      obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
207  }
208
209  /**
210   * Counter for use with createUid
211   */
212  var uidCounter = 1;
213
214  /**
215   * @return {number} A new unique ID.
216   */
217  function createUid() {
218    return uidCounter++;
219  }
220
221  /**
222   * Returns a unique ID for the item. This mutates the item so it needs to be
223   * an object
224   * @param {!Object} item The item to get the unique ID for.
225   * @return {number} The unique ID for the item.
226   */
227  function getUid(item) {
228    if (item.hasOwnProperty('uid'))
229      return item.uid;
230    return item.uid = createUid();
231  }
232
233  /**
234   * Dispatches a simple event on an event target.
235   * @param {!EventTarget} target The event target to dispatch the event on.
236   * @param {string} type The type of the event.
237   * @param {boolean=} opt_bubbles Whether the event bubbles or not.
238   * @param {boolean=} opt_cancelable Whether the default action of the event
239   *     can be prevented.
240   * @return {boolean} If any of the listeners called {@code preventDefault}
241   *     during the dispatch this will return false.
242   */
243  function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
244    var e = new cr.Event(type, opt_bubbles, opt_cancelable);
245    return target.dispatchEvent(e);
246  }
247
248  /**
249   * Calls |fun| and adds all the fields of the returned object to the object
250   * named by |name|. For example, cr.define('cr.ui', function() {
251   *   function List() {
252   *     ...
253   *   }
254   *   function ListItem() {
255   *     ...
256   *   }
257   *   return {
258   *     List: List,
259   *     ListItem: ListItem,
260   *   };
261   * });
262   * defines the functions cr.ui.List and cr.ui.ListItem.
263   * @param {string} name The name of the object that we are adding fields to.
264   * @param {!Function} fun The function that will return an object containing
265   *     the names and values of the new fields.
266   */
267  function define(name, fun) {
268    var obj = exportPath(name);
269    var exports = fun();
270    for (var propertyName in exports) {
271      // Maybe we should check the prototype chain here? The current usage
272      // pattern is always using an object literal so we only care about own
273      // properties.
274      var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
275                                                               propertyName);
276      if (propertyDescriptor)
277        Object.defineProperty(obj, propertyName, propertyDescriptor);
278    }
279  }
280
281  /**
282   * Adds a {@code getInstance} static method that always return the same
283   * instance object.
284   * @param {!Function} ctor The constructor for the class to add the static
285   *     method to.
286   */
287  function addSingletonGetter(ctor) {
288    ctor.getInstance = function() {
289      return ctor.instance_ || (ctor.instance_ = new ctor());
290    };
291  }
292
293  /**
294   * Creates a new event to be used with cr.EventTarget or DOM EventTarget
295   * objects.
296   * @param {string} type The name of the event.
297   * @param {boolean=} opt_bubbles Whether the event bubbles.
298   *     Default is false.
299   * @param {boolean=} opt_preventable Whether the default action of the event
300   *     can be prevented.
301   * @constructor
302   * @extends {Event}
303   */
304  function Event(type, opt_bubbles, opt_preventable) {
305    var e = cr.doc.createEvent('Event');
306    e.initEvent(type, !!opt_bubbles, !!opt_preventable);
307    e.__proto__ = global.Event.prototype;
308    return e;
309  };
310
311  /**
312   * Initialization which must be deferred until run-time.
313   */
314  function initialize() {
315    // If 'document' isn't defined, then we must be being pre-compiled,
316    // so set a trap so that we're initialized on first access at run-time.
317    if (!global.document) {
318      var originalCr = cr;
319
320      Object.defineProperty(global, 'cr', {
321        get: function() {
322          Object.defineProperty(global, 'cr', {value: originalCr});
323          originalCr.initialize();
324          return originalCr;
325        },
326        configurable: true
327      });
328
329      return;
330    }
331
332    Event.prototype = {__proto__: global.Event.prototype};
333
334    /**
335     * Whether we are using a Mac or not.
336     */
337    cr.isMac = /Mac/.test(navigator.platform);
338
339    /**
340     * Whether this is on the Windows platform or not.
341     */
342    cr.isWindows = /Win/.test(navigator.platform);
343
344    /**
345     * Whether this is on chromeOS or not.
346     */
347    cr.isChromeOS = /CrOS/.test(navigator.userAgent);
348
349    /**
350     * Whether this is on vanilla Linux (not chromeOS).
351     */
352    cr.isLinux = /Linux/.test(navigator.userAgent);
353
354    /**
355     * Whether this uses GTK or not.
356     */
357    cr.isGTK = /GTK/.test(chrome.toolkit);
358
359    /**
360     * Whether this uses the views toolkit or not.
361     */
362    cr.isViews = /views/.test(chrome.toolkit);
363
364    /**
365     * Whether this window is optimized for touch-based input.
366     */
367    cr.isTouchOptimized = !!chrome.touchOptimized;
368
369    enableTouchOptimizedCss();
370  }
371
372  return {
373    addSingletonGetter: addSingletonGetter,
374    createUid: createUid,
375    define: define,
376    defineProperty: defineProperty,
377    dispatchPropertyChange: dispatchPropertyChange,
378    dispatchSimpleEvent: dispatchSimpleEvent,
379    Event: Event,
380    getUid: getUid,
381    initialize: initialize,
382    PropertyKind: PropertyKind,
383
384    /**
385     * The document that we are currently using.
386     * @type {!Document}
387     */
388    get doc() {
389      return document;
390    }
391  };
392})();
393
394
395/**
396 * TODO(kgr): Move this to another file which is to be loaded last.
397 * This will be done as part of future work to make this code pre-compilable.
398 */
399cr.initialize();
400