]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.rfb.js
Remove createEvent() fallbacks
[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
6a4c4119
PO
2509 ev = new MouseEvent('mousemove',
2510 { 'screenX': pos.x + window.screenX,
2511 'screenY': pos.y + window.screenY,
2512 'clientX': pos.x,
2513 'clientY': pos.y });
50cde2fa
PO
2514 client._canvas.dispatchEvent(ev);
2515 }
2516
2517 function sendMouseButtonEvent(x, y, down, button) {
2518 let pos = elementToClient(x, y);
2519 let ev;
2520
6a4c4119
PO
2521 ev = new MouseEvent(down ? 'mousedown' : 'mouseup',
2522 { 'screenX': pos.x + window.screenX,
2523 'screenY': pos.y + window.screenY,
2524 'clientX': pos.x,
2525 'clientY': pos.y,
2526 'button': button,
2527 'buttons': 1 << button });
50cde2fa
PO
2528 client._canvas.dispatchEvent(ev);
2529 }
2530
b1dee947 2531 it('should not send button messages in view-only mode', function () {
747b4623 2532 client._viewOnly = true;
50cde2fa
PO
2533 sendMouseButtonEvent(10, 10, true, 0);
2534 clock.tick(50);
2535 expect(pointerEvent).to.not.have.been.called;
b1dee947
SR
2536 });
2537
2538 it('should not send movement messages in view-only mode', function () {
747b4623 2539 client._viewOnly = true;
50cde2fa
PO
2540 sendMouseMoveEvent(10, 10);
2541 clock.tick(50);
2542 expect(pointerEvent).to.not.have.been.called;
b1dee947
SR
2543 });
2544
50cde2fa
PO
2545 it('should handle left mouse button', function () {
2546 sendMouseButtonEvent(10, 10, true, 0);
2547
2548 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2549 10, 10, 0x1);
2550 pointerEvent.resetHistory();
2551
2552 sendMouseButtonEvent(10, 10, false, 0);
b1dee947 2553
50cde2fa
PO
2554 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2555 10, 10, 0x0);
d02a99f0
SR
2556 });
2557
50cde2fa
PO
2558 it('should handle middle mouse button', function () {
2559 sendMouseButtonEvent(10, 10, true, 1);
2560
2561 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2562 10, 10, 0x2);
2563 pointerEvent.resetHistory();
2564
2565 sendMouseButtonEvent(10, 10, false, 1);
2566
2567 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2568 10, 10, 0x0);
d02a99f0
SR
2569 });
2570
50cde2fa
PO
2571 it('should handle right mouse button', function () {
2572 sendMouseButtonEvent(10, 10, true, 2);
2573
2574 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2575 10, 10, 0x4);
2576 pointerEvent.resetHistory();
2577
2578 sendMouseButtonEvent(10, 10, false, 2);
2579
2580 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2581 10, 10, 0x0);
b1dee947
SR
2582 });
2583
50cde2fa
PO
2584 it('should handle multiple mouse buttons', function () {
2585 sendMouseButtonEvent(10, 10, true, 0);
2586 sendMouseButtonEvent(10, 10, true, 2);
2587
2588 expect(pointerEvent).to.have.been.calledTwice;
2589 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2590 10, 10, 0x1);
2591 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2592 10, 10, 0x5);
2593
2594 pointerEvent.resetHistory();
2595
2596 sendMouseButtonEvent(10, 10, false, 0);
2597 sendMouseButtonEvent(10, 10, false, 2);
2598
2599 expect(pointerEvent).to.have.been.calledTwice;
2600 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2601 10, 10, 0x4);
2602 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2603 10, 10, 0x0);
150596be
SM
2604 });
2605
50cde2fa
PO
2606 it('should handle mouse movement', function () {
2607 sendMouseMoveEvent(50, 70);
2608 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2609 50, 70, 0x0);
150596be
SM
2610 });
2611
50cde2fa
PO
2612 it('should handle click and drag', function () {
2613 sendMouseButtonEvent(10, 10, true, 0);
2614 sendMouseMoveEvent(50, 70);
2615
2616 expect(pointerEvent).to.have.been.calledTwice;
2617 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2618 10, 10, 0x1);
2619 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2620 50, 70, 0x1);
2621
2622 pointerEvent.resetHistory();
2623
2624 sendMouseButtonEvent(50, 70, false, 0);
2625
2626 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2627 50, 70, 0x0);
150596be
SM
2628 });
2629
50cde2fa
PO
2630 describe('Event Aggregation', function () {
2631 it('should send a single pointer event on mouse movement', function () {
2632 sendMouseMoveEvent(50, 70);
2633 clock.tick(100);
2634 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2635 50, 70, 0x0);
2636 });
150596be 2637
50cde2fa
PO
2638 it('should delay one move if two events are too close', function () {
2639 sendMouseMoveEvent(18, 30);
2640 sendMouseMoveEvent(20, 50);
150596be 2641
50cde2fa
PO
2642 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2643 18, 30, 0x0);
2644 pointerEvent.resetHistory();
150596be 2645
50cde2fa 2646 clock.tick(100);
150596be 2647
50cde2fa
PO
2648 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2649 20, 50, 0x0);
2650 });
2651
2652 it('should only send first and last move of many close events', function () {
2653 sendMouseMoveEvent(18, 30);
2654 sendMouseMoveEvent(20, 50);
2655 sendMouseMoveEvent(21, 55);
2656
2657 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2658 18, 30, 0x0);
2659 pointerEvent.resetHistory();
2660
2661 clock.tick(100);
2662
2663 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2664 21, 55, 0x0);
2665 });
2666
2667 // We selected the 17ms since that is ~60 FPS
2668 it('should send move events every 17 ms', function () {
2669 sendMouseMoveEvent(1, 10); // instant send
2670 clock.tick(10);
2671
2672 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2673 1, 10, 0x0);
2674 pointerEvent.resetHistory();
2675
2676 sendMouseMoveEvent(2, 20); // delayed
2677 clock.tick(10); // timeout send
2678
2679 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2680 2, 20, 0x0);
2681 pointerEvent.resetHistory();
2682
2683 sendMouseMoveEvent(3, 30); // delayed
2684 clock.tick(10);
2685 sendMouseMoveEvent(4, 40); // delayed
2686 clock.tick(10); // timeout send
2687
2688 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2689 4, 40, 0x0);
2690 pointerEvent.resetHistory();
2691
2692 sendMouseMoveEvent(5, 50); // delayed
2693
2694 expect(pointerEvent).to.not.have.been.called;
2695 });
2696
2697 it('should send waiting move events before a button press', function () {
2698 sendMouseMoveEvent(13, 9);
2699
2700 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2701 13, 9, 0x0);
2702 pointerEvent.resetHistory();
2703
2704 sendMouseMoveEvent(20, 70);
2705
2706 expect(pointerEvent).to.not.have.been.called;
2707
2708 sendMouseButtonEvent(20, 70, true, 0);
2709
2710 expect(pointerEvent).to.have.been.calledTwice;
2711 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2712 20, 70, 0x0);
2713 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2714 20, 70, 0x1);
2715 });
2716
2717 it('should send move events with enough time apart normally', function () {
2718 sendMouseMoveEvent(58, 60);
2719
2720 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2721 58, 60, 0x0);
2722 pointerEvent.resetHistory();
2723
2724 clock.tick(20);
2725
2726 sendMouseMoveEvent(25, 60);
2727
2728 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2729 25, 60, 0x0);
2730 pointerEvent.resetHistory();
2731 });
2732
2733 it('should not send waiting move events if disconnected', function () {
2734 sendMouseMoveEvent(88, 99);
2735
2736 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2737 88, 99, 0x0);
2738 pointerEvent.resetHistory();
2739
2740 sendMouseMoveEvent(66, 77);
2741 client.disconnect();
2742 clock.tick(20);
2743
2744 expect(pointerEvent).to.not.have.been.called;
2745 });
150596be
SM
2746 });
2747
50cde2fa
PO
2748 it.skip('should block click events', function () {
2749 /* FIXME */
2750 });
2751
2752 it.skip('should block contextmenu events', function () {
2753 /* FIXME */
b1dee947 2754 });
b1dee947
SR
2755 });
2756
f84bc57b
PO
2757 describe('Wheel Events', function () {
2758 function sendWheelEvent(x, y, dx, dy, mode=0) {
2759 let pos = elementToClient(x, y);
2760 let ev;
2761
6a4c4119
PO
2762 ev = new WheelEvent('wheel',
2763 { 'screenX': pos.x + window.screenX,
2764 'screenY': pos.y + window.screenY,
2765 'clientX': pos.x,
2766 'clientY': pos.y,
2767 'deltaX': dx,
2768 'deltaY': dy,
2769 'deltaMode': mode });
f84bc57b
PO
2770 client._canvas.dispatchEvent(ev);
2771 }
2772
2773 it('should handle wheel up event', function () {
2774 sendWheelEvent(10, 10, 0, -50);
2775
2776 expect(pointerEvent).to.have.been.calledTwice;
2777 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2778 10, 10, 1<<3);
2779 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2780 10, 10, 0);
2781 });
2782
2783 it('should handle wheel down event', function () {
2784 sendWheelEvent(10, 10, 0, 50);
2785
2786 expect(pointerEvent).to.have.been.calledTwice;
2787 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2788 10, 10, 1<<4);
2789 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2790 10, 10, 0);
2791 });
2792
2793 it('should handle wheel left event', function () {
2794 sendWheelEvent(10, 10, -50, 0);
2795
2796 expect(pointerEvent).to.have.been.calledTwice;
2797 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2798 10, 10, 1<<5);
2799 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2800 10, 10, 0);
2801 });
2802
2803 it('should handle wheel right event', function () {
2804 sendWheelEvent(10, 10, 50, 0);
2805
2806 expect(pointerEvent).to.have.been.calledTwice;
2807 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2808 10, 10, 1<<6);
2809 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2810 10, 10, 0);
2811 });
2812
2813 it('should ignore wheel when in view only', function () {
2814 client._viewOnly = true;
2815
2816 sendWheelEvent(10, 10, 50, 0);
2817
2818 expect(pointerEvent).to.not.have.been.called;
2819 });
2820
2821 it('should accumulate wheel events if small enough', function () {
88589a44
PO
2822 sendWheelEvent(10, 10, 0, 20);
2823 sendWheelEvent(10, 10, 0, 20);
f84bc57b
PO
2824
2825 expect(pointerEvent).to.not.have.been.called;
2826
88589a44 2827 sendWheelEvent(10, 10, 0, 20);
f84bc57b
PO
2828
2829 expect(pointerEvent).to.have.been.calledTwice;
2830 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2831 10, 10, 1<<4);
2832 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2833 10, 10, 0);
2834 });
2835
2836 it('should not accumulate large wheel events', function () {
2837 sendWheelEvent(10, 10, 0, 400);
2838
2839 expect(pointerEvent).to.have.been.calledTwice;
2840 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2841 10, 10, 1<<4);
2842 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2843 10, 10, 0);
2844 });
2845
2846 it('should handle line based wheel event', function () {
88589a44 2847 sendWheelEvent(10, 10, 0, 3, 1);
f84bc57b
PO
2848
2849 expect(pointerEvent).to.have.been.calledTwice;
2850 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2851 10, 10, 1<<4);
2852 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2853 10, 10, 0);
2854 });
2855
2856 it('should handle page based wheel event', function () {
88589a44 2857 sendWheelEvent(10, 10, 0, 3, 2);
f84bc57b
PO
2858
2859 expect(pointerEvent).to.have.been.calledTwice;
2860 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2861 10, 10, 1<<4);
2862 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2863 10, 10, 0);
2864 });
2865 });
2866
2867 describe('Keyboard Events', function () {
b1dee947 2868 it('should send a key message on a key press', function () {
747b4623 2869 client._handleKeyEvent(0x41, 'KeyA', true);
e7dec527
SM
2870 const keyMsg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
2871 RFB.messages.keyEvent(keyMsg, 0x41, 1);
2872 expect(client._sock).to.have.sent(keyMsg._sQ);
b1dee947
SR
2873 });
2874
2875 it('should not send messages in view-only mode', function () {
747b4623 2876 client._viewOnly = true;
057b8fec 2877 sinon.spy(client._sock, 'flush');
747b4623 2878 client._handleKeyEvent('a', 'KeyA', true);
9ff86fb7 2879 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2880 });
2881 });
2882
8be924c9 2883 describe('Gesture event handlers', function () {
8be924c9
PO
2884 function gestureStart(gestureType, x, y,
2885 magnitudeX = 0, magnitudeY = 0) {
2886 let pos = elementToClient(x, y);
2887 let detail = {type: gestureType, clientX: pos.x, clientY: pos.y};
2888
2889 detail.magnitudeX = magnitudeX;
2890 detail.magnitudeY = magnitudeY;
2891
2892 let ev = new CustomEvent('gesturestart', { detail: detail });
2893 client._canvas.dispatchEvent(ev);
2894 }
2895
2896 function gestureMove(gestureType, x, y,
2897 magnitudeX = 0, magnitudeY = 0) {
2898 let pos = elementToClient(x, y);
2899 let detail = {type: gestureType, clientX: pos.x, clientY: pos.y};
2900
2901 detail.magnitudeX = magnitudeX;
2902 detail.magnitudeY = magnitudeY;
2903
2904 let ev = new CustomEvent('gesturemove', { detail: detail });
2905 client._canvas.dispatchEvent(ev);
2906 }
2907
2908 function gestureEnd(gestureType, x, y) {
2909 let pos = elementToClient(x, y);
2910 let detail = {type: gestureType, clientX: pos.x, clientY: pos.y};
2911 let ev = new CustomEvent('gestureend', { detail: detail });
2912 client._canvas.dispatchEvent(ev);
2913 }
2914
2915 describe('Gesture onetap', function () {
2916 it('should handle onetap events', function () {
2917 let bmask = 0x1;
2918
2919 gestureStart('onetap', 20, 40);
2920 gestureEnd('onetap', 20, 40);
2921
2922 expect(pointerEvent).to.have.been.calledThrice;
2923 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2924 20, 40, 0x0);
2925 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2926 20, 40, bmask);
2927 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2928 20, 40, 0x0);
2929 });
2930
2931 it('should keep same position for multiple onetap events', function () {
2932 let bmask = 0x1;
2933
2934 gestureStart('onetap', 20, 40);
2935 gestureEnd('onetap', 20, 40);
2936
2937 expect(pointerEvent).to.have.been.calledThrice;
2938 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2939 20, 40, 0x0);
2940 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2941 20, 40, bmask);
2942 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2943 20, 40, 0x0);
2944
2945 pointerEvent.resetHistory();
2946
2947 gestureStart('onetap', 20, 50);
2948 gestureEnd('onetap', 20, 50);
2949
2950 expect(pointerEvent).to.have.been.calledThrice;
2951 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2952 20, 40, 0x0);
2953 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2954 20, 40, bmask);
2955 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2956 20, 40, 0x0);
2957
2958 pointerEvent.resetHistory();
2959
2960 gestureStart('onetap', 30, 50);
2961 gestureEnd('onetap', 30, 50);
2962
2963 expect(pointerEvent).to.have.been.calledThrice;
2964 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2965 20, 40, 0x0);
2966 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2967 20, 40, bmask);
2968 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2969 20, 40, 0x0);
2970 });
2971
2972 it('should not keep same position for onetap events when too far apart', function () {
2973 let bmask = 0x1;
2974
2975 gestureStart('onetap', 20, 40);
2976 gestureEnd('onetap', 20, 40);
2977
2978 expect(pointerEvent).to.have.been.calledThrice;
2979 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2980 20, 40, 0x0);
2981 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2982 20, 40, bmask);
2983 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2984 20, 40, 0x0);
2985
2986 pointerEvent.resetHistory();
2987
2988 gestureStart('onetap', 80, 95);
2989 gestureEnd('onetap', 80, 95);
2990
2991 expect(pointerEvent).to.have.been.calledThrice;
2992 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2993 80, 95, 0x0);
2994 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2995 80, 95, bmask);
2996 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2997 80, 95, 0x0);
2998 });
2999
3000 it('should not keep same position for onetap events when enough time inbetween', function () {
3001 let bmask = 0x1;
3002
3003 gestureStart('onetap', 10, 20);
3004 gestureEnd('onetap', 10, 20);
3005
3006 expect(pointerEvent).to.have.been.calledThrice;
3007 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3008 10, 20, 0x0);
3009 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3010 10, 20, bmask);
3011 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3012 10, 20, 0x0);
3013
3014 pointerEvent.resetHistory();
3015 this.clock.tick(1500);
3016
3017 gestureStart('onetap', 15, 20);
3018 gestureEnd('onetap', 15, 20);
3019
3020 expect(pointerEvent).to.have.been.calledThrice;
3021 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3022 15, 20, 0x0);
3023 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3024 15, 20, bmask);
3025 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3026 15, 20, 0x0);
3027
3028 pointerEvent.resetHistory();
3029 });
3030 });
3031
3032 describe('Gesture twotap', function () {
3033 it('should handle gesture twotap events', function () {
3034 let bmask = 0x4;
3035
3036 gestureStart("twotap", 20, 40);
3037
3038 expect(pointerEvent).to.have.been.calledThrice;
3039 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3040 20, 40, 0x0);
3041 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3042 20, 40, bmask);
3043 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3044 20, 40, 0x0);
3045 });
3046
3047 it('should keep same position for multiple twotap events', function () {
3048 let bmask = 0x4;
3049
3050 for (let offset = 0;offset < 30;offset += 10) {
3051 pointerEvent.resetHistory();
3052
3053 gestureStart('twotap', 20, 40 + offset);
3054 gestureEnd('twotap', 20, 40 + offset);
3055
3056 expect(pointerEvent).to.have.been.calledThrice;
3057 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3058 20, 40, 0x0);
3059 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3060 20, 40, bmask);
3061 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3062 20, 40, 0x0);
3063 }
3064 });
3065 });
3066
3067 describe('Gesture threetap', function () {
3068 it('should handle gesture start for threetap events', function () {
3069 let bmask = 0x2;
3070
3071 gestureStart("threetap", 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 threetap events', function () {
3083 let bmask = 0x2;
3084
3085 for (let offset = 0;offset < 30;offset += 10) {
3086 pointerEvent.resetHistory();
3087
3088 gestureStart('threetap', 20, 40 + offset);
3089 gestureEnd('threetap', 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 drag', function () {
3103 it('should handle gesture drag events', function () {
3104 let bmask = 0x1;
3105
3106 gestureStart('drag', 20, 40);
3107
3108 expect(pointerEvent).to.have.been.calledTwice;
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
3114 pointerEvent.resetHistory();
3115
3116 gestureMove('drag', 30, 50);
3117 clock.tick(50);
3118
3119 expect(pointerEvent).to.have.been.calledOnce;
3120 expect(pointerEvent).to.have.been.calledWith(client._sock,
3121 30, 50, bmask);
3122
3123 pointerEvent.resetHistory();
3124
3125 gestureEnd('drag', 30, 50);
3126
3127 expect(pointerEvent).to.have.been.calledTwice;
3128 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3129 30, 50, bmask);
3130 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3131 30, 50, 0x0);
3132 });
3133 });
3134
3135 describe('Gesture long press', function () {
3136 it('should handle long press events', function () {
3137 let bmask = 0x4;
3138
3139 gestureStart('longpress', 20, 40);
3140
3141 expect(pointerEvent).to.have.been.calledTwice;
3142 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3143 20, 40, 0x0);
3144 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3145 20, 40, bmask);
3146 pointerEvent.resetHistory();
3147
3148 gestureMove('longpress', 40, 60);
3149 clock.tick(50);
3150
3151 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3152 40, 60, bmask);
3153
3154 pointerEvent.resetHistory();
3155
3156 gestureEnd('longpress', 40, 60);
3157
3158 expect(pointerEvent).to.have.been.calledTwice;
3159 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3160 40, 60, bmask);
3161 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3162 40, 60, 0x0);
3163 });
3164 });
3165
3166 describe('Gesture twodrag', function () {
3167 it('should handle gesture twodrag up events', function () {
3168 let bmask = 0x10; // Button mask for scroll down
3169
3170 gestureStart('twodrag', 20, 40, 0, 0);
3171
3172 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3173 20, 40, 0x0);
3174
3175 pointerEvent.resetHistory();
3176
3177 gestureMove('twodrag', 20, 40, 0, -60);
3178
3179 expect(pointerEvent).to.have.been.calledThrice;
3180 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3181 20, 40, 0x0);
3182 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3183 20, 40, bmask);
3184 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3185 20, 40, 0x0);
3186 });
3187
3188 it('should handle gesture twodrag down events', function () {
3189 let bmask = 0x8; // Button mask for scroll up
3190
3191 gestureStart('twodrag', 20, 40, 0, 0);
3192
3193 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3194 20, 40, 0x0);
3195
3196 pointerEvent.resetHistory();
3197
3198 gestureMove('twodrag', 20, 40, 0, 60);
3199
3200 expect(pointerEvent).to.have.been.calledThrice;
3201 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3202 20, 40, 0x0);
3203 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3204 20, 40, bmask);
3205 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3206 20, 40, 0x0);
3207 });
3208
3209 it('should handle gesture twodrag right events', function () {
3210 let bmask = 0x20; // Button mask for scroll right
3211
3212 gestureStart('twodrag', 20, 40, 0, 0);
3213
3214 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3215 20, 40, 0x0);
3216
3217 pointerEvent.resetHistory();
3218
3219 gestureMove('twodrag', 20, 40, 60, 0);
3220
3221 expect(pointerEvent).to.have.been.calledThrice;
3222 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3223 20, 40, 0x0);
3224 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3225 20, 40, bmask);
3226 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3227 20, 40, 0x0);
3228 });
3229
3230 it('should handle gesture twodrag left events', function () {
3231 let bmask = 0x40; // Button mask for scroll left
3232
3233 gestureStart('twodrag', 20, 40, 0, 0);
3234
3235 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3236 20, 40, 0x0);
3237
3238 pointerEvent.resetHistory();
3239
3240 gestureMove('twodrag', 20, 40, -60, 0);
3241
3242 expect(pointerEvent).to.have.been.calledThrice;
3243 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3244 20, 40, 0x0);
3245 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3246 20, 40, bmask);
3247 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3248 20, 40, 0x0);
3249 });
3250
3251 it('should handle gesture twodrag diag events', function () {
3252 let scrlUp = 0x8; // Button mask for scroll up
3253 let scrlRight = 0x20; // Button mask for scroll right
3254
3255 gestureStart('twodrag', 20, 40, 0, 0);
3256
3257 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3258 20, 40, 0x0);
3259
3260 pointerEvent.resetHistory();
3261
3262 gestureMove('twodrag', 20, 40, 60, 60);
3263
3264 expect(pointerEvent).to.have.been.callCount(5);
3265 expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,
3266 20, 40, 0x0);
3267 expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,
3268 20, 40, scrlUp);
3269 expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,
3270 20, 40, 0x0);
3271 expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,
3272 20, 40, scrlRight);
3273 expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,
3274 20, 40, 0x0);
3275 });
3276
3277 it('should handle multiple small gesture twodrag events', function () {
3278 let bmask = 0x8; // Button mask for scroll up
3279
3280 gestureStart('twodrag', 20, 40, 0, 0);
3281
3282 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3283 20, 40, 0x0);
3284
3285 pointerEvent.resetHistory();
3286
3287 gestureMove('twodrag', 20, 40, 0, 10);
3288 clock.tick(50);
3289
3290 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3291 20, 40, 0x0);
3292
3293 pointerEvent.resetHistory();
3294
3295 gestureMove('twodrag', 20, 40, 0, 20);
3296 clock.tick(50);
3297
3298 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3299 20, 40, 0x0);
3300
3301 pointerEvent.resetHistory();
3302
3303 gestureMove('twodrag', 20, 40, 0, 60);
3304
3305 expect(pointerEvent).to.have.been.calledThrice;
3306 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3307 20, 40, 0x0);
3308 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3309 20, 40, bmask);
3310 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3311 20, 40, 0x0);
3312 });
3313
3314 it('should handle large gesture twodrag events', function () {
3315 let bmask = 0x8; // Button mask for scroll up
3316
3317 gestureStart('twodrag', 30, 50, 0, 0);
3318
3319 expect(pointerEvent).
3320 to.have.been.calledOnceWith(client._sock, 30, 50, 0x0);
3321
3322 pointerEvent.resetHistory();
3323
3324 gestureMove('twodrag', 30, 50, 0, 200);
3325
3326 expect(pointerEvent).to.have.callCount(7);
3327 expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,
3328 30, 50, 0x0);
3329 expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,
3330 30, 50, bmask);
3331 expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,
3332 30, 50, 0x0);
3333 expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,
3334 30, 50, bmask);
3335 expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,
3336 30, 50, 0x0);
3337 expect(pointerEvent.getCall(5)).to.have.been.calledWith(client._sock,
3338 30, 50, bmask);
3339 expect(pointerEvent.getCall(6)).to.have.been.calledWith(client._sock,
3340 30, 50, 0x0);
3341 });
3342 });
3343
3344 describe('Gesture pinch', function () {
8be924c9
PO
3345 it('should handle gesture pinch in events', function () {
3346 let keysym = KeyTable.XK_Control_L;
3347 let bmask = 0x10; // Button mask for scroll down
3348
3349 gestureStart('pinch', 20, 40, 90, 90);
3350
3351 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3352 20, 40, 0x0);
3353 expect(keyEvent).to.not.have.been.called;
3354
3355 pointerEvent.resetHistory();
3356
3357 gestureMove('pinch', 20, 40, 30, 30);
3358
3359 expect(pointerEvent).to.have.been.calledThrice;
3360 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3361 20, 40, 0x0);
3362 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3363 20, 40, bmask);
3364 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3365 20, 40, 0x0);
3366
3367 expect(keyEvent).to.have.been.calledTwice;
3368 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3369 keysym, 1);
3370 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3371 keysym, 0);
3372
3373 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3374 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3375
3376 pointerEvent.resetHistory();
3377 keyEvent.resetHistory();
3378
3379 gestureEnd('pinch', 20, 40);
3380
3381 expect(pointerEvent).to.not.have.been.called;
3382 expect(keyEvent).to.not.have.been.called;
3383 });
3384
3385 it('should handle gesture pinch out events', function () {
3386 let keysym = KeyTable.XK_Control_L;
3387 let bmask = 0x8; // Button mask for scroll up
3388
3389 gestureStart('pinch', 10, 20, 10, 20);
3390
3391 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3392 10, 20, 0x0);
3393 expect(keyEvent).to.not.have.been.called;
3394
3395 pointerEvent.resetHistory();
3396
3397 gestureMove('pinch', 10, 20, 70, 80);
3398
3399 expect(pointerEvent).to.have.been.calledThrice;
3400 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3401 10, 20, 0x0);
3402 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3403 10, 20, bmask);
3404 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3405 10, 20, 0x0);
3406
3407 expect(keyEvent).to.have.been.calledTwice;
3408 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3409 keysym, 1);
3410 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3411 keysym, 0);
3412
3413 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3414 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3415
3416 pointerEvent.resetHistory();
3417 keyEvent.resetHistory();
3418
3419 gestureEnd('pinch', 10, 20);
3420
3421 expect(pointerEvent).to.not.have.been.called;
3422 expect(keyEvent).to.not.have.been.called;
3423 });
3424
3425 it('should handle large gesture pinch', function () {
3426 let keysym = KeyTable.XK_Control_L;
3427 let bmask = 0x10; // Button mask for scroll down
3428
3429 gestureStart('pinch', 20, 40, 150, 150);
3430
3431 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3432 20, 40, 0x0);
3433 expect(keyEvent).to.not.have.been.called;
3434
3435 pointerEvent.resetHistory();
3436
3437 gestureMove('pinch', 20, 40, 30, 30);
3438
3439 expect(pointerEvent).to.have.been.callCount(5);
3440 expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,
3441 20, 40, 0x0);
3442 expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,
3443 20, 40, bmask);
3444 expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,
3445 20, 40, 0x0);
3446 expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,
3447 20, 40, bmask);
3448 expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,
3449 20, 40, 0x0);
3450
3451 expect(keyEvent).to.have.been.calledTwice;
3452 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3453 keysym, 1);
3454 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3455 keysym, 0);
3456
3457 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3458 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3459
3460 pointerEvent.resetHistory();
3461 keyEvent.resetHistory();
3462
3463 gestureEnd('pinch', 20, 40);
3464
3465 expect(pointerEvent).to.not.have.been.called;
3466 expect(keyEvent).to.not.have.been.called;
3467 });
3468
3469 it('should handle multiple small gesture pinch out events', function () {
3470 let keysym = KeyTable.XK_Control_L;
3471 let bmask = 0x8; // Button mask for scroll down
3472
3473 gestureStart('pinch', 20, 40, 0, 10);
3474
3475 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3476 20, 40, 0x0);
3477 expect(keyEvent).to.not.have.been.called;
3478
3479 pointerEvent.resetHistory();
3480
3481 gestureMove('pinch', 20, 40, 0, 30);
3482 clock.tick(50);
3483
3484 expect(pointerEvent).to.have.been.calledWith(client._sock,
3485 20, 40, 0x0);
3486
3487 pointerEvent.resetHistory();
3488
3489 gestureMove('pinch', 20, 40, 0, 60);
3490 clock.tick(50);
3491
3492 expect(pointerEvent).to.have.been.calledWith(client._sock,
3493 20, 40, 0x0);
3494
3495 pointerEvent.resetHistory();
3496 keyEvent.resetHistory();
3497
3498 gestureMove('pinch', 20, 40, 0, 90);
3499
3500 expect(pointerEvent).to.have.been.calledThrice;
3501 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3502 20, 40, 0x0);
3503 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3504 20, 40, bmask);
3505 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3506 20, 40, 0x0);
3507
3508 expect(keyEvent).to.have.been.calledTwice;
3509 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3510 keysym, 1);
3511 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3512 keysym, 0);
3513
3514 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3515 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3516
3517 pointerEvent.resetHistory();
3518 keyEvent.resetHistory();
3519
3520 gestureEnd('pinch', 20, 40);
3521
3522 expect(keyEvent).to.not.have.been.called;
3523 });
3524
3525 it('should send correct key control code', function () {
3526 let keysym = KeyTable.XK_Control_L;
3527 let code = 0x1d;
3528 let bmask = 0x10; // Button mask for scroll down
3529
3530 client._qemuExtKeyEventSupported = true;
3531
3532 gestureStart('pinch', 20, 40, 90, 90);
3533
3534 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3535 20, 40, 0x0);
3536 expect(qemuKeyEvent).to.not.have.been.called;
3537
3538 pointerEvent.resetHistory();
3539
3540 gestureMove('pinch', 20, 40, 30, 30);
3541
3542 expect(pointerEvent).to.have.been.calledThrice;
3543 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3544 20, 40, 0x0);
3545 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3546 20, 40, bmask);
3547 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3548 20, 40, 0x0);
3549
3550 expect(qemuKeyEvent).to.have.been.calledTwice;
3551 expect(qemuKeyEvent.firstCall).to.have.been.calledWith(client._sock,
3552 keysym,
3553 true,
3554 code);
3555 expect(qemuKeyEvent.secondCall).to.have.been.calledWith(client._sock,
3556 keysym,
3557 false,
3558 code);
3559
3560 expect(qemuKeyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3561 expect(qemuKeyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3562
3563 pointerEvent.resetHistory();
3564 qemuKeyEvent.resetHistory();
3565
3566 gestureEnd('pinch', 20, 40);
3567
3568 expect(pointerEvent).to.not.have.been.called;
3569 expect(qemuKeyEvent).to.not.have.been.called;
3570 });
3571 });
3572 });
3573
3574 describe('WebSocket Events', function () {
b1dee947 3575 // message events
d80d9d37 3576 it('should do nothing if we receive an empty message and have nothing in the queue', function () {
00674385 3577 client._normalMsg = sinon.spy();
95632e41 3578 client._sock._websocket._receiveData(new Uint8Array([]));
00674385 3579 expect(client._normalMsg).to.not.have.been.called;
b1dee947
SR
3580 });
3581
c2a4d3ef 3582 it('should handle a message in the connected state as a normal message', function () {
00674385 3583 client._normalMsg = sinon.spy();
95632e41 3584 client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));
00674385 3585 expect(client._normalMsg).to.have.been.called;
b1dee947
SR
3586 });
3587
3588 it('should handle a message in any non-disconnected/failed state like an init message', function () {
e7dec527
SM
3589 client._rfbConnectionState = 'connecting';
3590 client._rfbInitState = 'ProtocolVersion';
00674385 3591 client._initMsg = sinon.spy();
95632e41 3592 client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));
00674385 3593 expect(client._initMsg).to.have.been.called;
b1dee947
SR
3594 });
3595
9535539b 3596 it('should process all normal messages directly', function () {
2b5f94fa 3597 const spy = sinon.spy();
e89eef94 3598 client.addEventListener("bell", spy);
95632e41 3599 client._sock._websocket._receiveData(new Uint8Array([0x02, 0x02]));
e89eef94 3600 expect(spy).to.have.been.calledTwice;
b1dee947
SR
3601 });
3602
3603 // open events
c2a4d3ef 3604 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
9b84f516 3605 client = new RFB(document.createElement('div'), 'wss://host:8675');
2f4516f2 3606 this.clock.tick();
b1dee947 3607 client._sock._websocket._open();
e7dec527 3608 expect(client._rfbInitState).to.equal('ProtocolVersion');
b1dee947
SR
3609 });
3610
3611 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
3bb12056 3612 sinon.spy(client, "_fail");
e7dec527 3613 client._rfbConnectionState = 'connected';
b1dee947 3614 client._sock._websocket._open();
3bb12056 3615 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
3616 });
3617
3618 // close events
c2a4d3ef 3619 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2b5f94fa 3620 const real = client._sock._websocket.close;
651c23ec 3621 client._sock._websocket.close = () => {};
bb25d3d6 3622 client.disconnect();
e7dec527 3623 expect(client._rfbConnectionState).to.equal('disconnecting');
bb25d3d6 3624 client._sock._websocket.close = real;
b1dee947 3625 client._sock._websocket.close();
e7dec527 3626 expect(client._rfbConnectionState).to.equal('disconnected');
b1dee947
SR
3627 });
3628
b45905ab 3629 it('should fail if we get a close event while connecting', function () {
3bb12056 3630 sinon.spy(client, "_fail");
e7dec527 3631 client._rfbConnectionState = 'connecting';
b1dee947 3632 client._sock._websocket.close();
3bb12056 3633 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
3634 });
3635
155d78b3
JS
3636 it('should unregister close event handler', function () {
3637 sinon.spy(client._sock, 'off');
bb25d3d6 3638 client.disconnect();
155d78b3
JS
3639 client._sock._websocket.close();
3640 expect(client._sock.off).to.have.been.calledWith('close');
3641 });
3642
b1dee947
SR
3643 // error events do nothing
3644 });
efd1f8a4
AT
3645 });
3646
3647 describe('Quality level setting', function () {
3648 const defaultQuality = 6;
3649
3650 let client;
3651
3652 beforeEach(function () {
00674385 3653 client = makeRFB();
efd1f8a4
AT
3654 sinon.spy(RFB.messages, "clientEncodings");
3655 });
3656
3657 afterEach(function () {
3658 RFB.messages.clientEncodings.restore();
3659 });
3660
3661 it(`should equal ${defaultQuality} by default`, function () {
3662 expect(client._qualityLevel).to.equal(defaultQuality);
3663 });
3664
3665 it('should ignore non-integers when set', function () {
3666 client.qualityLevel = '1';
3667 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3668
3669 RFB.messages.clientEncodings.resetHistory();
3670
3671 client.qualityLevel = 1.5;
3672 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3673
3674 RFB.messages.clientEncodings.resetHistory();
3675
3676 client.qualityLevel = null;
3677 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3678
3679 RFB.messages.clientEncodings.resetHistory();
3680
3681 client.qualityLevel = undefined;
3682 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3683
3684 RFB.messages.clientEncodings.resetHistory();
3685
3686 client.qualityLevel = {};
3687 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3688 });
3689
3690 it('should ignore integers out of range [0, 9]', function () {
3691 client.qualityLevel = -1;
3692 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3693
3694 RFB.messages.clientEncodings.resetHistory();
3695
3696 client.qualityLevel = 10;
3697 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3698 });
3699
3700 it('should send clientEncodings with new quality value', function () {
3701 let newQuality;
3702
3703 newQuality = 8;
3704 client.qualityLevel = newQuality;
3705 expect(client.qualityLevel).to.equal(newQuality);
3706 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3707 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3708 });
3709
3710 it('should not send clientEncodings if quality is the same', function () {
3711 let newQuality;
3712
3713 newQuality = 2;
3714 client.qualityLevel = newQuality;
3715 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3716 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3717
3718 RFB.messages.clientEncodings.resetHistory();
3719
3720 client.qualityLevel = newQuality;
3721 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3722 });
3723
3724 it('should not send clientEncodings if not in connected state', function () {
3725 let newQuality;
3726
e7dec527 3727 client._rfbConnectionState = '';
efd1f8a4
AT
3728 newQuality = 2;
3729 client.qualityLevel = newQuality;
3730 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3731
3732 RFB.messages.clientEncodings.resetHistory();
3733
e7dec527 3734 client._rfbConnectionState = 'connnecting';
efd1f8a4
AT
3735 newQuality = 6;
3736 client.qualityLevel = newQuality;
3737 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3738
3739 RFB.messages.clientEncodings.resetHistory();
3740
e7dec527 3741 client._rfbConnectionState = 'connected';
efd1f8a4
AT
3742 newQuality = 5;
3743 client.qualityLevel = newQuality;
3744 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3745 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3746 });
479d8cef
SM
3747 });
3748
3749 describe('Compression level setting', function () {
3750 const defaultCompression = 2;
3751
3752 let client;
3753
3754 beforeEach(function () {
00674385 3755 client = makeRFB();
479d8cef
SM
3756 sinon.spy(RFB.messages, "clientEncodings");
3757 });
3758
3759 afterEach(function () {
3760 RFB.messages.clientEncodings.restore();
3761 });
3762
3763 it(`should equal ${defaultCompression} by default`, function () {
3764 expect(client._compressionLevel).to.equal(defaultCompression);
3765 });
3766
3767 it('should ignore non-integers when set', function () {
3768 client.compressionLevel = '1';
3769 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3770
3771 RFB.messages.clientEncodings.resetHistory();
3772
3773 client.compressionLevel = 1.5;
3774 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3775
3776 RFB.messages.clientEncodings.resetHistory();
3777
3778 client.compressionLevel = null;
3779 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3780
3781 RFB.messages.clientEncodings.resetHistory();
3782
3783 client.compressionLevel = undefined;
3784 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3785
3786 RFB.messages.clientEncodings.resetHistory();
3787
3788 client.compressionLevel = {};
3789 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3790 });
3791
3792 it('should ignore integers out of range [0, 9]', function () {
3793 client.compressionLevel = -1;
3794 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3795
3796 RFB.messages.clientEncodings.resetHistory();
3797
3798 client.compressionLevel = 10;
3799 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3800 });
3801
3802 it('should send clientEncodings with new compression value', function () {
3803 let newCompression;
3804
3805 newCompression = 5;
3806 client.compressionLevel = newCompression;
3807 expect(client.compressionLevel).to.equal(newCompression);
3808 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3809 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3810 });
3811
3812 it('should not send clientEncodings if compression is the same', function () {
3813 let newCompression;
3814
3815 newCompression = 9;
3816 client.compressionLevel = newCompression;
3817 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3818 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3819
3820 RFB.messages.clientEncodings.resetHistory();
3821
3822 client.compressionLevel = newCompression;
3823 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3824 });
3825
3826 it('should not send clientEncodings if not in connected state', function () {
3827 let newCompression;
3828
e7dec527 3829 client._rfbConnectionState = '';
479d8cef
SM
3830 newCompression = 7;
3831 client.compressionLevel = newCompression;
3832 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3833
3834 RFB.messages.clientEncodings.resetHistory();
3835
e7dec527 3836 client._rfbConnectionState = 'connnecting';
479d8cef
SM
3837 newCompression = 6;
3838 client.compressionLevel = newCompression;
3839 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3840
3841 RFB.messages.clientEncodings.resetHistory();
3842
e7dec527 3843 client._rfbConnectionState = 'connected';
479d8cef
SM
3844 newCompression = 5;
3845 client.compressionLevel = newCompression;
3846 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3847 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3848 });
b1dee947
SR
3849 });
3850});
f73fdc3e
NL
3851
3852describe('RFB messages', function () {
3853 let sock;
3854
3855 before(function () {
3856 FakeWebSocket.replace();
3857 sock = new Websock();
3858 sock.open();
3859 });
3860
3861 after(function () {
3862 FakeWebSocket.restore();
3863 });
3864
3865 describe('Extended Clipboard Handling Send', function () {
3866 beforeEach(function () {
3867 sinon.spy(RFB.messages, 'clientCutText');
3868 });
3869
3870 afterEach(function () {
3871 RFB.messages.clientCutText.restore();
3872 });
3873
3874 it('should call clientCutText with correct Caps data', function () {
3875 let formats = {
3876 0: 2,
3877 2: 4121
3878 };
3879 let expectedData = new Uint8Array([0x1F, 0x00, 0x00, 0x05,
3880 0x00, 0x00, 0x00, 0x02,
3881 0x00, 0x00, 0x10, 0x19]);
3882 let actions = [
3883 1 << 24, // Caps
3884 1 << 25, // Request
3885 1 << 26, // Peek
3886 1 << 27, // Notify
3887 1 << 28 // Provide
3888 ];
3889
3890 RFB.messages.extendedClipboardCaps(sock, actions, formats);
3891 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3892 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3893 });
3894
3895 it('should call clientCutText with correct Request data', function () {
3896 let formats = new Uint8Array([0x01]);
3897 let expectedData = new Uint8Array([0x02, 0x00, 0x00, 0x01]);
3898
3899 RFB.messages.extendedClipboardRequest(sock, formats);
3900 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3901 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3902 });
3903
3904 it('should call clientCutText with correct Notify data', function () {
3905 let formats = new Uint8Array([0x01]);
3906 let expectedData = new Uint8Array([0x08, 0x00, 0x00, 0x01]);
3907
3908 RFB.messages.extendedClipboardNotify(sock, formats);
3909 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3910 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3911 });
3912
3913 it('should call clientCutText with correct Provide data', function () {
3914 let testText = "Test string";
3915 let expectedText = encodeUTF8(testText + "\0");
3916
3917 let deflatedData = deflateWithSize(expectedText);
3918
3919 // Build Expected with flags and deflated data
3920 let expectedData = new Uint8Array(4 + deflatedData.length);
3921 expectedData[0] = 0x10; // The client capabilities
3922 expectedData[1] = 0x00; // Reserved flags
3923 expectedData[2] = 0x00; // Reserved flags
3924 expectedData[3] = 0x01; // The formats client supports
3925 expectedData.set(deflatedData, 4);
3926
3927 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3928 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3929 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3930
3931 });
3932
3933 describe('End of line characters', function () {
3934 it('Carriage return', function () {
3935
3936 let testText = "Hello\rworld\r\r!";
3937 let expectedText = encodeUTF8("Hello\r\nworld\r\n\r\n!\0");
3938
3939 let deflatedData = deflateWithSize(expectedText);
3940
3941 // Build Expected with flags and deflated data
3942 let expectedData = new Uint8Array(4 + deflatedData.length);
3943 expectedData[0] = 0x10; // The client capabilities
3944 expectedData[1] = 0x00; // Reserved flags
3945 expectedData[2] = 0x00; // Reserved flags
3946 expectedData[3] = 0x01; // The formats client supports
3947 expectedData.set(deflatedData, 4);
3948
3949 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3950 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3951 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3952 });
3953
3954 it('Carriage return Line feed', function () {
3955
3956 let testText = "Hello\r\n\r\nworld\r\n!";
3957 let expectedText = encodeUTF8(testText + "\0");
3958
3959 let deflatedData = deflateWithSize(expectedText);
3960
3961 // Build Expected with flags and deflated data
3962 let expectedData = new Uint8Array(4 + deflatedData.length);
3963 expectedData[0] = 0x10; // The client capabilities
3964 expectedData[1] = 0x00; // Reserved flags
3965 expectedData[2] = 0x00; // Reserved flags
3966 expectedData[3] = 0x01; // The formats client supports
3967 expectedData.set(deflatedData, 4);
3968
3969 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3970 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3971 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3972 });
3973
3974 it('Line feed', function () {
3975 let testText = "Hello\n\n\nworld\n!";
3976 let expectedText = encodeUTF8("Hello\r\n\r\n\r\nworld\r\n!\0");
3977
3978 let deflatedData = deflateWithSize(expectedText);
3979
3980 // Build Expected with flags and deflated data
3981 let expectedData = new Uint8Array(4 + deflatedData.length);
3982 expectedData[0] = 0x10; // The client capabilities
3983 expectedData[1] = 0x00; // Reserved flags
3984 expectedData[2] = 0x00; // Reserved flags
3985 expectedData[3] = 0x01; // The formats client supports
3986 expectedData.set(deflatedData, 4);
3987
3988 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3989 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3990 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3991 });
3992
3993 it('Carriage return and Line feed mixed', function () {
3994 let testText = "\rHello\r\n\rworld\n\n!";
3995 let expectedText = encodeUTF8("\r\nHello\r\n\r\nworld\r\n\r\n!\0");
3996
3997 let deflatedData = deflateWithSize(expectedText);
3998
3999 // Build Expected with flags and deflated data
4000 let expectedData = new Uint8Array(4 + deflatedData.length);
4001 expectedData[0] = 0x10; // The client capabilities
4002 expectedData[1] = 0x00; // Reserved flags
4003 expectedData[2] = 0x00; // Reserved flags
4004 expectedData[3] = 0x01; // The formats client supports
4005 expectedData.set(deflatedData, 4);
4006
4007 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
4008 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
4009 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
4010 });
4011 });
4012 });
4013});