@novnc/novnc 1.2.0-test → 1.3.0-g7ad4e60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE.txt +0 -6
  2. package/README.md +4 -4
  3. package/core/decoders/copyrect.js +5 -0
  4. package/core/decoders/hextile.js +57 -3
  5. package/core/decoders/raw.js +12 -2
  6. package/core/decoders/tight.js +24 -8
  7. package/core/display.js +9 -151
  8. package/core/input/domkeytable.js +25 -21
  9. package/core/input/keyboard.js +12 -127
  10. package/core/input/util.js +18 -35
  11. package/core/input/vkeys.js +0 -1
  12. package/core/input/xtscancodes.js +5 -3
  13. package/core/rfb.js +116 -109
  14. package/core/util/browser.js +0 -17
  15. package/core/util/cursor.js +1 -11
  16. package/core/util/events.js +0 -4
  17. package/core/websock.js +76 -17
  18. package/docs/API.md +10 -3
  19. package/docs/LIBRARY.md +3 -7
  20. package/lib/base64.js +4 -4
  21. package/lib/decoders/copyrect.js +7 -2
  22. package/lib/decoders/hextile.js +63 -7
  23. package/lib/decoders/raw.js +13 -4
  24. package/lib/decoders/rre.js +2 -2
  25. package/lib/decoders/tight.js +38 -20
  26. package/lib/decoders/tightpng.js +8 -8
  27. package/lib/deflator.js +4 -4
  28. package/lib/des.js +2 -2
  29. package/lib/display.js +45 -212
  30. package/lib/inflator.js +4 -4
  31. package/lib/input/domkeytable.js +197 -194
  32. package/lib/input/fixedkeys.js +2 -2
  33. package/lib/input/gesturehandler.js +2 -2
  34. package/lib/input/keyboard.js +38 -158
  35. package/lib/input/keysym.js +2 -2
  36. package/lib/input/keysymdef.js +2 -2
  37. package/lib/input/util.js +34 -79
  38. package/lib/input/vkeys.js +2 -4
  39. package/lib/input/xtscancodes.js +11 -5
  40. package/lib/rfb.js +292 -286
  41. package/lib/util/browser.js +8 -26
  42. package/lib/util/cursor.js +4 -16
  43. package/lib/util/events.js +3 -5
  44. package/lib/util/eventtarget.js +3 -3
  45. package/lib/util/int.js +1 -1
  46. package/lib/util/logging.js +2 -2
  47. package/lib/vendor/pako/lib/utils/common.js +2 -2
  48. package/lib/vendor/pako/lib/zlib/adler32.js +1 -1
  49. package/lib/vendor/pako/lib/zlib/constants.js +2 -2
  50. package/lib/vendor/pako/lib/zlib/crc32.js +1 -1
  51. package/lib/vendor/pako/lib/zlib/deflate.js +113 -112
  52. package/lib/vendor/pako/lib/zlib/gzheader.js +1 -1
  53. package/lib/vendor/pako/lib/zlib/inffast.js +5 -5
  54. package/lib/vendor/pako/lib/zlib/inflate.js +50 -48
  55. package/lib/vendor/pako/lib/zlib/inftrees.js +3 -3
  56. package/lib/vendor/pako/lib/zlib/messages.js +2 -2
  57. package/lib/vendor/pako/lib/zlib/trees.js +4 -4
  58. package/lib/vendor/pako/lib/zlib/zstream.js +1 -1
  59. package/lib/websock.js +105 -44
  60. package/package.json +2 -7
  61. package/core/util/polyfill.js +0 -61
  62. package/lib/util/polyfill.js +0 -72
  63. package/lib/vendor/promise.js +0 -255
@@ -20,16 +20,13 @@ export default class Keyboard {
20
20
 
21
21
  this._keyDownList = {}; // List of depressed keys
22
22
  // (even if they are happy)
23
- this._pendingKey = null; // Key waiting for keypress
24
23
  this._altGrArmed = false; // Windows AltGr detection
25
24
 
26
25
  // keep these here so we can refer to them later
27
26
  this._eventHandlers = {
28
27
  'keyup': this._handleKeyUp.bind(this),
29
28
  'keydown': this._handleKeyDown.bind(this),
30
- 'keypress': this._handleKeyPress.bind(this),
31
29
  'blur': this._allKeysUp.bind(this),
32
- 'checkalt': this._checkAlt.bind(this),
33
30
  };
34
31
 
35
32
  // ===== EVENT HANDLERS =====
@@ -62,9 +59,7 @@ export default class Keyboard {
62
59
  }
63
60
 
64
61
  // Unstable, but we don't have anything else to go on
65
- // (don't use it for 'keypress' events thought since
66
- // WebKit sets it to the same as charCode)
67
- if (e.keyCode && (e.type !== 'keypress')) {
62
+ if (e.keyCode) {
68
63
  // 229 is used for composition events
69
64
  if (e.keyCode !== 229) {
70
65
  return 'Platform' + e.keyCode;
@@ -169,20 +164,20 @@ export default class Keyboard {
169
164
  return;
170
165
  }
171
166
 
172
- // If this is a legacy browser then we'll need to wait for
173
- // a keypress event as well
174
- // (IE and Edge has a broken KeyboardEvent.key, so we can't
175
- // just check for the presence of that field)
176
- if (!keysym && (!e.key || browser.isIE() || browser.isEdge())) {
177
- this._pendingKey = code;
178
- // However we might not get a keypress event if the key
179
- // is non-printable, which needs some special fallback
180
- // handling
181
- setTimeout(this._handleKeyPressTimeout.bind(this), 10, e);
167
+ // Windows doesn't send proper key releases for a bunch of
168
+ // Japanese IM keys so we have to fake the release right away
169
+ const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku,
170
+ KeyTable.XK_Eisu_toggle,
171
+ KeyTable.XK_Katakana,
172
+ KeyTable.XK_Hiragana,
173
+ KeyTable.XK_Romaji ];
174
+ if (browser.isWindows() && jpBadKeys.includes(keysym)) {
175
+ this._sendKeyEvent(keysym, code, true);
176
+ this._sendKeyEvent(keysym, code, false);
177
+ stopEvent(e);
182
178
  return;
183
179
  }
184
180
 
185
- this._pendingKey = null;
186
181
  stopEvent(e);
187
182
 
188
183
  // Possible start of AltGr sequence? (see above)
@@ -197,69 +192,6 @@ export default class Keyboard {
197
192
  this._sendKeyEvent(keysym, code, true);
198
193
  }
199
194
 
200
- // Legacy event for browsers without code/key
201
- _handleKeyPress(e) {
202
- stopEvent(e);
203
-
204
- // Are we expecting a keypress?
205
- if (this._pendingKey === null) {
206
- return;
207
- }
208
-
209
- let code = this._getKeyCode(e);
210
- const keysym = KeyboardUtil.getKeysym(e);
211
-
212
- // The key we were waiting for?
213
- if ((code !== 'Unidentified') && (code != this._pendingKey)) {
214
- return;
215
- }
216
-
217
- code = this._pendingKey;
218
- this._pendingKey = null;
219
-
220
- if (!keysym) {
221
- Log.Info('keypress with no keysym:', e);
222
- return;
223
- }
224
-
225
- this._sendKeyEvent(keysym, code, true);
226
- }
227
-
228
- _handleKeyPressTimeout(e) {
229
- // Did someone manage to sort out the key already?
230
- if (this._pendingKey === null) {
231
- return;
232
- }
233
-
234
- let keysym;
235
-
236
- const code = this._pendingKey;
237
- this._pendingKey = null;
238
-
239
- // We have no way of knowing the proper keysym with the
240
- // information given, but the following are true for most
241
- // layouts
242
- if ((e.keyCode >= 0x30) && (e.keyCode <= 0x39)) {
243
- // Digit
244
- keysym = e.keyCode;
245
- } else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
246
- // Character (A-Z)
247
- let char = String.fromCharCode(e.keyCode);
248
- // A feeble attempt at the correct case
249
- if (e.shiftKey) {
250
- char = char.toUpperCase();
251
- } else {
252
- char = char.toLowerCase();
253
- }
254
- keysym = char.charCodeAt();
255
- } else {
256
- // Unknown, give up
257
- keysym = 0;
258
- }
259
-
260
- this._sendKeyEvent(keysym, code, true);
261
- }
262
-
263
195
  _handleKeyUp(e) {
264
196
  stopEvent(e);
265
197
 
@@ -312,30 +244,6 @@ export default class Keyboard {
312
244
  Log.Debug("<< Keyboard.allKeysUp");
313
245
  }
314
246
 
315
- // Alt workaround for Firefox on Windows, see below
316
- _checkAlt(e) {
317
- if (e.skipCheckAlt) {
318
- return;
319
- }
320
- if (e.altKey) {
321
- return;
322
- }
323
-
324
- const target = this._target;
325
- const downList = this._keyDownList;
326
- ['AltLeft', 'AltRight'].forEach((code) => {
327
- if (!(code in downList)) {
328
- return;
329
- }
330
-
331
- const event = new KeyboardEvent('keyup',
332
- { key: downList[code],
333
- code: code });
334
- event.skipCheckAlt = true;
335
- target.dispatchEvent(event);
336
- });
337
- }
338
-
339
247
  // ===== PUBLIC METHODS =====
340
248
 
341
249
  grab() {
@@ -343,41 +251,18 @@ export default class Keyboard {
343
251
 
344
252
  this._target.addEventListener('keydown', this._eventHandlers.keydown);
345
253
  this._target.addEventListener('keyup', this._eventHandlers.keyup);
346
- this._target.addEventListener('keypress', this._eventHandlers.keypress);
347
254
 
348
255
  // Release (key up) if window loses focus
349
256
  window.addEventListener('blur', this._eventHandlers.blur);
350
257
 
351
- // Firefox on Windows has broken handling of Alt, so we need to
352
- // poll as best we can for releases (still doesn't prevent the
353
- // menu from popping up though as we can't call
354
- // preventDefault())
355
- if (browser.isWindows() && browser.isFirefox()) {
356
- const handler = this._eventHandlers.checkalt;
357
- ['mousedown', 'mouseup', 'mousemove', 'wheel',
358
- 'touchstart', 'touchend', 'touchmove',
359
- 'keydown', 'keyup'].forEach(type =>
360
- document.addEventListener(type, handler,
361
- { capture: true,
362
- passive: true }));
363
- }
364
-
365
258
  //Log.Debug("<< Keyboard.grab");
366
259
  }
367
260
 
368
261
  ungrab() {
369
262
  //Log.Debug(">> Keyboard.ungrab");
370
263
 
371
- if (browser.isWindows() && browser.isFirefox()) {
372
- const handler = this._eventHandlers.checkalt;
373
- ['mousedown', 'mouseup', 'mousemove', 'wheel',
374
- 'touchstart', 'touchend', 'touchmove',
375
- 'keydown', 'keyup'].forEach(type => document.removeEventListener(type, handler));
376
- }
377
-
378
264
  this._target.removeEventListener('keydown', this._eventHandlers.keydown);
379
265
  this._target.removeEventListener('keyup', this._eventHandlers.keyup);
380
- this._target.removeEventListener('keypress', this._eventHandlers.keypress);
381
266
  window.removeEventListener('blur', this._eventHandlers.blur);
382
267
 
383
268
  // Release (key up) all keys that are in a down state
@@ -22,9 +22,8 @@ export function getKeycode(evt) {
22
22
  }
23
23
 
24
24
  // The de-facto standard is to use Windows Virtual-Key codes
25
- // in the 'keyCode' field for non-printable characters. However
26
- // Webkit sets it to the same as charCode in 'keypress' events.
27
- if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
25
+ // in the 'keyCode' field for non-printable characters
26
+ if (evt.keyCode in vkeys) {
28
27
  let code = vkeys[evt.keyCode];
29
28
 
30
29
  // macOS has messed up this code for some reason
@@ -69,26 +68,6 @@ export function getKeycode(evt) {
69
68
  export function getKey(evt) {
70
69
  // Are we getting a proper key value?
71
70
  if (evt.key !== undefined) {
72
- // IE and Edge use some ancient version of the spec
73
- // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
74
- switch (evt.key) {
75
- case 'Spacebar': return ' ';
76
- case 'Esc': return 'Escape';
77
- case 'Scroll': return 'ScrollLock';
78
- case 'Win': return 'Meta';
79
- case 'Apps': return 'ContextMenu';
80
- case 'Up': return 'ArrowUp';
81
- case 'Left': return 'ArrowLeft';
82
- case 'Right': return 'ArrowRight';
83
- case 'Down': return 'ArrowDown';
84
- case 'Del': return 'Delete';
85
- case 'Divide': return '/';
86
- case 'Multiply': return '*';
87
- case 'Subtract': return '-';
88
- case 'Add': return '+';
89
- case 'Decimal': return evt.char;
90
- }
91
-
92
71
  // Mozilla isn't fully in sync with the spec yet
93
72
  switch (evt.key) {
94
73
  case 'OS': return 'Meta';
@@ -110,18 +89,7 @@ export function getKey(evt) {
110
89
  return 'Delete';
111
90
  }
112
91
 
113
- // IE and Edge need special handling, but for everyone else we
114
- // can trust the value provided
115
- if (!browser.isIE() && !browser.isEdge()) {
116
- return evt.key;
117
- }
118
-
119
- // IE and Edge have broken handling of AltGraph so we can only
120
- // trust them for non-printable characters (and unfortunately
121
- // they also specify 'Unidentified' for some problem keys)
122
- if ((evt.key.length !== 1) && (evt.key !== 'Unidentified')) {
123
- return evt.key;
124
- }
92
+ return evt.key;
125
93
  }
126
94
 
127
95
  // Try to deduce it based on the physical key
@@ -189,6 +157,21 @@ export function getKeysym(evt) {
189
157
  }
190
158
  }
191
159
 
160
+ // Windows sends alternating symbols for some keys when using a
161
+ // Japanese layout. We have no way of synchronising with the IM
162
+ // running on the remote system, so we send some combined keysym
163
+ // instead and hope for the best.
164
+ if (browser.isWindows()) {
165
+ switch (key) {
166
+ case 'Zenkaku':
167
+ case 'Hankaku':
168
+ return KeyTable.XK_Zenkaku_Hankaku;
169
+ case 'Romaji':
170
+ case 'KanaMode':
171
+ return KeyTable.XK_Romaji;
172
+ }
173
+ }
174
+
192
175
  return DOMKeyTable[key][location];
193
176
  }
194
177
 
@@ -13,7 +13,6 @@ export default {
13
13
  0x08: 'Backspace',
14
14
  0x09: 'Tab',
15
15
  0x0a: 'NumpadClear',
16
- 0x0c: 'Numpad5', // IE11 sends evt.keyCode: 12 when numlock is off
17
16
  0x0d: 'Enter',
18
17
  0x10: 'ShiftLeft',
19
18
  0x11: 'ControlLeft',
@@ -1,8 +1,8 @@
1
1
  /*
2
- * This file is auto-generated from keymaps.csv on 2017-05-31 16:20
3
- * Database checksum sha256(92fd165507f2a3b8c5b3fa56e425d45788dbcb98cf067a307527d91ce22cab94)
2
+ * This file is auto-generated from keymaps.csv
3
+ * Database checksum sha256(76d68c10e97d37fe2ea459e210125ae41796253fb217e900bf2983ade13a7920)
4
4
  * To re-generate, run:
5
- * keymap-gen --lang=js code-map keymaps.csv html atset1
5
+ * keymap-gen code-map --lang=js keymaps.csv html atset1
6
6
  */
7
7
  export default {
8
8
  "Again": 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */
@@ -111,6 +111,8 @@ export default {
111
111
  "KeyX": 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */
112
112
  "KeyY": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */
113
113
  "KeyZ": 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */
114
+ "Lang1": 0x72, /* html:Lang1 (Lang1) -> linux:122 (KEY_HANGEUL) -> atset1:114 */
115
+ "Lang2": 0x71, /* html:Lang2 (Lang2) -> linux:123 (KEY_HANJA) -> atset1:113 */
114
116
  "Lang3": 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
115
117
  "Lang4": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
116
118
  "Lang5": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */
package/core/rfb.js CHANGED
@@ -25,7 +25,6 @@ import DES from "./des.js";
25
25
  import KeyTable from "./input/keysym.js";
26
26
  import XtScancode from "./input/xtscancodes.js";
27
27
  import { encodings } from "./encodings.js";
28
- import "./util/polyfill.js";
29
28
 
30
29
  import RawDecoder from "./decoders/raw.js";
31
30
  import CopyRectDecoder from "./decoders/copyrect.js";
@@ -67,20 +66,25 @@ const extendedClipboardActionPeek = 1 << 26;
67
66
  const extendedClipboardActionNotify = 1 << 27;
68
67
  const extendedClipboardActionProvide = 1 << 28;
69
68
 
70
-
71
69
  export default class RFB extends EventTargetMixin {
72
- constructor(target, url, options) {
70
+ constructor(target, urlOrChannel, options) {
73
71
  if (!target) {
74
72
  throw new Error("Must specify target");
75
73
  }
76
- if (!url) {
77
- throw new Error("Must specify URL");
74
+ if (!urlOrChannel) {
75
+ throw new Error("Must specify URL, WebSocket or RTCDataChannel");
78
76
  }
79
77
 
80
78
  super();
81
79
 
82
80
  this._target = target;
83
- this._url = url;
81
+
82
+ if (typeof urlOrChannel === "string") {
83
+ this._url = urlOrChannel;
84
+ } else {
85
+ this._url = null;
86
+ this._rawChannel = urlOrChannel;
87
+ }
84
88
 
85
89
  // Connection details
86
90
  options = options || {};
@@ -130,6 +134,7 @@ export default class RFB extends EventTargetMixin {
130
134
  this._flushing = false; // Display flushing state
131
135
  this._keyboard = null; // Keyboard input handler object
132
136
  this._gestures = null; // Gesture input handler object
137
+ this._resizeObserver = null; // Resize observer object
133
138
 
134
139
  // Timers
135
140
  this._disconnTimer = null; // disconnection timer
@@ -167,7 +172,7 @@ export default class RFB extends EventTargetMixin {
167
172
  // Bound event handlers
168
173
  this._eventHandlers = {
169
174
  focusCanvas: this._focusCanvas.bind(this),
170
- windowResize: this._windowResize.bind(this),
175
+ handleResize: this._handleResize.bind(this),
171
176
  handleMouse: this._handleMouse.bind(this),
172
177
  handleWheel: this._handleWheel.bind(this),
173
178
  handleGesture: this._handleGesture.bind(this),
@@ -187,8 +192,6 @@ export default class RFB extends EventTargetMixin {
187
192
  this._canvas.style.margin = 'auto';
188
193
  // Some browsers add an outline on focus
189
194
  this._canvas.style.outline = 'none';
190
- // IE miscalculates width without this :(
191
- this._canvas.style.flexShrink = '0';
192
195
  this._canvas.width = 0;
193
196
  this._canvas.height = 0;
194
197
  this._canvas.tabIndex = -1;
@@ -232,58 +235,15 @@ export default class RFB extends EventTargetMixin {
232
235
  this._gestures = new GestureHandler();
233
236
 
234
237
  this._sock = new Websock();
235
- this._sock.on('message', () => {
236
- this._handleMessage();
237
- });
238
- this._sock.on('open', () => {
239
- if ((this._rfbConnectionState === 'connecting') &&
240
- (this._rfbInitState === '')) {
241
- this._rfbInitState = 'ProtocolVersion';
242
- Log.Debug("Starting VNC handshake");
243
- } else {
244
- this._fail("Unexpected server connection while " +
245
- this._rfbConnectionState);
246
- }
247
- });
248
- this._sock.on('close', (e) => {
249
- Log.Debug("WebSocket on-close event");
250
- let msg = "";
251
- if (e.code) {
252
- msg = "(code: " + e.code;
253
- if (e.reason) {
254
- msg += ", reason: " + e.reason;
255
- }
256
- msg += ")";
257
- }
258
- switch (this._rfbConnectionState) {
259
- case 'connecting':
260
- this._fail("Connection closed " + msg);
261
- break;
262
- case 'connected':
263
- // Handle disconnects that were initiated server-side
264
- this._updateConnectionState('disconnecting');
265
- this._updateConnectionState('disconnected');
266
- break;
267
- case 'disconnecting':
268
- // Normal disconnection path
269
- this._updateConnectionState('disconnected');
270
- break;
271
- case 'disconnected':
272
- this._fail("Unexpected server disconnect " +
273
- "when already disconnected " + msg);
274
- break;
275
- default:
276
- this._fail("Unexpected server disconnect before connecting " +
277
- msg);
278
- break;
279
- }
280
- this._sock.off('close');
281
- });
282
- this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
238
+ this._sock.on('open', this._socketOpen.bind(this));
239
+ this._sock.on('close', this._socketClose.bind(this));
240
+ this._sock.on('message', this._handleMessage.bind(this));
241
+ this._sock.on('error', this._socketError.bind(this));
242
+
243
+ this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
283
244
 
284
- // Slight delay of the actual connection so that the caller has
285
- // time to set up callbacks
286
- setTimeout(this._updateConnectionState.bind(this, 'connecting'));
245
+ // All prepared, kick off the connection
246
+ this._updateConnectionState('connecting');
287
247
 
288
248
  Log.Debug("<< RFB.constructor");
289
249
 
@@ -472,8 +432,8 @@ export default class RFB extends EventTargetMixin {
472
432
  }
473
433
  }
474
434
 
475
- focus() {
476
- this._canvas.focus();
435
+ focus(options) {
436
+ this._canvas.focus(options);
477
437
  }
478
438
 
479
439
  blur() {
@@ -504,16 +464,22 @@ export default class RFB extends EventTargetMixin {
504
464
  _connect() {
505
465
  Log.Debug(">> RFB.connect");
506
466
 
507
- Log.Info("connecting to " + this._url);
508
-
509
- try {
510
- // WebSocket.onopen transitions to the RFB init states
467
+ if (this._url) {
468
+ Log.Info(`connecting to ${this._url}`);
511
469
  this._sock.open(this._url, this._wsProtocols);
512
- } catch (e) {
513
- if (e.name === 'SyntaxError') {
514
- this._fail("Invalid host or port (" + e + ")");
515
- } else {
516
- this._fail("Error when opening socket (" + e + ")");
470
+ } else {
471
+ Log.Info(`attaching ${this._rawChannel} to Websock`);
472
+ this._sock.attach(this._rawChannel);
473
+
474
+ if (this._sock.readyState === 'closed') {
475
+ throw Error("Cannot use already closed WebSocket/RTCDataChannel");
476
+ }
477
+
478
+ if (this._sock.readyState === 'open') {
479
+ // FIXME: _socketOpen() can in theory call _fail(), which
480
+ // isn't allowed this early, but I'm not sure that can
481
+ // happen without a bug messing up our state variables
482
+ this._socketOpen();
517
483
  }
518
484
  }
519
485
 
@@ -525,9 +491,8 @@ export default class RFB extends EventTargetMixin {
525
491
  this._cursor.attach(this._canvas);
526
492
  this._refreshCursor();
527
493
 
528
- // Monitor size changes of the screen
529
- // FIXME: Use ResizeObserver, or hidden overflow
530
- window.addEventListener('resize', this._eventHandlers.windowResize);
494
+ // Monitor size changes of the screen element
495
+ this._resizeObserver.observe(this._screen);
531
496
 
532
497
  // Always grab focus on some kind of click event
533
498
  this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
@@ -568,7 +533,7 @@ export default class RFB extends EventTargetMixin {
568
533
  this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
569
534
  this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
570
535
  this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
571
- window.removeEventListener('resize', this._eventHandlers.windowResize);
536
+ this._resizeObserver.disconnect();
572
537
  this._keyboard.ungrab();
573
538
  this._gestures.detach();
574
539
  this._sock.close();
@@ -587,12 +552,64 @@ export default class RFB extends EventTargetMixin {
587
552
  Log.Debug("<< RFB.disconnect");
588
553
  }
589
554
 
555
+ _socketOpen() {
556
+ if ((this._rfbConnectionState === 'connecting') &&
557
+ (this._rfbInitState === '')) {
558
+ this._rfbInitState = 'ProtocolVersion';
559
+ Log.Debug("Starting VNC handshake");
560
+ } else {
561
+ this._fail("Unexpected server connection while " +
562
+ this._rfbConnectionState);
563
+ }
564
+ }
565
+
566
+ _socketClose(e) {
567
+ Log.Debug("WebSocket on-close event");
568
+ let msg = "";
569
+ if (e.code) {
570
+ msg = "(code: " + e.code;
571
+ if (e.reason) {
572
+ msg += ", reason: " + e.reason;
573
+ }
574
+ msg += ")";
575
+ }
576
+ switch (this._rfbConnectionState) {
577
+ case 'connecting':
578
+ this._fail("Connection closed " + msg);
579
+ break;
580
+ case 'connected':
581
+ // Handle disconnects that were initiated server-side
582
+ this._updateConnectionState('disconnecting');
583
+ this._updateConnectionState('disconnected');
584
+ break;
585
+ case 'disconnecting':
586
+ // Normal disconnection path
587
+ this._updateConnectionState('disconnected');
588
+ break;
589
+ case 'disconnected':
590
+ this._fail("Unexpected server disconnect " +
591
+ "when already disconnected " + msg);
592
+ break;
593
+ default:
594
+ this._fail("Unexpected server disconnect before connecting " +
595
+ msg);
596
+ break;
597
+ }
598
+ this._sock.off('close');
599
+ // Delete reference to raw channel to allow cleanup.
600
+ this._rawChannel = null;
601
+ }
602
+
603
+ _socketError(e) {
604
+ Log.Warn("WebSocket on-error event");
605
+ }
606
+
590
607
  _focusCanvas(event) {
591
608
  if (!this.focusOnClick) {
592
609
  return;
593
610
  }
594
611
 
595
- this.focus();
612
+ this.focus({ preventScroll: true });
596
613
  }
597
614
 
598
615
  _setDesktopName(name) {
@@ -602,7 +619,7 @@ export default class RFB extends EventTargetMixin {
602
619
  { detail: { name: this._fbName } }));
603
620
  }
604
621
 
605
- _windowResize(event) {
622
+ _handleResize() {
606
623
  // If the window resized then our screen element might have
607
624
  // as well. Update the viewport dimensions.
608
625
  window.requestAnimationFrame(() => {
@@ -1263,17 +1280,6 @@ export default class RFB extends EventTargetMixin {
1263
1280
  }
1264
1281
 
1265
1282
  _negotiateSecurity() {
1266
- // Polyfill since IE and PhantomJS doesn't have
1267
- // TypedArray.includes()
1268
- function includes(item, array) {
1269
- for (let i = 0; i < array.length; i++) {
1270
- if (array[i] === item) {
1271
- return true;
1272
- }
1273
- }
1274
- return false;
1275
- }
1276
-
1277
1283
  if (this._rfbVersion >= 3.7) {
1278
1284
  // Server sends supported list, client decides
1279
1285
  const numTypes = this._sock.rQshift8();
@@ -1290,15 +1296,15 @@ export default class RFB extends EventTargetMixin {
1290
1296
  Log.Debug("Server security types: " + types);
1291
1297
 
1292
1298
  // Look for each auth in preferred order
1293
- if (includes(1, types)) {
1299
+ if (types.includes(1)) {
1294
1300
  this._rfbAuthScheme = 1; // None
1295
- } else if (includes(22, types)) {
1301
+ } else if (types.includes(22)) {
1296
1302
  this._rfbAuthScheme = 22; // XVP
1297
- } else if (includes(16, types)) {
1303
+ } else if (types.includes(16)) {
1298
1304
  this._rfbAuthScheme = 16; // Tight
1299
- } else if (includes(2, types)) {
1305
+ } else if (types.includes(2)) {
1300
1306
  this._rfbAuthScheme = 2; // VNC Auth
1301
- } else if (includes(19, types)) {
1307
+ } else if (types.includes(19)) {
1302
1308
  this._rfbAuthScheme = 19; // VeNCrypt Auth
1303
1309
  } else {
1304
1310
  return this._fail("Unsupported security types (types: " + types + ")");
@@ -1441,8 +1447,8 @@ export default class RFB extends EventTargetMixin {
1441
1447
 
1442
1448
  // negotiated Plain subtype, server waits for password
1443
1449
  if (this._rfbVeNCryptState == 4) {
1444
- if (!this._rfbCredentials.username ||
1445
- !this._rfbCredentials.password) {
1450
+ if (this._rfbCredentials.username === undefined ||
1451
+ this._rfbCredentials.password === undefined) {
1446
1452
  this.dispatchEvent(new CustomEvent(
1447
1453
  "credentialsrequired",
1448
1454
  { detail: { types: ["username", "password"] } }));
@@ -1452,9 +1458,18 @@ export default class RFB extends EventTargetMixin {
1452
1458
  const user = encodeUTF8(this._rfbCredentials.username);
1453
1459
  const pass = encodeUTF8(this._rfbCredentials.password);
1454
1460
 
1455
- // XXX we assume lengths are <= 255 (should not be an issue in the real world)
1456
- this._sock.send([0, 0, 0, user.length]);
1457
- this._sock.send([0, 0, 0, pass.length]);
1461
+ this._sock.send([
1462
+ (user.length >> 24) & 0xFF,
1463
+ (user.length >> 16) & 0xFF,
1464
+ (user.length >> 8) & 0xFF,
1465
+ user.length & 0xFF
1466
+ ]);
1467
+ this._sock.send([
1468
+ (pass.length >> 24) & 0xFF,
1469
+ (pass.length >> 16) & 0xFF,
1470
+ (pass.length >> 8) & 0xFF,
1471
+ pass.length & 0xFF
1472
+ ]);
1458
1473
  this._sock.sendString(user);
1459
1474
  this._sock.sendString(pass);
1460
1475
 
@@ -2183,15 +2198,7 @@ export default class RFB extends EventTargetMixin {
2183
2198
  return this._handleCursor();
2184
2199
 
2185
2200
  case encodings.pseudoEncodingQEMUExtendedKeyEvent:
2186
- // Old Safari doesn't support creating keyboard events
2187
- try {
2188
- const keyboardEvent = document.createEvent("keyboardEvent");
2189
- if (keyboardEvent.code !== undefined) {
2190
- this._qemuExtKeyEventSupported = true;
2191
- }
2192
- } catch (err) {
2193
- // Do nothing
2194
- }
2201
+ this._qemuExtKeyEventSupported = true;
2195
2202
  return true;
2196
2203
 
2197
2204
  case encodings.pseudoEncodingDesktopName:
@@ -2882,9 +2889,9 @@ RFB.messages = {
2882
2889
  buff[offset + 12] = 0; // blue-max
2883
2890
  buff[offset + 13] = (1 << bits) - 1; // blue-max
2884
2891
 
2885
- buff[offset + 14] = bits * 2; // red-shift
2892
+ buff[offset + 14] = bits * 0; // red-shift
2886
2893
  buff[offset + 15] = bits * 1; // green-shift
2887
- buff[offset + 16] = bits * 0; // blue-shift
2894
+ buff[offset + 16] = bits * 2; // blue-shift
2888
2895
 
2889
2896
  buff[offset + 17] = 0; // padding
2890
2897
  buff[offset + 18] = 0; // padding