@novnc/novnc 1.3.0-g42ec5f3 → 1.3.0-g58dfb7d

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.
package/core/display.js CHANGED
@@ -224,6 +224,18 @@ export default class Display {
224
224
  this.viewportChangePos(0, 0);
225
225
  }
226
226
 
227
+ getImageData() {
228
+ return this._drawCtx.getImageData(0, 0, this.width, this.height);
229
+ }
230
+
231
+ toDataURL(type, encoderOptions) {
232
+ return this._backbuffer.toDataURL(type, encoderOptions);
233
+ }
234
+
235
+ toBlob(callback, type, quality) {
236
+ return this._backbuffer.toBlob(callback, type, quality);
237
+ }
238
+
227
239
  // Track what parts of the visible canvas that need updating
228
240
  _damage(x, y, w, h) {
229
241
  if (x < this._damageBounds.left) {
package/core/rfb.js CHANGED
@@ -27,7 +27,6 @@ import XtScancode from "./input/xtscancodes.js";
27
27
  import { encodings } from "./encodings.js";
28
28
  import RSAAESAuthenticationState from "./ra2.js";
29
29
  import { MD5 } from "./util/md5.js";
30
- import { modPow } from "./util/bigint-mod-arith.js";
31
30
 
32
31
  import RawDecoder from "./decoders/raw.js";
33
32
  import CopyRectDecoder from "./decoders/copyrect.js";
@@ -55,6 +54,21 @@ const GESTURE_SCRLSENS = 50;
55
54
  const DOUBLE_TAP_TIMEOUT = 1000;
56
55
  const DOUBLE_TAP_THRESHOLD = 50;
57
56
 
57
+ // Security types
58
+ const securityTypeNone = 1;
59
+ const securityTypeVNCAuth = 2;
60
+ const securityTypeRA2ne = 6;
61
+ const securityTypeTight = 16;
62
+ const securityTypeVeNCrypt = 19;
63
+ const securityTypeXVP = 22;
64
+ const securityTypeARD = 30;
65
+
66
+ // Special Tight security types
67
+ const securityTypeUnixLogon = 129;
68
+
69
+ // VeNCrypt security types
70
+ const securityTypePlain = 256;
71
+
58
72
  // Extended clipboard pseudo-encoding formats
59
73
  const extendedClipboardFormatText = 1;
60
74
  /*eslint-disable no-unused-vars */
@@ -80,6 +94,12 @@ export default class RFB extends EventTargetMixin {
80
94
  throw new Error("Must specify URL, WebSocket or RTCDataChannel");
81
95
  }
82
96
 
97
+ // We rely on modern APIs which might not be available in an
98
+ // insecure context
99
+ if (!window.isSecureContext) {
100
+ Log.Error("noVNC requires a secure context (TLS). Expect crashes!");
101
+ }
102
+
83
103
  super();
84
104
 
85
105
  this._target = target;
@@ -397,7 +417,7 @@ export default class RFB extends EventTargetMixin {
397
417
 
398
418
  sendCredentials(creds) {
399
419
  this._rfbCredentials = creds;
400
- setTimeout(this._initMsg.bind(this), 0);
420
+ this._resumeAuthentication();
401
421
  }
402
422
 
403
423
  sendCtrlAltDel() {
@@ -480,6 +500,18 @@ export default class RFB extends EventTargetMixin {
480
500
  }
481
501
  }
482
502
 
503
+ getImageData() {
504
+ return this._display.getImageData();
505
+ }
506
+
507
+ toDataURL(type, encoderOptions) {
508
+ return this._display.toDataURL(type, encoderOptions);
509
+ }
510
+
511
+ toBlob(callback, type, quality) {
512
+ return this._display.toBlob(callback, type, quality);
513
+ }
514
+
483
515
  // ===== PRIVATE METHODS =====
484
516
 
485
517
  _connect() {
@@ -917,8 +949,15 @@ export default class RFB extends EventTargetMixin {
917
949
  }
918
950
  }
919
951
  break;
952
+ case 'connecting':
953
+ while (this._rfbConnectionState === 'connecting') {
954
+ if (!this._initMsg()) {
955
+ break;
956
+ }
957
+ }
958
+ break;
920
959
  default:
921
- this._initMsg();
960
+ Log.Error("Got data while in an invalid state");
922
961
  break;
923
962
  }
924
963
  }
@@ -1327,6 +1366,21 @@ export default class RFB extends EventTargetMixin {
1327
1366
  this._rfbInitState = 'Security';
1328
1367
  }
1329
1368
 
1369
+ _isSupportedSecurityType(type) {
1370
+ const clientTypes = [
1371
+ securityTypeNone,
1372
+ securityTypeVNCAuth,
1373
+ securityTypeRA2ne,
1374
+ securityTypeTight,
1375
+ securityTypeVeNCrypt,
1376
+ securityTypeXVP,
1377
+ securityTypeARD,
1378
+ securityTypePlain,
1379
+ ];
1380
+
1381
+ return clientTypes.includes(type);
1382
+ }
1383
+
1330
1384
  _negotiateSecurity() {
1331
1385
  if (this._rfbVersion >= 3.7) {
1332
1386
  // Server sends supported list, client decides
@@ -1337,28 +1391,23 @@ export default class RFB extends EventTargetMixin {
1337
1391
  this._rfbInitState = "SecurityReason";
1338
1392
  this._securityContext = "no security types";
1339
1393
  this._securityStatus = 1;
1340
- return this._initMsg();
1394
+ return true;
1341
1395
  }
1342
1396
 
1343
1397
  const types = this._sock.rQshiftBytes(numTypes);
1344
1398
  Log.Debug("Server security types: " + types);
1345
1399
 
1346
- // Look for each auth in preferred order
1347
- if (types.includes(1)) {
1348
- this._rfbAuthScheme = 1; // None
1349
- } else if (types.includes(22)) {
1350
- this._rfbAuthScheme = 22; // XVP
1351
- } else if (types.includes(16)) {
1352
- this._rfbAuthScheme = 16; // Tight
1353
- } else if (types.includes(6)) {
1354
- this._rfbAuthScheme = 6; // RA2ne Auth
1355
- } else if (types.includes(2)) {
1356
- this._rfbAuthScheme = 2; // VNC Auth
1357
- } else if (types.includes(30)) {
1358
- this._rfbAuthScheme = 30; // ARD Auth
1359
- } else if (types.includes(19)) {
1360
- this._rfbAuthScheme = 19; // VeNCrypt Auth
1361
- } else {
1400
+ // Look for a matching security type in the order that the
1401
+ // server prefers
1402
+ this._rfbAuthScheme = -1;
1403
+ for (let type of types) {
1404
+ if (this._isSupportedSecurityType(type)) {
1405
+ this._rfbAuthScheme = type;
1406
+ break;
1407
+ }
1408
+ }
1409
+
1410
+ if (this._rfbAuthScheme === -1) {
1362
1411
  return this._fail("Unsupported security types (types: " + types + ")");
1363
1412
  }
1364
1413
 
@@ -1372,14 +1421,14 @@ export default class RFB extends EventTargetMixin {
1372
1421
  this._rfbInitState = "SecurityReason";
1373
1422
  this._securityContext = "authentication scheme";
1374
1423
  this._securityStatus = 1;
1375
- return this._initMsg();
1424
+ return true;
1376
1425
  }
1377
1426
  }
1378
1427
 
1379
1428
  this._rfbInitState = 'Authentication';
1380
1429
  Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);
1381
1430
 
1382
- return this._initMsg(); // jump to authentication
1431
+ return true;
1383
1432
  }
1384
1433
 
1385
1434
  _handleSecurityReason() {
@@ -1429,7 +1478,7 @@ export default class RFB extends EventTargetMixin {
1429
1478
  this._rfbCredentials.username +
1430
1479
  this._rfbCredentials.target;
1431
1480
  this._sock.sendString(xvpAuthStr);
1432
- this._rfbAuthScheme = 2;
1481
+ this._rfbAuthScheme = securityTypeVNCAuth;
1433
1482
  return this._negotiateAuthentication();
1434
1483
  }
1435
1484
 
@@ -1487,49 +1536,66 @@ export default class RFB extends EventTargetMixin {
1487
1536
  subtypes.push(this._sock.rQshift32());
1488
1537
  }
1489
1538
 
1490
- // 256 = Plain subtype
1491
- if (subtypes.indexOf(256) != -1) {
1492
- // 0x100 = 256
1493
- this._sock.send([0, 0, 1, 0]);
1494
- this._rfbVeNCryptState = 4;
1495
- } else {
1496
- return this._fail("VeNCrypt Plain subtype not offered by server");
1539
+ // Look for a matching security type in the order that the
1540
+ // server prefers
1541
+ this._rfbAuthScheme = -1;
1542
+ for (let type of subtypes) {
1543
+ // Avoid getting in to a loop
1544
+ if (type === securityTypeVeNCrypt) {
1545
+ continue;
1546
+ }
1547
+
1548
+ if (this._isSupportedSecurityType(type)) {
1549
+ this._rfbAuthScheme = type;
1550
+ break;
1551
+ }
1497
1552
  }
1498
- }
1499
1553
 
1500
- // negotiated Plain subtype, server waits for password
1501
- if (this._rfbVeNCryptState == 4) {
1502
- if (this._rfbCredentials.username === undefined ||
1503
- this._rfbCredentials.password === undefined) {
1504
- this.dispatchEvent(new CustomEvent(
1505
- "credentialsrequired",
1506
- { detail: { types: ["username", "password"] } }));
1507
- return false;
1554
+ if (this._rfbAuthScheme === -1) {
1555
+ return this._fail("Unsupported security types (types: " + subtypes + ")");
1508
1556
  }
1509
1557
 
1510
- const user = encodeUTF8(this._rfbCredentials.username);
1511
- const pass = encodeUTF8(this._rfbCredentials.password);
1512
-
1513
- this._sock.send([
1514
- (user.length >> 24) & 0xFF,
1515
- (user.length >> 16) & 0xFF,
1516
- (user.length >> 8) & 0xFF,
1517
- user.length & 0xFF
1518
- ]);
1519
- this._sock.send([
1520
- (pass.length >> 24) & 0xFF,
1521
- (pass.length >> 16) & 0xFF,
1522
- (pass.length >> 8) & 0xFF,
1523
- pass.length & 0xFF
1524
- ]);
1525
- this._sock.sendString(user);
1526
- this._sock.sendString(pass);
1558
+ this._sock.send([this._rfbAuthScheme >> 24,
1559
+ this._rfbAuthScheme >> 16,
1560
+ this._rfbAuthScheme >> 8,
1561
+ this._rfbAuthScheme]);
1527
1562
 
1528
- this._rfbInitState = "SecurityResult";
1563
+ this._rfbVeNCryptState == 4;
1529
1564
  return true;
1530
1565
  }
1531
1566
  }
1532
1567
 
1568
+ _negotiatePlainAuth() {
1569
+ if (this._rfbCredentials.username === undefined ||
1570
+ this._rfbCredentials.password === undefined) {
1571
+ this.dispatchEvent(new CustomEvent(
1572
+ "credentialsrequired",
1573
+ { detail: { types: ["username", "password"] } }));
1574
+ return false;
1575
+ }
1576
+
1577
+ const user = encodeUTF8(this._rfbCredentials.username);
1578
+ const pass = encodeUTF8(this._rfbCredentials.password);
1579
+
1580
+ this._sock.send([
1581
+ (user.length >> 24) & 0xFF,
1582
+ (user.length >> 16) & 0xFF,
1583
+ (user.length >> 8) & 0xFF,
1584
+ user.length & 0xFF
1585
+ ]);
1586
+ this._sock.send([
1587
+ (pass.length >> 24) & 0xFF,
1588
+ (pass.length >> 16) & 0xFF,
1589
+ (pass.length >> 8) & 0xFF,
1590
+ pass.length & 0xFF
1591
+ ]);
1592
+ this._sock.sendString(user);
1593
+ this._sock.sendString(pass);
1594
+
1595
+ this._rfbInitState = "SecurityResult";
1596
+ return true;
1597
+ }
1598
+
1533
1599
  _negotiateStdVNCAuth() {
1534
1600
  if (this._sock.rQwait("auth challenge", 16)) { return false; }
1535
1601
 
@@ -1595,7 +1661,19 @@ export default class RFB extends EventTargetMixin {
1595
1661
  let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1596
1662
  let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1597
1663
 
1598
- let hexResult = modPow(BigInt(baseHex), BigInt(exponentHex), BigInt(modulusHex)).toString(16);
1664
+ let b = BigInt(baseHex);
1665
+ let e = BigInt(exponentHex);
1666
+ let m = BigInt(modulusHex);
1667
+ let r = 1n;
1668
+ b = b % m;
1669
+ while (e > 0) {
1670
+ if (e % 2n === 1n) {
1671
+ r = (r * b) % m;
1672
+ }
1673
+ e = e / 2n;
1674
+ b = (b * b) % m;
1675
+ }
1676
+ let hexResult = r.toString(16);
1599
1677
 
1600
1678
  while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
1601
1679
  hexResult = "0"+hexResult;
@@ -1644,7 +1722,7 @@ export default class RFB extends EventTargetMixin {
1644
1722
  this._rfbCredentials.ardCredentials = encrypted;
1645
1723
  this._rfbCredentials.ardPublicKey = clientPublicKey;
1646
1724
 
1647
- setTimeout(this._initMsg.bind(this), 0);
1725
+ this._resumeAuthentication();
1648
1726
  }
1649
1727
 
1650
1728
  _negotiateTightUnixAuth() {
@@ -1754,12 +1832,12 @@ export default class RFB extends EventTargetMixin {
1754
1832
  case 'STDVNOAUTH__': // no auth
1755
1833
  this._rfbInitState = 'SecurityResult';
1756
1834
  return true;
1757
- case 'STDVVNCAUTH_': // VNC auth
1758
- this._rfbAuthScheme = 2;
1759
- return this._initMsg();
1760
- case 'TGHTULGNAUTH': // UNIX auth
1761
- this._rfbAuthScheme = 129;
1762
- return this._initMsg();
1835
+ case 'STDVVNCAUTH_':
1836
+ this._rfbAuthScheme = securityTypeVNCAuth;
1837
+ return true;
1838
+ case 'TGHTULGNAUTH':
1839
+ this._rfbAuthScheme = securityTypeUnixLogon;
1840
+ return true;
1763
1841
  default:
1764
1842
  return this._fail("Unsupported tiny auth scheme " +
1765
1843
  "(scheme: " + authType + ")");
@@ -1796,7 +1874,7 @@ export default class RFB extends EventTargetMixin {
1796
1874
  }).then(() => {
1797
1875
  this.dispatchEvent(new CustomEvent('securityresult'));
1798
1876
  this._rfbInitState = "SecurityResult";
1799
- this._initMsg();
1877
+ return true;
1800
1878
  }).finally(() => {
1801
1879
  this._rfbRSAAESAuthenticationState.removeEventListener(
1802
1880
  "serververification", this._eventHandlers.handleRSAAESServerVerification);
@@ -1810,33 +1888,32 @@ export default class RFB extends EventTargetMixin {
1810
1888
 
1811
1889
  _negotiateAuthentication() {
1812
1890
  switch (this._rfbAuthScheme) {
1813
- case 1: // no auth
1814
- if (this._rfbVersion >= 3.8) {
1815
- this._rfbInitState = 'SecurityResult';
1816
- return true;
1817
- }
1818
- this._rfbInitState = 'ClientInitialisation';
1819
- return this._initMsg();
1891
+ case securityTypeNone:
1892
+ this._rfbInitState = 'SecurityResult';
1893
+ return true;
1820
1894
 
1821
- case 22: // XVP auth
1895
+ case securityTypeXVP:
1822
1896
  return this._negotiateXvpAuth();
1823
1897
 
1824
- case 30: // ARD auth
1898
+ case securityTypeARD:
1825
1899
  return this._negotiateARDAuth();
1826
1900
 
1827
- case 2: // VNC authentication
1901
+ case securityTypeVNCAuth:
1828
1902
  return this._negotiateStdVNCAuth();
1829
1903
 
1830
- case 16: // TightVNC Security Type
1904
+ case securityTypeTight:
1831
1905
  return this._negotiateTightAuth();
1832
1906
 
1833
- case 19: // VeNCrypt Security Type
1907
+ case securityTypeVeNCrypt:
1834
1908
  return this._negotiateVeNCryptAuth();
1835
1909
 
1836
- case 129: // TightVNC UNIX Security Type
1910
+ case securityTypePlain:
1911
+ return this._negotiatePlainAuth();
1912
+
1913
+ case securityTypeUnixLogon:
1837
1914
  return this._negotiateTightUnixAuth();
1838
1915
 
1839
- case 6: // RA2ne Security Type
1916
+ case securityTypeRA2ne:
1840
1917
  return this._negotiateRA2neAuth();
1841
1918
 
1842
1919
  default:
@@ -1846,6 +1923,13 @@ export default class RFB extends EventTargetMixin {
1846
1923
  }
1847
1924
 
1848
1925
  _handleSecurityResult() {
1926
+ // There is no security choice, and hence no security result
1927
+ // until RFB 3.7
1928
+ if (this._rfbVersion < 3.7) {
1929
+ this._rfbInitState = 'ClientInitialisation';
1930
+ return true;
1931
+ }
1932
+
1849
1933
  if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
1850
1934
 
1851
1935
  const status = this._sock.rQshift32();
@@ -1853,13 +1937,13 @@ export default class RFB extends EventTargetMixin {
1853
1937
  if (status === 0) { // OK
1854
1938
  this._rfbInitState = 'ClientInitialisation';
1855
1939
  Log.Debug('Authentication OK');
1856
- return this._initMsg();
1940
+ return true;
1857
1941
  } else {
1858
1942
  if (this._rfbVersion >= 3.8) {
1859
1943
  this._rfbInitState = "SecurityReason";
1860
1944
  this._securityContext = "security result";
1861
1945
  this._securityStatus = status;
1862
- return this._initMsg();
1946
+ return true;
1863
1947
  } else {
1864
1948
  this.dispatchEvent(new CustomEvent(
1865
1949
  "securityfailure",
@@ -2035,6 +2119,14 @@ export default class RFB extends EventTargetMixin {
2035
2119
  }
2036
2120
  }
2037
2121
 
2122
+ // Resume authentication handshake after it was paused for some
2123
+ // reason, e.g. waiting for a password from the user
2124
+ _resumeAuthentication() {
2125
+ // We use setTimeout() so it's run in its own context, just like
2126
+ // it originally did via the WebSocket's event handler
2127
+ setTimeout(this._initMsg.bind(this), 0);
2128
+ }
2129
+
2038
2130
  _handleSetColourMapMsg() {
2039
2131
  Log.Debug("SetColorMapEntries");
2040
2132
 
package/docs/API.md CHANGED
@@ -155,6 +155,15 @@ protocol stream.
155
155
  [`RFB.clipboardPasteFrom()`](#rfbclipboardpastefrom)
156
156
  - Send clipboard contents to server.
157
157
 
158
+ [`RFB.getImageData()`](#rfbgetimagedata)
159
+ - Return the current content of the screen as an ImageData array.
160
+
161
+ [`RFB.toDataURL()`](#rfbtodataurl)
162
+ - Return the current content of the screen as data-url encoded image file.
163
+
164
+ [`RFB.toBlob()`](#rfbtoblob)
165
+ - Return the current content of the screen as Blob encoded image file.
166
+
158
167
  ### Details
159
168
 
160
169
  #### RFB()
@@ -423,3 +432,55 @@ to the remote server.
423
432
 
424
433
  **`text`**
425
434
  - A `DOMString` specifying the clipboard data to send.
435
+
436
+ #### RFB.getImageData()
437
+
438
+ The `RFB.getImageData()` method is used to return the current content of the
439
+ screen encoded as [`ImageData`](https://developer.mozilla.org/en-US/docs/Web/API/ImageData).
440
+
441
+ ##### Syntax
442
+
443
+ RFB.getImageData();
444
+
445
+ #### RFB.toDataURL()
446
+
447
+ The `RFB.toDataURL()` method is used to return the current content of the
448
+ screen encoded as a data URL that could for example be put in the `src` attribute
449
+ of an `img` tag.
450
+
451
+ ##### Syntax
452
+
453
+ RFB.toDataURL();
454
+ RFB.toDataURL(type);
455
+ RFB.toDataURL(type, encoderOptions);
456
+
457
+ ###### Parameters
458
+
459
+ **`type`** *Optional*
460
+ - A string indicating the requested MIME type of the image
461
+
462
+ **`encoderOptions`** *Optional*
463
+ - A number between 0 and 1 indicating the image quality.
464
+
465
+ #### RFB.toBlob()
466
+
467
+ The `RFB.toBlob()` method is used to return the current content of the
468
+ screen encoded as [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob).
469
+
470
+ ##### Syntax
471
+
472
+ RFB.toDataURL(callback);
473
+ RFB.toDataURL(callback, type);
474
+ RFB.toDataURL(callback, type, quality);
475
+
476
+ ###### Parameters
477
+
478
+ **`callback`**
479
+ - A callback function which will receive the resulting [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
480
+ as the single argument
481
+
482
+ **`type`** *Optional*
483
+ - A string indicating the requested MIME type of the image
484
+
485
+ **`encoderOptions`** *Optional*
486
+ - A number between 0 and 1 indicating the image quality.
@@ -19,7 +19,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
19
19
 
20
20
  function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
21
21
 
22
- function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
22
+ function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
23
23
 
24
24
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
25
25
 
@@ -29,7 +29,7 @@ function _assertThisInitialized(self) { if (self === void 0) { throw new Referen
29
29
 
30
30
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
31
31
 
32
- function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
32
+ function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
33
33
 
34
34
  var TightPNGDecoder = /*#__PURE__*/function (_TightDecoder) {
35
35
  _inherits(TightPNGDecoder, _TightDecoder);
package/lib/display.js CHANGED
@@ -246,6 +246,21 @@ var Display = /*#__PURE__*/function () {
246
246
  var vp = this._viewportLoc;
247
247
  this.viewportChangeSize(vp.w, vp.h);
248
248
  this.viewportChangePos(0, 0);
249
+ }
250
+ }, {
251
+ key: "getImageData",
252
+ value: function getImageData() {
253
+ return this._drawCtx.getImageData(0, 0, this.width, this.height);
254
+ }
255
+ }, {
256
+ key: "toDataURL",
257
+ value: function toDataURL(type, encoderOptions) {
258
+ return this._backbuffer.toDataURL(type, encoderOptions);
259
+ }
260
+ }, {
261
+ key: "toBlob",
262
+ value: function toBlob(callback, type, quality) {
263
+ return this._backbuffer.toBlob(callback, type, quality);
249
264
  } // Track what parts of the visible canvas that need updating
250
265
 
251
266
  }, {