toolkit/devtools/client/connection-manager.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:196217f4ff1d
1 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 "use strict";
8
9 const {Cc, Ci, Cu} = require("chrome");
10 const {setTimeout, clearTimeout} = require('sdk/timers');
11 const EventEmitter = require("devtools/toolkit/event-emitter");
12
13 Cu.import("resource://gre/modules/Services.jsm");
14 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
15 Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
16
17 /**
18 * Connection Manager.
19 *
20 * To use this module:
21 * const {ConnectionManager} = require("devtools/client/connection-manager");
22 *
23 * # ConnectionManager
24 *
25 * Methods:
26 * ⬩ Connection createConnection(host, port)
27 * ⬩ void destroyConnection(connection)
28 * ⬩ Number getFreeTCPPort()
29 *
30 * Properties:
31 * ⬩ Array connections
32 *
33 * # Connection
34 *
35 * A connection is a wrapper around a debugger client. It has a simple
36 * API to instantiate a connection to a debugger server. Once disconnected,
37 * no need to re-create a Connection object. Calling `connect()` again
38 * will re-create a debugger client.
39 *
40 * Methods:
41 * ⬩ connect() Connect to host:port. Expect a "connecting" event. If
42 * host is not specified, a local pipe is used
43 * ⬩ disconnect() Disconnect if connected. Expect a "disconnecting" event
44 *
45 * Properties:
46 * ⬩ host IP address or hostname
47 * ⬩ port Port
48 * ⬩ logs Current logs. "newlog" event notifies new available logs
49 * ⬩ store Reference to a local data store (see below)
50 * ⬩ keepConnecting Should the connection keep trying connecting
51 * ⬩ status Connection status:
52 * Connection.Status.CONNECTED
53 * Connection.Status.DISCONNECTED
54 * Connection.Status.CONNECTING
55 * Connection.Status.DISCONNECTING
56 * Connection.Status.DESTROYED
57 *
58 * Events (as in event-emitter.js):
59 * ⬩ Connection.Events.CONNECTING Trying to connect to host:port
60 * ⬩ Connection.Events.CONNECTED Connection is successful
61 * ⬩ Connection.Events.DISCONNECTING Trying to disconnect from server
62 * ⬩ Connection.Events.DISCONNECTED Disconnected (at client request, or because of a timeout or connection error)
63 * ⬩ Connection.Events.STATUS_CHANGED The connection status (connection.status) has changed
64 * ⬩ Connection.Events.TIMEOUT Connection timeout
65 * ⬩ Connection.Events.HOST_CHANGED Host has changed
66 * ⬩ Connection.Events.PORT_CHANGED Port has changed
67 * ⬩ Connection.Events.NEW_LOG A new log line is available
68 *
69 */
70
71 let ConnectionManager = {
72 _connections: new Set(),
73 createConnection: function(host, port) {
74 let c = new Connection(host, port);
75 c.once("destroy", (event) => this.destroyConnection(c));
76 this._connections.add(c);
77 this.emit("new", c);
78 return c;
79 },
80 destroyConnection: function(connection) {
81 if (this._connections.has(connection)) {
82 this._connections.delete(connection);
83 if (connection.status != Connection.Status.DESTROYED) {
84 connection.destroy();
85 }
86 }
87 },
88 get connections() {
89 return [c for (c of this._connections)];
90 },
91 getFreeTCPPort: function () {
92 let serv = Cc['@mozilla.org/network/server-socket;1']
93 .createInstance(Ci.nsIServerSocket);
94 serv.init(-1, true, -1);
95 let port = serv.port;
96 serv.close();
97 return port;
98 },
99 }
100
101 EventEmitter.decorate(ConnectionManager);
102
103 let lastID = -1;
104
105 function Connection(host, port) {
106 EventEmitter.decorate(this);
107 this.uid = ++lastID;
108 this.host = host;
109 this.port = port;
110 this._setStatus(Connection.Status.DISCONNECTED);
111 this._onDisconnected = this._onDisconnected.bind(this);
112 this._onConnected = this._onConnected.bind(this);
113 this._onTimeout = this._onTimeout.bind(this);
114 this.keepConnecting = false;
115 }
116
117 Connection.Status = {
118 CONNECTED: "connected",
119 DISCONNECTED: "disconnected",
120 CONNECTING: "connecting",
121 DISCONNECTING: "disconnecting",
122 DESTROYED: "destroyed",
123 }
124
125 Connection.Events = {
126 CONNECTED: Connection.Status.CONNECTED,
127 DISCONNECTED: Connection.Status.DISCONNECTED,
128 CONNECTING: Connection.Status.CONNECTING,
129 DISCONNECTING: Connection.Status.DISCONNECTING,
130 DESTROYED: Connection.Status.DESTROYED,
131 TIMEOUT: "timeout",
132 STATUS_CHANGED: "status-changed",
133 HOST_CHANGED: "host-changed",
134 PORT_CHANGED: "port-changed",
135 NEW_LOG: "new_log"
136 }
137
138 Connection.prototype = {
139 logs: "",
140 log: function(str) {
141 let d = new Date();
142 let hours = ("0" + d.getHours()).slice(-2);
143 let minutes = ("0" + d.getMinutes()).slice(-2);
144 let seconds = ("0" + d.getSeconds()).slice(-2);
145 let timestamp = [hours, minutes, seconds].join(":") + ": ";
146 str = timestamp + str;
147 this.logs += "\n" + str;
148 this.emit(Connection.Events.NEW_LOG, str);
149 },
150
151 get client() {
152 return this._client
153 },
154
155 get host() {
156 return this._host
157 },
158
159 set host(value) {
160 if (this._host && this._host == value)
161 return;
162 this._host = value;
163 this.emit(Connection.Events.HOST_CHANGED);
164 },
165
166 get port() {
167 return this._port
168 },
169
170 set port(value) {
171 if (this._port && this._port == value)
172 return;
173 this._port = value;
174 this.emit(Connection.Events.PORT_CHANGED);
175 },
176
177 disconnect: function(force) {
178 if (this.status == Connection.Status.DESTROYED) {
179 return;
180 }
181 clearTimeout(this._timeoutID);
182 if (this.status == Connection.Status.CONNECTED ||
183 this.status == Connection.Status.CONNECTING) {
184 this.log("disconnecting");
185 this._setStatus(Connection.Status.DISCONNECTING);
186 this._client.close();
187 }
188 },
189
190 connect: function() {
191 if (this.status == Connection.Status.DESTROYED) {
192 return;
193 }
194 if (!this._client) {
195 this.log("connecting to " + this.host + ":" + this.port);
196 this._setStatus(Connection.Status.CONNECTING);
197 let delay = Services.prefs.getIntPref("devtools.debugger.remote-timeout");
198 this._timeoutID = setTimeout(this._onTimeout, delay);
199
200 this._clientConnect();
201 } else {
202 let msg = "Can't connect. Client is not fully disconnected";
203 this.log(msg);
204 throw new Error(msg);
205 }
206 },
207
208 destroy: function() {
209 this.log("killing connection");
210 clearTimeout(this._timeoutID);
211 this.keepConnecting = false;
212 if (this._client) {
213 this._client.close();
214 this._client = null;
215 }
216 this._setStatus(Connection.Status.DESTROYED);
217 },
218
219 _clientConnect: function () {
220 let transport;
221 if (!this.host) {
222 transport = DebuggerServer.connectPipe();
223 } else {
224 try {
225 transport = debuggerSocketConnect(this.host, this.port);
226 } catch (e) {
227 // In some cases, especially on Mac, the openOutputStream call in
228 // debuggerSocketConnect may throw NS_ERROR_NOT_INITIALIZED.
229 // It occurs when we connect agressively to the simulator,
230 // and keep trying to open a socket to the server being started in
231 // the simulator.
232 this._onDisconnected();
233 return;
234 }
235 }
236 this._client = new DebuggerClient(transport);
237 this._client.addOneTimeListener("closed", this._onDisconnected);
238 this._client.connect(this._onConnected);
239 },
240
241 get status() {
242 return this._status
243 },
244
245 _setStatus: function(value) {
246 if (this._status && this._status == value)
247 return;
248 this._status = value;
249 this.emit(value);
250 this.emit(Connection.Events.STATUS_CHANGED, value);
251 },
252
253 _onDisconnected: function() {
254 this._client = null;
255
256 if (this._status == Connection.Status.CONNECTING && this.keepConnecting) {
257 setTimeout(() => this._clientConnect(), 100);
258 return;
259 }
260
261 clearTimeout(this._timeoutID);
262
263 switch (this.status) {
264 case Connection.Status.CONNECTED:
265 this.log("disconnected (unexpected)");
266 break;
267 case Connection.Status.CONNECTING:
268 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.");
269 break;
270 default:
271 this.log("disconnected");
272 }
273 this._setStatus(Connection.Status.DISCONNECTED);
274 },
275
276 _onConnected: function() {
277 this.log("connected");
278 clearTimeout(this._timeoutID);
279 this._setStatus(Connection.Status.CONNECTED);
280 },
281
282 _onTimeout: function() {
283 this.log("connection timeout. Possible causes: didn't click on 'accept' (prompt).");
284 this.emit(Connection.Events.TIMEOUT);
285 this.disconnect();
286 },
287 }
288
289 exports.ConnectionManager = ConnectionManager;
290 exports.Connection = Connection;
291

mercurial