Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
264
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/actions.js
Executable file
264
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/actions.js
Executable file
|
|
@ -0,0 +1,264 @@
|
|||
/* global Marionette */
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Actions is used to denote a set of actions that
|
||||
* have to be executed in particular order.
|
||||
*
|
||||
* @class Marionette.Actions
|
||||
* @param {Marionette.Client} context of a client.
|
||||
*/
|
||||
function Actions(client) {
|
||||
this.client = client;
|
||||
this.actionChain = [];
|
||||
this.currentId = null;
|
||||
}
|
||||
|
||||
Actions.prototype = {
|
||||
|
||||
/**
|
||||
* Send a 'touchstart' event to this element. If no coordinates are given,
|
||||
* it will be targeted at the center of the element.
|
||||
* If given, it will be targeted at the (x,y) coordinates
|
||||
* relative to the top-left corner of the element.
|
||||
*
|
||||
* @method press
|
||||
* @param {Object} element {{#crossLink "Marionette.Element"}}{{/crossLink}}
|
||||
* to press on.
|
||||
* @param {Number} x optional, x-coordinate to tap,
|
||||
* relative to the top-left corner of the element.
|
||||
* @param {Number} y optional, y-coordinate to tap,
|
||||
* relative to the top-left corner of the element.
|
||||
* @return {Object} self.
|
||||
*/
|
||||
press: function press(element, x, y) {
|
||||
this.actionChain.push(['press', element.id, x, y]);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* release() can only be called
|
||||
* if press() has already be called on this element.
|
||||
* Send 'touchend' event to whenever the finger is.
|
||||
*
|
||||
* @method release
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
release: function release() {
|
||||
this.actionChain.push(['release']);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* move() can only be called
|
||||
* if press() has already be called on this element.
|
||||
* move() send 'touchmove' event which moves the finger to target element.
|
||||
*
|
||||
* @method move
|
||||
* @param {Object} element {{#crossLink "Marionette.Element"}}{{/crossLink}}
|
||||
* of the move gesture.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
move: function move(element) {
|
||||
this.actionChain.push(['move', element.id]);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* moveByOffset() can only be called
|
||||
* if press() has already be called on this element.
|
||||
* moveByOffset() send 'touchmove' event.
|
||||
*
|
||||
* @method moveByOffset
|
||||
*
|
||||
* @param {Number} x x-coordinate relative to the top-left corner of
|
||||
* the target element of the last touch.
|
||||
*
|
||||
* @param {Number} y y-coordinate relative to the top-left corner of
|
||||
* the target element of the last touch.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
moveByOffset: function moveByOffset(x, y) {
|
||||
this.actionChain.push(['moveByOffset', x, y]);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* wait() waits for specified time period.
|
||||
*
|
||||
* @method wait
|
||||
* @param {Number} time wait for "time" seconds.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
wait: function wait(time) {
|
||||
this.actionChain.push(['wait', time]);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* cancel() can only be called
|
||||
* if press() has already be called on this element.
|
||||
* cancel() send 'touchcancel' event to whenever the finger is.
|
||||
*
|
||||
* @method cancel
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
cancel: function cancel() {
|
||||
this.actionChain.push(['cancel']);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* tap() performs a quick tap on the target.
|
||||
* action.tap() is essentially action.press().release().
|
||||
*
|
||||
* @method tap
|
||||
* @param {Object} element {{#crossLink "Marionette.Element"}}{{/crossLink}}
|
||||
* to press/release on.
|
||||
*
|
||||
* @param {Number} x optional, x-coordinate relative to the top-left corner
|
||||
* of the element of the last touch.
|
||||
*
|
||||
* @param {Number} y optional, y-coordinate relative to the top-left corner
|
||||
* of the element of the last touch.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
tap: function tap(element, x, y) {
|
||||
this.actionChain.push(['press', element.id, x, y]);
|
||||
this.actionChain.push(['release']);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* doubleTap() performs a double
|
||||
* {{#crossLink "Marionette.Actions/tap"}}{{/crossLink}} on the element.
|
||||
*
|
||||
* @method doubleTap
|
||||
* @param {Object} element {{#crossLink "Marionette.Element"}}{{/crossLink}}
|
||||
* to double tap on.
|
||||
*
|
||||
* @param {Number} x optional, x-coordinate relative to
|
||||
* the top-left corner of the element of the last touch.
|
||||
*
|
||||
* @param {Number} y optional, y-coordinate relative to
|
||||
* the top-left corner of the element of the last touch.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
doubleTap: function doubleTap(element, x, y) {
|
||||
this.actionChain.push(['press', element.id, x, y]);
|
||||
this.actionChain.push(['release']);
|
||||
this.actionChain.push(['press', element.id, x, y]);
|
||||
this.actionChain.push(['release']);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* flick() sends a sequence of touch events:
|
||||
* touchstart, touchmove, touchend.
|
||||
* It scrolls the page in any direction
|
||||
* within period of time provided(duration).
|
||||
*
|
||||
* @method flick
|
||||
* @param {Object} element {{#crossLink "Marionette.Element"}}{{/crossLink}}
|
||||
* to double tap on.
|
||||
*
|
||||
* @param {Number} x1 starting x-coordinates of
|
||||
* the finger relative to the element.
|
||||
*
|
||||
* @param {Number} y1 starting y-coordinates of
|
||||
* the finger relative to the element.
|
||||
*
|
||||
* @param {Number} x2 ending x-coordinates of
|
||||
* the finger relative to the element.
|
||||
*
|
||||
* @param {Number} y2 ending y-coordinates of
|
||||
* the finger relative to the element.
|
||||
*
|
||||
* @param {Number} duration optional,
|
||||
* time needed for the flick gesture for complete.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
flick: function flick(element, x1, y1, x2, y2, duration) {
|
||||
var time = 0;
|
||||
var timeIncrement = 10.0;
|
||||
if (duration === undefined) {
|
||||
duration = 200;
|
||||
}
|
||||
if (timeIncrement >= duration) {
|
||||
timeIncrement = duration;
|
||||
}
|
||||
var moveX = timeIncrement / duration * (x2 - x1);
|
||||
var moveY = timeIncrement / duration * (y2 - y1);
|
||||
|
||||
this.actionChain.push(['press', element.id, x1, y1]);
|
||||
while (time < duration) {
|
||||
time += timeIncrement;
|
||||
this.actionChain.push(['moveByOffset', moveX, moveY]);
|
||||
this.actionChain.push(['wait', timeIncrement / 1000]);
|
||||
}
|
||||
this.actionChain.push(['release']);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends 'touchstart',
|
||||
* then wait for 'time' seconds, and send 'touchend' eventually.
|
||||
* longPress() cannot follow any active touch,
|
||||
* i.e. the finger must leave the screen before longPress() gets called.
|
||||
*
|
||||
* @method longPress
|
||||
* @param {Object} element {{#crossLink "Marionette.Element"}}{{/crossLink}}
|
||||
* of the long press.
|
||||
*
|
||||
* @param {Number} time the waiting time between touchstart and touchend.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
longPress: function longPress(element, time) {
|
||||
this.actionChain.push(['press', element.id]);
|
||||
this.actionChain.push(['wait', time]);
|
||||
this.actionChain.push(['release']);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* perform() will send the whole action chain built
|
||||
* so far to the server side for execution.
|
||||
*
|
||||
* @method perform
|
||||
* @param {Function} callback callback when the perform completes.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
perform: function perform(callback) {
|
||||
var cmd = {
|
||||
name: 'actionChain',
|
||||
parameters: {
|
||||
chain: this.actionChain,
|
||||
nextId: this.currentId
|
||||
}
|
||||
};
|
||||
this.currentId = this.client._sendCommand(cmd, callback, 'value');
|
||||
this.actionChain = [];
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Actions;
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette('actions'), Marionette] :
|
||||
[module, require('../../lib/marionette/marionette')]
|
||||
));
|
||||
1656
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/client.js
Executable file
1656
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/client.js
Executable file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,94 @@
|
|||
/* global Marionette */
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
var debug = require('debug')('marionette:command-stream');
|
||||
var Responder = ns.require('responder');
|
||||
var wire = require('json-wire-protocol');
|
||||
|
||||
/**
|
||||
* Command stream accepts a socket or any event
|
||||
* emitter that will emit data events
|
||||
*
|
||||
* @class Marionette.CommandStream
|
||||
* @param {EventEmitter} socket socket instance.
|
||||
* @constructor
|
||||
*/
|
||||
function CommandStream(socket) {
|
||||
this.socket = socket;
|
||||
this._handler = new wire.Stream();
|
||||
|
||||
this._handler.on('data', this.emit.bind(this, this.commandEvent));
|
||||
|
||||
Responder.apply(this);
|
||||
|
||||
socket.on('data', this._handler.write.bind(this._handler));
|
||||
socket.on('error', function(err) {
|
||||
this.emit('error', err);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
var proto = CommandStream.prototype = Object.create(
|
||||
Responder.prototype
|
||||
);
|
||||
|
||||
/**
|
||||
* name of the event this class
|
||||
* will emit when a response to a
|
||||
* command is received.
|
||||
*
|
||||
* @property commandEvent
|
||||
* @type String
|
||||
*/
|
||||
proto.commandEvent = 'command';
|
||||
|
||||
/**
|
||||
* Parses command into a string to
|
||||
* be sent over a tcp socket to marionette.
|
||||
*
|
||||
*
|
||||
* @method stringify
|
||||
* @param {Object} command marionette command.
|
||||
* @return {String} command as a string.
|
||||
*/
|
||||
proto.stringify = function stringify(command) {
|
||||
return wire.stringify(command);
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a command to the socket.
|
||||
* Handles conversion and formatting of object.
|
||||
*
|
||||
* @method send
|
||||
* @param {Object} data marionette command.
|
||||
*/
|
||||
proto.send = function send(data) {
|
||||
debug('writing ', data, 'to socket');
|
||||
if (this.socket.write) {
|
||||
//nodejs socket
|
||||
this.socket.write(this.stringify(data), 'utf8');
|
||||
} else {
|
||||
//moztcp socket
|
||||
this.socket.send(this.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a chunk (string or buffer) to the
|
||||
* total buffer of this instance.
|
||||
*
|
||||
* @this
|
||||
* @param {String|Buffer} buffer buffer or string to add.
|
||||
*/
|
||||
proto.add = function add(buffer) {
|
||||
this._handler.write(buffer);
|
||||
};
|
||||
|
||||
module.exports = exports = CommandStream;
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette('command-stream'), Marionette] :
|
||||
[module, require('./marionette')]
|
||||
));
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
/* global Marionette */
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
var Command = require('../message').Command;
|
||||
var Response = require('../message').Response;
|
||||
|
||||
/**
|
||||
*
|
||||
* Abstract driver that will handle
|
||||
* all common tasks between implementations.
|
||||
* Such as error handling, request/response queuing
|
||||
* and timeouts.
|
||||
*
|
||||
* @constructor
|
||||
* @class Marionette.Drivers.Abstract
|
||||
* @param {Object} options set options on prototype.
|
||||
*/
|
||||
function Abstract(options) {
|
||||
this._sendQueue = [];
|
||||
this._responseQueue = [];
|
||||
this._lastId = 0;
|
||||
}
|
||||
|
||||
Abstract.prototype = {
|
||||
|
||||
/**
|
||||
* Duration in milliseconds to wait before timing out a command
|
||||
* request.
|
||||
*
|
||||
* @property timeout
|
||||
* @type Number
|
||||
*/
|
||||
timeout: 10000,
|
||||
|
||||
/**
|
||||
* Waiting for a command to finish?
|
||||
*
|
||||
* @private
|
||||
* @property _waiting
|
||||
* @type Boolean
|
||||
*/
|
||||
_waiting: true,
|
||||
|
||||
/**
|
||||
* Is system ready for commands?
|
||||
*
|
||||
* @property ready
|
||||
* @type Boolean
|
||||
*/
|
||||
ready: false,
|
||||
|
||||
/**
|
||||
* A unique identifier for the current connection.
|
||||
*
|
||||
* @property connectionId
|
||||
* @type Number
|
||||
*/
|
||||
connectionId: null,
|
||||
|
||||
/**
|
||||
* Duration in milliseconds defining when to abort an attempt to
|
||||
* execute script in the remote end.
|
||||
*
|
||||
* @method setScriptTiemout
|
||||
* @param {Number} the timeout value.
|
||||
*/
|
||||
setScriptTimeout: function setScriptTimeout(timeout) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a JSON data structure across the wire to the remote end.
|
||||
*
|
||||
* Each command is queued whilst waiting for any pending commands.
|
||||
* This ensures order of response is correct, but also means the client
|
||||
* implements a synchronous interface for Marionette.
|
||||
*
|
||||
* @method send
|
||||
*
|
||||
* @param {(Object|Command|Response)} obj
|
||||
* An object that can be dumped into a JSON data structure,
|
||||
* or a message that can be marshaled.
|
||||
* @param {Function} callback
|
||||
* Callback to be invoked when a response for the request is
|
||||
* received.
|
||||
*/
|
||||
send: function send(obj, callback) {
|
||||
if (!this.ready) {
|
||||
throw new Error('Connection is not ready');
|
||||
}
|
||||
|
||||
if (typeof callback == 'undefined') {
|
||||
throw new Error('Callback is required');
|
||||
}
|
||||
|
||||
if (obj instanceof Command || obj instanceof Response) {
|
||||
this.sendMessage(obj, callback);
|
||||
} else {
|
||||
this.sendRaw(obj, callback);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message across the wire to the remote end.
|
||||
*
|
||||
* @param {(Command|Response)} message
|
||||
* The message to send.
|
||||
* @param {Function} callback
|
||||
* Callback to be invoked when a response for the request is
|
||||
* received.
|
||||
*/
|
||||
sendMessage: function(message, callback) {
|
||||
message.id = ++this._lastId;
|
||||
this.sendRaw(message.toMsg(), callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a JSON data structure across the wire to the remote end.
|
||||
*
|
||||
* @param {Object} packet
|
||||
* An object that can be marshaled into a JSON data structure.
|
||||
* @param {Function} callback
|
||||
* Callback to be invoked when a response for the request is
|
||||
* received.
|
||||
*/
|
||||
sendRaw: function(packet, callback) {
|
||||
this._responseQueue.push(callback);
|
||||
this._sendQueue.push(packet);
|
||||
this._nextCommand();
|
||||
},
|
||||
|
||||
/**
|
||||
* Connects to the remote end.
|
||||
*
|
||||
* Requires the _connect function to be defined in the concrete class.
|
||||
*
|
||||
* MyConcreteClass.prototype._connect = function _connect(){
|
||||
* // open a socket to marrionete accept response
|
||||
* // you *must* call _onDeviceResponse with the first
|
||||
* // response from Marionette it looks like this:
|
||||
* // {applicationType: "gecko", marionetteProtocol: 2}
|
||||
* this.connectionId = result.id;
|
||||
* }
|
||||
*
|
||||
* @method connect
|
||||
* @param {Function} callback executes
|
||||
* after successfully connecting to the server.
|
||||
*/
|
||||
connect: function connect(callback) {
|
||||
this.ready = true;
|
||||
this._responseQueue.push(function(data) {
|
||||
this.marionetteProtocol = data.marionetteProtocol || 1;
|
||||
this.traits = data.traits;
|
||||
this.applicationType = data.applicationType;
|
||||
|
||||
callback();
|
||||
}.bind(this));
|
||||
this._connect();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys connection to server
|
||||
*
|
||||
* Will immediately close connection to server
|
||||
* closing any pending responses.
|
||||
*
|
||||
* @method close
|
||||
*/
|
||||
close: function() {
|
||||
this.ready = false;
|
||||
this._responseQueue.length = 0;
|
||||
if (this._close) {
|
||||
this._close();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks queue if not waiting for a response
|
||||
* Sends command to websocket server
|
||||
*
|
||||
* @private
|
||||
* @method _nextCommand
|
||||
*/
|
||||
_nextCommand: function _nextCommand() {
|
||||
if (!this._waiting && this._sendQueue.length) {
|
||||
this._waiting = true;
|
||||
var next = this._sendQueue.shift();
|
||||
this._sendCommand(next);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles responses from devices.
|
||||
* Will only respond to the event if the connectionId
|
||||
* is equal to the event id and the client is ready.
|
||||
*
|
||||
* @param {Object} data response from server.
|
||||
* @private
|
||||
* @method _onDeviceResponse
|
||||
*/
|
||||
_onDeviceResponse: function _onDeviceResponse(data) {
|
||||
if (this.ready && data.id === this.connectionId) {
|
||||
this._waiting = false;
|
||||
|
||||
var resp;
|
||||
if (this.marionetteProtocol >= 3) {
|
||||
var msg = Response.fromMsg(data.response);
|
||||
resp = msg.result;
|
||||
} else {
|
||||
resp = data.response;
|
||||
}
|
||||
|
||||
var cb = this._responseQueue.shift();
|
||||
cb(resp);
|
||||
|
||||
this._nextCommand();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = Abstract;
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette('drivers/abstract'), Marionette] :
|
||||
[module, require('../marionette')]
|
||||
));
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/* global Marionette */
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
Abstract: ns.require('drivers/abstract')
|
||||
};
|
||||
|
||||
if (typeof(window) === 'undefined') {
|
||||
module.exports.Tcp = require('./tcp');
|
||||
module.exports.TcpSync = require('./tcp-sync');
|
||||
} else {
|
||||
if (typeof(window.TCPSocket) !== 'undefined') {
|
||||
module.exports.MozTcp = ns.require('drivers/moz-tcp');
|
||||
}
|
||||
}
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette('drivers'), Marionette] :
|
||||
[module, require('../marionette')]
|
||||
));
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
'use strict';
|
||||
|
||||
var debug = require('debug')('marionette:tcp-sync');
|
||||
var wire = require('json-wire-protocol');
|
||||
var sockittome = require('sockit-to-me');
|
||||
|
||||
var Command = require('../message').Command;
|
||||
var Response = require('../message').Response;
|
||||
|
||||
var DEFAULT_HOST = 'localhost';
|
||||
var DEFAULT_PORT = 2828;
|
||||
var SOCKET_TIMEOUT_EXTRA = 500;
|
||||
|
||||
function TcpSync(options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
this.host = options.host || DEFAULT_HOST;
|
||||
this.port = options.port || DEFAULT_PORT;
|
||||
this.connectionTimeout = options.connectionTimeout || 2000;
|
||||
|
||||
this.isSync = true;
|
||||
this.retryInterval = 300;
|
||||
this.sockit = new sockittome.Sockit();
|
||||
this.sockit.setPollTimeout(this.connectionTimeout + SOCKET_TIMEOUT_EXTRA);
|
||||
|
||||
this._lastId = 0;
|
||||
}
|
||||
|
||||
TcpSync.prototype._createSocketConfig = function() {
|
||||
return {host: this.host, port: this.port};
|
||||
};
|
||||
|
||||
TcpSync.prototype.setScriptTimeout = function(timeout) {
|
||||
this.sockit.setPollTimeout(timeout + SOCKET_TIMEOUT_EXTRA);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility to wait for the marionette socket to be ready.
|
||||
*
|
||||
* @method waitForSocket
|
||||
* @param {Object} [options] for timeout.
|
||||
* @param {Number} [options.interval] time between running test.
|
||||
* @param {Number} [options.timeout] maximum wallclock time before
|
||||
* failing test.
|
||||
* @param {Function} [callback] callback.
|
||||
*/
|
||||
TcpSync.prototype.waitForSocket = function(options, callback) {
|
||||
// the logic is lifted from
|
||||
// mozilla-central/source/testing/marionette/client/marionette/marionette.py
|
||||
|
||||
if (typeof(options) === 'function') {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
var interval = options.interval || 100;
|
||||
var timeout = options.timeout || 300000;
|
||||
var socketTimeout = 1000;
|
||||
|
||||
var sockit = new sockittome.Sockit();
|
||||
var start = Date.now();
|
||||
var lastDebugMessage = '';
|
||||
var self = this;
|
||||
|
||||
// Use sockittome's built in polling timeout during calls to connect
|
||||
// to avoid socket misuse.
|
||||
sockit.setPollTimeout(socketTimeout);
|
||||
|
||||
var probeSocket = function() {
|
||||
try {
|
||||
sockit.connect(this._createSocketConfig());
|
||||
var s = sockit.read(16).toString();
|
||||
sockit.close();
|
||||
|
||||
if (s.indexOf(':') != -1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// This may seem ridiculous, but we have to keep these errors showing up
|
||||
// but, they often repeat like CRAZY, so, quiet them down by only showing
|
||||
// each exception we encounter once.
|
||||
if (lastDebugMessage != e.message) {
|
||||
lastDebugMessage = e.message;
|
||||
debug('exception when probing socket', lastDebugMessage);
|
||||
}
|
||||
|
||||
// Above read _may_ fail so it is important to close the socket...
|
||||
sockit.close();
|
||||
}
|
||||
// timeout. Abort.
|
||||
if ((Date.now() - start) > timeout) {
|
||||
console.error('Timeout connecting to B2G');
|
||||
return;
|
||||
}
|
||||
// interval delay for the next iteration.
|
||||
setTimeout(probeSocket.bind(self), interval);
|
||||
}.bind(this);
|
||||
|
||||
debug('probing socket');
|
||||
probeSocket();
|
||||
};
|
||||
|
||||
TcpSync.prototype.connect = function(callback) {
|
||||
this.waitForSocket(function _connect() {
|
||||
try {
|
||||
this.sockit.connect(this._createSocketConfig());
|
||||
} catch(err) {
|
||||
if (Date.now() - this._beginConnect >= this.connectionTimeout) {
|
||||
callback(err);
|
||||
}
|
||||
setTimeout(_connect.bind(this, callback), this.retryInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._beginConnect) {
|
||||
this._beginConnect = Date.now();
|
||||
}
|
||||
|
||||
// Ensure this method's resolution is asynchronous in all cases
|
||||
process.nextTick(function() {
|
||||
debug('socket connected');
|
||||
delete this._beginConnect;
|
||||
|
||||
var resp = this._readResponse();
|
||||
this.marionetteProtocol = resp.marionetteProtocol || 1;
|
||||
this.traits = resp.traits;
|
||||
this.applicationType = resp.applicationType;
|
||||
|
||||
callback();
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
TcpSync.prototype.defaultCallback = function(err, result) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Following the json-wire-protocol implementation, read one byte at a time
|
||||
// until the 'separator' character is received. The data preceding the
|
||||
// delimiter describes the length of the JSON string.
|
||||
TcpSync.prototype._readResponse = function() {
|
||||
var stream = new wire.Stream();
|
||||
var data, error;
|
||||
|
||||
stream.on('data', function(parsed) {
|
||||
data = parsed;
|
||||
});
|
||||
stream.on('error', function(err) {
|
||||
error = err;
|
||||
});
|
||||
|
||||
while (!data && !error) {
|
||||
stream.write(this.sockit.read(1));
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (this.marionetteProtocol >= 3) {
|
||||
var resp = Response.fromMsg(data);
|
||||
return resp.error || resp.result;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a JSON data structure across the wire to the remote end.
|
||||
*
|
||||
* @param {(Object|Command|Response)} obj
|
||||
* An object that can be dumped into a JSON data structure,
|
||||
* or a message that can be marshaled.
|
||||
* @param {Function} cb
|
||||
* Callback to be invoked when a response for the request is
|
||||
* received.
|
||||
*
|
||||
* @return {?}
|
||||
* The return value from the passed in callback.
|
||||
*/
|
||||
TcpSync.prototype.send = function(obj, cb) {
|
||||
if (obj instanceof Command || obj instanceof Response) {
|
||||
return this.sendMessage(obj, cb);
|
||||
} else {
|
||||
return this.sendRaw(obj, cb);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message across the wire to the remote end.
|
||||
*
|
||||
* @param {(Command|Response)} msg
|
||||
* The message to send.
|
||||
* @param {Function} cb
|
||||
* Callback to be invoked when a response for the request is received.
|
||||
*
|
||||
* @return {?}
|
||||
* The return value from the passed in callback.
|
||||
*/
|
||||
TcpSync.prototype.sendMessage = function(msg, cb) {
|
||||
msg.id = ++this._lastId;
|
||||
return this.sendRaw(msg.toMsg(), cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a JSON data structure across the wire to the remote end.
|
||||
*
|
||||
* @param {Object} data
|
||||
* An object that can be marshaled into a JSON data structure.
|
||||
* @param {Function} cb
|
||||
* Callback to be invoked when a response for the request is received.
|
||||
*
|
||||
* @return {?}
|
||||
* The return value from the passed in callback.
|
||||
*/
|
||||
TcpSync.prototype.sendRaw = function(data, cb) {
|
||||
debug('write', data);
|
||||
this.sockit.write(wire.stringify(data));
|
||||
return cb(this._readResponse());
|
||||
};
|
||||
|
||||
TcpSync.prototype.close = function() {
|
||||
this.sockit.close();
|
||||
};
|
||||
|
||||
module.exports = TcpSync;
|
||||
106
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/drivers/tcp.js
Executable file
106
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/drivers/tcp.js
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
'use strict';
|
||||
var Abstract = require('./abstract');
|
||||
var CommandStream = require('../../marionette/command-stream');
|
||||
var debug = require('debug')('marionette:tcp');
|
||||
var net = require('net');
|
||||
var retrySocket = require('socket-retry-connect');
|
||||
|
||||
/** TCP **/
|
||||
Tcp.Socket = net.Socket;
|
||||
|
||||
/**
|
||||
* NodeJS only tcp socket driver for marionette.
|
||||
* See {{#crossLink "Marionette.Drivers.MozTcp"}}{{/crossLink}}
|
||||
* for the gecko/xpcom vesion of this driver.
|
||||
*
|
||||
* @class Marionette.Drivers.Tcp
|
||||
* @extends Marionette.Drivers.Abstract
|
||||
* @constructor
|
||||
* @param {Options} options connection options.
|
||||
* @param {String} [options.host="localhost"] host.
|
||||
* @param {Numeric} [options.port="2828"] port.
|
||||
*/
|
||||
function Tcp(options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
Abstract.call(this, options);
|
||||
|
||||
/**
|
||||
* The unique identifier of the current connection.
|
||||
*
|
||||
* @property connectionId
|
||||
* @type Number
|
||||
*/
|
||||
this.connectionId = 0;
|
||||
|
||||
/**
|
||||
* The remote hostname.
|
||||
*
|
||||
* @property host
|
||||
* @type String
|
||||
*/
|
||||
this.host = options.host || 'localhost';
|
||||
|
||||
/**
|
||||
* The remote port number.
|
||||
*
|
||||
* @property port
|
||||
* @type Number
|
||||
*/
|
||||
this.port = options.port || 2828;
|
||||
}
|
||||
|
||||
Tcp.prototype = Object.create(Abstract.prototype);
|
||||
|
||||
/**
|
||||
* Sends a command to the server.
|
||||
*
|
||||
* @param {Object} cmd remote marionette command.
|
||||
*/
|
||||
Tcp.prototype._sendCommand = function _sendCommand(cmd) {
|
||||
this.client.send(cmd);
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens TCP socket for marionette client.
|
||||
*/
|
||||
Tcp.prototype._connect = function connect() {
|
||||
var options = {
|
||||
port: this.port,
|
||||
host: this.host,
|
||||
tries: this.tries
|
||||
};
|
||||
|
||||
retrySocket.waitForSocket(options, function(err, socket) {
|
||||
debug('got socket starting command stream');
|
||||
this.socket = socket;
|
||||
this.client = new CommandStream(this.socket);
|
||||
this.client.on('command', this._onClientCommand.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Receives command from server.
|
||||
*
|
||||
* @param {Object} data response from marionette server.
|
||||
*/
|
||||
Tcp.prototype._onClientCommand = function(data) {
|
||||
this._onDeviceResponse({
|
||||
id: this.connectionId,
|
||||
response: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes connection to marionette.
|
||||
*/
|
||||
Tcp.prototype._close = function close() {
|
||||
if (this.socket && this.socket.destroy) {
|
||||
this.socket.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
/** export */
|
||||
module.exports = exports = Tcp;
|
||||
361
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/element.js
Executable file
361
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/element.js
Executable file
|
|
@ -0,0 +1,361 @@
|
|||
/* global Marionette */
|
||||
/**
|
||||
@namespace
|
||||
*/
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Creates an element reference
|
||||
* based on an id and a client instance.
|
||||
* You should never need to manually create
|
||||
* an instance of element.
|
||||
*
|
||||
* Use {{#crossLink "Marionette.Client/findElement"}}{{/crossLink}} or
|
||||
* {{#crossLink "Marionette.Client/findElements"}}{{/crossLink}} to create
|
||||
* instance(s) of this class.
|
||||
*
|
||||
* @class Marionette.Element
|
||||
* @param {String} id id of element.
|
||||
* @param {Marionette.Client} client client instance.
|
||||
*/
|
||||
function Element(id, client) {
|
||||
this.id = id;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
Element.prototype = {
|
||||
/**
|
||||
* Sends remote command processes the result.
|
||||
* Appends element id to each command.
|
||||
*
|
||||
* @method _sendCommand
|
||||
* @chainable
|
||||
* @private
|
||||
* @param {Object} command marionette request.
|
||||
* @param {Function} callback callback function receives the result of
|
||||
* response[key] as its first argument.
|
||||
* @param {String} key key in the response to pass to callback.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
_sendCommand: function(command, callback, key) {
|
||||
if (!command.parameters) {
|
||||
command.parameters = {};
|
||||
}
|
||||
if (typeof command.parameters.id == 'undefined') {
|
||||
command.parameters.id = this.id;
|
||||
}
|
||||
|
||||
var isSync = this.client.isSync;
|
||||
var result = this.client._sendCommand(command, callback, key);
|
||||
|
||||
if (isSync)
|
||||
return result;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds a single child of this element.
|
||||
*
|
||||
* @method findElement
|
||||
* @param {String} query search string.
|
||||
* @param {String} method search method.
|
||||
* @param {Function} callback element callback.
|
||||
* @return {Object} self.
|
||||
*/
|
||||
findElement: function findElement(query, method, callback) {
|
||||
var result = this.client.findElement(query, method, this.id, callback);
|
||||
|
||||
if (this.client.isSync) {
|
||||
return result;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds a all children of this element that match a pattern.
|
||||
*
|
||||
* @method findElements
|
||||
* @param {String} query search string.
|
||||
* @param {String} method search method.
|
||||
* @param {Function} callback element callback.
|
||||
* @return {Array} elements matched the query string.
|
||||
*/
|
||||
findElements: function findElement(query, method, callback) {
|
||||
var result = this.client.findElements(query, method, this.id, callback);
|
||||
if (this.client.isSync) {
|
||||
return result;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shortcut method to execute
|
||||
* a function with this element as first argument.
|
||||
* @method scriptWith
|
||||
* @param {Function|String} script remote script.
|
||||
* @param {Array} [args] optional arguments for script.
|
||||
* @param {Function} callback callback when script completes.
|
||||
* @return {String|Number} return value of the script.
|
||||
*/
|
||||
scriptWith: function scriptWith(script, args, callback) {
|
||||
if (!Array.isArray(args)) {
|
||||
callback = args;
|
||||
args = [];
|
||||
}
|
||||
return this.client.executeScript(script, [this].concat(args), callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks to see if two elements are equal
|
||||
*
|
||||
* @method equals
|
||||
* @param {Marionette.Element} element element to test.
|
||||
* @return {Boolean} true when equal.
|
||||
*/
|
||||
equals: function equals(element) {
|
||||
if (!(element instanceof Element)) {
|
||||
throw new Error('must pass an element');
|
||||
}
|
||||
|
||||
return this.id === element.id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets attribute value for element.
|
||||
*
|
||||
* @method getAttribute
|
||||
* @param {String} attr attribtue name.
|
||||
* @param {Function} callback gets called with attribute's value.
|
||||
* @return {String} the value of the Attribute.
|
||||
*/
|
||||
getAttribute: function getAttribute(attr, callback) {
|
||||
var body = {
|
||||
name: 'getElementAttribute',
|
||||
parameters: {
|
||||
name: attr,
|
||||
},
|
||||
};
|
||||
|
||||
return this._sendCommand(body, callback, 'value');
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends typing event keys to element.
|
||||
*
|
||||
*
|
||||
* @method sendKeys
|
||||
* @param {String|Array} input message to type.
|
||||
* @param {Function} callback boolean success.
|
||||
* @return {Object} self.
|
||||
*/
|
||||
sendKeys: function sendKeys(input, callback) {
|
||||
if (!Array.isArray(input)) {
|
||||
input = [input];
|
||||
}
|
||||
var body = {
|
||||
name: 'sendKeysToElement',
|
||||
parameters: {
|
||||
value: input,
|
||||
},
|
||||
};
|
||||
return this._sendCommand(body, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clicks element.
|
||||
*
|
||||
* @method click
|
||||
* @param {Function} callback boolean result.
|
||||
* @return {Object} self.
|
||||
*/
|
||||
click: function click(callback) {
|
||||
var body = {name: 'clickElement'};
|
||||
return this._sendCommand(body, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets text of element
|
||||
*
|
||||
* @method text
|
||||
* @param {Function} callback text of element.
|
||||
* @return {String} text of element.
|
||||
*/
|
||||
text: function text(callback) {
|
||||
var body = {name: 'getElementText'};
|
||||
return this._sendCommand(body, callback, 'value');
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns tag name of element.
|
||||
*
|
||||
* @method tagName
|
||||
* @param {Function} callback node style [err, tagName].
|
||||
* @return {String} tag name of element.
|
||||
*/
|
||||
tagName: function tagName(callback) {
|
||||
var body = {name: 'getElementTagName'};
|
||||
return this._sendCommand(body, callback, 'value');
|
||||
},
|
||||
|
||||
/**
|
||||
* Taps an element at given x and y coordinates.
|
||||
* If no offsets are given, it will be tapped at the center of the element.
|
||||
*
|
||||
* @method tap
|
||||
* @param {Number} [x] offset for the tap.
|
||||
* @param {Number} [y] offset for the tap.
|
||||
* @param {Function} [callback] [Error err]
|
||||
* @return {object} self
|
||||
*/
|
||||
tap: function(x, y, callback) {
|
||||
var body = {
|
||||
name: 'singleTap',
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
if (typeof x == 'number') {
|
||||
body.parameters.x = x;
|
||||
}
|
||||
|
||||
if (typeof y == 'number') {
|
||||
body.parameters.y = y;
|
||||
}
|
||||
|
||||
return this._sendCommand(body, callback, 'value');
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears element.
|
||||
*
|
||||
* @method clear
|
||||
* @param {Function} callback value of element.
|
||||
* @return {Object} self.
|
||||
*/
|
||||
clear: function clear(callback) {
|
||||
var body = {name: 'clearElement'};
|
||||
return this._sendCommand(body, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if element is selected.
|
||||
*
|
||||
* @method selected
|
||||
* @param {Function} callback boolean argument.
|
||||
* @return {Object} self.
|
||||
*/
|
||||
selected: function selected(callback) {
|
||||
var body = {name: 'isElementSelected'};
|
||||
return this._sendCommand(body, callback, 'value');
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if element is enabled.
|
||||
*
|
||||
* @method enabled
|
||||
* @param {Function} callback boolean argument.
|
||||
* @return {Object} self.
|
||||
*/
|
||||
enabled: function enabled(callback) {
|
||||
var body = {name: 'isElementEnabled'};
|
||||
return this._sendCommand(body, callback, 'value');
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if element is displayed.
|
||||
*
|
||||
* @method displayed
|
||||
* @param {Function} callback boolean argument.
|
||||
* @return {Object} self.
|
||||
*/
|
||||
displayed: function displayed(callback) {
|
||||
var body = {name: 'isElementDisplayed'};
|
||||
return this._sendCommand(body, callback, 'value');
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the size of a given element.
|
||||
*
|
||||
* The returned size will be in the following format:
|
||||
*
|
||||
* // returned in callback or result of this call in the sync driver.
|
||||
* {
|
||||
* width: Number,
|
||||
* height: Number
|
||||
* }
|
||||
*
|
||||
* @method size
|
||||
* @param {Function} callback [Error err, Object size].
|
||||
* @return {Object} self.
|
||||
*/
|
||||
size: function size(callback) {
|
||||
var body = {name: 'getElementRect'};
|
||||
return this._sendCommand(
|
||||
body, callback, this.client.protocol == 1 ? 'value' : undefined);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the dictionary with x and y location of an element.
|
||||
*
|
||||
* The returned location will be in the following format:
|
||||
*
|
||||
* // returned in callback or result of this call in the sync driver.
|
||||
* {
|
||||
* x: Number,
|
||||
* y: Number
|
||||
* }
|
||||
*
|
||||
* @method location
|
||||
* @param {Function} callback [Error err, Object location].
|
||||
* @return {Object} self.
|
||||
*/
|
||||
location: function location(callback) {
|
||||
var body = {name: 'getElementRect'};
|
||||
return this._sendCommand(
|
||||
body, callback, this.client.protocol == 1 ? 'value' : undefined);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the object with:
|
||||
* x and y location of the element
|
||||
* height and width of the element
|
||||
*
|
||||
* @method rect
|
||||
* @param {Function} callback [Error err, Object rect]
|
||||
* @return {Object} self.
|
||||
*/
|
||||
rect: function rect (callback) {
|
||||
var body = {name: 'getElementRect'};
|
||||
return this._sendCommand(
|
||||
body, callback, this.client.protocol == 1 ? 'value' : undefined);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the value of the specified CSS property name.
|
||||
*
|
||||
* @method cssProperty
|
||||
* @param {String} property css property.
|
||||
* @param {Function} callback [Error err, String value].
|
||||
* @return {Object} self.
|
||||
*/
|
||||
cssProperty: function cssProperty(property, callback) {
|
||||
var body = {
|
||||
name: 'getElementValueOfCssProperty',
|
||||
parameters: {
|
||||
propertyName: property,
|
||||
},
|
||||
};
|
||||
return this._sendCommand(body, callback, 'value');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Element;
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette('element'), Marionette] :
|
||||
[module, require('../../lib/marionette/marionette')]
|
||||
));
|
||||
131
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/error.js
Executable file
131
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/error.js
Executable file
|
|
@ -0,0 +1,131 @@
|
|||
/* jshint -W069 */
|
||||
/* global Marionette */
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
var ERRORS = Object.freeze({
|
||||
'element not accessible': 'ElementNotAccessibleError',
|
||||
'element not selectable': 'ElementIsNotSelectable',
|
||||
'element not visible': 'ElementNotVisible',
|
||||
'invalid cookie domain': 'InvalidCookieDomain',
|
||||
'invalid element coordinates': 'InvalidElementCoordinates',
|
||||
'invalid element state': 'InvalidElementState',
|
||||
'invalid selector': 'InvalidSelector',
|
||||
'invalid xpath selector': 'XPathLookupError',
|
||||
'javascript error': 'JavaScriptError',
|
||||
'no such alert': 'NoAlertOpenError',
|
||||
'no such element': 'NoSuchElement',
|
||||
'no such frame': 'NoSuchFrame',
|
||||
'no such window': 'NoSuchWindow',
|
||||
'script timeout': 'ScriptTimeout',
|
||||
'stale element reference': 'StaleElementReference',
|
||||
'timeout': 'Timeout',
|
||||
'unable to set cookie': 'UnableToSetCookie',
|
||||
'unexpected alert open': 'UnexpectedAlertOpen',
|
||||
'unknown command': 'UnknownCommand',
|
||||
'unknown error': 'UnknownError',
|
||||
'webdriver error': 'GenericError',
|
||||
});
|
||||
|
||||
var CODES = Object.freeze({
|
||||
7: ERRORS['no such element'],
|
||||
8: ERRORS['no such frame'],
|
||||
9: ERRORS['unknown command'],
|
||||
10: ERRORS['stale element reference'],
|
||||
11: ERRORS['element not visible'],
|
||||
12: ERRORS['invalid element state'],
|
||||
13: ERRORS['unknown error'],
|
||||
15: ERRORS['element not selectable'],
|
||||
17: ERRORS['javascript error'],
|
||||
19: ERRORS['invalid xpath selector'],
|
||||
21: ERRORS['timeout'],
|
||||
23: ERRORS['no such window'],
|
||||
24: ERRORS['invalid cookie domain'],
|
||||
25: ERRORS['unable to set cookie'],
|
||||
26: ERRORS['unexpected alert open'],
|
||||
27: ERRORS['no such alert'],
|
||||
28: ERRORS['script timeout'],
|
||||
29: ERRORS['invalid element coordinates'],
|
||||
32: ERRORS['invalid selector'],
|
||||
56: ERRORS['element not accessible'],
|
||||
500: ERRORS['webdriver error']
|
||||
});
|
||||
|
||||
var DEFAULT_ERROR = ERRORS['webdriver error'];
|
||||
|
||||
/**
|
||||
* Returns an error object given an error object from the Marionette client.
|
||||
*
|
||||
* Codes are from:
|
||||
* http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
|
||||
*
|
||||
* Status strings are from:
|
||||
* https://w3c.github.io/webdriver/webdriver-spec.html#handling-errors
|
||||
*
|
||||
* The expected input for protocol version 2 and higher:
|
||||
*
|
||||
* {
|
||||
* error: "javascript error",
|
||||
* message: "Something",
|
||||
* stacktrace: "wentwrong@line",
|
||||
* }
|
||||
*
|
||||
* The expected input for protocol version 1:
|
||||
*
|
||||
* {
|
||||
* message: "Something",
|
||||
* stacktrace: "wentwrong@line",
|
||||
* status: "javascript error",
|
||||
* }
|
||||
*
|
||||
* @param {Client} client which the error originates from.
|
||||
* @param {Object} options for error (see above).
|
||||
*/
|
||||
function MarionetteError(client, options) {
|
||||
var error = DEFAULT_ERROR;
|
||||
if (options.status in CODES) {
|
||||
error = CODES[options.status];
|
||||
} else if (options.status in ERRORS) {
|
||||
error = ERRORS[options.status];
|
||||
} else if (options.error in ERRORS) {
|
||||
error = ERRORS[options.error];
|
||||
}
|
||||
|
||||
this.client = client;
|
||||
this.type = error;
|
||||
this.name = this.type;
|
||||
|
||||
this.message = this.name;
|
||||
if (options.message)
|
||||
this.message += ': ' + options.message;
|
||||
this.message += '\nRemote Stack:\n';
|
||||
this.message += options.stacktrace || '<none>';
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
// elsewhere we do horrible try/catch
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
this.stack = e.stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MarionetteError.prototype = Object.create(Error.prototype, {
|
||||
constructor: {
|
||||
value: Error
|
||||
}
|
||||
});
|
||||
|
||||
MarionetteError.ERRORS = ERRORS;
|
||||
MarionetteError.CODES = CODES;
|
||||
module.exports = MarionetteError;
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette('error'), Marionette] :
|
||||
[module, require('./marionette')]
|
||||
));
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/* global Marionette */
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
function deepmerge(d) {
|
||||
for (var i = 1; i < arguments.length; ++i) {
|
||||
var o = arguments[i];
|
||||
for (var k in o) {
|
||||
var v = o[k];
|
||||
if (typeof v == 'object') {
|
||||
d[k] = deepmerge(d[k], v);
|
||||
} else {
|
||||
if (Array.isArray(d)) {
|
||||
if (d.indexOf(v) < 0) {
|
||||
d.push(v);
|
||||
}
|
||||
} else {
|
||||
d[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function cmd(defaults) {
|
||||
return function(override) {
|
||||
if (typeof(override) === 'undefined') {
|
||||
override = {};
|
||||
}
|
||||
return deepmerge(defaults, override);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
connectProto1: cmd({from: 'root', applicationType: 'gecko', traits: []}),
|
||||
connectProto2: cmd({applicationType: 'gecko', marionetteProtocol: 2}),
|
||||
|
||||
getMarionetteID: cmd({type: 'getMarionetteID'}),
|
||||
getMarionetteIDResponse: cmd({from: 'root', id: 'con1'}),
|
||||
|
||||
newSession: cmd({type: 'newSession'}),
|
||||
newSessionResponseProto1: cmd({
|
||||
from: 'actor',
|
||||
value: 'b2g-7',
|
||||
}),
|
||||
newSessionResponseProto2: cmd({
|
||||
sessionId: '{2dddca75-7f78-415f-a7de-63eb2bc9412b}',
|
||||
capabilities: {browserName: 'firefox'},
|
||||
}),
|
||||
|
||||
getWindow: cmd({name: 'getWindow'}),
|
||||
getWindowResponse: cmd({value: '3-b2g'}),
|
||||
|
||||
getWindows: cmd({name: 'getWindows'}),
|
||||
getWindowsResponseProto1: cmd({from: 'actor', value: ['1-b2g', '2-b2g']}),
|
||||
getWindowsResponseProto2: cmd(['1-b2g', '2-b2g']),
|
||||
|
||||
getUrl: cmd({name: 'getUrl'}),
|
||||
getUrlResponse: cmd({value: 'http://localhost/'}),
|
||||
|
||||
getLogsResponseProto1: cmd({
|
||||
from: 'actor',
|
||||
value: [
|
||||
['debug', 'wow', 'Fri Apr 27 2012 11:00:32 GMT-0700 (PDT)'],
|
||||
],
|
||||
}),
|
||||
getLogsResponseProto2: cmd([
|
||||
['debug', 'wow', 'Fri Apr 27 2012 11:00:32 GMT-0700 (PDT)'],
|
||||
]),
|
||||
|
||||
screenshotResponse: cmd({
|
||||
value: 'data:image/png;base64,iVBOgoAAAANSUhEUgAAAUAAAAHMCAYAAACk4nEJA',
|
||||
}),
|
||||
|
||||
elementEqualsResponse: cmd({value: false}),
|
||||
|
||||
findElementResponse: cmd(
|
||||
{value: {ELEMENT: '{8056e6f7-2213-41d4-9db6-ad77ac7a96d3}'}}),
|
||||
findElementsResponseProto1: cmd({
|
||||
from: 'actor',
|
||||
value: [
|
||||
{ELEMENT: '{46982c9e-bb0c-486e-a514-d1cf20b42641}'},
|
||||
{ELEMENT: '{585e70ee-e088-43cc-9b07-a4b078c1b8db}'},
|
||||
],
|
||||
}),
|
||||
findElementsResponseProto2: cmd([
|
||||
{ELEMENT: '{46982c9e-bb0c-486e-a514-d1cf20b42641}'},
|
||||
{ELEMENT: '{585e70ee-e088-43cc-9b07-a4b078c1b8db}'},
|
||||
]),
|
||||
|
||||
numberError: cmd({
|
||||
from: 'actor',
|
||||
error: {
|
||||
message: 'you fail',
|
||||
status: 7,
|
||||
stacktrace: 'fail@url\nother:300',
|
||||
},
|
||||
}),
|
||||
stringError: cmd({
|
||||
from: 'actor',
|
||||
error: {
|
||||
message: 'you fail',
|
||||
status: 'no such element',
|
||||
stacktrace: 'fail@url\nother:300',
|
||||
},
|
||||
}),
|
||||
modernError: cmd({
|
||||
error: 'no such element',
|
||||
message: 'you fail',
|
||||
stacktrace: 'fail@url\nother:300',
|
||||
}),
|
||||
|
||||
//valueProto1: cmd({from: 'actor', value: 'zomg'}),
|
||||
value: cmd({value: 'zomg'}),
|
||||
|
||||
capabilities: cmd({capabilities: {browserName: 'firefox'}}),
|
||||
|
||||
//okProto1: cmd({from: 'actor', ok: true}),
|
||||
ok: cmd({}),
|
||||
};
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette('example-commands'), Marionette] :
|
||||
[module, require('./marionette')]
|
||||
));
|
||||
20
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/index.js
Executable file
20
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/index.js
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
/* global Marionette */
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
var exports = module.exports;
|
||||
|
||||
exports.Element = ns.require('element');
|
||||
exports.Error = ns.require('error');
|
||||
exports.Client = ns.require('client');
|
||||
exports.Drivers = ns.require('drivers');
|
||||
exports.CommandStream = ns.require('command-stream');
|
||||
exports.Actions = ns.require('actions');
|
||||
exports.MultiActions = ns.require('multi-actions');
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette, Marionette] :
|
||||
[module, require('./marionette')]
|
||||
));
|
||||
101
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/marionette.js
Executable file
101
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/marionette.js
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
(function(global, module) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Define a list of paths
|
||||
* this will only be used in the browser.
|
||||
*/
|
||||
var paths = {};
|
||||
|
||||
|
||||
/**
|
||||
* Exports object is a shim
|
||||
* we use in the browser to
|
||||
* create an object that will behave much
|
||||
* like module.exports
|
||||
*/
|
||||
function Exports(path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
Exports.prototype = {
|
||||
|
||||
/**
|
||||
* Unified require between browser/node.
|
||||
* Path is relative to this file so you
|
||||
* will want to use it like this from any depth.
|
||||
*
|
||||
*
|
||||
* var Leaf = ns.require('sub/leaf');
|
||||
*
|
||||
*
|
||||
* @param {String} path path lookup relative to this file.
|
||||
*/
|
||||
require: function exportRequire(path) {
|
||||
if (typeof(window) === 'undefined') {
|
||||
return require(require('path').join(__dirname, path));
|
||||
} else {
|
||||
return paths[path];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Maps exports to a file path.
|
||||
*/
|
||||
set exports(val) {
|
||||
return (paths[this.path] = val);
|
||||
},
|
||||
|
||||
get exports() {
|
||||
return paths[this.path];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Module object constructor.
|
||||
*
|
||||
*
|
||||
* var module = Module('sub/leaf');
|
||||
* module.exports = function Leaf(){}
|
||||
*
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} path file path.
|
||||
*/
|
||||
function Module(path) {
|
||||
return new Exports(path);
|
||||
}
|
||||
|
||||
Module.require = Exports.prototype.require;
|
||||
Module.exports = Module;
|
||||
Module._paths = paths;
|
||||
|
||||
|
||||
/**
|
||||
* Reference self as exports
|
||||
* which also happens to be the constructor
|
||||
* so you can assign items to the namespace:
|
||||
*
|
||||
* //assign to Module.X
|
||||
* //assume module.exports is Module
|
||||
* module.exports.X = Foo; //Module.X === Foo;
|
||||
* Module.exports('foo'); //creates module.exports object.
|
||||
*
|
||||
*/
|
||||
module.exports = Module;
|
||||
|
||||
/**
|
||||
* In the browser assign
|
||||
* to a global namespace
|
||||
* obviously 'Module' would
|
||||
* be whatever your global namespace is.
|
||||
*/
|
||||
if (typeof(window) !== 'undefined')
|
||||
window.Marionette = Module;
|
||||
|
||||
}(
|
||||
this,
|
||||
(typeof(module) === 'undefined') ?
|
||||
{} :
|
||||
module
|
||||
));
|
||||
67
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/message.js
Executable file
67
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/message.js
Executable file
|
|
@ -0,0 +1,67 @@
|
|||
/* 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';
|
||||
|
||||
function Command(body) {
|
||||
this.id = null;
|
||||
this.name = body.name;
|
||||
this.params = body.parameters || {};
|
||||
}
|
||||
|
||||
Command.prototype.toString = function() {
|
||||
return 'Command ' + JSON.stringify({
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
params: this.params
|
||||
});
|
||||
};
|
||||
|
||||
Command.prototype.toMsg = function() {
|
||||
if (!this.id) {
|
||||
throw new RangeError('Command ID must be specified');
|
||||
}
|
||||
return [Command.TYPE, this.id, this.name, this.params];
|
||||
};
|
||||
|
||||
Command.fromMsg = function(msg) {
|
||||
return new Command(msg[1], msg[2], msg[3]);
|
||||
};
|
||||
|
||||
Command.TYPE = 0;
|
||||
|
||||
function Response(msgId, obj) {
|
||||
this.id = msgId;
|
||||
this.error = null;
|
||||
this.result = null;
|
||||
|
||||
if (obj instanceof Error) {
|
||||
this.error = obj;
|
||||
} else {
|
||||
this.result = obj;
|
||||
}
|
||||
}
|
||||
|
||||
Response.prototype.toString = function() {
|
||||
return 'Response ' + JSON.stringify({
|
||||
id: this.id,
|
||||
error: this.error,
|
||||
result: this.result
|
||||
});
|
||||
};
|
||||
|
||||
Response.prototype.toMsg = function() {
|
||||
return [Response.TYPE, this.id, this.error, this.result];
|
||||
};
|
||||
|
||||
Response.fromMsg = function(msg) {
|
||||
return new Response(msg[1], msg[2] || msg[3]);
|
||||
};
|
||||
|
||||
Response.TYPE = 1;
|
||||
|
||||
module.exports = {
|
||||
Command: Command,
|
||||
Response: Response,
|
||||
};
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/* global Marionette */
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* For a multifinger gesture, we can use MultiActions.
|
||||
* For example, one finger to hold down
|
||||
* while the other finger moves from one element to another.
|
||||
*
|
||||
* @class Marionette.MultiActions
|
||||
* @param {Marionette.Client} context of a client.
|
||||
*/
|
||||
function MultiActions(client) {
|
||||
this.client = client;
|
||||
this.multiActions = [];
|
||||
this.maxLength = 0;
|
||||
}
|
||||
|
||||
MultiActions.prototype = {
|
||||
|
||||
/**
|
||||
* Adds a action chain for execution.
|
||||
*
|
||||
* @method add
|
||||
* @param {Object} action {{#crossLink "Marionette.Actions"}}{{/crossLink}}.
|
||||
*
|
||||
* @return {Object} self.
|
||||
*/
|
||||
add: function add(action) {
|
||||
this.multiActions.push(action.actionChain);
|
||||
if (action.actionChain.length > this.maxLength) {
|
||||
this.maxLength = action.actionChain.length;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send multiple action chains that have been added
|
||||
* to the server side for execution.
|
||||
*
|
||||
* @method perform
|
||||
* @param {Function} callback callback when the perform completes.
|
||||
*/
|
||||
perform: function perform(callback) {
|
||||
var cmd = {
|
||||
type: 'multiAction',
|
||||
value: this.multiActions,
|
||||
max_length: this.maxLength
|
||||
};
|
||||
|
||||
this.client._sendCommand(cmd, callback);
|
||||
this.multiActions = [];
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = MultiActions;
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette('multi-actions'), Marionette] :
|
||||
[module, require('../../lib/marionette/marionette')]
|
||||
));
|
||||
212
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/responder.js
Executable file
212
gaia/tests/jsmarionette/client/marionette-client/lib/marionette/responder.js
Executable file
|
|
@ -0,0 +1,212 @@
|
|||
/* global Marionette */
|
||||
(function(module, ns) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param {Object} list of events to add onto responder.
|
||||
*/
|
||||
function Responder(events) {
|
||||
this._$events = {};
|
||||
|
||||
if (typeof(events) !== 'undefined') {
|
||||
this.addEventListener(events);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringifies request to websocket
|
||||
*
|
||||
*
|
||||
* @param {String} command command name.
|
||||
* @param {Object} data object to be sent over the wire.
|
||||
* @return {String} json object.
|
||||
*/
|
||||
Responder.stringify = function stringify(command, data) {
|
||||
return JSON.stringify([command, data]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses request from WebSocket.
|
||||
*
|
||||
* @param {String} json json string to translate.
|
||||
* @return {Object} ex: { event: 'test', data: {} }.
|
||||
*/
|
||||
Responder.parse = function parse(json) {
|
||||
var data;
|
||||
try {
|
||||
data = (json.forEach) ? json : JSON.parse(json);
|
||||
} catch (e) {
|
||||
throw new Error('Could not parse json: "' + json + '"');
|
||||
}
|
||||
|
||||
return {event: data[0], data: data[1]};
|
||||
};
|
||||
|
||||
Responder.prototype = {
|
||||
parse: Responder.parse,
|
||||
stringify: Responder.stringify,
|
||||
|
||||
/**
|
||||
* Events on this instance
|
||||
*
|
||||
* @type Object
|
||||
*/
|
||||
_$events: null,
|
||||
|
||||
/**
|
||||
* Recieves json string event and dispatches an event.
|
||||
*
|
||||
* @param {String|Object} json data object to respond to.
|
||||
* @param {String} json.event event to emit.
|
||||
* @param {Object} json.data data to emit with event.
|
||||
* @param {Object} [params] option number of params to pass to emit.
|
||||
* @return {Object} result of WebSocketCommon.parse.
|
||||
*/
|
||||
respond: function respond(json) {
|
||||
var event = Responder.parse(json),
|
||||
args = Array.prototype.slice.call(arguments).slice(1);
|
||||
|
||||
args.unshift(event.data);
|
||||
args.unshift(event.event);
|
||||
|
||||
this.emit.apply(this, args);
|
||||
|
||||
return event;
|
||||
},
|
||||
|
||||
//TODO: Extract event emitter logic
|
||||
|
||||
/**
|
||||
* Adds an event listener to this object.
|
||||
*
|
||||
*
|
||||
* @param {String} type event name.
|
||||
* @param {Function} callback event callback.
|
||||
*/
|
||||
addEventListener: function addEventListener(type, callback) {
|
||||
var event;
|
||||
|
||||
if (typeof(callback) === 'undefined' && typeof(type) === 'object') {
|
||||
for (event in type) {
|
||||
if (type.hasOwnProperty(event)) {
|
||||
this.addEventListener(event, type[event]);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!(type in this._$events)) {
|
||||
this._$events[type] = [];
|
||||
}
|
||||
|
||||
this._$events[type].push(callback);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an event listener which will
|
||||
* only fire once and then remove itself.
|
||||
*
|
||||
*
|
||||
* @param {String} type event name.
|
||||
* @param {Function} callback fired when event is emitted.
|
||||
*/
|
||||
once: function once(type, callback) {
|
||||
var self = this;
|
||||
function onceCb() {
|
||||
self.removeEventListener(type, onceCb);
|
||||
callback.apply(this, arguments);
|
||||
}
|
||||
|
||||
this.addEventListener(type, onceCb);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Emits an event.
|
||||
*
|
||||
* Accepts any number of additional arguments to pass unto
|
||||
* event listener.
|
||||
*
|
||||
* @param {String} eventName name of the event to emit.
|
||||
* @param {Object} [arguments] additional arguments to pass.
|
||||
*/
|
||||
emit: function emit() {
|
||||
var args = Array.prototype.slice.call(arguments),
|
||||
event = args.shift(),
|
||||
eventList,
|
||||
self = this;
|
||||
|
||||
if (event in this._$events) {
|
||||
eventList = this._$events[event];
|
||||
|
||||
eventList.forEach(function(callback) {
|
||||
callback.apply(self, args);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all event listeners for a given event type
|
||||
*
|
||||
*
|
||||
* @param {String} event event type to remove.
|
||||
*/
|
||||
removeAllEventListeners: function removeAllEventListeners(name) {
|
||||
if (name in this._$events) {
|
||||
//reuse array
|
||||
this._$events[name].length = 0;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a single event listener from a given event type
|
||||
* and callback function.
|
||||
*
|
||||
*
|
||||
* @param {String} eventName event name.
|
||||
* @param {Function} callback same instance of event handler.
|
||||
*/
|
||||
removeEventListener: function removeEventListener(name, callback) {
|
||||
var i, length, events;
|
||||
|
||||
if (!(name in this._$events)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
events = this._$events[name];
|
||||
|
||||
for (i = 0, length = events.length; i < length; i++) {
|
||||
if (events[i] && events[i] === callback) {
|
||||
events.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Responder.prototype.on = Responder.prototype.addEventListener;
|
||||
Responder.prototype.removeListener = Responder.prototype.removeEventListener;
|
||||
|
||||
module.exports = Responder;
|
||||
|
||||
}.apply(
|
||||
this,
|
||||
(this.Marionette) ?
|
||||
[Marionette('responder'), Marionette] :
|
||||
[module, require('../../lib/marionette/marionette')]
|
||||
));
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
'use strict';
|
||||
var CommandStream = require('../marionette/command-stream');
|
||||
var debug = require('debug')('marionette:connection-manager');
|
||||
|
||||
/**
|
||||
* Socket class
|
||||
*/
|
||||
ConnectionManger.Socket = require('net').Socket;
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @constructor
|
||||
*/
|
||||
function ConnectionManger() {
|
||||
this.connections = {};
|
||||
}
|
||||
|
||||
ConnectionManger.prototype = {
|
||||
|
||||
currentId: 0,
|
||||
|
||||
defaultPort: 2828,
|
||||
|
||||
/**
|
||||
* Opens a connection and returns the id and connection
|
||||
*
|
||||
* manager.open()
|
||||
* // => {id: 0, connection: new CommandStream() }
|
||||
*
|
||||
* @this
|
||||
* @param {Numeric} port port number.
|
||||
* @return {Object} { id: connectionId, connection: commandStream }.
|
||||
*/
|
||||
open: function open(port) {
|
||||
var socket,
|
||||
stream,
|
||||
id = this.currentId++,
|
||||
closeFn = this.close.bind(this, id);
|
||||
|
||||
port = port || this.defaultPort;
|
||||
|
||||
socket = new ConnectionManger.Socket();
|
||||
socket.on('close', closeFn);
|
||||
socket.on('end', closeFn);
|
||||
|
||||
stream = new CommandStream(socket);
|
||||
|
||||
socket.on('error', function() {
|
||||
debug(arguments);
|
||||
});
|
||||
|
||||
socket.on('end', function() {
|
||||
debug('socket close');
|
||||
});
|
||||
|
||||
socket.connect(port);
|
||||
this.connections[id] = stream;
|
||||
|
||||
return { id: id, connection: stream };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns connection by id
|
||||
*
|
||||
* @this
|
||||
* @param {Number} id connection id.
|
||||
* @return {CommandStream} command stream for connection id.
|
||||
*/
|
||||
get: function get(id) {
|
||||
return this.connections[id];
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes and removes a connection.
|
||||
*
|
||||
* @this
|
||||
* @param {Number} id connection id to close.
|
||||
*/
|
||||
close: function close(id) {
|
||||
delete this.connections[id];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = exports = ConnectionManger;
|
||||
Loading…
Reference in a new issue