]> git.proxmox.com Git - mirror_novnc.git/blame - core/display.js
Validate decoded image dimensions
[mirror_novnc.git] / core / display.js
CommitLineData
c4164bda
JM
1/*
2 * noVNC: HTML5 VNC client
412d9306 3 * Copyright (C) 2019 The noVNC Authors
1d728ace 4 * Licensed under MPL 2.0 (see LICENSE.txt)
c4164bda
JM
5 *
6 * See README.md for usage and integration instructions.
7 */
c4164bda 8
6d6f0db0 9import * as Log from './util/logging.js';
3ae0bb09 10import Base64 from "./base64.js";
44f4c554 11import { supportsImageMetadata } from './util/browser.js';
ae510306 12
0e4808bf
JD
13export default class Display {
14 constructor(target) {
15 this._drawCtx = null;
16 this._c_forceCanvas = false;
ae510306 17
0e4808bf
JD
18 this._renderQ = []; // queue drawing actions for in-oder rendering
19 this._flushing = false;
ae510306 20
0e4808bf
JD
21 // the full frame buffer (logical canvas) size
22 this._fb_width = 0;
23 this._fb_height = 0;
ae510306 24
0e4808bf
JD
25 this._prevDrawStyle = "";
26 this._tile = null;
27 this._tile16x16 = null;
28 this._tile_x = 0;
29 this._tile_y = 0;
ae510306 30
0e4808bf 31 Log.Debug(">> Display.constructor");
3d7bb020 32
0e4808bf
JD
33 // The visible canvas
34 this._target = target;
ae510306 35
0e4808bf
JD
36 if (!this._target) {
37 throw new Error("Target must be set");
38 }
ae510306 39
0e4808bf
JD
40 if (typeof this._target === 'string') {
41 throw new Error('target must be a DOM element');
42 }
ae510306 43
0e4808bf
JD
44 if (!this._target.getContext) {
45 throw new Error("no getContext method");
46 }
2ba767a7 47
0e4808bf 48 this._targetCtx = this._target.getContext('2d');
adf345fd 49
0e4808bf
JD
50 // the visible canvas viewport (i.e. what actually gets seen)
51 this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
ae510306 52
0e4808bf
JD
53 // The hidden canvas, where we do the actual rendering
54 this._backbuffer = document.createElement('canvas');
55 this._drawCtx = this._backbuffer.getContext('2d');
84cd0e71 56
942a3127 57 this._damageBounds = { left: 0, top: 0,
0e4808bf
JD
58 right: this._backbuffer.width,
59 bottom: this._backbuffer.height };
ae510306 60
0e4808bf 61 Log.Debug("User Agent: " + navigator.userAgent);
ae510306 62
0e4808bf
JD
63 // Check canvas features
64 if (!('createImageData' in this._drawCtx)) {
65 throw new Error("Canvas does not support createImageData");
66 }
ff36b127 67
0e4808bf
JD
68 this._tile16x16 = this._drawCtx.createImageData(16, 16);
69 Log.Debug("<< Display.constructor");
70
71 // ===== PROPERTIES =====
72
73 this._scale = 1.0;
74 this._clipViewport = false;
0e4808bf
JD
75
76 // ===== EVENT HANDLERS =====
77
78 this.onflush = () => {}; // A flush request has finished
79 }
6d6f0db0 80
747b4623
PO
81 // ===== PROPERTIES =====
82
0e4808bf 83 get scale() { return this._scale; }
747b4623
PO
84 set scale(scale) {
85 this._rescale(scale);
0e4808bf 86 }
747b4623 87
0e4808bf 88 get clipViewport() { return this._clipViewport; }
0460e5fd
PO
89 set clipViewport(viewport) {
90 this._clipViewport = viewport;
747b4623 91 // May need to readjust the viewport dimensions
2b5f94fa 92 const vp = this._viewportLoc;
747b4623
PO
93 this.viewportChangeSize(vp.w, vp.h);
94 this.viewportChangePos(0, 0);
0e4808bf 95 }
747b4623
PO
96
97 get width() {
98 return this._fb_width;
0e4808bf
JD
99 }
100
747b4623
PO
101 get height() {
102 return this._fb_height;
0e4808bf 103 }
747b4623
PO
104
105 // ===== PUBLIC METHODS =====
106
0e4808bf 107 viewportChangePos(deltaX, deltaY) {
2b5f94fa 108 const vp = this._viewportLoc;
6d6f0db0
SR
109 deltaX = Math.floor(deltaX);
110 deltaY = Math.floor(deltaY);
111
0460e5fd 112 if (!this._clipViewport) {
6d6f0db0
SR
113 deltaX = -vp.w; // clamped later of out of bounds
114 deltaY = -vp.h;
115 }
d1800d09 116
2b5f94fa
JD
117 const vx2 = vp.x + vp.w - 1;
118 const vy2 = vp.y + vp.h - 1;
490d471c 119
6d6f0db0 120 // Position change
1e13775b 121
6d6f0db0
SR
122 if (deltaX < 0 && vp.x + deltaX < 0) {
123 deltaX = -vp.x;
124 }
125 if (vx2 + deltaX >= this._fb_width) {
126 deltaX -= vx2 + deltaX - this._fb_width + 1;
127 }
54e7cbdf 128
6d6f0db0
SR
129 if (vp.y + deltaY < 0) {
130 deltaY = -vp.y;
131 }
132 if (vy2 + deltaY >= this._fb_height) {
133 deltaY -= (vy2 + deltaY - this._fb_height + 1);
134 }
54e7cbdf 135
6d6f0db0
SR
136 if (deltaX === 0 && deltaY === 0) {
137 return;
138 }
139 Log.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
54e7cbdf 140
6d6f0db0
SR
141 vp.x += deltaX;
142 vp.y += deltaY;
54e7cbdf 143
6d6f0db0 144 this._damage(vp.x, vp.y, vp.w, vp.h);
54e7cbdf 145
6d6f0db0 146 this.flip();
0e4808bf 147 }
1e13775b 148
0e4808bf 149 viewportChangeSize(width, height) {
a8255821 150
0460e5fd 151 if (!this._clipViewport ||
6d6f0db0
SR
152 typeof(width) === "undefined" ||
153 typeof(height) === "undefined") {
84cd0e71 154
6d6f0db0
SR
155 Log.Debug("Setting viewport to full display region");
156 width = this._fb_width;
157 height = this._fb_height;
158 }
1e13775b 159
ab1ace38
PO
160 width = Math.floor(width);
161 height = Math.floor(height);
162
6d6f0db0
SR
163 if (width > this._fb_width) {
164 width = this._fb_width;
165 }
166 if (height > this._fb_height) {
167 height = this._fb_height;
168 }
636be753 169
2b5f94fa 170 const vp = this._viewportLoc;
6d6f0db0
SR
171 if (vp.w !== width || vp.h !== height) {
172 vp.w = width;
173 vp.h = height;
636be753 174
2b5f94fa 175 const canvas = this._target;
6d6f0db0
SR
176 canvas.width = width;
177 canvas.height = height;
636be753 178
6d6f0db0
SR
179 // The position might need to be updated if we've grown
180 this.viewportChangePos(0, 0);
adf345fd 181
6d6f0db0
SR
182 this._damage(vp.x, vp.y, vp.w, vp.h);
183 this.flip();
636be753 184
6d6f0db0
SR
185 // Update the visible size of the target canvas
186 this._rescale(this._scale);
187 }
0e4808bf 188 }
adf345fd 189
0e4808bf 190 absX(x) {
a136b4b0
SM
191 if (this._scale === 0) {
192 return 0;
193 }
6d6f0db0 194 return x / this._scale + this._viewportLoc.x;
0e4808bf 195 }
adf345fd 196
0e4808bf 197 absY(y) {
a136b4b0
SM
198 if (this._scale === 0) {
199 return 0;
200 }
6d6f0db0 201 return y / this._scale + this._viewportLoc.y;
0e4808bf 202 }
adf345fd 203
0e4808bf 204 resize(width, height) {
6d6f0db0 205 this._prevDrawStyle = "";
636be753 206
6d6f0db0
SR
207 this._fb_width = width;
208 this._fb_height = height;
1e13775b 209
2b5f94fa 210 const canvas = this._backbuffer;
6d6f0db0 211 if (canvas.width !== width || canvas.height !== height) {
1e13775b 212
6d6f0db0 213 // We have to save the canvas data since changing the size will clear it
2b5f94fa 214 let saveImg = null;
6d6f0db0
SR
215 if (canvas.width > 0 && canvas.height > 0) {
216 saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height);
217 }
1e13775b 218
6d6f0db0
SR
219 if (canvas.width !== width) {
220 canvas.width = width;
221 }
222 if (canvas.height !== height) {
223 canvas.height = height;
224 }
1e13775b 225
6d6f0db0
SR
226 if (saveImg) {
227 this._drawCtx.putImageData(saveImg, 0, 0);
228 }
229 }
2ba767a7 230
6d6f0db0
SR
231 // Readjust the viewport as it may be incorrectly sized
232 // and positioned
2b5f94fa 233 const vp = this._viewportLoc;
6d6f0db0
SR
234 this.viewportChangeSize(vp.w, vp.h);
235 this.viewportChangePos(0, 0);
0e4808bf 236 }
6d6f0db0
SR
237
238 // Track what parts of the visible canvas that need updating
0e4808bf 239 _damage(x, y, w, h) {
6d6f0db0
SR
240 if (x < this._damageBounds.left) {
241 this._damageBounds.left = x;
242 }
243 if (y < this._damageBounds.top) {
244 this._damageBounds.top = y;
245 }
246 if ((x + w) > this._damageBounds.right) {
247 this._damageBounds.right = x + w;
248 }
249 if ((y + h) > this._damageBounds.bottom) {
250 this._damageBounds.bottom = y + h;
251 }
0e4808bf 252 }
2ba767a7 253
6d6f0db0
SR
254 // Update the visible canvas with the contents of the
255 // rendering canvas
0e4808bf 256 flip(from_queue) {
6d6f0db0
SR
257 if (this._renderQ.length !== 0 && !from_queue) {
258 this._renderQ_push({
259 'type': 'flip'
260 });
261 } else {
2b5f94fa
JD
262 let x = this._damageBounds.left;
263 let y = this._damageBounds.top;
264 let w = this._damageBounds.right - x;
265 let h = this._damageBounds.bottom - y;
2ba767a7 266
2b5f94fa
JD
267 let vx = x - this._viewportLoc.x;
268 let vy = y - this._viewportLoc.y;
1e13775b 269
6d6f0db0
SR
270 if (vx < 0) {
271 w += vx;
272 x -= vx;
273 vx = 0;
84cd0e71 274 }
6d6f0db0
SR
275 if (vy < 0) {
276 h += vy;
277 y -= vy;
278 vy = 0;
84cd0e71 279 }
6d6f0db0
SR
280
281 if ((vx + w) > this._viewportLoc.w) {
282 w = this._viewportLoc.w - vx;
84cd0e71 283 }
6d6f0db0
SR
284 if ((vy + h) > this._viewportLoc.h) {
285 h = this._viewportLoc.h - vy;
84cd0e71 286 }
84cd0e71 287
6d6f0db0
SR
288 if ((w > 0) && (h > 0)) {
289 // FIXME: We may need to disable image smoothing here
290 // as well (see copyImage()), but we haven't
291 // noticed any problem yet.
292 this._targetCtx.drawImage(this._backbuffer,
293 x, y, w, h,
294 vx, vy, w, h);
295 }
84cd0e71 296
6d6f0db0
SR
297 this._damageBounds.left = this._damageBounds.top = 65535;
298 this._damageBounds.right = this._damageBounds.bottom = 0;
299 }
0e4808bf 300 }
84cd0e71 301
0e4808bf 302 pending() {
6d6f0db0 303 return this._renderQ.length > 0;
0e4808bf 304 }
84cd0e71 305
0e4808bf 306 flush() {
6d6f0db0 307 if (this._renderQ.length === 0) {
747b4623 308 this.onflush();
6d6f0db0
SR
309 } else {
310 this._flushing = true;
311 }
0e4808bf 312 }
2ba767a7 313
0e4808bf 314 fillRect(x, y, width, height, color, from_queue) {
6d6f0db0
SR
315 if (this._renderQ.length !== 0 && !from_queue) {
316 this._renderQ_push({
317 'type': 'fill',
318 'x': x,
319 'y': y,
320 'width': width,
321 'height': height,
322 'color': color
323 });
324 } else {
325 this._setFillColor(color);
326 this._drawCtx.fillRect(x, y, width, height);
327 this._damage(x, y, width, height);
328 }
0e4808bf 329 }
1e13775b 330
0e4808bf 331 copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) {
6d6f0db0
SR
332 if (this._renderQ.length !== 0 && !from_queue) {
333 this._renderQ_push({
334 'type': 'copy',
335 'old_x': old_x,
336 'old_y': old_y,
337 'x': new_x,
338 'y': new_y,
339 'width': w,
340 'height': h,
341 });
342 } else {
343 // Due to this bug among others [1] we need to disable the image-smoothing to
344 // avoid getting a blur effect when copying data.
345 //
346 // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719
347 //
348 // We need to set these every time since all properties are reset
349 // when the the size is changed
350 this._drawCtx.mozImageSmoothingEnabled = false;
351 this._drawCtx.webkitImageSmoothingEnabled = false;
352 this._drawCtx.msImageSmoothingEnabled = false;
353 this._drawCtx.imageSmoothingEnabled = false;
354
355 this._drawCtx.drawImage(this._backbuffer,
356 old_x, old_y, w, h,
357 new_x, new_y, w, h);
358 this._damage(new_x, new_y, w, h);
359 }
0e4808bf 360 }
6d6f0db0 361
4babdf33 362 imageRect(x, y, width, height, mime, arr) {
2b5f94fa 363 const img = new Image();
6d6f0db0
SR
364 img.src = "data: " + mime + ";base64," + Base64.encode(arr);
365 this._renderQ_push({
366 'type': 'img',
367 'img': img,
368 'x': x,
4babdf33
PO
369 'y': y,
370 'width': width,
371 'height': height
6d6f0db0 372 });
0e4808bf 373 }
6d6f0db0
SR
374
375 // start updating a tile
0e4808bf 376 startTile(x, y, width, height, color) {
6d6f0db0
SR
377 this._tile_x = x;
378 this._tile_y = y;
379 if (width === 16 && height === 16) {
380 this._tile = this._tile16x16;
381 } else {
382 this._tile = this._drawCtx.createImageData(width, height);
383 }
d9ca5e5b 384
2b5f94fa
JD
385 const red = color[2];
386 const green = color[1];
387 const blue = color[0];
6d6f0db0 388
2b5f94fa
JD
389 const data = this._tile.data;
390 for (let i = 0; i < width * height * 4; i += 4) {
134ec26e
PO
391 data[i] = red;
392 data[i + 1] = green;
393 data[i + 2] = blue;
394 data[i + 3] = 255;
6d6f0db0 395 }
0e4808bf 396 }
6d6f0db0
SR
397
398 // update sub-rectangle of the current tile
0e4808bf 399 subTile(x, y, w, h, color) {
2b5f94fa
JD
400 const red = color[2];
401 const green = color[1];
402 const blue = color[0];
403 const xend = x + w;
404 const yend = y + h;
405
406 const data = this._tile.data;
407 const width = this._tile.width;
408 for (let j = y; j < yend; j++) {
409 for (let i = x; i < xend; i++) {
410 const p = (i + (j * width)) * 4;
134ec26e
PO
411 data[p] = red;
412 data[p + 1] = green;
413 data[p + 2] = blue;
414 data[p + 3] = 255;
f00193e0 415 }
6d6f0db0 416 }
0e4808bf 417 }
6d6f0db0
SR
418
419 // draw the current tile to the screen
0e4808bf 420 finishTile() {
134ec26e
PO
421 this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
422 this._damage(this._tile_x, this._tile_y,
423 this._tile.width, this._tile.height);
0e4808bf 424 }
6d6f0db0 425
0e4808bf 426 blitImage(x, y, width, height, arr, offset, from_queue) {
6d6f0db0
SR
427 if (this._renderQ.length !== 0 && !from_queue) {
428 // NB(directxman12): it's technically more performant here to use preallocated arrays,
429 // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
430 // this probably isn't getting called *nearly* as much
2b5f94fa 431 const new_arr = new Uint8Array(width * height * 4);
6d6f0db0 432 new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
1578fa68 433 this._renderQ_push({
6d6f0db0
SR
434 'type': 'blit',
435 'data': new_arr,
1578fa68 436 'x': x,
6d6f0db0
SR
437 'y': y,
438 'width': width,
439 'height': height,
1578fa68 440 });
6d6f0db0 441 } else {
26586b9d 442 this._bgrxImageData(x, y, width, height, arr, offset);
6d6f0db0 443 }
0e4808bf 444 }
6d6f0db0 445
6786fd87 446 blitRgbImage(x, y, width, height, arr, offset, from_queue) {
6d6f0db0
SR
447 if (this._renderQ.length !== 0 && !from_queue) {
448 // NB(directxman12): it's technically more performant here to use preallocated arrays,
449 // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
450 // this probably isn't getting called *nearly* as much
2b5f94fa 451 const new_arr = new Uint8Array(width * height * 3);
6d6f0db0
SR
452 new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
453 this._renderQ_push({
454 'type': 'blitRgb',
455 'data': new_arr,
456 'x': x,
457 'y': y,
458 'width': width,
459 'height': height,
460 });
6d6f0db0 461 } else {
26586b9d 462 this._rgbImageData(x, y, width, height, arr, offset);
6d6f0db0 463 }
0e4808bf 464 }
6d6f0db0 465
0e4808bf 466 blitRgbxImage(x, y, width, height, arr, offset, from_queue) {
6d6f0db0
SR
467 if (this._renderQ.length !== 0 && !from_queue) {
468 // NB(directxman12): it's technically more performant here to use preallocated arrays,
469 // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
470 // this probably isn't getting called *nearly* as much
2b5f94fa 471 const new_arr = new Uint8Array(width * height * 4);
6d6f0db0
SR
472 new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
473 this._renderQ_push({
474 'type': 'blitRgbx',
475 'data': new_arr,
476 'x': x,
477 'y': y,
478 'width': width,
479 'height': height,
480 });
481 } else {
482 this._rgbxImageData(x, y, width, height, arr, offset);
483 }
0e4808bf 484 }
1e13775b 485
0e4808bf 486 drawImage(img, x, y) {
6d6f0db0
SR
487 this._drawCtx.drawImage(img, x, y);
488 this._damage(x, y, img.width, img.height);
0e4808bf 489 }
d1800d09 490
0e4808bf 491 autoscale(containerWidth, containerHeight) {
a136b4b0 492 let scaleRatio;
6e7e6f9c 493
a136b4b0
SM
494 if (containerWidth === 0 || containerHeight === 0) {
495 scaleRatio = 0;
6d6f0db0 496
6d6f0db0 497 } else {
a136b4b0
SM
498
499 const vp = this._viewportLoc;
500 const targetAspectRatio = containerWidth / containerHeight;
501 const fbAspectRatio = vp.w / vp.h;
502
503 if (fbAspectRatio >= targetAspectRatio) {
504 scaleRatio = containerWidth / vp.w;
505 } else {
506 scaleRatio = containerHeight / vp.h;
507 }
6d6f0db0 508 }
d3796c14 509
6d6f0db0 510 this._rescale(scaleRatio);
0e4808bf 511 }
6d6f0db0 512
747b4623
PO
513 // ===== PRIVATE METHODS =====
514
0e4808bf 515 _rescale(factor) {
6d6f0db0 516 this._scale = factor;
2b5f94fa 517 const vp = this._viewportLoc;
6d6f0db0
SR
518
519 // NB(directxman12): If you set the width directly, or set the
520 // style width to a number, the canvas is cleared.
521 // However, if you set the style width to a string
522 // ('NNNpx'), the canvas is scaled without clearing.
ab1ace38
PO
523 const width = factor * vp.w + 'px';
524 const height = factor * vp.h + 'px';
6d6f0db0
SR
525
526 if ((this._target.style.width !== width) ||
527 (this._target.style.height !== height)) {
528 this._target.style.width = width;
529 this._target.style.height = height;
530 }
0e4808bf 531 }
9a23006e 532
0e4808bf 533 _setFillColor(color) {
2b5f94fa 534 const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
6d6f0db0
SR
535 if (newStyle !== this._prevDrawStyle) {
536 this._drawCtx.fillStyle = newStyle;
537 this._prevDrawStyle = newStyle;
538 }
0e4808bf 539 }
6d6f0db0 540
0e4808bf 541 _rgbImageData(x, y, width, height, arr, offset) {
2b5f94fa
JD
542 const img = this._drawCtx.createImageData(width, height);
543 const data = img.data;
544 for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
6d6f0db0
SR
545 data[i] = arr[j];
546 data[i + 1] = arr[j + 1];
547 data[i + 2] = arr[j + 2];
548 data[i + 3] = 255; // Alpha
549 }
550 this._drawCtx.putImageData(img, x, y);
551 this._damage(x, y, img.width, img.height);
0e4808bf 552 }
6d6f0db0 553
0e4808bf 554 _bgrxImageData(x, y, width, height, arr, offset) {
2b5f94fa
JD
555 const img = this._drawCtx.createImageData(width, height);
556 const data = img.data;
557 for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
6d6f0db0
SR
558 data[i] = arr[j + 2];
559 data[i + 1] = arr[j + 1];
560 data[i + 2] = arr[j];
561 data[i + 3] = 255; // Alpha
562 }
563 this._drawCtx.putImageData(img, x, y);
564 this._damage(x, y, img.width, img.height);
0e4808bf 565 }
6d6f0db0 566
0e4808bf 567 _rgbxImageData(x, y, width, height, arr, offset) {
6d6f0db0 568 // NB(directxman12): arr must be an Type Array view
2b5f94fa 569 let img;
44f4c554 570 if (supportsImageMetadata) {
6d6f0db0
SR
571 img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
572 } else {
573 img = this._drawCtx.createImageData(width, height);
574 img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4));
575 }
576 this._drawCtx.putImageData(img, x, y);
577 this._damage(x, y, img.width, img.height);
0e4808bf 578 }
6d6f0db0 579
0e4808bf 580 _renderQ_push(action) {
6d6f0db0
SR
581 this._renderQ.push(action);
582 if (this._renderQ.length === 1) {
583 // If this can be rendered immediately it will be, otherwise
584 // the scanner will wait for the relevant event
585 this._scan_renderQ();
586 }
0e4808bf 587 }
6d6f0db0 588
0e4808bf 589 _resume_renderQ() {
6d6f0db0
SR
590 // "this" is the object that is ready, not the
591 // display object
592 this.removeEventListener('load', this._noVNC_display._resume_renderQ);
593 this._noVNC_display._scan_renderQ();
0e4808bf 594 }
6d6f0db0 595
0e4808bf 596 _scan_renderQ() {
2b5f94fa 597 let ready = true;
6d6f0db0 598 while (ready && this._renderQ.length > 0) {
2b5f94fa 599 const a = this._renderQ[0];
6d6f0db0
SR
600 switch (a.type) {
601 case 'flip':
602 this.flip(true);
603 break;
604 case 'copy':
605 this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
606 break;
607 case 'fill':
608 this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
609 break;
610 case 'blit':
611 this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
612 break;
613 case 'blitRgb':
614 this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
615 break;
616 case 'blitRgbx':
617 this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
618 break;
619 case 'img':
620 if (a.img.complete) {
4babdf33
PO
621 if (a.img.width !== a.width || a.img.height !== a.height) {
622 Log.Error("Decoded image has incorrect dimensions. Got " +
623 a.img.width + "x" + a.img.height + ". Expected " +
624 a.width + "x" + a.height + ".");
625 return;
626 }
6d6f0db0
SR
627 this.drawImage(a.img, a.x, a.y);
628 } else {
629 a.img._noVNC_display = this;
630 a.img.addEventListener('load', this._resume_renderQ);
631 // We need to wait for this image to 'load'
632 // to keep things in-order
633 ready = false;
634 }
635 break;
1e13775b
SR
636 }
637
6d6f0db0
SR
638 if (ready) {
639 this._renderQ.shift();
1578fa68 640 }
6d6f0db0 641 }
1e13775b 642
6d6f0db0
SR
643 if (this._renderQ.length === 0 && this._flushing) {
644 this._flushing = false;
747b4623 645 this.onflush();
6d6f0db0 646 }
0e4808bf
JD
647 }
648}