diff --git a/guacamole-common-js/src/main/webapp/modules/Display.js b/guacamole-common-js/src/main/webapp/modules/Display.js index 8baa6b7d7..be01e8353 100644 --- a/guacamole-common-js/src/main/webapp/modules/Display.js +++ b/guacamole-common-js/src/main/webapp/modules/Display.js @@ -163,10 +163,28 @@ Guacamole.Display = function() { var frames = []; /** - * Flushes all pending frames. + * The ID of the animation frame request returned by the last call to + * requestAnimationFrame(). This value will only be set if the browser + * supports requestAnimationFrame(), if a frame render is currently + * pending, and if the current browser tab is currently focused (likely to + * handle requests for animation frames). In all other cases, this will be + * null. + * + * @private + * @type {number} + */ + var inProgressFrame = null; + + /** + * Flushes all pending frames synchronously. This function will block until + * all pending frames have rendered. If a frame is currently blocked by an + * asynchronous operation like an image load, this function will return + * after reaching that operation and the flush operation will + * automamtically resume after that operation completes. + * * @private */ - function __flush_frames() { + var syncFlush = function syncFlush() { var rendered_frames = 0; @@ -185,6 +203,71 @@ Guacamole.Display = function() { // Remove rendered frames from array frames.splice(0, rendered_frames); + }; + + /** + * Flushes all pending frames asynchronously. This function returns + * immediately, relying on requestAnimationFrame() to dictate when each + * frame should be flushed. + * + * @private + */ + var asyncFlush = function asyncFlush() { + + var continueFlush = function continueFlush() { + + // We're no longer waiting to render a frame + inProgressFrame = null; + + // Nothing to do if there are no frames remaining + if (!frames.length) + return; + + // Flush the next frame only if it is ready (not awaiting + // completion of some asynchronous operation like an image load) + if (frames[0].isReady()) + frames.shift().flush(); + + // Request yet another animation frame if frames remain to be + // flushed + if (frames.length) + inProgressFrame = window.requestAnimationFrame(continueFlush); + + }; + + // Begin flushing frames if not already waiting to render a frame + if (!inProgressFrame) + inProgressFrame = window.requestAnimationFrame(continueFlush); + + }; + + // Switch from asynchronous frame handling to synchronous frame handling if + // requestAnimationFrame() is unlikely to be usable (browsers may not + // invoke the animation frame callback if the relevant tab is not focused) + window.addEventListener('blur', function switchToSyncFlush() { + if (inProgressFrame && !document.hasFocus()) { + + // Cancel pending asynchronous processing of frame ... + window.cancelAnimationFrame(inProgressFrame); + inProgressFrame = null; + + // ... and instead process it synchronously + syncFlush(); + + } + }, true); + + /** + * Flushes all pending frames. + * @private + */ + function __flush_frames() { + + if (window.requestAnimationFrame && document.hasFocus()) + asyncFlush(); + else + syncFlush(); + } /**