2 * Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
12 // CSP means that we can't kick off the initialization from the html file,
13 // so we do it like this instead.
14 window.onload = function() {
21 * The hterm-powered terminal command.
23 * This class defines a command that can be run in an hterm.Terminal instance.
25 * @param {Object} argv The argument object passed in from the Terminal.
27 function NaClTerm(argv) {
28 this.io = argv.io.push();
32 var ansiCyan = '\x1b[36m';
33 var ansiReset = '\x1b[0m';
36 * Static initialier called from index.html.
38 * This constructs a new Terminal instance and instructs it to run the NaClTerm
41 NaClTerm.init = function() {
42 var profileName = lib.f.parseQuery(document.location.search)['profile'];
43 var terminal = new hterm.Terminal(profileName);
44 terminal.decorate(document.querySelector('#terminal'));
46 // Useful for console debugging.
47 window.term_ = terminal;
49 terminal.runCommandClass(NaClTerm, document.location.hash.substr(1));
53 NaClTerm.prototype.updateStatus = function(message) {
54 document.getElementById('statusField').textContent = message;
55 this.io.print(message + '\n');
59 * Handle messages sent to us from NaCl.
63 NaClTerm.prototype.handleMessage_ = function(e) {
64 if (e.data.indexOf(NaClTerm.prefix) == 0) {
65 var msg = e.data.substring(NaClTerm.prefix.length);
67 this.bufferedOutput += msg;
71 } else if (e.data.indexOf('exited') == 0) {
72 var exitCode = e.data.split(':', 2)[1]
73 if (exitCode === undefined)
77 console.log('unexpected message: ' + e.data);
83 * Handle load error event from NaCl.
85 NaClTerm.prototype.handleLoadAbort_ = function(e) {
86 this.updateStatus('Load aborted.');
90 * Handle load abort event from NaCl.
92 NaClTerm.prototype.handleLoadError_ = function(e) {
93 this.updateStatus(embed.lastError);
96 NaClTerm.prototype.doneLoadingUrl = function() {
97 var width = this.io.terminal_.screenSize.width;
98 this.io.print('\r' + Array(width+1).join(' '));
99 var message = '\rLoaded ' + this.lastUrl;
100 if (this.lastTotal) {
101 var kbsize = Math.round(this.lastTotal/1024)
102 message += ' ['+ kbsize + ' KiB]';
104 this.io.print(message.slice(0, width) + '\n')
108 * Handle load end event from NaCl.
110 NaClTerm.prototype.handleLoad_ = function(e) {
112 this.doneLoadingUrl();
114 this.io.print('Loaded.\n');
117 document.getElementById('loading-cover').style.display = 'none';
119 this.io.print(ansiReset);
121 // Now that have completed loading and displaying
122 // loading messages we output any messages from the
123 // NaCl module that were buffered up unto this point
125 this.io.print(this.bufferedOutput);
126 this.sendMessage(this.bufferedInput);
127 this.bufferedOutput = ''
128 this.bufferedInput = ''
132 * Handle load progress event from NaCl.
134 NaClTerm.prototype.handleProgress_ = function(e) {
135 var url = e.url.substring(e.url.lastIndexOf('/') + 1);
137 if (this.lastUrl && this.lastUrl != url)
138 this.doneLoadingUrl()
144 var message = 'Loading ' + url;
146 if (e.lengthComputable && e.total > 0) {
147 percent = Math.round(e.loaded * 100 / e.total);
148 var kbloaded = Math.round(e.loaded / 1024);
149 var kbtotal = Math.round(e.total / 1024);
150 message += ' [' + kbloaded + ' KiB/' + kbtotal + ' KiB ' + percent + '%]';
153 document.getElementById('progress-bar').style.width = percent + "%";
155 var width = this.io.terminal_.screenSize.width;
156 this.io.print('\r' + message.slice(-width));
158 this.lastTotal = e.total;
162 * Handle crash event from NaCl.
164 NaClTerm.prototype.handleCrash_ = function(e) {
165 this.exit(this.embed.exitStatus);
171 NaClTerm.prototype.exit = function(code) {
172 this.io.print(ansiCyan)
174 this.io.print('Program crashed (exit status -1)\n')
176 this.io.print('Program exited (status=' + code + ')\n');
181 NaClTerm.prototype.restartNaCl = function() {
182 if (this.embed !== undefined) {
183 document.getElementById("listener").removeChild(this.embed);
186 this.io.terminal_.reset();
188 this.createEmbed(this.io.terminal_.screenSize.width, this.io.terminal_.screenSize.height);
192 * Create the NaCl embed element.
193 * We delay this until the first terminal resize event so that we start
194 * with the correct size.
196 NaClTerm.prototype.createEmbed = function(width, height) {
197 var mimetype = 'application/x-pnacl';
198 if (navigator.mimeTypes[mimetype] === undefined) {
199 if (mimetype.indexOf('pnacl') != -1)
200 this.updateStatus('Browser does not support PNaCl or PNaCl is disabled');
202 this.updateStatus('Browser does not support NaCl or NaCl is disabled');
206 var embed = document.createElement('object');
209 embed.data = NaClTerm.nmf;
210 embed.type = mimetype;
211 embed.addEventListener('message', this.handleMessage_.bind(this));
212 embed.addEventListener('progress', this.handleProgress_.bind(this));
213 embed.addEventListener('load', this.handleLoad_.bind(this));
214 embed.addEventListener('error', this.handleLoadError_.bind(this));
215 embed.addEventListener('abort', this.handleLoadAbort_.bind(this));
216 embed.addEventListener('crash', this.handleCrash_.bind(this));
218 function addParam(name, value) {
219 var param = document.createElement('param');
222 embed.appendChild(param);
225 addParam('PS_TTY_PREFIX', NaClTerm.prefix);
226 addParam('PS_TTY_RESIZE', 'tty_resize');
227 addParam('PS_TTY_COLS', width);
228 addParam('PS_TTY_ROWS', height);
229 addParam('PS_STDIN', '/dev/tty');
230 addParam('PS_STDOUT', '/dev/tty');
231 addParam('PS_STDERR', '/dev/tty');
232 addParam('PS_VERBOSITY', '2');
233 addParam('PS_EXIT_MESSAGE', 'exited');
234 addParam('TERM', 'xterm-256color');
235 addParam('LUA_DATA_URL', 'http://storage.googleapis.com/gonacl/demos/publish/234230_dev/lua');
237 // Add ARGV arguments from query parameters.
238 var args = lib.f.parseQuery(document.location.search);
239 for (var argname in args) {
240 addParam(argname, args[argname]);
243 // If the application has set NaClTerm.argv and there were
244 // no arguments set in the query parameters then add the default
245 // NaClTerm.argv arguments.
246 if (args['arg1'] === undefined && NaClTerm.argv) {
248 NaClTerm.argv.forEach(function(arg) {
249 var argname = 'arg' + argn;
250 addParam(argname, arg);
255 this.updateStatus('Loading...');
256 this.io.print('Loading NaCl module.\n')
257 document.getElementById("listener").appendChild(embed);
261 NaClTerm.prototype.onTerminalResize_ = function(width, height) {
262 if (this.embed === undefined)
263 this.createEmbed(width, height);
265 this.embed.postMessage({'tty_resize': [ width, height ]});
267 // Require at least 80 columns, otherwise some of the demos look
269 var width = this.io.terminal_.scrollPort_.characterSize.width * 80;
270 document.getElementById("terminal").style.minWidth = width + 'px';
273 NaClTerm.prototype.sendMessage = function(msg) {
275 this.bufferedInput += msg;
279 message[NaClTerm.prefix] = msg;
280 this.embed.postMessage(message);
283 NaClTerm.prototype.onVTKeystroke_ = function(str) {
284 this.sendMessage(str)
287 NaClTerm.prototype.startCommand = function() {
288 // We don't properly support the hterm bell sound, so we need to disable it.
289 this.io.terminal_.prefs_.definePreference('audible-bell-sound', '');
290 this.io.terminal_.setAutoCarriageReturn(true);
291 this.io.terminal_.setCursorPosition(0, 0);
292 this.io.terminal_.setCursorVisible(true);
294 this.bufferedOutput = '';
295 this.bufferedInput = '';
297 this.io.print(ansiCyan);
301 * This is invoked by the terminal as a result of terminal.runCommandClass().
303 NaClTerm.prototype.run = function() {
305 this.io.onVTKeystroke = this.onVTKeystroke_.bind(this);
306 this.io.onTerminalResize = this.onTerminalResize_.bind(this);