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 + '/voronoi/' + 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('crash', handleCrash, true); 94 listenerDiv.addEventListener('message', handleMessage, 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 * Handle a message coming from the NaCl module. 115 * @param {Object} message_event 116 */ 117function handleMessage(event) { 118 if (event.data.message == 'fps') { 119 $('fps').textContent = event.data.value.toFixed(1); 120 } 121} 122 123/** 124 * Called when the NaCl module is loaded. 125 * 126 * This event listener is registered in attachDefaultListeners above. 127 */ 128function moduleDidLoad() { 129 var bar = $('progress-bar'); 130 bar.style.width = 100; 131 naclModule = $('nacl_module'); 132 hideStatus(); 133 setThreadCount(); 134} 135 136/** 137 * Hide the status field and progress bar. 138 */ 139function hideStatus() { 140 $('loading-cover').style.display = 'none'; 141 142} 143 144/** 145 * Called when the plugin fails to load. 146 * 147 * @param {Object} event 148 */ 149function moduleLoadError(event) { 150 updateStatus('Load failed.'); 151} 152 153/** 154 * Called when the plugin reports progress events. 155 * 156 * @param {Object} event 157 */ 158function moduleLoadProgress(event) { 159 $('progress').style.display = 'block'; 160 161 var loadPercent = 0.0; 162 var bar = $('progress-bar'); 163 164 if (event.lengthComputable && event.total > 0) { 165 loadPercent = event.loaded / event.total * 100.0; 166 } else { 167 // The total length is not yet known. 168 loadPercent = 10; 169 } 170 bar.style.width = loadPercent + "%"; 171} 172 173/** 174 * If the element with id 'statusField' exists, then set its HTML to the status 175 * message as well. 176 * 177 * @param {string} opt_message The message to set. 178 */ 179function updateStatus(opt_message) { 180 var statusField = $('statusField'); 181 if (statusField) { 182 statusField.style.display = 'block'; 183 statusField.textContent = opt_message; 184 } 185} 186 187/** 188 * Send the current value of the element threadCount to the NaCl module. 189 * 190 * @param {number} threads The number of threads to use to render. 191 */ 192function setThreadCount(threads) { 193 var value = parseInt($('threadCount').value); 194 naclModule.postMessage({'message': 'set_threads', 195 'value': value}); 196} 197 198/** 199 * Add event listeners after the NaCl module has loaded. These listeners will 200 * forward messages to the NaCl module via postMessage() 201 */ 202function attachListeners() { 203 $('threadCount').addEventListener('change', setThreadCount); 204 $('drawPoints').addEventListener('click', 205 function() { 206 var checked = $('drawPoints').checked; 207 naclModule.postMessage({'message' : 'draw_points', 208 'value' : checked}); 209 }); 210 $('drawInteriors').addEventListener('click', 211 function() { 212 var checked = $('drawInteriors').checked; 213 naclModule.postMessage({'message' : 'draw_interiors', 214 'value' : checked}); 215 }); 216 $('pointRange').addEventListener('change', 217 function() { 218 var value = parseFloat($('pointRange').value); 219 naclModule.postMessage({'message' : 'set_points', 220 'value' : value}); 221 $('pointCount').textContent = value + ' points'; 222 }); 223} 224 225/** 226 * Listen for the DOM content to be loaded. This event is fired when parsing of 227 * the page's document has finished. 228 */ 229document.addEventListener('DOMContentLoaded', function() { 230 updateStatus('Loading...'); 231 if (!browserSupportsPNaCl()) { 232 updateStatus('Browser does not support PNaCl or PNaCl is disabled'); 233 } else if (naclModule == null) { 234 createNaClModule('voronoi', '100%', '100%'); 235 attachDefaultListeners(); 236 } else { 237 // It's possible that the Native Client module onload event fired 238 // before the page's onload event. In this case, the status message 239 // will reflect 'SUCCESS', but won't be displayed. This call will 240 // display the current message. 241 updateStatus('Waiting.'); 242 } 243}); 244