| 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 |