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