Make vnc_playback.html functional once more

This commit makes vnc_playback.html functional once more, and completely
refactors tests/playback.js to make it usable in other scenarios.

In order for vnc_playback.js to properly load playback files now, they
must `export` their variables.
This commit is contained in:
Solly Ross 2017-03-01 21:10:09 -05:00
parent 7569cdc21e
commit d6c17390f0
5 changed files with 330 additions and 239 deletions

174
tests/playback-ui.js Normal file
View File

@ -0,0 +1,174 @@
import * as WebUtil from '../app/webutil.js';
import RecordingPlayer from './playback.js';
var frames = null;
var encoding = null;
function message(str) {
console.log(str);
var cell = document.getElementById('messages');
cell.textContent += str + "\n";
cell.scrollTop = cell.scrollHeight;
}
function loadFile() {
const fname = WebUtil.getQueryVar('data', null);
if (!fname) {
return Promise.reject("Must specify data=FOO in query string.");
}
message("Loading " + fname);
return import(`../recordings/${fname}#nocache`);
}
function enableUI(recording) {
var iterations = WebUtil.getQueryVar('iterations', 3);
document.getElementById('iterations').value = iterations;
var mode = WebUtil.getQueryVar('mode', 3);
if (mode === 'realtime') {
document.getElementById('mode2').checked = true;
} else {
document.getElementById('mode1').checked = true;
}
message("VNC_frame_data.length: " + recording.VNC_frame_data.length);
const startButton = document.getElementById('startButton');
startButton.disabled = false
startButton.addEventListener('click', start);
frames = recording.VNC_frame_data;
encoding = recording.VNC_frame_encoding;
}
const notification = function (rfb, mesg, level, options) {
document.getElementById('VNC_status').textContent = mesg;
}
function IterationPlayer (iterations, frames, encoding) {
this._iterations = iterations;
this._iteration = undefined;
this._player = undefined;
this._start_time = undefined;
this._frames = frames;
this._encoding = encoding;
this._state = 'running';
this.onfinish = function() {};
this.oniterationfinish = function() {};
this.rfbdisconnected = function() {};
this.rfbnotification = function() {};
}
IterationPlayer.prototype = {
start: function (mode) {
this._iteration = 0;
this._start_time = (new Date()).getTime();
this._realtime = mode.startsWith('realtime');
this._trafficMgmt = !mode.endsWith('-no-mgmt');
this._nextIteration();
},
_nextIteration: function () {
const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this), this._notification.bind(this));
player.onfinish = this._iterationFinish.bind(this);
if (this._state !== 'running') { return; }
this._iteration++;
if (this._iteration > this._iterations) {
this._finish();
return;
}
player.run(this._realtime, this._trafficMgmt);
},
_finish: function () {
const endTime = (new Date()).getTime();
const totalDuration = endTime - this._start_time;
const evt = new Event('finish');
evt.duration = totalDuration;
evt.iterations = this._iterations;
this.onfinish(evt);
},
_iterationFinish: function (duration) {
const evt = new Event('iterationfinish');
evt.duration = duration;
evt.number = this._iteration;
this.oniterationfinish(evt);
this._nextIteration();
},
_disconnected: function (rfb, reason, frame) {
if (reason) {
this._state = 'failed';
}
var evt = new Event('rfbdisconnected');
evt.reason = reason;
evt.frame = frame;
this.onrfbdisconnected(evt);
},
_notification: function (rfb, msg, level, options) {
var evt = new Event('rfbnotification');
evt.message = msg;
evt.level = level;
evt.options = options;
this.onrfbnotification(evt);
},
};
function start() {
document.getElementById('startButton').value = "Running";
document.getElementById('startButton').disabled = true;
const iterations = document.getElementById('iterations').value;
var mode;
if (document.getElementById('mode1').checked) {
message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`);
mode = 'perftest';
} else {
message(`Starting realtime playback [${iterations} iteration(s)]`);
mode = 'realtime';
}
const player = new IterationPlayer(iterations, frames, encoding);
player.oniterationfinish = function (evt) {
message(`Iteration ${evt.number} took ${evt.duration}ms`);
};
player.onrfbdisconnected = function (evt) {
if (evt.reason) {
message(`noVNC sent disconnected during iteration ${evt.iteration} frame ${evt.frame}`);
}
};
player.onrfbnotification = function (evt) {
document.getElementById('VNC_status').textContent = evt.message;
};
player.onfinish = function (evt) {
const iterTime = parseInt(evt.duration / evt.iterations, 10);
message(`${evt.iterations} iterations took ${evt.duration}ms (average ${iterTime}ms / iteration)`);
document.getElementById('startButton').disabled = false;
document.getElementById('startButton').value = "Start";
};
player.start(mode);
}
loadFile().then(enableUI).catch(message);

View File

@ -4,29 +4,17 @@
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
*/ */
"use strict"; import RFB from '../core/rfb.js';
/*jslint browser: true, white: false */ import * as Log from '../core/util/logging.js';
/*global Util, VNC_frame_data, finish */ import Base64 from '../core/base64.js';
var rfb, mode, test_state, frame_idx, frame_length,
iteration, iterations, istart_time, encoding,
// Pre-declarations for jslint
send_array, next_iteration, end_iteration, queue_next_packet,
do_packet, enable_test_mode;
// Override send_array
send_array = function (arr) {
// Stub out send_array
};
// Immediate polyfill // Immediate polyfill
if (window.setImmediate === undefined) { if (setImmediate === undefined) {
var _immediateIdCounter = 1; var _immediateIdCounter = 1;
var _immediateFuncs = {}; var _immediateFuncs = {};
window.setImmediate = function (func) { var setImmediate = function (func) {
var index = Util._immediateIdCounter++; var index = _immediateIdCounter++;
_immediateFuncs[index] = func; _immediateFuncs[index] = func;
window.postMessage("noVNC immediate trigger:" + index, "*"); window.postMessage("noVNC immediate trigger:" + index, "*");
return index; return index;
@ -56,12 +44,67 @@ if (window.setImmediate === undefined) {
window.addEventListener("message", _onMessage); window.addEventListener("message", _onMessage);
} }
enable_test_mode = function () { export default function RecordingPlayer (frames, encoding, disconnected, notification) {
rfb._sock.send = send_array; this._frames = frames;
rfb._sock.close = function () {}; this._encoding = encoding;
rfb._sock.flush = function () {};
rfb._checkEvents = function () {}; this._disconnected = disconnected;
rfb.connect = function (host, port, password, path) { this._notification = notification;
if (this._encoding === undefined) {
let frame = this._frames[0];
let start = frame.indexOf('{', 1) + 1;
if (frame.slice(start).startsWith('UkZC')) {
this._encoding = 'base64';
} else {
this._encoding = 'binary';
}
}
this._rfb = undefined;
this._frame_length = this._frames.length;
this._frame_index = 0;
this._start_time = undefined;
this._realtime = true;
this._trafficManagement = true;
this._running = false;
this.onfinish = function () {};
}
RecordingPlayer.prototype = {
run: function (realtime, trafficManagement) {
// initialize a new RFB
this._rfb = new RFB({'target': document.getElementById('VNC_canvas'),
'view_only': true,
'onDisconnected': this._handleDisconnect.bind(this),
'onNotification': this._notification});
this._enablePlaybackMode();
// reset the frame index and timer
this._frame_index = 0;
this._start_time = (new Date()).getTime();
this._realtime = realtime;
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
this._running = true;
// launch the tests
this._rfb.connect('test', 0, 'bogus');
this._queueNextPacket();
},
// _enablePlaybackMode mocks out things not required for running playback
_enablePlaybackMode: function () {
this._rfb._sock.send = function (arr) {};
this._rfb._sock.close = function () {};
this._rfb._sock.flush = function () {};
this._rfb._checkEvents = function () {};
this._rfb.connect = function (host, port, password, path) {
this._rfb_host = host; this._rfb_host = host;
this._rfb_port = port; this._rfb_port = port;
this._rfb_password = (password !== undefined) ? password : ""; this._rfb_password = (password !== undefined) ? password : "";
@ -70,129 +113,91 @@ enable_test_mode = function () {
this._rfb_connection_state = 'connecting'; this._rfb_connection_state = 'connecting';
this._rfb_init_state = 'ProtocolVersion'; this._rfb_init_state = 'ProtocolVersion';
}; };
}; },
next_iteration = function () { _queueNextPacket: function () {
rfb = new RFB({'target': document.getElementById('VNC_canvas'), if (!this._running) { return; }
'view_only': true,
'onDisconnected': disconnected,
'onNotification': notification});
enable_test_mode();
// Missing in older recordings var frame = this._frames[this._frame_index];
if (typeof VNC_frame_encoding === 'undefined') {
var frame = VNC_frame_data[0];
var start = frame.indexOf('{', 1) + 1;
if (frame.slice(start).startsWith('UkZC')) {
encoding = 'base64';
} else {
encoding = 'binary';
}
} else {
encoding = VNC_frame_encoding;
}
if (iteration === 0) { // skip send frames
frame_length = VNC_frame_data.length; while (this._frame_index < this._frame_length && frame.charAt(0) === "}") {
test_state = 'running'; this._frame_index++;
} frame = this._frames[this._frame_index];
if (test_state !== 'running') { return; }
iteration += 1;
if (iteration > iterations) {
finish();
return;
}
frame_idx = 0;
istart_time = (new Date()).getTime();
rfb.connect('test', 0, "bogus");
queue_next_packet();
};
end_iteration = function () {
if (rfb._display.pending()) {
rfb._display.set_onFlush(function () {
if (rfb._flushing) {
rfb._onFlush();
}
end_iteration();
});
rfb._display.flush();
} else {
next_iteration();
}
};
queue_next_packet = function () {
var frame, foffset, toffset, delay;
if (test_state !== 'running') { return; }
frame = VNC_frame_data[frame_idx];
while ((frame_idx < frame_length) && (frame.charAt(0) === "}")) {
//Util.Debug("Send frame " + frame_idx);
frame_idx += 1;
frame = VNC_frame_data[frame_idx];
} }
if (frame === 'EOF') { if (frame === 'EOF') {
Util.Debug("Finished, found EOF"); Log.Debug('Finished, found EOF');
end_iteration(); this._finish();
return;
}
if (frame_idx >= frame_length) {
Util.Debug("Finished, no more frames");
end_iteration();
return; return;
} }
if (mode === 'realtime') { if (this._frame_index >= this._frame_length) {
foffset = frame.slice(1, frame.indexOf('{', 1)); Log.Debug('Finished, no more frames');
toffset = (new Date()).getTime() - istart_time; this._finish();
delay = foffset - toffset; return;
if (delay < 1) {
delay = 1;
} }
setTimeout(do_packet, delay); if (this._realtime) {
let foffset = frame.slice(1, frame.indexOf('{', 1));
let toffset = (new Date()).getTime() - this._start_time;
let delay = foffset - toffset;
if (delay < 1) delay = 1;
setTimeout(this._doPacket.bind(this), delay);
} else { } else {
window.setImmediate(do_packet); setImmediate(this._doPacket.bind(this));
} }
}; },
var bytes_processed = 0; _doPacket: function () {
// Avoid having excessive queue buildup in non-realtime mode
do_packet = function () { if (!this._trafficManagement && this._rfb._flushing) {
// Avoid having an excessive queue buildup let player = this;
if (rfb._flushing && (mode !== 'realtime')) { this._rfb.display.set_onFlush(function () {
rfb._display.set_onFlush(function () { this._rfb._display.set_onFlush(this._rfb._onFlush.bind(this._rfb));
rfb._display.set_onFlush(rfb._onFlush.bind(rfb)); this._rfb._onFlush();
rfb._onFlush(); player._doPacket();
do_packet();
}); });
return; return;
} }
//Util.Debug("Processing frame: " + frame_idx); const frame = this._frames[this._frame_index];
var frame = VNC_frame_data[frame_idx], var start = frame.indexOf('{', 1) + 1;
start = frame.indexOf('{', 1) + 1; if (this._encoding === 'base64') {
var u8; var u8 = Base64.decode(frame.slice(start));
if (encoding === 'base64') {
u8 = Base64.decode(frame.slice(start));
start = 0; start = 0;
} else { } else {
u8 = new Uint8Array(frame.length - start); var u8 = new Uint8Array(frame.length - start);
for (var i = 0; i < frame.length - start; i++) { for (let i = 0; i < frame.length - start; i++) {
u8[i] = frame.charCodeAt(start + i); u8[i] = frame.charCodeAt(start + i);
} }
} }
bytes_processed += u8.length;
rfb._sock._recv_message({'data' : u8});
frame_idx += 1;
queue_next_packet(); this._rfb._sock._recv_message({'data': u8});
this._frame_index++;
this._queueNextPacket();
},
_finish() {
if (this._rfb._display.pending()) {
var player = this;
this._rfb._display.set_onFlush(function () {
if (player._rfb._flushing) {
player._rfb._onFlush();
}
player._finish();
});
this._rfb._display.flush();
} else {
this._running = false;
this.onfinish((new Date()).getTime() - this._start_time);
}
},
_handleDisconnect(rfb, reason) {
this._running = false;
this._disconnected(rfb, reason, this._frame_index);
}
}; };

View File

@ -2,6 +2,8 @@
<html> <html>
<head> <head>
<title>VNC Playback</title> <title>VNC Playback</title>
<script src="/vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
<script type="module" src="./playback.js"></script>
</head> </head>
<body> <body>
@ -9,8 +11,7 @@
Perftest:<input type='radio' id='mode1' name='mode' checked>&nbsp; Perftest:<input type='radio' id='mode1' name='mode' checked>&nbsp;
Realtime:<input type='radio' id='mode2' name='mode'>&nbsp;&nbsp; Realtime:<input type='radio' id='mode2' name='mode'>&nbsp;&nbsp;
<input id='startButton' type='button' value='Start' style='width:100px' <input id='startButton' type='button' value='Start' style='width:100px' disabled>&nbsp;
onclick="start();" disabled>&nbsp;
<br><br> <br><br>
@ -37,98 +38,5 @@
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
--> -->
<script type="text/javascript"> <script type="module" src="./playback-ui.js">
var INCLUDE_URI= "../";
</script>
<script src="../core/util.js"></script>
<script src="../app/webutil.js"></script>
<script>
var fname, start_time;
function message(str) {
console.log(str);
var cell = document.getElementById('messages');
cell.textContent += str + "\n";
cell.scrollTop = cell.scrollHeight;
}
fname = WebUtil.getQueryVar('data', null);
if (fname) {
message("Loading " + fname);
// Load supporting scripts
WebUtil.load_scripts({
'core': ["base64.js", "websock.js", "des.js", "input/keysym.js",
"input/keysymdef.js", "input/xtscancodes.js", "input/util.js",
"input/devices.js", "display.js", "rfb.js", "inflator.js"],
'tests': ["playback.js"],
'recordings': [fname]});
} else {
message("Must specify data=FOO in query string.");
}
disconnected = function (rfb, reason) {
if (reason) {
message("noVNC sent '" + state + "' state during iteration " + iteration + " frame " + frame_idx);
test_state = 'failed';
}
}
notification = function (rfb, mesg, level, options) {
document.getElementById('VNC_status').textContent = mesg;
}
function start() {
document.getElementById('startButton').value = "Running";
document.getElementById('startButton').disabled = true;
iterations = document.getElementById('iterations').value;
iteration = 0;
start_time = (new Date()).getTime();
if (document.getElementById('mode1').checked) {
message("Starting performance playback (fullspeed) [" + iterations + " iteration(s)]");
mode = 'perftest';
} else {
message("Starting realtime playback [" + iterations + " iteration(s)]");
mode = 'realtime';
}
//recv_message = rfb.testMode(send_array, VNC_frame_encoding);
next_iteration();
}
function finish() {
// Finished with all iterations
var total_time, end_time = (new Date()).getTime();
total_time = end_time - start_time;
iter_time = parseInt(total_time / iterations, 10);
message(iterations + " iterations took " + total_time + "ms, " +
iter_time + "ms per iteration");
// Shut-off event interception
rfb.get_mouse().ungrab();
rfb.get_keyboard().ungrab();
document.getElementById('startButton').disabled = false;
document.getElementById('startButton').value = "Start";
}
window.onscriptsload = function () {
iterations = WebUtil.getQueryVar('iterations', 3);
document.getElementById('iterations').value = iterations;
mode = WebUtil.getQueryVar('mode', 3);
if (mode === 'realtime') {
document.getElementById('mode2').checked = true;
} else {
document.getElementById('mode1').checked = true;
}
if (fname) {
message("VNC_frame_data.length: " + VNC_frame_data.length);
}
document.getElementById('startButton').disabled = false;
}
</script>
</html> </html>

View File

@ -1350,7 +1350,7 @@ WorkerPool.prototype = {
}; };
var promiseMap = new Map(); var promiseMap = new Map();
var babelWorker = new WorkerPool('vendor/browser-es-module-loader/dist/babel-worker.js', 3); var babelWorker = new WorkerPool('/vendor/browser-es-module-loader/dist/babel-worker.js', 3);
babelWorker.onmessage = function (evt) { babelWorker.onmessage = function (evt) {
var promFuncs = promiseMap.get(evt.data.key); var promFuncs = promiseMap.get(evt.data.key);
promFuncs.resolve(evt.data); promFuncs.resolve(evt.data);
@ -1391,8 +1391,10 @@ BrowserESModuleLoader.prototype[RegisterLoader$1.instantiate] = function(key, pr
}).then(function (data) { }).then(function (data) {
// evaluate without require, exports and module variables // evaluate without require, exports and module variables
// we leave module in for now to allow module.require access // we leave module in for now to allow module.require access
if (data.key.slice(-8) !== '#nocache') {
localStorage.setItem(key+'!raw', data.source); localStorage.setItem(key+'!raw', data.source);
localStorage.setItem(data.key+'!transpiled', data.code); localStorage.setItem(data.key+'!transpiled', data.code);
}
(0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled'); (0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
processAnonRegister(); processAnonRegister();
}); });

View File

@ -190,7 +190,7 @@ WorkerPool.prototype = {
}; };
var promiseMap = new Map(); var promiseMap = new Map();
var babelWorker = new WorkerPool('vendor/browser-es-module-loader/dist/babel-worker.js', 3); var babelWorker = new WorkerPool('/vendor/browser-es-module-loader/dist/babel-worker.js', 3);
babelWorker.onmessage = function (evt) { babelWorker.onmessage = function (evt) {
var promFuncs = promiseMap.get(evt.data.key); var promFuncs = promiseMap.get(evt.data.key);
promFuncs.resolve(evt.data); promFuncs.resolve(evt.data);
@ -231,8 +231,10 @@ BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, proc
}).then(function (data) { }).then(function (data) {
// evaluate without require, exports and module variables // evaluate without require, exports and module variables
// we leave module in for now to allow module.require access // we leave module in for now to allow module.require access
if (data.key.slice(-8) !== '#nocache') {
localStorage.setItem(key+'!raw', data.source); localStorage.setItem(key+'!raw', data.source);
localStorage.setItem(data.key+'!transpiled', data.code); localStorage.setItem(data.key+'!transpiled', data.code);
}
(0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled'); (0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
processAnonRegister(); processAnonRegister();
}); });