Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | const Cc = Components.classes; |
michael@0 | 8 | const Ci = Components.interfaces; |
michael@0 | 9 | const Cu = Components.utils; |
michael@0 | 10 | const Cr = Components.results; |
michael@0 | 11 | const CC = Components.Constructor; |
michael@0 | 12 | |
michael@0 | 13 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 15 | |
michael@0 | 16 | const InputStreamPump = CC( |
michael@0 | 17 | "@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init"), |
michael@0 | 18 | AsyncStreamCopier = CC( |
michael@0 | 19 | "@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"), |
michael@0 | 20 | ScriptableInputStream = CC( |
michael@0 | 21 | "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"), |
michael@0 | 22 | BinaryInputStream = CC( |
michael@0 | 23 | "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"), |
michael@0 | 24 | StringInputStream = CC( |
michael@0 | 25 | '@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'), |
michael@0 | 26 | ArrayBufferInputStream = CC( |
michael@0 | 27 | '@mozilla.org/io/arraybuffer-input-stream;1', 'nsIArrayBufferInputStream'), |
michael@0 | 28 | MultiplexInputStream = CC( |
michael@0 | 29 | '@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream'); |
michael@0 | 30 | const TCPServerSocket = CC( |
michael@0 | 31 | "@mozilla.org/tcp-server-socket;1", "nsITCPServerSocketInternal", "init"); |
michael@0 | 32 | |
michael@0 | 33 | const kCONNECTING = 'connecting'; |
michael@0 | 34 | const kOPEN = 'open'; |
michael@0 | 35 | const kCLOSING = 'closing'; |
michael@0 | 36 | const kCLOSED = 'closed'; |
michael@0 | 37 | const kRESUME_ERROR = 'Calling resume() on a connection that was not suspended.'; |
michael@0 | 38 | |
michael@0 | 39 | const BUFFER_SIZE = 65536; |
michael@0 | 40 | const NETWORK_STATS_THRESHOLD = 65536; |
michael@0 | 41 | |
michael@0 | 42 | // XXX we have no TCPError implementation right now because it's really hard to |
michael@0 | 43 | // do on b2g18. On mozilla-central we want a proper TCPError that ideally |
michael@0 | 44 | // sub-classes DOMError. Bug 867872 has been filed to implement this and |
michael@0 | 45 | // contains a documented TCPError.webidl that maps all the error codes we use in |
michael@0 | 46 | // this file to slightly more readable explanations. |
michael@0 | 47 | function createTCPError(aWindow, aErrorName, aErrorType) { |
michael@0 | 48 | return new (aWindow ? aWindow.DOMError : DOMError)(aErrorName); |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | |
michael@0 | 52 | /* |
michael@0 | 53 | * Debug logging function |
michael@0 | 54 | */ |
michael@0 | 55 | |
michael@0 | 56 | let debug = false; |
michael@0 | 57 | function LOG(msg) { |
michael@0 | 58 | if (debug) |
michael@0 | 59 | dump("TCPSocket: " + msg + "\n"); |
michael@0 | 60 | } |
michael@0 | 61 | |
michael@0 | 62 | /* |
michael@0 | 63 | * nsITCPSocketEvent object |
michael@0 | 64 | */ |
michael@0 | 65 | |
michael@0 | 66 | function TCPSocketEvent(type, sock, data) { |
michael@0 | 67 | this._type = type; |
michael@0 | 68 | this._target = sock; |
michael@0 | 69 | this._data = data; |
michael@0 | 70 | } |
michael@0 | 71 | |
michael@0 | 72 | TCPSocketEvent.prototype = { |
michael@0 | 73 | __exposedProps__: { |
michael@0 | 74 | type: 'r', |
michael@0 | 75 | target: 'r', |
michael@0 | 76 | data: 'r' |
michael@0 | 77 | }, |
michael@0 | 78 | get type() { |
michael@0 | 79 | return this._type; |
michael@0 | 80 | }, |
michael@0 | 81 | get target() { |
michael@0 | 82 | return this._target; |
michael@0 | 83 | }, |
michael@0 | 84 | get data() { |
michael@0 | 85 | return this._data; |
michael@0 | 86 | } |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | /* |
michael@0 | 90 | * nsIDOMTCPSocket object |
michael@0 | 91 | */ |
michael@0 | 92 | |
michael@0 | 93 | function TCPSocket() { |
michael@0 | 94 | this._readyState = kCLOSED; |
michael@0 | 95 | |
michael@0 | 96 | this._onopen = null; |
michael@0 | 97 | this._ondrain = null; |
michael@0 | 98 | this._ondata = null; |
michael@0 | 99 | this._onerror = null; |
michael@0 | 100 | this._onclose = null; |
michael@0 | 101 | |
michael@0 | 102 | this._binaryType = "string"; |
michael@0 | 103 | |
michael@0 | 104 | this._host = ""; |
michael@0 | 105 | this._port = 0; |
michael@0 | 106 | this._ssl = false; |
michael@0 | 107 | |
michael@0 | 108 | this.useWin = null; |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | TCPSocket.prototype = { |
michael@0 | 112 | __exposedProps__: { |
michael@0 | 113 | open: 'r', |
michael@0 | 114 | host: 'r', |
michael@0 | 115 | port: 'r', |
michael@0 | 116 | ssl: 'r', |
michael@0 | 117 | bufferedAmount: 'r', |
michael@0 | 118 | suspend: 'r', |
michael@0 | 119 | resume: 'r', |
michael@0 | 120 | close: 'r', |
michael@0 | 121 | send: 'r', |
michael@0 | 122 | readyState: 'r', |
michael@0 | 123 | binaryType: 'r', |
michael@0 | 124 | listen: 'r', |
michael@0 | 125 | onopen: 'rw', |
michael@0 | 126 | ondrain: 'rw', |
michael@0 | 127 | ondata: 'rw', |
michael@0 | 128 | onerror: 'rw', |
michael@0 | 129 | onclose: 'rw' |
michael@0 | 130 | }, |
michael@0 | 131 | // The binary type, "string" or "arraybuffer" |
michael@0 | 132 | _binaryType: null, |
michael@0 | 133 | |
michael@0 | 134 | // Internal |
michael@0 | 135 | _hasPrivileges: null, |
michael@0 | 136 | |
michael@0 | 137 | // Raw socket streams |
michael@0 | 138 | _transport: null, |
michael@0 | 139 | _socketInputStream: null, |
michael@0 | 140 | _socketOutputStream: null, |
michael@0 | 141 | |
michael@0 | 142 | // Input stream machinery |
michael@0 | 143 | _inputStreamPump: null, |
michael@0 | 144 | _inputStreamScriptable: null, |
michael@0 | 145 | _inputStreamBinary: null, |
michael@0 | 146 | |
michael@0 | 147 | // Output stream machinery |
michael@0 | 148 | _multiplexStream: null, |
michael@0 | 149 | _multiplexStreamCopier: null, |
michael@0 | 150 | |
michael@0 | 151 | _asyncCopierActive: false, |
michael@0 | 152 | _waitingForDrain: false, |
michael@0 | 153 | _suspendCount: 0, |
michael@0 | 154 | |
michael@0 | 155 | // Reported parent process buffer |
michael@0 | 156 | _bufferedAmount: 0, |
michael@0 | 157 | |
michael@0 | 158 | // IPC socket actor |
michael@0 | 159 | _socketBridge: null, |
michael@0 | 160 | |
michael@0 | 161 | // StartTLS |
michael@0 | 162 | _waitingForStartTLS: false, |
michael@0 | 163 | _pendingDataAfterStartTLS: [], |
michael@0 | 164 | |
michael@0 | 165 | // Used to notify when update bufferedAmount is updated. |
michael@0 | 166 | _onUpdateBufferedAmount: null, |
michael@0 | 167 | _trackingNumber: 0, |
michael@0 | 168 | |
michael@0 | 169 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 170 | // Network statistics (Gonk-specific feature) |
michael@0 | 171 | _txBytes: 0, |
michael@0 | 172 | _rxBytes: 0, |
michael@0 | 173 | _appId: Ci.nsIScriptSecurityManager.NO_APP_ID, |
michael@0 | 174 | _activeNetwork: null, |
michael@0 | 175 | #endif |
michael@0 | 176 | |
michael@0 | 177 | // Public accessors. |
michael@0 | 178 | get readyState() { |
michael@0 | 179 | return this._readyState; |
michael@0 | 180 | }, |
michael@0 | 181 | get binaryType() { |
michael@0 | 182 | return this._binaryType; |
michael@0 | 183 | }, |
michael@0 | 184 | get host() { |
michael@0 | 185 | return this._host; |
michael@0 | 186 | }, |
michael@0 | 187 | get port() { |
michael@0 | 188 | return this._port; |
michael@0 | 189 | }, |
michael@0 | 190 | get ssl() { |
michael@0 | 191 | return this._ssl; |
michael@0 | 192 | }, |
michael@0 | 193 | get bufferedAmount() { |
michael@0 | 194 | if (this._inChild) { |
michael@0 | 195 | return this._bufferedAmount; |
michael@0 | 196 | } |
michael@0 | 197 | return this._multiplexStream.available(); |
michael@0 | 198 | }, |
michael@0 | 199 | get onopen() { |
michael@0 | 200 | return this._onopen; |
michael@0 | 201 | }, |
michael@0 | 202 | set onopen(f) { |
michael@0 | 203 | this._onopen = f; |
michael@0 | 204 | }, |
michael@0 | 205 | get ondrain() { |
michael@0 | 206 | return this._ondrain; |
michael@0 | 207 | }, |
michael@0 | 208 | set ondrain(f) { |
michael@0 | 209 | this._ondrain = f; |
michael@0 | 210 | }, |
michael@0 | 211 | get ondata() { |
michael@0 | 212 | return this._ondata; |
michael@0 | 213 | }, |
michael@0 | 214 | set ondata(f) { |
michael@0 | 215 | this._ondata = f; |
michael@0 | 216 | }, |
michael@0 | 217 | get onerror() { |
michael@0 | 218 | return this._onerror; |
michael@0 | 219 | }, |
michael@0 | 220 | set onerror(f) { |
michael@0 | 221 | this._onerror = f; |
michael@0 | 222 | }, |
michael@0 | 223 | get onclose() { |
michael@0 | 224 | return this._onclose; |
michael@0 | 225 | }, |
michael@0 | 226 | set onclose(f) { |
michael@0 | 227 | this._onclose = f; |
michael@0 | 228 | }, |
michael@0 | 229 | |
michael@0 | 230 | _activateTLS: function() { |
michael@0 | 231 | let securityInfo = this._transport.securityInfo |
michael@0 | 232 | .QueryInterface(Ci.nsISSLSocketControl); |
michael@0 | 233 | securityInfo.StartTLS(); |
michael@0 | 234 | }, |
michael@0 | 235 | |
michael@0 | 236 | // Helper methods. |
michael@0 | 237 | _createTransport: function ts_createTransport(host, port, sslMode) { |
michael@0 | 238 | let options; |
michael@0 | 239 | if (sslMode === 'ssl') { |
michael@0 | 240 | options = ['ssl']; |
michael@0 | 241 | } else { |
michael@0 | 242 | options = ['starttls']; |
michael@0 | 243 | } |
michael@0 | 244 | return Cc["@mozilla.org/network/socket-transport-service;1"] |
michael@0 | 245 | .getService(Ci.nsISocketTransportService) |
michael@0 | 246 | .createTransport(options, 1, host, port, null); |
michael@0 | 247 | }, |
michael@0 | 248 | |
michael@0 | 249 | _sendBufferedAmount: function ts_sendBufferedAmount() { |
michael@0 | 250 | if (this._onUpdateBufferedAmount) { |
michael@0 | 251 | this._onUpdateBufferedAmount(this.bufferedAmount, this._trackingNumber); |
michael@0 | 252 | } |
michael@0 | 253 | }, |
michael@0 | 254 | |
michael@0 | 255 | _ensureCopying: function ts_ensureCopying() { |
michael@0 | 256 | let self = this; |
michael@0 | 257 | if (this._asyncCopierActive) { |
michael@0 | 258 | return; |
michael@0 | 259 | } |
michael@0 | 260 | this._asyncCopierActive = true; |
michael@0 | 261 | this._multiplexStreamCopier.asyncCopy({ |
michael@0 | 262 | onStartRequest: function ts_output_onStartRequest() { |
michael@0 | 263 | }, |
michael@0 | 264 | onStopRequest: function ts_output_onStopRequest(request, context, status) { |
michael@0 | 265 | self._asyncCopierActive = false; |
michael@0 | 266 | self._multiplexStream.removeStream(0); |
michael@0 | 267 | self._sendBufferedAmount(); |
michael@0 | 268 | |
michael@0 | 269 | if (!Components.isSuccessCode(status)) { |
michael@0 | 270 | // Note that we can/will get an error here as well as in the |
michael@0 | 271 | // onStopRequest for inbound data. |
michael@0 | 272 | self._maybeReportErrorAndCloseIfOpen(status); |
michael@0 | 273 | return; |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | if (self._multiplexStream.count) { |
michael@0 | 277 | self._ensureCopying(); |
michael@0 | 278 | } else { |
michael@0 | 279 | // If we are waiting for initiating starttls, we can begin to |
michael@0 | 280 | // activate tls now. |
michael@0 | 281 | if (self._waitingForStartTLS && self._readyState == kOPEN) { |
michael@0 | 282 | self._activateTLS(); |
michael@0 | 283 | self._waitingForStartTLS = false; |
michael@0 | 284 | // If we have pending data, we should send them, or fire |
michael@0 | 285 | // a drain event if we are waiting for it. |
michael@0 | 286 | if (self._pendingDataAfterStartTLS.length > 0) { |
michael@0 | 287 | while (self._pendingDataAfterStartTLS.length) |
michael@0 | 288 | self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift()); |
michael@0 | 289 | self._ensureCopying(); |
michael@0 | 290 | return; |
michael@0 | 291 | } |
michael@0 | 292 | } |
michael@0 | 293 | |
michael@0 | 294 | // If we have a callback to update bufferedAmount, we let child to |
michael@0 | 295 | // decide whether ondrain should be dispatched. |
michael@0 | 296 | if (self._waitingForDrain && !self._onUpdateBufferedAmount) { |
michael@0 | 297 | self._waitingForDrain = false; |
michael@0 | 298 | self.callListener("drain"); |
michael@0 | 299 | } |
michael@0 | 300 | if (self._readyState === kCLOSING) { |
michael@0 | 301 | self._socketOutputStream.close(); |
michael@0 | 302 | self._readyState = kCLOSED; |
michael@0 | 303 | self.callListener("close"); |
michael@0 | 304 | } |
michael@0 | 305 | } |
michael@0 | 306 | } |
michael@0 | 307 | }, null); |
michael@0 | 308 | }, |
michael@0 | 309 | |
michael@0 | 310 | _initStream: function ts_initStream(binaryType) { |
michael@0 | 311 | this._binaryType = binaryType; |
michael@0 | 312 | this._socketInputStream = this._transport.openInputStream(0, 0, 0); |
michael@0 | 313 | this._socketOutputStream = this._transport.openOutputStream( |
michael@0 | 314 | Ci.nsITransport.OPEN_UNBUFFERED, 0, 0); |
michael@0 | 315 | |
michael@0 | 316 | // If the other side is not listening, we will |
michael@0 | 317 | // get an onInputStreamReady callback where available |
michael@0 | 318 | // raises to indicate the connection was refused. |
michael@0 | 319 | this._socketInputStream.asyncWait( |
michael@0 | 320 | this, this._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread); |
michael@0 | 321 | |
michael@0 | 322 | if (this._binaryType === "arraybuffer") { |
michael@0 | 323 | this._inputStreamBinary = new BinaryInputStream(this._socketInputStream); |
michael@0 | 324 | } else { |
michael@0 | 325 | this._inputStreamScriptable = new ScriptableInputStream(this._socketInputStream); |
michael@0 | 326 | } |
michael@0 | 327 | |
michael@0 | 328 | this._multiplexStream = new MultiplexInputStream(); |
michael@0 | 329 | |
michael@0 | 330 | this._multiplexStreamCopier = new AsyncStreamCopier( |
michael@0 | 331 | this._multiplexStream, |
michael@0 | 332 | this._socketOutputStream, |
michael@0 | 333 | // (nsSocketTransport uses gSocketTransportService) |
michael@0 | 334 | Cc["@mozilla.org/network/socket-transport-service;1"] |
michael@0 | 335 | .getService(Ci.nsIEventTarget), |
michael@0 | 336 | /* source buffered */ true, /* sink buffered */ false, |
michael@0 | 337 | BUFFER_SIZE, /* close source*/ false, /* close sink */ false); |
michael@0 | 338 | }, |
michael@0 | 339 | |
michael@0 | 340 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 341 | // Helper method for collecting network statistics. |
michael@0 | 342 | // Note this method is Gonk-specific. |
michael@0 | 343 | _saveNetworkStats: function ts_saveNetworkStats(enforce) { |
michael@0 | 344 | if (this._txBytes <= 0 && this._rxBytes <= 0) { |
michael@0 | 345 | // There is no traffic at all. No need to save statistics. |
michael@0 | 346 | return; |
michael@0 | 347 | } |
michael@0 | 348 | |
michael@0 | 349 | // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy |
michael@0 | 350 | // only when the total amount exceeds the predefined threshold value. |
michael@0 | 351 | // The purpose is to avoid too much overhead for collecting statistics. |
michael@0 | 352 | let totalBytes = this._txBytes + this._rxBytes; |
michael@0 | 353 | if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) { |
michael@0 | 354 | return; |
michael@0 | 355 | } |
michael@0 | 356 | |
michael@0 | 357 | let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"] |
michael@0 | 358 | .getService(Ci.nsINetworkStatsServiceProxy); |
michael@0 | 359 | if (!nssProxy) { |
michael@0 | 360 | LOG("Error: Ci.nsINetworkStatsServiceProxy service is not available."); |
michael@0 | 361 | return; |
michael@0 | 362 | } |
michael@0 | 363 | nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(), |
michael@0 | 364 | this._rxBytes, this._txBytes, false); |
michael@0 | 365 | |
michael@0 | 366 | // Reset the counters once the statistics is saved to NetworkStatsServiceProxy. |
michael@0 | 367 | this._txBytes = this._rxBytes = 0; |
michael@0 | 368 | }, |
michael@0 | 369 | // End of helper method for network statistics. |
michael@0 | 370 | #endif |
michael@0 | 371 | |
michael@0 | 372 | callListener: function ts_callListener(type, data) { |
michael@0 | 373 | if (!this["on" + type]) |
michael@0 | 374 | return; |
michael@0 | 375 | |
michael@0 | 376 | this["on" + type].call(null, new TCPSocketEvent(type, this, data || "")); |
michael@0 | 377 | }, |
michael@0 | 378 | |
michael@0 | 379 | /* nsITCPSocketInternal methods */ |
michael@0 | 380 | callListenerError: function ts_callListenerError(type, name) { |
michael@0 | 381 | // XXX we're not really using TCPError at this time, so there's only a name |
michael@0 | 382 | // attribute to pass. |
michael@0 | 383 | this.callListener(type, createTCPError(this.useWin, name)); |
michael@0 | 384 | }, |
michael@0 | 385 | |
michael@0 | 386 | callListenerData: function ts_callListenerString(type, data) { |
michael@0 | 387 | this.callListener(type, data); |
michael@0 | 388 | }, |
michael@0 | 389 | |
michael@0 | 390 | callListenerArrayBuffer: function ts_callListenerArrayBuffer(type, data) { |
michael@0 | 391 | this.callListener(type, data); |
michael@0 | 392 | }, |
michael@0 | 393 | |
michael@0 | 394 | callListenerVoid: function ts_callListenerVoid(type) { |
michael@0 | 395 | this.callListener(type); |
michael@0 | 396 | }, |
michael@0 | 397 | |
michael@0 | 398 | /** |
michael@0 | 399 | * This method is expected to be called by TCPSocketChild to update child's |
michael@0 | 400 | * readyState. |
michael@0 | 401 | */ |
michael@0 | 402 | updateReadyState: function ts_updateReadyState(readyState) { |
michael@0 | 403 | if (!this._inChild) { |
michael@0 | 404 | LOG("Calling updateReadyState in parent, which should only be called " + |
michael@0 | 405 | "in child"); |
michael@0 | 406 | return; |
michael@0 | 407 | } |
michael@0 | 408 | this._readyState = readyState; |
michael@0 | 409 | }, |
michael@0 | 410 | |
michael@0 | 411 | updateBufferedAmount: function ts_updateBufferedAmount(bufferedAmount, trackingNumber) { |
michael@0 | 412 | if (trackingNumber != this._trackingNumber) { |
michael@0 | 413 | LOG("updateBufferedAmount is called but trackingNumber is not matched " + |
michael@0 | 414 | "parent's trackingNumber: " + trackingNumber + ", child's trackingNumber: " + |
michael@0 | 415 | this._trackingNumber); |
michael@0 | 416 | return; |
michael@0 | 417 | } |
michael@0 | 418 | this._bufferedAmount = bufferedAmount; |
michael@0 | 419 | if (bufferedAmount == 0) { |
michael@0 | 420 | if (this._waitingForDrain) { |
michael@0 | 421 | this._waitingForDrain = false; |
michael@0 | 422 | this.callListener("drain"); |
michael@0 | 423 | } |
michael@0 | 424 | } else { |
michael@0 | 425 | LOG("bufferedAmount is updated but haven't reaches zero. bufferedAmount: " + |
michael@0 | 426 | bufferedAmount); |
michael@0 | 427 | } |
michael@0 | 428 | }, |
michael@0 | 429 | |
michael@0 | 430 | createAcceptedParent: function ts_createAcceptedParent(transport, binaryType) { |
michael@0 | 431 | let that = new TCPSocket(); |
michael@0 | 432 | that._transport = transport; |
michael@0 | 433 | that._initStream(binaryType); |
michael@0 | 434 | |
michael@0 | 435 | // ReadyState is kOpen since accepted transport stream has already been connected |
michael@0 | 436 | that._readyState = kOPEN; |
michael@0 | 437 | that._inputStreamPump = new InputStreamPump(that._socketInputStream, -1, -1, 0, 0, false); |
michael@0 | 438 | that._inputStreamPump.asyncRead(that, null); |
michael@0 | 439 | |
michael@0 | 440 | return that; |
michael@0 | 441 | }, |
michael@0 | 442 | |
michael@0 | 443 | createAcceptedChild: function ts_createAcceptedChild(socketChild, binaryType, windowObject) { |
michael@0 | 444 | let that = new TCPSocket(); |
michael@0 | 445 | |
michael@0 | 446 | that._binaryType = binaryType; |
michael@0 | 447 | that._inChild = true; |
michael@0 | 448 | that._readyState = kOPEN; |
michael@0 | 449 | socketChild.setSocketAndWindow(that, windowObject); |
michael@0 | 450 | that._socketBridge = socketChild; |
michael@0 | 451 | |
michael@0 | 452 | return that; |
michael@0 | 453 | }, |
michael@0 | 454 | |
michael@0 | 455 | setAppId: function ts_setAppId(appId) { |
michael@0 | 456 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 457 | this._appId = appId; |
michael@0 | 458 | #else |
michael@0 | 459 | // Do nothing because _appId only exists on Gonk-specific platform. |
michael@0 | 460 | #endif |
michael@0 | 461 | }, |
michael@0 | 462 | |
michael@0 | 463 | setOnUpdateBufferedAmountHandler: function(aFunction) { |
michael@0 | 464 | if (typeof(aFunction) == 'function') { |
michael@0 | 465 | this._onUpdateBufferedAmount = aFunction; |
michael@0 | 466 | } else { |
michael@0 | 467 | throw new Error("only function can be passed to " + |
michael@0 | 468 | "setOnUpdateBufferedAmountHandler"); |
michael@0 | 469 | } |
michael@0 | 470 | }, |
michael@0 | 471 | |
michael@0 | 472 | /** |
michael@0 | 473 | * Handle the requst of sending data and update trackingNumber from |
michael@0 | 474 | * child. |
michael@0 | 475 | * This function is expected to be called by TCPSocketChild. |
michael@0 | 476 | */ |
michael@0 | 477 | onRecvSendFromChild: function(data, byteOffset, byteLength, trackingNumber) { |
michael@0 | 478 | this._trackingNumber = trackingNumber; |
michael@0 | 479 | this.send(data, byteOffset, byteLength); |
michael@0 | 480 | }, |
michael@0 | 481 | |
michael@0 | 482 | /* end nsITCPSocketInternal methods */ |
michael@0 | 483 | |
michael@0 | 484 | initWindowless: function ts_initWindowless() { |
michael@0 | 485 | try { |
michael@0 | 486 | return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled"); |
michael@0 | 487 | } catch (e) { |
michael@0 | 488 | // no pref means return false |
michael@0 | 489 | return false; |
michael@0 | 490 | } |
michael@0 | 491 | }, |
michael@0 | 492 | |
michael@0 | 493 | init: function ts_init(aWindow) { |
michael@0 | 494 | if (!this.initWindowless()) |
michael@0 | 495 | return null; |
michael@0 | 496 | |
michael@0 | 497 | let principal = aWindow.document.nodePrincipal; |
michael@0 | 498 | let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] |
michael@0 | 499 | .getService(Ci.nsIScriptSecurityManager); |
michael@0 | 500 | |
michael@0 | 501 | let perm = principal == secMan.getSystemPrincipal() |
michael@0 | 502 | ? Ci.nsIPermissionManager.ALLOW_ACTION |
michael@0 | 503 | : Services.perms.testExactPermissionFromPrincipal(principal, "tcp-socket"); |
michael@0 | 504 | |
michael@0 | 505 | this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION; |
michael@0 | 506 | |
michael@0 | 507 | let util = aWindow.QueryInterface( |
michael@0 | 508 | Ci.nsIInterfaceRequestor |
michael@0 | 509 | ).getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 510 | |
michael@0 | 511 | this.useWin = XPCNativeWrapper.unwrap(aWindow); |
michael@0 | 512 | this.innerWindowID = util.currentInnerWindowID; |
michael@0 | 513 | LOG("window init: " + this.innerWindowID); |
michael@0 | 514 | }, |
michael@0 | 515 | |
michael@0 | 516 | observe: function(aSubject, aTopic, aData) { |
michael@0 | 517 | if (aTopic == "inner-window-destroyed") { |
michael@0 | 518 | let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; |
michael@0 | 519 | if (wId == this.innerWindowID) { |
michael@0 | 520 | LOG("inner-window-destroyed: " + this.innerWindowID); |
michael@0 | 521 | |
michael@0 | 522 | // This window is now dead, so we want to clear the callbacks |
michael@0 | 523 | // so that we don't get a "can't access dead object" when the |
michael@0 | 524 | // underlying stream goes to tell us that we are closed |
michael@0 | 525 | this.onopen = null; |
michael@0 | 526 | this.ondrain = null; |
michael@0 | 527 | this.ondata = null; |
michael@0 | 528 | this.onerror = null; |
michael@0 | 529 | this.onclose = null; |
michael@0 | 530 | |
michael@0 | 531 | this.useWin = null; |
michael@0 | 532 | |
michael@0 | 533 | // Clean up our socket |
michael@0 | 534 | this.close(); |
michael@0 | 535 | } |
michael@0 | 536 | } |
michael@0 | 537 | }, |
michael@0 | 538 | |
michael@0 | 539 | // nsIDOMTCPSocket |
michael@0 | 540 | open: function ts_open(host, port, options) { |
michael@0 | 541 | this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) |
michael@0 | 542 | .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; |
michael@0 | 543 | LOG("content process: " + (this._inChild ? "true" : "false")); |
michael@0 | 544 | |
michael@0 | 545 | // in the testing case, init won't be called and |
michael@0 | 546 | // hasPrivileges will be null. We want to proceed to test. |
michael@0 | 547 | if (this._hasPrivileges !== true && this._hasPrivileges !== null) { |
michael@0 | 548 | throw new Error("TCPSocket does not have permission in this context.\n"); |
michael@0 | 549 | } |
michael@0 | 550 | let that = new TCPSocket(); |
michael@0 | 551 | |
michael@0 | 552 | that.useWin = this.useWin; |
michael@0 | 553 | that.innerWindowID = this.innerWindowID; |
michael@0 | 554 | that._inChild = this._inChild; |
michael@0 | 555 | |
michael@0 | 556 | LOG("window init: " + that.innerWindowID); |
michael@0 | 557 | Services.obs.addObserver(that, "inner-window-destroyed", true); |
michael@0 | 558 | |
michael@0 | 559 | LOG("startup called"); |
michael@0 | 560 | LOG("Host info: " + host + ":" + port); |
michael@0 | 561 | |
michael@0 | 562 | that._readyState = kCONNECTING; |
michael@0 | 563 | that._host = host; |
michael@0 | 564 | that._port = port; |
michael@0 | 565 | if (options !== undefined) { |
michael@0 | 566 | if (options.useSecureTransport) { |
michael@0 | 567 | that._ssl = 'ssl'; |
michael@0 | 568 | } else { |
michael@0 | 569 | that._ssl = false; |
michael@0 | 570 | } |
michael@0 | 571 | that._binaryType = options.binaryType || that._binaryType; |
michael@0 | 572 | } |
michael@0 | 573 | |
michael@0 | 574 | LOG("SSL: " + that.ssl); |
michael@0 | 575 | |
michael@0 | 576 | if (this._inChild) { |
michael@0 | 577 | that._socketBridge = Cc["@mozilla.org/tcp-socket-child;1"] |
michael@0 | 578 | .createInstance(Ci.nsITCPSocketChild); |
michael@0 | 579 | that._socketBridge.sendOpen(that, host, port, !!that._ssl, |
michael@0 | 580 | that._binaryType, this.useWin, this.useWin || this); |
michael@0 | 581 | return that; |
michael@0 | 582 | } |
michael@0 | 583 | |
michael@0 | 584 | let transport = that._transport = this._createTransport(host, port, that._ssl); |
michael@0 | 585 | transport.setEventSink(that, Services.tm.currentThread); |
michael@0 | 586 | that._initStream(that._binaryType); |
michael@0 | 587 | |
michael@0 | 588 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 589 | // Set _activeNetwork, which is only required for network statistics. |
michael@0 | 590 | // Note that nsINetworkManager, as well as nsINetworkStatsServiceProxy, is |
michael@0 | 591 | // Gonk-specific. |
michael@0 | 592 | let networkManager = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); |
michael@0 | 593 | if (networkManager) { |
michael@0 | 594 | that._activeNetwork = networkManager.active; |
michael@0 | 595 | } |
michael@0 | 596 | #endif |
michael@0 | 597 | |
michael@0 | 598 | return that; |
michael@0 | 599 | }, |
michael@0 | 600 | |
michael@0 | 601 | upgradeToSecure: function ts_upgradeToSecure() { |
michael@0 | 602 | if (this._readyState !== kOPEN) { |
michael@0 | 603 | throw new Error("Socket not open."); |
michael@0 | 604 | } |
michael@0 | 605 | if (this._ssl == 'ssl') { |
michael@0 | 606 | // Already SSL |
michael@0 | 607 | return; |
michael@0 | 608 | } |
michael@0 | 609 | |
michael@0 | 610 | this._ssl = 'ssl'; |
michael@0 | 611 | |
michael@0 | 612 | if (this._inChild) { |
michael@0 | 613 | this._socketBridge.sendStartTLS(); |
michael@0 | 614 | return; |
michael@0 | 615 | } |
michael@0 | 616 | |
michael@0 | 617 | if (this._multiplexStream.count == 0) { |
michael@0 | 618 | this._activateTLS(); |
michael@0 | 619 | } else { |
michael@0 | 620 | this._waitingForStartTLS = true; |
michael@0 | 621 | } |
michael@0 | 622 | }, |
michael@0 | 623 | |
michael@0 | 624 | listen: function ts_listen(localPort, options, backlog) { |
michael@0 | 625 | // in the testing case, init won't be called and |
michael@0 | 626 | // hasPrivileges will be null. We want to proceed to test. |
michael@0 | 627 | if (this._hasPrivileges !== true && this._hasPrivileges !== null) { |
michael@0 | 628 | throw new Error("TCPSocket does not have permission in this context.\n"); |
michael@0 | 629 | } |
michael@0 | 630 | |
michael@0 | 631 | let that = new TCPServerSocket(this.useWin || this); |
michael@0 | 632 | |
michael@0 | 633 | options = options || { binaryType : this.binaryType }; |
michael@0 | 634 | backlog = backlog || -1; |
michael@0 | 635 | that.listen(localPort, options, backlog); |
michael@0 | 636 | return that; |
michael@0 | 637 | }, |
michael@0 | 638 | |
michael@0 | 639 | close: function ts_close() { |
michael@0 | 640 | if (this._readyState === kCLOSED || this._readyState === kCLOSING) |
michael@0 | 641 | return; |
michael@0 | 642 | |
michael@0 | 643 | LOG("close called"); |
michael@0 | 644 | this._readyState = kCLOSING; |
michael@0 | 645 | |
michael@0 | 646 | if (this._inChild) { |
michael@0 | 647 | this._socketBridge.sendClose(); |
michael@0 | 648 | return; |
michael@0 | 649 | } |
michael@0 | 650 | |
michael@0 | 651 | if (!this._multiplexStream.count) { |
michael@0 | 652 | this._socketOutputStream.close(); |
michael@0 | 653 | } |
michael@0 | 654 | this._socketInputStream.close(); |
michael@0 | 655 | }, |
michael@0 | 656 | |
michael@0 | 657 | send: function ts_send(data, byteOffset, byteLength) { |
michael@0 | 658 | if (this._readyState !== kOPEN) { |
michael@0 | 659 | throw new Error("Socket not open."); |
michael@0 | 660 | } |
michael@0 | 661 | |
michael@0 | 662 | if (this._binaryType === "arraybuffer") { |
michael@0 | 663 | byteLength = byteLength || data.byteLength; |
michael@0 | 664 | } |
michael@0 | 665 | |
michael@0 | 666 | if (this._inChild) { |
michael@0 | 667 | this._socketBridge.sendSend(data, byteOffset, byteLength, ++this._trackingNumber); |
michael@0 | 668 | } |
michael@0 | 669 | |
michael@0 | 670 | let length = this._binaryType === "arraybuffer" ? byteLength : data.length; |
michael@0 | 671 | let newBufferedAmount = this.bufferedAmount + length; |
michael@0 | 672 | let bufferFull = newBufferedAmount >= BUFFER_SIZE; |
michael@0 | 673 | |
michael@0 | 674 | if (bufferFull) { |
michael@0 | 675 | // If we buffered more than some arbitrary amount of data, |
michael@0 | 676 | // (65535 right now) we should tell the caller so they can |
michael@0 | 677 | // wait until ondrain is called if they so desire. Once all the |
michael@0 | 678 | // buffered data has been written to the socket, ondrain is |
michael@0 | 679 | // called. |
michael@0 | 680 | this._waitingForDrain = true; |
michael@0 | 681 | } |
michael@0 | 682 | |
michael@0 | 683 | if (this._inChild) { |
michael@0 | 684 | // In child, we just add buffer length to our bufferedAmount and let |
michael@0 | 685 | // parent to update our bufferedAmount when data have been sent. |
michael@0 | 686 | this._bufferedAmount = newBufferedAmount; |
michael@0 | 687 | return !bufferFull; |
michael@0 | 688 | } |
michael@0 | 689 | |
michael@0 | 690 | let new_stream; |
michael@0 | 691 | if (this._binaryType === "arraybuffer") { |
michael@0 | 692 | new_stream = new ArrayBufferInputStream(); |
michael@0 | 693 | new_stream.setData(data, byteOffset, byteLength); |
michael@0 | 694 | } else { |
michael@0 | 695 | new_stream = new StringInputStream(); |
michael@0 | 696 | new_stream.setData(data, length); |
michael@0 | 697 | } |
michael@0 | 698 | |
michael@0 | 699 | if (this._waitingForStartTLS) { |
michael@0 | 700 | // When we are waiting for starttls, new_stream is added to pendingData |
michael@0 | 701 | // and will be appended to multiplexStream after tls had been set up. |
michael@0 | 702 | this._pendingDataAfterStartTLS.push(new_stream); |
michael@0 | 703 | } else { |
michael@0 | 704 | this._multiplexStream.appendStream(new_stream); |
michael@0 | 705 | } |
michael@0 | 706 | |
michael@0 | 707 | this._ensureCopying(); |
michael@0 | 708 | |
michael@0 | 709 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 710 | // Collect transmitted amount for network statistics. |
michael@0 | 711 | this._txBytes += length; |
michael@0 | 712 | this._saveNetworkStats(false); |
michael@0 | 713 | #endif |
michael@0 | 714 | |
michael@0 | 715 | return !bufferFull; |
michael@0 | 716 | }, |
michael@0 | 717 | |
michael@0 | 718 | suspend: function ts_suspend() { |
michael@0 | 719 | if (this._inChild) { |
michael@0 | 720 | this._socketBridge.sendSuspend(); |
michael@0 | 721 | return; |
michael@0 | 722 | } |
michael@0 | 723 | |
michael@0 | 724 | if (this._inputStreamPump) { |
michael@0 | 725 | this._inputStreamPump.suspend(); |
michael@0 | 726 | } else { |
michael@0 | 727 | ++this._suspendCount; |
michael@0 | 728 | } |
michael@0 | 729 | }, |
michael@0 | 730 | |
michael@0 | 731 | resume: function ts_resume() { |
michael@0 | 732 | if (this._inChild) { |
michael@0 | 733 | this._socketBridge.sendResume(); |
michael@0 | 734 | return; |
michael@0 | 735 | } |
michael@0 | 736 | |
michael@0 | 737 | if (this._inputStreamPump) { |
michael@0 | 738 | this._inputStreamPump.resume(); |
michael@0 | 739 | } else if (this._suspendCount < 1) { |
michael@0 | 740 | throw new Error(kRESUME_ERROR); |
michael@0 | 741 | } else { |
michael@0 | 742 | --this._suspendCount; |
michael@0 | 743 | } |
michael@0 | 744 | }, |
michael@0 | 745 | |
michael@0 | 746 | _maybeReportErrorAndCloseIfOpen: function(status) { |
michael@0 | 747 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 748 | // Save network statistics once the connection is closed. |
michael@0 | 749 | // For now this function is Gonk-specific. |
michael@0 | 750 | this._saveNetworkStats(true); |
michael@0 | 751 | #endif |
michael@0 | 752 | |
michael@0 | 753 | // If we're closed, we've already reported the error or just don't need to |
michael@0 | 754 | // report the error. |
michael@0 | 755 | if (this._readyState === kCLOSED) |
michael@0 | 756 | return; |
michael@0 | 757 | this._readyState = kCLOSED; |
michael@0 | 758 | |
michael@0 | 759 | if (!Components.isSuccessCode(status)) { |
michael@0 | 760 | // Convert the status code to an appropriate error message. Raw constants |
michael@0 | 761 | // are used inline in all cases for consistency. Some error codes are |
michael@0 | 762 | // available in Components.results, some aren't. Network error codes are |
michael@0 | 763 | // effectively stable, NSS error codes are officially not, but we have no |
michael@0 | 764 | // symbolic way to dynamically resolve them anyways (other than an ability |
michael@0 | 765 | // to determine the error class.) |
michael@0 | 766 | let errName, errType; |
michael@0 | 767 | // security module? (and this is an error) |
michael@0 | 768 | if ((status & 0xff0000) === 0x5a0000) { |
michael@0 | 769 | const nsINSSErrorsService = Ci.nsINSSErrorsService; |
michael@0 | 770 | let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1'] |
michael@0 | 771 | .getService(nsINSSErrorsService); |
michael@0 | 772 | let errorClass; |
michael@0 | 773 | // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is |
michael@0 | 774 | // somehow not in the set of covered errors. |
michael@0 | 775 | try { |
michael@0 | 776 | errorClass = nssErrorsService.getErrorClass(status); |
michael@0 | 777 | } |
michael@0 | 778 | catch (ex) { |
michael@0 | 779 | errorClass = 'SecurityProtocol'; |
michael@0 | 780 | } |
michael@0 | 781 | switch (errorClass) { |
michael@0 | 782 | case nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL: |
michael@0 | 783 | errType = 'SecurityProtocol'; |
michael@0 | 784 | break; |
michael@0 | 785 | case nsINSSErrorsService.ERROR_CLASS_BAD_CERT: |
michael@0 | 786 | errType = 'SecurityCertificate'; |
michael@0 | 787 | break; |
michael@0 | 788 | // no default is required; the platform impl automatically defaults to |
michael@0 | 789 | // ERROR_CLASS_SSL_PROTOCOL. |
michael@0 | 790 | } |
michael@0 | 791 | |
michael@0 | 792 | // NSS_SEC errors (happen below the base value because of negative vals) |
michael@0 | 793 | if ((status & 0xffff) < |
michael@0 | 794 | Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) { |
michael@0 | 795 | // The bases are actually negative, so in our positive numeric space, we |
michael@0 | 796 | // need to subtract the base off our value. |
michael@0 | 797 | let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) - |
michael@0 | 798 | (status & 0xffff); |
michael@0 | 799 | switch (nssErr) { |
michael@0 | 800 | case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11) |
michael@0 | 801 | errName = 'SecurityExpiredCertificateError'; |
michael@0 | 802 | break; |
michael@0 | 803 | case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12) |
michael@0 | 804 | errName = 'SecurityRevokedCertificateError'; |
michael@0 | 805 | break; |
michael@0 | 806 | // per bsmith, we will be unable to tell these errors apart very soon, |
michael@0 | 807 | // so it makes sense to just folder them all together already. |
michael@0 | 808 | case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13) |
michael@0 | 809 | case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20) |
michael@0 | 810 | case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21) |
michael@0 | 811 | case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36) |
michael@0 | 812 | errName = 'SecurityUntrustedCertificateIssuerError'; |
michael@0 | 813 | break; |
michael@0 | 814 | case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90) |
michael@0 | 815 | errName = 'SecurityInadequateKeyUsageError'; |
michael@0 | 816 | break; |
michael@0 | 817 | case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176) |
michael@0 | 818 | errName = 'SecurityCertificateSignatureAlgorithmDisabledError'; |
michael@0 | 819 | break; |
michael@0 | 820 | default: |
michael@0 | 821 | errName = 'SecurityError'; |
michael@0 | 822 | break; |
michael@0 | 823 | } |
michael@0 | 824 | } |
michael@0 | 825 | // NSS_SSL errors |
michael@0 | 826 | else { |
michael@0 | 827 | let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) - |
michael@0 | 828 | (status & 0xffff); |
michael@0 | 829 | switch (sslErr) { |
michael@0 | 830 | case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3) |
michael@0 | 831 | errName = 'SecurityNoCertificateError'; |
michael@0 | 832 | break; |
michael@0 | 833 | case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4) |
michael@0 | 834 | errName = 'SecurityBadCertificateError'; |
michael@0 | 835 | break; |
michael@0 | 836 | case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8) |
michael@0 | 837 | errName = 'SecurityUnsupportedCertificateTypeError'; |
michael@0 | 838 | break; |
michael@0 | 839 | case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9) |
michael@0 | 840 | errName = 'SecurityUnsupportedTLSVersionError'; |
michael@0 | 841 | break; |
michael@0 | 842 | case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12) |
michael@0 | 843 | errName = 'SecurityCertificateDomainMismatchError'; |
michael@0 | 844 | break; |
michael@0 | 845 | default: |
michael@0 | 846 | errName = 'SecurityError'; |
michael@0 | 847 | break; |
michael@0 | 848 | } |
michael@0 | 849 | } |
michael@0 | 850 | } |
michael@0 | 851 | // must be network |
michael@0 | 852 | else { |
michael@0 | 853 | errType = 'Network'; |
michael@0 | 854 | switch (status) { |
michael@0 | 855 | // connect to host:port failed |
michael@0 | 856 | case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13) |
michael@0 | 857 | errName = 'ConnectionRefusedError'; |
michael@0 | 858 | break; |
michael@0 | 859 | // network timeout error |
michael@0 | 860 | case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14) |
michael@0 | 861 | errName = 'NetworkTimeoutError'; |
michael@0 | 862 | break; |
michael@0 | 863 | // hostname lookup failed |
michael@0 | 864 | case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30) |
michael@0 | 865 | errName = 'DomainNotFoundError'; |
michael@0 | 866 | break; |
michael@0 | 867 | case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71) |
michael@0 | 868 | errName = 'NetworkInterruptError'; |
michael@0 | 869 | break; |
michael@0 | 870 | default: |
michael@0 | 871 | errName = 'NetworkError'; |
michael@0 | 872 | break; |
michael@0 | 873 | } |
michael@0 | 874 | } |
michael@0 | 875 | let err = createTCPError(this.useWin, errName, errType); |
michael@0 | 876 | this.callListener("error", err); |
michael@0 | 877 | } |
michael@0 | 878 | this.callListener("close"); |
michael@0 | 879 | }, |
michael@0 | 880 | |
michael@0 | 881 | // nsITransportEventSink (Triggered by transport.setEventSink) |
michael@0 | 882 | onTransportStatus: function ts_onTransportStatus( |
michael@0 | 883 | transport, status, progress, max) { |
michael@0 | 884 | if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) { |
michael@0 | 885 | this._readyState = kOPEN; |
michael@0 | 886 | this.callListener("open"); |
michael@0 | 887 | |
michael@0 | 888 | this._inputStreamPump = new InputStreamPump( |
michael@0 | 889 | this._socketInputStream, -1, -1, 0, 0, false |
michael@0 | 890 | ); |
michael@0 | 891 | |
michael@0 | 892 | while (this._suspendCount--) { |
michael@0 | 893 | this._inputStreamPump.suspend(); |
michael@0 | 894 | } |
michael@0 | 895 | |
michael@0 | 896 | this._inputStreamPump.asyncRead(this, null); |
michael@0 | 897 | } |
michael@0 | 898 | }, |
michael@0 | 899 | |
michael@0 | 900 | // nsIAsyncInputStream (Triggered by _socketInputStream.asyncWait) |
michael@0 | 901 | // Only used for detecting connection refused |
michael@0 | 902 | onInputStreamReady: function ts_onInputStreamReady(input) { |
michael@0 | 903 | try { |
michael@0 | 904 | input.available(); |
michael@0 | 905 | } catch (e) { |
michael@0 | 906 | // NS_ERROR_CONNECTION_REFUSED |
michael@0 | 907 | this._maybeReportErrorAndCloseIfOpen(0x804B000C); |
michael@0 | 908 | } |
michael@0 | 909 | }, |
michael@0 | 910 | |
michael@0 | 911 | // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead) |
michael@0 | 912 | onStartRequest: function ts_onStartRequest(request, context) { |
michael@0 | 913 | }, |
michael@0 | 914 | |
michael@0 | 915 | // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead) |
michael@0 | 916 | onStopRequest: function ts_onStopRequest(request, context, status) { |
michael@0 | 917 | let buffered_output = this._multiplexStream.count !== 0; |
michael@0 | 918 | |
michael@0 | 919 | this._inputStreamPump = null; |
michael@0 | 920 | |
michael@0 | 921 | let statusIsError = !Components.isSuccessCode(status); |
michael@0 | 922 | |
michael@0 | 923 | if (buffered_output && !statusIsError) { |
michael@0 | 924 | // If we have some buffered output still, and status is not an |
michael@0 | 925 | // error, the other side has done a half-close, but we don't |
michael@0 | 926 | // want to be in the close state until we are done sending |
michael@0 | 927 | // everything that was buffered. We also don't want to call onclose |
michael@0 | 928 | // yet. |
michael@0 | 929 | return; |
michael@0 | 930 | } |
michael@0 | 931 | |
michael@0 | 932 | // We call this even if there is no error. |
michael@0 | 933 | this._maybeReportErrorAndCloseIfOpen(status); |
michael@0 | 934 | }, |
michael@0 | 935 | |
michael@0 | 936 | // nsIStreamListener (Triggered by _inputStreamPump.asyncRead) |
michael@0 | 937 | onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) { |
michael@0 | 938 | if (this._binaryType === "arraybuffer") { |
michael@0 | 939 | let buffer = new (this.useWin ? this.useWin.ArrayBuffer : ArrayBuffer)(count); |
michael@0 | 940 | this._inputStreamBinary.readArrayBuffer(count, buffer); |
michael@0 | 941 | this.callListener("data", buffer); |
michael@0 | 942 | } else { |
michael@0 | 943 | this.callListener("data", this._inputStreamScriptable.read(count)); |
michael@0 | 944 | } |
michael@0 | 945 | |
michael@0 | 946 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 947 | // Collect received amount for network statistics. |
michael@0 | 948 | this._rxBytes += count; |
michael@0 | 949 | this._saveNetworkStats(false); |
michael@0 | 950 | #endif |
michael@0 | 951 | }, |
michael@0 | 952 | |
michael@0 | 953 | classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"), |
michael@0 | 954 | |
michael@0 | 955 | classInfo: XPCOMUtils.generateCI({ |
michael@0 | 956 | classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"), |
michael@0 | 957 | contractID: "@mozilla.org/tcp-socket;1", |
michael@0 | 958 | classDescription: "Client TCP Socket", |
michael@0 | 959 | interfaces: [ |
michael@0 | 960 | Ci.nsIDOMTCPSocket, |
michael@0 | 961 | ], |
michael@0 | 962 | flags: Ci.nsIClassInfo.DOM_OBJECT, |
michael@0 | 963 | }), |
michael@0 | 964 | |
michael@0 | 965 | QueryInterface: XPCOMUtils.generateQI([ |
michael@0 | 966 | Ci.nsIDOMTCPSocket, |
michael@0 | 967 | Ci.nsITCPSocketInternal, |
michael@0 | 968 | Ci.nsIDOMGlobalPropertyInitializer, |
michael@0 | 969 | Ci.nsIObserver, |
michael@0 | 970 | Ci.nsISupportsWeakReference |
michael@0 | 971 | ]) |
michael@0 | 972 | } |
michael@0 | 973 | |
michael@0 | 974 | this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]); |