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 |