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