// 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 }; });