common.js revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 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// Javascript module pattern:
6//   see http://en.wikipedia.org/wiki/Unobtrusive_JavaScript#Namespaces
7// In essence, we define an anonymous function which is immediately called and
8// returns a new object. The new object contains only the exported definitions;
9// all other definitions in the anonymous function are inaccessible to external
10// code.
11var common = (function () {
12
13  /**
14   * Create the Native Client <embed> element as a child of the DOM element
15   * named "listener".
16   *
17   * @param {string} name The name of the example.
18   * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc.
19   * @param {string} path Directory name where .nmf file can be found.
20   * @param {number} width The width to create the plugin.
21   * @param {number} height The height to create the plugin.
22   * @param {Object} optional dictionary of args to send to DidCreateInstance
23   */
24  function createNaClModule(name, tool, path, width, height, args) {
25    var moduleEl = document.createElement('embed');
26    moduleEl.setAttribute('name', 'nacl_module');
27    moduleEl.setAttribute('id', 'nacl_module');
28    moduleEl.setAttribute('width', width);
29    moduleEl.setAttribute('height',height);
30    moduleEl.setAttribute('src', path + '/' + name + '.nmf');
31
32    // Add any optional arguments
33    if (args) {
34      for (var key in args) {
35        moduleEl.setAttribute(key, args[key])
36      }
37    }
38
39    // For NaCL modules use application/x-nacl.
40    var mimetype = 'application/x-nacl';
41    var isHost = tool == 'win' || tool == 'linux' || tool == 'mac';
42    if (isHost) {
43      // For non-nacl PPAPI plugins use the x-ppapi-debug/release
44      // mime type.
45      if (path.toLowerCase().indexOf('release') != -1)
46        mimetype = 'application/x-ppapi-release';
47      else
48        mimetype = 'application/x-ppapi-debug';
49    }
50    moduleEl.setAttribute('type', mimetype);
51
52    // The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
53    // and a 'message' event listener attached.  This wrapping method is used
54    // instead of attaching the event listeners directly to the <EMBED> element
55    // to ensure that the listeners are active before the NaCl module 'load'
56    // event fires.
57    var listenerDiv = document.getElementById('listener');
58    listenerDiv.appendChild(moduleEl);
59
60    // Host plugins don't send a moduleDidLoad message. We'll fake it here.
61    if (isHost) {
62      window.setTimeout(function () {
63        var evt = document.createEvent('Event');
64        evt.initEvent('load', true, true);  // bubbles, cancelable
65        moduleEl.dispatchEvent(evt);
66      }, 100);  // 100 ms
67    }
68  }
69
70  /**
71   * Add the default "load" and "message" event listeners to the element with
72   * id "listener".
73   *
74   * The "load" event is sent when the module is successfully loaded. The
75   * "message" event is sent when the naclModule posts a message using
76   * PPB_Messaging.PostMessage() (in C) or pp::Instance().PostMessage() (in
77   * C++).
78   */
79  function attachDefaultListeners() {
80    var listenerDiv = document.getElementById('listener');
81    listenerDiv.addEventListener('load', moduleDidLoad, true);
82    listenerDiv.addEventListener('message', handleMessage, true);
83
84    if (typeof window.attachListeners !== 'undefined') {
85      window.attachListeners();
86    }
87  }
88
89  /**
90   * Called when the NaCl module is loaded.
91   *
92   * This event listener is registered in createNaClModule above.
93   */
94  function moduleDidLoad() {
95    common.naclModule = document.getElementById('nacl_module');
96    updateStatus('SUCCESS');
97
98    if (typeof window.moduleDidLoad !== 'undefined') {
99      window.moduleDidLoad();
100    }
101  }
102
103  /**
104   * Hide the NaCl module's embed element.
105   *
106   * We don't want to hide by default; if we do, it is harder to determine that
107   * a plugin failed to load. Instead, call this function inside the example's
108   * "moduleDidLoad" function.
109   *
110   */
111  function hideModule() {
112    // Setting common.naclModule.style.display = "None" doesn't work; the
113    // module will no longer be able to receive postMessages.
114    common.naclModule.style.height = "0";
115  }
116
117  /**
118   * Return true when |s| starts with the string |prefix|.
119   *
120   * @param {string} s The string to search.
121   * @param {string} prefix The prefix to search for in |s|.
122   */
123  function startsWith(s, prefix) {
124    // indexOf would search the entire string, lastIndexOf(p, 0) only checks at
125    // the first index. See: http://stackoverflow.com/a/4579228
126    return s.lastIndexOf(prefix, 0) === 0;
127  }
128
129  /**
130   * Add a message to an element with id "log", separated by a <br> element.
131   *
132   * This function is used by the default "log:" message handler.
133   *
134   * @param {string} message The message to log.
135   */
136  function logMessage(message) {
137    var logEl = document.getElementById('log');
138    logEl.innerHTML += message + '<br>';
139    console.log(message)
140  }
141
142  /**
143   */
144  var defaultMessageTypes = {
145    'alert': alert,
146    'log': logMessage
147  };
148
149  /**
150   * Called when the NaCl module sends a message to JavaScript (via
151   * PPB_Messaging.PostMessage())
152   *
153   * This event listener is registered in createNaClModule above.
154   *
155   * @param {Event} message_event A message event. message_event.data contains
156   *     the data sent from the NaCl module.
157   */
158  function handleMessage(message_event) {
159    if (typeof message_event.data === 'string') {
160      for (var type in defaultMessageTypes) {
161        if (defaultMessageTypes.hasOwnProperty(type)) {
162          if (startsWith(message_event.data, type + ':')) {
163            func = defaultMessageTypes[type];
164            func(message_event.data.slice(type.length + 1));
165          }
166        }
167      }
168    }
169
170    if (typeof window.handleMessage !== 'undefined') {
171      window.handleMessage(message_event);
172    }
173  }
174
175  /**
176   * Called when the DOM content has loaded; i.e. the page's document is fully
177   * parsed. At this point, we can safely query any elements in the document via
178   * document.querySelector, document.getElementById, etc.
179   *
180   * @param {string} name The name of the example.
181   * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc.
182   * @param {string} path Directory name where .nmf file can be found.
183   * @param {number} width The width to create the plugin.
184   * @param {number} height The height to create the plugin.
185   */
186  function domContentLoaded(name, tool, path, width, height) {
187    // If the page loads before the Native Client module loads, then set the
188    // status message indicating that the module is still loading.  Otherwise,
189    // do not change the status message.
190    updateStatus('Page loaded.');
191    if (common.naclModule == null) {
192      updateStatus('Creating embed: ' + tool)
193
194      // We use a non-zero sized embed to give Chrome space to place the bad
195      // plug-in graphic, if there is a problem.
196      width = typeof width !== 'undefined' ? width : 200;
197      height = typeof height !== 'undefined' ? height : 200;
198      attachDefaultListeners();
199      createNaClModule(name, tool, path, width, height);
200    } else {
201      // It's possible that the Native Client module onload event fired
202      // before the page's onload event.  In this case, the status message
203      // will reflect 'SUCCESS', but won't be displayed.  This call will
204      // display the current message.
205      updateStatus('Waiting.');
206    }
207  }
208
209  /** Saved text to display in the element with id 'statusField'. */
210  var statusText = 'NO-STATUSES';
211
212  /**
213   * Set the global status message. If the element with id 'statusField'
214   * exists, then set its HTML to the status message as well.
215   *
216   * @param {string} opt_message The message to set. If null or undefined, then
217   *     set element 'statusField' to the message from the last call to
218   *     updateStatus.
219   */
220  function updateStatus(opt_message) {
221    if (opt_message) {
222      statusText = opt_message;
223    }
224    var statusField = document.getElementById('statusField');
225    if (statusField) {
226      statusField.innerHTML = statusText;
227    }
228  }
229
230  // The symbols to export.
231  return {
232    /** A reference to the NaCl module, once it is loaded. */
233    naclModule: null,
234
235    attachDefaultListeners: attachDefaultListeners,
236    domContentLoaded: domContentLoaded,
237    createNaClModule: createNaClModule,
238    hideModule: hideModule,
239    updateStatus: updateStatus
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.
246document.addEventListener('DOMContentLoaded', function() {
247  var body = document.querySelector('body');
248
249  // The data-* attributes on the body can be referenced via body.dataset.
250  if (body.dataset) {
251    var loadFunction;
252    if (!body.dataset.customLoad) {
253      loadFunction = common.domContentLoaded;
254    } else if (typeof window.domContentLoaded !== 'undefined') {
255      loadFunction = window.domContentLoaded;
256    }
257
258    // From https://developer.mozilla.org/en-US/docs/DOM/window.location
259    var searchVars = {};
260    if (window.location.search.length > 1) {
261      var pairs = window.location.search.substr(1).split("&");
262      for (var key_ix = 0; key_ix < pairs.length; key_ix++) {
263        var keyValue = pairs[key_ix].split("=");
264        searchVars[unescape(keyValue[0])] =
265            keyValue.length > 1 ? unescape(keyValue[1]) : "";
266      }
267    }
268
269    if (loadFunction) {
270      var toolchains = body.dataset.tools.split(' ');
271      var configs = body.dataset.configs.split(' ');
272
273      var tc = toolchains.indexOf(searchVars.tc) !== -1 ?
274          searchVars.tc : toolchains[0];
275      var config = configs.indexOf(searchVars.config) !== -1 ?
276          searchVars.config : configs[0];
277      var pathFormat = body.dataset.path;
278      var path = pathFormat.replace('{tc}', tc).replace('{config}', config);
279
280      loadFunction(body.dataset.name, tc, path, body.dataset.width,
281                   body.dataset.height);
282    }
283  }
284});
285