OLD | NEW |
1 /* | 1 /* |
2 * Copyright (c) 2014 The Native Client Authors. All rights reserved. | 2 * Copyright (c) 2014 The Native Client Authors. All rights reserved. |
3 * Use of this source code is governed by a BSD-style license that can be | 3 * Use of this source code is governed by a BSD-style license that can be |
4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
5 */ | 5 */ |
6 | 6 |
7 /* globals lib, NaClProcessManager, hterm */ | 7 /* globals lib, NaClProcessManager, hterm */ |
8 | 8 |
9 'use strict'; | 9 'use strict'; |
10 | 10 |
11 lib.rtdep('lib.f', | 11 lib.rtdep('lib.f', |
12 'hterm', | 12 'hterm', |
13 'NaClProcessManager'); | 13 'NaClProcessManager'); |
14 | 14 |
15 // CSP means that we can't kick off the initialization from the html file, | |
16 // so we do it like this instead. | |
17 window.onload = function() { | |
18 lib.init(function() { | |
19 NaClTerm.init(); | |
20 }); | |
21 }; | |
22 | |
23 /** | 15 /** |
24 * This class uses the NaClProcessManager to run NaCl executables within an | 16 * This class uses the NaClProcessManager to run NaCl executables within an |
25 * hterm. | 17 * hterm. |
26 * | 18 * |
27 * @param {Object} argv The argument object passed in from the Terminal. | 19 * @param {Object} argv The argument object passed in from the Terminal. |
28 */ | 20 */ |
29 function NaClTerm(argv) { | 21 function NaClTerm(argv) { |
30 this.argv = argv; | 22 this.argv = argv; |
31 this.io = argv.io.push(); | 23 this.io = argv.io.push(); |
32 this.width = this.io.terminal_.screenSize.width; | 24 this.width = this.io.terminal_.screenSize.width; |
33 | 25 |
34 this.bufferedOutput = ''; | 26 this.bufferedOutput = ''; |
35 | 27 |
36 // Have we started spawning the initial process? | 28 // Have we started spawning the initial process? |
37 this.started = false; | 29 this.started = false; |
38 | 30 |
39 // Has the initial process finished loading? | 31 // Has the initial process finished loading? |
40 this.loaded = false; | 32 this.loaded = false; |
41 | 33 |
42 this.print = this.io.print.bind(this.io); | 34 this.print = this.io.print.bind(this.io); |
43 | 35 |
44 var mgr = this.processManager = new NaClProcessManager(); | 36 this.processManager = new NaClProcessManager(); |
45 mgr.setStdoutListener(this.handleStdout_.bind(this)); | 37 this.processManager.setStdoutListener(this.handleStdout_.bind(this)); |
46 mgr.setErrorListener(this.handleError_.bind(this)); | 38 this.processManager.setErrorListener(this.handleError_.bind(this)); |
47 mgr.setRootProgressListener(this.handleRootProgress_.bind(this)); | 39 this.processManager.setRootProgressListener( |
48 mgr.setRootLoadListener(this.handleRootLoad_.bind(this)); | 40 this.handleRootProgress_.bind(this)); |
| 41 this.processManager.setRootLoadListener(this.handleRootLoad_.bind(this)); |
49 } | 42 } |
50 | 43 |
51 /** | 44 /** |
52 * Flag for cyan coloring in the terminal. | 45 * Flag for cyan coloring in the terminal. |
53 * @const | 46 * @const |
54 */ | 47 */ |
55 NaClTerm.ANSI_CYAN = '\x1b[36m'; | 48 NaClTerm.ANSI_CYAN = '\x1b[36m'; |
56 | 49 |
57 /** | 50 /** |
58 * Flag for color reset in the terminal. | 51 * Flag for color reset in the terminal. |
59 * @const | 52 * @const |
60 */ | 53 */ |
61 NaClTerm.ANSI_RESET = '\x1b[0m'; | 54 NaClTerm.ANSI_RESET = '\x1b[0m'; |
62 | 55 |
63 /* | 56 /* |
64 * Character code for Control+C in the terminal. | 57 * Character code for Control+C in the terminal. |
65 * @type {number} | 58 * @type {number} |
66 */ | 59 */ |
67 NaClTerm.CONTROL_C = 3; | 60 NaClTerm.CONTROL_C = 3; |
68 | 61 |
69 /** | 62 /** |
70 * Add the appropriate hooks to HTerm to start the session. | 63 * Add the appropriate hooks to HTerm to start the session. |
71 */ | 64 */ |
72 NaClTerm.prototype.run = function() { | 65 NaClTerm.prototype.run = function () { |
73 this.io.onVTKeystroke = this.onVTKeystroke_.bind(this); | 66 this.io.onVTKeystroke = this.onVTKeystroke_.bind(this); |
74 this.io.sendString = this.onVTKeystroke_.bind(this); | 67 this.io.sendString = this.onVTKeystroke_.bind(this); |
75 this.io.onTerminalResize = this.onTerminalResize_.bind(this); | 68 this.io.onTerminalResize = this.onTerminalResize_.bind(this); |
76 | 69 |
77 this.print(NaClTerm.ANSI_CYAN); | 70 this.print(NaClTerm.ANSI_CYAN); |
78 }; | 71 }; |
79 | 72 |
80 /** | 73 /** |
81 * Static initializer called from index.html. | 74 * Static initializer called from index.html. |
82 * | 75 * |
83 * This constructs a new Terminal instance and instructs it to run a NaClTerm. | 76 * This constructs a new Terminal instance and instructs it to run a NaClTerm. |
84 */ | 77 */ |
85 NaClTerm.init = function() { | 78 NaClTerm.init = function () { |
86 var profileName = lib.f.parseQuery(document.location.search)['profile']; | 79 var profileName = lib.f.parseQuery(document.location.search).profile; |
87 var terminal = new hterm.Terminal(profileName); | 80 var terminal = new hterm.Terminal(profileName); |
88 terminal.decorate(document.querySelector('#terminal')); | 81 terminal.decorate(document.querySelector('#terminal')); |
89 | 82 |
90 // Useful for console debugging. | 83 // Useful for console debugging. |
91 window.term_ = terminal; | 84 window.term_ = terminal; |
92 | 85 |
93 // We don't properly support the hterm bell sound, so we need to disable it. | 86 // We don't properly support the hterm bell sound, so we need to disable it. |
94 terminal.prefs_.definePreference('audible-bell-sound', ''); | 87 terminal.prefs_.definePreference('audible-bell-sound', ''); |
95 | 88 |
96 // TODO(bradnelson/rginda): Drop when hterm auto-detects this. | 89 // TODO(bradnelson/rginda): Drop when hterm auto-detects this. |
(...skipping 11 matching lines...) Expand all Loading... |
108 terminal.runCommandClass(NaClTerm, document.location.hash.substr(1)); | 101 terminal.runCommandClass(NaClTerm, document.location.hash.substr(1)); |
109 | 102 |
110 return true; | 103 return true; |
111 }; | 104 }; |
112 | 105 |
113 /** | 106 /** |
114 * Handle stdout event from NaClProcessManager. | 107 * Handle stdout event from NaClProcessManager. |
115 * @private | 108 * @private |
116 * @param {string} msg The string sent to stdout. | 109 * @param {string} msg The string sent to stdout. |
117 */ | 110 */ |
118 NaClTerm.prototype.handleStdout_ = function(msg) { | 111 NaClTerm.prototype.handleStdout_ = function (msg) { |
119 if (!this.loaded) { | 112 if (!this.loaded) { |
120 this.bufferedOutput += msg; | 113 this.bufferedOutput += msg; |
121 } else { | 114 } else { |
122 this.print(msg); | 115 this.print(msg); |
123 } | 116 } |
124 }; | 117 }; |
125 | 118 |
126 /** | 119 /** |
127 * Handle error event from NaCl. | 120 * Handle error event from NaCl. |
128 * @private | 121 * @private |
129 * @param {string} cmd The name of the process with the error. | 122 * @param {string} cmd The name of the process with the error. |
130 * @param {string} err The error message. | 123 * @param {string} err The error message. |
131 */ | 124 */ |
132 NaClTerm.prototype.handleError_ = function(cmd, err) { | 125 NaClTerm.prototype.handleError_ = function (cmd, err) { |
133 this.print(cmd + ': ' + err + '\n'); | 126 this.print(cmd + ': ' + err + '\n'); |
134 }; | 127 }; |
135 | 128 |
136 /** | 129 /** |
137 * Notify the user when we are done loading a URL. | 130 * Notify the user when we are done loading a URL. |
138 * @private | 131 * @private |
139 */ | 132 */ |
140 NaClTerm.prototype.doneLoadingUrl_ = function() { | 133 NaClTerm.prototype.doneLoadingUrl_ = function () { |
141 var width = this.width; | 134 var width = this.width; |
142 this.print('\r' + new Array(width+1).join(' ')); | 135 this.print('\r' + ' '.repeat(width + 1)); |
143 var message = '\rLoaded ' + this.lastUrl; | 136 var message = '\rLoaded ' + this.lastUrl; |
144 if (this.lastTotal) { | 137 if (this.lastTotal) { |
145 var kbsize = Math.round(this.lastTotal/1024); | 138 var kbsize = Math.round(this.lastTotal / 1024); |
146 message += ' ['+ kbsize + ' KiB]'; | 139 message += ' [' + kbsize + ' KiB]'; |
147 } | 140 } |
148 this.print(message.slice(0, width) + '\n'); | 141 this.print(message.slice(0, width) + '\n'); |
149 }; | 142 }; |
150 | 143 |
151 /** | 144 /** |
152 * Handle load progress event from NaCl for the root process. | 145 * Handle load progress event from NaCl for the root process. |
153 * @private | 146 * @private |
154 * @param {string} url The URL that is being loaded. | 147 * @param {string} url The URL that is being loaded. |
155 * @param {boolean} lengthComputable Is our progress quantitatively measurable? | 148 * @param {boolean} lengthComputable Is our progress quantitatively measurable? |
156 * @param {number} loaded The number of bytes that have been loaded. | 149 * @param {number} loaded The number of bytes that have been loaded. |
157 * @param {number} total The total number of bytes to be loaded. | 150 * @param {number} total The total number of bytes to be loaded. |
158 */ | 151 */ |
159 NaClTerm.prototype.handleRootProgress_ = function( | 152 NaClTerm.prototype.handleRootProgress_ = function ( |
160 url, lengthComputable, loaded, total) { | 153 url, lengthComputable, loaded, total) { |
161 if (url !== undefined) | 154 if (url !== undefined) { |
162 url = url.substring(url.lastIndexOf('/') + 1); | 155 url = url.substring(url.lastIndexOf('/') + 1); |
| 156 } |
163 | 157 |
164 if (this.lastUrl && this.lastUrl !== url) | 158 if (this.lastUrl && this.lastUrl !== url) { |
165 this.doneLoadingUrl_(); | 159 this.doneLoadingUrl_(); |
| 160 } |
166 | 161 |
167 if (!url) | 162 if (!url) { |
168 return; | 163 return; |
| 164 } |
169 | 165 |
170 this.lastUrl = url; | 166 this.lastUrl = url; |
171 this.lastTotal = total; | 167 this.lastTotal = total; |
172 | 168 |
173 var message = 'Loading ' + url; | 169 var message = 'Loading ' + url; |
174 if (lengthComputable && total) { | 170 if (lengthComputable && total) { |
175 var percent = Math.round(loaded * 100 / total); | 171 var percent = Math.round(loaded * 100 / total); |
176 var kbloaded = Math.round(loaded / 1024); | 172 var kbloaded = Math.round(loaded / 1024); |
177 var kbtotal = Math.round(total / 1024); | 173 var kbtotal = Math.round(total / 1024); |
178 message += ' [' + kbloaded + ' KiB/' + kbtotal + ' KiB ' + percent + '%]'; | 174 message += ' [' + kbloaded + ' KiB/' + kbtotal + ' KiB ' + percent + '%]'; |
179 } | 175 } |
180 | 176 |
181 this.print('\r' + message.slice(-this.width)); | 177 this.print('\r' + message.slice(-this.width)); |
182 }; | 178 }; |
183 | 179 |
184 /** | 180 /** |
185 * Handle load end event from NaCl for the root process. | 181 * Handle load end event from NaCl for the root process. |
186 * @private | 182 * @private |
187 */ | 183 */ |
188 NaClTerm.prototype.handleRootLoad_ = function() { | 184 NaClTerm.prototype.handleRootLoad_ = function () { |
189 if (this.lastUrl) | 185 if (this.lastUrl) { |
190 this.doneLoadingUrl_(); | 186 this.doneLoadingUrl_(); |
191 else | 187 } else { |
192 this.print('Loaded.\n'); | 188 this.print('Loaded.\n'); |
| 189 } |
193 | 190 |
194 this.print(NaClTerm.ANSI_RESET); | 191 this.print(NaClTerm.ANSI_RESET); |
195 | 192 |
196 // Now that have completed loading and displaying | 193 // Now that have completed loading and displaying |
197 // loading messages we output any messages from the | 194 // loading messages we output any messages from the |
198 // NaCl module that were buffered up unto this point | 195 // NaCl module that were buffered up unto this point |
199 this.loaded = true; | 196 this.loaded = true; |
200 this.print(this.bufferedOutput); | 197 this.print(this.bufferedOutput); |
201 this.bufferedOutput = ''; | 198 this.bufferedOutput = ''; |
202 }; | 199 }; |
203 | 200 |
204 /** | 201 /** |
205 * Clean up once the root process exits. | 202 * Clean up once the root process exits. |
206 * @private | 203 * @private |
207 * @param {number} pid The PID of the process that exited. | 204 * @param {number} pid The PID of the process that exited. |
208 * @param {number} status The exit code of the process. | 205 * @param {number} status The exit code of the process. |
209 */ | 206 */ |
210 NaClTerm.prototype.handleExit_ = function(pid, status) { | 207 NaClTerm.prototype.handleExit_ = function (pid, status) { |
211 this.print(NaClTerm.ANSI_CYAN); | 208 this.print(NaClTerm.ANSI_CYAN); |
212 | 209 |
213 // The root process finished. | 210 // The root process finished. |
214 if (status === -1) { | 211 if (status === -1) { |
215 this.print('Program (' + NaClTerm.nmf + | 212 this.print('Program (' + NaClTerm.nmf + |
216 ') crashed (exit status -1)\n'); | 213 ') crashed (exit status -1)\n'); |
217 } else { | 214 } else { |
218 this.print('Program (' + NaClTerm.nmf + ') exited ' + | 215 this.print('Program (' + NaClTerm.nmf + ') exited ' + |
219 '(status=' + status + ')\n'); | 216 '(status=' + status + ')\n'); |
220 } | 217 } |
221 this.argv.io.pop(); | 218 this.argv.io.pop(); |
222 // Remove window close handler. | 219 // Remove window close handler. |
223 window.onbeforeunload = function() { return null; }; | 220 window.onbeforeunload = function () { return null; }; |
224 if (this.argv.onExit) { | 221 if (this.argv.onExit) { |
225 this.argv.onExit(status); | 222 this.argv.onExit(status); |
226 } | 223 } |
227 }; | 224 }; |
228 | 225 |
229 /** | 226 /** |
230 * Spawn the root process (usually bash). | 227 * Spawn the root process (usually bash). |
231 * @private | 228 * @private |
232 * | 229 * |
233 * We delay this call until the first terminal resize event so that we start | 230 * We delay this call until the first terminal resize event so that we start |
234 * with the correct size. | 231 * with the correct size. |
235 */ | 232 */ |
236 NaClTerm.prototype.spawnRootProcess_ = function() { | 233 NaClTerm.prototype.spawnRootProcess_ = function () { |
237 var self = this; | 234 var self = this; |
238 var argv = NaClTerm.argv || []; | 235 var argv = NaClTerm.argv || []; |
239 var env = NaClTerm.env || []; | 236 var env = NaClTerm.env || []; |
240 var cwd = NaClTerm.cwd || '/'; | 237 var cwd = NaClTerm.cwd || '/'; |
241 argv = [NaClTerm.nmf].concat(argv); | 238 argv = [NaClTerm.nmf].concat(argv); |
242 | 239 |
243 try { | 240 try { |
244 var handleSuccess = function(naclType) { | 241 var handleSuccess = function (naclType) { |
245 self.print('Loading NaCl module.\n'); | 242 self.print('Loading NaCl module.\n'); |
246 self.processManager.spawn( | 243 self.processManager.spawn( |
247 NaClTerm.nmf, argv, env, cwd, naclType, null, function(rootPid) { | 244 NaClTerm.nmf, argv, env, cwd, naclType, null, function (rootPid) { |
248 // Warn if we close while still running. | 245 // Warn if we close while still running. |
249 window.onbeforeunload = function() { | 246 window.onbeforeunload = function () { |
250 return 'Processes still running!'; | 247 return 'Processes still running!'; |
251 }; | 248 }; |
252 self.processManager.waitpid(rootPid, 0, self.handleExit_.bind(self)); | 249 self.processManager.waitpid(rootPid, 0, self.handleExit_.bind(self)); |
253 }); | 250 }); |
254 }; | 251 }; |
255 var handleFailure = function(message) { | 252 var handleFailure = function (message) { |
256 self.print(message); | 253 self.print(message); |
257 }; | 254 }; |
258 self.processManager.checkUrlNaClManifestType( | 255 self.processManager.checkUrlNaClManifestType( |
259 NaClTerm.nmf, handleSuccess, handleFailure); | 256 NaClTerm.nmf, handleSuccess, handleFailure); |
260 } catch (e) { | 257 } catch (e) { |
261 self.print(e.message); | 258 self.print(e.message); |
262 } | 259 } |
263 | 260 |
264 self.started = true; | 261 self.started = true; |
265 }; | 262 }; |
266 | 263 |
267 /** | 264 /** |
268 * Handle hterm terminal resize events. | 265 * Handle hterm terminal resize events. |
269 * @private | 266 * @private |
270 * @param {number} width The width of the terminal. | 267 * @param {number} width The width of the terminal. |
271 * @param {number} height The height of the terminal. | 268 * @param {number} height The height of the terminal. |
272 */ | 269 */ |
273 NaClTerm.prototype.onTerminalResize_ = function(width, height) { | 270 NaClTerm.prototype.onTerminalResize_ = function (width, height) { |
274 this.processManager.onTerminalResize(width, height); | 271 this.processManager.onTerminalResize(width, height); |
275 if (!this.started) { | 272 if (!this.started) { |
276 this.spawnRootProcess_(); | 273 this.spawnRootProcess_(); |
277 } | 274 } |
278 }; | 275 }; |
279 | 276 |
280 /** | 277 /** |
281 * Handle hterm keystroke events. | 278 * Handle hterm keystroke events. |
282 * @private | 279 * @private |
283 * @param {string} str The characters sent by hterm. | 280 * @param {string} str The characters sent by hterm. |
284 */ | 281 */ |
285 NaClTerm.prototype.onVTKeystroke_ = function(str) { | 282 NaClTerm.prototype.onVTKeystroke_ = function (str) { |
286 try { | 283 try { |
287 if (str.charCodeAt(0) === NaClTerm.CONTROL_C) { | 284 if (str.charCodeAt(0) === NaClTerm.CONTROL_C) { |
288 if (this.processManager.sigint()) { | 285 if (this.processManager.sigint()) { |
289 this.print('\n'); | 286 this.print('\n'); |
290 return; | 287 return; |
291 } | 288 } |
292 } | 289 } |
293 this.processManager.sendStdinForeground(str); | 290 this.processManager.sendStdinForeground(str); |
294 } catch (e) { | 291 } catch (e) { |
295 this.print(e.message); | 292 this.print(e.message); |
296 } | 293 } |
297 }; | 294 }; |
| 295 |
| 296 // CSP means that we can't kick off the initialization from the html file, |
| 297 // so we do it like this instead. |
| 298 window.onload = function () { |
| 299 lib.init(function () { |
| 300 NaClTerm.init(); |
| 301 }); |
| 302 }; |
OLD | NEW |