example.js revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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 7var naclModule = null; 8 9/** 10 * A helper function to abbreviate getElementById. 11 * 12 * @param {string} elementId The id to get. 13 * @return {Element} 14 */ 15function $(elementId) { 16 return document.getElementById(elementId); 17} 18 19/** 20 * MIME type for PNaCl 21 * 22 * @return {string} MIME type 23 */ 24function PNaClmimeType() { 25 return 'application/x-pnacl'; 26} 27 28/** 29 * Check if the browser supports PNaCl. 30 * 31 * @return {bool} 32 */ 33function 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 */ 44function getDataURL(name) { 45 var revision = '236779'; 46 var baseUrl = '//storage.googleapis.com/gonacl/demos/publish/'; 47 return baseUrl + revision + '/earth/' + 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 */ 59function 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 */ 88function 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 */ 105function 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 */ 118function moduleDidLoad() { 119 var bar = $('progress-bar'); 120 bar.style.width = 100; 121 naclModule = $('nacl_module'); 122 hideStatus(); 123 setThreadCount(); 124} 125 126/** 127 * Hide the status field and progress bar. 128 */ 129function hideStatus() { 130 $('loading-cover').style.display = 'none'; 131} 132 133/** 134 * Called when the plugin fails to load. 135 * 136 * @param {Object} event 137 */ 138function moduleLoadError(event) { 139 updateStatus('Load failed.'); 140} 141 142/** 143 * Called when the plugin reports progress events. 144 * 145 * @param {Object} event 146 */ 147function moduleLoadProgress(event) { 148 $('progress').style.display = 'block'; 149 150 var loadPercent = 0.0; 151 var bar = $('progress-bar'); 152 153 if (event.lengthComputable && event.total > 0) { 154 loadPercent = event.loaded / event.total * 100.0; 155 } else { 156 // The total length is not yet known. 157 loadPercent = 10; 158 } 159 bar.style.width = loadPercent + "%"; 160} 161 162 163/** 164 * If the element with id 'statusField' exists, then set its HTML to the status 165 * message as well. 166 * 167 * @param {string} opt_message The message to set. 168 */ 169function updateStatus(opt_message) { 170 var statusField = $('statusField'); 171 if (statusField) { 172 statusField.style.display = 'block'; 173 statusField.textContent = opt_message; 174 } 175} 176 177/** 178 * Send the current value of the element threadCount to the NaCl module. 179 * 180 * @param {number} threads The number of threads to use to render. 181 */ 182function setThreadCount(threads) { 183 var value = parseInt($('threadCount').value); 184 naclModule.postMessage({'message': 'set_threads', 185 'value': value}); 186} 187 188/** 189 * Add event listeners after the NaCl module has loaded. These listeners will 190 * forward messages to the NaCl module via postMessage() 191 */ 192function attachListeners() { 193 $('threadCount').addEventListener('change', setThreadCount); 194 $('zoomRange').addEventListener('change', 195 function() { 196 var value = parseFloat($('zoomRange').value); 197 naclModule.postMessage({'message' : 'set_zoom', 198 'value' : value}); 199 }); 200 $('lightRange').addEventListener('change', 201 function() { 202 var value = parseFloat($('lightRange').value); 203 naclModule.postMessage({'message' : 'set_light', 204 'value' : value}); 205 }); 206} 207 208/** 209 * Load a texture and send pixel data down to NaCl module. 210 * @param {string} name 211 */ 212function loadTexture(name) { 213 // Load image from jpg, decompress into canvas. 214 var img = new Image(); 215 img.onload = function() { 216 var graph = document.createElement('canvas'); 217 graph.width = img.width; 218 graph.height = img.height; 219 var context = graph.getContext('2d'); 220 context.drawImage(img, 0, 0); 221 var imageData = context.getImageData(0, 0, img.width, img.height); 222 // Send NaCl module the raw image data obtained from canvas. 223 naclModule.postMessage({'message' : 'texture', 224 'name' : name, 225 'width' : img.width, 226 'height' : img.height, 227 'data' : imageData.data.buffer}); 228 }; 229 // A cross-origin request to an image is "tainted", and cannot be read into a 230 // canvas without specifying this. See 231 // https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image 232 img.crossOrigin = 'Anonymous'; 233 img.src = getDataURL(name); 234} 235 236/** 237 * Handle a message coming from the NaCl module. 238 * @param {Object} message_event 239 */ 240function handleMessage(message_event) { 241 var message = message_event.data.message; 242 var value = message_event.data.value; 243 if (message == 'set_zoom') { 244 // zoom slider 245 $('zoomRange').value = value; 246 } else if (message == 'set_light') { 247 // light slider 248 $('lightRange').value = value; 249 } else if (message == 'request_textures') { 250 // NaCl module is requesting a set of textures. 251 var names = message_event.data.names; 252 for (var i = 0; i < names.length; i++) 253 loadTexture(names[i]); 254 } else if (message == 'fps') { 255 // NaCl module notifying current FPS. 256 $('fps').textContent = message_event.data.value.toFixed(1); 257 } 258} 259 260/** 261 * Listen for the DOM content to be loaded. This event is fired when parsing of 262 * the page's document has finished. 263 */ 264document.addEventListener('DOMContentLoaded', function() { 265 updateStatus('Loading...'); 266 if (!browserSupportsPNaCl()) { 267 updateStatus('Browser does not support PNaCl or PNaCl is disabled'); 268 } else if (naclModule == null) { 269 createNaClModule('earth', '100%', '100%'); 270 attachDefaultListeners(); 271 } else { 272 // It's possible that the Native Client module onload event fired 273 // before the page's onload event. In this case, the status message 274 // will reflect 'SUCCESS', but won't be displayed. This call will 275 // display the current message. 276 updateStatus('Waiting.'); 277 } 278}); 279