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