toolkit/devtools/client/connection-manager.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial