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