2014-06-25 05:12:07 +00:00
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2013-08-21 06:56:40 +00:00
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/ * T h i s S o u r c e C o d e F o r m i s s u b j e c t t o t h e t e r m s o f t h e M o z i l l a P u b l i c
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/. */
"use strict" ;
2015-02-12 11:44:00 +00:00
const { Cc , Ci , Cu , Cr } = require ( "chrome" ) ;
2013-08-21 06:56:40 +00:00
const { setTimeout , clearTimeout } = require ( 'sdk/timers' ) ;
2014-02-26 04:22:05 +00:00
const EventEmitter = require ( "devtools/toolkit/event-emitter" ) ;
2014-12-11 02:55:52 +00:00
const DevToolsUtils = require ( "devtools/toolkit/DevToolsUtils" ) ;
2013-08-21 06:56:40 +00:00
Cu . import ( "resource://gre/modules/Services.jsm" ) ;
Cu . import ( "resource://gre/modules/devtools/dbg-client.jsm" ) ;
Cu . import ( "resource://gre/modules/devtools/dbg-server.jsm" ) ;
2014-12-11 02:55:52 +00:00
DevToolsUtils . defineLazyModuleGetter ( this , "Task" ,
"resource://gre/modules/Task.jsm" ) ;
2013-08-21 06:56:40 +00:00
2015-03-19 17:58:19 +00:00
const REMOTE _TIMEOUT = "devtools.debugger.remote-timeout" ;
2013-08-21 06:56:40 +00:00
/ * *
* Connection Manager .
*
* To use this module :
* const { ConnectionManager } = require ( "devtools/client/connection-manager" ) ;
*
* # ConnectionManager
*
* Methods :
2014-09-05 16:38:33 +00:00
* . Connection createConnection ( host , port )
* . void destroyConnection ( connection )
* . Number getFreeTCPPort ( )
2013-08-21 06:56:40 +00:00
*
* Properties :
2014-09-05 16:38:33 +00:00
* . Array connections
2013-08-21 06:56:40 +00:00
*
* # Connection
*
* A connection is a wrapper around a debugger client . It has a simple
* API to instantiate a connection to a debugger server . Once disconnected ,
* no need to re - create a Connection object . Calling ` connect() ` again
* will re - create a debugger client .
*
* Methods :
2014-09-05 16:38:33 +00:00
* . connect ( ) Connect to host : port . Expect a "connecting" event .
* If no host is not specified , a local pipe is used
* . connect ( transport ) Connect via transport . Expect a "connecting" event .
* . disconnect ( ) Disconnect if connected . Expect a "disconnecting" event
2013-08-21 06:56:40 +00:00
*
* Properties :
2014-09-05 16:38:33 +00:00
* . host IP address or hostname
* . port Port
* . logs Current logs . "newlog" event notifies new available logs
* . store Reference to a local data store ( see below )
2014-12-11 02:55:52 +00:00
* . keepConnecting Should the connection keep trying to connect ?
2015-03-19 17:58:19 +00:00
* . timeoutDelay When should we give up ( in ms ) ?
* 0 means wait forever .
2014-12-11 02:55:52 +00:00
* . encryption Should the connection be encrypted ?
2015-01-26 18:47:13 +00:00
* . authentication What authentication scheme should be used ?
* . authenticator The | Authenticator | instance used . Overriding
* properties of this instance may be useful to
* customize authentication UX for a specific use case .
* . advertisement The server ' s advertisement if found by discovery
2014-09-05 16:38:33 +00:00
* . status Connection status :
* Connection . Status . CONNECTED
* Connection . Status . DISCONNECTED
* Connection . Status . CONNECTING
* Connection . Status . DISCONNECTING
* Connection . Status . DESTROYED
2013-08-21 06:56:40 +00:00
*
* Events ( as in event - emitter . js ) :
2014-09-05 16:38:33 +00:00
* . Connection . Events . CONNECTING Trying to connect to host : port
* . Connection . Events . CONNECTED Connection is successful
* . Connection . Events . DISCONNECTING Trying to disconnect from server
* . Connection . Events . DISCONNECTED Disconnected ( at client request , or because of a timeout or connection error )
* . Connection . Events . STATUS _CHANGED The connection status ( connection . status ) has changed
* . Connection . Events . TIMEOUT Connection timeout
* . Connection . Events . HOST _CHANGED Host has changed
* . Connection . Events . PORT _CHANGED Port has changed
* . Connection . Events . NEW _LOG A new log line is available
2013-08-21 06:56:40 +00:00
*
* /
let ConnectionManager = {
_connections : new Set ( ) ,
createConnection : function ( host , port ) {
let c = new Connection ( host , port ) ;
c . once ( "destroy" , ( event ) => this . destroyConnection ( c ) ) ;
this . _connections . add ( c ) ;
this . emit ( "new" , c ) ;
return c ;
} ,
destroyConnection : function ( connection ) {
if ( this . _connections . has ( connection ) ) {
this . _connections . delete ( connection ) ;
if ( connection . status != Connection . Status . DESTROYED ) {
connection . destroy ( ) ;
}
}
} ,
get connections ( ) {
return [ c for ( c of this . _connections ) ] ;
} ,
2013-08-29 01:01:00 +00:00
getFreeTCPPort : function ( ) {
let serv = Cc [ '@mozilla.org/network/server-socket;1' ]
. createInstance ( Ci . nsIServerSocket ) ;
serv . init ( - 1 , true , - 1 ) ;
let port = serv . port ;
serv . close ( ) ;
return port ;
} ,
2013-08-21 06:56:40 +00:00
}
EventEmitter . decorate ( ConnectionManager ) ;
let lastID = - 1 ;
function Connection ( host , port ) {
EventEmitter . decorate ( this ) ;
this . uid = ++ lastID ;
this . host = host ;
this . port = port ;
this . _setStatus ( Connection . Status . DISCONNECTED ) ;
this . _onDisconnected = this . _onDisconnected . bind ( this ) ;
this . _onConnected = this . _onConnected . bind ( this ) ;
this . _onTimeout = this . _onTimeout . bind ( this ) ;
2014-12-11 02:55:52 +00:00
this . resetOptions ( ) ;
2013-08-21 06:56:40 +00:00
}
Connection . Status = {
CONNECTED : "connected" ,
DISCONNECTED : "disconnected" ,
CONNECTING : "connecting" ,
DISCONNECTING : "disconnecting" ,
DESTROYED : "destroyed" ,
}
Connection . Events = {
CONNECTED : Connection . Status . CONNECTED ,
DISCONNECTED : Connection . Status . DISCONNECTED ,
CONNECTING : Connection . Status . CONNECTING ,
DISCONNECTING : Connection . Status . DISCONNECTING ,
DESTROYED : Connection . Status . DESTROYED ,
TIMEOUT : "timeout" ,
STATUS _CHANGED : "status-changed" ,
HOST _CHANGED : "host-changed" ,
PORT _CHANGED : "port-changed" ,
NEW _LOG : "new_log"
}
Connection . prototype = {
logs : "" ,
log : function ( str ) {
2013-09-05 13:15:37 +00:00
let d = new Date ( ) ;
let hours = ( "0" + d . getHours ( ) ) . slice ( - 2 ) ;
let minutes = ( "0" + d . getMinutes ( ) ) . slice ( - 2 ) ;
let seconds = ( "0" + d . getSeconds ( ) ) . slice ( - 2 ) ;
let timestamp = [ hours , minutes , seconds ] . join ( ":" ) + ": " ;
str = timestamp + str ;
2013-08-21 06:56:40 +00:00
this . logs += "\n" + str ;
this . emit ( Connection . Events . NEW _LOG , str ) ;
} ,
get client ( ) {
return this . _client
} ,
get host ( ) {
return this . _host
} ,
set host ( value ) {
if ( this . _host && this . _host == value )
return ;
this . _host = value ;
this . emit ( Connection . Events . HOST _CHANGED ) ;
} ,
get port ( ) {
return this . _port
} ,
set port ( value ) {
if ( this . _port && this . _port == value )
return ;
this . _port = value ;
this . emit ( Connection . Events . PORT _CHANGED ) ;
} ,
2015-01-26 18:47:13 +00:00
get authentication ( ) {
return this . _authentication ;
} ,
set authentication ( value ) {
this . _authentication = value ;
// Create an |Authenticator| of this type
if ( ! value ) {
this . authenticator = null ;
return ;
}
let AuthenticatorType = DebuggerClient . Authenticators . get ( value ) ;
this . authenticator = new AuthenticatorType . Client ( ) ;
} ,
get advertisement ( ) {
return this . _advertisement ;
} ,
set advertisement ( advertisement ) {
// The full advertisement may contain more info than just the standard keys
// below, so keep a copy for use during connection later.
this . _advertisement = advertisement ;
if ( advertisement ) {
[ "host" , "port" , "encryption" , "authentication" ] . forEach ( key => {
this [ key ] = advertisement [ key ] ;
} ) ;
}
} ,
/ * *
* Settings to be passed to | socketConnect | at connection time .
* /
get socketSettings ( ) {
let settings = { } ;
if ( this . advertisement ) {
// Use the advertisement as starting point if it exists, as it may contain
// extra data, like the server's cert.
Object . assign ( settings , this . advertisement ) ;
}
Object . assign ( settings , {
host : this . host ,
port : this . port ,
encryption : this . encryption ,
authenticator : this . authenticator
} ) ;
return settings ;
} ,
2015-03-19 17:58:19 +00:00
timeoutDelay : Services . prefs . getIntPref ( REMOTE _TIMEOUT ) ,
2014-12-11 02:55:52 +00:00
resetOptions ( ) {
this . keepConnecting = false ;
2015-03-19 17:58:19 +00:00
this . timeoutDelay = Services . prefs . getIntPref ( REMOTE _TIMEOUT ) ;
2014-12-11 02:55:52 +00:00
this . encryption = false ;
2015-01-26 18:47:13 +00:00
this . authentication = null ;
this . advertisement = null ;
2014-12-11 02:55:52 +00:00
} ,
2013-08-21 06:56:40 +00:00
disconnect : function ( force ) {
if ( this . status == Connection . Status . DESTROYED ) {
return ;
}
clearTimeout ( this . _timeoutID ) ;
if ( this . status == Connection . Status . CONNECTED ||
this . status == Connection . Status . CONNECTING ) {
this . log ( "disconnecting" ) ;
this . _setStatus ( Connection . Status . DISCONNECTING ) ;
2015-02-06 18:09:39 +00:00
if ( this . _client ) {
this . _client . close ( ) ;
}
2013-08-21 06:56:40 +00:00
}
} ,
2014-09-05 16:38:33 +00:00
connect : function ( transport ) {
2013-08-21 06:56:40 +00:00
if ( this . status == Connection . Status . DESTROYED ) {
return ;
}
if ( ! this . _client ) {
2014-09-08 19:18:20 +00:00
this . _customTransport = transport ;
if ( this . _customTransport ) {
2014-09-05 16:38:33 +00:00
this . log ( "connecting (custom transport)" ) ;
} else {
this . log ( "connecting to " + this . host + ":" + this . port ) ;
}
2013-08-21 06:56:40 +00:00
this . _setStatus ( Connection . Status . CONNECTING ) ;
2014-09-05 16:38:33 +00:00
2015-03-19 17:58:19 +00:00
if ( this . timeoutDelay > 0 ) {
this . _timeoutID = setTimeout ( this . _onTimeout , this . timeoutDelay ) ;
}
2013-08-29 01:01:00 +00:00
this . _clientConnect ( ) ;
2013-08-21 06:56:40 +00:00
} else {
let msg = "Can't connect. Client is not fully disconnected" ;
this . log ( msg ) ;
throw new Error ( msg ) ;
}
} ,
destroy : function ( ) {
this . log ( "killing connection" ) ;
clearTimeout ( this . _timeoutID ) ;
2013-08-29 01:01:00 +00:00
this . keepConnecting = false ;
2013-08-21 06:56:40 +00:00
if ( this . _client ) {
this . _client . close ( ) ;
this . _client = null ;
}
this . _setStatus ( Connection . Status . DESTROYED ) ;
} ,
2014-12-11 02:55:52 +00:00
_getTransport : Task . async ( function * ( ) {
2014-09-08 19:18:20 +00:00
if ( this . _customTransport ) {
2014-12-11 02:55:52 +00:00
return this . _customTransport ;
2013-08-29 01:01:00 +00:00
}
2014-12-11 02:55:52 +00:00
if ( ! this . host ) {
return DebuggerServer . connectPipe ( ) ;
}
2015-01-26 18:47:13 +00:00
let settings = this . socketSettings ;
let transport = yield DebuggerClient . socketConnect ( settings ) ;
2014-12-11 02:55:52 +00:00
return transport ;
} ) ,
_clientConnect : function ( ) {
this . _getTransport ( ) . then ( transport => {
if ( ! transport ) {
return ;
}
this . _client = new DebuggerClient ( transport ) ;
this . _client . addOneTimeListener ( "closed" , this . _onDisconnected ) ;
this . _client . connect ( this . _onConnected ) ;
} , e => {
2015-02-12 11:44:00 +00:00
// If we're continuously trying to connect, we expect the connection to be
// rejected a couple times, so don't log these.
if ( ! this . keepConnecting || e . result !== Cr . NS _ERROR _CONNECTION _REFUSED ) {
console . error ( e ) ;
}
2014-12-11 02:55:52 +00:00
// In some cases, especially on Mac, the openOutputStream call in
// DebuggerClient.socketConnect may throw NS_ERROR_NOT_INITIALIZED.
// It occurs when we connect agressively to the simulator,
// and keep trying to open a socket to the server being started in
// the simulator.
this . _onDisconnected ( ) ;
} ) ;
2013-08-29 01:01:00 +00:00
} ,
2013-08-21 06:56:40 +00:00
get status ( ) {
return this . _status
} ,
_setStatus : function ( value ) {
if ( this . _status && this . _status == value )
return ;
this . _status = value ;
this . emit ( value ) ;
this . emit ( Connection . Events . STATUS _CHANGED , value ) ;
} ,
_onDisconnected : function ( ) {
2013-08-29 01:01:00 +00:00
this . _client = null ;
2014-09-08 19:18:20 +00:00
this . _customTransport = null ;
2013-08-29 01:01:00 +00:00
if ( this . _status == Connection . Status . CONNECTING && this . keepConnecting ) {
2013-10-20 22:56:00 +00:00
setTimeout ( ( ) => this . _clientConnect ( ) , 100 ) ;
2013-08-29 01:01:00 +00:00
return ;
}
2013-08-21 06:56:40 +00:00
clearTimeout ( this . _timeoutID ) ;
2013-09-05 13:15:37 +00:00
2013-08-21 06:56:40 +00:00
switch ( this . status ) {
case Connection . Status . CONNECTED :
this . log ( "disconnected (unexpected)" ) ;
break ;
case Connection . Status . CONNECTING :
2013-09-05 13:15:37 +00:00
this . log ( "connection error. Possible causes: USB port not connected, port not forwarded (adb forward), wrong host or port, remote debugging not enabled on the device." ) ;
2013-08-21 06:56:40 +00:00
break ;
default :
this . log ( "disconnected" ) ;
}
this . _setStatus ( Connection . Status . DISCONNECTED ) ;
} ,
_onConnected : function ( ) {
this . log ( "connected" ) ;
clearTimeout ( this . _timeoutID ) ;
this . _setStatus ( Connection . Status . CONNECTED ) ;
} ,
_onTimeout : function ( ) {
2013-09-05 13:15:37 +00:00
this . log ( "connection timeout. Possible causes: didn't click on 'accept' (prompt)." ) ;
2013-08-29 01:01:00 +00:00
this . emit ( Connection . Events . TIMEOUT ) ;
2013-08-21 06:56:40 +00:00
this . disconnect ( ) ;
} ,
}
exports . ConnectionManager = ConnectionManager ;
exports . Connection = Connection ;