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 */ 13var global = this; 14 15 16/** Platform, package, object property, and Event support. */ 17this.base = (function() { 18 /** 19 * Base path for modules. Used to form URLs for module 'require' requests. 20 */ 21 var moduleBasePath = '.'; 22 function setModuleBasePath(path) { 23 if (path[path.length - 1] == '/') 24 path = path.substring(0, path.length - 1); 25 moduleBasePath = path; 26 } 27 28 function mLog(text, opt_indentLevel) { 29 if (true) 30 return; 31 32 var spacing = ''; 33 var indentLevel = opt_indentLevel || 0; 34 for (var i = 0; i < indentLevel; i++) 35 spacing += ' '; 36 console.log(spacing + text); 37 } 38 39 /** 40 * Builds an object structure for the provided namespace path, 41 * ensuring that names that already exist are not overwritten. For 42 * example: 43 * 'a.b.c' -> a = {};a.b={};a.b.c={}; 44 * @param {string} name Name of the object that this file defines. 45 * @param {*=} opt_object The object to expose at the end of the path. 46 * @param {Object=} opt_objectToExportTo The object to add the path to; 47 * default is {@code global}. 48 * @private 49 */ 50 function exportPath(name, opt_object, opt_objectToExportTo) { 51 var parts = name.split('.'); 52 var cur = opt_objectToExportTo || global; 53 54 for (var part; parts.length && (part = parts.shift());) { 55 if (!parts.length && opt_object !== undefined) { 56 // last part and we have an object; use it 57 cur[part] = opt_object; 58 } else if (part in cur) { 59 cur = cur[part]; 60 } else { 61 cur = cur[part] = {}; 62 } 63 } 64 return cur; 65 }; 66 67 var didLoadModules = false; 68 var moduleDependencies = {}; 69 var moduleStylesheets = {}; 70 var moduleRawScripts = {}; 71 72 function addModuleDependency(moduleName, dependentModuleName) { 73 if (!moduleDependencies[moduleName]) 74 moduleDependencies[moduleName] = []; 75 76 var dependentModules = moduleDependencies[moduleName]; 77 var found = false; 78 for (var i = 0; i < dependentModules.length; i++) 79 if (dependentModules[i] == dependentModuleName) 80 found = true; 81 if (!found) 82 dependentModules.push(dependentModuleName); 83 } 84 85 function addModuleRawScriptDependency(moduleName, rawScriptName) { 86 if (!moduleRawScripts[moduleName]) 87 moduleRawScripts[moduleName] = []; 88 89 var dependentRawScripts = moduleRawScripts[moduleName]; 90 var found = false; 91 for (var i = 0; i < moduleRawScripts.length; i++) 92 if (dependentRawScripts[i] == rawScriptName) 93 found = true; 94 if (!found) 95 dependentRawScripts.push(rawScriptName); 96 } 97 98 function addModuleStylesheet(moduleName, stylesheetName) { 99 if (!moduleStylesheets[moduleName]) 100 moduleStylesheets[moduleName] = []; 101 102 var stylesheets = moduleStylesheets[moduleName]; 103 var found = false; 104 for (var i = 0; i < stylesheets.length; i++) 105 if (stylesheets[i] == stylesheetName) 106 found = true; 107 if (!found) 108 stylesheets.push(stylesheetName); 109 } 110 111 function ensureDepsLoaded() { 112 if (window.FLATTENED) 113 return; 114 115 if (didLoadModules) 116 return; 117 didLoadModules = true; 118 119 var req = new XMLHttpRequest(); 120 var src = '/deps.js'; 121 req.open('GET', src, false); 122 req.send(null); 123 if (req.status != 200) { 124 var serverSideException = JSON.parse(req.responseText); 125 var msg = 'You have a module problem: ' + 126 serverSideException.message; 127 var baseWarningEl = document.createElement('div'); 128 baseWarningEl.style.position = 'fixed'; 129 baseWarningEl.style.border = '3px solid red'; 130 baseWarningEl.style.color = 'black'; 131 baseWarningEl.style.padding = '8px'; 132 baseWarningEl.innerHTML = 133 '<h2>Module parsing problem</h2>' + 134 '<div id="message"></div>' + 135 '<pre id="details"></pre>'; 136 baseWarningEl.querySelector('#message').textContent = 137 serverSideException.message; 138 var detailsEl = baseWarningEl.querySelector('#details'); 139 detailsEl.textContent = serverSideException.details; 140 detailsEl.style.maxWidth = '800px'; 141 detailsEl.style.overflow = 'auto'; 142 143 if (!document.body) { 144 setTimeout(function() { 145 document.body.appendChild(baseWarningEl); 146 }, 150); 147 } else { 148 document.body.appendChild(baseWarningEl); 149 } 150 throw new Error(msg); 151 } 152 153 base.addModuleDependency = addModuleDependency; 154 base.addModuleRawScriptDependency = addModuleRawScriptDependency; 155 base.addModuleStylesheet = addModuleStylesheet; 156 try { 157 // By construction, the deps should call addModuleDependency. 158 eval(req.responseText); 159 } catch (e) { 160 throw new Error('When loading deps, got ' + 161 e.stack ? e.stack : e.message); 162 } 163 delete base.addModuleStylesheet; 164 delete base.addModuleRawScriptDependency; 165 delete base.addModuleDependency; 166 } 167 168 // TODO(dsinclair): Remove this when HTML imports land as the templates 169 // will be pulled in by the requireTemplate calls. 170 var templatesLoaded_ = false; 171 function ensureTemplatesLoaded() { 172 if (templatesLoaded_ || window.FLATTENED) 173 return; 174 templatesLoaded_ = true; 175 176 var req = new XMLHttpRequest(); 177 req.open('GET', '/templates', false); 178 req.send(null); 179 180 var elem = document.createElement('div'); 181 elem.innerHTML = req.responseText; 182 while (elem.hasChildNodes()) 183 document.head.appendChild(elem.removeChild(elem.firstChild)); 184 } 185 186 var moduleLoadStatus = {}; 187 var rawScriptLoadStatus = {}; 188 function require(modules, opt_indentLevel) { 189 var indentLevel = opt_indentLevel || 0; 190 var dependentModules = modules; 191 if (!(modules instanceof Array)) 192 dependentModules = [modules]; 193 194 ensureDepsLoaded(); 195 ensureTemplatesLoaded(); 196 197 dependentModules.forEach(function(module) { 198 requireModule(module, indentLevel); 199 }); 200 } 201 202 var modulesWaiting = []; 203 function requireModule(dependentModuleName, indentLevel) { 204 if (window.FLATTENED) { 205 if (!window.FLATTENED[dependentModuleName]) { 206 throw new Error('Somehow, module ' + dependentModuleName + 207 ' didn\'t get stored in the flattened js file! ' + 208 'You may need to rerun ' + 209 'build/generate_about_tracing_contents.py'); 210 } 211 return; 212 } 213 214 if (moduleLoadStatus[dependentModuleName] == 'APPENDED') 215 return; 216 217 if (moduleLoadStatus[dependentModuleName] == 'RESOLVING') 218 return; 219 220 mLog('require(' + dependentModuleName + ')', indentLevel); 221 moduleLoadStatus[dependentModuleName] = 'RESOLVING'; 222 requireDependencies(dependentModuleName, indentLevel); 223 224 loadScript(dependentModuleName.replace(/\./g, '/') + '.js'); 225 moduleLoadStatus[name] = 'APPENDED'; 226 } 227 228 function requireDependencies(dependentModuleName, indentLevel) { 229 // Load the module's dependent scripts after. 230 var dependentModules = moduleDependencies[dependentModuleName] || []; 231 require(dependentModules, indentLevel + 1); 232 233 // Load the module stylesheet first. 234 var stylesheets = moduleStylesheets[dependentModuleName] || []; 235 for (var i = 0; i < stylesheets.length; i++) 236 requireStylesheet(stylesheets[i]); 237 238 // Load the module raw scripts next 239 var rawScripts = moduleRawScripts[dependentModuleName] || []; 240 for (var i = 0; i < rawScripts.length; i++) { 241 var rawScriptName = rawScripts[i]; 242 if (rawScriptLoadStatus[rawScriptName]) 243 continue; 244 245 loadScript(rawScriptName); 246 mLog('load(' + rawScriptName + ')', indentLevel); 247 rawScriptLoadStatus[rawScriptName] = 'APPENDED'; 248 } 249 } 250 251 function loadScript(path) { 252 var scriptEl = document.createElement('script'); 253 scriptEl.src = moduleBasePath + '/' + path; 254 scriptEl.type = 'text/javascript'; 255 scriptEl.defer = true; 256 scriptEl.async = false; 257 base.doc.head.appendChild(scriptEl); 258 } 259 260 /** 261 * Adds a dependency on a raw javascript file, e.g. a third party 262 * library. 263 * @param {String} rawScriptName The path to the script file, relative to 264 * moduleBasePath. 265 */ 266 function requireRawScript(rawScriptPath) { 267 if (window.FLATTENED_RAW_SCRIPTS) { 268 if (!window.FLATTENED_RAW_SCRIPTS[rawScriptPath]) { 269 throw new Error('Somehow, ' + rawScriptPath + 270 ' didn\'t get stored in the flattened js file! ' + 271 'You may need to rerun build/generate_about_tracing_contents.py'); 272 } 273 return; 274 } 275 276 if (rawScriptLoadStatus[rawScriptPath]) 277 return; 278 throw new Error(rawScriptPath + ' should already have been loaded.' + 279 ' Did you forget to run build/generate_about_tracing_contents.py?'); 280 } 281 282 var stylesheetLoadStatus = {}; 283 function requireStylesheet(dependentStylesheetName) { 284 if (window.FLATTENED) 285 return; 286 287 if (stylesheetLoadStatus[dependentStylesheetName]) 288 return; 289 stylesheetLoadStatus[dependentStylesheetName] = true; 290 291 var localPath = dependentStylesheetName.replace(/\./g, '/') + '.css'; 292 var stylesheetPath = moduleBasePath + '/' + localPath; 293 294 var linkEl = document.createElement('link'); 295 linkEl.setAttribute('rel', 'stylesheet'); 296 linkEl.setAttribute('href', stylesheetPath); 297 base.doc.head.appendChild(linkEl); 298 } 299 300 var templateLoadStatus = {}; 301 function requireTemplate(template) { 302 if (window.FLATTENED) 303 return; 304 305 if (templateLoadStatus[template]) 306 return; 307 templateLoadStatus[template] = true; 308 309 var localPath = template.replace(/\./g, '/') + '.html'; 310 var importPath = moduleBasePath + '/' + localPath; 311 312 var linkEl = document.createElement('link'); 313 linkEl.setAttribute('rel', 'import'); 314 linkEl.setAttribute('href', importPath); 315 // TODO(dsinclair): Enable when HTML imports are available. 316 //base.doc.head.appendChild(linkEl); 317 } 318 319 function exportTo(namespace, fn) { 320 var obj = exportPath(namespace); 321 try { 322 var exports = fn(); 323 } catch (e) { 324 console.log('While running exports for ', namespace, ':'); 325 console.log(e.stack || e); 326 return; 327 } 328 329 for (var propertyName in exports) { 330 // Maybe we should check the prototype chain here? The current usage 331 // pattern is always using an object literal so we only care about own 332 // properties. 333 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, 334 propertyName); 335 if (propertyDescriptor) { 336 Object.defineProperty(obj, propertyName, propertyDescriptor); 337 mLog(' +' + propertyName); 338 } 339 } 340 }; 341 342 /** 343 * Initialization which must be deferred until run-time. 344 */ 345 function initialize() { 346 // If 'document' isn't defined, then we must be being pre-compiled, 347 // so set a trap so that we're initialized on first access at run-time. 348 if (!global.document) { 349 var originalBase = base; 350 351 Object.defineProperty(global, 'base', { 352 get: function() { 353 Object.defineProperty(global, 'base', {value: originalBase}); 354 originalBase.initialize(); 355 return originalBase; 356 }, 357 configurable: true 358 }); 359 360 return; 361 } 362 363 base.doc = document; 364 365 base.isMac = /Mac/.test(navigator.platform); 366 base.isWindows = /Win/.test(navigator.platform); 367 base.isChromeOS = /CrOS/.test(navigator.userAgent); 368 base.isLinux = /Linux/.test(navigator.userAgent); 369 base.isGTK = /GTK/.test(chrome.toolkit); 370 base.isViews = /views/.test(chrome.toolkit); 371 372 setModuleBasePath('/src'); 373 } 374 375 return { 376 set moduleBasePath(path) { 377 setModuleBasePath(path); 378 }, 379 380 get moduleBasePath() { 381 return moduleBasePath; 382 }, 383 384 initialize: initialize, 385 386 require: require, 387 requireStylesheet: requireStylesheet, 388 requireRawScript: requireRawScript, 389 requireTemplate: requireTemplate, 390 exportTo: exportTo 391 }; 392})(); 393 394base.initialize(); 395