1 // Copyright (c) 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 'use strict'; 6 7 var naclModule = null; 8 9 /** 10 * A helper function to abbreviate getElementById. 11 * 12 * @param {string} elementId The id to get. 13 * @return {Element} 14 */ 15 function $(elementId) { 16 return document.getElementById(elementId); 17 } 18 19 /** 20 * MIME type for PNaCl 21 * 22 * @return {string} MIME type 23 */ 24 function PNaClmimeType() { 25 return 'application/x-pnacl'; 26 } 27 28 /** 29 * Check if the browser supports PNaCl. 30 * 31 * @return {bool} 32 */ 33 function browserSupportsPNaCl() { 34 var mimetype = PNaClmimeType(); 35 return navigator.mimeTypes[mimetype] !== undefined; 36 } 37 38 /** 39 * Get the URL for Google Cloud Storage. 40 * 41 * @param {string} name The relative path to the file. 42 * @return {string} 43 */ 44 function getDataURL(name) { 45 var revision = '236779'; 46 var baseUrl = '//storage.googleapis.com/gonacl/demos/publish/'; 47 return baseUrl + revision + '/life/' + name; 48 } 49 50 /** 51 * Create the Native Client <embed> element as a child of the DOM element 52 * named "listener". 53 * 54 * @param {string} name The name of the example. 55 * @param {number} width The width to create the plugin. 56 * @param {number} height The height to create the plugin. 57 * @param {Object} attrs Dictionary of attributes to set on the module. 58 */ 59 function createNaClModule(name, width, height, attrs) { 60 var moduleEl = document.createElement('embed'); 61 moduleEl.setAttribute('name', 'nacl_module'); 62 moduleEl.setAttribute('id', 'nacl_module'); 63 moduleEl.setAttribute('width', width); 64 moduleEl.setAttribute('height', height); 65 moduleEl.setAttribute('path', ''); 66 moduleEl.setAttribute('src', getDataURL(name + '.nmf')); 67 moduleEl.setAttribute('type', PNaClmimeType()); 68 69 // Add any optional arguments 70 if (attrs) { 71 for (var key in attrs) { 72 moduleEl.setAttribute(key, attrs[key]); 73 } 74 } 75 76 // The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' 77 // and a 'message' event listener attached. This wrapping method is used 78 // instead of attaching the event listeners directly to the <EMBED> element 79 // to ensure that the listeners are active before the NaCl module 'load' 80 // event fires. 81 var listenerDiv = $('listener'); 82 listenerDiv.appendChild(moduleEl); 83 } 84 85 /** 86 * Add the default event listeners to the element with id "listener". 87 */ 88 function attachDefaultListeners() { 89 var listenerDiv = $('listener'); 90 listenerDiv.addEventListener('load', moduleDidLoad, true); 91 listenerDiv.addEventListener('error', moduleLoadError, true); 92 listenerDiv.addEventListener('progress', moduleLoadProgress, true); 93 listenerDiv.addEventListener('crash', handleCrash, true); 94 listenerDiv.addEventListener('message', handleMessage, true); 95 } 96 97 /** 98 * Called when the Browser can not communicate with the Module 99 * 100 * This event listener is registered in attachDefaultListeners above. 101 * 102 * @param {Object} event 103 */ 104 function handleCrash(event) { 105 if (naclModule.exitStatus == -1) { 106 updateStatus('CRASHED'); 107 } else { 108 updateStatus('EXITED [' + naclModule.exitStatus + ']'); 109 } 110 } 111 112 /** 113 * Handle a message coming from the NaCl module. 114 * @param {Object} message_event 115 */ 116 function handleMessage(message_event) { 117 // Assume value is the current fps, sent as a float. 118 $('fps').textContent = message_event.data.toFixed(1); 119 } 120 121 /** 122 * Called when the NaCl module is loaded. 123 * 124 * This event listener is registered in attachDefaultListeners above. 125 */ 126 function moduleDidLoad() { 127 var bar = $('progress-bar'); 128 bar.style.width = 100; 129 naclModule = $('nacl_module'); 130 hideStatus(); 131 } 132 133 /** 134 * Hide the status field and progress bar. 135 */ 136 function hideStatus() { 137 $('loading-cover').style.display = 'none'; 138 } 139 140 /** 141 * Called when the plugin fails to load. 142 * 143 * @param {Object} event 144 */ 145 function moduleLoadError(event) { 146 updateStatus('Load failed.'); 147 } 148 149 /** 150 * Called when the plugin reports progress events. 151 * 152 * @param {Object} event 153 */ 154 function moduleLoadProgress(event) { 155 $('progress').style.display = 'block'; 156 157 var loadPercent = 0.0; 158 var bar = $('progress-bar'); 159 160 if (event.lengthComputable && event.total > 0) { 161 loadPercent = event.loaded / event.total * 100.0; 162 } else { 163 // The total length is not yet known. 164 loadPercent = 10; 165 } 166 bar.style.width = loadPercent + "%"; 167 } 168 /** 169 * If the element with id 'statusField' exists, then set its HTML to the status 170 * message as well. 171 * 172 * @param {string} opt_message The message to set. 173 */ 174 function updateStatus(opt_message) { 175 var statusField = $('statusField'); 176 if (statusField) { 177 statusField.style.display = 'block'; 178 statusField.textContent = opt_message; 179 } 180 } 181 182 /** 183 * Listen for the DOM content to be loaded. This event is fired when parsing of 184 * the page's document has finished. 185 */ 186 document.addEventListener('DOMContentLoaded', function() { 187 updateStatus('Loading...'); 188 if (!browserSupportsPNaCl()) { 189 updateStatus('Browser does not support PNaCl or PNaCl is disabled'); 190 } else if (naclModule == null) { 191 createNaClModule('life', '100%', '100%'); 192 attachDefaultListeners(); 193 } else { 194 // It's possible that the Native Client module onload event fired 195 // before the page's onload event. In this case, the status message 196 // will reflect 'SUCCESS', but won't be displayed. This call will 197 // display the current message. 198 updateStatus('Waiting.'); 199 } 200 }); 201