diff --git a/guacamole-common-js/src/main/webapp/modules/Client.js b/guacamole-common-js/src/main/webapp/modules/Client.js index 866714162..83d89abaa 100644 --- a/guacamole-common-js/src/main/webapp/modules/Client.js +++ b/guacamole-common-js/src/main/webapp/modules/Client.js @@ -228,11 +228,14 @@ Guacamole.Client = function(tunnel) { currentState = state.currentState; currentTimestamp = state.currentTimestamp; + // Cancel any pending display operations/frames + display.cancel(); + // Dispose of all layers for (key in layers) { index = parseInt(key); if (index > 0) - display.dispose(layers[key]); + layers[key].dispose(); } layers = {}; diff --git a/guacamole-common-js/src/main/webapp/modules/Display.js b/guacamole-common-js/src/main/webapp/modules/Display.js index 341480777..8baa6b7d7 100644 --- a/guacamole-common-js/src/main/webapp/modules/Display.js +++ b/guacamole-common-js/src/main/webapp/modules/Display.js @@ -201,6 +201,22 @@ Guacamole.Display = function() { */ function Frame(callback, tasks) { + /** + * Cancels rendering of this frame and all associated tasks. The + * callback provided at construction time, if any, is not invoked. + */ + this.cancel = function cancel() { + + callback = null; + + tasks.forEach(function cancelTask(task) { + task.cancel(); + }); + + tasks = []; + + }; + /** * Returns whether this frame is ready to be rendered. This function * returns true if and only if ALL underlying tasks are unblocked. @@ -271,6 +287,16 @@ Guacamole.Display = function() { */ this.blocked = blocked; + /** + * Cancels this task such that it will not run. The task handler + * provided at construction time, if any, is not invoked. Calling + * execute() after calling this function has no effect. + */ + this.cancel = function cancel() { + task.blocked = false; + taskHandler = null; + }; + /** * Unblocks this Task, allowing it to run. */ @@ -417,6 +443,27 @@ Guacamole.Display = function() { }; + /** + * Cancels rendering of all pending frames and associated rendering + * operations. The callbacks provided to outstanding past calls to flush(), + * if any, are not invoked. + */ + this.cancel = function cancel() { + + frames.forEach(function cancelFrame(frame) { + frame.cancel(); + }); + + frames = []; + + tasks.forEach(function cancelTask(task) { + task.cancel(); + }); + + tasks = []; + + }; + /** * Sets the hotspot and image of the mouse cursor displayed within the * Guacamole display. diff --git a/guacamole-common-js/src/main/webapp/modules/SessionRecording.js b/guacamole-common-js/src/main/webapp/modules/SessionRecording.js index 8512c58dd..984d8772b 100644 --- a/guacamole-common-js/src/main/webapp/modules/SessionRecording.js +++ b/guacamole-common-js/src/main/webapp/modules/SessionRecording.js @@ -577,11 +577,12 @@ Guacamole.SessionRecording = function SessionRecording(source) { * @param {function} callback * The callback to invoke once the seek operation has completed. * - * @param {number} [delay=0] - * The number of milliseconds that the seek operation should be - * scheduled to take. + * @param {number} [nextRealTimestamp] + * The timestamp of the point in time that the given frame should be + * displayed, as would be returned by new Date().getTime(). If omitted, + * the frame will be displayed as soon as possible. */ - var seekToFrame = function seekToFrame(index, callback, delay) { + var seekToFrame = function seekToFrame(index, callback, nextRealTimestamp) { // Abort any in-progress seek abortSeek(); @@ -591,29 +592,7 @@ Guacamole.SessionRecording = function SessionRecording(source) { aborted : false }; - var startIndex; - - // Back up until startIndex represents current state - for (startIndex = index; startIndex >= 0; startIndex--) { - - var frame = frames[startIndex]; - - // If we've reached the current frame, startIndex represents - // current state by definition - if (startIndex === currentFrame) - break; - - // If frame has associated absolute state, make that frame the - // current state - if (frame.clientState) { - frame.clientState.text().then(function textReady(text) { - playbackClient.importState(JSON.parse(text)); - currentFrame = index; - }); - break; - } - - } + var startIndex = index; // Replay any applicable incremental frames var continueReplay = function continueReplay() { @@ -629,7 +608,7 @@ Guacamole.SessionRecording = function SessionRecording(source) { return; // If frames remain, replay the next frame - if (!thisSeek.aborted && currentFrame < index) + if (currentFrame < index) replayFrame(currentFrame + 1, continueReplay); // Otherwise, the seek operation is completed @@ -640,11 +619,39 @@ Guacamole.SessionRecording = function SessionRecording(source) { // Continue replay after requested delay has elapsed, or // immediately if no delay was requested - if (delay) - window.setTimeout(continueReplay, delay); - else - continueReplay(); + var continueAfterRequiredDelay = function continueAfterRequiredDelay() { + var delay = nextRealTimestamp ? Math.max(nextRealTimestamp - new Date().getTime(), 0) : 0; + if (delay) + window.setTimeout(continueReplay, delay); + else + continueReplay(); + }; + // Back up until startIndex represents current state + for (; startIndex >= 0; startIndex--) { + + var frame = frames[startIndex]; + + // If we've reached the current frame, startIndex represents + // current state by definition + if (startIndex === currentFrame) + break; + + // If frame has associated absolute state, make that frame the + // current state + if (frame.clientState) { + frame.clientState.text().then(function textReady(text) { + playbackClient.importState(JSON.parse(text)); + currentFrame = startIndex; + continueAfterRequiredDelay(); + }); + return; + } + + } + + continueAfterRequiredDelay(); + }; /** @@ -679,14 +686,10 @@ Guacamole.SessionRecording = function SessionRecording(source) { // frame begins var nextRealTimestamp = next.timestamp - startVideoTimestamp + startRealTimestamp; - // Calculate the relative delay between the current time and - // the next frame start - var delay = Math.max(nextRealTimestamp - new Date().getTime(), 0); - // Advance to next frame after enough time has elapsed seekToFrame(currentFrame + 1, function frameDelayElapsed() { continuePlayback(); - }, delay); + }, nextRealTimestamp); }