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