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