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