GUACAMOLE-884: Leverage createImageBitmap() for reading image data where supported.

Some browsers suffer from a memory leak when reading image data
repeatedly using the Image object. Reading from Blobs does not exhibit
the same behavior. While reading from Blobs has previously been seen to
perform poorly compared to data URIs, this was observed when reading
using createObjectURL(). The createImageBitmap() function appears to
perform identically to reading data URIs using Image.
diff --git a/guacamole-common-js/src/main/webapp/modules/Client.js b/guacamole-common-js/src/main/webapp/modules/Client.js
index 07baf77..ed8085d 100644
--- a/guacamole-common-js/src/main/webapp/modules/Client.js
+++ b/guacamole-common-js/src/main/webapp/modules/Client.js
@@ -1190,13 +1190,10 @@
 
             // Create stream
             var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
-            var reader = new Guacamole.DataURIReader(stream, mimetype);
 
-            // Draw image when stream is complete
-            reader.onend = function drawImageBlob() {
-                display.setChannelMask(layer, channelMask);
-                display.draw(layer, x, y, reader.getURI());
-            };
+            // Draw received contents once decoded
+            display.setChannelMask(layer, channelMask);
+            display.drawStream(layer, x, y, stream, mimetype);
 
         },
 
diff --git a/guacamole-common-js/src/main/webapp/modules/Display.js b/guacamole-common-js/src/main/webapp/modules/Display.js
index be5bf6c..0bc569f 100644
--- a/guacamole-common-js/src/main/webapp/modules/Display.js
+++ b/guacamole-common-js/src/main/webapp/modules/Display.js
@@ -543,26 +543,94 @@
      */
     this.drawBlob = function(layer, x, y, blob) {
 
-        // Create URL for blob
-        var url = URL.createObjectURL(blob);
+        var task;
 
-        // Draw and free blob URL when ready
-        var task = scheduleTask(function __display_drawBlob() {
+        // Prefer createImageBitmap() over blob URLs if available
+        if (window.createImageBitmap) {
 
-            // Draw the image only if it loaded without errors
-            if (image.width && image.height)
-                layer.drawImage(x, y, image);
+            var bitmap;
 
-            // Blob URL no longer needed
-            URL.revokeObjectURL(url);
+            // Draw image once loaded
+            task = scheduleTask(function drawImageBitmap() {
+                layer.drawImage(x, y, bitmap);
+            }, true);
 
-        }, true);
+            // Load image from provided blob
+            window.createImageBitmap(blob).then(function bitmapLoaded(decoded) {
+                bitmap = decoded;
+                task.unblock();
+            });
 
-        // Load image from URL
-        var image = new Image();
-        image.onload = task.unblock;
-        image.onerror = task.unblock;
-        image.src = url;
+        }
+
+        // Use blob URLs and the Image object if createImageBitmap() is
+        // unavailable
+        else {
+
+            // Create URL for blob
+            var url = URL.createObjectURL(blob);
+
+            // Draw and free blob URL when ready
+            task = scheduleTask(function __display_drawBlob() {
+
+                // Draw the image only if it loaded without errors
+                if (image.width && image.height)
+                    layer.drawImage(x, y, image);
+
+                // Blob URL no longer needed
+                URL.revokeObjectURL(url);
+
+            }, true);
+
+            // Load image from URL
+            var image = new Image();
+            image.onload = task.unblock;
+            image.onerror = task.unblock;
+            image.src = url;
+
+        }
+
+    };
+
+    /**
+     * Draws the image within the given stream at the given coordinates. The
+     * image will be loaded automatically, and this and any future operations
+     * will wait for the image to finish loading.
+     *
+     * @param {Guacamole.Layer} layer
+     *     The layer to draw upon.
+     *
+     * @param {Number} x
+     *     The destination X coordinate.
+     *
+     * @param {Number} y
+     *     The destination Y coordinate.
+     *
+     * @param {Guacamole.InputStream} stream
+     *     The stream along which image data will be received.
+     *
+     * @param {String} mimetype
+     *     The mimetype of the image within the stream.
+     */
+    this.drawStream = function drawStream(layer, x, y, stream, mimetype) {
+
+        // If createImageBitmap() is available, load the image as a blob so
+        // that function can be used
+        if (window.createImageBitmap) {
+            var reader = new Guacamole.BlobReader(stream, mimetype);
+            reader.onend = function drawImageBlob() {
+                guac_display.drawBlob(layer, x, y, reader.getBlob());
+            };
+        }
+
+        // Lacking createImageBitmap(), fall back to data URIs and the Image
+        // object
+        else {
+            var reader = new Guacamole.DataURIReader(stream, mimetype);
+            reader.onend = function drawImageDataURI() {
+                guac_display.draw(layer, x, y, reader.getURI());
+            };
+        }
 
     };