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.tvcm = (function() {
18 function mLog(text, opt_indentLevel) {
23 var indentLevel = opt_indentLevel || 0;
24 for (var i = 0; i < indentLevel; i++)
26 console.log(spacing + text);
30 * Builds an object structure for the provided namespace path,
31 * ensuring that names that already exist are not overwritten. For
33 * 'a.b.c' -> a = {};a.b={};a.b.c={};
34 * @param {string} name Name of the object that this file defines.
35 * @param {*=} opt_object The object to expose at the end of the path.
36 * @param {Object=} opt_objectToExportTo The object to add the path to;
37 * default is {@code global}.
40 function exportPath(name, opt_object, opt_objectToExportTo) {
41 var parts = name.split('.');
42 var cur = opt_objectToExportTo || global;
44 for (var part; parts.length && (part = parts.shift());) {
45 if (!parts.length && opt_object !== undefined) {
46 // last part and we have an object; use it
47 cur[part] = opt_object;
48 } else if (part in cur) {
57 var didLoadModules = false;
58 var moduleDependencies = {};
59 var moduleStylesheets = {};
60 var moduleRawScripts = {};
61 var resourceFileNames = {};
63 function setResourceFileName(moduleName, relativeFileName) {
64 if (resourceFileNames[moduleName] !== undefined)
65 throw new Error('Cannot set file name twice!');
66 resourceFileNames[moduleName] = relativeFileName;
69 function addModuleDependency(moduleName, dependentModuleName) {
70 if (!moduleDependencies[moduleName])
71 moduleDependencies[moduleName] = [];
73 var dependentModules = moduleDependencies[moduleName];
75 for (var i = 0; i < dependentModules.length; i++)
76 if (dependentModules[i] == dependentModuleName)
79 dependentModules.push(dependentModuleName);
82 function addModuleRawScriptDependency(moduleName, rawScriptFilename) {
83 if (!moduleRawScripts[moduleName])
84 moduleRawScripts[moduleName] = [];
86 var dependentRawScripts = moduleRawScripts[moduleName];
88 for (var i = 0; i < moduleRawScripts.length; i++)
89 if (dependentRawScripts[i] == rawScriptFilename)
92 dependentRawScripts.push(rawScriptFilename);
95 function addModuleStylesheet(moduleName, stylesheetName) {
96 if (!moduleStylesheets[moduleName])
97 moduleStylesheets[moduleName] = [];
99 var stylesheets = moduleStylesheets[moduleName];
101 for (var i = 0; i < stylesheets.length; i++)
102 if (stylesheets[i] == stylesheetName)
105 stylesheets.push(stylesheetName);
108 function ensureDepsLoaded() {
109 if (window.FLATTENED)
114 didLoadModules = true;
116 var req = new XMLHttpRequest();
117 var src = '/tvcm/deps.js';
118 req.open('GET', src, false);
120 if (req.status != 200) {
121 var serverSideException = JSON.parse(req.responseText);
122 var msg = 'You have a module problem: ' +
123 serverSideException.message;
124 showPanic(serverSideException.message,
125 serverSideException.details);
126 throw new Error(msg);
129 tvcm.setResourceFileName = setResourceFileName;
130 tvcm.addModuleDependency = addModuleDependency;
131 tvcm.addModuleRawScriptDependency = addModuleRawScriptDependency;
132 tvcm.addModuleStylesheet = addModuleStylesheet;
134 // By construction, the deps should call addModuleDependency.
135 eval(req.responseText);
137 throw new Error('When loading deps, got ' +
138 e.stack ? e.stack : e.message);
140 delete tvcm.setResourceFileName;
141 delete tvcm.addModuleStylesheet;
142 delete tvcm.addModuleRawScriptDependency;
143 delete tvcm.addModuleDependency;
146 var panicElement = undefined;
147 var rawPanicMessages = [];
148 function showPanicElementIfNeeded() {
152 var panicOverlay = document.createElement('div');
153 panicOverlay.style.backgroundColor = 'white';
154 panicOverlay.style.border = '3px solid red';
155 panicOverlay.style.boxSizing = 'border-box';
156 panicOverlay.style.color = 'black';
157 panicOverlay.style.display = '-webkit-flex';
158 panicOverlay.style.height = '100%';
159 panicOverlay.style.left = 0;
160 panicOverlay.style.padding = '8px';
161 panicOverlay.style.position = 'fixed';
162 panicOverlay.style.top = 0;
163 panicOverlay.style.webkitFlexDirection = 'column';
164 panicOverlay.style.width = '100%';
166 panicElement = document.createElement('div');
167 panicElement.style.webkitFlex = '1 1 auto';
168 panicElement.style.overflow = 'auto';
169 panicOverlay.appendChild(panicElement);
171 if (!document.body) {
172 setTimeout(function() {
173 document.body.appendChild(panicOverlay);
176 document.body.appendChild(panicOverlay);
180 function showPanic(panicTitle, panicDetails) {
181 showPanicElementIfNeeded();
182 var panicMessageEl = document.createElement('div');
183 panicMessageEl.innerHTML =
184 '<h2 id="message"></h2>' +
185 '<pre id="details"></pre>';
186 panicMessageEl.querySelector('#message').textContent = panicTitle;
187 panicMessageEl.querySelector('#details').textContent = panicDetails;
188 panicElement.appendChild(panicMessageEl);
190 rawPanicMessages.push({
192 details: panicDetails
196 function hasPanic() {
197 return rawPanicMessages.length !== 0;
199 function getPanicText() {
200 return rawPanicMessages.map(function(msg) {
205 // TODO(dsinclair): Remove this when HTML imports land as the templates
206 // will be pulled in by the requireTemplate calls.
207 var templatesLoaded_ = false;
208 function ensureTemplatesLoaded() {
209 if (templatesLoaded_ || window.FLATTENED)
211 templatesLoaded_ = true;
213 var req = new XMLHttpRequest();
214 req.open('GET', '/tvcm/all_templates.html', false);
217 var elem = document.createElement('div');
218 elem.innerHTML = req.responseText;
219 while (elem.hasChildNodes())
220 document.head.appendChild(elem.removeChild(elem.firstChild));
223 var moduleLoadStatus = {};
224 var rawScriptLoadStatus = {};
225 function require(modules, opt_indentLevel) {
226 var indentLevel = opt_indentLevel || 0;
227 var dependentModules = modules;
228 if (!(modules instanceof Array))
229 dependentModules = [modules];
232 ensureTemplatesLoaded();
234 dependentModules.forEach(function(module) {
235 requireModule(module, indentLevel);
239 var modulesWaiting = [];
240 function requireModule(dependentModuleName, indentLevel) {
241 if (dependentModuleName == 'tvcm')
244 if (window.FLATTENED) {
245 if (!window.FLATTENED[dependentModuleName]) {
246 throw new Error('Somehow, module ' + dependentModuleName +
247 ' didn\'t get stored in the flattened js file! ' +
248 'You have likely found a tvcm bug.');
253 if (moduleLoadStatus[dependentModuleName] == 'APPENDED')
256 if (moduleLoadStatus[dependentModuleName] == 'RESOLVING')
259 mLog('require(' + dependentModuleName + ')', indentLevel);
260 moduleLoadStatus[dependentModuleName] = 'RESOLVING';
261 requireDependencies(dependentModuleName, indentLevel);
263 if (resourceFileNames[dependentModuleName] === undefined)
264 throw new Error('Not sure what filename is for ' + dependentModuleName);
265 loadScript(resourceFileNames[dependentModuleName]);
266 moduleLoadStatus[name] = 'APPENDED';
269 function requireDependencies(dependentModuleName, indentLevel) {
270 // Load the module's dependent scripts after.
271 var dependentModules = moduleDependencies[dependentModuleName] || [];
272 require(dependentModules, indentLevel + 1);
274 // Load the module stylesheet first.
275 var stylesheets = moduleStylesheets[dependentModuleName] || [];
276 for (var i = 0; i < stylesheets.length; i++)
277 requireStylesheet(stylesheets[i]);
279 // Load the module raw scripts next
280 var rawScripts = moduleRawScripts[dependentModuleName] || [];
281 for (var i = 0; i < rawScripts.length; i++) {
282 var rawScriptFilename = rawScripts[i];
283 if (rawScriptLoadStatus[rawScriptFilename])
286 loadScript(rawScriptFilename);
287 mLog('load(' + rawScriptFilename + ')', indentLevel);
288 rawScriptLoadStatus[rawScriptFilename] = 'APPENDED';
292 function loadScript(path) {
293 var scriptEl = document.createElement('script');
294 scriptEl.src = '/' + path;
295 scriptEl.type = 'text/javascript';
296 scriptEl.defer = true;
297 scriptEl.async = false;
298 tvcm.doc.head.appendChild(scriptEl);
302 * Adds a dependency on a raw javascript file, e.g. a third party
304 * @param {String} rawScriptFilename The path to the script file, relative to
305 * the calling file. rawScriptFilename must be in the data path used by the
306 * calling project, typically a third_party directory.
308 function requireRawScript(relativeRawScriptPath) {
309 if (window.FLATTENED_RAW_SCRIPTS) {
310 if (!window.FLATTENED_RAW_SCRIPTS[relativeRawScriptPath]) {
311 throw new Error('Somehow, ' + relativeRawScriptPath +
312 ' didn\'t get stored in the flattened js file! ' +
313 'You have probably found a tvcm bug.');
318 if (rawScriptLoadStatus[relativeRawScriptPath])
321 relativeRawScriptPath + ' should already have been loaded.' +
322 'You have probably found a tvcm bug.');
325 var stylesheetLoadStatus = {};
326 function requireStylesheet(dependentStylesheetName) {
327 if (window.FLATTENED)
330 if (stylesheetLoadStatus[dependentStylesheetName])
332 stylesheetLoadStatus[dependentStylesheetName] = true;
334 var localPath = dependentStylesheetName.replace(/\./g, '/') + '.css';
335 var stylesheetPath = '/' + localPath;
337 var linkEl = document.createElement('link');
338 linkEl.setAttribute('rel', 'stylesheet');
339 linkEl.setAttribute('href', stylesheetPath);
340 tvcm.doc.head.appendChild(linkEl);
343 var templateLoadStatus = {};
344 function requireTemplate(template) {
345 if (window.FLATTENED)
348 if (templateLoadStatus[template])
350 templateLoadStatus[template] = true;
352 var localPath = template.replace(/\./g, '/') + '.html';
353 var importPath = localPath;
355 var linkEl = document.createElement('link');
356 linkEl.setAttribute('rel', 'import');
357 linkEl.setAttribute('href', importPath);
358 // TODO(dsinclair): Enable when HTML imports are available.
359 //tvcm.doc.head.appendChild(linkEl);
362 function exportTo(namespace, fn) {
363 var obj = exportPath(namespace);
367 console.log('While running exports for ', namespace, ':');
368 console.log(e.stack || e);
372 for (var propertyName in exports) {
373 // Maybe we should check the prototype chain here? The current usage
374 // pattern is always using an object literal so we only care about own
376 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
378 if (propertyDescriptor) {
379 Object.defineProperty(obj, propertyName, propertyDescriptor);
380 mLog(' +' + propertyName);
386 * Initialization which must be deferred until run-time.
388 function initialize() {
389 // If 'document' isn't defined, then we must be being pre-compiled,
390 // so set a trap so that we're initialized on first access at run-time.
391 if (!global.document) {
392 var originalTVCM = tvcm;
394 Object.defineProperty(global, 'tvcm', {
396 Object.defineProperty(global, 'tvcm', {value: originalTVCM});
397 originalTVCM.initialize();
408 tvcm.isMac = /Mac/.test(navigator.platform);
409 tvcm.isWindows = /Win/.test(navigator.platform);
410 tvcm.isChromeOS = /CrOS/.test(navigator.userAgent);
411 tvcm.isLinux = /Linux/.test(navigator.userAgent);
412 tvcm.isGTK = /GTK/.test(chrome.toolkit);
413 tvcm.isViews = /views/.test(chrome.toolkit);
417 initialize: initialize,
420 requireStylesheet: requireStylesheet,
421 requireRawScript: requireRawScript,
422 requireTemplate: requireTemplate,
424 showPanic: showPanic,
426 getPanicText: getPanicText