1 // Copyright 2013 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(assets.callback, 0); 157 } 158 159 /** 160 * Load an asset bundle, invoking the callback when finished. 161 * @param {string} id Identifier for the asset bundle to load. 162 * @param {function()=} callback Function to invoke when done loading. 163 */ 164 function loadAssets(id, callback) { 165 var assets = ASSETS[id]; 166 assets.callback = callback || function() {}; 167 console.log('Loading asset bundle', id); 168 if (alreadyLoadedAssets(id)) 169 console.warn('asset bundle', id, 'already loaded!'); 170 if (assets.count == 0) { 171 finishedLoading(id); 172 } else { 173 assets.css.forEach(loadCSS.bind(null, id)); 174 assets.js.forEach(loadJS.bind(null, id)); 175 assets.html.forEach(loadHTML.bind(null, id)); 176 } 177 } 178 179 return { 180 alreadyLoadedAssets: alreadyLoadedAssets, 181 hasDeferredAssets: hasDeferredAssets, 182 loadAssets: loadAssets, 183 registerAssets: registerAssets 184 }; 185 }); 186