OLD | NEW |
(Empty) | |
| 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 'use strict'; |
| 6 |
| 7 |
| 8 /** |
| 9 * The global object. |
| 10 * @type {!Object} |
| 11 * @const |
| 12 */ |
| 13 var global = this; |
| 14 |
| 15 |
| 16 /** Platform, package, object property, and Event support. */ |
| 17 this.base = (function() { |
| 18 |
| 19 /** |
| 20 * Base path for modules. Used to form URLs for module 'require' requests. |
| 21 */ |
| 22 var moduleBasePath = '.'; |
| 23 function setModuleBasePath(path) { |
| 24 if (path[path.length - 1] == '/') |
| 25 path = path.substring(0, path.length - 1); |
| 26 moduleBasePath = path; |
| 27 } |
| 28 |
| 29 |
| 30 function mLog(text, opt_indentLevel) { |
| 31 if (true) |
| 32 return; |
| 33 |
| 34 var spacing = ''; |
| 35 var indentLevel = opt_indentLevel || 0; |
| 36 for (var i = 0; i < indentLevel; i++) |
| 37 spacing += ' '; |
| 38 console.log(spacing + text); |
| 39 } |
| 40 |
| 41 /** |
| 42 * Builds an object structure for the provided namespace path, |
| 43 * ensuring that names that already exist are not overwritten. For |
| 44 * example: |
| 45 * 'a.b.c' -> a = {};a.b={};a.b.c={}; |
| 46 * @param {string} name Name of the object that this file defines. |
| 47 * @param {*=} opt_object The object to expose at the end of the path. |
| 48 * @param {Object=} opt_objectToExportTo The object to add the path to; |
| 49 * default is {@code global}. |
| 50 * @private |
| 51 */ |
| 52 function exportPath(name, opt_object, opt_objectToExportTo) { |
| 53 var parts = name.split('.'); |
| 54 var cur = opt_objectToExportTo || global; |
| 55 |
| 56 for (var part; parts.length && (part = parts.shift());) { |
| 57 if (!parts.length && opt_object !== undefined) { |
| 58 // last part and we have an object; use it |
| 59 cur[part] = opt_object; |
| 60 } else if (part in cur) { |
| 61 cur = cur[part]; |
| 62 } else { |
| 63 cur = cur[part] = {}; |
| 64 } |
| 65 } |
| 66 return cur; |
| 67 }; |
| 68 |
| 69 var didLoadModules = false; |
| 70 var moduleDependencies = {}; |
| 71 var moduleStylesheets = {}; |
| 72 var moduleRawScripts = {}; |
| 73 |
| 74 function addModuleDependency(moduleName, dependentModuleName) { |
| 75 if (!moduleDependencies[moduleName]) |
| 76 moduleDependencies[moduleName] = []; |
| 77 |
| 78 var dependentModules = moduleDependencies[moduleName]; |
| 79 var found = false; |
| 80 for (var i = 0; i < dependentModules.length; i++) |
| 81 if (dependentModules[i] == dependentModuleName) |
| 82 found = true; |
| 83 if (!found) |
| 84 dependentModules.push(dependentModuleName); |
| 85 } |
| 86 |
| 87 function addModuleRawScriptDependency(moduleName, rawScriptName) { |
| 88 if (!moduleRawScripts[moduleName]) |
| 89 moduleRawScripts[moduleName] = []; |
| 90 |
| 91 var dependentRawScripts = moduleRawScripts[moduleName]; |
| 92 var found = false; |
| 93 for (var i = 0; i < moduleRawScripts.length; i++) |
| 94 if (dependentRawScripts[i] == rawScriptName) |
| 95 found = true; |
| 96 if (!found) |
| 97 dependentRawScripts.push(rawScriptName); |
| 98 } |
| 99 |
| 100 function addModuleStylesheet(moduleName, stylesheetName) { |
| 101 if (!moduleStylesheets[moduleName]) |
| 102 moduleStylesheets[moduleName] = []; |
| 103 |
| 104 var stylesheets = moduleStylesheets[moduleName]; |
| 105 var found = false; |
| 106 for (var i = 0; i < stylesheets.length; i++) |
| 107 if (stylesheets[i] == stylesheetName) |
| 108 found = true; |
| 109 if (!found) |
| 110 stylesheets.push(stylesheetName); |
| 111 } |
| 112 |
| 113 function ensureDepsLoaded() { |
| 114 if (didLoadModules) |
| 115 return; |
| 116 didLoadModules = true; |
| 117 |
| 118 var req = new XMLHttpRequest(); |
| 119 var src = moduleBasePath + '/base/' + 'deps.js'; |
| 120 req.open('GET', src, false); |
| 121 req.send(null); |
| 122 if (req.status != 200) |
| 123 throw new Error('Could not find ' + src + |
| 124 '. Run calcdeps.py and try again.'); |
| 125 |
| 126 base.addModuleDependency = addModuleDependency; |
| 127 base.addModuleRawScriptDependency = addModuleRawScriptDependency; |
| 128 base.addModuleStylesheet = addModuleStylesheet; |
| 129 try { |
| 130 // By construction, the deps file should call addModuleDependency. |
| 131 eval(req.responseText); |
| 132 } catch (e) { |
| 133 throw new Error('When loading deps, got ' + e.stack ? e.stack : e); |
| 134 } |
| 135 delete base.addModuleStylesheet; |
| 136 delete base.addModuleRawScriptDependency; |
| 137 delete base.addModuleDependency; |
| 138 |
| 139 } |
| 140 |
| 141 var moduleLoadStatus = {}; |
| 142 var rawScriptLoadStatus = {}; |
| 143 function require(dependentModuleName, opt_indentLevel) { |
| 144 var indentLevel = opt_indentLevel || 0; |
| 145 |
| 146 if (window.FLATTENED) { |
| 147 if (!window.FLATTENED[dependentModuleName]) { |
| 148 throw new Error('Somehow, module ' + dependentModuleName + |
| 149 ' didn\'t get stored in the flattened js file! ' + |
| 150 'You may need to rerun build/calcdeps.py'); |
| 151 } |
| 152 return; |
| 153 } |
| 154 ensureDepsLoaded(); |
| 155 |
| 156 mLog('require(' + dependentModuleName + ')', indentLevel); |
| 157 |
| 158 if (moduleLoadStatus[dependentModuleName] == 'APPENDED') |
| 159 return; |
| 160 if (moduleLoadStatus[dependentModuleName] == 'RESOLVING') |
| 161 throw new Error('Circular dependency betwen modules. Cannot continue!'); |
| 162 moduleLoadStatus[dependentModuleName] = 'RESOLVING'; |
| 163 |
| 164 // Load the module stylesheet first. |
| 165 var stylesheets = moduleStylesheets[dependentModuleName] || []; |
| 166 for (var i = 0; i < stylesheets.length; i++) |
| 167 requireStylesheet(stylesheets[i]); |
| 168 |
| 169 // Load the module raw scripts next |
| 170 var rawScripts = moduleRawScripts[dependentModuleName] || []; |
| 171 for (var i = 0; i < rawScripts.length; i++) { |
| 172 var rawScriptName = rawScripts[i]; |
| 173 if (rawScriptLoadStatus[rawScriptName]) |
| 174 continue; |
| 175 |
| 176 mLog('load(' + rawScriptName + ')', indentLevel); |
| 177 var src = moduleBasePath + '/' + rawScriptName; |
| 178 var text = '<script type="text/javascript" src="' + src + |
| 179 '"></' + 'script>'; |
| 180 base.doc.write(text); |
| 181 rawScriptLoadStatus[rawScriptName] = 'APPENDED'; |
| 182 } |
| 183 |
| 184 // Load the module's dependent scripts after. |
| 185 var dependentModules = |
| 186 moduleDependencies[dependentModuleName] || []; |
| 187 for (var i = 0; i < dependentModules.length; i++) |
| 188 require(dependentModules[i], indentLevel + 1); |
| 189 |
| 190 mLog('load(' + dependentModuleName + ')', indentLevel); |
| 191 // Load the module itself. |
| 192 var localPath = dependentModuleName.replace(/\./g, '/') + '.js'; |
| 193 var src = moduleBasePath + '/' + localPath; |
| 194 var text = '<script type="text/javascript" src="' + src + |
| 195 '"></' + 'script>'; |
| 196 base.doc.write(text); |
| 197 moduleLoadStatus[dependentModuleName] = 'APPENDED'; |
| 198 } |
| 199 |
| 200 /** |
| 201 * Adds a dependency on a raw javascript file, e.g. a third party |
| 202 * library. |
| 203 * @param {String} rawScriptName The path to the script file, relative to |
| 204 * moduleBasePath. |
| 205 */ |
| 206 function requireRawScript(rawScriptPath) { |
| 207 if (window.FLATTENED_RAW_SCRIPTS) { |
| 208 if (!window.FLATTENED_RAW_SCRIPTS[rawScriptPath]) { |
| 209 throw new Error('Somehow, ' + rawScriptPath + |
| 210 ' didn\'t get stored in the flattened js file! ' + |
| 211 'You may need to rerun build/calcdeps.py'); |
| 212 } |
| 213 return; |
| 214 } |
| 215 |
| 216 if (rawScriptLoadStatus[rawScriptPath]) |
| 217 return; |
| 218 throw new Error(rawScriptPath + ' should already have been loaded.' + |
| 219 ' Did you forget to run calcdeps.py?'); |
| 220 } |
| 221 |
| 222 var stylesheetLoadStatus = {}; |
| 223 function requireStylesheet(dependentStylesheetName) { |
| 224 if (window.FLATTENED) |
| 225 return; |
| 226 |
| 227 if (stylesheetLoadStatus[dependentStylesheetName]) |
| 228 return; |
| 229 stylesheetLoadStatus[dependentStylesheetName] = true; |
| 230 var localPath = dependentStylesheetName.replace(/\./g, '/') + '.css'; |
| 231 var stylesheetPath = moduleBasePath + '/' + localPath; |
| 232 |
| 233 var linkEl = document.createElement('link'); |
| 234 linkEl.setAttribute('rel', 'stylesheet'); |
| 235 linkEl.setAttribute('href', stylesheetPath); |
| 236 base.doc.head.appendChild(linkEl); |
| 237 } |
| 238 |
| 239 function exportTo(namespace, fn) { |
| 240 var obj = exportPath(namespace); |
| 241 try { |
| 242 var exports = fn(); |
| 243 } catch (e) { |
| 244 console.log('While running exports for ', name, ':'); |
| 245 console.log(e.stack || e); |
| 246 return; |
| 247 } |
| 248 |
| 249 for (var propertyName in exports) { |
| 250 // Maybe we should check the prototype chain here? The current usage |
| 251 // pattern is always using an object literal so we only care about own |
| 252 // properties. |
| 253 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, |
| 254 propertyName); |
| 255 if (propertyDescriptor) { |
| 256 Object.defineProperty(obj, propertyName, propertyDescriptor); |
| 257 mLog(' +' + propertyName); |
| 258 } |
| 259 } |
| 260 }; |
| 261 |
| 262 /** |
| 263 * Fires a property change event on the target. |
| 264 * @param {EventTarget} target The target to dispatch the event on. |
| 265 * @param {string} propertyName The name of the property that changed. |
| 266 * @param {*} newValue The new value for the property. |
| 267 * @param {*} oldValue The old value for the property. |
| 268 */ |
| 269 function dispatchPropertyChange(target, propertyName, newValue, oldValue) { |
| 270 var e = new base.Event(propertyName + 'Change'); |
| 271 e.propertyName = propertyName; |
| 272 e.newValue = newValue; |
| 273 e.oldValue = oldValue; |
| 274 target.dispatchEvent(e); |
| 275 } |
| 276 |
| 277 /** |
| 278 * Converts a camelCase javascript property name to a hyphenated-lower-case |
| 279 * attribute name. |
| 280 * @param {string} jsName The javascript camelCase property name. |
| 281 * @return {string} The equivalent hyphenated-lower-case attribute name. |
| 282 */ |
| 283 function getAttributeName(jsName) { |
| 284 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase(); |
| 285 } |
| 286 |
| 287 /** |
| 288 * The kind of property to define in {@code defineProperty}. |
| 289 * @enum {number} |
| 290 * @const |
| 291 */ |
| 292 var PropertyKind = { |
| 293 /** |
| 294 * Plain old JS property where the backing data is stored as a 'private' |
| 295 * field on the object. |
| 296 */ |
| 297 JS: 'js', |
| 298 |
| 299 /** |
| 300 * The property backing data is stored as an attribute on an element. |
| 301 */ |
| 302 ATTR: 'attr', |
| 303 |
| 304 /** |
| 305 * The property backing data is stored as an attribute on an element. If the |
| 306 * element has the attribute then the value is true. |
| 307 */ |
| 308 BOOL_ATTR: 'boolAttr' |
| 309 }; |
| 310 |
| 311 /** |
| 312 * Helper function for defineProperty that returns the getter to use for the |
| 313 * property. |
| 314 * @param {string} name The name of the property. |
| 315 * @param {base.PropertyKind} kind The kind of the property. |
| 316 * @return {function():*} The getter for the property. |
| 317 */ |
| 318 function getGetter(name, kind) { |
| 319 switch (kind) { |
| 320 case PropertyKind.JS: |
| 321 var privateName = name + '_'; |
| 322 return function() { |
| 323 return this[privateName]; |
| 324 }; |
| 325 case PropertyKind.ATTR: |
| 326 var attributeName = getAttributeName(name); |
| 327 return function() { |
| 328 return this.getAttribute(attributeName); |
| 329 }; |
| 330 case PropertyKind.BOOL_ATTR: |
| 331 var attributeName = getAttributeName(name); |
| 332 return function() { |
| 333 return this.hasAttribute(attributeName); |
| 334 }; |
| 335 } |
| 336 } |
| 337 |
| 338 /** |
| 339 * Helper function for defineProperty that returns the setter of the right |
| 340 * kind. |
| 341 * @param {string} name The name of the property we are defining the setter |
| 342 * for. |
| 343 * @param {base.PropertyKind} kind The kind of property we are getting the |
| 344 * setter for. |
| 345 * @param {function(*):void} opt_setHook A function to run after the property |
| 346 * is set, but before the propertyChange event is fired. |
| 347 * @return {function(*):void} The function to use as a setter. |
| 348 */ |
| 349 function getSetter(name, kind, opt_setHook) { |
| 350 switch (kind) { |
| 351 case PropertyKind.JS: |
| 352 var privateName = name + '_'; |
| 353 return function(value) { |
| 354 var oldValue = this[privateName]; |
| 355 if (value !== oldValue) { |
| 356 this[privateName] = value; |
| 357 if (opt_setHook) |
| 358 opt_setHook.call(this, value, oldValue); |
| 359 dispatchPropertyChange(this, name, value, oldValue); |
| 360 } |
| 361 }; |
| 362 |
| 363 case PropertyKind.ATTR: |
| 364 var attributeName = getAttributeName(name); |
| 365 return function(value) { |
| 366 var oldValue = this[attributeName]; |
| 367 if (value !== oldValue) { |
| 368 if (value == undefined) |
| 369 this.removeAttribute(attributeName); |
| 370 else |
| 371 this.setAttribute(attributeName, value); |
| 372 if (opt_setHook) |
| 373 opt_setHook.call(this, value, oldValue); |
| 374 dispatchPropertyChange(this, name, value, oldValue); |
| 375 } |
| 376 }; |
| 377 |
| 378 case PropertyKind.BOOL_ATTR: |
| 379 var attributeName = getAttributeName(name); |
| 380 return function(value) { |
| 381 var oldValue = this[attributeName]; |
| 382 if (value !== oldValue) { |
| 383 if (value) |
| 384 this.setAttribute(attributeName, name); |
| 385 else |
| 386 this.removeAttribute(attributeName); |
| 387 if (opt_setHook) |
| 388 opt_setHook.call(this, value, oldValue); |
| 389 dispatchPropertyChange(this, name, value, oldValue); |
| 390 } |
| 391 }; |
| 392 } |
| 393 } |
| 394 |
| 395 /** |
| 396 * Defines a property on an object. When the setter changes the value a |
| 397 * property change event with the type {@code name + 'Change'} is fired. |
| 398 * @param {!Object} obj The object to define the property for. |
| 399 * @param {string} name The name of the property. |
| 400 * @param {base.PropertyKind=} opt_kind What kind of underlying storage to |
| 401 * use. |
| 402 * @param {function(*):void} opt_setHook A function to run after the |
| 403 * property is set, but before the propertyChange event is fired. |
| 404 */ |
| 405 function defineProperty(obj, name, opt_kind, opt_setHook) { |
| 406 if (typeof obj == 'function') |
| 407 obj = obj.prototype; |
| 408 |
| 409 var kind = opt_kind || PropertyKind.JS; |
| 410 |
| 411 if (!obj.__lookupGetter__(name)) |
| 412 obj.__defineGetter__(name, getGetter(name, kind)); |
| 413 |
| 414 if (!obj.__lookupSetter__(name)) |
| 415 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook)); |
| 416 } |
| 417 |
| 418 /** |
| 419 * Counter for use with createUid |
| 420 */ |
| 421 var uidCounter = 1; |
| 422 |
| 423 /** |
| 424 * @return {number} A new unique ID. |
| 425 */ |
| 426 function createUid() { |
| 427 return uidCounter++; |
| 428 } |
| 429 |
| 430 /** |
| 431 * Returns a unique ID for the item. This mutates the item so it needs to be |
| 432 * an object |
| 433 * @param {!Object} item The item to get the unique ID for. |
| 434 * @return {number} The unique ID for the item. |
| 435 */ |
| 436 function getUid(item) { |
| 437 if (item.hasOwnProperty('uid')) |
| 438 return item.uid; |
| 439 return item.uid = createUid(); |
| 440 } |
| 441 |
| 442 /** |
| 443 * Dispatches a simple event on an event target. |
| 444 * @param {!EventTarget} target The event target to dispatch the event on. |
| 445 * @param {string} type The type of the event. |
| 446 * @param {boolean=} opt_bubbles Whether the event bubbles or not. |
| 447 * @param {boolean=} opt_cancelable Whether the default action of the event |
| 448 * can be prevented. |
| 449 * @return {boolean} If any of the listeners called {@code preventDefault} |
| 450 * during the dispatch this will return false. |
| 451 */ |
| 452 function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { |
| 453 var e = new base.Event(type, opt_bubbles, opt_cancelable); |
| 454 return target.dispatchEvent(e); |
| 455 } |
| 456 |
| 457 /** |
| 458 * Adds a {@code getInstance} static method that always return the same |
| 459 * instance object. |
| 460 * @param {!Function} ctor The constructor for the class to add the static |
| 461 * method to. |
| 462 */ |
| 463 function addSingletonGetter(ctor) { |
| 464 ctor.getInstance = function() { |
| 465 return ctor.instance_ || (ctor.instance_ = new ctor()); |
| 466 }; |
| 467 } |
| 468 |
| 469 /** |
| 470 * Creates a new event to be used with base.EventTarget or DOM EventTarget |
| 471 * objects. |
| 472 * @param {string} type The name of the event. |
| 473 * @param {boolean=} opt_bubbles Whether the event bubbles. |
| 474 * Default is false. |
| 475 * @param {boolean=} opt_preventable Whether the default action of the event |
| 476 * can be prevented. |
| 477 * @constructor |
| 478 * @extends {Event} |
| 479 */ |
| 480 function Event(type, opt_bubbles, opt_preventable) { |
| 481 var e = base.doc.createEvent('Event'); |
| 482 e.initEvent(type, !!opt_bubbles, !!opt_preventable); |
| 483 e.__proto__ = global.Event.prototype; |
| 484 return e; |
| 485 }; |
| 486 |
| 487 /** |
| 488 * Initialization which must be deferred until run-time. |
| 489 */ |
| 490 function initialize() { |
| 491 // If 'document' isn't defined, then we must be being pre-compiled, |
| 492 // so set a trap so that we're initialized on first access at run-time. |
| 493 if (!global.document) { |
| 494 var originalCr = cr; |
| 495 |
| 496 Object.defineProperty(global, 'cr', { |
| 497 get: function() { |
| 498 Object.defineProperty(global, 'cr', {value: originalCr}); |
| 499 originalBase.initialize(); |
| 500 return originalCr; |
| 501 }, |
| 502 configurable: true |
| 503 }); |
| 504 |
| 505 return; |
| 506 } |
| 507 |
| 508 Event.prototype = {__proto__: global.Event.prototype}; |
| 509 |
| 510 base.doc = document; |
| 511 |
| 512 base.isMac = /Mac/.test(navigator.platform); |
| 513 base.isWindows = /Win/.test(navigator.platform); |
| 514 base.isChromeOS = /CrOS/.test(navigator.userAgent); |
| 515 base.isLinux = /Linux/.test(navigator.userAgent); |
| 516 base.isGTK = /GTK/.test(chrome.toolkit); |
| 517 base.isViews = /views/.test(chrome.toolkit); |
| 518 |
| 519 setModuleBasePath('/src'); |
| 520 } |
| 521 |
| 522 function asArray(arrayish) { |
| 523 var values = []; |
| 524 for (var i = 0; i < arrayish.length; i++) |
| 525 values.push(arrayish[i]); |
| 526 return values; |
| 527 } |
| 528 |
| 529 function concatenateArrays(/*arguments*/) { |
| 530 var values = []; |
| 531 for (var i = 0; i < arguments.length; i++) { |
| 532 if(!(arguments[i] instanceof Array)) |
| 533 throw new Error('Arguments ' + i + 'is not an array'); |
| 534 values.push.apply(values, arguments[i]); |
| 535 } |
| 536 return values; |
| 537 } |
| 538 |
| 539 function dictionaryKeys(dict) { |
| 540 var keys = []; |
| 541 for (var key in dict) |
| 542 keys.push(key); |
| 543 return keys; |
| 544 } |
| 545 |
| 546 function dictionaryValues(dict) { |
| 547 var values = []; |
| 548 for (var key in dict) |
| 549 values.push(dict[key]); |
| 550 return values; |
| 551 } |
| 552 |
| 553 /** |
| 554 * Maps types to a given value. |
| 555 * @constructor |
| 556 */ |
| 557 function TypeMap() { |
| 558 this.types = []; |
| 559 this.values = []; |
| 560 } |
| 561 TypeMap.prototype = { |
| 562 __proto__: Object.prototype, |
| 563 |
| 564 add: function(type, value) { |
| 565 this.types.push(type); |
| 566 this.values.push(value); |
| 567 }, |
| 568 |
| 569 get: function(instance) { |
| 570 for (var i = 0; i < this.types.length; i++) { |
| 571 if (instance instanceof this.types[i]) |
| 572 return this.values[i]; |
| 573 } |
| 574 return undefined; |
| 575 } |
| 576 }; |
| 577 |
| 578 return { |
| 579 set moduleBasePath(path) { |
| 580 setModuleBasePath(path); |
| 581 }, |
| 582 |
| 583 get moduleBasePath() { |
| 584 return moduleBasePath; |
| 585 }, |
| 586 |
| 587 require: require, |
| 588 requireStylesheet: requireStylesheet, |
| 589 requireRawScript: requireRawScript, |
| 590 exportTo: exportTo, |
| 591 |
| 592 addSingletonGetter: addSingletonGetter, |
| 593 createUid: createUid, |
| 594 defineProperty: defineProperty, |
| 595 dispatchPropertyChange: dispatchPropertyChange, |
| 596 dispatchSimpleEvent: dispatchSimpleEvent, |
| 597 Event: Event, |
| 598 getUid: getUid, |
| 599 initialize: initialize, |
| 600 PropertyKind: PropertyKind, |
| 601 asArray: asArray, |
| 602 concatenateArrays: concatenateArrays, |
| 603 dictionaryKeys: dictionaryKeys, |
| 604 dictionaryValues: dictionaryValues, |
| 605 TypeMap: TypeMap, |
| 606 }; |
| 607 })(); |
| 608 |
| 609 |
| 610 /** |
| 611 * TODO(kgr): Move this to another file which is to be loaded last. |
| 612 * This will be done as part of future work to make this code pre-compilable. |
| 613 */ |
| 614 base.initialize(); |
OLD | NEW |