mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
ca83343b85
--HG-- rename : browser/devtools/shared/event-emitter.js => toolkit/devtools/event-emitter.js rename : browser/devtools/shared/test/browser_eventemitter_basic.js => toolkit/devtools/tests/mochitest/test_eventemitter_basic.html
292 lines
8.8 KiB
JavaScript
292 lines
8.8 KiB
JavaScript
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* 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";
|
|
|
|
const {Cc, Ci, Cu} = require("chrome");
|
|
const {setTimeout, clearTimeout} = require('sdk/timers');
|
|
const EventEmitter = require("devtools/toolkit/event-emitter");
|
|
|
|
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)
|
|
* ⬩ Number getFreeTCPPort()
|
|
*
|
|
* 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)
|
|
* ⬩ keepConnecting Should the connection keep trying connecting
|
|
* ⬩ 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)];
|
|
},
|
|
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;
|
|
},
|
|
}
|
|
|
|
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);
|
|
this.keepConnecting = false;
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
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);
|
|
|
|
this._clientConnect();
|
|
} 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);
|
|
this.keepConnecting = false;
|
|
if (this._client) {
|
|
this._client.close();
|
|
this._client = null;
|
|
}
|
|
this._setStatus(Connection.Status.DESTROYED);
|
|
},
|
|
|
|
_clientConnect: function () {
|
|
let transport;
|
|
if (!this.host) {
|
|
transport = DebuggerServer.connectPipe();
|
|
} else {
|
|
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;
|
|
}
|
|
}
|
|
this._client = new DebuggerClient(transport);
|
|
this._client.addOneTimeListener("closed", this._onDisconnected);
|
|
this._client.connect(this._onConnected);
|
|
},
|
|
|
|
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() {
|
|
this._client = null;
|
|
|
|
if (this._status == Connection.Status.CONNECTING && this.keepConnecting) {
|
|
setTimeout(() => this._clientConnect(), 100);
|
|
return;
|
|
}
|
|
|
|
clearTimeout(this._timeoutID);
|
|
|
|
switch (this.status) {
|
|
case Connection.Status.CONNECTED:
|
|
this.log("disconnected (unexpected)");
|
|
break;
|
|
case Connection.Status.CONNECTING:
|
|
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.");
|
|
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() {
|
|
this.log("connection timeout. Possible causes: didn't click on 'accept' (prompt).");
|
|
this.emit(Connection.Events.TIMEOUT);
|
|
this.disconnect();
|
|
},
|
|
}
|
|
|
|
exports.ConnectionManager = ConnectionManager;
|
|
exports.Connection = Connection;
|
|
|