Home | History | Annotate | Download | only in earth
      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 + '/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  */
     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   setThreadCount();
    124 }
    125 
    126 /**
    127  * Hide the status field and progress bar.
    128  */
    129 function hideStatus() {
    130   $('loading-cover').style.display = 'none';
    131 }
    132 
    133 /**
    134  * Called when the plugin fails to load.
    135  *
    136  * @param {Object} event
    137  */
    138 function moduleLoadError(event) {
    139   updateStatus('Load failed.');
    140 }
    141 
    142 /**
    143  * Called when the plugin reports progress events.
    144  *
    145  * @param {Object} event
    146  */
    147 function 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  */
    169 function 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  */
    182 function 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  */
    192 function 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  */
    212 function 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  */
    240 function 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  */
    264 document.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