Home | History | Annotate | Download | only in login
      1 // Copyright 2014 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 /**
      6  * @fileoverview Deferred resource loader for OOBE/Login screens.
      7  */
      8 
      9 cr.define('cr.ui.login.ResourceLoader', function() {
     10   'use strict';
     11 
     12   // Deferred assets.
     13   var ASSETS = {};
     14 
     15   /**
     16    * Register assets for deferred loading.  When the bundle is loaded
     17    * assets will be added to the current page's DOM: <link> and <script>
     18    * tags pointing to the CSS and JavaScript will be added to the
     19    * <head>, and HTML will be appended to a specified element.
     20    *
     21    * @param {Object} desc Descriptor for the asset bundle
     22    * @param {string} desc.id Unique identifier for the asset bundle.
     23    * @param {Array=} desc.js URLs containing JavaScript sources.
     24    * @param {Array=} desc.css URLs containing CSS rules.
     25    * @param {Array.<Object>=} desc.html Descriptors for HTML fragments,
     26    * each of which has a 'url' property and a 'targetID' property that
     27    * specifies the node under which the HTML should be appended.
     28    *
     29    * Example:
     30    *   ResourceLoader.registerAssets({
     31    *     id: 'bundle123',
     32    *     js: ['//foo.com/src.js', '//bar.com/lib.js'],
     33    *     css: ['//foo.com/style.css'],
     34    *     html: [{ url: '//foo.com/tmpls.html' targetID: 'tmpls'}]
     35    *   });
     36    *
     37    * Note: to avoid cross-site requests, all HTML assets must be served
     38    * from the same host as the rendered page.  For example, if the
     39    * rendered page is served as chrome://oobe, then all the HTML assets
     40    * must be served as chrome://oobe/path/to/something.html.
     41    */
     42   function registerAssets(desc) {
     43     var html = desc.html || [];
     44     var css = desc.css || [];
     45     var js = desc.js || [];
     46     ASSETS[desc.id] = {
     47       html: html, css: css, js: js,
     48       loaded: false,
     49       count: html.length + css.length + js.length
     50     };
     51   }
     52 
     53   /**
     54    * Determines whether an asset bundle is defined for a specified id.
     55    * @param {string} id The possible identifier.
     56    */
     57   function hasDeferredAssets(id) {
     58     return id in ASSETS;
     59   }
     60 
     61   /**
     62    * Determines whether an asset bundle has already been loaded.
     63    * @param {string} id The identifier of the asset bundle.
     64    */
     65   function alreadyLoadedAssets(id) {
     66     return hasDeferredAssets(id) && ASSETS[id].loaded;
     67   }
     68 
     69   /**
     70    * Load a stylesheet into the current document.
     71    * @param {string} id Identifier of the stylesheet's asset bundle.
     72    * @param {string} url The URL resolving to a stylesheet.
     73    */
     74   function loadCSS(id, url) {
     75     var link = document.createElement('link');
     76     link.setAttribute('rel', 'stylesheet');
     77     link.setAttribute('href', url);
     78     link.onload = resourceLoaded.bind(null, id);
     79     document.head.appendChild(link);
     80   }
     81 
     82   /**
     83    * Load a script into the current document.
     84    * @param {string} id Identifier of the script's asset bundle.
     85    * @param {string} url The URL resolving to a script.
     86    */
     87   function loadJS(id, url) {
     88     var script = document.createElement('script');
     89     script.src = url;
     90     script.onload = resourceLoaded.bind(null, id);
     91     document.head.appendChild(script);
     92   }
     93 
     94   /**
     95    * Move DOM nodes from one parent element to another.
     96    * @param {HTMLElement} from Element whose children should be moved.
     97    * @param {HTMLElement} to Element to which nodes should be appended.
     98    */
     99   function moveNodes(from, to) {
    100     Array.prototype.forEach.call(from.children, to.appendChild, to);
    101   }
    102 
    103   /**
    104    * Tests whether an XMLHttpRequest has successfully finished loading.
    105    * @param {string} url The requested URL.
    106    * @param {XMLHttpRequest} xhr The XHR object.
    107    */
    108   function isSuccessful(url, xhr) {
    109     var fileURL = /^file:\/\//;
    110     return xhr.readyState == 4 &&
    111         (xhr.status == 200 || fileURL.test(url) && xhr.status == 0);
    112   }
    113 
    114   /*
    115    * Load a chunk of HTML into the current document.
    116    * @param {string} id Identifier of the page's asset bundle.
    117    * @param {Object} html Descriptor of the HTML to fetch.
    118    * @param {string} html.url The URL resolving to some HTML.
    119    * @param {string} html.targetID The element ID to which the retrieved
    120    * HTML nodes should be appended.
    121    */
    122   function loadHTML(id, html) {
    123     var xhr = new XMLHttpRequest();
    124     xhr.open('GET', html.url);
    125     xhr.onreadystatechange = function() {
    126       if (isSuccessful(html.url, xhr)) {
    127         moveNodes(this.responseXML.body, $(html.targetID));
    128         resourceLoaded(id);
    129       }
    130     };
    131     xhr.responseType = 'document';
    132     xhr.send();
    133   }
    134 
    135   /**
    136    * Record that a resource has been loaded for an asset bundle.  When
    137    * all the resources have been loaded the callback that was specified
    138    * in the loadAssets call is invoked.
    139    * @param {string} id Identifier of the asset bundle.
    140    */
    141   function resourceLoaded(id) {
    142     var assets = ASSETS[id];
    143     assets.count--;
    144     if (assets.count == 0)
    145       finishedLoading(id);
    146   }
    147 
    148   /**
    149    * Finishes loading an asset bundle.
    150    * @param {string} id Identifier of the asset bundle.
    151    */
    152   function finishedLoading(id) {
    153     var assets = ASSETS[id];
    154     console.log('Finished loading asset bundle', id);
    155     assets.loaded = true;
    156     window.setTimeout(function() {
    157       assets.callback();
    158       chrome.send('screenAssetsLoaded', [id]);
    159     }, 0);
    160   }
    161 
    162   /**
    163    * Load an asset bundle, invoking the callback when finished.
    164    * @param {string} id Identifier for the asset bundle to load.
    165    * @param {function()=} callback Function to invoke when done loading.
    166    */
    167   function loadAssets(id, callback) {
    168     var assets = ASSETS[id];
    169     assets.callback = callback || function() {};
    170     console.log('Loading asset bundle', id);
    171     if (alreadyLoadedAssets(id))
    172       console.warn('asset bundle', id, 'already loaded!');
    173     if (assets.count == 0) {
    174       finishedLoading(id);
    175     } else {
    176       assets.css.forEach(loadCSS.bind(null, id));
    177       assets.js.forEach(loadJS.bind(null, id));
    178       assets.html.forEach(loadHTML.bind(null, id));
    179     }
    180   }
    181 
    182   return {
    183     alreadyLoadedAssets: alreadyLoadedAssets,
    184     hasDeferredAssets: hasDeferredAssets,
    185     loadAssets: loadAssets,
    186     registerAssets: registerAssets
    187   };
    188 });
    189