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. **/ 13var 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 {string} 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 * Use for properties of any type. Type will not be checked. 79 */ 80 JS: 'js', 81 82 /** 83 * The property backing data is stored as an attribute on an element. 84 * Use only for properties of type {string}. 85 */ 86 ATTR: 'attr', 87 88 /** 89 * The property backing data is stored as an attribute on an element. If the 90 * element has the attribute then the value is true. 91 * Use only for properties of type {boolean}. 92 */ 93 BOOL_ATTR: 'boolAttr' 94 }; 95 96 /** 97 * Helper function for defineProperty that returns the getter to use for the 98 * property. 99 * @param {string} name The name of the property. 100 * @param {PropertyKind} kind The kind of the property. 101 * @return {function():*} The getter for the property. 102 */ 103 function getGetter(name, kind) { 104 switch (kind) { 105 case PropertyKind.JS: 106 var privateName = name + '_'; 107 return function() { 108 return this[privateName]; 109 }; 110 case PropertyKind.ATTR: 111 var attributeName = getAttributeName(name); 112 return function() { 113 return this.getAttribute(attributeName); 114 }; 115 case PropertyKind.BOOL_ATTR: 116 var attributeName = getAttributeName(name); 117 return function() { 118 return this.hasAttribute(attributeName); 119 }; 120 } 121 122 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax 123 // the browser/unit tests to preprocess this file through grit. 124 throw 'not reached'; 125 } 126 127 /** 128 * Helper function for defineProperty that returns the setter of the right 129 * kind. 130 * @param {string} name The name of the property we are defining the setter 131 * for. 132 * @param {PropertyKind} kind The kind of property we are getting the 133 * setter for. 134 * @param {function(*, *):void=} opt_setHook A function to run after the 135 * property is set, but before the propertyChange event is fired. 136 * @return {function(*):void} The function to use as a setter. 137 */ 138 function getSetter(name, kind, opt_setHook) { 139 switch (kind) { 140 case PropertyKind.JS: 141 var privateName = name + '_'; 142 return function(value) { 143 var oldValue = this[name]; 144 if (value !== oldValue) { 145 this[privateName] = value; 146 if (opt_setHook) 147 opt_setHook.call(this, value, oldValue); 148 dispatchPropertyChange(this, name, value, oldValue); 149 } 150 }; 151 152 case PropertyKind.ATTR: 153 var attributeName = getAttributeName(name); 154 return function(value) { 155 var oldValue = this[name]; 156 if (value !== oldValue) { 157 if (value == undefined) 158 this.removeAttribute(attributeName); 159 else 160 this.setAttribute(attributeName, value); 161 if (opt_setHook) 162 opt_setHook.call(this, value, oldValue); 163 dispatchPropertyChange(this, name, value, oldValue); 164 } 165 }; 166 167 case PropertyKind.BOOL_ATTR: 168 var attributeName = getAttributeName(name); 169 return function(value) { 170 var oldValue = this[name]; 171 if (value !== oldValue) { 172 if (value) 173 this.setAttribute(attributeName, name); 174 else 175 this.removeAttribute(attributeName); 176 if (opt_setHook) 177 opt_setHook.call(this, value, oldValue); 178 dispatchPropertyChange(this, name, value, oldValue); 179 } 180 }; 181 } 182 183 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax 184 // the browser/unit tests to preprocess this file through grit. 185 throw 'not reached'; 186 } 187 188 /** 189 * Defines a property on an object. When the setter changes the value a 190 * property change event with the type {@code name + 'Change'} is fired. 191 * @param {!Object} obj The object to define the property for. 192 * @param {string} name The name of the property. 193 * @param {PropertyKind=} opt_kind What kind of underlying storage to use. 194 * @param {function(*, *):void=} opt_setHook A function to run after the 195 * property is set, but before the propertyChange event is fired. 196 */ 197 function defineProperty(obj, name, opt_kind, opt_setHook) { 198 if (typeof obj == 'function') 199 obj = obj.prototype; 200 201 var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS); 202 203 if (!obj.__lookupGetter__(name)) 204 obj.__defineGetter__(name, getGetter(name, kind)); 205 206 if (!obj.__lookupSetter__(name)) 207 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook)); 208 } 209 210 /** 211 * Counter for use with createUid 212 */ 213 var uidCounter = 1; 214 215 /** 216 * @return {number} A new unique ID. 217 */ 218 function createUid() { 219 return uidCounter++; 220 } 221 222 /** 223 * Returns a unique ID for the item. This mutates the item so it needs to be 224 * an object 225 * @param {!Object} item The item to get the unique ID for. 226 * @return {number} The unique ID for the item. 227 */ 228 function getUid(item) { 229 if (item.hasOwnProperty('uid')) 230 return item.uid; 231 return item.uid = createUid(); 232 } 233 234 /** 235 * Dispatches a simple event on an event target. 236 * @param {!EventTarget} target The event target to dispatch the event on. 237 * @param {string} type The type of the event. 238 * @param {boolean=} opt_bubbles Whether the event bubbles or not. 239 * @param {boolean=} opt_cancelable Whether the default action of the event 240 * can be prevented. Default is true. 241 * @return {boolean} If any of the listeners called {@code preventDefault} 242 * during the dispatch this will return false. 243 */ 244 function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { 245 var e = new Event(type, { 246 bubbles: opt_bubbles, 247 cancelable: opt_cancelable === undefined || opt_cancelable 248 }); 249 return target.dispatchEvent(e); 250 } 251 252 /** 253 * Calls |fun| and adds all the fields of the returned object to the object 254 * named by |name|. For example, cr.define('cr.ui', function() { 255 * function List() { 256 * ... 257 * } 258 * function ListItem() { 259 * ... 260 * } 261 * return { 262 * List: List, 263 * ListItem: ListItem, 264 * }; 265 * }); 266 * defines the functions cr.ui.List and cr.ui.ListItem. 267 * @param {string} name The name of the object that we are adding fields to. 268 * @param {!Function} fun The function that will return an object containing 269 * the names and values of the new fields. 270 */ 271 function define(name, fun) { 272 var obj = exportPath(name); 273 var exports = fun(); 274 for (var propertyName in exports) { 275 // Maybe we should check the prototype chain here? The current usage 276 // pattern is always using an object literal so we only care about own 277 // properties. 278 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, 279 propertyName); 280 if (propertyDescriptor) 281 Object.defineProperty(obj, propertyName, propertyDescriptor); 282 } 283 } 284 285 /** 286 * Adds a {@code getInstance} static method that always return the same 287 * instance object. 288 * @param {!Function} ctor The constructor for the class to add the static 289 * method to. 290 */ 291 function addSingletonGetter(ctor) { 292 ctor.getInstance = function() { 293 return ctor.instance_ || (ctor.instance_ = new ctor()); 294 }; 295 } 296 297 /** 298 * Forwards public APIs to private implementations. 299 * @param {Function} ctor Constructor that have private implementations in its 300 * prototype. 301 * @param {Array.<string>} methods List of public method names that have their 302 * underscored counterparts in constructor's prototype. 303 * @param {string=} opt_target Selector for target node. 304 */ 305 function makePublic(ctor, methods, opt_target) { 306 methods.forEach(function(method) { 307 ctor[method] = function() { 308 var target = opt_target ? document.getElementById(opt_target) : 309 ctor.getInstance(); 310 return target[method + '_'].apply(target, arguments); 311 }; 312 }); 313 } 314 315 return { 316 addSingletonGetter: addSingletonGetter, 317 createUid: createUid, 318 define: define, 319 defineProperty: defineProperty, 320 dispatchPropertyChange: dispatchPropertyChange, 321 dispatchSimpleEvent: dispatchSimpleEvent, 322 exportPath: exportPath, 323 getUid: getUid, 324 makePublic: makePublic, 325 PropertyKind: PropertyKind, 326 327 get doc() { 328 return document; 329 }, 330 331 /** Whether we are using a Mac or not. */ 332 get isMac() { 333 return /Mac/.test(navigator.platform); 334 }, 335 336 /** Whether this is on the Windows platform or not. */ 337 get isWindows() { 338 return /Win/.test(navigator.platform); 339 }, 340 341 /** Whether this is on chromeOS or not. */ 342 get isChromeOS() { 343 return /CrOS/.test(navigator.userAgent); 344 }, 345 346 /** Whether this is on vanilla Linux (not chromeOS). */ 347 get isLinux() { 348 return /Linux/.test(navigator.userAgent); 349 }, 350 351 /** Whether this uses the views toolkit or not. */ 352 get isViews() { 353 return typeof chrome.getVariableValue == 'function' && 354 /views/.test(chrome.getVariableValue('toolkit')); 355 }, 356 }; 357}(); 358