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