]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.rfb.js
Don't fake open events in Websock
[mirror_novnc.git] / tests / test.rfb.js
CommitLineData
2b5f94fa 1const expect = chai.expect;
b1dee947 2
dfae3209
SR
3import RFB from '../core/rfb.js';
4import Websock from '../core/websock.js';
f73fdc3e
NL
5import ZStream from "../vendor/pako/lib/zlib/zstream.js";
6import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
69411b9e 7import { encodings } from '../core/encodings.js';
f73fdc3e
NL
8import { toUnsigned32bit } from '../core/util/int.js';
9import { encodeUTF8 } from '../core/util/strings.js';
8be924c9 10import KeyTable from '../core/input/keysym.js';
dfae3209
SR
11
12import FakeWebSocket from './fake.websocket.js';
dfae3209 13
885363a3 14function push8(arr, num) {
3949a095
SR
15 "use strict";
16 arr.push(num & 0xFF);
885363a3 17}
3949a095 18
885363a3 19function push16(arr, num) {
3949a095
SR
20 "use strict";
21 arr.push((num >> 8) & 0xFF,
7b536961 22 num & 0xFF);
885363a3 23}
3949a095 24
885363a3 25function push32(arr, num) {
3949a095
SR
26 "use strict";
27 arr.push((num >> 24) & 0xFF,
7b536961
PO
28 (num >> 16) & 0xFF,
29 (num >> 8) & 0xFF,
30 num & 0xFF);
885363a3 31}
3949a095 32
ce66b469
NL
33function pushString(arr, string) {
34 let utf8 = unescape(encodeURIComponent(string));
35 for (let i = 0; i < utf8.length; i++) {
36 arr.push(utf8.charCodeAt(i));
37 }
38}
39
f73fdc3e
NL
40function deflateWithSize(data) {
41 // Adds the size of the string in front before deflating
42
43 let unCompData = [];
44 unCompData.push((data.length >> 24) & 0xFF,
45 (data.length >> 16) & 0xFF,
46 (data.length >> 8) & 0xFF,
47 (data.length & 0xFF));
48
49 for (let i = 0; i < data.length; i++) {
50 unCompData.push(data.charCodeAt(i));
51 }
52
53 let strm = new ZStream();
54 let chunkSize = 1024 * 10 * 10;
55 strm.output = new Uint8Array(chunkSize);
56 deflateInit(strm, 5);
57
cfb824ed 58 /* eslint-disable camelcase */
f73fdc3e
NL
59 strm.input = unCompData;
60 strm.avail_in = strm.input.length;
61 strm.next_in = 0;
62 strm.next_out = 0;
63 strm.avail_out = chunkSize;
cfb824ed 64 /* eslint-enable camelcase */
f73fdc3e
NL
65
66 deflate(strm, 3);
67
68 return new Uint8Array(strm.output.buffer, 0, strm.next_out);
69}
70
2c5491e1 71describe('Remote Frame Buffer Protocol Client', function () {
2b5f94fa
JD
72 let clock;
73 let raf;
2f4516f2 74
b1dee947
SR
75 before(FakeWebSocket.replace);
76 after(FakeWebSocket.restore);
77
38781d93 78 before(function () {
440ec8a0 79 this.clock = clock = sinon.useFakeTimers(Date.now());
9b84f516
PO
80 // sinon doesn't support this yet
81 raf = window.requestAnimationFrame;
82 window.requestAnimationFrame = setTimeout;
38781d93
SR
83 // Use a single set of buffers instead of reallocating to
84 // speed up tests
2b5f94fa
JD
85 const sock = new Websock();
86 const _sQ = new Uint8Array(sock._sQbufferSize);
87 const rQ = new Uint8Array(sock._rQbufferSize);
38781d93 88
ea858bfa
SM
89 Websock.prototype._oldAllocateBuffers = Websock.prototype._allocateBuffers;
90 Websock.prototype._allocateBuffers = function () {
9ff86fb7 91 this._sQ = _sQ;
38781d93
SR
92 this._rQ = rQ;
93 };
94
0a6aec35
PO
95 // Avoiding printing the entire Websock buffer on errors
96 Websock.prototype.toString = function () { return "[object Websock]"; };
38781d93
SR
97 });
98
99 after(function () {
0a6aec35 100 delete Websock.prototype.toString;
38781d93 101 this.clock.restore();
9b84f516 102 window.requestAnimationFrame = raf;
38781d93
SR
103 });
104
2b5f94fa
JD
105 let container;
106 let rfbs;
bb25d3d6
PO
107
108 beforeEach(function () {
9b84f516
PO
109 // Create a container element for all RFB objects to attach to
110 container = document.createElement('div');
111 container.style.width = "100%";
112 container.style.height = "100%";
113 document.body.appendChild(container);
114
115 // And track all created RFB objects
bb25d3d6
PO
116 rfbs = [];
117 });
118 afterEach(function () {
119 // Make sure every created RFB object is properly cleaned up
120 // or they might affect subsequent tests
121 rfbs.forEach(function (rfb) {
122 rfb.disconnect();
123 expect(rfb._disconnect).to.have.been.called;
124 });
125 rfbs = [];
9b84f516
PO
126
127 document.body.removeChild(container);
128 container = null;
bb25d3d6
PO
129 });
130
00674385 131 function makeRFB(url, options) {
2f4516f2 132 url = url || 'wss://host:8675';
2b5f94fa 133 const rfb = new RFB(container, url, options);
2f4516f2
PO
134 clock.tick();
135 rfb._sock._websocket._open();
e7dec527 136 rfb._rfbConnectionState = 'connected';
bb25d3d6
PO
137 sinon.spy(rfb, "_disconnect");
138 rfbs.push(rfb);
2f4516f2
PO
139 return rfb;
140 }
b1dee947 141
2f4516f2 142 describe('Connecting/Disconnecting', function () {
f0e4908d 143 describe('#RFB (constructor)', function () {
ae3c01f7 144 let open, attach;
f0e4908d
PO
145 beforeEach(function () {
146 open = sinon.spy(Websock.prototype, 'open');
ae3c01f7 147 attach = sinon.spy(Websock.prototype, 'attach');
f0e4908d
PO
148 });
149 afterEach(function () {
150 open.restore();
ae3c01f7 151 attach.restore();
f0e4908d
PO
152 });
153
154 it('should not connect from constructor', function () {
155 new RFB(document.createElement('div'), 'wss://host:8675');
156 expect(open).to.not.have.been.called;
157 this.clock.tick(); // Flush the pending connection
b1dee947
SR
158 });
159
2f4516f2 160 it('should actually connect to the websocket', function () {
f0e4908d 161 new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
2f4516f2 162 this.clock.tick();
f0e4908d 163 expect(open).to.have.been.calledOnceWithExactly('ws://HOST:8675/PATH', []);
b1dee947 164 });
ae3c01f7 165
42100e82
PO
166 it('should report connection problems via event', function () {
167 open.restore();
168 open = sinon.stub(Websock.prototype, 'open');
169 open.throws(Error('Failure'));
170 const client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
171 let callback = sinon.spy();
172 client.addEventListener('disconnect', callback);
173 this.clock.tick();
174 expect(callback).to.have.been.calledOnce;
175 expect(callback.args[0][0].detail.clean).to.be.false;
176 });
177
ae3c01f7
PO
178 it('should handle WebSocket/RTCDataChannel objects', function () {
179 let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
180 new RFB(document.createElement('div'), sock);
181 this.clock.tick();
182 expect(open).to.not.have.been.called;
183 expect(attach).to.have.been.calledOnceWithExactly(sock);
184 });
42100e82 185
de9fc950
PO
186 it('should handle already open WebSocket/RTCDataChannel objects', function () {
187 let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
188 sock._open();
189 const client = new RFB(document.createElement('div'), sock);
190 let callback = sinon.spy();
191 client.addEventListener('disconnect', callback);
192 this.clock.tick();
193 expect(open).to.not.have.been.called;
194 expect(attach).to.have.been.calledOnceWithExactly(sock);
195 // Check if it is ready for some data
196 sock._receiveData(new Uint8Array(['R', 'F', 'B', '0', '0', '3', '0', '0', '8']));
197 expect(callback).to.not.have.been.called;
198 });
199
9376191f
PO
200 it('should refuse closed WebSocket/RTCDataChannel objects', function () {
201 let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
202 sock.readyState = WebSocket.CLOSED;
203 const client = new RFB(document.createElement('div'), sock);
204 let callback = sinon.spy();
205 client.addEventListener('disconnect', callback);
206 this.clock.tick();
207 expect(callback).to.have.been.calledOnce;
208 expect(callback.args[0][0].detail.clean).to.be.false;
209 });
210
42100e82
PO
211 it('should report attach problems via event', function () {
212 attach.restore();
213 attach = sinon.stub(Websock.prototype, 'attach');
214 attach.throws(Error('Failure'));
215 let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
216 const client = new RFB(document.createElement('div'), sock);
217 let callback = sinon.spy();
218 client.addEventListener('disconnect', callback);
219 this.clock.tick();
220 expect(callback).to.have.been.calledOnce;
221 expect(callback.args[0][0].detail.clean).to.be.false;
222 });
b1dee947
SR
223 });
224
225 describe('#disconnect', function () {
2b5f94fa 226 let client;
f0e4908d
PO
227 let close;
228
2f4516f2 229 beforeEach(function () {
00674385 230 client = makeRFB();
f0e4908d
PO
231 close = sinon.stub(Websock.prototype, "close");
232 });
233 afterEach(function () {
234 close.restore();
2f4516f2 235 });
b1dee947 236
f0e4908d
PO
237 it('should start closing WebSocket', function () {
238 let callback = sinon.spy();
239 client.addEventListener('disconnect', callback);
b1dee947 240 client.disconnect();
f0e4908d
PO
241 expect(close).to.have.been.calledOnceWithExactly();
242 expect(callback).to.not.have.been.called;
243 });
244
245 it('should send disconnect event', function () {
246 let callback = sinon.spy();
247 client.addEventListener('disconnect', callback);
248 client.disconnect();
249 close.thisValues[0]._eventHandlers.close(new CloseEvent("close", { 'code': 1000, 'reason': "", 'wasClean': true }));
250 expect(callback).to.have.been.calledOnce;
251 expect(callback.args[0][0].detail.clean).to.be.true;
252 });
253
254 it('should force disconnect if disconnecting takes too long', function () {
255 let callback = sinon.spy();
256 client.addEventListener('disconnect', callback);
257 client.disconnect();
258 this.clock.tick(3 * 1000);
259 expect(callback).to.have.been.calledOnce;
260 expect(callback.args[0][0].detail.clean).to.be.true;
261 });
262
263 it('should not fail if disconnect completes before timeout', function () {
264 let callback = sinon.spy();
265 client.addEventListener('disconnect', callback);
266 client.disconnect();
267 client._updateConnectionState('disconnecting');
268 this.clock.tick(3 * 1000 / 2);
269 close.thisValues[0]._eventHandlers.close(new CloseEvent("close", { 'code': 1000, 'reason': "", 'wasClean': true }));
270 this.clock.tick(3 * 1000 / 2 + 1);
271 expect(callback).to.have.been.calledOnce;
272 expect(callback.args[0][0].detail.clean).to.be.true;
b1dee947 273 });
155d78b3
JS
274
275 it('should unregister error event handler', function () {
276 sinon.spy(client._sock, 'off');
277 client.disconnect();
278 expect(client._sock.off).to.have.been.calledWith('error');
279 });
280
281 it('should unregister message event handler', function () {
282 sinon.spy(client._sock, 'off');
283 client.disconnect();
284 expect(client._sock.off).to.have.been.calledWith('message');
285 });
286
287 it('should unregister open event handler', function () {
288 sinon.spy(client._sock, 'off');
289 client.disconnect();
290 expect(client._sock.off).to.have.been.calledWith('open');
291 });
b1dee947
SR
292 });
293
430f00d6 294 describe('#sendCredentials', function () {
2b5f94fa 295 let client;
2f4516f2 296 beforeEach(function () {
00674385 297 client = makeRFB();
e7dec527 298 client._rfbConnectionState = 'connecting';
2f4516f2
PO
299 });
300
430f00d6
PO
301 it('should set the rfb credentials properly"', function () {
302 client.sendCredentials({ password: 'pass' });
e7dec527 303 expect(client._rfbCredentials).to.deep.equal({ password: 'pass' });
b1dee947
SR
304 });
305
00674385
SM
306 it('should call initMsg "soon"', function () {
307 client._initMsg = sinon.spy();
430f00d6 308 client.sendCredentials({ password: 'pass' });
b1dee947 309 this.clock.tick(5);
00674385 310 expect(client._initMsg).to.have.been.calledOnce;
b1dee947
SR
311 });
312 });
057b8fec 313 });
b1dee947 314
057b8fec 315 describe('Public API Basic Behavior', function () {
2b5f94fa 316 let client;
057b8fec 317 beforeEach(function () {
00674385 318 client = makeRFB();
057b8fec 319 });
b1dee947 320
057b8fec 321 describe('#sendCtrlAlDel', function () {
b1dee947 322 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
0e4808bf 323 const expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
324 RFB.messages.keyEvent(expected, 0xFFE3, 1);
325 RFB.messages.keyEvent(expected, 0xFFE9, 1);
326 RFB.messages.keyEvent(expected, 0xFFFF, 1);
327 RFB.messages.keyEvent(expected, 0xFFFF, 0);
328 RFB.messages.keyEvent(expected, 0xFFE9, 0);
329 RFB.messages.keyEvent(expected, 0xFFE3, 0);
b1dee947
SR
330
331 client.sendCtrlAltDel();
9ff86fb7 332 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
333 });
334
335 it('should not send the keys if we are not in a normal state', function () {
057b8fec 336 sinon.spy(client._sock, 'flush');
e7dec527 337 client._rfbConnectionState = "connecting";
b1dee947 338 client.sendCtrlAltDel();
9ff86fb7 339 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
340 });
341
342 it('should not send the keys if we are set as view_only', function () {
057b8fec 343 sinon.spy(client._sock, 'flush');
747b4623 344 client._viewOnly = true;
b1dee947 345 client.sendCtrlAltDel();
9ff86fb7 346 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
347 });
348 });
349
350 describe('#sendKey', function () {
b1dee947 351 it('should send a single key with the given code and state (down = true)', function () {
0e4808bf 352 const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
9ff86fb7 353 RFB.messages.keyEvent(expected, 123, 1);
94f5cf05 354 client.sendKey(123, 'Key123', true);
9ff86fb7 355 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
356 });
357
358 it('should send both a down and up event if the state is not specified', function () {
0e4808bf 359 const expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
360 RFB.messages.keyEvent(expected, 123, 1);
361 RFB.messages.keyEvent(expected, 123, 0);
94f5cf05 362 client.sendKey(123, 'Key123');
9ff86fb7 363 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
364 });
365
366 it('should not send the key if we are not in a normal state', function () {
057b8fec 367 sinon.spy(client._sock, 'flush');
e7dec527 368 client._rfbConnectionState = "connecting";
94f5cf05 369 client.sendKey(123, 'Key123');
9ff86fb7 370 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
371 });
372
373 it('should not send the key if we are set as view_only', function () {
057b8fec 374 sinon.spy(client._sock, 'flush');
747b4623 375 client._viewOnly = true;
94f5cf05 376 client.sendKey(123, 'Key123');
9ff86fb7 377 expect(client._sock.flush).to.not.have.been.called;
b1dee947 378 });
94f5cf05
PO
379
380 it('should send QEMU extended events if supported', function () {
381 client._qemuExtKeyEventSupported = true;
0e4808bf 382 const expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
94f5cf05
PO
383 RFB.messages.QEMUExtendedKeyEvent(expected, 0x20, true, 0x0039);
384 client.sendKey(0x20, 'Space', true);
385 expect(client._sock).to.have.sent(expected._sQ);
386 });
be70fe0a
PO
387
388 it('should not send QEMU extended events if unknown key code', function () {
389 client._qemuExtKeyEventSupported = true;
0e4808bf 390 const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
be70fe0a
PO
391 RFB.messages.keyEvent(expected, 123, 1);
392 client.sendKey(123, 'FooBar', true);
393 expect(client._sock).to.have.sent(expected._sQ);
394 });
b1dee947
SR
395 });
396
9b84f516
PO
397 describe('#focus', function () {
398 it('should move focus to canvas object', function () {
399 client._canvas.focus = sinon.spy();
400 client.focus();
b875486d 401 expect(client._canvas.focus).to.have.been.calledOnce;
9b84f516
PO
402 });
403 });
404
405 describe('#blur', function () {
406 it('should remove focus from canvas object', function () {
407 client._canvas.blur = sinon.spy();
408 client.blur();
b875486d 409 expect(client._canvas.blur).to.have.been.calledOnce;
9b84f516
PO
410 });
411 });
412
b1dee947 413 describe('#clipboardPasteFrom', function () {
f73fdc3e
NL
414 describe('Clipboard update handling', function () {
415 beforeEach(function () {
416 sinon.spy(RFB.messages, 'clientCutText');
417 sinon.spy(RFB.messages, 'extendedClipboardNotify');
418 });
3b562e8a 419
f73fdc3e
NL
420 afterEach(function () {
421 RFB.messages.clientCutText.restore();
422 RFB.messages.extendedClipboardNotify.restore();
423 });
3b562e8a 424
f73fdc3e
NL
425 it('should send the given text in an clipboard update', function () {
426 client.clipboardPasteFrom('abc');
427
428 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
429 expect(RFB.messages.clientCutText).to.have.been.calledWith(client._sock,
430 new Uint8Array([97, 98, 99]));
431 });
432
433 it('should send an notify if extended clipboard is supported by server', function () {
434 // Send our capabilities
435 let data = [3, 0, 0, 0];
436 const flags = [0x1F, 0x00, 0x00, 0x01];
437 let fileSizes = [0x00, 0x00, 0x00, 0x1E];
438
439 push32(data, toUnsigned32bit(-8));
440 data = data.concat(flags);
441 data = data.concat(fileSizes);
95632e41 442 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
443
444 client.clipboardPasteFrom('extended test');
445 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
446 });
b1dee947
SR
447 });
448
2bb8b28d
SM
449 it('should flush multiple times for large clipboards', function () {
450 sinon.spy(client._sock, 'flush');
e7dec527 451 let longText = "";
2bb8b28d 452 for (let i = 0; i < client._sock._sQbufferSize + 100; i++) {
e7dec527 453 longText += 'a';
2bb8b28d 454 }
e7dec527 455 client.clipboardPasteFrom(longText);
2bb8b28d
SM
456 expect(client._sock.flush).to.have.been.calledTwice;
457 });
458
b1dee947 459 it('should not send the text if we are not in a normal state', function () {
057b8fec 460 sinon.spy(client._sock, 'flush');
e7dec527 461 client._rfbConnectionState = "connecting";
b1dee947 462 client.clipboardPasteFrom('abc');
9ff86fb7 463 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
464 });
465 });
466
467 describe("XVP operations", function () {
468 beforeEach(function () {
e7dec527 469 client._rfbXvpVer = 1;
b1dee947
SR
470 });
471
cd523e8f
PO
472 it('should send the shutdown signal on #machineShutdown', function () {
473 client.machineShutdown();
9ff86fb7 474 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
b1dee947
SR
475 });
476
cd523e8f
PO
477 it('should send the reboot signal on #machineReboot', function () {
478 client.machineReboot();
9ff86fb7 479 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
b1dee947
SR
480 });
481
cd523e8f
PO
482 it('should send the reset signal on #machineReset', function () {
483 client.machineReset();
9ff86fb7 484 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
b1dee947
SR
485 });
486
b1dee947 487 it('should not send XVP operations with higher versions than we support', function () {
057b8fec 488 sinon.spy(client._sock, 'flush');
cd523e8f 489 client._xvpOp(2, 7);
9ff86fb7 490 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
491 });
492 });
493 });
494
9b84f516 495 describe('Clipping', function () {
2b5f94fa 496 let client;
9b84f516 497 beforeEach(function () {
00674385 498 client = makeRFB();
9b84f516
PO
499 container.style.width = '70px';
500 container.style.height = '80px';
501 client.clipViewport = true;
502 });
503
504 it('should update display clip state when changing the property', function () {
2b5f94fa 505 const spy = sinon.spy(client._display, "clipViewport", ["set"]);
9b84f516
PO
506
507 client.clipViewport = false;
508 expect(spy.set).to.have.been.calledOnce;
509 expect(spy.set).to.have.been.calledWith(false);
c9765e50 510 spy.set.resetHistory();
9b84f516
PO
511
512 client.clipViewport = true;
513 expect(spy.set).to.have.been.calledOnce;
514 expect(spy.set).to.have.been.calledWith(true);
515 });
516
517 it('should update the viewport when the container size changes', function () {
518 sinon.spy(client._display, "viewportChangeSize");
519
520 container.style.width = '40px';
521 container.style.height = '50px';
2b5f94fa 522 const event = new UIEvent('resize');
9b84f516
PO
523 window.dispatchEvent(event);
524 clock.tick();
525
526 expect(client._display.viewportChangeSize).to.have.been.calledOnce;
527 expect(client._display.viewportChangeSize).to.have.been.calledWith(40, 50);
528 });
529
530 it('should update the viewport when the remote session resizes', function () {
531 // Simple ExtendedDesktopSize FBU message
2b5f94fa 532 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
533 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
534 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
535 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
536 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
537
538 sinon.spy(client._display, "viewportChangeSize");
539
95632e41 540 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
541
542 // FIXME: Display implicitly calls viewportChangeSize() when
543 // resizing the framebuffer, hence calledTwice.
544 expect(client._display.viewportChangeSize).to.have.been.calledTwice;
545 expect(client._display.viewportChangeSize).to.have.been.calledWith(70, 80);
546 });
547
548 it('should not update the viewport if not clipping', function () {
549 client.clipViewport = false;
550 sinon.spy(client._display, "viewportChangeSize");
551
552 container.style.width = '40px';
553 container.style.height = '50px';
2b5f94fa 554 const event = new UIEvent('resize');
9b84f516
PO
555 window.dispatchEvent(event);
556 clock.tick();
557
558 expect(client._display.viewportChangeSize).to.not.have.been.called;
559 });
560
561 it('should not update the viewport if scaling', function () {
562 client.scaleViewport = true;
563 sinon.spy(client._display, "viewportChangeSize");
564
565 container.style.width = '40px';
566 container.style.height = '50px';
2b5f94fa 567 const event = new UIEvent('resize');
9b84f516
PO
568 window.dispatchEvent(event);
569 clock.tick();
570
571 expect(client._display.viewportChangeSize).to.not.have.been.called;
572 });
573
574 describe('Dragging', function () {
575 beforeEach(function () {
576 client.dragViewport = true;
577 sinon.spy(RFB.messages, "pointerEvent");
578 });
579
580 afterEach(function () {
581 RFB.messages.pointerEvent.restore();
582 });
583
584 it('should not send button messages when initiating viewport dragging', function () {
585 client._handleMouseButton(13, 9, 0x001);
586 expect(RFB.messages.pointerEvent).to.not.have.been.called;
587 });
588
589 it('should send button messages when release without movement', function () {
590 // Just up and down
591 client._handleMouseButton(13, 9, 0x001);
592 client._handleMouseButton(13, 9, 0x000);
593 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
594
c9765e50 595 RFB.messages.pointerEvent.resetHistory();
9b84f516
PO
596
597 // Small movement
598 client._handleMouseButton(13, 9, 0x001);
599 client._handleMouseMove(15, 14);
600 client._handleMouseButton(15, 14, 0x000);
601 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
602 });
603
11a22dbf
SM
604 it('should not send button messages when in view only', function () {
605 client._viewOnly = true;
606 client._handleMouseButton(13, 9, 0x001);
607 client._handleMouseButton(13, 9, 0x000);
608 expect(RFB.messages.pointerEvent).to.not.have.been.called;
609 });
610
9b84f516
PO
611 it('should send button message directly when drag is disabled', function () {
612 client.dragViewport = false;
613 client._handleMouseButton(13, 9, 0x001);
614 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
615 });
616
617 it('should be initiate viewport dragging on sufficient movement', function () {
618 sinon.spy(client._display, "viewportChangePos");
619
620 // Too small movement
621
622 client._handleMouseButton(13, 9, 0x001);
623 client._handleMouseMove(18, 9);
624
625 expect(RFB.messages.pointerEvent).to.not.have.been.called;
626 expect(client._display.viewportChangePos).to.not.have.been.called;
627
628 // Sufficient movement
629
630 client._handleMouseMove(43, 9);
631
632 expect(RFB.messages.pointerEvent).to.not.have.been.called;
633 expect(client._display.viewportChangePos).to.have.been.calledOnce;
634 expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0);
635
c9765e50 636 client._display.viewportChangePos.resetHistory();
9b84f516
PO
637
638 // Now a small movement should move right away
639
640 client._handleMouseMove(43, 14);
641
642 expect(RFB.messages.pointerEvent).to.not.have.been.called;
643 expect(client._display.viewportChangePos).to.have.been.calledOnce;
644 expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5);
645 });
646
647 it('should not send button messages when dragging ends', function () {
648 // First the movement
649
650 client._handleMouseButton(13, 9, 0x001);
651 client._handleMouseMove(43, 9);
652 client._handleMouseButton(43, 9, 0x000);
653
654 expect(RFB.messages.pointerEvent).to.not.have.been.called;
655 });
656
657 it('should terminate viewport dragging on a button up event', function () {
658 // First the dragging movement
659
660 client._handleMouseButton(13, 9, 0x001);
661 client._handleMouseMove(43, 9);
662 client._handleMouseButton(43, 9, 0x000);
663
664 // Another movement now should not move the viewport
665
666 sinon.spy(client._display, "viewportChangePos");
667
668 client._handleMouseMove(43, 59);
669
670 expect(client._display.viewportChangePos).to.not.have.been.called;
671 });
672 });
673 });
674
675 describe('Scaling', function () {
2b5f94fa 676 let client;
9b84f516 677 beforeEach(function () {
00674385 678 client = makeRFB();
9b84f516
PO
679 container.style.width = '70px';
680 container.style.height = '80px';
681 client.scaleViewport = true;
682 });
683
684 it('should update display scale factor when changing the property', function () {
2b5f94fa 685 const spy = sinon.spy(client._display, "scale", ["set"]);
9b84f516
PO
686 sinon.spy(client._display, "autoscale");
687
688 client.scaleViewport = false;
689 expect(spy.set).to.have.been.calledOnce;
690 expect(spy.set).to.have.been.calledWith(1.0);
691 expect(client._display.autoscale).to.not.have.been.called;
692
693 client.scaleViewport = true;
694 expect(client._display.autoscale).to.have.been.calledOnce;
695 expect(client._display.autoscale).to.have.been.calledWith(70, 80);
696 });
697
698 it('should update the clipping setting when changing the property', function () {
699 client.clipViewport = true;
700
2b5f94fa 701 const spy = sinon.spy(client._display, "clipViewport", ["set"]);
9b84f516
PO
702
703 client.scaleViewport = false;
704 expect(spy.set).to.have.been.calledOnce;
705 expect(spy.set).to.have.been.calledWith(true);
706
c9765e50 707 spy.set.resetHistory();
9b84f516
PO
708
709 client.scaleViewport = true;
710 expect(spy.set).to.have.been.calledOnce;
711 expect(spy.set).to.have.been.calledWith(false);
712 });
713
714 it('should update the scaling when the container size changes', function () {
715 sinon.spy(client._display, "autoscale");
716
717 container.style.width = '40px';
718 container.style.height = '50px';
2b5f94fa 719 const event = new UIEvent('resize');
9b84f516
PO
720 window.dispatchEvent(event);
721 clock.tick();
722
723 expect(client._display.autoscale).to.have.been.calledOnce;
724 expect(client._display.autoscale).to.have.been.calledWith(40, 50);
725 });
726
727 it('should update the scaling when the remote session resizes', function () {
728 // Simple ExtendedDesktopSize FBU message
2b5f94fa 729 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
730 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
731 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
732 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
733 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
734
735 sinon.spy(client._display, "autoscale");
736
95632e41 737 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
738
739 expect(client._display.autoscale).to.have.been.calledOnce;
740 expect(client._display.autoscale).to.have.been.calledWith(70, 80);
741 });
742
743 it('should not update the display scale factor if not scaling', function () {
744 client.scaleViewport = false;
745
746 sinon.spy(client._display, "autoscale");
747
748 container.style.width = '40px';
749 container.style.height = '50px';
2b5f94fa 750 const event = new UIEvent('resize');
9b84f516
PO
751 window.dispatchEvent(event);
752 clock.tick();
753
754 expect(client._display.autoscale).to.not.have.been.called;
755 });
756 });
757
758 describe('Remote resize', function () {
2b5f94fa 759 let client;
9b84f516 760 beforeEach(function () {
00674385 761 client = makeRFB();
9b84f516
PO
762 client._supportsSetDesktopSize = true;
763 client.resizeSession = true;
764 container.style.width = '70px';
765 container.style.height = '80px';
766 sinon.spy(RFB.messages, "setDesktopSize");
767 });
768
769 afterEach(function () {
770 RFB.messages.setDesktopSize.restore();
771 });
772
773 it('should only request a resize when turned on', function () {
774 client.resizeSession = false;
775 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
776 client.resizeSession = true;
777 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
778 });
779
780 it('should request a resize when initially connecting', function () {
781 // Simple ExtendedDesktopSize FBU message
2b5f94fa 782 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
783 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
784 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
785 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
786 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
787
788 // First message should trigger a resize
789
790 client._supportsSetDesktopSize = false;
791
95632e41 792 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
793
794 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
795 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 70, 80, 0, 0);
796
c9765e50 797 RFB.messages.setDesktopSize.resetHistory();
9b84f516
PO
798
799 // Second message should not trigger a resize
800
95632e41 801 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
802
803 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
804 });
805
806 it('should request a resize when the container resizes', function () {
807 container.style.width = '40px';
808 container.style.height = '50px';
2b5f94fa 809 const event = new UIEvent('resize');
9b84f516
PO
810 window.dispatchEvent(event);
811 clock.tick(1000);
812
813 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
814 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
815 });
816
817 it('should not resize until the container size is stable', function () {
818 container.style.width = '20px';
819 container.style.height = '30px';
2b5f94fa 820 const event1 = new UIEvent('resize');
8727f598 821 window.dispatchEvent(event1);
9b84f516
PO
822 clock.tick(400);
823
824 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
825
826 container.style.width = '40px';
827 container.style.height = '50px';
2b5f94fa 828 const event2 = new UIEvent('resize');
8727f598 829 window.dispatchEvent(event2);
9b84f516
PO
830 clock.tick(400);
831
832 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
833
834 clock.tick(200);
835
836 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
837 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
838 });
839
840 it('should not resize when resize is disabled', function () {
841 client._resizeSession = false;
842
843 container.style.width = '40px';
844 container.style.height = '50px';
2b5f94fa 845 const event = new UIEvent('resize');
9b84f516
PO
846 window.dispatchEvent(event);
847 clock.tick(1000);
848
849 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
850 });
851
852 it('should not resize when resize is not supported', function () {
853 client._supportsSetDesktopSize = false;
854
855 container.style.width = '40px';
856 container.style.height = '50px';
2b5f94fa 857 const event = new UIEvent('resize');
9b84f516
PO
858 window.dispatchEvent(event);
859 clock.tick(1000);
860
861 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
862 });
863
864 it('should not resize when in view only mode', function () {
865 client._viewOnly = true;
866
867 container.style.width = '40px';
868 container.style.height = '50px';
2b5f94fa 869 const event = new UIEvent('resize');
9b84f516
PO
870 window.dispatchEvent(event);
871 clock.tick(1000);
872
873 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
874 });
875
876 it('should not try to override a server resize', function () {
877 // Simple ExtendedDesktopSize FBU message
2b5f94fa 878 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
879 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
880 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
881 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
882 0x00, 0x00, 0x00, 0x00 ];
9b84f516 883
95632e41 884 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
885
886 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
887 });
888 });
889
b1dee947 890 describe('Misc Internals', function () {
3bb12056 891 describe('#_fail', function () {
2b5f94fa 892 let client;
3bb12056 893 beforeEach(function () {
00674385 894 client = makeRFB();
3bb12056
SM
895 });
896
3bb12056
SM
897 it('should close the WebSocket connection', function () {
898 sinon.spy(client._sock, 'close');
899 client._fail();
900 expect(client._sock.close).to.have.been.calledOnce;
901 });
902
903 it('should transition to disconnected', function () {
904 sinon.spy(client, '_updateConnectionState');
905 client._fail();
906 this.clock.tick(2000);
907 expect(client._updateConnectionState).to.have.been.called;
e7dec527 908 expect(client._rfbConnectionState).to.equal('disconnected');
3bb12056
SM
909 });
910
d472f3f1 911 it('should set clean_disconnect variable', function () {
e7dec527
SM
912 client._rfbCleanDisconnect = true;
913 client._rfbConnectionState = 'connected';
d472f3f1 914 client._fail();
e7dec527 915 expect(client._rfbCleanDisconnect).to.be.false;
67cd2072
SM
916 });
917
d472f3f1 918 it('should result in disconnect event with clean set to false', function () {
e7dec527 919 client._rfbConnectionState = 'connected';
2b5f94fa 920 const spy = sinon.spy();
e89eef94 921 client.addEventListener("disconnect", spy);
d472f3f1 922 client._fail();
3bb12056
SM
923 this.clock.tick(2000);
924 expect(spy).to.have.been.calledOnce;
d472f3f1 925 expect(spy.args[0][0].detail.clean).to.be.false;
3bb12056
SM
926 });
927
b1dee947
SR
928 });
929 });
930
b1dee947 931 describe('Protocol Initialization States', function () {
2b5f94fa 932 let client;
057b8fec 933 beforeEach(function () {
00674385 934 client = makeRFB();
e7dec527 935 client._rfbConnectionState = 'connecting';
057b8fec 936 });
b1dee947 937
057b8fec 938 describe('ProtocolVersion', function () {
00674385 939 function sendVer(ver, client) {
2b5f94fa
JD
940 const arr = new Uint8Array(12);
941 for (let i = 0; i < ver.length; i++) {
b1dee947
SR
942 arr[i+4] = ver.charCodeAt(i);
943 }
944 arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';
945 arr[11] = '\n';
95632e41 946 client._sock._websocket._receiveData(arr);
b1dee947
SR
947 }
948
949 describe('version parsing', function () {
b1dee947 950 it('should interpret version 003.003 as version 3.3', function () {
00674385 951 sendVer('003.003', client);
e7dec527 952 expect(client._rfbVersion).to.equal(3.3);
b1dee947
SR
953 });
954
955 it('should interpret version 003.006 as version 3.3', function () {
00674385 956 sendVer('003.006', client);
e7dec527 957 expect(client._rfbVersion).to.equal(3.3);
b1dee947
SR
958 });
959
960 it('should interpret version 003.889 as version 3.3', function () {
00674385 961 sendVer('003.889', client);
e7dec527 962 expect(client._rfbVersion).to.equal(3.3);
b1dee947
SR
963 });
964
965 it('should interpret version 003.007 as version 3.7', function () {
00674385 966 sendVer('003.007', client);
e7dec527 967 expect(client._rfbVersion).to.equal(3.7);
b1dee947
SR
968 });
969
970 it('should interpret version 003.008 as version 3.8', function () {
00674385 971 sendVer('003.008', client);
e7dec527 972 expect(client._rfbVersion).to.equal(3.8);
b1dee947
SR
973 });
974
975 it('should interpret version 004.000 as version 3.8', function () {
00674385 976 sendVer('004.000', client);
e7dec527 977 expect(client._rfbVersion).to.equal(3.8);
b1dee947
SR
978 });
979
980 it('should interpret version 004.001 as version 3.8', function () {
00674385 981 sendVer('004.001', client);
e7dec527 982 expect(client._rfbVersion).to.equal(3.8);
b1dee947
SR
983 });
984
49aa5b81 985 it('should interpret version 005.000 as version 3.8', function () {
00674385 986 sendVer('005.000', client);
e7dec527 987 expect(client._rfbVersion).to.equal(3.8);
49aa5b81
LOH
988 });
989
b1dee947 990 it('should fail on an invalid version', function () {
3bb12056 991 sinon.spy(client, "_fail");
00674385 992 sendVer('002.000', client);
3bb12056 993 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
994 });
995 });
996
b1dee947 997 it('should send back the interpreted version', function () {
00674385 998 sendVer('004.000', client);
b1dee947 999
e7dec527 1000 const expectedStr = 'RFB 003.008\n';
2b5f94fa 1001 const expected = [];
e7dec527
SM
1002 for (let i = 0; i < expectedStr.length; i++) {
1003 expected[i] = expectedStr.charCodeAt(i);
b1dee947
SR
1004 }
1005
9ff86fb7 1006 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
1007 });
1008
1009 it('should transition to the Security state on successful negotiation', function () {
00674385 1010 sendVer('003.008', client);
e7dec527 1011 expect(client._rfbInitState).to.equal('Security');
b1dee947 1012 });
3d7bb020
PO
1013
1014 describe('Repeater', function () {
057b8fec 1015 beforeEach(function () {
00674385 1016 client = makeRFB('wss://host:8675', { repeaterID: "12345" });
e7dec527 1017 client._rfbConnectionState = 'connecting';
057b8fec
PO
1018 });
1019
1020 it('should interpret version 000.000 as a repeater', function () {
00674385 1021 sendVer('000.000', client);
e7dec527 1022 expect(client._rfbVersion).to.equal(0);
3d7bb020 1023
95632e41 1024 const sentData = client._sock._websocket._getSentData();
e7dec527
SM
1025 expect(new Uint8Array(sentData.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));
1026 expect(sentData).to.have.length(250);
3d7bb020
PO
1027 });
1028
1029 it('should handle two step repeater negotiation', function () {
00674385
SM
1030 sendVer('000.000', client);
1031 sendVer('003.008', client);
e7dec527 1032 expect(client._rfbVersion).to.equal(3.8);
3d7bb020
PO
1033 });
1034 });
b1dee947
SR
1035 });
1036
1037 describe('Security', function () {
b1dee947 1038 beforeEach(function () {
e7dec527 1039 client._rfbInitState = 'Security';
b1dee947
SR
1040 });
1041
1042 it('should simply receive the auth scheme when for versions < 3.7', function () {
e7dec527
SM
1043 client._rfbVersion = 3.6;
1044 const authSchemeRaw = [1, 2, 3, 4];
1045 const authScheme = (authSchemeRaw[0] << 24) + (authSchemeRaw[1] << 16) +
1046 (authSchemeRaw[2] << 8) + authSchemeRaw[3];
95632e41 1047 client._sock._websocket._receiveData(new Uint8Array(authSchemeRaw));
e7dec527 1048 expect(client._rfbAuthScheme).to.equal(authScheme);
b1dee947
SR
1049 });
1050
0ee5ca6e 1051 it('should prefer no authentication is possible', function () {
e7dec527
SM
1052 client._rfbVersion = 3.7;
1053 const authSchemes = [2, 1, 3];
95632e41 1054 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
e7dec527 1055 expect(client._rfbAuthScheme).to.equal(1);
0ee5ca6e
PO
1056 expect(client._sock).to.have.sent(new Uint8Array([1, 1]));
1057 });
1058
b1dee947 1059 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
e7dec527
SM
1060 client._rfbVersion = 3.7;
1061 const authSchemes = [2, 22, 16];
95632e41 1062 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
e7dec527 1063 expect(client._rfbAuthScheme).to.equal(22);
0ee5ca6e 1064 expect(client._sock).to.have.sent(new Uint8Array([22]));
b1dee947
SR
1065 });
1066
1067 it('should fail if there are no supported schemes for versions >= 3.7', function () {
3bb12056 1068 sinon.spy(client, "_fail");
e7dec527
SM
1069 client._rfbVersion = 3.7;
1070 const authSchemes = [1, 32];
95632e41 1071 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
3bb12056 1072 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1073 });
1074
1075 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
e7dec527
SM
1076 client._rfbVersion = 3.7;
1077 const failureData = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
b1dee947 1078 sinon.spy(client, '_fail');
95632e41 1079 client._sock._websocket._receiveData(new Uint8Array(failureData));
b1dee947 1080
159c50c0 1081 expect(client._fail).to.have.been.calledOnce;
67cd2072 1082 expect(client._fail).to.have.been.calledWith(
d472f3f1 1083 'Security negotiation failed on no security types (reason: whoops)');
b1dee947
SR
1084 });
1085
1086 it('should transition to the Authentication state and continue on successful negotiation', function () {
e7dec527
SM
1087 client._rfbVersion = 3.7;
1088 const authSchemes = [1, 1];
00674385 1089 client._negotiateAuthentication = sinon.spy();
95632e41 1090 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
e7dec527 1091 expect(client._rfbInitState).to.equal('Authentication');
00674385 1092 expect(client._negotiateAuthentication).to.have.been.calledOnce;
b1dee947
SR
1093 });
1094 });
1095
1096 describe('Authentication', function () {
b1dee947 1097 beforeEach(function () {
e7dec527 1098 client._rfbInitState = 'Security';
b1dee947
SR
1099 });
1100
00674385 1101 function sendSecurity(type, cl) {
95632e41 1102 cl._sock._websocket._receiveData(new Uint8Array([1, type]));
b1dee947
SR
1103 }
1104
1105 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
e7dec527
SM
1106 client._rfbVersion = 3.6;
1107 const errMsg = "Whoopsies";
2b5f94fa 1108 const data = [0, 0, 0, 0];
e7dec527
SM
1109 const errLen = errMsg.length;
1110 push32(data, errLen);
1111 for (let i = 0; i < errLen; i++) {
1112 data.push(errMsg.charCodeAt(i));
b1dee947
SR
1113 }
1114
1115 sinon.spy(client, '_fail');
95632e41 1116 client._sock._websocket._receiveData(new Uint8Array(data));
67cd2072 1117 expect(client._fail).to.have.been.calledWith(
d472f3f1 1118 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
b1dee947
SR
1119 });
1120
1121 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
e7dec527 1122 client._rfbVersion = 3.8;
00674385 1123 sendSecurity(1, client);
e7dec527 1124 expect(client._rfbInitState).to.equal('SecurityResult');
b1dee947
SR
1125 });
1126
c00ee156 1127 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
e7dec527 1128 client._rfbVersion = 3.7;
00674385 1129 sendSecurity(1, client);
e7dec527 1130 expect(client._rfbInitState).to.equal('ServerInitialisation');
b1dee947
SR
1131 });
1132
1133 it('should fail on an unknown auth scheme', function () {
3bb12056 1134 sinon.spy(client, "_fail");
e7dec527 1135 client._rfbVersion = 3.8;
00674385 1136 sendSecurity(57, client);
3bb12056 1137 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1138 });
1139
1140 describe('VNC Authentication (type 2) Handler', function () {
b1dee947 1141 beforeEach(function () {
e7dec527
SM
1142 client._rfbInitState = 'Security';
1143 client._rfbVersion = 3.8;
b1dee947
SR
1144 });
1145
e89eef94 1146 it('should fire the credentialsrequired event if missing a password', function () {
2b5f94fa 1147 const spy = sinon.spy();
e89eef94 1148 client.addEventListener("credentialsrequired", spy);
00674385 1149 sendSecurity(2, client);
7d714b15 1150
2b5f94fa
JD
1151 const challenge = [];
1152 for (let i = 0; i < 16; i++) { challenge[i] = i; }
95632e41 1153 client._sock._websocket._receiveData(new Uint8Array(challenge));
aa5b3a35 1154
e7dec527 1155 expect(client._rfbCredentials).to.be.empty;
7d714b15 1156 expect(spy).to.have.been.calledOnce;
e89eef94 1157 expect(spy.args[0][0].detail.types).to.have.members(["password"]);
b1dee947
SR
1158 });
1159
1160 it('should encrypt the password with DES and then send it back', function () {
e7dec527 1161 client._rfbCredentials = { password: 'passwd' };
00674385 1162 sendSecurity(2, client);
95632e41 1163 client._sock._websocket._getSentData(); // skip the choice of auth reply
b1dee947 1164
2b5f94fa
JD
1165 const challenge = [];
1166 for (let i = 0; i < 16; i++) { challenge[i] = i; }
95632e41 1167 client._sock._websocket._receiveData(new Uint8Array(challenge));
b1dee947 1168
e7dec527
SM
1169 const desPass = RFB.genDES('passwd', challenge);
1170 expect(client._sock).to.have.sent(new Uint8Array(desPass));
b1dee947
SR
1171 });
1172
1173 it('should transition to SecurityResult immediately after sending the password', function () {
e7dec527 1174 client._rfbCredentials = { password: 'passwd' };
00674385 1175 sendSecurity(2, client);
b1dee947 1176
2b5f94fa
JD
1177 const challenge = [];
1178 for (let i = 0; i < 16; i++) { challenge[i] = i; }
95632e41 1179 client._sock._websocket._receiveData(new Uint8Array(challenge));
b1dee947 1180
e7dec527 1181 expect(client._rfbInitState).to.equal('SecurityResult');
b1dee947
SR
1182 });
1183 });
1184
1185 describe('XVP Authentication (type 22) Handler', function () {
b1dee947 1186 beforeEach(function () {
e7dec527
SM
1187 client._rfbInitState = 'Security';
1188 client._rfbVersion = 3.8;
b1dee947
SR
1189 });
1190
1191 it('should fall through to standard VNC authentication upon completion', function () {
e7dec527
SM
1192 client._rfbCredentials = { username: 'user',
1193 target: 'target',
1194 password: 'password' };
00674385
SM
1195 client._negotiateStdVNCAuth = sinon.spy();
1196 sendSecurity(22, client);
1197 expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;
b1dee947
SR
1198 });
1199
2c5491e1 1200 it('should fire the credentialsrequired event if all credentials are missing', function () {
2b5f94fa 1201 const spy = sinon.spy();
e89eef94 1202 client.addEventListener("credentialsrequired", spy);
e7dec527 1203 client._rfbCredentials = {};
00674385 1204 sendSecurity(22, client);
7d714b15 1205
e7dec527 1206 expect(client._rfbCredentials).to.be.empty;
7d714b15 1207 expect(spy).to.have.been.calledOnce;
e89eef94 1208 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
b1dee947
SR
1209 });
1210
2c5491e1 1211 it('should fire the credentialsrequired event if some credentials are missing', function () {
2b5f94fa 1212 const spy = sinon.spy();
e89eef94 1213 client.addEventListener("credentialsrequired", spy);
e7dec527
SM
1214 client._rfbCredentials = { username: 'user',
1215 target: 'target' };
00674385 1216 sendSecurity(22, client);
7d714b15 1217
7d714b15 1218 expect(spy).to.have.been.calledOnce;
e89eef94 1219 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
b1dee947
SR
1220 });
1221
430f00d6 1222 it('should send user and target separately', function () {
e7dec527
SM
1223 client._rfbCredentials = { username: 'user',
1224 target: 'target',
1225 password: 'password' };
00674385 1226 client._negotiateStdVNCAuth = sinon.spy();
b1dee947 1227
00674385 1228 sendSecurity(22, client);
b1dee947 1229
2b5f94fa
JD
1230 const expected = [22, 4, 6]; // auth selection, len user, len target
1231 for (let i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
b1dee947 1232
9ff86fb7 1233 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
1234 });
1235 });
1236
1237 describe('TightVNC Authentication (type 16) Handler', function () {
b1dee947 1238 beforeEach(function () {
e7dec527
SM
1239 client._rfbInitState = 'Security';
1240 client._rfbVersion = 3.8;
00674385 1241 sendSecurity(16, client);
95632e41 1242 client._sock._websocket._getSentData(); // skip the security reply
b1dee947
SR
1243 });
1244
00674385 1245 function sendNumStrPairs(pairs, client) {
2b5f94fa 1246 const data = [];
651c23ec 1247 push32(data, pairs.length);
b1dee947 1248
651c23ec 1249 for (let i = 0; i < pairs.length; i++) {
3949a095 1250 push32(data, pairs[i][0]);
2b5f94fa 1251 for (let j = 0; j < 4; j++) {
b1dee947
SR
1252 data.push(pairs[i][1].charCodeAt(j));
1253 }
2b5f94fa 1254 for (let j = 0; j < 8; j++) {
b1dee947
SR
1255 data.push(pairs[i][2].charCodeAt(j));
1256 }
1257 }
1258
95632e41 1259 client._sock._websocket._receiveData(new Uint8Array(data));
b1dee947
SR
1260 }
1261
1262 it('should skip tunnel negotiation if no tunnels are requested', function () {
95632e41 1263 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
e7dec527 1264 expect(client._rfbTightVNC).to.be.true;
b1dee947
SR
1265 });
1266
1267 it('should fail if no supported tunnels are listed', function () {
3bb12056 1268 sinon.spy(client, "_fail");
00674385 1269 sendNumStrPairs([[123, 'OTHR', 'SOMETHNG']], client);
3bb12056 1270 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1271 });
1272
1273 it('should choose the notunnel tunnel type', function () {
00674385 1274 sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
9ff86fb7 1275 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
b1dee947
SR
1276 });
1277
8f47bd29 1278 it('should choose the notunnel tunnel type for Siemens devices', function () {
00674385 1279 sendNumStrPairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client);
8f47bd29
PO
1280 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
1281 });
1282
b1dee947 1283 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
00674385 1284 sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL']], client);
95632e41 1285 client._sock._websocket._getSentData(); // skip the tunnel choice here
00674385 1286 sendNumStrPairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 1287 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
e7dec527 1288 expect(client._rfbInitState).to.equal('SecurityResult');
b1dee947
SR
1289 });
1290
1291 /*it('should attempt to use VNC auth over no auth when possible', function () {
e7dec527 1292 client._rfbTightVNC = true;
00674385
SM
1293 client._negotiateStdVNCAuth = sinon.spy();
1294 sendNumStrPairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
b1dee947 1295 expect(client._sock).to.have.sent([0, 0, 0, 1]);
00674385 1296 expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;
e7dec527 1297 expect(client._rfbAuthScheme).to.equal(2);
b1dee947
SR
1298 });*/ // while this would make sense, the original code doesn't actually do this
1299
1300 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
e7dec527 1301 client._rfbTightVNC = true;
00674385 1302 sendNumStrPairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 1303 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
e7dec527 1304 expect(client._rfbInitState).to.equal('SecurityResult');
b1dee947
SR
1305 });
1306
1307 it('should accept VNC authentication and transition to that', function () {
e7dec527 1308 client._rfbTightVNC = true;
00674385
SM
1309 client._negotiateStdVNCAuth = sinon.spy();
1310 sendNumStrPairs([[2, 'STDV', 'VNCAUTH__']], client);
9ff86fb7 1311 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
00674385 1312 expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;
e7dec527 1313 expect(client._rfbAuthScheme).to.equal(2);
b1dee947
SR
1314 });
1315
1316 it('should fail if there are no supported auth types', function () {
3bb12056 1317 sinon.spy(client, "_fail");
e7dec527 1318 client._rfbTightVNC = true;
00674385 1319 sendNumStrPairs([[23, 'stdv', 'badval__']], client);
3bb12056 1320 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1321 });
1322 });
f9a8c4cc 1323
1324 describe('VeNCrypt Authentication (type 19) Handler', function () {
1325 beforeEach(function () {
1326 client._rfbInitState = 'Security';
1327 client._rfbVersion = 3.8;
1328 sendSecurity(19, client);
1329 expect(client._sock).to.have.sent(new Uint8Array([19]));
1330 });
1331
1332 it('should fail with non-0.2 versions', function () {
1333 sinon.spy(client, "_fail");
1334 client._sock._websocket._receiveData(new Uint8Array([0, 1]));
1335 expect(client._fail).to.have.been.calledOnce;
1336 });
1337
1338 it('should fail if the Plain authentication is not present', function () {
1339 // VeNCrypt version
1340 client._sock._websocket._receiveData(new Uint8Array([0, 2]));
1341 expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
1342 // Server ACK.
1343 client._sock._websocket._receiveData(new Uint8Array([0]));
1344 // Subtype list, only list subtype 1.
1345 sinon.spy(client, "_fail");
1346 client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 0, 1]));
1347 expect(client._fail).to.have.been.calledOnce;
1348 });
1349
1350 it('should support Plain authentication', function () {
1351 client._rfbCredentials = { username: 'username', password: 'password' };
1352 // VeNCrypt version
1353 client._sock._websocket._receiveData(new Uint8Array([0, 2]));
1354 expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
1355 // Server ACK.
1356 client._sock._websocket._receiveData(new Uint8Array([0]));
1357 // Subtype list.
1358 client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));
1359
1360 const expectedResponse = [];
1361 push32(expectedResponse, 256); // Chosen subtype.
1362 push32(expectedResponse, client._rfbCredentials.username.length);
1363 push32(expectedResponse, client._rfbCredentials.password.length);
1364 pushString(expectedResponse, client._rfbCredentials.username);
1365 pushString(expectedResponse, client._rfbCredentials.password);
1366 expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));
1367
1368 client._initMsg = sinon.spy();
1369 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1370 expect(client._initMsg).to.have.been.called;
1371 });
1372
1373 it('should support Plain authentication with an empty password', function () {
1374 client._rfbCredentials = { username: 'username', password: '' };
1375 // VeNCrypt version
1376 client._sock._websocket._receiveData(new Uint8Array([0, 2]));
1377 expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
1378 // Server ACK.
1379 client._sock._websocket._receiveData(new Uint8Array([0]));
1380 // Subtype list.
1381 client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));
1382
1383 const expectedResponse = [];
1384 push32(expectedResponse, 256); // Chosen subtype.
1385 push32(expectedResponse, client._rfbCredentials.username.length);
1386 push32(expectedResponse, client._rfbCredentials.password.length);
1387 pushString(expectedResponse, client._rfbCredentials.username);
1388 pushString(expectedResponse, client._rfbCredentials.password);
1389 expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));
1390
1391 client._initMsg = sinon.spy();
1392 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1393 expect(client._initMsg).to.have.been.called;
1394 });
1395
1396 it('should support Plain authentication with a very long username and password', function () {
1397 client._rfbCredentials = { username: 'a'.repeat(300), password: 'a'.repeat(300) };
1398 // VeNCrypt version
1399 client._sock._websocket._receiveData(new Uint8Array([0, 2]));
1400 expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
1401 // Server ACK.
1402 client._sock._websocket._receiveData(new Uint8Array([0]));
1403 // Subtype list.
1404 client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));
1405
1406 const expectedResponse = [];
1407 push32(expectedResponse, 256); // Chosen subtype.
1408 push32(expectedResponse, client._rfbCredentials.username.length);
1409 push32(expectedResponse, client._rfbCredentials.password.length);
1410 pushString(expectedResponse, client._rfbCredentials.username);
1411 pushString(expectedResponse, client._rfbCredentials.password);
1412 expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));
1413
1414 client._initMsg = sinon.spy();
1415 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1416 expect(client._initMsg).to.have.been.called;
1417 });
1418 });
b1dee947
SR
1419 });
1420
1421 describe('SecurityResult', function () {
b1dee947 1422 beforeEach(function () {
e7dec527 1423 client._rfbInitState = 'SecurityResult';
b1dee947
SR
1424 });
1425
c00ee156 1426 it('should fall through to ServerInitialisation on a response code of 0', function () {
95632e41 1427 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
e7dec527 1428 expect(client._rfbInitState).to.equal('ServerInitialisation');
b1dee947
SR
1429 });
1430
1431 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
e7dec527 1432 client._rfbVersion = 3.8;
b1dee947 1433 sinon.spy(client, '_fail');
e7dec527 1434 const failureData = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
95632e41 1435 client._sock._websocket._receiveData(new Uint8Array(failureData));
67cd2072 1436 expect(client._fail).to.have.been.calledWith(
d472f3f1 1437 'Security negotiation failed on security result (reason: whoops)');
b1dee947
SR
1438 });
1439
1440 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
3bb12056 1441 sinon.spy(client, '_fail');
e7dec527 1442 client._rfbVersion = 3.7;
95632e41 1443 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1]));
d472f3f1
SM
1444 expect(client._fail).to.have.been.calledWith(
1445 'Security handshake failed');
1446 });
1447
1448 it('should result in securityfailure event when receiving a non zero status', function () {
2b5f94fa 1449 const spy = sinon.spy();
d472f3f1 1450 client.addEventListener("securityfailure", spy);
95632e41 1451 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
d472f3f1
SM
1452 expect(spy).to.have.been.calledOnce;
1453 expect(spy.args[0][0].detail.status).to.equal(2);
1454 });
1455
1456 it('should include reason when provided in securityfailure event', function () {
e7dec527 1457 client._rfbVersion = 3.8;
2b5f94fa 1458 const spy = sinon.spy();
d472f3f1 1459 client.addEventListener("securityfailure", spy);
e7dec527
SM
1460 const failureData = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
1461 32, 102, 97, 105, 108, 117, 114, 101];
95632e41 1462 client._sock._websocket._receiveData(new Uint8Array(failureData));
d472f3f1
SM
1463 expect(spy.args[0][0].detail.status).to.equal(1);
1464 expect(spy.args[0][0].detail.reason).to.equal('such failure');
1465 });
1466
1467 it('should not include reason when length is zero in securityfailure event', function () {
e7dec527 1468 client._rfbVersion = 3.9;
2b5f94fa 1469 const spy = sinon.spy();
d472f3f1 1470 client.addEventListener("securityfailure", spy);
e7dec527 1471 const failureData = [0, 0, 0, 1, 0, 0, 0, 0];
95632e41 1472 client._sock._websocket._receiveData(new Uint8Array(failureData));
d472f3f1
SM
1473 expect(spy.args[0][0].detail.status).to.equal(1);
1474 expect('reason' in spy.args[0][0].detail).to.be.false;
1475 });
1476
1477 it('should not include reason in securityfailure event for version < 3.8', function () {
e7dec527 1478 client._rfbVersion = 3.6;
2b5f94fa 1479 const spy = sinon.spy();
d472f3f1 1480 client.addEventListener("securityfailure", spy);
95632e41 1481 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
d472f3f1
SM
1482 expect(spy.args[0][0].detail.status).to.equal(2);
1483 expect('reason' in spy.args[0][0].detail).to.be.false;
b1dee947
SR
1484 });
1485 });
1486
1487 describe('ClientInitialisation', function () {
b1dee947 1488 it('should transition to the ServerInitialisation state', function () {
00674385 1489 const client = makeRFB();
e7dec527
SM
1490 client._rfbConnectionState = 'connecting';
1491 client._rfbInitState = 'SecurityResult';
95632e41 1492 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
e7dec527 1493 expect(client._rfbInitState).to.equal('ServerInitialisation');
b1dee947
SR
1494 });
1495
1496 it('should send 1 if we are in shared mode', function () {
00674385 1497 const client = makeRFB('wss://host:8675', { shared: true });
e7dec527
SM
1498 client._rfbConnectionState = 'connecting';
1499 client._rfbInitState = 'SecurityResult';
95632e41 1500 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 1501 expect(client._sock).to.have.sent(new Uint8Array([1]));
b1dee947
SR
1502 });
1503
1504 it('should send 0 if we are not in shared mode', function () {
00674385 1505 const client = makeRFB('wss://host:8675', { shared: false });
e7dec527
SM
1506 client._rfbConnectionState = 'connecting';
1507 client._rfbInitState = 'SecurityResult';
95632e41 1508 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 1509 expect(client._sock).to.have.sent(new Uint8Array([0]));
b1dee947
SR
1510 });
1511 });
1512
1513 describe('ServerInitialisation', function () {
b1dee947 1514 beforeEach(function () {
e7dec527 1515 client._rfbInitState = 'ServerInitialisation';
b1dee947
SR
1516 });
1517
00674385 1518 function sendServerInit(opts, client) {
80187d15
SM
1519 const fullOpts = { width: 10, height: 12, bpp: 24, depth: 24, bigEndian: 0,
1520 trueColor: 1, redMax: 255, greenMax: 255, blueMax: 255,
e7dec527 1521 redShift: 16, greenShift: 8, blueShift: 0, name: 'a name' };
2b5f94fa 1522 for (let opt in opts) {
e7dec527 1523 fullOpts[opt] = opts[opt];
b1dee947 1524 }
2b5f94fa 1525 const data = [];
b1dee947 1526
e7dec527
SM
1527 push16(data, fullOpts.width);
1528 push16(data, fullOpts.height);
b1dee947 1529
e7dec527
SM
1530 data.push(fullOpts.bpp);
1531 data.push(fullOpts.depth);
80187d15
SM
1532 data.push(fullOpts.bigEndian);
1533 data.push(fullOpts.trueColor);
b1dee947 1534
e7dec527
SM
1535 push16(data, fullOpts.redMax);
1536 push16(data, fullOpts.greenMax);
1537 push16(data, fullOpts.blueMax);
1538 push8(data, fullOpts.redShift);
1539 push8(data, fullOpts.greenShift);
1540 push8(data, fullOpts.blueShift);
b1dee947
SR
1541
1542 // padding
3949a095
SR
1543 push8(data, 0);
1544 push8(data, 0);
1545 push8(data, 0);
b1dee947 1546
95632e41 1547 client._sock._websocket._receiveData(new Uint8Array(data));
b1dee947 1548
e7dec527
SM
1549 const nameData = [];
1550 let nameLen = [];
1551 pushString(nameData, fullOpts.name);
1552 push32(nameLen, nameData.length);
8d6f686b 1553
95632e41
SM
1554 client._sock._websocket._receiveData(new Uint8Array(nameLen));
1555 client._sock._websocket._receiveData(new Uint8Array(nameData));
b1dee947
SR
1556 }
1557
1558 it('should set the framebuffer width and height', function () {
00674385 1559 sendServerInit({ width: 32, height: 84 }, client);
e7dec527
SM
1560 expect(client._fbWidth).to.equal(32);
1561 expect(client._fbHeight).to.equal(84);
b1dee947
SR
1562 });
1563
1564 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
1565
1566 it('should set the framebuffer name and call the callback', function () {
2b5f94fa 1567 const spy = sinon.spy();
e89eef94 1568 client.addEventListener("desktopname", spy);
00674385 1569 sendServerInit({ name: 'som€ nam€' }, client);
b1dee947 1570
e7dec527 1571 expect(client._fbName).to.equal('som€ nam€');
b1dee947 1572 expect(spy).to.have.been.calledOnce;
8d6f686b 1573 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
b1dee947
SR
1574 });
1575
1576 it('should handle the extended init message of the tight encoding', function () {
1577 // NB(sross): we don't actually do anything with it, so just test that we can
1578 // read it w/o throwing an error
e7dec527 1579 client._rfbTightVNC = true;
00674385 1580 sendServerInit({}, client);
b1dee947 1581
e7dec527
SM
1582 const tightData = [];
1583 push16(tightData, 1);
1584 push16(tightData, 2);
1585 push16(tightData, 3);
1586 push16(tightData, 0);
2b5f94fa 1587 for (let i = 0; i < 16 + 32 + 48; i++) {
e7dec527 1588 tightData.push(i);
b1dee947 1589 }
95632e41 1590 client._sock._websocket._receiveData(new Uint8Array(tightData));
b1dee947 1591
e7dec527 1592 expect(client._rfbConnectionState).to.equal('connected');
b1dee947
SR
1593 });
1594
9b84f516 1595 it('should resize the display', function () {
b1dee947 1596 sinon.spy(client._display, 'resize');
00674385 1597 sendServerInit({ width: 27, height: 32 }, client);
b1dee947 1598
b1dee947
SR
1599 expect(client._display.resize).to.have.been.calledOnce;
1600 expect(client._display.resize).to.have.been.calledWith(27, 32);
b1dee947
SR
1601 });
1602
50cde2fa 1603 it('should grab the keyboard', function () {
b1dee947 1604 sinon.spy(client._keyboard, 'grab');
00674385 1605 sendServerInit({}, client);
b1dee947 1606 expect(client._keyboard.grab).to.have.been.calledOnce;
b1dee947
SR
1607 });
1608
69411b9e
PO
1609 describe('Initial Update Request', function () {
1610 beforeEach(function () {
1611 sinon.spy(RFB.messages, "pixelFormat");
1612 sinon.spy(RFB.messages, "clientEncodings");
1613 sinon.spy(RFB.messages, "fbUpdateRequest");
1614 });
49a81837 1615
69411b9e
PO
1616 afterEach(function () {
1617 RFB.messages.pixelFormat.restore();
1618 RFB.messages.clientEncodings.restore();
1619 RFB.messages.fbUpdateRequest.restore();
1620 });
b1dee947 1621
69411b9e
PO
1622 // TODO(directxman12): test the various options in this configuration matrix
1623 it('should reply with the pixel format, client encodings, and initial update request', function () {
00674385 1624 sendServerInit({ width: 27, height: 32 }, client);
69411b9e
PO
1625
1626 expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
1627 expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 24, true);
1628 expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
1629 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
1630 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.encodingTight);
1631 expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
1632 expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
1633 expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
1634 });
1635
1636 it('should reply with restricted settings for Intel AMT servers', function () {
00674385 1637 sendServerInit({ width: 27, height: 32, name: "Intel(r) AMT KVM"}, client);
69411b9e
PO
1638
1639 expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
1640 expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 8, true);
1641 expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
1642 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
1643 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingTight);
1644 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingHextile);
1645 expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
1646 expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
1647 expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
1648 });
b1dee947
SR
1649 });
1650
f0e4908d
PO
1651 it('should send the "connect" event', function () {
1652 let spy = sinon.spy();
1653 client.addEventListener('connect', spy);
00674385 1654 sendServerInit({}, client);
f0e4908d 1655 expect(spy).to.have.been.calledOnce;
b1dee947
SR
1656 });
1657 });
1658 });
1659
1660 describe('Protocol Message Processing After Completing Initialization', function () {
2b5f94fa 1661 let client;
b1dee947
SR
1662
1663 beforeEach(function () {
00674385 1664 client = makeRFB();
e7dec527
SM
1665 client._fbName = 'some device';
1666 client._fbWidth = 640;
1667 client._fbHeight = 20;
b1dee947
SR
1668 });
1669
1670 describe('Framebuffer Update Handling', function () {
00674385 1671 function sendFbuMsg(rectInfo, rectData, client, rectCnt) {
2b5f94fa 1672 let data = [];
b1dee947 1673
e7dec527 1674 if (!rectCnt || rectCnt > -1) {
b1dee947
SR
1675 // header
1676 data.push(0); // msg type
1677 data.push(0); // padding
e7dec527 1678 push16(data, rectCnt || rectData.length);
b1dee947
SR
1679 }
1680
e7dec527
SM
1681 for (let i = 0; i < rectData.length; i++) {
1682 if (rectInfo[i]) {
1683 push16(data, rectInfo[i].x);
1684 push16(data, rectInfo[i].y);
1685 push16(data, rectInfo[i].width);
1686 push16(data, rectInfo[i].height);
1687 push32(data, rectInfo[i].encoding);
b1dee947 1688 }
e7dec527 1689 data = data.concat(rectData[i]);
b1dee947
SR
1690 }
1691
95632e41 1692 client._sock._websocket._receiveData(new Uint8Array(data));
b1dee947
SR
1693 }
1694
1695 it('should send an update request if there is sufficient data', function () {
e7dec527
SM
1696 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
1697 RFB.messages.fbUpdateRequest(expectedMsg, true, 0, 0, 640, 20);
b1dee947 1698
651c23ec 1699 client._framebufferUpdate = () => true;
95632e41 1700 client._sock._websocket._receiveData(new Uint8Array([0]));
b1dee947 1701
e7dec527 1702 expect(client._sock).to.have.sent(expectedMsg._sQ);
b1dee947
SR
1703 });
1704
1705 it('should not send an update request if we need more data', function () {
95632e41
SM
1706 client._sock._websocket._receiveData(new Uint8Array([0]));
1707 expect(client._sock._websocket._getSentData()).to.have.length(0);
b1dee947
SR
1708 });
1709
1710 it('should resume receiving an update if we previously did not have enough data', function () {
e7dec527
SM
1711 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
1712 RFB.messages.fbUpdateRequest(expectedMsg, true, 0, 0, 640, 20);
b1dee947
SR
1713
1714 // just enough to set FBU.rects
95632e41
SM
1715 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 3]));
1716 expect(client._sock._websocket._getSentData()).to.have.length(0);
b1dee947 1717
8a189a62 1718 client._framebufferUpdate = function () { this._sock.rQskipBytes(1); return true; }; // we magically have enough data
b1dee947 1719 // 247 should *not* be used as the message type here
95632e41 1720 client._sock._websocket._receiveData(new Uint8Array([247]));
e7dec527 1721 expect(client._sock).to.have.sent(expectedMsg._sQ);
b1dee947
SR
1722 });
1723
2ba767a7 1724 it('should not send a request in continuous updates mode', function () {
76a86ff5 1725 client._enabledContinuousUpdates = true;
651c23ec 1726 client._framebufferUpdate = () => true;
95632e41 1727 client._sock._websocket._receiveData(new Uint8Array([0]));
76a86ff5 1728
95632e41 1729 expect(client._sock._websocket._getSentData()).to.have.length(0);
76a86ff5 1730 });
1731
b1dee947 1732 it('should fail on an unsupported encoding', function () {
3bb12056 1733 sinon.spy(client, "_fail");
e7dec527 1734 const rectInfo = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
00674385 1735 sendFbuMsg([rectInfo], [[]], client);
3bb12056 1736 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1737 });
1738
b1dee947 1739 describe('Message Encoding Handlers', function () {
b1dee947 1740 beforeEach(function () {
b1dee947 1741 // a really small frame
e7dec527
SM
1742 client._fbWidth = 4;
1743 client._fbHeight = 4;
1744 client._fbDepth = 24;
02329ab1 1745 client._display.resize(4, 4);
b1dee947
SR
1746 });
1747
b1dee947 1748 it('should handle the DesktopSize pseduo-encoding', function () {
b1dee947 1749 sinon.spy(client._display, 'resize');
00674385 1750 sendFbuMsg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
b1dee947 1751
e7dec527
SM
1752 expect(client._fbWidth).to.equal(20);
1753 expect(client._fbHeight).to.equal(50);
b1dee947
SR
1754
1755 expect(client._display.resize).to.have.been.calledOnce;
1756 expect(client._display.resize).to.have.been.calledWith(20, 50);
1757 });
1758
4dec490a 1759 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
4dec490a 1760 beforeEach(function () {
4dec490a 1761 // a really small frame
e7dec527
SM
1762 client._fbWidth = 4;
1763 client._fbHeight = 4;
02329ab1 1764 client._display.resize(4, 4);
4dec490a 1765 sinon.spy(client._display, 'resize');
4dec490a 1766 });
1767
00674385 1768 function makeScreenData(nrOfScreens) {
2b5f94fa 1769 const data = [];
e7dec527 1770 push8(data, nrOfScreens); // number-of-screens
3949a095
SR
1771 push8(data, 0); // padding
1772 push16(data, 0); // padding
e7dec527 1773 for (let i=0; i<nrOfScreens; i += 1) {
3949a095
SR
1774 push32(data, 0); // id
1775 push16(data, 0); // x-position
1776 push16(data, 0); // y-position
1777 push16(data, 20); // width
1778 push16(data, 50); // height
1779 push32(data, 0); // flags
4dec490a 1780 }
1781 return data;
1782 }
1783
1784 it('should handle a resize requested by this client', function () {
e7dec527
SM
1785 const reasonForChange = 1; // requested by this client
1786 const statusCode = 0; // No error
4dec490a 1787
00674385
SM
1788 sendFbuMsg([{ x: reasonForChange, y: statusCode,
1789 width: 20, height: 50, encoding: -308 }],
1790 makeScreenData(1), client);
4dec490a 1791
e7dec527
SM
1792 expect(client._fbWidth).to.equal(20);
1793 expect(client._fbHeight).to.equal(50);
4dec490a 1794
1795 expect(client._display.resize).to.have.been.calledOnce;
1796 expect(client._display.resize).to.have.been.calledWith(20, 50);
4dec490a 1797 });
1798
1799 it('should handle a resize requested by another client', function () {
e7dec527
SM
1800 const reasonForChange = 2; // requested by another client
1801 const statusCode = 0; // No error
4dec490a 1802
00674385
SM
1803 sendFbuMsg([{ x: reasonForChange, y: statusCode,
1804 width: 20, height: 50, encoding: -308 }],
1805 makeScreenData(1), client);
4dec490a 1806
e7dec527
SM
1807 expect(client._fbWidth).to.equal(20);
1808 expect(client._fbHeight).to.equal(50);
4dec490a 1809
1810 expect(client._display.resize).to.have.been.calledOnce;
1811 expect(client._display.resize).to.have.been.calledWith(20, 50);
4dec490a 1812 });
1813
1814 it('should be able to recieve requests which contain data for multiple screens', function () {
e7dec527
SM
1815 const reasonForChange = 2; // requested by another client
1816 const statusCode = 0; // No error
4dec490a 1817
00674385
SM
1818 sendFbuMsg([{ x: reasonForChange, y: statusCode,
1819 width: 60, height: 50, encoding: -308 }],
1820 makeScreenData(3), client);
4dec490a 1821
e7dec527
SM
1822 expect(client._fbWidth).to.equal(60);
1823 expect(client._fbHeight).to.equal(50);
4dec490a 1824
1825 expect(client._display.resize).to.have.been.calledOnce;
1826 expect(client._display.resize).to.have.been.calledWith(60, 50);
4dec490a 1827 });
1828
1829 it('should not handle a failed request', function () {
e7dec527
SM
1830 const reasonForChange = 1; // requested by this client
1831 const statusCode = 1; // Resize is administratively prohibited
4dec490a 1832
00674385
SM
1833 sendFbuMsg([{ x: reasonForChange, y: statusCode,
1834 width: 20, height: 50, encoding: -308 }],
1835 makeScreenData(1), client);
4dec490a 1836
e7dec527
SM
1837 expect(client._fbWidth).to.equal(4);
1838 expect(client._fbHeight).to.equal(4);
4dec490a 1839
1840 expect(client._display.resize).to.not.have.been.called;
4dec490a 1841 });
1842 });
1843
d1050405
PO
1844 describe('the Cursor pseudo-encoding handler', function () {
1845 beforeEach(function () {
1846 sinon.spy(client._cursor, 'change');
1847 });
1848
1849 it('should handle a standard cursor', function () {
1850 const info = { x: 5, y: 7,
1851 width: 4, height: 4,
1852 encoding: -239};
1853 let rect = [];
1854 let expected = [];
1855
1856 for (let i = 0;i < info.width*info.height;i++) {
1857 push32(rect, 0x11223300);
1858 }
1859 push32(rect, 0xa0a0a0a0);
1860
1861 for (let i = 0;i < info.width*info.height/2;i++) {
1862 push32(expected, 0x332211ff);
1863 push32(expected, 0x33221100);
1864 }
1865 expected = new Uint8Array(expected);
1866
00674385 1867 sendFbuMsg([info], [rect], client);
d1050405
PO
1868
1869 expect(client._cursor.change).to.have.been.calledOnce;
1870 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
1871 });
1872
1873 it('should handle an empty cursor', function () {
1874 const info = { x: 0, y: 0,
1875 width: 0, height: 0,
1876 encoding: -239};
1877 const rect = [];
1878
00674385 1879 sendFbuMsg([info], [rect], client);
d1050405
PO
1880
1881 expect(client._cursor.change).to.have.been.calledOnce;
1882 expect(client._cursor.change).to.have.been.calledWith(new Uint8Array, 0, 0, 0, 0);
1883 });
1884
1885 it('should handle a transparent cursor', function () {
1886 const info = { x: 5, y: 7,
1887 width: 4, height: 4,
1888 encoding: -239};
1889 let rect = [];
1890 let expected = [];
1891
1892 for (let i = 0;i < info.width*info.height;i++) {
1893 push32(rect, 0x11223300);
1894 }
1895 push32(rect, 0x00000000);
1896
1897 for (let i = 0;i < info.width*info.height;i++) {
1898 push32(expected, 0x33221100);
1899 }
1900 expected = new Uint8Array(expected);
1901
00674385 1902 sendFbuMsg([info], [rect], client);
d1050405
PO
1903
1904 expect(client._cursor.change).to.have.been.calledOnce;
1905 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
1906 });
1907
1908 describe('dot for empty cursor', function () {
1909 beforeEach(function () {
1910 client.showDotCursor = true;
1911 // Was called when we enabled dot cursor
c9765e50 1912 client._cursor.change.resetHistory();
d1050405
PO
1913 });
1914
1915 it('should show a standard cursor', function () {
1916 const info = { x: 5, y: 7,
1917 width: 4, height: 4,
1918 encoding: -239};
1919 let rect = [];
1920 let expected = [];
1921
1922 for (let i = 0;i < info.width*info.height;i++) {
1923 push32(rect, 0x11223300);
1924 }
1925 push32(rect, 0xa0a0a0a0);
1926
1927 for (let i = 0;i < info.width*info.height/2;i++) {
1928 push32(expected, 0x332211ff);
1929 push32(expected, 0x33221100);
1930 }
1931 expected = new Uint8Array(expected);
1932
00674385 1933 sendFbuMsg([info], [rect], client);
d1050405
PO
1934
1935 expect(client._cursor.change).to.have.been.calledOnce;
1936 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
1937 });
1938
1939 it('should handle an empty cursor', function () {
1940 const info = { x: 0, y: 0,
1941 width: 0, height: 0,
1942 encoding: -239};
1943 const rect = [];
1944 const dot = RFB.cursors.dot;
1945
00674385 1946 sendFbuMsg([info], [rect], client);
d1050405
PO
1947
1948 expect(client._cursor.change).to.have.been.calledOnce;
1949 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
1950 dot.hotx,
1951 dot.hoty,
1952 dot.w,
1953 dot.h);
1954 });
1955
1956 it('should handle a transparent cursor', function () {
1957 const info = { x: 5, y: 7,
1958 width: 4, height: 4,
1959 encoding: -239};
1960 let rect = [];
1961 const dot = RFB.cursors.dot;
1962
1963 for (let i = 0;i < info.width*info.height;i++) {
1964 push32(rect, 0x11223300);
1965 }
1966 push32(rect, 0x00000000);
1967
00674385 1968 sendFbuMsg([info], [rect], client);
d1050405
PO
1969
1970 expect(client._cursor.change).to.have.been.calledOnce;
1971 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
1972 dot.hotx,
1973 dot.hoty,
1974 dot.w,
1975 dot.h);
1976 });
1977 });
b1dee947
SR
1978 });
1979
296ba51f
NL
1980 describe('the VMware Cursor pseudo-encoding handler', function () {
1981 beforeEach(function () {
1982 sinon.spy(client._cursor, 'change');
1983 });
1984 afterEach(function () {
1985 client._cursor.change.resetHistory();
1986 });
1987
1988 it('should handle the VMware cursor pseudo-encoding', function () {
1989 let data = [0x00, 0x00, 0xff, 0,
1990 0x00, 0xff, 0x00, 0,
1991 0x00, 0xff, 0x00, 0,
1992 0x00, 0x00, 0xff, 0];
1993 let rect = [];
1994 push8(rect, 0);
1995 push8(rect, 0);
1996
1997 //AND-mask
1998 for (let i = 0; i < data.length; i++) {
1999 push8(rect, data[i]);
2000 }
2001 //XOR-mask
2002 for (let i = 0; i < data.length; i++) {
2003 push8(rect, data[i]);
2004 }
2005
00674385
SM
2006 sendFbuMsg([{ x: 0, y: 0, width: 2, height: 2,
2007 encoding: 0x574d5664}],
2008 [rect], client);
296ba51f
NL
2009 expect(client._FBU.rects).to.equal(0);
2010 });
2011
2012 it('should handle insufficient cursor pixel data', function () {
2013
2014 // Specified 14x23 pixels for the cursor,
2015 // but only send 2x2 pixels worth of data
2016 let w = 14;
2017 let h = 23;
2018 let data = [0x00, 0x00, 0xff, 0,
2019 0x00, 0xff, 0x00, 0];
2020 let rect = [];
2021
2022 push8(rect, 0);
2023 push8(rect, 0);
2024
2025 //AND-mask
2026 for (let i = 0; i < data.length; i++) {
2027 push8(rect, data[i]);
2028 }
2029 //XOR-mask
2030 for (let i = 0; i < data.length; i++) {
2031 push8(rect, data[i]);
2032 }
2033
00674385
SM
2034 sendFbuMsg([{ x: 0, y: 0, width: w, height: h,
2035 encoding: 0x574d5664}],
2036 [rect], client);
296ba51f
NL
2037
2038 // expect one FBU to remain unhandled
2039 expect(client._FBU.rects).to.equal(1);
2040 });
2041
2042 it('should update the cursor when type is classic', function () {
e7dec527 2043 let andMask =
296ba51f
NL
2044 [0xff, 0xff, 0xff, 0xff, //Transparent
2045 0xff, 0xff, 0xff, 0xff, //Transparent
2046 0x00, 0x00, 0x00, 0x00, //Opaque
2047 0xff, 0xff, 0xff, 0xff]; //Inverted
2048
e7dec527 2049 let xorMask =
296ba51f
NL
2050 [0x00, 0x00, 0x00, 0x00, //Transparent
2051 0x00, 0x00, 0x00, 0x00, //Transparent
2052 0x11, 0x22, 0x33, 0x44, //Opaque
2053 0xff, 0xff, 0xff, 0x44]; //Inverted
2054
2055 let rect = [];
2056 push8(rect, 0); //cursor_type
2057 push8(rect, 0); //padding
2058 let hotx = 0;
2059 let hoty = 0;
2060 let w = 2;
2061 let h = 2;
2062
2063 //AND-mask
e7dec527
SM
2064 for (let i = 0; i < andMask.length; i++) {
2065 push8(rect, andMask[i]);
296ba51f
NL
2066 }
2067 //XOR-mask
e7dec527
SM
2068 for (let i = 0; i < xorMask.length; i++) {
2069 push8(rect, xorMask[i]);
296ba51f
NL
2070 }
2071
e7dec527
SM
2072 let expectedRgba = [0x00, 0x00, 0x00, 0x00,
2073 0x00, 0x00, 0x00, 0x00,
2074 0x33, 0x22, 0x11, 0xff,
2075 0x00, 0x00, 0x00, 0xff];
296ba51f 2076
00674385
SM
2077 sendFbuMsg([{ x: hotx, y: hoty,
2078 width: w, height: h,
2079 encoding: 0x574d5664}],
2080 [rect], client);
296ba51f
NL
2081
2082 expect(client._cursor.change)
2083 .to.have.been.calledOnce;
2084 expect(client._cursor.change)
e7dec527 2085 .to.have.been.calledWith(expectedRgba,
296ba51f
NL
2086 hotx, hoty,
2087 w, h);
2088 });
2089
2090 it('should update the cursor when type is alpha', function () {
71bb3fdf 2091 let data = [0xee, 0x55, 0xff, 0x00, // rgba
296ba51f
NL
2092 0x00, 0xff, 0x00, 0xff,
2093 0x00, 0xff, 0x00, 0x22,
2094 0x00, 0xff, 0x00, 0x22,
2095 0x00, 0xff, 0x00, 0x22,
2096 0x00, 0x00, 0xff, 0xee];
2097 let rect = [];
2098 push8(rect, 1); //cursor_type
2099 push8(rect, 0); //padding
2100 let hotx = 0;
2101 let hoty = 0;
2102 let w = 3;
2103 let h = 2;
2104
2105 for (let i = 0; i < data.length; i++) {
2106 push8(rect, data[i]);
2107 }
2108
e7dec527
SM
2109 let expectedRgba = [0xee, 0x55, 0xff, 0x00,
2110 0x00, 0xff, 0x00, 0xff,
2111 0x00, 0xff, 0x00, 0x22,
2112 0x00, 0xff, 0x00, 0x22,
2113 0x00, 0xff, 0x00, 0x22,
2114 0x00, 0x00, 0xff, 0xee];
296ba51f 2115
00674385
SM
2116 sendFbuMsg([{ x: hotx, y: hoty,
2117 width: w, height: h,
2118 encoding: 0x574d5664}],
2119 [rect], client);
296ba51f
NL
2120
2121 expect(client._cursor.change)
2122 .to.have.been.calledOnce;
2123 expect(client._cursor.change)
e7dec527 2124 .to.have.been.calledWith(expectedRgba,
296ba51f
NL
2125 hotx, hoty,
2126 w, h);
2127 });
2128
2129 it('should not update cursor when incorrect cursor type given', function () {
2130 let rect = [];
2131 push8(rect, 3); // invalid cursor type
2132 push8(rect, 0); // padding
2133
2134 client._cursor.change.resetHistory();
00674385
SM
2135 sendFbuMsg([{ x: 0, y: 0, width: 2, height: 2,
2136 encoding: 0x574d5664}],
2137 [rect], client);
296ba51f
NL
2138
2139 expect(client._cursor.change)
2140 .to.not.have.been.called;
2141 });
2142 });
2143
b1dee947 2144 it('should handle the last_rect pseudo-encoding', function () {
00674385 2145 sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
b1dee947 2146 expect(client._FBU.rects).to.equal(0);
b1dee947 2147 });
ce66b469
NL
2148
2149 it('should handle the DesktopName pseudo-encoding', function () {
2150 let data = [];
8d6f686b
NL
2151 push32(data, 13);
2152 pushString(data, "som€ nam€");
ce66b469
NL
2153
2154 const spy = sinon.spy();
2155 client.addEventListener("desktopname", spy);
2156
00674385 2157 sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -307 }], [data], client);
ce66b469 2158
e7dec527 2159 expect(client._fbName).to.equal('som€ nam€');
ce66b469 2160 expect(spy).to.have.been.calledOnce;
8d6f686b 2161 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
ce66b469 2162 });
b1dee947
SR
2163 });
2164 });
2165
b1dee947 2166 describe('XVP Message Handling', function () {
b1dee947 2167 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
2b5f94fa 2168 const spy = sinon.spy();
e89eef94 2169 client.addEventListener("capabilities", spy);
95632e41 2170 client._sock._websocket._receiveData(new Uint8Array([250, 0, 10, 1]));
e7dec527 2171 expect(client._rfbXvpVer).to.equal(10);
e89eef94
PO
2172 expect(spy).to.have.been.calledOnce;
2173 expect(spy.args[0][0].detail.capabilities.power).to.be.true;
747b4623 2174 expect(client.capabilities.power).to.be.true;
b1dee947
SR
2175 });
2176
2177 it('should fail on unknown XVP message types', function () {
3bb12056 2178 sinon.spy(client, "_fail");
95632e41 2179 client._sock._websocket._receiveData(new Uint8Array([250, 0, 10, 237]));
3bb12056 2180 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2181 });
2182 });
2183
f73fdc3e
NL
2184 describe('Normal Clipboard Handling Receive', function () {
2185 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
e7dec527 2186 const expectedStr = 'cheese!';
f73fdc3e 2187 const data = [3, 0, 0, 0];
e7dec527
SM
2188 push32(data, expectedStr.length);
2189 for (let i = 0; i < expectedStr.length; i++) { data.push(expectedStr.charCodeAt(i)); }
f73fdc3e
NL
2190 const spy = sinon.spy();
2191 client.addEventListener("clipboard", spy);
2192
95632e41 2193 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e 2194 expect(spy).to.have.been.calledOnce;
e7dec527 2195 expect(spy.args[0][0].detail.text).to.equal(expectedStr);
f73fdc3e
NL
2196 });
2197 });
2198
2199 describe('Extended clipboard Handling', function () {
2200
2201 describe('Extended clipboard initialization', function () {
2202 beforeEach(function () {
2203 sinon.spy(RFB.messages, 'extendedClipboardCaps');
2204 });
2205
2206 afterEach(function () {
2207 RFB.messages.extendedClipboardCaps.restore();
2208 });
2209
2210 it('should update capabilities when receiving a Caps message', function () {
2211 let data = [3, 0, 0, 0];
2212 const flags = [0x1F, 0x00, 0x00, 0x03];
2213 let fileSizes = [0x00, 0x00, 0x00, 0x1E,
2214 0x00, 0x00, 0x00, 0x3C];
2215
2216 push32(data, toUnsigned32bit(-12));
2217 data = data.concat(flags);
2218 data = data.concat(fileSizes);
95632e41 2219 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2220
2221 // Check that we give an response caps when we receive one
2222 expect(RFB.messages.extendedClipboardCaps).to.have.been.calledOnce;
2223
2224 // FIXME: Can we avoid checking internal variables?
2225 expect(client._clipboardServerCapabilitiesFormats[0]).to.not.equal(true);
2226 expect(client._clipboardServerCapabilitiesFormats[1]).to.equal(true);
2227 expect(client._clipboardServerCapabilitiesFormats[2]).to.equal(true);
2228 expect(client._clipboardServerCapabilitiesActions[(1 << 24)]).to.equal(true);
2229 });
2230
2231
2232 });
2233
2234 describe('Extended Clipboard Handling Receive', function () {
2235
2236 beforeEach(function () {
2237 // Send our capabilities
2238 let data = [3, 0, 0, 0];
2239 const flags = [0x1F, 0x00, 0x00, 0x01];
2240 let fileSizes = [0x00, 0x00, 0x00, 0x1E];
2241
2242 push32(data, toUnsigned32bit(-8));
2243 data = data.concat(flags);
2244 data = data.concat(fileSizes);
95632e41 2245 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2246 });
2247
2248 describe('Handle Provide', function () {
2249 it('should update clipboard with correct Unicode data from a Provide message', function () {
2250 let expectedData = "Aå漢字!";
2251 let data = [3, 0, 0, 0];
2252 const flags = [0x10, 0x00, 0x00, 0x01];
2253
2254 /* The size 10 (utf8 encoded string size) and the
2255 string "Aå漢字!" utf8 encoded and deflated. */
2256 let deflatedData = [120, 94, 99, 96, 96, 224, 114, 60,
2257 188, 244, 217, 158, 69, 79, 215,
2258 78, 87, 4, 0, 35, 207, 6, 66];
2259
2260 // How much data we are sending.
2261 push32(data, toUnsigned32bit(-(4 + deflatedData.length)));
2262
2263 data = data.concat(flags);
2264 data = data.concat(deflatedData);
2265
2266 const spy = sinon.spy();
2267 client.addEventListener("clipboard", spy);
2268
95632e41 2269 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2270 expect(spy).to.have.been.calledOnce;
2271 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2272 client.removeEventListener("clipboard", spy);
2273 });
2274
2275 it('should update clipboard with correct escape characters from a Provide message ', function () {
2276 let expectedData = "Oh\nmy!";
2277 let data = [3, 0, 0, 0];
2278 const flags = [0x10, 0x00, 0x00, 0x01];
2279
2280 let text = encodeUTF8("Oh\r\nmy!\0");
2281
2282 let deflatedText = deflateWithSize(text);
2283
2284 // How much data we are sending.
2285 push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
ceb8ef4e
AT
2286
2287 data = data.concat(flags);
2288
2289 let sendData = new Uint8Array(data.length + deflatedText.length);
2290 sendData.set(data);
2291 sendData.set(deflatedText, data.length);
2292
2293 const spy = sinon.spy();
2294 client.addEventListener("clipboard", spy);
2295
95632e41 2296 client._sock._websocket._receiveData(sendData);
ceb8ef4e
AT
2297 expect(spy).to.have.been.calledOnce;
2298 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2299 client.removeEventListener("clipboard", spy);
2300 });
2301
2302 it('should be able to handle large Provide messages', function () {
5b5b7474 2303 let expectedData = "hello".repeat(100000);
ceb8ef4e
AT
2304 let data = [3, 0, 0, 0];
2305 const flags = [0x10, 0x00, 0x00, 0x01];
2306
2307 let text = encodeUTF8(expectedData + "\0");
2308
2309 let deflatedText = deflateWithSize(text);
2310
2311 // How much data we are sending.
2312 push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
f73fdc3e
NL
2313
2314 data = data.concat(flags);
2315
2316 let sendData = new Uint8Array(data.length + deflatedText.length);
2317 sendData.set(data);
2318 sendData.set(deflatedText, data.length);
2319
2320 const spy = sinon.spy();
2321 client.addEventListener("clipboard", spy);
2322
95632e41 2323 client._sock._websocket._receiveData(sendData);
f73fdc3e
NL
2324 expect(spy).to.have.been.calledOnce;
2325 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2326 client.removeEventListener("clipboard", spy);
2327 });
2328
2329 });
2330
2331 describe('Handle Notify', function () {
2332 beforeEach(function () {
2333 sinon.spy(RFB.messages, 'extendedClipboardRequest');
2334 });
2335
2336 afterEach(function () {
2337 RFB.messages.extendedClipboardRequest.restore();
2338 });
2339
2340 it('should make a request with supported formats when receiving a notify message', function () {
2341 let data = [3, 0, 0, 0];
2342 const flags = [0x08, 0x00, 0x00, 0x07];
2343 push32(data, toUnsigned32bit(-4));
2344 data = data.concat(flags);
2345 let expectedData = [0x01];
2346
95632e41 2347 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2348
2349 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledOnce;
2350 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledWith(client._sock, expectedData);
2351 });
2352 });
2353
2354 describe('Handle Peek', function () {
2355 beforeEach(function () {
2356 sinon.spy(RFB.messages, 'extendedClipboardNotify');
2357 });
2358
2359 afterEach(function () {
2360 RFB.messages.extendedClipboardNotify.restore();
2361 });
2362
2363 it('should send an empty Notify when receiving a Peek and no excisting clipboard data', function () {
2364 let data = [3, 0, 0, 0];
2365 const flags = [0x04, 0x00, 0x00, 0x00];
2366 push32(data, toUnsigned32bit(-4));
2367 data = data.concat(flags);
2368 let expectedData = [];
2369
95632e41 2370 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2371
2372 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2373 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2374 });
2375
2376 it('should send a Notify message with supported formats when receiving a Peek', function () {
2377 let data = [3, 0, 0, 0];
2378 const flags = [0x04, 0x00, 0x00, 0x00];
2379 push32(data, toUnsigned32bit(-4));
2380 data = data.concat(flags);
2381 let expectedData = [0x01];
2382
2383 // Needed to have clipboard data to read.
2384 // This will trigger a call to Notify, reset history
2385 client.clipboardPasteFrom("HejHej");
2386 RFB.messages.extendedClipboardNotify.resetHistory();
2387
95632e41 2388 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2389
2390 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2391 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2392 });
2393 });
2394
2395 describe('Handle Request', function () {
2396 beforeEach(function () {
2397 sinon.spy(RFB.messages, 'extendedClipboardProvide');
2398 });
2399
2400 afterEach(function () {
2401 RFB.messages.extendedClipboardProvide.restore();
2402 });
2403
2404 it('should send a Provide message with supported formats when receiving a Request', function () {
2405 let data = [3, 0, 0, 0];
2406 const flags = [0x02, 0x00, 0x00, 0x01];
2407 push32(data, toUnsigned32bit(-4));
2408 data = data.concat(flags);
2409 let expectedData = [0x01];
2410
2411 client.clipboardPasteFrom("HejHej");
2412 expect(RFB.messages.extendedClipboardProvide).to.not.have.been.called;
2413
95632e41 2414 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2415
2416 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledOnce;
2417 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledWith(client._sock, expectedData, ["HejHej"]);
2418 });
2419 });
2420 });
b1dee947 2421
b1dee947
SR
2422 });
2423
2424 it('should fire the bell callback on Bell', function () {
2b5f94fa 2425 const spy = sinon.spy();
e89eef94 2426 client.addEventListener("bell", spy);
95632e41 2427 client._sock._websocket._receiveData(new Uint8Array([2]));
e89eef94 2428 expect(spy).to.have.been.calledOnce;
b1dee947
SR
2429 });
2430
3df13262 2431 it('should respond correctly to ServerFence', function () {
e7dec527
SM
2432 const expectedMsg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
2433 const incomingMsg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
3df13262 2434
2b5f94fa 2435 const payload = "foo\x00ab9";
3df13262 2436
2437 // ClientFence and ServerFence are identical in structure
e7dec527
SM
2438 RFB.messages.clientFence(expectedMsg, (1<<0) | (1<<1), payload);
2439 RFB.messages.clientFence(incomingMsg, 0xffffffff, payload);
3df13262 2440
95632e41 2441 client._sock._websocket._receiveData(incomingMsg._sQ);
3df13262 2442
e7dec527 2443 expect(client._sock).to.have.sent(expectedMsg._sQ);
3df13262 2444
e7dec527
SM
2445 expectedMsg._sQlen = 0;
2446 incomingMsg._sQlen = 0;
3df13262 2447
e7dec527
SM
2448 RFB.messages.clientFence(expectedMsg, (1<<0), payload);
2449 RFB.messages.clientFence(incomingMsg, (1<<0) | (1<<31), payload);
3df13262 2450
95632e41 2451 client._sock._websocket._receiveData(incomingMsg._sQ);
3df13262 2452
e7dec527 2453 expect(client._sock).to.have.sent(expectedMsg._sQ);
3df13262 2454 });
2455
76a86ff5 2456 it('should enable continuous updates on first EndOfContinousUpdates', function () {
e7dec527 2457 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
76a86ff5 2458
e7dec527 2459 RFB.messages.enableContinuousUpdates(expectedMsg, true, 0, 0, 640, 20);
76a86ff5 2460
2461 expect(client._enabledContinuousUpdates).to.be.false;
2462
95632e41 2463 client._sock._websocket._receiveData(new Uint8Array([150]));
76a86ff5 2464
2465 expect(client._enabledContinuousUpdates).to.be.true;
e7dec527 2466 expect(client._sock).to.have.sent(expectedMsg._sQ);
76a86ff5 2467 });
2468
2469 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
2470 client._enabledContinuousUpdates = true;
2471 client._supportsContinuousUpdates = true;
2472
95632e41 2473 client._sock._websocket._receiveData(new Uint8Array([150]));
76a86ff5 2474
2475 expect(client._enabledContinuousUpdates).to.be.false;
2476 });
2477
2478 it('should update continuous updates on resize', function () {
e7dec527
SM
2479 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2480 RFB.messages.enableContinuousUpdates(expectedMsg, true, 0, 0, 90, 700);
76a86ff5 2481
91d5c625 2482 client._resize(450, 160);
76a86ff5 2483
95632e41 2484 expect(client._sock._websocket._getSentData()).to.have.length(0);
76a86ff5 2485
2486 client._enabledContinuousUpdates = true;
2487
91d5c625 2488 client._resize(90, 700);
76a86ff5 2489
e7dec527 2490 expect(client._sock).to.have.sent(expectedMsg._sQ);
76a86ff5 2491 });
2492
b1dee947 2493 it('should fail on an unknown message type', function () {
3bb12056 2494 sinon.spy(client, "_fail");
95632e41 2495 client._sock._websocket._receiveData(new Uint8Array([87]));
3bb12056 2496 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2497 });
2498 });
2499
2500 describe('Asynchronous Events', function () {
2b5f94fa 2501 let client;
f84bc57b
PO
2502 let pointerEvent;
2503 let keyEvent;
2504 let qemuKeyEvent;
2505
057b8fec 2506 beforeEach(function () {
00674385 2507 client = makeRFB();
f84bc57b
PO
2508 client._display.resize(100, 100);
2509
50cde2fa
PO
2510 // We need to disable this as focusing the canvas will
2511 // cause the browser to scoll to it, messing up our
2512 // client coordinate calculations
2513 client.focusOnClick = false;
2514
f84bc57b
PO
2515 pointerEvent = sinon.spy(RFB.messages, 'pointerEvent');
2516 keyEvent = sinon.spy(RFB.messages, 'keyEvent');
2517 qemuKeyEvent = sinon.spy(RFB.messages, 'QEMUExtendedKeyEvent');
057b8fec 2518 });
b1dee947 2519
f84bc57b
PO
2520 afterEach(function () {
2521 pointerEvent.restore();
2522 keyEvent.restore();
2523 qemuKeyEvent.restore();
2524 });
150596be 2525
f84bc57b
PO
2526 function elementToClient(x, y) {
2527 let res = { x: 0, y: 0 };
2528
2529 let bounds = client._canvas.getBoundingClientRect();
2530
2531 /*
2532 * If the canvas is on a fractional position we will calculate
2533 * a fractional mouse position. But that gets truncated when we
2534 * send the event, AND the same thing happens in RFB when it
2535 * generates the PointerEvent message. To compensate for that
2536 * fact we round the value upwards here.
2537 */
2538 res.x = Math.ceil(bounds.left + x);
2539 res.y = Math.ceil(bounds.top + y);
2540
2541 return res;
2542 }
2543
2544 describe('Mouse Events', function () {
50cde2fa
PO
2545 function sendMouseMoveEvent(x, y) {
2546 let pos = elementToClient(x, y);
2547 let ev;
2548
6a4c4119
PO
2549 ev = new MouseEvent('mousemove',
2550 { 'screenX': pos.x + window.screenX,
2551 'screenY': pos.y + window.screenY,
2552 'clientX': pos.x,
2553 'clientY': pos.y });
50cde2fa
PO
2554 client._canvas.dispatchEvent(ev);
2555 }
2556
2557 function sendMouseButtonEvent(x, y, down, button) {
2558 let pos = elementToClient(x, y);
2559 let ev;
2560
6a4c4119
PO
2561 ev = new MouseEvent(down ? 'mousedown' : 'mouseup',
2562 { 'screenX': pos.x + window.screenX,
2563 'screenY': pos.y + window.screenY,
2564 'clientX': pos.x,
2565 'clientY': pos.y,
2566 'button': button,
2567 'buttons': 1 << button });
50cde2fa
PO
2568 client._canvas.dispatchEvent(ev);
2569 }
2570
b1dee947 2571 it('should not send button messages in view-only mode', function () {
747b4623 2572 client._viewOnly = true;
50cde2fa
PO
2573 sendMouseButtonEvent(10, 10, true, 0);
2574 clock.tick(50);
2575 expect(pointerEvent).to.not.have.been.called;
b1dee947
SR
2576 });
2577
2578 it('should not send movement messages in view-only mode', function () {
747b4623 2579 client._viewOnly = true;
50cde2fa
PO
2580 sendMouseMoveEvent(10, 10);
2581 clock.tick(50);
2582 expect(pointerEvent).to.not.have.been.called;
b1dee947
SR
2583 });
2584
50cde2fa
PO
2585 it('should handle left mouse button', function () {
2586 sendMouseButtonEvent(10, 10, true, 0);
2587
2588 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2589 10, 10, 0x1);
2590 pointerEvent.resetHistory();
2591
2592 sendMouseButtonEvent(10, 10, false, 0);
b1dee947 2593
50cde2fa
PO
2594 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2595 10, 10, 0x0);
d02a99f0
SR
2596 });
2597
50cde2fa
PO
2598 it('should handle middle mouse button', function () {
2599 sendMouseButtonEvent(10, 10, true, 1);
2600
2601 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2602 10, 10, 0x2);
2603 pointerEvent.resetHistory();
2604
2605 sendMouseButtonEvent(10, 10, false, 1);
2606
2607 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2608 10, 10, 0x0);
d02a99f0
SR
2609 });
2610
50cde2fa
PO
2611 it('should handle right mouse button', function () {
2612 sendMouseButtonEvent(10, 10, true, 2);
2613
2614 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2615 10, 10, 0x4);
2616 pointerEvent.resetHistory();
2617
2618 sendMouseButtonEvent(10, 10, false, 2);
2619
2620 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2621 10, 10, 0x0);
b1dee947
SR
2622 });
2623
50cde2fa
PO
2624 it('should handle multiple mouse buttons', function () {
2625 sendMouseButtonEvent(10, 10, true, 0);
2626 sendMouseButtonEvent(10, 10, true, 2);
2627
2628 expect(pointerEvent).to.have.been.calledTwice;
2629 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2630 10, 10, 0x1);
2631 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2632 10, 10, 0x5);
2633
2634 pointerEvent.resetHistory();
2635
2636 sendMouseButtonEvent(10, 10, false, 0);
2637 sendMouseButtonEvent(10, 10, false, 2);
2638
2639 expect(pointerEvent).to.have.been.calledTwice;
2640 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2641 10, 10, 0x4);
2642 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2643 10, 10, 0x0);
150596be
SM
2644 });
2645
50cde2fa
PO
2646 it('should handle mouse movement', function () {
2647 sendMouseMoveEvent(50, 70);
2648 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2649 50, 70, 0x0);
150596be
SM
2650 });
2651
50cde2fa
PO
2652 it('should handle click and drag', function () {
2653 sendMouseButtonEvent(10, 10, true, 0);
2654 sendMouseMoveEvent(50, 70);
2655
2656 expect(pointerEvent).to.have.been.calledTwice;
2657 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2658 10, 10, 0x1);
2659 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2660 50, 70, 0x1);
2661
2662 pointerEvent.resetHistory();
2663
2664 sendMouseButtonEvent(50, 70, false, 0);
2665
2666 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2667 50, 70, 0x0);
150596be
SM
2668 });
2669
50cde2fa
PO
2670 describe('Event Aggregation', function () {
2671 it('should send a single pointer event on mouse movement', function () {
2672 sendMouseMoveEvent(50, 70);
2673 clock.tick(100);
2674 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2675 50, 70, 0x0);
2676 });
150596be 2677
50cde2fa
PO
2678 it('should delay one move if two events are too close', function () {
2679 sendMouseMoveEvent(18, 30);
2680 sendMouseMoveEvent(20, 50);
150596be 2681
50cde2fa
PO
2682 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2683 18, 30, 0x0);
2684 pointerEvent.resetHistory();
150596be 2685
50cde2fa 2686 clock.tick(100);
150596be 2687
50cde2fa
PO
2688 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2689 20, 50, 0x0);
2690 });
2691
2692 it('should only send first and last move of many close events', function () {
2693 sendMouseMoveEvent(18, 30);
2694 sendMouseMoveEvent(20, 50);
2695 sendMouseMoveEvent(21, 55);
2696
2697 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2698 18, 30, 0x0);
2699 pointerEvent.resetHistory();
2700
2701 clock.tick(100);
2702
2703 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2704 21, 55, 0x0);
2705 });
2706
2707 // We selected the 17ms since that is ~60 FPS
2708 it('should send move events every 17 ms', function () {
2709 sendMouseMoveEvent(1, 10); // instant send
2710 clock.tick(10);
2711
2712 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2713 1, 10, 0x0);
2714 pointerEvent.resetHistory();
2715
2716 sendMouseMoveEvent(2, 20); // delayed
2717 clock.tick(10); // timeout send
2718
2719 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2720 2, 20, 0x0);
2721 pointerEvent.resetHistory();
2722
2723 sendMouseMoveEvent(3, 30); // delayed
2724 clock.tick(10);
2725 sendMouseMoveEvent(4, 40); // delayed
2726 clock.tick(10); // timeout send
2727
2728 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2729 4, 40, 0x0);
2730 pointerEvent.resetHistory();
2731
2732 sendMouseMoveEvent(5, 50); // delayed
2733
2734 expect(pointerEvent).to.not.have.been.called;
2735 });
2736
2737 it('should send waiting move events before a button press', function () {
2738 sendMouseMoveEvent(13, 9);
2739
2740 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2741 13, 9, 0x0);
2742 pointerEvent.resetHistory();
2743
2744 sendMouseMoveEvent(20, 70);
2745
2746 expect(pointerEvent).to.not.have.been.called;
2747
2748 sendMouseButtonEvent(20, 70, true, 0);
2749
2750 expect(pointerEvent).to.have.been.calledTwice;
2751 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2752 20, 70, 0x0);
2753 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2754 20, 70, 0x1);
2755 });
2756
2757 it('should send move events with enough time apart normally', function () {
2758 sendMouseMoveEvent(58, 60);
2759
2760 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2761 58, 60, 0x0);
2762 pointerEvent.resetHistory();
2763
2764 clock.tick(20);
2765
2766 sendMouseMoveEvent(25, 60);
2767
2768 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2769 25, 60, 0x0);
2770 pointerEvent.resetHistory();
2771 });
2772
2773 it('should not send waiting move events if disconnected', function () {
2774 sendMouseMoveEvent(88, 99);
2775
2776 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2777 88, 99, 0x0);
2778 pointerEvent.resetHistory();
2779
2780 sendMouseMoveEvent(66, 77);
2781 client.disconnect();
2782 clock.tick(20);
2783
2784 expect(pointerEvent).to.not.have.been.called;
2785 });
150596be
SM
2786 });
2787
50cde2fa
PO
2788 it.skip('should block click events', function () {
2789 /* FIXME */
2790 });
2791
2792 it.skip('should block contextmenu events', function () {
2793 /* FIXME */
b1dee947 2794 });
b1dee947
SR
2795 });
2796
f84bc57b
PO
2797 describe('Wheel Events', function () {
2798 function sendWheelEvent(x, y, dx, dy, mode=0) {
2799 let pos = elementToClient(x, y);
2800 let ev;
2801
6a4c4119
PO
2802 ev = new WheelEvent('wheel',
2803 { 'screenX': pos.x + window.screenX,
2804 'screenY': pos.y + window.screenY,
2805 'clientX': pos.x,
2806 'clientY': pos.y,
2807 'deltaX': dx,
2808 'deltaY': dy,
2809 'deltaMode': mode });
f84bc57b
PO
2810 client._canvas.dispatchEvent(ev);
2811 }
2812
2813 it('should handle wheel up event', function () {
2814 sendWheelEvent(10, 10, 0, -50);
2815
2816 expect(pointerEvent).to.have.been.calledTwice;
2817 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2818 10, 10, 1<<3);
2819 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2820 10, 10, 0);
2821 });
2822
2823 it('should handle wheel down event', function () {
2824 sendWheelEvent(10, 10, 0, 50);
2825
2826 expect(pointerEvent).to.have.been.calledTwice;
2827 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2828 10, 10, 1<<4);
2829 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2830 10, 10, 0);
2831 });
2832
2833 it('should handle wheel left event', function () {
2834 sendWheelEvent(10, 10, -50, 0);
2835
2836 expect(pointerEvent).to.have.been.calledTwice;
2837 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2838 10, 10, 1<<5);
2839 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2840 10, 10, 0);
2841 });
2842
2843 it('should handle wheel right event', function () {
2844 sendWheelEvent(10, 10, 50, 0);
2845
2846 expect(pointerEvent).to.have.been.calledTwice;
2847 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2848 10, 10, 1<<6);
2849 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2850 10, 10, 0);
2851 });
2852
2853 it('should ignore wheel when in view only', function () {
2854 client._viewOnly = true;
2855
2856 sendWheelEvent(10, 10, 50, 0);
2857
2858 expect(pointerEvent).to.not.have.been.called;
2859 });
2860
2861 it('should accumulate wheel events if small enough', function () {
88589a44
PO
2862 sendWheelEvent(10, 10, 0, 20);
2863 sendWheelEvent(10, 10, 0, 20);
f84bc57b
PO
2864
2865 expect(pointerEvent).to.not.have.been.called;
2866
88589a44 2867 sendWheelEvent(10, 10, 0, 20);
f84bc57b
PO
2868
2869 expect(pointerEvent).to.have.been.calledTwice;
2870 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2871 10, 10, 1<<4);
2872 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2873 10, 10, 0);
2874 });
2875
2876 it('should not accumulate large wheel events', function () {
2877 sendWheelEvent(10, 10, 0, 400);
2878
2879 expect(pointerEvent).to.have.been.calledTwice;
2880 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2881 10, 10, 1<<4);
2882 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2883 10, 10, 0);
2884 });
2885
2886 it('should handle line based wheel event', function () {
88589a44 2887 sendWheelEvent(10, 10, 0, 3, 1);
f84bc57b
PO
2888
2889 expect(pointerEvent).to.have.been.calledTwice;
2890 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2891 10, 10, 1<<4);
2892 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2893 10, 10, 0);
2894 });
2895
2896 it('should handle page based wheel event', function () {
88589a44 2897 sendWheelEvent(10, 10, 0, 3, 2);
f84bc57b
PO
2898
2899 expect(pointerEvent).to.have.been.calledTwice;
2900 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2901 10, 10, 1<<4);
2902 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2903 10, 10, 0);
2904 });
2905 });
2906
2907 describe('Keyboard Events', function () {
b1dee947 2908 it('should send a key message on a key press', function () {
747b4623 2909 client._handleKeyEvent(0x41, 'KeyA', true);
e7dec527
SM
2910 const keyMsg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
2911 RFB.messages.keyEvent(keyMsg, 0x41, 1);
2912 expect(client._sock).to.have.sent(keyMsg._sQ);
b1dee947
SR
2913 });
2914
2915 it('should not send messages in view-only mode', function () {
747b4623 2916 client._viewOnly = true;
057b8fec 2917 sinon.spy(client._sock, 'flush');
747b4623 2918 client._handleKeyEvent('a', 'KeyA', true);
9ff86fb7 2919 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2920 });
2921 });
2922
8be924c9 2923 describe('Gesture event handlers', function () {
8be924c9
PO
2924 function gestureStart(gestureType, x, y,
2925 magnitudeX = 0, magnitudeY = 0) {
2926 let pos = elementToClient(x, y);
2927 let detail = {type: gestureType, clientX: pos.x, clientY: pos.y};
2928
2929 detail.magnitudeX = magnitudeX;
2930 detail.magnitudeY = magnitudeY;
2931
2932 let ev = new CustomEvent('gesturestart', { detail: detail });
2933 client._canvas.dispatchEvent(ev);
2934 }
2935
2936 function gestureMove(gestureType, x, y,
2937 magnitudeX = 0, magnitudeY = 0) {
2938 let pos = elementToClient(x, y);
2939 let detail = {type: gestureType, clientX: pos.x, clientY: pos.y};
2940
2941 detail.magnitudeX = magnitudeX;
2942 detail.magnitudeY = magnitudeY;
2943
2944 let ev = new CustomEvent('gesturemove', { detail: detail });
2945 client._canvas.dispatchEvent(ev);
2946 }
2947
2948 function gestureEnd(gestureType, x, y) {
2949 let pos = elementToClient(x, y);
2950 let detail = {type: gestureType, clientX: pos.x, clientY: pos.y};
2951 let ev = new CustomEvent('gestureend', { detail: detail });
2952 client._canvas.dispatchEvent(ev);
2953 }
2954
2955 describe('Gesture onetap', function () {
2956 it('should handle onetap events', function () {
2957 let bmask = 0x1;
2958
2959 gestureStart('onetap', 20, 40);
2960 gestureEnd('onetap', 20, 40);
2961
2962 expect(pointerEvent).to.have.been.calledThrice;
2963 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2964 20, 40, 0x0);
2965 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2966 20, 40, bmask);
2967 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2968 20, 40, 0x0);
2969 });
2970
2971 it('should keep same position for multiple onetap events', function () {
2972 let bmask = 0x1;
2973
2974 gestureStart('onetap', 20, 40);
2975 gestureEnd('onetap', 20, 40);
2976
2977 expect(pointerEvent).to.have.been.calledThrice;
2978 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2979 20, 40, 0x0);
2980 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2981 20, 40, bmask);
2982 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2983 20, 40, 0x0);
2984
2985 pointerEvent.resetHistory();
2986
2987 gestureStart('onetap', 20, 50);
2988 gestureEnd('onetap', 20, 50);
2989
2990 expect(pointerEvent).to.have.been.calledThrice;
2991 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2992 20, 40, 0x0);
2993 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2994 20, 40, bmask);
2995 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2996 20, 40, 0x0);
2997
2998 pointerEvent.resetHistory();
2999
3000 gestureStart('onetap', 30, 50);
3001 gestureEnd('onetap', 30, 50);
3002
3003 expect(pointerEvent).to.have.been.calledThrice;
3004 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3005 20, 40, 0x0);
3006 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3007 20, 40, bmask);
3008 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3009 20, 40, 0x0);
3010 });
3011
3012 it('should not keep same position for onetap events when too far apart', function () {
3013 let bmask = 0x1;
3014
3015 gestureStart('onetap', 20, 40);
3016 gestureEnd('onetap', 20, 40);
3017
3018 expect(pointerEvent).to.have.been.calledThrice;
3019 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3020 20, 40, 0x0);
3021 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3022 20, 40, bmask);
3023 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3024 20, 40, 0x0);
3025
3026 pointerEvent.resetHistory();
3027
3028 gestureStart('onetap', 80, 95);
3029 gestureEnd('onetap', 80, 95);
3030
3031 expect(pointerEvent).to.have.been.calledThrice;
3032 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3033 80, 95, 0x0);
3034 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3035 80, 95, bmask);
3036 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3037 80, 95, 0x0);
3038 });
3039
3040 it('should not keep same position for onetap events when enough time inbetween', function () {
3041 let bmask = 0x1;
3042
3043 gestureStart('onetap', 10, 20);
3044 gestureEnd('onetap', 10, 20);
3045
3046 expect(pointerEvent).to.have.been.calledThrice;
3047 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3048 10, 20, 0x0);
3049 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3050 10, 20, bmask);
3051 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3052 10, 20, 0x0);
3053
3054 pointerEvent.resetHistory();
3055 this.clock.tick(1500);
3056
3057 gestureStart('onetap', 15, 20);
3058 gestureEnd('onetap', 15, 20);
3059
3060 expect(pointerEvent).to.have.been.calledThrice;
3061 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3062 15, 20, 0x0);
3063 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3064 15, 20, bmask);
3065 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3066 15, 20, 0x0);
3067
3068 pointerEvent.resetHistory();
3069 });
3070 });
3071
3072 describe('Gesture twotap', function () {
3073 it('should handle gesture twotap events', function () {
3074 let bmask = 0x4;
3075
3076 gestureStart("twotap", 20, 40);
3077
3078 expect(pointerEvent).to.have.been.calledThrice;
3079 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3080 20, 40, 0x0);
3081 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3082 20, 40, bmask);
3083 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3084 20, 40, 0x0);
3085 });
3086
3087 it('should keep same position for multiple twotap events', function () {
3088 let bmask = 0x4;
3089
3090 for (let offset = 0;offset < 30;offset += 10) {
3091 pointerEvent.resetHistory();
3092
3093 gestureStart('twotap', 20, 40 + offset);
3094 gestureEnd('twotap', 20, 40 + offset);
3095
3096 expect(pointerEvent).to.have.been.calledThrice;
3097 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3098 20, 40, 0x0);
3099 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3100 20, 40, bmask);
3101 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3102 20, 40, 0x0);
3103 }
3104 });
3105 });
3106
3107 describe('Gesture threetap', function () {
3108 it('should handle gesture start for threetap events', function () {
3109 let bmask = 0x2;
3110
3111 gestureStart("threetap", 20, 40);
3112
3113 expect(pointerEvent).to.have.been.calledThrice;
3114 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3115 20, 40, 0x0);
3116 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3117 20, 40, bmask);
3118 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3119 20, 40, 0x0);
3120 });
3121
3122 it('should keep same position for multiple threetap events', function () {
3123 let bmask = 0x2;
3124
3125 for (let offset = 0;offset < 30;offset += 10) {
3126 pointerEvent.resetHistory();
3127
3128 gestureStart('threetap', 20, 40 + offset);
3129 gestureEnd('threetap', 20, 40 + offset);
3130
3131 expect(pointerEvent).to.have.been.calledThrice;
3132 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3133 20, 40, 0x0);
3134 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3135 20, 40, bmask);
3136 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3137 20, 40, 0x0);
3138 }
3139 });
3140 });
3141
3142 describe('Gesture drag', function () {
3143 it('should handle gesture drag events', function () {
3144 let bmask = 0x1;
3145
3146 gestureStart('drag', 20, 40);
3147
3148 expect(pointerEvent).to.have.been.calledTwice;
3149 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3150 20, 40, 0x0);
3151 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3152 20, 40, bmask);
3153
3154 pointerEvent.resetHistory();
3155
3156 gestureMove('drag', 30, 50);
3157 clock.tick(50);
3158
3159 expect(pointerEvent).to.have.been.calledOnce;
3160 expect(pointerEvent).to.have.been.calledWith(client._sock,
3161 30, 50, bmask);
3162
3163 pointerEvent.resetHistory();
3164
3165 gestureEnd('drag', 30, 50);
3166
3167 expect(pointerEvent).to.have.been.calledTwice;
3168 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3169 30, 50, bmask);
3170 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3171 30, 50, 0x0);
3172 });
3173 });
3174
3175 describe('Gesture long press', function () {
3176 it('should handle long press events', function () {
3177 let bmask = 0x4;
3178
3179 gestureStart('longpress', 20, 40);
3180
3181 expect(pointerEvent).to.have.been.calledTwice;
3182 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3183 20, 40, 0x0);
3184 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3185 20, 40, bmask);
3186 pointerEvent.resetHistory();
3187
3188 gestureMove('longpress', 40, 60);
3189 clock.tick(50);
3190
3191 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3192 40, 60, bmask);
3193
3194 pointerEvent.resetHistory();
3195
3196 gestureEnd('longpress', 40, 60);
3197
3198 expect(pointerEvent).to.have.been.calledTwice;
3199 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3200 40, 60, bmask);
3201 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3202 40, 60, 0x0);
3203 });
3204 });
3205
3206 describe('Gesture twodrag', function () {
3207 it('should handle gesture twodrag up events', function () {
3208 let bmask = 0x10; // Button mask for scroll down
3209
3210 gestureStart('twodrag', 20, 40, 0, 0);
3211
3212 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3213 20, 40, 0x0);
3214
3215 pointerEvent.resetHistory();
3216
3217 gestureMove('twodrag', 20, 40, 0, -60);
3218
3219 expect(pointerEvent).to.have.been.calledThrice;
3220 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3221 20, 40, 0x0);
3222 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3223 20, 40, bmask);
3224 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3225 20, 40, 0x0);
3226 });
3227
3228 it('should handle gesture twodrag down events', function () {
3229 let bmask = 0x8; // Button mask for scroll up
3230
3231 gestureStart('twodrag', 20, 40, 0, 0);
3232
3233 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3234 20, 40, 0x0);
3235
3236 pointerEvent.resetHistory();
3237
3238 gestureMove('twodrag', 20, 40, 0, 60);
3239
3240 expect(pointerEvent).to.have.been.calledThrice;
3241 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3242 20, 40, 0x0);
3243 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3244 20, 40, bmask);
3245 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3246 20, 40, 0x0);
3247 });
3248
3249 it('should handle gesture twodrag right events', function () {
3250 let bmask = 0x20; // Button mask for scroll right
3251
3252 gestureStart('twodrag', 20, 40, 0, 0);
3253
3254 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3255 20, 40, 0x0);
3256
3257 pointerEvent.resetHistory();
3258
3259 gestureMove('twodrag', 20, 40, 60, 0);
3260
3261 expect(pointerEvent).to.have.been.calledThrice;
3262 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3263 20, 40, 0x0);
3264 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3265 20, 40, bmask);
3266 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3267 20, 40, 0x0);
3268 });
3269
3270 it('should handle gesture twodrag left events', function () {
3271 let bmask = 0x40; // Button mask for scroll left
3272
3273 gestureStart('twodrag', 20, 40, 0, 0);
3274
3275 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3276 20, 40, 0x0);
3277
3278 pointerEvent.resetHistory();
3279
3280 gestureMove('twodrag', 20, 40, -60, 0);
3281
3282 expect(pointerEvent).to.have.been.calledThrice;
3283 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3284 20, 40, 0x0);
3285 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3286 20, 40, bmask);
3287 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3288 20, 40, 0x0);
3289 });
3290
3291 it('should handle gesture twodrag diag events', function () {
3292 let scrlUp = 0x8; // Button mask for scroll up
3293 let scrlRight = 0x20; // Button mask for scroll right
3294
3295 gestureStart('twodrag', 20, 40, 0, 0);
3296
3297 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3298 20, 40, 0x0);
3299
3300 pointerEvent.resetHistory();
3301
3302 gestureMove('twodrag', 20, 40, 60, 60);
3303
3304 expect(pointerEvent).to.have.been.callCount(5);
3305 expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,
3306 20, 40, 0x0);
3307 expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,
3308 20, 40, scrlUp);
3309 expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,
3310 20, 40, 0x0);
3311 expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,
3312 20, 40, scrlRight);
3313 expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,
3314 20, 40, 0x0);
3315 });
3316
3317 it('should handle multiple small gesture twodrag events', function () {
3318 let bmask = 0x8; // Button mask for scroll up
3319
3320 gestureStart('twodrag', 20, 40, 0, 0);
3321
3322 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3323 20, 40, 0x0);
3324
3325 pointerEvent.resetHistory();
3326
3327 gestureMove('twodrag', 20, 40, 0, 10);
3328 clock.tick(50);
3329
3330 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3331 20, 40, 0x0);
3332
3333 pointerEvent.resetHistory();
3334
3335 gestureMove('twodrag', 20, 40, 0, 20);
3336 clock.tick(50);
3337
3338 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3339 20, 40, 0x0);
3340
3341 pointerEvent.resetHistory();
3342
3343 gestureMove('twodrag', 20, 40, 0, 60);
3344
3345 expect(pointerEvent).to.have.been.calledThrice;
3346 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3347 20, 40, 0x0);
3348 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3349 20, 40, bmask);
3350 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3351 20, 40, 0x0);
3352 });
3353
3354 it('should handle large gesture twodrag events', function () {
3355 let bmask = 0x8; // Button mask for scroll up
3356
3357 gestureStart('twodrag', 30, 50, 0, 0);
3358
3359 expect(pointerEvent).
3360 to.have.been.calledOnceWith(client._sock, 30, 50, 0x0);
3361
3362 pointerEvent.resetHistory();
3363
3364 gestureMove('twodrag', 30, 50, 0, 200);
3365
3366 expect(pointerEvent).to.have.callCount(7);
3367 expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,
3368 30, 50, 0x0);
3369 expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,
3370 30, 50, bmask);
3371 expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,
3372 30, 50, 0x0);
3373 expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,
3374 30, 50, bmask);
3375 expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,
3376 30, 50, 0x0);
3377 expect(pointerEvent.getCall(5)).to.have.been.calledWith(client._sock,
3378 30, 50, bmask);
3379 expect(pointerEvent.getCall(6)).to.have.been.calledWith(client._sock,
3380 30, 50, 0x0);
3381 });
3382 });
3383
3384 describe('Gesture pinch', function () {
8be924c9
PO
3385 it('should handle gesture pinch in events', function () {
3386 let keysym = KeyTable.XK_Control_L;
3387 let bmask = 0x10; // Button mask for scroll down
3388
3389 gestureStart('pinch', 20, 40, 90, 90);
3390
3391 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3392 20, 40, 0x0);
3393 expect(keyEvent).to.not.have.been.called;
3394
3395 pointerEvent.resetHistory();
3396
3397 gestureMove('pinch', 20, 40, 30, 30);
3398
3399 expect(pointerEvent).to.have.been.calledThrice;
3400 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3401 20, 40, 0x0);
3402 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3403 20, 40, bmask);
3404 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3405 20, 40, 0x0);
3406
3407 expect(keyEvent).to.have.been.calledTwice;
3408 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3409 keysym, 1);
3410 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3411 keysym, 0);
3412
3413 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3414 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3415
3416 pointerEvent.resetHistory();
3417 keyEvent.resetHistory();
3418
3419 gestureEnd('pinch', 20, 40);
3420
3421 expect(pointerEvent).to.not.have.been.called;
3422 expect(keyEvent).to.not.have.been.called;
3423 });
3424
3425 it('should handle gesture pinch out events', function () {
3426 let keysym = KeyTable.XK_Control_L;
3427 let bmask = 0x8; // Button mask for scroll up
3428
3429 gestureStart('pinch', 10, 20, 10, 20);
3430
3431 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3432 10, 20, 0x0);
3433 expect(keyEvent).to.not.have.been.called;
3434
3435 pointerEvent.resetHistory();
3436
3437 gestureMove('pinch', 10, 20, 70, 80);
3438
3439 expect(pointerEvent).to.have.been.calledThrice;
3440 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3441 10, 20, 0x0);
3442 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3443 10, 20, bmask);
3444 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3445 10, 20, 0x0);
3446
3447 expect(keyEvent).to.have.been.calledTwice;
3448 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3449 keysym, 1);
3450 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3451 keysym, 0);
3452
3453 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3454 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3455
3456 pointerEvent.resetHistory();
3457 keyEvent.resetHistory();
3458
3459 gestureEnd('pinch', 10, 20);
3460
3461 expect(pointerEvent).to.not.have.been.called;
3462 expect(keyEvent).to.not.have.been.called;
3463 });
3464
3465 it('should handle large gesture pinch', function () {
3466 let keysym = KeyTable.XK_Control_L;
3467 let bmask = 0x10; // Button mask for scroll down
3468
3469 gestureStart('pinch', 20, 40, 150, 150);
3470
3471 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3472 20, 40, 0x0);
3473 expect(keyEvent).to.not.have.been.called;
3474
3475 pointerEvent.resetHistory();
3476
3477 gestureMove('pinch', 20, 40, 30, 30);
3478
3479 expect(pointerEvent).to.have.been.callCount(5);
3480 expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,
3481 20, 40, 0x0);
3482 expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,
3483 20, 40, bmask);
3484 expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,
3485 20, 40, 0x0);
3486 expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,
3487 20, 40, bmask);
3488 expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,
3489 20, 40, 0x0);
3490
3491 expect(keyEvent).to.have.been.calledTwice;
3492 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3493 keysym, 1);
3494 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3495 keysym, 0);
3496
3497 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3498 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3499
3500 pointerEvent.resetHistory();
3501 keyEvent.resetHistory();
3502
3503 gestureEnd('pinch', 20, 40);
3504
3505 expect(pointerEvent).to.not.have.been.called;
3506 expect(keyEvent).to.not.have.been.called;
3507 });
3508
3509 it('should handle multiple small gesture pinch out events', function () {
3510 let keysym = KeyTable.XK_Control_L;
3511 let bmask = 0x8; // Button mask for scroll down
3512
3513 gestureStart('pinch', 20, 40, 0, 10);
3514
3515 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3516 20, 40, 0x0);
3517 expect(keyEvent).to.not.have.been.called;
3518
3519 pointerEvent.resetHistory();
3520
3521 gestureMove('pinch', 20, 40, 0, 30);
3522 clock.tick(50);
3523
3524 expect(pointerEvent).to.have.been.calledWith(client._sock,
3525 20, 40, 0x0);
3526
3527 pointerEvent.resetHistory();
3528
3529 gestureMove('pinch', 20, 40, 0, 60);
3530 clock.tick(50);
3531
3532 expect(pointerEvent).to.have.been.calledWith(client._sock,
3533 20, 40, 0x0);
3534
3535 pointerEvent.resetHistory();
3536 keyEvent.resetHistory();
3537
3538 gestureMove('pinch', 20, 40, 0, 90);
3539
3540 expect(pointerEvent).to.have.been.calledThrice;
3541 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3542 20, 40, 0x0);
3543 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3544 20, 40, bmask);
3545 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3546 20, 40, 0x0);
3547
3548 expect(keyEvent).to.have.been.calledTwice;
3549 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3550 keysym, 1);
3551 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3552 keysym, 0);
3553
3554 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3555 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3556
3557 pointerEvent.resetHistory();
3558 keyEvent.resetHistory();
3559
3560 gestureEnd('pinch', 20, 40);
3561
3562 expect(keyEvent).to.not.have.been.called;
3563 });
3564
3565 it('should send correct key control code', function () {
3566 let keysym = KeyTable.XK_Control_L;
3567 let code = 0x1d;
3568 let bmask = 0x10; // Button mask for scroll down
3569
3570 client._qemuExtKeyEventSupported = true;
3571
3572 gestureStart('pinch', 20, 40, 90, 90);
3573
3574 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3575 20, 40, 0x0);
3576 expect(qemuKeyEvent).to.not.have.been.called;
3577
3578 pointerEvent.resetHistory();
3579
3580 gestureMove('pinch', 20, 40, 30, 30);
3581
3582 expect(pointerEvent).to.have.been.calledThrice;
3583 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3584 20, 40, 0x0);
3585 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3586 20, 40, bmask);
3587 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3588 20, 40, 0x0);
3589
3590 expect(qemuKeyEvent).to.have.been.calledTwice;
3591 expect(qemuKeyEvent.firstCall).to.have.been.calledWith(client._sock,
3592 keysym,
3593 true,
3594 code);
3595 expect(qemuKeyEvent.secondCall).to.have.been.calledWith(client._sock,
3596 keysym,
3597 false,
3598 code);
3599
3600 expect(qemuKeyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3601 expect(qemuKeyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3602
3603 pointerEvent.resetHistory();
3604 qemuKeyEvent.resetHistory();
3605
3606 gestureEnd('pinch', 20, 40);
3607
3608 expect(pointerEvent).to.not.have.been.called;
3609 expect(qemuKeyEvent).to.not.have.been.called;
3610 });
3611 });
3612 });
3613
3614 describe('WebSocket Events', function () {
b1dee947 3615 // message events
d80d9d37 3616 it('should do nothing if we receive an empty message and have nothing in the queue', function () {
00674385 3617 client._normalMsg = sinon.spy();
95632e41 3618 client._sock._websocket._receiveData(new Uint8Array([]));
00674385 3619 expect(client._normalMsg).to.not.have.been.called;
b1dee947
SR
3620 });
3621
c2a4d3ef 3622 it('should handle a message in the connected state as a normal message', function () {
00674385 3623 client._normalMsg = sinon.spy();
95632e41 3624 client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));
00674385 3625 expect(client._normalMsg).to.have.been.called;
b1dee947
SR
3626 });
3627
3628 it('should handle a message in any non-disconnected/failed state like an init message', function () {
e7dec527
SM
3629 client._rfbConnectionState = 'connecting';
3630 client._rfbInitState = 'ProtocolVersion';
00674385 3631 client._initMsg = sinon.spy();
95632e41 3632 client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));
00674385 3633 expect(client._initMsg).to.have.been.called;
b1dee947
SR
3634 });
3635
9535539b 3636 it('should process all normal messages directly', function () {
2b5f94fa 3637 const spy = sinon.spy();
e89eef94 3638 client.addEventListener("bell", spy);
95632e41 3639 client._sock._websocket._receiveData(new Uint8Array([0x02, 0x02]));
e89eef94 3640 expect(spy).to.have.been.calledTwice;
b1dee947
SR
3641 });
3642
3643 // open events
c2a4d3ef 3644 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
9b84f516 3645 client = new RFB(document.createElement('div'), 'wss://host:8675');
2f4516f2 3646 this.clock.tick();
b1dee947 3647 client._sock._websocket._open();
e7dec527 3648 expect(client._rfbInitState).to.equal('ProtocolVersion');
b1dee947
SR
3649 });
3650
3651 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
3bb12056 3652 sinon.spy(client, "_fail");
e7dec527 3653 client._rfbConnectionState = 'connected';
b1dee947 3654 client._sock._websocket._open();
3bb12056 3655 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
3656 });
3657
3658 // close events
c2a4d3ef 3659 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2b5f94fa 3660 const real = client._sock._websocket.close;
651c23ec 3661 client._sock._websocket.close = () => {};
bb25d3d6 3662 client.disconnect();
e7dec527 3663 expect(client._rfbConnectionState).to.equal('disconnecting');
bb25d3d6 3664 client._sock._websocket.close = real;
b1dee947 3665 client._sock._websocket.close();
e7dec527 3666 expect(client._rfbConnectionState).to.equal('disconnected');
b1dee947
SR
3667 });
3668
b45905ab 3669 it('should fail if we get a close event while connecting', function () {
3bb12056 3670 sinon.spy(client, "_fail");
e7dec527 3671 client._rfbConnectionState = 'connecting';
b1dee947 3672 client._sock._websocket.close();
3bb12056 3673 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
3674 });
3675
155d78b3
JS
3676 it('should unregister close event handler', function () {
3677 sinon.spy(client._sock, 'off');
bb25d3d6 3678 client.disconnect();
155d78b3
JS
3679 client._sock._websocket.close();
3680 expect(client._sock.off).to.have.been.calledWith('close');
3681 });
3682
b1dee947
SR
3683 // error events do nothing
3684 });
efd1f8a4
AT
3685 });
3686
3687 describe('Quality level setting', function () {
3688 const defaultQuality = 6;
3689
3690 let client;
3691
3692 beforeEach(function () {
00674385 3693 client = makeRFB();
efd1f8a4
AT
3694 sinon.spy(RFB.messages, "clientEncodings");
3695 });
3696
3697 afterEach(function () {
3698 RFB.messages.clientEncodings.restore();
3699 });
3700
3701 it(`should equal ${defaultQuality} by default`, function () {
3702 expect(client._qualityLevel).to.equal(defaultQuality);
3703 });
3704
3705 it('should ignore non-integers when set', function () {
3706 client.qualityLevel = '1';
3707 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3708
3709 RFB.messages.clientEncodings.resetHistory();
3710
3711 client.qualityLevel = 1.5;
3712 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3713
3714 RFB.messages.clientEncodings.resetHistory();
3715
3716 client.qualityLevel = null;
3717 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3718
3719 RFB.messages.clientEncodings.resetHistory();
3720
3721 client.qualityLevel = undefined;
3722 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3723
3724 RFB.messages.clientEncodings.resetHistory();
3725
3726 client.qualityLevel = {};
3727 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3728 });
3729
3730 it('should ignore integers out of range [0, 9]', function () {
3731 client.qualityLevel = -1;
3732 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3733
3734 RFB.messages.clientEncodings.resetHistory();
3735
3736 client.qualityLevel = 10;
3737 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3738 });
3739
3740 it('should send clientEncodings with new quality value', function () {
3741 let newQuality;
3742
3743 newQuality = 8;
3744 client.qualityLevel = newQuality;
3745 expect(client.qualityLevel).to.equal(newQuality);
3746 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3747 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3748 });
3749
3750 it('should not send clientEncodings if quality is the same', function () {
3751 let newQuality;
3752
3753 newQuality = 2;
3754 client.qualityLevel = newQuality;
3755 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3756 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3757
3758 RFB.messages.clientEncodings.resetHistory();
3759
3760 client.qualityLevel = newQuality;
3761 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3762 });
3763
3764 it('should not send clientEncodings if not in connected state', function () {
3765 let newQuality;
3766
e7dec527 3767 client._rfbConnectionState = '';
efd1f8a4
AT
3768 newQuality = 2;
3769 client.qualityLevel = newQuality;
3770 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3771
3772 RFB.messages.clientEncodings.resetHistory();
3773
e7dec527 3774 client._rfbConnectionState = 'connnecting';
efd1f8a4
AT
3775 newQuality = 6;
3776 client.qualityLevel = newQuality;
3777 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3778
3779 RFB.messages.clientEncodings.resetHistory();
3780
e7dec527 3781 client._rfbConnectionState = 'connected';
efd1f8a4
AT
3782 newQuality = 5;
3783 client.qualityLevel = newQuality;
3784 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3785 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3786 });
479d8cef
SM
3787 });
3788
3789 describe('Compression level setting', function () {
3790 const defaultCompression = 2;
3791
3792 let client;
3793
3794 beforeEach(function () {
00674385 3795 client = makeRFB();
479d8cef
SM
3796 sinon.spy(RFB.messages, "clientEncodings");
3797 });
3798
3799 afterEach(function () {
3800 RFB.messages.clientEncodings.restore();
3801 });
3802
3803 it(`should equal ${defaultCompression} by default`, function () {
3804 expect(client._compressionLevel).to.equal(defaultCompression);
3805 });
3806
3807 it('should ignore non-integers when set', function () {
3808 client.compressionLevel = '1';
3809 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3810
3811 RFB.messages.clientEncodings.resetHistory();
3812
3813 client.compressionLevel = 1.5;
3814 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3815
3816 RFB.messages.clientEncodings.resetHistory();
3817
3818 client.compressionLevel = null;
3819 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3820
3821 RFB.messages.clientEncodings.resetHistory();
3822
3823 client.compressionLevel = undefined;
3824 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3825
3826 RFB.messages.clientEncodings.resetHistory();
3827
3828 client.compressionLevel = {};
3829 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3830 });
3831
3832 it('should ignore integers out of range [0, 9]', function () {
3833 client.compressionLevel = -1;
3834 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3835
3836 RFB.messages.clientEncodings.resetHistory();
3837
3838 client.compressionLevel = 10;
3839 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3840 });
3841
3842 it('should send clientEncodings with new compression value', function () {
3843 let newCompression;
3844
3845 newCompression = 5;
3846 client.compressionLevel = newCompression;
3847 expect(client.compressionLevel).to.equal(newCompression);
3848 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3849 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3850 });
3851
3852 it('should not send clientEncodings if compression is the same', function () {
3853 let newCompression;
3854
3855 newCompression = 9;
3856 client.compressionLevel = newCompression;
3857 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3858 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3859
3860 RFB.messages.clientEncodings.resetHistory();
3861
3862 client.compressionLevel = newCompression;
3863 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3864 });
3865
3866 it('should not send clientEncodings if not in connected state', function () {
3867 let newCompression;
3868
e7dec527 3869 client._rfbConnectionState = '';
479d8cef
SM
3870 newCompression = 7;
3871 client.compressionLevel = newCompression;
3872 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3873
3874 RFB.messages.clientEncodings.resetHistory();
3875
e7dec527 3876 client._rfbConnectionState = 'connnecting';
479d8cef
SM
3877 newCompression = 6;
3878 client.compressionLevel = newCompression;
3879 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3880
3881 RFB.messages.clientEncodings.resetHistory();
3882
e7dec527 3883 client._rfbConnectionState = 'connected';
479d8cef
SM
3884 newCompression = 5;
3885 client.compressionLevel = newCompression;
3886 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3887 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3888 });
b1dee947
SR
3889 });
3890});
f73fdc3e
NL
3891
3892describe('RFB messages', function () {
3893 let sock;
3894
3895 before(function () {
3896 FakeWebSocket.replace();
3897 sock = new Websock();
3898 sock.open();
3899 });
3900
3901 after(function () {
3902 FakeWebSocket.restore();
3903 });
3904
3905 describe('Extended Clipboard Handling Send', function () {
3906 beforeEach(function () {
3907 sinon.spy(RFB.messages, 'clientCutText');
3908 });
3909
3910 afterEach(function () {
3911 RFB.messages.clientCutText.restore();
3912 });
3913
3914 it('should call clientCutText with correct Caps data', function () {
3915 let formats = {
3916 0: 2,
3917 2: 4121
3918 };
3919 let expectedData = new Uint8Array([0x1F, 0x00, 0x00, 0x05,
3920 0x00, 0x00, 0x00, 0x02,
3921 0x00, 0x00, 0x10, 0x19]);
3922 let actions = [
3923 1 << 24, // Caps
3924 1 << 25, // Request
3925 1 << 26, // Peek
3926 1 << 27, // Notify
3927 1 << 28 // Provide
3928 ];
3929
3930 RFB.messages.extendedClipboardCaps(sock, actions, formats);
3931 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3932 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3933 });
3934
3935 it('should call clientCutText with correct Request data', function () {
3936 let formats = new Uint8Array([0x01]);
3937 let expectedData = new Uint8Array([0x02, 0x00, 0x00, 0x01]);
3938
3939 RFB.messages.extendedClipboardRequest(sock, formats);
3940 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3941 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3942 });
3943
3944 it('should call clientCutText with correct Notify data', function () {
3945 let formats = new Uint8Array([0x01]);
3946 let expectedData = new Uint8Array([0x08, 0x00, 0x00, 0x01]);
3947
3948 RFB.messages.extendedClipboardNotify(sock, formats);
3949 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3950 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3951 });
3952
3953 it('should call clientCutText with correct Provide data', function () {
3954 let testText = "Test string";
3955 let expectedText = encodeUTF8(testText + "\0");
3956
3957 let deflatedData = deflateWithSize(expectedText);
3958
3959 // Build Expected with flags and deflated data
3960 let expectedData = new Uint8Array(4 + deflatedData.length);
3961 expectedData[0] = 0x10; // The client capabilities
3962 expectedData[1] = 0x00; // Reserved flags
3963 expectedData[2] = 0x00; // Reserved flags
3964 expectedData[3] = 0x01; // The formats client supports
3965 expectedData.set(deflatedData, 4);
3966
3967 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3968 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3969 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3970
3971 });
3972
3973 describe('End of line characters', function () {
3974 it('Carriage return', function () {
3975
3976 let testText = "Hello\rworld\r\r!";
3977 let expectedText = encodeUTF8("Hello\r\nworld\r\n\r\n!\0");
3978
3979 let deflatedData = deflateWithSize(expectedText);
3980
3981 // Build Expected with flags and deflated data
3982 let expectedData = new Uint8Array(4 + deflatedData.length);
3983 expectedData[0] = 0x10; // The client capabilities
3984 expectedData[1] = 0x00; // Reserved flags
3985 expectedData[2] = 0x00; // Reserved flags
3986 expectedData[3] = 0x01; // The formats client supports
3987 expectedData.set(deflatedData, 4);
3988
3989 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3990 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3991 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3992 });
3993
3994 it('Carriage return Line feed', function () {
3995
3996 let testText = "Hello\r\n\r\nworld\r\n!";
3997 let expectedText = encodeUTF8(testText + "\0");
3998
3999 let deflatedData = deflateWithSize(expectedText);
4000
4001 // Build Expected with flags and deflated data
4002 let expectedData = new Uint8Array(4 + deflatedData.length);
4003 expectedData[0] = 0x10; // The client capabilities
4004 expectedData[1] = 0x00; // Reserved flags
4005 expectedData[2] = 0x00; // Reserved flags
4006 expectedData[3] = 0x01; // The formats client supports
4007 expectedData.set(deflatedData, 4);
4008
4009 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
4010 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
4011 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
4012 });
4013
4014 it('Line feed', function () {
4015 let testText = "Hello\n\n\nworld\n!";
4016 let expectedText = encodeUTF8("Hello\r\n\r\n\r\nworld\r\n!\0");
4017
4018 let deflatedData = deflateWithSize(expectedText);
4019
4020 // Build Expected with flags and deflated data
4021 let expectedData = new Uint8Array(4 + deflatedData.length);
4022 expectedData[0] = 0x10; // The client capabilities
4023 expectedData[1] = 0x00; // Reserved flags
4024 expectedData[2] = 0x00; // Reserved flags
4025 expectedData[3] = 0x01; // The formats client supports
4026 expectedData.set(deflatedData, 4);
4027
4028 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
4029 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
4030 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
4031 });
4032
4033 it('Carriage return and Line feed mixed', function () {
4034 let testText = "\rHello\r\n\rworld\n\n!";
4035 let expectedText = encodeUTF8("\r\nHello\r\n\r\nworld\r\n\r\n!\0");
4036
4037 let deflatedData = deflateWithSize(expectedText);
4038
4039 // Build Expected with flags and deflated data
4040 let expectedData = new Uint8Array(4 + deflatedData.length);
4041 expectedData[0] = 0x10; // The client capabilities
4042 expectedData[1] = 0x00; // Reserved flags
4043 expectedData[2] = 0x00; // Reserved flags
4044 expectedData[3] = 0x01; // The formats client supports
4045 expectedData.set(deflatedData, 4);
4046
4047 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
4048 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
4049 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
4050 });
4051 });
4052 });
4053});