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 + '/cube/' + 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('message', handleMessage, true); 94 listenerDiv.addEventListener('crash', handleCrash, true); 95 attachListeners(); 96 } 97 98 /** 99 * Called when the Browser can not communicate with the Module 100 * 101 * This event listener is registered in attachDefaultListeners above. 102 * 103 * @param {Object} event 104 */ 105 function handleCrash(event) { 106 if (naclModule.exitStatus == -1) { 107 updateStatus('CRASHED'); 108 } else { 109 updateStatus('EXITED [' + naclModule.exitStatus + ']'); 110 } 111 } 112 113 /** 114 * Called when the NaCl module is loaded. 115 * 116 * This event listener is registered in attachDefaultListeners above. 117 */ 118 function moduleDidLoad() { 119 var bar = $('progress-bar'); 120 bar.style.width = 100; 121 naclModule = $('nacl_module'); 122 hideStatus(); 123 } 124 125 /** 126 * Hide the status field and progress bar. 127 */ 128 function hideStatus() { 129 $('loading-cover').style.display = 'none'; 130 } 131 132 /** 133 * Called when the plugin fails to load. 134 * 135 * @param {Object} event 136 */ 137 function moduleLoadError(event) { 138 updateStatus('Load failed.'); 139 } 140 141 /** 142 * Called when the plugin reports progress events. 143 * 144 * @param {Object} event 145 */ 146 function moduleLoadProgress(event) { 147 $('progress').style.display = 'block'; 148 149 var loadPercent = 0.0; 150 var bar = $('progress-bar'); 151 152 if (event.lengthComputable && event.total > 0) { 153 loadPercent = event.loaded / event.total * 100.0; 154 } else { 155 // The total length is not yet known. 156 loadPercent = 10; 157 } 158 bar.style.width = loadPercent + "%"; 159 } 160 161 /** 162 * If the element with id 'statusField' exists, then set its HTML to the status 163 * message as well. 164 * 165 * @param {string} opt_message The message to set. 166 */ 167 function updateStatus(opt_message) { 168 var statusField = $('statusField'); 169 if (statusField) { 170 statusField.style.display = 'block'; 171 statusField.textContent = opt_message; 172 } 173 } 174 175 /** 176 * Add event listeners after the NaCl module has loaded. These listeners will 177 * forward messages to the NaCl module via postMessage() 178 */ 179 function attachListeners() { 180 $('xAngle').addEventListener('change', postAngleMessage); 181 $('yAngle').addEventListener('change', postAngleMessage); 182 $('animateOff').addEventListener('click', function() { 183 $('animateOn').checked = ''; 184 naclModule.postMessage(false); 185 }); 186 $('animateOn').addEventListener('click', function() { 187 $('animateOff').checked = ''; 188 naclModule.postMessage(true); 189 }); 190 } 191 192 function postAngleMessage() { 193 var xAngle = parseFloat($('xAngle').value); 194 var yAngle = parseFloat($('yAngle').value); 195 naclModule.postMessage([xAngle, yAngle]); 196 } 197 198 /** 199 * Load a texture and send pixel data down to NaCl module. 200 * @param {string} name 201 */ 202 function loadTexture(name) { 203 // Load image from jpg, decompress into canvas. 204 var img = new Image(); 205 img.onload = function() { 206 var graph = document.createElement('canvas'); 207 graph.width = img.width; 208 graph.height = img.height; 209 var context = graph.getContext('2d'); 210 context.drawImage(img, 0, 0); 211 var imageData = context.getImageData(0, 0, img.width, img.height); 212 // Send NaCl module the raw image data obtained from canvas. 213 naclModule.postMessage({'message' : 'texture', 214 'name' : name, 215 'width' : img.width, 216 'height' : img.height, 217 'data' : imageData.data.buffer}); 218 }; 219 // A cross-origin request to an image is "tainted", and cannot be read into a 220 // canvas without specifying this. See 221 // https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image 222 img.crossOrigin = 'Anonymous'; 223 img.src = getDataURL(name); 224 } 225 226 /** 227 * Handle a message coming from the NaCl module. 228 * @param {Object} message_event 229 */ 230 function handleMessage(event) { 231 if (event.data instanceof Array) { 232 if (event.data.length != 2) 233 return; 234 var xAngle = event.data[0]; 235 var yAngle = event.data[1]; 236 $('xAngle').value = xAngle; 237 $('yAngle').value = yAngle; 238 } else if (typeof(event.data) === 'number') { 239 $('fps').textContent = event.data.toFixed(1); 240 } 241 } 242 243 /** 244 * Listen for the DOM content to be loaded. This event is fired when parsing of 245 * the page's document has finished. 246 */ 247 document.addEventListener('DOMContentLoaded', function() { 248 updateStatus('Loading...'); 249 if (!browserSupportsPNaCl()) { 250 updateStatus('Browser does not support PNaCl or PNaCl is disabled'); 251 } else if (naclModule == null) { 252 createNaClModule('cube', '100%', '100%'); 253 attachDefaultListeners(); 254 } else { 255 // It's possible that the Native Client module onload event fired 256 // before the page's onload event. In this case, the status message 257 // will reflect 'SUCCESS', but won't be displayed. This call will 258 // display the current message. 259 updateStatus('Waiting.'); 260 } 261 }); 262