OLD | NEW |
| (Empty) |
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 // Set to true when the Document is loaded IFF "test=true" is in the query | |
6 // string. | |
7 var isTest = false; | |
8 | |
9 // Set to true when loading a "Release" NaCl module, false when loading a | |
10 // "Debug" NaCl module. | |
11 var isRelease = false; | |
12 | |
13 // Javascript module pattern: | |
14 // see http://en.wikipedia.org/wiki/Unobtrusive_JavaScript#Namespaces | |
15 // In essence, we define an anonymous function which is immediately called and | |
16 // returns a new object. The new object contains only the exported definitions; | |
17 // all other definitions in the anonymous function are inaccessible to external | |
18 // code. | |
19 var common = (function() { | |
20 | |
21 function isHostToolchain(tool) { | |
22 return tool == 'win' || tool == 'linux' || tool == 'mac'; | |
23 } | |
24 | |
25 /** | |
26 * Return the mime type for NaCl plugin. | |
27 * | |
28 * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. | |
29 * @return {string} The mime-type for the kind of NaCl plugin matching | |
30 * the given toolchain. | |
31 */ | |
32 function mimeTypeForTool(tool) { | |
33 // For NaCl modules use application/x-nacl. | |
34 var mimetype = 'application/x-nacl'; | |
35 if (isHostToolchain(tool)) { | |
36 // For non-NaCl PPAPI plugins use the x-ppapi-debug/release | |
37 // mime type. | |
38 if (isRelease) | |
39 mimetype = 'application/x-ppapi-release'; | |
40 else | |
41 mimetype = 'application/x-ppapi-debug'; | |
42 } else if (tool == 'pnacl' && isRelease) { | |
43 mimetype = 'application/x-pnacl'; | |
44 } | |
45 return mimetype; | |
46 } | |
47 | |
48 /** | |
49 * Check if the browser supports NaCl plugins. | |
50 * | |
51 * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. | |
52 * @return {bool} True if the browser supports the type of NaCl plugin | |
53 * produced by the given toolchain. | |
54 */ | |
55 function browserSupportsNaCl(tool) { | |
56 // Assume host toolchains always work with the given browser. | |
57 // The below mime-type checking might not work with | |
58 // --register-pepper-plugins. | |
59 if (isHostToolchain(tool)) { | |
60 return true; | |
61 } | |
62 var mimetype = mimeTypeForTool(tool); | |
63 return navigator.mimeTypes[mimetype] !== undefined; | |
64 } | |
65 | |
66 /** | |
67 * Inject a script into the DOM, and call a callback when it is loaded. | |
68 * | |
69 * @param {string} url The url of the script to load. | |
70 * @param {Function} onload The callback to call when the script is loaded. | |
71 * @param {Function} onerror The callback to call if the script fails to load. | |
72 */ | |
73 function injectScript(url, onload, onerror) { | |
74 var scriptEl = document.createElement('script'); | |
75 scriptEl.type = 'text/javascript'; | |
76 scriptEl.src = url; | |
77 scriptEl.onload = onload; | |
78 if (onerror) { | |
79 scriptEl.addEventListener('error', onerror, false); | |
80 } | |
81 document.head.appendChild(scriptEl); | |
82 } | |
83 | |
84 /** | |
85 * Run all tests for this example. | |
86 * | |
87 * @param {Object} moduleEl The module DOM element. | |
88 */ | |
89 function runTests(moduleEl) { | |
90 console.log('runTests()'); | |
91 common.tester = new Tester(); | |
92 | |
93 // All NaCl SDK examples are OK if the example exits cleanly; (i.e. the | |
94 // NaCl module returns 0 or calls exit(0)). | |
95 // | |
96 // Without this exception, the browser_tester thinks that the module | |
97 // has crashed. | |
98 common.tester.exitCleanlyIsOK(); | |
99 | |
100 common.tester.addAsyncTest('loaded', function(test) { | |
101 test.pass(); | |
102 }); | |
103 | |
104 if (typeof window.addTests !== 'undefined') { | |
105 window.addTests(); | |
106 } | |
107 | |
108 common.tester.waitFor(moduleEl); | |
109 common.tester.run(); | |
110 } | |
111 | |
112 /** | |
113 * Create the Native Client <embed> element as a child of the DOM element | |
114 * named "listener". | |
115 * | |
116 * @param {string} name The name of the example. | |
117 * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. | |
118 * @param {string} path Directory name where .nmf file can be found. | |
119 * @param {number} width The width to create the plugin. | |
120 * @param {number} height The height to create the plugin. | |
121 * @param {Object} attrs Dictionary of attributes to set on the module. | |
122 */ | |
123 function createNaClModule(name, tool, path, width, height, attrs) { | |
124 var moduleEl = document.createElement('embed'); | |
125 moduleEl.setAttribute('name', 'nacl_module'); | |
126 moduleEl.setAttribute('id', 'nacl_module'); | |
127 moduleEl.setAttribute('width', width); | |
128 moduleEl.setAttribute('height', height); | |
129 moduleEl.setAttribute('path', path); | |
130 moduleEl.setAttribute('src', path + '/' + name + '.nmf'); | |
131 | |
132 // Add any optional arguments | |
133 if (attrs) { | |
134 for (var key in attrs) { | |
135 moduleEl.setAttribute(key, attrs[key]); | |
136 } | |
137 } | |
138 | |
139 var mimetype = mimeTypeForTool(tool); | |
140 moduleEl.setAttribute('type', mimetype); | |
141 | |
142 // The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' | |
143 // and a 'message' event listener attached. This wrapping method is used | |
144 // instead of attaching the event listeners directly to the <EMBED> element | |
145 // to ensure that the listeners are active before the NaCl module 'load' | |
146 // event fires. | |
147 var listenerDiv = document.getElementById('listener'); | |
148 listenerDiv.appendChild(moduleEl); | |
149 | |
150 // Host plugins don't send a moduleDidLoad message. We'll fake it here. | |
151 var isHost = isHostToolchain(tool); | |
152 if (isHost) { | |
153 window.setTimeout(function() { | |
154 moduleEl.readyState = 1; | |
155 moduleEl.dispatchEvent(new CustomEvent('loadstart')); | |
156 moduleEl.readyState = 4; | |
157 moduleEl.dispatchEvent(new CustomEvent('load')); | |
158 moduleEl.dispatchEvent(new CustomEvent('loadend')); | |
159 }, 100); // 100 ms | |
160 } | |
161 | |
162 // This is code that is only used to test the SDK. | |
163 if (isTest) { | |
164 var loadNaClTest = function() { | |
165 injectScript('nacltest.js', function() { | |
166 runTests(moduleEl); | |
167 }); | |
168 }; | |
169 | |
170 // Try to load test.js for the example. Whether or not it exists, load | |
171 // nacltest.js. | |
172 injectScript('test.js', loadNaClTest, loadNaClTest); | |
173 } | |
174 } | |
175 | |
176 /** | |
177 * Add the default "load" and "message" event listeners to the element with | |
178 * id "listener". | |
179 * | |
180 * The "load" event is sent when the module is successfully loaded. The | |
181 * "message" event is sent when the naclModule posts a message using | |
182 * PPB_Messaging.PostMessage() (in C) or pp::Instance().PostMessage() (in | |
183 * C++). | |
184 */ | |
185 function attachDefaultListeners() { | |
186 var listenerDiv = document.getElementById('listener'); | |
187 listenerDiv.addEventListener('load', moduleDidLoad, true); | |
188 listenerDiv.addEventListener('message', handleMessage, true); | |
189 listenerDiv.addEventListener('crash', handleCrash, true); | |
190 if (typeof window.attachListeners !== 'undefined') { | |
191 window.attachListeners(); | |
192 } | |
193 } | |
194 | |
195 | |
196 /** | |
197 * Called when the Browser can not communicate with the Module | |
198 * | |
199 * This event listener is registered in attachDefaultListeners above. | |
200 */ | |
201 function handleCrash(event) { | |
202 if (common.naclModule.exitStatus == -1) { | |
203 updateStatus('CRASHED'); | |
204 } else { | |
205 updateStatus('EXITED [' + common.naclModule.exitStatus + ']'); | |
206 } | |
207 if (typeof window.handleCrash !== 'undefined') { | |
208 window.handleCrash(common.naclModule.lastError); | |
209 } | |
210 } | |
211 | |
212 /** | |
213 * Called when the NaCl module is loaded. | |
214 * | |
215 * This event listener is registered in attachDefaultListeners above. | |
216 */ | |
217 function moduleDidLoad() { | |
218 common.naclModule = document.getElementById('nacl_module'); | |
219 updateStatus('RUNNING'); | |
220 | |
221 if (typeof window.moduleDidLoad !== 'undefined') { | |
222 window.moduleDidLoad(); | |
223 } | |
224 } | |
225 | |
226 /** | |
227 * Hide the NaCl module's embed element. | |
228 * | |
229 * We don't want to hide by default; if we do, it is harder to determine that | |
230 * a plugin failed to load. Instead, call this function inside the example's | |
231 * "moduleDidLoad" function. | |
232 * | |
233 */ | |
234 function hideModule() { | |
235 // Setting common.naclModule.style.display = "None" doesn't work; the | |
236 // module will no longer be able to receive postMessages. | |
237 common.naclModule.style.height = '0'; | |
238 } | |
239 | |
240 /** | |
241 * Return true when |s| starts with the string |prefix|. | |
242 * | |
243 * @param {string} s The string to search. | |
244 * @param {string} prefix The prefix to search for in |s|. | |
245 */ | |
246 function startsWith(s, prefix) { | |
247 // indexOf would search the entire string, lastIndexOf(p, 0) only checks at | |
248 // the first index. See: http://stackoverflow.com/a/4579228 | |
249 return s.lastIndexOf(prefix, 0) === 0; | |
250 } | |
251 | |
252 /** Maximum length of logMessageArray. */ | |
253 var kMaxLogMessageLength = 20; | |
254 | |
255 /** An array of messages to display in the element with id "log". */ | |
256 var logMessageArray = []; | |
257 | |
258 /** | |
259 * Add a message to an element with id "log". | |
260 * | |
261 * This function is used by the default "log:" message handler. | |
262 * | |
263 * @param {string} message The message to log. | |
264 */ | |
265 function logMessage(message) { | |
266 logMessageArray.push(message); | |
267 if (logMessageArray.length > kMaxLogMessageLength) | |
268 logMessageArray.shift(); | |
269 | |
270 document.getElementById('log').textContent = logMessageArray.join('\n'); | |
271 console.log(message); | |
272 } | |
273 | |
274 /** | |
275 */ | |
276 var defaultMessageTypes = { | |
277 'alert': alert, | |
278 'log': logMessage | |
279 }; | |
280 | |
281 /** | |
282 * Called when the NaCl module sends a message to JavaScript (via | |
283 * PPB_Messaging.PostMessage()) | |
284 * | |
285 * This event listener is registered in createNaClModule above. | |
286 * | |
287 * @param {Event} message_event A message event. message_event.data contains | |
288 * the data sent from the NaCl module. | |
289 */ | |
290 function handleMessage(message_event) { | |
291 if (typeof message_event.data === 'string') { | |
292 for (var type in defaultMessageTypes) { | |
293 if (defaultMessageTypes.hasOwnProperty(type)) { | |
294 if (startsWith(message_event.data, type + ':')) { | |
295 func = defaultMessageTypes[type]; | |
296 func(message_event.data.slice(type.length + 1)); | |
297 return; | |
298 } | |
299 } | |
300 } | |
301 } | |
302 | |
303 if (typeof window.handleMessage !== 'undefined') { | |
304 window.handleMessage(message_event); | |
305 return; | |
306 } | |
307 | |
308 logMessage('Unhandled message: ' + message_event.data); | |
309 } | |
310 | |
311 /** | |
312 * Called when the DOM content has loaded; i.e. the page's document is fully | |
313 * parsed. At this point, we can safely query any elements in the document via | |
314 * document.querySelector, document.getElementById, etc. | |
315 * | |
316 * @param {string} name The name of the example. | |
317 * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. | |
318 * @param {string} path Directory name where .nmf file can be found. | |
319 * @param {number} width The width to create the plugin. | |
320 * @param {number} height The height to create the plugin. | |
321 * @param {Object} attrs Optional dictionary of additional attributes. | |
322 */ | |
323 function domContentLoaded(name, tool, path, width, height, attrs) { | |
324 // If the page loads before the Native Client module loads, then set the | |
325 // status message indicating that the module is still loading. Otherwise, | |
326 // do not change the status message. | |
327 updateStatus('Page loaded.'); | |
328 if (!browserSupportsNaCl(tool)) { | |
329 updateStatus( | |
330 'Browser does not support NaCl (' + tool + '), or NaCl is disabled'); | |
331 } else if (common.naclModule == null) { | |
332 updateStatus('Creating embed: ' + tool); | |
333 | |
334 // We use a non-zero sized embed to give Chrome space to place the bad | |
335 // plug-in graphic, if there is a problem. | |
336 width = typeof width !== 'undefined' ? width : 200; | |
337 height = typeof height !== 'undefined' ? height : 200; | |
338 attachDefaultListeners(); | |
339 createNaClModule(name, tool, path, width, height, attrs); | |
340 } else { | |
341 // It's possible that the Native Client module onload event fired | |
342 // before the page's onload event. In this case, the status message | |
343 // will reflect 'SUCCESS', but won't be displayed. This call will | |
344 // display the current message. | |
345 updateStatus('Waiting.'); | |
346 } | |
347 } | |
348 | |
349 /** Saved text to display in the element with id 'statusField'. */ | |
350 var statusText = 'NO-STATUSES'; | |
351 | |
352 /** | |
353 * Set the global status message. If the element with id 'statusField' | |
354 * exists, then set its HTML to the status message as well. | |
355 * | |
356 * @param {string} opt_message The message to set. If null or undefined, then | |
357 * set element 'statusField' to the message from the last call to | |
358 * updateStatus. | |
359 */ | |
360 function updateStatus(opt_message) { | |
361 if (opt_message) { | |
362 statusText = opt_message; | |
363 } | |
364 var statusField = document.getElementById('statusField'); | |
365 if (statusField) { | |
366 statusField.innerHTML = statusText; | |
367 } | |
368 } | |
369 | |
370 // The symbols to export. | |
371 return { | |
372 /** A reference to the NaCl module, once it is loaded. */ | |
373 naclModule: null, | |
374 | |
375 attachDefaultListeners: attachDefaultListeners, | |
376 domContentLoaded: domContentLoaded, | |
377 createNaClModule: createNaClModule, | |
378 hideModule: hideModule, | |
379 logMessage: logMessage, | |
380 updateStatus: updateStatus | |
381 }; | |
382 | |
383 }()); | |
384 | |
385 // Listen for the DOM content to be loaded. This event is fired when parsing of | |
386 // the page's document has finished. | |
387 document.addEventListener('DOMContentLoaded', function() { | |
388 var body = document.body; | |
389 | |
390 // The data-* attributes on the body can be referenced via body.dataset. | |
391 if (body.dataset) { | |
392 var loadFunction; | |
393 if (!body.dataset.customLoad) { | |
394 loadFunction = common.domContentLoaded; | |
395 } else if (typeof window.domContentLoaded !== 'undefined') { | |
396 loadFunction = window.domContentLoaded; | |
397 } | |
398 | |
399 // From https://developer.mozilla.org/en-US/docs/DOM/window.location | |
400 var searchVars = {}; | |
401 if (window.location.search.length > 1) { | |
402 var pairs = window.location.search.substr(1).split('&'); | |
403 for (var key_ix = 0; key_ix < pairs.length; key_ix++) { | |
404 var keyValue = pairs[key_ix].split('='); | |
405 searchVars[unescape(keyValue[0])] = | |
406 keyValue.length > 1 ? unescape(keyValue[1]) : ''; | |
407 } | |
408 } | |
409 | |
410 if (loadFunction) { | |
411 var toolchains = body.dataset.tools.split(' '); | |
412 var configs = body.dataset.configs.split(' '); | |
413 | |
414 var attrs = {}; | |
415 if (body.dataset.attrs) { | |
416 var attr_list = body.dataset.attrs.split(' '); | |
417 for (var key in attr_list) { | |
418 var attr = attr_list[key].split('='); | |
419 var key = attr[0]; | |
420 var value = attr[1]; | |
421 attrs[key] = value; | |
422 } | |
423 } | |
424 | |
425 var tc = toolchains.indexOf(searchVars.tc) !== -1 ? | |
426 searchVars.tc : toolchains[0]; | |
427 var config = configs.indexOf(searchVars.config) !== -1 ? | |
428 searchVars.config : configs[0]; | |
429 var pathFormat = body.dataset.path; | |
430 var path = pathFormat.replace('{tc}', tc).replace('{config}', config); | |
431 | |
432 isTest = searchVars.test === 'true'; | |
433 isRelease = path.toLowerCase().indexOf('release') != -1; | |
434 | |
435 loadFunction(body.dataset.name, tc, path, body.dataset.width, | |
436 body.dataset.height, attrs); | |
437 } | |
438 } | |
439 }); | |
OLD | NEW |