Roll src/third_party/WebKit 05e9c31:d6595eb (svn 198725:198727)
[chromium-blink-merge.git] / ui / login / resource_loader.js
blobe44a088c47acbf04655289295960b76d751b89db
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.
5 /**
6  * @fileoverview Deferred resource loader for OOBE/Login screens.
7  */
9 cr.define('cr.ui.login.ResourceLoader', function() {
10   'use strict';
12   // Deferred assets.
13   var ASSETS = {};
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   }
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   }
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   }
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   }
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   }
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, function(child) {
101       to.appendChild(document.importNode(child, true));
102     });
103   }
105   /**
106    * Tests whether an XMLHttpRequest has successfully finished loading.
107    * @param {string} url The requested URL.
108    * @param {XMLHttpRequest} xhr The XHR object.
109    */
110   function isSuccessful(url, xhr) {
111     var fileURL = /^file:\/\//;
112     return xhr.readyState == 4 &&
113         (xhr.status == 200 || fileURL.test(url) && xhr.status == 0);
114   }
116   /*
117    * Load a chunk of HTML into the current document.
118    * @param {string} id Identifier of the page's asset bundle.
119    * @param {Object} html Descriptor of the HTML to fetch.
120    * @param {string} html.url The URL resolving to some HTML.
121    * @param {string} html.targetID The element ID to which the retrieved
122    * HTML nodes should be appended.
123    */
124   function loadHTML(id, html) {
125     var xhr = new XMLHttpRequest();
126     xhr.open('GET', html.url);
127     xhr.onreadystatechange = function() {
128       if (isSuccessful(html.url, xhr)) {
129         moveNodes(this.responseXML.body, $(html.targetID));
130         resourceLoaded(id);
131       }
132     };
133     xhr.responseType = 'document';
134     xhr.send();
135   }
137   /**
138    * Record that a resource has been loaded for an asset bundle.  When
139    * all the resources have been loaded the callback that was specified
140    * in the loadAssets call is invoked.
141    * @param {string} id Identifier of the asset bundle.
142    */
143   function resourceLoaded(id) {
144     var assets = ASSETS[id];
145     assets.count--;
146     if (assets.count == 0)
147       finishedLoading(id);
148   }
150   /**
151    * Finishes loading an asset bundle.
152    * @param {string} id Identifier of the asset bundle.
153    */
154   function finishedLoading(id) {
155     var assets = ASSETS[id];
156     console.log('Finished loading asset bundle', id);
157     assets.loaded = true;
158     window.setTimeout(function() {
159       assets.callback();
160       chrome.send('screenAssetsLoaded', [id]);
161     }, 0);
162   }
164   /**
165    * Load an asset bundle, invoking the callback when finished.
166    * @param {string} id Identifier for the asset bundle to load.
167    * @param {function()=} callback Function to invoke when done loading.
168    */
169   function loadAssets(id, callback) {
170     var assets = ASSETS[id];
171     assets.callback = callback || function() {};
172     console.log('Loading asset bundle', id);
173     if (alreadyLoadedAssets(id))
174       console.warn('asset bundle', id, 'already loaded!');
175     if (assets.count == 0) {
176       finishedLoading(id);
177     } else {
178       assets.css.forEach(loadCSS.bind(null, id));
179       assets.js.forEach(loadJS.bind(null, id));
180       assets.html.forEach(loadHTML.bind(null, id));
181     }
182   }
184   return {
185     alreadyLoadedAssets: alreadyLoadedAssets,
186     hasDeferredAssets: hasDeferredAssets,
187     loadAssets: loadAssets,
188     registerAssets: registerAssets
189   };