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