Javascript  |  189行  |  6 KB

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Deferred resource loader for OOBE/Login screens.
 */

cr.define('cr.ui.login.ResourceLoader', function() {
  'use strict';

  // Deferred assets.
  var ASSETS = {};

  /**
   * Register assets for deferred loading.  When the bundle is loaded
   * assets will be added to the current page's DOM: <link> and <script>
   * tags pointing to the CSS and JavaScript will be added to the
   * <head>, and HTML will be appended to a specified element.
   *
   * @param {Object} desc Descriptor for the asset bundle
   * @param {string} desc.id Unique identifier for the asset bundle.
   * @param {Array=} desc.js URLs containing JavaScript sources.
   * @param {Array=} desc.css URLs containing CSS rules.
   * @param {Array.<Object>=} desc.html Descriptors for HTML fragments,
   * each of which has a 'url' property and a 'targetID' property that
   * specifies the node under which the HTML should be appended.
   *
   * Example:
   *   ResourceLoader.registerAssets({
   *     id: 'bundle123',
   *     js: ['//foo.com/src.js', '//bar.com/lib.js'],
   *     css: ['//foo.com/style.css'],
   *     html: [{ url: '//foo.com/tmpls.html' targetID: 'tmpls'}]
   *   });
   *
   * Note: to avoid cross-site requests, all HTML assets must be served
   * from the same host as the rendered page.  For example, if the
   * rendered page is served as chrome://oobe, then all the HTML assets
   * must be served as chrome://oobe/path/to/something.html.
   */
  function registerAssets(desc) {
    var html = desc.html || [];
    var css = desc.css || [];
    var js = desc.js || [];
    ASSETS[desc.id] = {
      html: html, css: css, js: js,
      loaded: false,
      count: html.length + css.length + js.length
    };
  }

  /**
   * Determines whether an asset bundle is defined for a specified id.
   * @param {string} id The possible identifier.
   */
  function hasDeferredAssets(id) {
    return id in ASSETS;
  }

  /**
   * Determines whether an asset bundle has already been loaded.
   * @param {string} id The identifier of the asset bundle.
   */
  function alreadyLoadedAssets(id) {
    return hasDeferredAssets(id) && ASSETS[id].loaded;
  }

  /**
   * Load a stylesheet into the current document.
   * @param {string} id Identifier of the stylesheet's asset bundle.
   * @param {string} url The URL resolving to a stylesheet.
   */
  function loadCSS(id, url) {
    var link = document.createElement('link');
    link.setAttribute('rel', 'stylesheet');
    link.setAttribute('href', url);
    link.onload = resourceLoaded.bind(null, id);
    document.head.appendChild(link);
  }

  /**
   * Load a script into the current document.
   * @param {string} id Identifier of the script's asset bundle.
   * @param {string} url The URL resolving to a script.
   */
  function loadJS(id, url) {
    var script = document.createElement('script');
    script.src = url;
    script.onload = resourceLoaded.bind(null, id);
    document.head.appendChild(script);
  }

  /**
   * Move DOM nodes from one parent element to another.
   * @param {HTMLElement} from Element whose children should be moved.
   * @param {HTMLElement} to Element to which nodes should be appended.
   */
  function moveNodes(from, to) {
    Array.prototype.forEach.call(from.children, to.appendChild, to);
  }

  /**
   * Tests whether an XMLHttpRequest has successfully finished loading.
   * @param {string} url The requested URL.
   * @param {XMLHttpRequest} xhr The XHR object.
   */
  function isSuccessful(url, xhr) {
    var fileURL = /^file:\/\//;
    return xhr.readyState == 4 &&
        (xhr.status == 200 || fileURL.test(url) && xhr.status == 0);
  }

  /*
   * Load a chunk of HTML into the current document.
   * @param {string} id Identifier of the page's asset bundle.
   * @param {Object} html Descriptor of the HTML to fetch.
   * @param {string} html.url The URL resolving to some HTML.
   * @param {string} html.targetID The element ID to which the retrieved
   * HTML nodes should be appended.
   */
  function loadHTML(id, html) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', html.url);
    xhr.onreadystatechange = function() {
      if (isSuccessful(html.url, xhr)) {
        moveNodes(this.responseXML.body, $(html.targetID));
        resourceLoaded(id);
      }
    };
    xhr.responseType = 'document';
    xhr.send();
  }

  /**
   * Record that a resource has been loaded for an asset bundle.  When
   * all the resources have been loaded the callback that was specified
   * in the loadAssets call is invoked.
   * @param {string} id Identifier of the asset bundle.
   */
  function resourceLoaded(id) {
    var assets = ASSETS[id];
    assets.count--;
    if (assets.count == 0)
      finishedLoading(id);
  }

  /**
   * Finishes loading an asset bundle.
   * @param {string} id Identifier of the asset bundle.
   */
  function finishedLoading(id) {
    var assets = ASSETS[id];
    console.log('Finished loading asset bundle', id);
    assets.loaded = true;
    window.setTimeout(function() {
      assets.callback();
      chrome.send('screenAssetsLoaded', [id]);
    }, 0);
  }

  /**
   * Load an asset bundle, invoking the callback when finished.
   * @param {string} id Identifier for the asset bundle to load.
   * @param {function()=} callback Function to invoke when done loading.
   */
  function loadAssets(id, callback) {
    var assets = ASSETS[id];
    assets.callback = callback || function() {};
    console.log('Loading asset bundle', id);
    if (alreadyLoadedAssets(id))
      console.warn('asset bundle', id, 'already loaded!');
    if (assets.count == 0) {
      finishedLoading(id);
    } else {
      assets.css.forEach(loadCSS.bind(null, id));
      assets.js.forEach(loadJS.bind(null, id));
      assets.html.forEach(loadHTML.bind(null, id));
    }
  }

  return {
    alreadyLoadedAssets: alreadyLoadedAssets,
    hasDeferredAssets: hasDeferredAssets,
    loadAssets: loadAssets,
    registerAssets: registerAssets
  };
});