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