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.
16 /** Platform, package, object property, and Event support. */
17 this.base = (function() {
20 * Base path for modules. Used to form URLs for module 'require' requests.
22 var moduleBasePath = '.';
23 function setModuleBasePath(path) {
24 if (path[path.length - 1] == '/')
25 path = path.substring(0, path.length - 1);
26 moduleBasePath = path;
30 function mLog(text, opt_indentLevel) {
35 var indentLevel = opt_indentLevel || 0;
36 for (var i = 0; i < indentLevel; i++)
38 console.log(spacing + text);
42 * Builds an object structure for the provided namespace path,
43 * ensuring that names that already exist are not overwritten. For
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}.
52 function exportPath(name, opt_object, opt_objectToExportTo) {
53 var parts = name.split('.');
54 var cur = opt_objectToExportTo || global;
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) {
69 var didLoadModules = false;
70 var moduleDependencies = {};
71 var moduleStylesheets = {};
72 var moduleRawScripts = {};
74 function addModuleDependency(moduleName, dependentModuleName) {
75 if (!moduleDependencies[moduleName])
76 moduleDependencies[moduleName] = [];
78 var dependentModules = moduleDependencies[moduleName];
80 for (var i = 0; i < dependentModules.length; i++)
81 if (dependentModules[i] == dependentModuleName)
84 dependentModules.push(dependentModuleName);
87 function addModuleRawScriptDependency(moduleName, rawScriptName) {
88 if (!moduleRawScripts[moduleName])
89 moduleRawScripts[moduleName] = [];
91 var dependentRawScripts = moduleRawScripts[moduleName];
93 for (var i = 0; i < moduleRawScripts.length; i++)
94 if (dependentRawScripts[i] == rawScriptName)
97 dependentRawScripts.push(rawScriptName);
100 function addModuleStylesheet(moduleName, stylesheetName) {
101 if (!moduleStylesheets[moduleName])
102 moduleStylesheets[moduleName] = [];
104 var stylesheets = moduleStylesheets[moduleName];
106 for (var i = 0; i < stylesheets.length; i++)
107 if (stylesheets[i] == stylesheetName)
110 stylesheets.push(stylesheetName);
113 function ensureDepsLoaded() {
116 didLoadModules = true;
118 var req = new XMLHttpRequest();
119 var src = moduleBasePath + '/base/' + 'deps.js';
120 req.open('GET', src, false);
122 if (req.status != 200)
123 throw new Error('Could not find ' + src +
124 '. Run calcdeps.py and try again.');
126 base.addModuleDependency = addModuleDependency;
127 base.addModuleRawScriptDependency = addModuleRawScriptDependency;
128 base.addModuleStylesheet = addModuleStylesheet;
130 // By construction, the deps file should call addModuleDependency.
131 eval(req.responseText);
133 throw new Error('When loading deps, got ' + e.stack ? e.stack : e);
135 delete base.addModuleStylesheet;
136 delete base.addModuleRawScriptDependency;
137 delete base.addModuleDependency;
141 var moduleLoadStatus = {};
142 var rawScriptLoadStatus = {};
143 function require(dependentModuleName, opt_indentLevel) {
144 var indentLevel = opt_indentLevel || 0;
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');
156 mLog('require(' + dependentModuleName + ')', indentLevel);
158 if (moduleLoadStatus[dependentModuleName] == 'APPENDED')
160 if (moduleLoadStatus[dependentModuleName] == 'RESOLVING')
161 throw new Error('Circular dependency betwen modules. Cannot continue!');
162 moduleLoadStatus[dependentModuleName] = 'RESOLVING';
164 // Load the module stylesheet first.
165 var stylesheets = moduleStylesheets[dependentModuleName] || [];
166 for (var i = 0; i < stylesheets.length; i++)
167 requireStylesheet(stylesheets[i]);
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])
176 mLog('load(' + rawScriptName + ')', indentLevel);
177 var src = moduleBasePath + '/' + rawScriptName;
178 var text = '<script type="text/javascript" src="' + src +
180 base.doc.write(text);
181 rawScriptLoadStatus[rawScriptName] = 'APPENDED';
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);
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 +
196 base.doc.write(text);
197 moduleLoadStatus[dependentModuleName] = 'APPENDED';
201 * Adds a dependency on a raw javascript file, e.g. a third party
203 * @param {String} rawScriptName The path to the script file, relative to
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');
216 if (rawScriptLoadStatus[rawScriptPath])
218 throw new Error(rawScriptPath + ' should already have been loaded.' +
219 ' Did you forget to run calcdeps.py?');
222 var stylesheetLoadStatus = {};
223 function requireStylesheet(dependentStylesheetName) {
224 if (window.FLATTENED)
227 if (stylesheetLoadStatus[dependentStylesheetName])
229 stylesheetLoadStatus[dependentStylesheetName] = true;
230 var localPath = dependentStylesheetName.replace(/\./g, '/') + '.css';
231 var stylesheetPath = moduleBasePath + '/' + localPath;
233 var linkEl = document.createElement('link');
234 linkEl.setAttribute('rel', 'stylesheet');
235 linkEl.setAttribute('href', stylesheetPath);
236 base.doc.head.appendChild(linkEl);
239 function exportTo(namespace, fn) {
240 var obj = exportPath(namespace);
244 console.log('While running exports for ', name, ':');
245 console.log(e.stack || e);
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
253 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
255 if (propertyDescriptor) {
256 Object.defineProperty(obj, propertyName, propertyDescriptor);
257 mLog(' +' + propertyName);
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.
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);
278 * Converts a camelCase javascript property name to a hyphenated-lower-case
280 * @param {string} jsName The javascript camelCase property name.
281 * @return {string} The equivalent hyphenated-lower-case attribute name.
283 function getAttributeName(jsName) {
284 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
288 * The kind of property to define in {@code defineProperty}.
294 * Plain old JS property where the backing data is stored as a 'private'
295 * field on the object.
300 * The property backing data is stored as an attribute on an element.
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.
308 BOOL_ATTR: 'boolAttr'
312 * Helper function for defineProperty that returns the getter to use for the
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.
318 function getGetter(name, kind) {
320 case PropertyKind.JS:
321 var privateName = name + '_';
323 return this[privateName];
325 case PropertyKind.ATTR:
326 var attributeName = getAttributeName(name);
328 return this.getAttribute(attributeName);
330 case PropertyKind.BOOL_ATTR:
331 var attributeName = getAttributeName(name);
333 return this.hasAttribute(attributeName);
339 * Helper function for defineProperty that returns the setter of the right
341 * @param {string} name The name of the property we are defining the setter
343 * @param {base.PropertyKind} kind The kind of property we are getting the
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.
349 function getSetter(name, kind, opt_setHook) {
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;
358 opt_setHook.call(this, value, oldValue);
359 dispatchPropertyChange(this, name, value, oldValue);
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);
371 this.setAttribute(attributeName, value);
373 opt_setHook.call(this, value, oldValue);
374 dispatchPropertyChange(this, name, value, oldValue);
378 case PropertyKind.BOOL_ATTR:
379 var attributeName = getAttributeName(name);
380 return function(value) {
381 var oldValue = this[attributeName];
382 if (value !== oldValue) {
384 this.setAttribute(attributeName, name);
386 this.removeAttribute(attributeName);
388 opt_setHook.call(this, value, oldValue);
389 dispatchPropertyChange(this, name, value, oldValue);
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
402 * @param {function(*):void} opt_setHook A function to run after the
403 * property is set, but before the propertyChange event is fired.
405 function defineProperty(obj, name, opt_kind, opt_setHook) {
406 if (typeof obj == 'function')
409 var kind = opt_kind || PropertyKind.JS;
411 if (!obj.__lookupGetter__(name))
412 obj.__defineGetter__(name, getGetter(name, kind));
414 if (!obj.__lookupSetter__(name))
415 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
419 * Counter for use with createUid
424 * @return {number} A new unique ID.
426 function createUid() {
431 * Returns a unique ID for the item. This mutates the item so it needs to be
433 * @param {!Object} item The item to get the unique ID for.
434 * @return {number} The unique ID for the item.
436 function getUid(item) {
437 if (item.hasOwnProperty('uid'))
439 return item.uid = createUid();
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
449 * @return {boolean} If any of the listeners called {@code preventDefault}
450 * during the dispatch this will return false.
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);
458 * Adds a {@code getInstance} static method that always return the same
460 * @param {!Function} ctor The constructor for the class to add the static
463 function addSingletonGetter(ctor) {
464 ctor.getInstance = function() {
465 return ctor.instance_ || (ctor.instance_ = new ctor());
470 * Creates a new event to be used with base.EventTarget or DOM EventTarget
472 * @param {string} type The name of the event.
473 * @param {boolean=} opt_bubbles Whether the event bubbles.
475 * @param {boolean=} opt_preventable Whether the default action of the event
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;
488 * Initialization which must be deferred until run-time.
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) {
496 Object.defineProperty(global, 'cr', {
498 Object.defineProperty(global, 'cr', {value: originalCr});
499 originalBase.initialize();
508 Event.prototype = {__proto__: global.Event.prototype};
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);
519 setModuleBasePath('/src');
522 function asArray(arrayish) {
524 for (var i = 0; i < arrayish.length; i++)
525 values.push(arrayish[i]);
529 function concatenateArrays(/*arguments*/) {
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]);
539 function dictionaryKeys(dict) {
541 for (var key in dict)
546 function dictionaryValues(dict) {
548 for (var key in dict)
549 values.push(dict[key]);
554 * Maps types to a given value.
561 TypeMap.prototype = {
562 __proto__: Object.prototype,
564 add: function(type, value) {
565 this.types.push(type);
566 this.values.push(value);
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];
579 set moduleBasePath(path) {
580 setModuleBasePath(path);
583 get moduleBasePath() {
584 return moduleBasePath;
588 requireStylesheet: requireStylesheet,
589 requireRawScript: requireRawScript,
592 addSingletonGetter: addSingletonGetter,
593 createUid: createUid,
594 defineProperty: defineProperty,
595 dispatchPropertyChange: dispatchPropertyChange,
596 dispatchSimpleEvent: dispatchSimpleEvent,
599 initialize: initialize,
600 PropertyKind: PropertyKind,
602 concatenateArrays: concatenateArrays,
603 dictionaryKeys: dictionaryKeys,
604 dictionaryValues: dictionaryValues,
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.