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" ;
2013-08-29 01:01:00 +00:00
const { Cc , Ci , Cu } = 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" ) ;
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" ) ;
/ * *
* Connection Manager .
*
* To use this module :
* const { ConnectionManager } = require ( "devtools/client/connection-manager" ) ;
*
* # ConnectionManager
*
* Methods :
* ⬩ Connection createConnection ( host , port )
* ⬩ void destroyConnection ( connection )
2013-08-29 01:01:00 +00:00
* ⬩ Number getFreeTCPPort ( )
2013-08-21 06:56:40 +00:00
*
* Properties :
* ⬩ Array connections
*
* # 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 :
* ⬩ connect ( ) Connect to host : port . Expect a "connecting" event . If
* host is not specified , a local pipe is used
* ⬩ disconnect ( ) Disconnect if connected . Expect a "disconnecting" event
*
* Properties :
* ⬩ 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 )
2013-08-29 01:01:00 +00:00
* ⬩ keepConnecting Should the connection keep trying connecting
2013-08-21 06:56:40 +00:00
* ⬩ status Connection status :
* Connection . Status . CONNECTED
* Connection . Status . DISCONNECTED
* Connection . Status . CONNECTING
* Connection . Status . DISCONNECTING
* Connection . Status . DESTROYED
*
* Events ( as in event - emitter . js ) :
* ⬩ 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
*
* /
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 ) ;
2013-08-29 01:01:00 +00:00
this . keepConnecting = false ;
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 ) ;
} ,
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 ) ;
this . _client . close ( ) ;
}
} ,
connect : function ( ) {
if ( this . status == Connection . Status . DESTROYED ) {
return ;
}
if ( ! this . _client ) {
this . log ( "connecting to " + this . host + ":" + this . port ) ;
this . _setStatus ( Connection . Status . CONNECTING ) ;
let delay = Services . prefs . getIntPref ( "devtools.debugger.remote-timeout" ) ;
this . _timeoutID = setTimeout ( this . _onTimeout , delay ) ;
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 ) ;
} ,
2013-08-29 01:01:00 +00:00
_clientConnect : function ( ) {
let transport ;
2013-09-05 13:15:37 +00:00
if ( ! this . host ) {
2013-08-29 01:01:00 +00:00
transport = DebuggerServer . connectPipe ( ) ;
} else {
2013-10-20 22:56:00 +00:00
try {
transport = debuggerSocketConnect ( this . host , this . port ) ;
} catch ( e ) {
// In some cases, especially on Mac, the openOutputStream call in
// debuggerSocketConnect 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 ( ) ;
return ;
}
2013-08-29 01:01:00 +00:00
}
this . _client = new DebuggerClient ( transport ) ;
this . _client . addOneTimeListener ( "closed" , this . _onDisconnected ) ;
this . _client . connect ( this . _onConnected ) ;
} ,
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 ;
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 ;