Merge pull request #774 from novnc/refactor/es6-module-loader

[Refactor] ES6 Modules
This commit is contained in:
Solly Ross 2017-03-22 15:28:13 -07:00 committed by GitHub
commit dd7068984d
78 changed files with 64652 additions and 9711 deletions

8
.gitignore vendored
View File

@ -3,7 +3,9 @@
tests/data_*.js
utils/rebind.so
utils/websockify
node_modules
build
lib
/node_modules
/build
/lib
recordings
*.swp
*~

View File

@ -7,7 +7,6 @@ node_js:
- '6.1'
env:
matrix:
- TEST_BROWSER_NAME=PhantomJS
- TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 10,Linux,OS X 10.11'
- TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 10,Linux,OS X 10.11'
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 10'

View File

@ -35,20 +35,8 @@ Contributing Guidelines
Running the unit tests
----------------------
There are two ways to run the unit tests. For both ways, you should first run
`npm install` (not as root).
The first way to run the tests is to run `npm test`. This will run all the
tests in the headless PhantomJS browser (which uses WebKit).
The second way to run the tests is using the `tests/run_from_console.js` file.
This way is a bit more flexible, and can provide more information about what
went wrong. To run all the tests, simply run `tests/run_from_console.js`.
To run a specific test file, you can use the `-t path/to/test/file.js` option.
If you wish to simply generate the HTML for the test, use the `-g` option, and
the path to the temporary HTML file will be written to standard out. To open
this file in your default browser automatically, pass the `-o` option as well.
More information can be found by passing the `--help` or `-h` option.
We use Karma to run our tests. You can launch karma manually, or simply
run `npm test`. The Karma debug page will display the tests in normal
mocha form, if you need it.
Thanks, and happy coding!

View File

@ -5,19 +5,9 @@ Public License 2.0). The noVNC core library is composed of the
Javascript code necessary for full noVNC operation. This includes (but
is not limited to):
core/base64.js
core/des.js
core/display.js
core/input/devices.js
core/input/keysym.js
core/logo.js
core/playback.js
core/rfb.js
app/ui.js
core/util.js
core/websock.js
app/webutil.js
core/input/xtscancodes.js
core/**/*.js
app/*.js
test/playback.js
The HTML, CSS, font and images files that included with the noVNC
source distibution (or repository) are not considered part of the
@ -49,8 +39,11 @@ licenses (all MPL 2.0 compatible):
core/des.js : Various BSD style licenses
utils/inflator.mod.js
include/inflator.js : MIT (for pako)
vendor/pako/ : MIT
vendor/browser-es-module-loader: MIT
vendor/promise.js : MIT
Any other files not mentioned above are typically marked with
a copyright/license header at the top of the file. The default noVNC
@ -66,7 +59,7 @@ The following license texts are included:
docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
docs/LICENSE.zlib
docs/LICENSE.Apache-2.0
docs/LICENSE.pako
vendor/pako/LICENSE (MIT)
Or alternatively the license texts may be found here:
@ -77,3 +70,4 @@ Or alternatively the license texts may be found here:
http://en.wikipedia.org/wiki/BSD_licenses
http://www.gzip.org/zlib/zlib_license.html
http://www.apache.org/licenses/LICENSE-2.0.html
https://opensource.org/licenses/MIT

61
app/error-handler.js Normal file
View File

@ -0,0 +1,61 @@
// NB: this should *not* be included as a module until we have
// native support in the browsers, so that our error handler
// can catch script-loading errors.
(function(){
"use strict";
function convertNewlines(msg, parentElem) {
const lines = msg.split("\n");
lines.forEach(function (line) {
parentElem.appendChild(document.createElement("br"));
parentElem.appendChild(document.createTextNode(line));
});
parentElem.removeChild(parentElem.firstChild);
return parentElem;
}
// Fallback for all uncought errors
function handleError (event, err) {
try {
const msg = document.getElementById('noVNC_fallback_errormsg');
// Only show the initial error
if (msg.hasChildNodes()) {
return false;
}
var div = document.createElement("div");
div.classList.add('noVNC_message');
convertNewlines(event.message, div);
msg.appendChild(div);
if (event.filename !== undefined && event.lineno !== undefined && event.colno !== undefined) {
div = document.createElement("div");
div.className = 'noVNC_location';
const text = event.filename + ":" + event.lineno + ":" + event.colno;
div.appendChild(document.createTextNode(text));
msg.appendChild(div);
}
if ((err !== undefined) &&
(err.stack !== undefined)) {
div = document.createElement("div");
div.className = 'noVNC_stack';
div.appendChild(document.createTextNode(err.stack));
msg.appendChild(div);
}
document.getElementById('noVNC_fallback_error')
.classList.add("noVNC_open");
} catch (exc) {
document.write("noVNC encountered an error.");
}
// Don't return true since this would prevent the error
// from being printed to the browser console.
return false;
}
window.addEventListener('error', function (evt) { handleError(evt, evt.error); });
window.addEventListener('unhandledrejection', function (evt) { handleError(evt.reason, evt.reason); });
})();

View File

@ -1,11 +1,4 @@
/*
* Translations for de
*
* This file was autotomatically generated from de.po
* DO NOT EDIT!
*/
Language = {
{
"Connecting...": "Verbunden...",
"Connected (encrypted) to ": "Verbunden mit (verschlüsselt) ",
"Connected (unencrypted) to ": "Verbunden mit (unverschlüsselt) ",
@ -14,5 +7,5 @@ Language = {
"Must set host and port": "Richten Sie Host und Port ein",
"Password is required": "Passwort ist erforderlich",
"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt",
"Disconnect timeout": "Timeout beim trennen",
};
"Disconnect timeout": "Timeout beim trennen"
}

View File

@ -1,11 +1,4 @@
/*
* Translations for el
*
* This file was autotomatically generated from el.po
* DO NOT EDIT!
*/
Language = {
{
"Connecting...": "Συνδέεται...",
"Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ",
"Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ",
@ -15,7 +8,7 @@ Language = {
"Password is required": "Απαιτείται ο κωδικός πρόσβασης",
"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Εφαρμογή λειτουργίας αποκοπής αφού δεν υποστηρίζονται οι λωρίδες κύλισης σε πλήρη οθόνη στον IE",
"Disconnect timeout": "Παρέλευση χρονικού ορίου αποσύνδεσης",
"noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα",
"noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
"Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
"Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
"viewport drag": "σύρσιμο θεατού πεδίου",
@ -65,10 +58,13 @@ Language = {
"default": "προεπιλεγμένο",
"Logging:": "Καταγραφή:",
"Apply": "Εφαρμογή",
"Connect": "Σύνδεση",
"Disconnect": "Αποσύνδεση",
"Connection": "Σύνδεση",
"Host:": "Όνομα διακομιστή:",
"Port:": "Πόρτα διακομιστή:",
"Password:": "Κωδικός Πρόσβασης:",
"Token:": "Διακριτικό:",
"Send Password": "Αποστολή Κωδικού Πρόσβασης",
"Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas",
};
"Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas"
}

View File

@ -1,11 +1,4 @@
/*
* Translations for nl
*
* This file was autotomatically generated from nl.po
* DO NOT EDIT!
*/
Language = {
{
"Connecting...": "Verbinden...",
"Connected (encrypted) to ": "Verbonden (versleuteld) met ",
"Connected (unencrypted) to ": "Verbonden (onversleuteld) met ",
@ -14,5 +7,5 @@ Language = {
"Must set host and port": "Host en poort moeten worden ingesteld",
"Password is required": "Wachtwoord is vereist",
"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-modus in IE niet worden ondersteund",
"Disconnect timeout": "Timeout tijdens verbreken van verbinding",
};
"Disconnect timeout": "Timeout tijdens verbreken van verbinding"
}

View File

@ -1,11 +1,4 @@
/*
* Translations for sv
*
* This file was autotomatically generated from sv.po
* DO NOT EDIT!
*/
Language = {
{
"Connecting...": "Ansluter...",
"Connected (encrypted) to ": "Ansluten (krypterat) till ",
"Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
@ -73,5 +66,5 @@ Language = {
"Password:": "Lösenord:",
"Token:": "Token:",
"Send Password": "Skicka Lösenord",
"Canvas not supported.": "Canvas stöds ej",
};
"Canvas not supported.": "Canvas stöds ej"
}

View File

@ -247,6 +247,11 @@ select:active {
font-weight: normal;
}
#noVNC_fallback_errormsg .noVNC_message {
display: inline-block;
text-align: left;
}
#noVNC_fallback_error .noVNC_location {
font-style: italic;
font-size: 0.8em;
@ -774,6 +779,7 @@ select:active {
justify-content: center;
flex-direction: column;
}
:root.noVNC_loading #noVNC_transition,
:root.noVNC_connecting #noVNC_transition,
:root.noVNC_disconnecting #noVNC_transition,
:root.noVNC_reconnecting #noVNC_transition {

167
app/ui.js
View File

@ -11,81 +11,17 @@
/* jslint white: false, browser: true */
/* global window, document.getElementById, Util, WebUtil, RFB, Display */
/* [module]
* import Util from "../core/util";
* import KeyTable from "../core/input/keysym";
* import keysyms from "./keysymdef";
* import RFB from "../core/rfb";
* import Display from "../core/display";
* import WebUtil from "./webutil";
*/
import * as Log from '../core/util/logging.js';
import _, { l10n } from '../core/util/localization.js'
import { isTouchDevice, browserSupportsCursorURIs as cursorURIsSupported } from '../core/util/browsers.js';
import { setCapture, getPointerEvent } from '../core/util/events.js';
import KeyTable from "../core/input/keysym.js";
import keysyms from "../core/input/keysymdef.js";
import RFB from "../core/rfb.js";
import Display from "../core/display.js";
import * as WebUtil from "./webutil.js";
var UI;
(function () {
"use strict";
// Fallback for all uncought errors
window.addEventListener('error', function(event) {
try {
var msg, div, text;
msg = document.getElementById('noVNC_fallback_errormsg');
// Only show the initial error
if (msg.hasChildNodes()) {
return false;
}
div = document.createElement("div");
div.appendChild(document.createTextNode(event.message));
msg.appendChild(div);
div = document.createElement("div");
div.className = 'noVNC_location';
text = event.filename + ":" + event.lineno + ":" + event.colno;
div.appendChild(document.createTextNode(text));
msg.appendChild(div);
if ((event.error !== undefined) &&
(event.error.stack !== undefined)) {
div = document.createElement("div");
div.className = 'noVNC_stack';
div.appendChild(document.createTextNode(event.error.stack));
msg.appendChild(div);
}
document.getElementById('noVNC_fallback_error')
.classList.add("noVNC_open");
} catch (exc) {
document.write("noVNC encountered an error.");
}
// Don't return true since this would prevent the error
// from being printed to the browser console.
return false;
});
// Set up translations
var LINGUAS = ["de", "el", "nl", "sv"];
Util.Localisation.setup(LINGUAS);
if (Util.Localisation.language !== "en") {
WebUtil.load_scripts(
{'app': ["locale/" + Util.Localisation.language + ".js"]});
}
/* [begin skip-as-module] */
// Load supporting scripts
WebUtil.load_scripts(
{'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
"input/xtscancodes.js", "input/util.js", "input/devices.js",
"display.js", "inflator.js", "rfb.js", "input/keysym.js"]});
window.onscriptsload = function () { UI.load(); };
/* [end skip-as-module] */
var _ = Util.Localisation.get;
UI = {
const UI = {
connected: false,
desktopName: "",
@ -110,6 +46,14 @@ var UI;
reconnect_callback: null,
reconnect_password: null,
prime: function(callback) {
if (document.readyState === "interactive" || document.readyState === "complete") {
UI.load(callback);
} else {
document.addEventListener('DOMContentLoaded', UI.load.bind(UI, callback));
}
},
// Setup rfb object, load settings from browser storage, then call
// UI.init to setup the UI/menus
load: function(callback) {
@ -126,10 +70,10 @@ var UI;
UI.initSettings();
// Translate the DOM
Util.Localisation.translateDOM();
l10n.translateDOM();
// Adapt the interface for touch screen devices
if (Util.isTouchDevice) {
if (isTouchDevice) {
document.documentElement.classList.add("noVNC_touch");
// Remove the address bar
setTimeout(function() { window.scrollTo(0, 1); }, 100);
@ -166,6 +110,7 @@ var UI;
UI.updateVisualState();
document.getElementById('noVNC_setting_host').focus();
document.documentElement.classList.remove("noVNC_loading");
var autoconnect = WebUtil.getConfigVar('autoconnect', false);
if (autoconnect === 'true' || autoconnect == '1') {
@ -224,7 +169,7 @@ var UI;
UI.initSetting('port', port);
UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('true_color', true);
UI.initSetting('cursor', !Util.isTouchDevice);
UI.initSetting('cursor', !isTouchDevice);
UI.initSetting('clip', false);
UI.initSetting('resize', 'off');
UI.initSetting('shared', true);
@ -236,7 +181,6 @@ var UI;
UI.setupSettingLabels();
},
// Adds a link to the label elements on the corresponding input elements
setupSettingLabels: function() {
var labels = document.getElementsByTagName('LABEL');
@ -274,7 +218,7 @@ var UI;
return true;
} catch (exc) {
var msg = "Unable to create RFB client -- " + exc;
Util.Error(msg);
Log.Error(msg);
UI.showStatus(msg, 'error');
return false;
}
@ -509,7 +453,7 @@ var UI;
break;
default:
msg = "Invalid UI state";
Util.Error(msg);
Log.Error(msg);
UI.showStatus(msg, 'error');
break;
}
@ -519,11 +463,11 @@ var UI;
// Disable/enable controls depending on connection state
updateVisualState: function() {
//Util.Debug(">> updateVisualState");
//Log.Debug(">> updateVisualState");
UI.enableDisableViewClip();
if (Util.browserSupportsCursorURIs() && !Util.isTouchDevice) {
if (cursorURIsSupported() && !isTouchDevice) {
UI.enableSetting('cursor');
} else {
UI.disableSetting('cursor');
@ -575,7 +519,7 @@ var UI;
document.getElementById('noVNC_password_dlg')
.classList.remove('noVNC_open');
//Util.Debug("<< updateVisualState");
//Log.Debug("<< updateVisualState");
},
showStatus: function(text, status_type, time) {
@ -689,7 +633,7 @@ var UI;
dragControlbarHandle: function (e) {
if (!UI.controlbarGrabbed) return;
var ptr = Util.getPointerEvent(e);
var ptr = getPointerEvent(e);
var anchor = document.getElementById('noVNC_control_bar_anchor');
if (ptr.clientX < (window.innerWidth * 0.1)) {
@ -786,12 +730,12 @@ var UI;
controlbarHandleMouseDown: function(e) {
if ((e.type == "mousedown") && (e.button != 0)) return;
var ptr = Util.getPointerEvent(e);
var ptr = getPointerEvent(e);
var handle = document.getElementById("noVNC_control_bar_handle");
var bounds = handle.getBoundingClientRect();
Util.setCapture(handle);
setCapture(handle);
UI.controlbarGrabbed = true;
UI.controlbarDrag = false;
@ -872,7 +816,7 @@ var UI;
val = ctrl.value;
}
WebUtil.writeSetting(name, val);
//Util.Debug("Setting saved '" + name + "=" + val + "'");
//Log.Debug("Setting saved '" + name + "=" + val + "'");
return val;
},
@ -931,10 +875,10 @@ var UI;
// Refresh UI elements from saved cookies
UI.updateSetting('encrypt');
UI.updateSetting('true_color');
if (Util.browserSupportsCursorURIs()) {
if (cursorURIsSupported()) {
UI.updateSetting('cursor');
} else {
UI.updateSetting('cursor', !Util.isTouchDevice);
UI.updateSetting('cursor', !isTouchDevice);
UI.disableSetting('cursor');
}
UI.updateSetting('clip');
@ -1047,9 +991,9 @@ var UI;
},
clipboardReceive: function(rfb, text) {
Util.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "...");
Log.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "...");
document.getElementById('noVNC_clipboard_text').value = text;
Util.Debug("<< UI.clipboardReceive");
Log.Debug("<< UI.clipboardReceive");
},
clipboardClear: function() {
@ -1059,9 +1003,9 @@ var UI;
clipboardSend: function() {
var text = document.getElementById('noVNC_clipboard_text').value;
Util.Debug(">> UI.clipboardSend: " + text.substr(0,40) + "...");
Log.Debug(">> UI.clipboardSend: " + text.substr(0,40) + "...");
UI.rfb.clipboardPasteFrom(text);
Util.Debug("<< UI.clipboardSend");
Log.Debug("<< UI.clipboardSend");
},
/* ------^-------
@ -1095,7 +1039,7 @@ var UI;
if ((!host) || (!port)) {
var msg = _("Must set host and port");
Util.Error(msg);
Log.Error(msg);
UI.showStatus(msg, 'error');
return;
}
@ -1185,7 +1129,7 @@ var UI;
if (typeof msg === 'undefined') {
msg = _("Password is required");
}
Util.Warn(msg);
Log.Warn(msg);
UI.showStatus(msg, "warning");
},
@ -1279,7 +1223,7 @@ var UI;
UI.resizeTimeout = setTimeout(function(){
// Request a remote size covering the viewport
if (UI.rfb.requestDesktopSize(screen.w, screen.h)) {
Util.Debug('Requested new desktop size: ' +
Log.Debug('Requested new desktop size: ' +
screen.w + 'x' + screen.h);
}
}, 500);
@ -1334,7 +1278,7 @@ var UI;
if (resizeSetting === 'downscale' || resizeSetting === 'scale') {
// Disable clipping if we are scaling
new_clip = false;
} else if (Util.isTouchDevice) {
} else if (isTouchDevice) {
// Touch devices usually have shit scrollbars
new_clip = true;
}
@ -1362,7 +1306,7 @@ var UI;
var resizeSetting = UI.getSetting('resize');
// Disable clipping if we are scaling, connected or on touch
if (resizeSetting === 'downscale' || resizeSetting === 'scale' ||
Util.isTouchDevice) {
isTouchDevice) {
UI.disableSetting('clip');
} else {
UI.enableSetting('clip');
@ -1421,7 +1365,7 @@ var UI;
// Different behaviour for touch vs non-touch
// The button is disabled instead of hidden on touch devices
if (Util.isTouchDevice) {
if (isTouchDevice) {
viewDragButton.classList.remove("noVNC_hidden");
if (clipping) {
@ -1447,7 +1391,7 @@ var UI;
* ------v------*/
showVirtualKeyboard: function() {
if (!Util.isTouchDevice) return;
if (!isTouchDevice) return;
var input = document.getElementById('noVNC_keyboardinput');
@ -1463,7 +1407,7 @@ var UI;
},
hideVirtualKeyboard: function() {
if (!Util.isTouchDevice) return;
if (!isTouchDevice) return;
var input = document.getElementById('noVNC_keyboardinput');
@ -1701,10 +1645,12 @@ var UI;
},
updateLocalCursor: function() {
if (!UI.rfb) return;
UI.rfb.set_local_cursor(UI.getSetting('cursor'));
},
updateViewOnly: function() {
if (!UI.rfb) return;
UI.rfb.set_view_only(UI.getSetting('view_only'));
},
@ -1755,7 +1701,20 @@ var UI;
*/
};
/* [module] UI.load(); */
})();
// Set up translations
var LINGUAS = ["de", "el", "nl", "sv"];
l10n.setup(LINGUAS);
if (l10n.language !== "en" && l10n.dictionary === undefined) {
WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', function (translations) {
l10n.dictionary = translations;
/* [module] export default UI; */
// wait for translations to load before loading the UI
UI.prime();
}, function (err) {
throw err;
});
} else {
UI.prime();
}
export default UI;

View File

@ -10,62 +10,21 @@
/*jslint bitwise: false, white: false, browser: true, devel: true */
/*global Util, window, document */
/* [module]
* import Util from "../core/util";
*/
// Globals defined here
var WebUtil = {};
/*
* ------------------------------------------------------
* Namespaced in WebUtil
* ------------------------------------------------------
*/
import { init_logging as main_init_logging } from '../core/util/logging.js';
// init log level reading the logging HTTP param
WebUtil.init_logging = function (level) {
export function init_logging (level) {
"use strict";
if (typeof level !== "undefined") {
Util._log_level = level;
main_init_logging(level);
} else {
var param = document.location.href.match(/logging=([A-Za-z0-9\._\-]*)/);
Util._log_level = (param || ['', Util._log_level])[1];
main_init_logging(param || undefined);
}
Util.init_logging();
};
WebUtil.dirObj = function (obj, depth, parent) {
"use strict";
if (! depth) { depth = 2; }
if (! parent) { parent = ""; }
// Print the properties of the passed-in object
var msg = "";
for (var i in obj) {
if ((depth > 1) && (typeof obj[i] === "object")) {
// Recurse attributes that are objects
msg += WebUtil.dirObj(obj[i], depth - 1, parent + "." + i);
} else {
//val = new String(obj[i]).replace("\n", " ");
var val = "";
if (typeof(obj[i]) === "undefined") {
val = "undefined";
} else {
val = obj[i].toString().replace("\n", " ");
}
if (val.length > 30) {
val = val.substr(0, 30) + "...";
}
msg += parent + "." + i + ": " + val + "\n";
}
}
return msg;
};
// Read a query string variable
WebUtil.getQueryVar = function (name, defVal) {
export function getQueryVar (name, defVal) {
"use strict";
var re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = document.location.href.match(re);
@ -78,7 +37,7 @@ WebUtil.getQueryVar = function (name, defVal) {
};
// Read a hash fragment variable
WebUtil.getHashVar = function (name, defVal) {
export function getHashVar (name, defVal) {
"use strict";
var re = new RegExp('.*[&#]' + name + '=([^&]*)'),
match = document.location.hash.match(re);
@ -92,11 +51,11 @@ WebUtil.getHashVar = function (name, defVal) {
// Read a variable from the fragment or the query string
// Fragment takes precedence
WebUtil.getConfigVar = function (name, defVal) {
export function getConfigVar (name, defVal) {
"use strict";
var val = WebUtil.getHashVar(name);
var val = getHashVar(name);
if (val === null) {
val = WebUtil.getQueryVar(name, defVal);
val = getQueryVar(name, defVal);
}
return val;
};
@ -106,7 +65,7 @@ WebUtil.getConfigVar = function (name, defVal) {
*/
// No days means only for this browser session
WebUtil.createCookie = function (name, value, days) {
export function createCookie (name, value, days) {
"use strict";
var date, expires;
if (days) {
@ -126,7 +85,7 @@ WebUtil.createCookie = function (name, value, days) {
document.cookie = name + "=" + value + expires + "; path=/" + secure;
};
WebUtil.readCookie = function (name, defaultValue) {
export function readCookie (name, defaultValue) {
"use strict";
var nameEQ = name + "=",
ca = document.cookie.split(';');
@ -139,22 +98,24 @@ WebUtil.readCookie = function (name, defaultValue) {
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
};
WebUtil.eraseCookie = function (name) {
export function eraseCookie (name) {
"use strict";
WebUtil.createCookie(name, "", -1);
createCookie(name, "", -1);
};
/*
* Setting handling.
*/
WebUtil.initSettings = function (callback /*, ...callbackArgs */) {
var settings = {};
export function initSettings (callback /*, ...callbackArgs */) {
"use strict";
var callbackArgs = Array.prototype.slice.call(arguments, 1);
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.get(function (cfg) {
WebUtil.settings = cfg;
console.log(WebUtil.settings);
settings = cfg;
console.log(settings);
if (callback) {
callback.apply(this, callbackArgs);
}
@ -168,24 +129,24 @@ WebUtil.initSettings = function (callback /*, ...callbackArgs */) {
};
// No days means only for this browser session
WebUtil.writeSetting = function (name, value) {
export function writeSetting (name, value) {
"use strict";
if (window.chrome && window.chrome.storage) {
//console.log("writeSetting:", name, value);
if (WebUtil.settings[name] !== value) {
WebUtil.settings[name] = value;
window.chrome.storage.sync.set(WebUtil.settings);
if (settings[name] !== value) {
settings[name] = value;
window.chrome.storage.sync.set(settings);
}
} else {
localStorage.setItem(name, value);
}
};
WebUtil.readSetting = function (name, defaultValue) {
export function readSetting (name, defaultValue) {
"use strict";
var value;
if (window.chrome && window.chrome.storage) {
value = WebUtil.settings[name];
value = settings[name];
} else {
value = localStorage.getItem(name);
}
@ -199,17 +160,17 @@ WebUtil.readSetting = function (name, defaultValue) {
}
};
WebUtil.eraseSetting = function (name) {
export function eraseSetting (name) {
"use strict";
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name);
delete WebUtil.settings[name];
delete settings[name];
} else {
localStorage.removeItem(name);
}
};
WebUtil.injectParamIfMissing = function (path, param, value) {
export function injectParamIfMissing (path, param, value) {
// force pretend that we're dealing with a relative path
// (assume that we wanted an extra if we pass one in)
path = "/" + path;
@ -239,73 +200,36 @@ WebUtil.injectParamIfMissing = function (path, param, value) {
}
};
// Dynamically load scripts without using document.write()
// Reference: http://unixpapa.com/js/dyna.html
//
// Handles the case where load_scripts is invoked from a script that
// itself is loaded via load_scripts. Once all scripts are loaded the
// window.onscriptsloaded handler is called (if set).
WebUtil.get_include_uri = function (root_dir) {
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI + root_dir + '/' : root_dir + '/';
};
WebUtil._loading_scripts = [];
WebUtil._pending_scripts = [];
WebUtil.load_scripts = function (files_by_dir) {
"use strict";
var head = document.getElementsByTagName('head')[0], script,
ls = WebUtil._loading_scripts, ps = WebUtil._pending_scripts;
// sadly, we can't use the Fetch API until we decide to drop
// IE11 support or polyfill promises and fetch in IE11.
// resolve will receive an object on success, while reject
// will receive either an event or an error on failure.
export function fetchJSON(path, resolve, reject) {
// NB: IE11 doesn't support JSON as a responseType
const req = new XMLHttpRequest();
req.open('GET', path);
var loadFunc = function (e) {
while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
ls[0].readyState === 'complete')) {
// For IE, append the script to trigger execution
var s = ls.shift();
//console.log("loaded script: " + s.src);
head.appendChild(s);
req.onload = function () {
if (req.status === 200) {
try {
var resObj = JSON.parse(req.responseText);
} catch (err) {
reject(err);
return;
}
if (!this.readyState ||
(Util.Engine.presto && this.readyState === 'loaded') ||
this.readyState === 'complete') {
if (ps.indexOf(this) >= 0) {
this.onload = this.onreadystatechange = null;
//console.log("completed script: " + this.src);
ps.splice(ps.indexOf(this), 1);
// Call window.onscriptsload after last script loads
if (ps.length === 0 && window.onscriptsload) {
window.onscriptsload();
}
}
}
};
var root_dirs = Object.keys(files_by_dir);
for (var d = 0; d < root_dirs.length; d++) {
var root_dir = root_dirs[d];
var files = files_by_dir[root_dir];
for (var f = 0; f < files.length; f++) {
script = document.createElement('script');
script.type = 'text/javascript';
script.src = WebUtil.get_include_uri(root_dir) + files[f];
//console.log("loading script: " + script.src);
script.onload = script.onreadystatechange = loadFunc;
// In-order script execution tricks
if (Util.Engine.trident) {
// For IE wait until readyState is 'loaded' before
// appending it which will trigger execution
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
ls.push(script);
resolve(resObj);
} else {
// For webkit and firefox set async=false and append now
// https://developer.mozilla.org/en-US/docs/HTML/Element/script
script.async = false;
head.appendChild(script);
}
ps.push(script);
}
reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status));
}
};
/* [module] export default WebUtil; */
req.onerror = function (evt) {
reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
};
req.ontimeout = function (evt) {
reject(new Error("XHR timed out while trying to load '" + path + "'"));
};
req.send();
}

View File

@ -7,7 +7,7 @@
/*jslint white: false */
/*global console */
var Base64 = {
export default {
/* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
base64Pad : '=',
@ -15,7 +15,7 @@ var Base64 = {
encode: function (data) {
"use strict";
var result = '';
var toBase64Table = Base64.toBase64Table;
var toBase64Table = this.toBase64Table;
var length = data.length;
var lengthpad = (length % 3);
// Convert every three bytes to 4 ascii characters.
@ -63,8 +63,8 @@ var Base64 = {
decode: function (data, offset) {
"use strict";
offset = typeof(offset) !== 'undefined' ? offset : 0;
var toBinaryTable = Base64.toBinaryTable;
var base64Pad = Base64.base64Pad;
var toBinaryTable = this.toBinaryTable;
var base64Pad = this.base64Pad;
var result, result_length;
var leftbits = 0; // number of bits decoded, but yet to be appended
var leftdata = 0; // bits decoded, but yet to be appended
@ -111,5 +111,3 @@ var Base64 = {
return result;
}
}; /* End of Base64 namespace */
/* [module] export default Base64; */

View File

@ -77,7 +77,7 @@
/* jslint white: false */
/* [module] export default */ function DES(passwd) {
export default function DES(passwd) {
"use strict";
// Tables, permutations, S-boxes, etc.

View File

@ -10,12 +10,12 @@
/*jslint browser: true, white: false */
/*global Util, Base64, changeCursor */
/* [module]
* import Util from "./util";
* import Base64 from "./base64";
*/
import { Engine, browserSupportsCursorURIs as cursorURIsSupported } from './util/browsers.js';
import { set_defaults, make_properties } from './util/properties.js';
import * as Log from './util/logging.js';
import Base64 from "./base64.js";
/* [module] export default */ function Display(defaults) {
export default function Display(defaults) {
this._drawCtx = null;
this._c_forceCanvas = false;
@ -32,7 +32,7 @@
this._tile_x = 0;
this._tile_y = 0;
Util.set_defaults(this, defaults, {
set_defaults(this, defaults, {
'true_color': true,
'colourMap': [],
'scale': 1.0,
@ -41,7 +41,7 @@
"onFlush": function () {},
});
Util.Debug(">> Display.constructor");
Log.Debug(">> Display.constructor");
// The visible canvas
if (!this._target) {
@ -69,11 +69,11 @@
right: this._backbuffer.width,
bottom: this._backbuffer.height };
Util.Debug("User Agent: " + navigator.userAgent);
if (Util.Engine.gecko) { Util.Debug("Browser: gecko " + Util.Engine.gecko); }
if (Util.Engine.webkit) { Util.Debug("Browser: webkit " + Util.Engine.webkit); }
if (Util.Engine.trident) { Util.Debug("Browser: trident " + Util.Engine.trident); }
if (Util.Engine.presto) { Util.Debug("Browser: presto " + Util.Engine.presto); }
Log.Debug("User Agent: " + navigator.userAgent);
if (Engine.gecko) { Log.Debug("Browser: gecko " + Engine.gecko); }
if (Engine.webkit) { Log.Debug("Browser: webkit " + Engine.webkit); }
if (Engine.trident) { Log.Debug("Browser: trident " + Engine.trident); }
if (Engine.presto) { Log.Debug("Browser: presto " + Engine.presto); }
this.clear();
@ -85,22 +85,19 @@
}
if (this._prefer_js === null) {
Util.Info("Prefering javascript operations");
Log.Info("Prefering javascript operations");
this._prefer_js = true;
}
// Determine browser support for setting the cursor via data URI scheme
if (this._cursor_uri || this._cursor_uri === null ||
this._cursor_uri === undefined) {
this._cursor_uri = Util.browserSupportsCursorURIs();
this._cursor_uri = cursorURIsSupported();
}
Util.Debug("<< Display.constructor");
Log.Debug("<< Display.constructor");
};
(function () {
"use strict";
var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
try {
new ImageData(new Uint8ClampedArray(4), 1, 1);
@ -109,7 +106,6 @@
// ignore failure
}
Display.prototype = {
// Public methods
viewportChangePos: function (deltaX, deltaY) {
@ -144,7 +140,7 @@
if (deltaX === 0 && deltaY === 0) {
return;
}
Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
Log.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
vp.x += deltaX;
vp.y += deltaY;
@ -160,7 +156,7 @@
typeof(width) === "undefined" ||
typeof(height) === "undefined") {
Util.Debug("Setting viewport to full display region");
Log.Debug("Setting viewport to full display region");
width = this._fb_width;
height = this._fb_height;
}
@ -528,7 +524,7 @@
changeCursor: function (pixels, mask, hotx, hoty, w, h) {
if (this._cursor_uri === false) {
Util.Warn("changeCursor called but no cursor data URI support");
Log.Warn("changeCursor called but no cursor data URI support");
return;
}
@ -743,7 +739,7 @@
},
};
Util.make_properties(Display, [
make_properties(Display, [
['target', 'wo', 'dom'], // Canvas element for rendering
['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only)
['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "type": mime-type, "data": data}
@ -869,4 +865,3 @@
var url = 'data:image/x-icon;base64,' + Base64.encode(cur);
target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
};
})();

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +0,0 @@
var zlib = require('pako/lib/zlib/inflate.js');
var ZStream = require('pako/lib/zlib/zstream.js');
function Inflate() {
this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10;
this.strm.output = new Uint8Array(this.chunkSize);
this.windowBits = 5;
zlib.inflateInit(this.strm, this.windowBits);
};
Inflate.prototype = {
inflate: function (data, flush, expected) {
this.strm.input = data;
this.strm.avail_in = this.strm.input.length;
this.strm.next_in = 0;
this.strm.next_out = 0;
// resize our output buffer if it's too small
// (we could just use multiple chunks, but that would cause an extra
// allocation each time to flatten the chunks)
if (expected > this.chunkSize) {
this.chunkSize = expected;
this.strm.output = new Uint8Array(this.chunkSize);
}
this.strm.avail_out = this.chunkSize;
zlib.inflate(this.strm, flush);
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
},
reset: function () {
zlib.inflateReset(this.strm);
}
};
module.exports = { Inflate: Inflate };

View File

@ -8,25 +8,21 @@
/*jslint browser: true, white: false */
/*global window, Util */
/* [module]
* import Util from "../util";
* import KeyboardUtil from "./util";
*/
/* [module] export */ var Keyboard;
(function () {
"use strict";
import * as Log from '../util/logging.js';
import { isTouchDevice } from '../util/browsers.js'
import { setCapture, releaseCapture, stopEvent, getPointerEvent } from '../util/events.js';
import { set_defaults, make_properties } from '../util/properties.js';
import * as KeyboardUtil from "./util.js";
//
// Keyboard event handler
//
Keyboard = function (defaults) {
const Keyboard = function (defaults) {
this._keyDownList = []; // List of depressed keys
// (even if they are happy)
Util.set_defaults(this, defaults, {
set_defaults(this, defaults, {
'target': document,
'focused': true
});
@ -54,7 +50,7 @@
_handleRfbEvent: function (e) {
if (this._onKeyPress) {
Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
Log.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
this._onKeyPress(e);
}
@ -73,7 +69,7 @@
if (this._handler.keydown(e)) {
// Suppress bubbling/default actions
Util.stopEvent(e);
stopEvent(e);
} else {
// Allow the event to bubble and become a keyPress event which
// will have the character code translated
@ -85,7 +81,7 @@
if (this._handler.keypress(e)) {
// Suppress bubbling/default actions
Util.stopEvent(e);
stopEvent(e);
}
},
@ -94,20 +90,20 @@
if (this._handler.keyup(e)) {
// Suppress bubbling/default actions
Util.stopEvent(e);
stopEvent(e);
}
},
_allKeysUp: function () {
Util.Debug(">> Keyboard.allKeysUp");
Log.Debug(">> Keyboard.allKeysUp");
this._handler.releaseAll();
Util.Debug("<< Keyboard.allKeysUp");
Log.Debug("<< Keyboard.allKeysUp");
},
// Public methods
grab: function () {
//Util.Debug(">> Keyboard.grab");
//Log.Debug(">> Keyboard.grab");
var c = this._target;
c.addEventListener('keydown', this._eventHandlers.keydown);
@ -117,11 +113,11 @@
// Release (key up) if window loses focus
window.addEventListener('blur', this._eventHandlers.blur);
//Util.Debug("<< Keyboard.grab");
//Log.Debug("<< Keyboard.grab");
},
ungrab: function () {
//Util.Debug(">> Keyboard.ungrab");
//Log.Debug(">> Keyboard.ungrab");
var c = this._target;
c.removeEventListener('keydown', this._eventHandlers.keydown);
@ -132,7 +128,7 @@
// Release (key up) all keys that are in a down state
this._allKeysUp();
//Util.Debug(">> Keyboard.ungrab");
//Log.Debug(">> Keyboard.ungrab");
},
sync: function (e) {
@ -140,25 +136,21 @@
}
};
Util.make_properties(Keyboard, [
make_properties(Keyboard, [
['target', 'wo', 'dom'], // DOM element that captures keyboard input
['focused', 'rw', 'bool'], // Capture and send key events
['onKeyPress', 'rw', 'func'] // Handler for key press/release
]);
})();
/* [module] export */ var Mouse;
(function () {
Mouse = function (defaults) {
const Mouse = function (defaults) {
this._mouseCaptured = false;
this._doubleClickTimer = null;
this._lastTouchPos = null;
// Configuration attributes
Util.set_defaults(this, defaults, {
set_defaults(this, defaults, {
'target': document,
'focused': true,
'touchButton': 1
@ -177,7 +169,7 @@
// private methods
_captureMouse: function () {
// capturing the mouse ensures we get the mouseup event
Util.setCapture(this._target);
setCapture(this._target);
// some browsers give us mouseup events regardless,
// so if we never captured the mouse, we can disregard the event
@ -185,7 +177,7 @@
},
_releaseMouse: function () {
Util.releaseCapture();
releaseCapture();
this._mouseCaptured = false;
},
@ -244,11 +236,11 @@
}
if (this._onMouseButton) {
Util.Debug("onMouseButton " + (down ? "down" : "up") +
Log.Debug("onMouseButton " + (down ? "down" : "up") +
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
this._onMouseButton(pos.x, pos.y, down, bmask);
}
Util.stopEvent(e);
stopEvent(e);
},
_handleMouseDown: function (e) {
@ -290,7 +282,7 @@
}
}
Util.stopEvent(e);
stopEvent(e);
},
_handleMouseMove: function (e) {
@ -304,7 +296,7 @@
if (this._onMouseMove) {
this._onMouseMove(pos.x, pos.y);
}
Util.stopEvent(e);
stopEvent(e);
},
_handleMouseDisable: function (e) {
@ -317,14 +309,13 @@
* to listen on the document element instead.
*/
if (e.target == this._target) {
//Util.Debug("mouse event disabled");
Util.stopEvent(e);
stopEvent(e);
}
},
// Return coordinates relative to target
_getMousePosition: function(e) {
e = Util.getPointerEvent(e);
e = getPointerEvent(e);
var bounds = this._target.getBoundingClientRect();
var x, y;
// Clip to target bounds
@ -345,12 +336,11 @@
return {x:x, y:y};
},
// Public methods
grab: function () {
var c = this._target;
if (Util.isTouchDevice) {
if (isTouchDevice) {
c.addEventListener('touchstart', this._eventHandlers.mousedown);
window.addEventListener('touchend', this._eventHandlers.mouseup);
c.addEventListener('touchend', this._eventHandlers.mouseup);
@ -373,7 +363,7 @@
ungrab: function () {
var c = this._target;
if (Util.isTouchDevice) {
if (isTouchDevice) {
c.removeEventListener('touchstart', this._eventHandlers.mousedown);
window.removeEventListener('touchend', this._eventHandlers.mouseup);
c.removeEventListener('touchend', this._eventHandlers.mouseup);
@ -391,7 +381,7 @@
}
};
Util.make_properties(Mouse, [
make_properties(Mouse, [
['target', 'ro', 'dom'], // DOM element that captures mouse input
['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
@ -400,4 +390,5 @@
['onMouseMove', 'rw', 'func'], // Handler for mouse movement
['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
]);
})();
export { Keyboard, Mouse };

View File

@ -1,4 +1,4 @@
var KeyTable = {
export default {
XK_VoidSymbol: 0xffffff, /* Void symbol */
XK_BackSpace: 0xff08, /* Back space, back char */
@ -378,5 +378,3 @@ var KeyTable = {
XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
};
/* [module] export default KeyTable; */

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,7 @@
/* [module]
* import KeyTable from "./keysym";
* import keysyms from "./keysymdef";
*/
import KeyTable from "./keysym.js";
import keysyms from "./keysymdef.js";
var KeyboardUtil = {};
(function() {
"use strict";
function substituteCodepoint(cp) {
export function substituteCodepoint(cp) {
// Any Unicode code points which do not have corresponding keysym entries
// can be swapped out for another code point by adding them to this table
var substitutions = {
@ -35,7 +28,7 @@ var KeyboardUtil = {};
}
// Return true if a modifier which is not the specified char modifier (and is not shift) is down
function hasShortcutModifier(charModifier, currentModifiers) {
export function hasShortcutModifier(charModifier, currentModifiers) {
var mods = {};
for (var key in currentModifiers) {
if (parseInt(key) !== KeyTable.XK_Shift_L) {
@ -58,7 +51,7 @@ var KeyboardUtil = {};
}
// Return true if the specified char modifier is currently down
function hasCharModifier(charModifier, currentModifiers) {
export function hasCharModifier(charModifier, currentModifiers) {
if (charModifier.length === 0) { return false; }
for (var i = 0; i < charModifier.length; ++i) {
@ -71,7 +64,7 @@ var KeyboardUtil = {};
// Helper object tracking modifier key state
// and generates fake key events to compensate if it gets out of sync
function ModifierSync(charModifier) {
export function ModifierSync(charModifier) {
if (!charModifier) {
if (isMac()) {
// on Mac, Option (AKA Alt) is used as a char modifier
@ -157,7 +150,7 @@ var KeyboardUtil = {};
// Get a key ID from a keyboard event
// May be a string or an integer depending on the available properties
function getKey(evt){
export function getKey(evt){
if ('keyCode' in evt && 'key' in evt) {
return evt.key + ':' + evt.keyCode;
}
@ -171,7 +164,7 @@ var KeyboardUtil = {};
// Get the most reliable keysym value we can get from a key event
// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
function getKeysym(evt){
export function getKeysym(evt){
var codepoint;
if (evt.char && evt.char.length === 1) {
codepoint = evt.char.charCodeAt();
@ -201,7 +194,7 @@ var KeyboardUtil = {};
// Given a keycode, try to predict which keysym it might be.
// If the keycode is unknown, null is returned.
function keysymFromKeyCode(keycode, shiftPressed) {
export function keysymFromKeyCode(keycode, shiftPressed) {
if (typeof(keycode) !== 'number') {
return null;
}
@ -236,7 +229,7 @@ var KeyboardUtil = {};
// if the key is a known non-character key (any key which doesn't generate character data)
// return its keysym value. Otherwise return null
function nonCharacterKey(evt) {
export function nonCharacterKey(evt) {
// evt.key not implemented yet
if (!evt.keyCode) { return null; }
var keycode = evt.keyCode;
@ -278,17 +271,7 @@ var KeyboardUtil = {};
}
}
KeyboardUtil.hasShortcutModifier = hasShortcutModifier;
KeyboardUtil.hasCharModifier = hasCharModifier;
KeyboardUtil.ModifierSync = ModifierSync;
KeyboardUtil.getKey = getKey;
KeyboardUtil.getKeysym = getKeysym;
KeyboardUtil.keysymFromKeyCode = keysymFromKeyCode;
KeyboardUtil.nonCharacterKey = nonCharacterKey;
KeyboardUtil.substituteCodepoint = substituteCodepoint;
})();
KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) {
export function QEMUKeyEventDecoder (modifierState, next) {
"use strict";
function sendAll(evts) {
@ -334,7 +317,7 @@ KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) {
var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!KeyboardUtil.nonCharacterKey(evt));
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
next(result);
return suppress;
@ -358,7 +341,7 @@ KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) {
};
};
KeyboardUtil.TrackQEMUKeyState = function(next) {
export function TrackQEMUKeyState (next) {
"use strict";
var state = [];
@ -426,7 +409,7 @@ KeyboardUtil.TrackQEMUKeyState = function(next) {
// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
// This information is collected into an object which is passed to the next() function. (one call per event)
KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
export function KeyEventDecoder (modifierState, next) {
"use strict";
function sendAll(evts) {
for (var i = 0; i < evts.length; ++i) {
@ -435,18 +418,18 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
}
function process(evt, type) {
var result = {type: type};
var keyId = KeyboardUtil.getKey(evt);
var keyId = getKey(evt);
if (keyId) {
result.keyId = keyId;
}
var keysym = KeyboardUtil.getKeysym(evt);
var keysym = getKeysym(evt);
var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
// Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
// "special" keys like enter, tab or backspace don't send keypress events,
// and some browsers don't send keypresses at all if a modifier is down
if (keysym && (type !== 'keydown' || KeyboardUtil.nonCharacterKey(evt) || hasModifier)) {
if (keysym && (type !== 'keydown' || nonCharacterKey(evt) || hasModifier)) {
result.keysym = keysym;
}
@ -455,11 +438,11 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
// Should we prevent the browser from handling the event?
// Doing so on a keydown (in most browsers) prevents keypress from being generated
// so only do that if we have to.
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!KeyboardUtil.nonCharacterKey(evt));
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
// If a char modifier is down on a keydown, we need to insert a stall,
// so VerifyCharModifier knows to wait and see if a keypress is comnig
var stall = type === 'keydown' && modifierState.activeCharModifier() && !KeyboardUtil.nonCharacterKey(evt);
var stall = type === 'keydown' && modifierState.activeCharModifier() && !nonCharacterKey(evt);
// if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
var active = modifierState.activeCharModifier();
@ -513,7 +496,7 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not.
// The only way we can distinguish these cases is to wait and see if a keypress event arrives
// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
KeyboardUtil.VerifyCharModifier = function(next) {
export function VerifyCharModifier (next) {
"use strict";
var queue = [];
var timer = null;
@ -570,7 +553,7 @@ KeyboardUtil.VerifyCharModifier = function(next) {
// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
// key repeat events should be merged into a single entry.
// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
KeyboardUtil.TrackKeyState = function(next) {
export function TrackKeyState (next) {
"use strict";
var state = [];
@ -654,7 +637,7 @@ KeyboardUtil.TrackKeyState = function(next) {
// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
// then the modifier must be "undone" before sending the @, and "redone" afterwards.
KeyboardUtil.EscapeModifiers = function(next) {
export function EscapeModifiers (next) {
"use strict";
return function(evt) {
if (evt.type !== 'keydown' || evt.escape === undefined) {
@ -675,5 +658,3 @@ KeyboardUtil.EscapeModifiers = function(next) {
/* jshint shadow: false */
};
};
/* [module] export default KeyboardUtil; */

View File

@ -1,4 +1,4 @@
var XtScancode = {
export default {
"Escape": 0x0001,
"Digit1": 0x0002,
"Digit2": 0x0003,
@ -147,5 +147,3 @@ var XtScancode = {
"LaunchMail": 0xE06C,
"MediaSelect": 0xE06D,
};
/* [module] export default XtScancode */

View File

@ -10,21 +10,23 @@
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
*/
/* [module]
* import Util from "./util";
* import Display from "./display";
* import { Keyboard, Mouse } from "./input/devices"
* import Websock from "./websock"
* import Base64 from "./base64";
* import DES from "./des";
* import KeyTable from "./input/keysym";
* import XtScancode from "./input/xtscancodes";
* import Inflator from "./inflator.mod";
*/
import * as Log from './util/logging.js';
import _ from './util/localization.js';
import { decodeUTF8 } from './util/strings.js';
import { set_defaults, make_properties } from './util/properties.js';
import Display from "./display.js";
import { Keyboard, Mouse } from "./input/devices.js";
import Websock from "./websock.js";
import Base64 from "./base64.js";
import DES from "./des.js";
import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js";
import Inflator from "./inflator.js";
/*jslint white: false, browser: true */
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */
/* [module] export default */ function RFB(defaults) {
export default function RFB(defaults) {
"use strict";
if (!defaults) {
defaults = {};
@ -144,7 +146,7 @@
this._qemuExtKeyEventSupported = false;
// set the default value on user-facing properties
Util.set_defaults(this, defaults, {
set_defaults(this, defaults, {
'target': 'null', // VNC display rendering Canvas object
'focusContainer': document, // DOM element that captures keyboard input
'encrypt': false, // Use TLS/SSL/wss encryption
@ -173,7 +175,7 @@
});
// main setup
Util.Debug(">> RFB.constructor");
Log.Debug(">> RFB.constructor");
// populate encHandlers with bound versions
Object.keys(RFB.encodingHandlers).forEach(function (encName) {
@ -193,7 +195,7 @@
this._display = new Display({target: this._target,
onFlush: this._onFlush.bind(this)});
} catch (exc) {
Util.Error("Display exception: " + exc);
Log.Error("Display exception: " + exc);
throw exc;
}
@ -211,13 +213,13 @@
if ((this._rfb_connection_state === 'connecting') &&
(this._rfb_init_state === '')) {
this._rfb_init_state = 'ProtocolVersion';
Util.Debug("Starting VNC handshake");
Log.Debug("Starting VNC handshake");
} else {
this._fail("Unexpected server connection");
}
}.bind(this));
this._sock.on('close', function (e) {
Util.Warn("WebSocket on-close event");
Log.Warn("WebSocket on-close event");
var msg = "";
if (e.code) {
msg = " (code: " + e.code;
@ -250,21 +252,18 @@
this._sock.off('close');
}.bind(this));
this._sock.on('error', function (e) {
Util.Warn("WebSocket on-error event");
Log.Warn("WebSocket on-error event");
});
this._init_vars();
this._cleanup();
var rmode = this._display.get_render_mode();
Util.Info("Using native WebSockets, render mode: " + rmode);
Log.Info("Using native WebSockets, render mode: " + rmode);
Util.Debug("<< RFB.constructor");
Log.Debug("<< RFB.constructor");
};
(function() {
var _ = Util.Localisation.get;
RFB.prototype = {
// Public methods
connect: function (host, port, password, path) {
@ -297,7 +296,7 @@
sendCtrlAltDel: function () {
if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
Util.Info("Sending Ctrl-Alt-Del");
Log.Info("Sending Ctrl-Alt-Del");
RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 1);
RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 1);
@ -310,7 +309,7 @@
xvpOp: function (ver, op) {
if (this._rfb_xvp_ver < ver) { return false; }
Util.Info("Sending XVP operation " + op + " (version " + ver + ")");
Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
return true;
},
@ -332,10 +331,10 @@
sendKey: function (keysym, down) {
if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
if (typeof down !== 'undefined') {
Util.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
} else {
Util.Info("Sending keysym (down + up): " + keysym);
Log.Info("Sending keysym (down + up): " + keysym);
RFB.messages.keyEvent(this._sock, keysym, 1);
RFB.messages.keyEvent(this._sock, keysym, 0);
}
@ -343,9 +342,7 @@
},
clipboardPasteFrom: function (text) {
if (this._rfb_connection_state !== 'connected' || this._view_only) {
return;
}
if (this._rfb_connection_state !== 'connected' || this._view_only) { return; }
RFB.messages.clientCutText(this._sock, text);
},
@ -371,7 +368,7 @@
// Private methods
_connect: function () {
Util.Debug(">> RFB.connect");
Log.Debug(">> RFB.connect");
this._init_vars();
var uri;
@ -382,7 +379,7 @@
}
uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
Util.Info("connecting to " + uri);
Log.Info("connecting to " + uri);
try {
// WebSocket.onopen transitions to the RFB init states
@ -395,15 +392,15 @@
}
}
Util.Debug("<< RFB.connect");
Log.Debug("<< RFB.connect");
},
_disconnect: function () {
Util.Debug(">> RFB.disconnect");
Log.Debug(">> RFB.disconnect");
this._cleanup();
this._sock.close();
this._print_stats();
Util.Debug("<< RFB.disconnect");
Log.Debug("<< RFB.disconnect");
},
_init_vars: function () {
@ -424,24 +421,24 @@
}
for (i = 0; i < 4; i++) {
this._FBU.zlibs[i] = new Inflator.Inflate();
this._FBU.zlibs[i] = new Inflator();
}
},
_print_stats: function () {
Util.Info("Encoding stats for this connection:");
Log.Info("Encoding stats for this connection:");
var i, s;
for (i = 0; i < this._encodings.length; i++) {
s = this._encStats[this._encodings[i][1]];
if (s[0] + s[1] > 0) {
Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects");
Log.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects");
}
}
Util.Info("Encoding stats since page load:");
Log.Info("Encoding stats since page load:");
for (i = 0; i < this._encodings.length; i++) {
s = this._encStats[this._encodings[i][1]];
Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects");
Log.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects");
}
},
@ -449,7 +446,7 @@
if (!this._view_only) { this._keyboard.ungrab(); }
if (!this._view_only) { this._mouse.ungrab(); }
this._display.defaultCursor();
if (Util.get_logging() !== 'debug') {
if (Log.get_logging() !== 'debug') {
// Show noVNC logo on load and when disconnected, unless in
// debug mode
this._display.clear();
@ -467,13 +464,13 @@
var oldstate = this._rfb_connection_state;
if (state === oldstate) {
Util.Debug("Already in state '" + state + "', ignoring");
Log.Debug("Already in state '" + state + "', ignoring");
return;
}
// The 'disconnected' state is permanent for each RFB object
if (oldstate === 'disconnected') {
Util.Error("Tried changing state of a disconnected RFB object");
Log.Error("Tried changing state of a disconnected RFB object");
return;
}
@ -481,7 +478,7 @@
switch (state) {
case 'connected':
if (oldstate !== 'connecting') {
Util.Error("Bad transition to connected state, " +
Log.Error("Bad transition to connected state, " +
"previous connection state: " + oldstate);
return;
}
@ -489,7 +486,7 @@
case 'disconnected':
if (oldstate !== 'disconnecting') {
Util.Error("Bad transition to disconnected state, " +
Log.Error("Bad transition to disconnected state, " +
"previous connection state: " + oldstate);
return;
}
@ -497,7 +494,7 @@
case 'connecting':
if (oldstate !== '') {
Util.Error("Bad transition to connecting state, " +
Log.Error("Bad transition to connecting state, " +
"previous connection state: " + oldstate);
return;
}
@ -505,14 +502,14 @@
case 'disconnecting':
if (oldstate !== 'connected' && oldstate !== 'connecting') {
Util.Error("Bad transition to disconnecting state, " +
Log.Error("Bad transition to disconnecting state, " +
"previous connection state: " + oldstate);
return;
}
break;
default:
Util.Error("Unknown connection state: " + state);
Log.Error("Unknown connection state: " + state);
return;
}
@ -522,10 +519,10 @@
this._onUpdateState(this, state, oldstate);
var smsg = "New state '" + state + "', was '" + oldstate + "'.";
Util.Debug(smsg);
Log.Debug(smsg);
if (this._disconnTimer && state !== 'disconnecting') {
Util.Debug("Clearing disconnect timer");
Log.Debug("Clearing disconnect timer");
clearTimeout(this._disconnTimer);
this._disconnTimer = null;
@ -572,16 +569,16 @@
}
switch (this._rfb_connection_state) {
case 'disconnecting':
Util.Error("Failed when disconnecting: " + fullmsg);
Log.Error("Failed when disconnecting: " + fullmsg);
break;
case 'connected':
Util.Error("Failed while connected: " + fullmsg);
Log.Error("Failed while connected: " + fullmsg);
break;
case 'connecting':
Util.Error("Failed when connecting: " + fullmsg);
Log.Error("Failed when connecting: " + fullmsg);
break;
default:
Util.Error("RFB failure: " + fullmsg);
Log.Error("RFB failure: " + fullmsg);
break;
}
this._rfb_disconnect_reason = msg; //This is sent to the UI
@ -606,10 +603,10 @@
case 'normal':
case 'warn':
case 'error':
Util.Debug("Notification[" + level + "]:" + msg);
Log.Debug("Notification[" + level + "]:" + msg);
break;
default:
Util.Error("Invalid notification level: " + level);
Log.Error("Invalid notification level: " + level);
return;
}
@ -622,13 +619,13 @@
_handle_message: function () {
if (this._sock.rQlen() === 0) {
Util.Warn("handle_message called on an empty receive queue");
Log.Warn("handle_message called on an empty receive queue");
return;
}
switch (this._rfb_connection_state) {
case 'disconnected':
Util.Error("Got data while disconnected");
Log.Error("Got data while disconnected");
break;
case 'connected':
while (true) {
@ -659,7 +656,7 @@
var keysym = keyevent.keysym;
RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
} else {
Util.Error('Unable to find a xt scancode for code = ' + keyevent.code);
Log.Error('Unable to find a xt scancode for code = ' + keyevent.code);
}
} else {
keysym = keyevent.keysym.keysym;
@ -735,7 +732,7 @@
}
var sversion = this._sock.rQshiftStr(12).substr(4, 7);
Util.Info("Server ProtocolVersion: " + sversion);
Log.Info("Server ProtocolVersion: " + sversion);
var is_repeater = 0;
switch (sversion) {
case "000.000": // UltraVNC repeater
@ -776,12 +773,23 @@
var cversion = "00" + parseInt(this._rfb_version, 10) +
".00" + ((this._rfb_version * 10) % 10);
this._sock.send_string("RFB " + cversion + "\n");
Util.Debug('Sent ProtocolVersion: ' + cversion);
Log.Debug('Sent ProtocolVersion: ' + cversion);
this._rfb_init_state = 'Security';
},
_negotiate_security: function () {
// Polyfill since IE and PhantomJS doesn't have
// TypedArray.includes()
function includes(item, array) {
for (var i = 0; i < array.length; i++) {
if (array[i] === item) {
return true;
}
}
return false;
}
if (this._rfb_version >= 3.7) {
// Server sends supported list, client decides
var num_types = this._sock.rQshift8();
@ -795,18 +803,7 @@
}
var types = this._sock.rQshiftBytes(num_types);
Util.Debug("Server security types: " + types);
// Polyfill since IE and PhantomJS doesn't have
// TypedArray.includes()
function includes(item, array) {
for (var i = 0; i < array.length; i++) {
if (array[i] === item) {
return true;
}
}
return false;
}
Log.Debug("Server security types: " + types);
// Look for each auth in preferred order
this._rfb_auth_scheme = 0;
@ -831,7 +828,7 @@
}
this._rfb_init_state = 'Authentication';
Util.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
return this._init_msg(); // jump to authentication
},
@ -1000,7 +997,7 @@
switch (this._sock.rQshift32()) {
case 0: // OK
this._rfb_init_state = 'ClientInitialisation';
Util.Debug('Authentication OK');
Log.Debug('Authentication OK');
return this._init_msg();
case 1: // failed
if (this._rfb_version >= 3.8) {
@ -1048,7 +1045,7 @@
/* Connection name/title */
var name_length = this._sock.rQshift32();
if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
if (this._rfb_tightvnc) {
if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
@ -1076,7 +1073,7 @@
// NB(directxman12): these are down here so that we don't run them multiple times
// if we backtrack
Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
Log.Info("Screen: " + this._fb_width + "x" + this._fb_height +
", bpp: " + bpp + ", depth: " + depth +
", big_endian: " + big_endian +
", true_color: " + true_color +
@ -1088,22 +1085,22 @@
", blue_shift: " + blue_shift);
if (big_endian !== 0) {
Util.Warn("Server native endian is not little endian");
Log.Warn("Server native endian is not little endian");
}
if (red_shift !== 16) {
Util.Warn("Server native red-shift is not 16");
Log.Warn("Server native red-shift is not 16");
}
if (blue_shift !== 0) {
Util.Warn("Server native blue-shift is not 0");
Log.Warn("Server native blue-shift is not 0");
}
// we're past the point where we could backtrack, so it's safe to call this
this._onDesktopName(this, this._fb_name);
if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
this._true_color = false;
}
@ -1170,7 +1167,7 @@
},
_handle_set_colour_map_msg: function () {
Util.Debug("SetColorMapEntries");
Log.Debug("SetColorMapEntries");
this._sock.rQskip8(); // Padding
var first_colour = this._sock.rQshift16();
@ -1183,15 +1180,14 @@
var blue = parseInt(this._sock.rQshift16() / 256, 10);
this._display.set_colourMap([blue, green, red], first_colour + c);
}
Util.Debug("colourMap: " + this._display.get_colourMap());
Util.Info("Registered " + num_colours + " colourMap entries");
Log.Debug("colourMap: " + this._display.get_colourMap());
Log.Info("Registered " + num_colours + " colourMap entries");
return true;
},
_handle_server_cut_text: function () {
Util.Debug("ServerCutText");
if (this._view_only) { return true; }
Log.Debug("ServerCutText");
if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
this._sock.rQskipBytes(3); // Padding
@ -1199,6 +1195,9 @@
if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
var text = this._sock.rQshiftStr(length);
if (this._view_only) { return true; }
this._onClipboard(this, text);
return true;
@ -1213,7 +1212,7 @@
if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
if (length > 64) {
Util.Warn("Bad payload length (" + length + ") in fence response");
Log.Warn("Bad payload length (" + length + ") in fence response");
length = 64;
}
@ -1255,12 +1254,12 @@
switch (xvp_msg) {
case 0: // XVP_FAIL
Util.Error("Operation Failed");
Log.Error("Operation Failed");
this._notification("XVP Operation Failed", 'error');
break;
case 1: // XVP_INIT
this._rfb_xvp_ver = xvp_ver;
Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
this._onXvpInit(this._rfb_xvp_ver);
break;
default:
@ -1294,7 +1293,7 @@
return this._handle_set_colour_map_msg();
case 2: // Bell
Util.Debug("Bell");
Log.Debug("Bell");
this._onBell(this);
return true;
@ -1308,7 +1307,7 @@
if (first) {
this._enabledContinuousUpdates = true;
this._updateContinuousUpdates();
Util.Info("Enabling continuous updates.");
Log.Info("Enabling continuous updates.");
} else {
// FIXME: We need to send a framebufferupdaterequest here
// if we add support for turning off continuous updates
@ -1323,7 +1322,7 @@
default:
this._fail("Unexpected server message", "Type:" + msg_type);
Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
return true;
}
},
@ -1348,7 +1347,7 @@
this._timing.cur_fbu = 0;
if (this._timing.fbu_rt_start > 0) {
now = (new Date()).getTime();
Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
Log.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
}
// Make sure the previous frame is fully rendered first
@ -1408,7 +1407,7 @@
this._timing.fbu_rt_start > 0) {
this._timing.full_fbu_total += this._timing.cur_fbu;
this._timing.full_fbu_cnt++;
Util.Info("Timing of full FBU, curr: " +
Log.Info("Timing of full FBU, curr: " +
this._timing.cur_fbu + ", total: " +
this._timing.full_fbu_total + ", cnt: " +
this._timing.full_fbu_cnt + ", avg: " +
@ -1419,7 +1418,7 @@
var fbu_rt_diff = now - this._timing.fbu_rt_start;
this._timing.fbu_rt_total += fbu_rt_diff;
this._timing.fbu_rt_cnt++;
Util.Info("full FBU round-trip, cur: " +
Log.Info("full FBU round-trip, cur: " +
fbu_rt_diff + ", total: " +
this._timing.fbu_rt_total + ", cnt: " +
this._timing.fbu_rt_cnt + ", avg: " +
@ -1450,7 +1449,7 @@
}
};
Util.make_properties(RFB, [
make_properties(RFB, [
['target', 'wo', 'dom'], // VNC display rendering Canvas object
['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
@ -1486,7 +1485,7 @@
if (this._display.get_cursor_uri()) {
this._local_cursor = true;
} else {
Util.Warn("Browser does not support local cursor");
Log.Warn("Browser does not support local cursor");
this._display.disableLocalCursor();
}
}
@ -1749,10 +1748,10 @@
var i, j = offset + 4, cnt = 0;
for (i = 0; i < encodings.length; i++) {
if (encodings[i][0] === "Cursor" && !local_cursor) {
Util.Debug("Skipping Cursor pseudo-encoding");
Log.Debug("Skipping Cursor pseudo-encoding");
} else if (encodings[i][0] === "TIGHT" && !true_color) {
// TODO: remove this when we have tight+non-true-color
Util.Warn("Skipping tight as it is only supported with true color");
Log.Warn("Skipping tight as it is only supported with true color");
} else {
var enc = encodings[i][1];
buff[j] = enc >> 24;
@ -1937,7 +1936,7 @@
if (this._FBU.subencoding === 0) {
if (this._FBU.lastsubencoding & 0x01) {
// Weird: ignore blanks are RAW
Util.Debug(" Ignoring blank after RAW");
Log.Debug(" Ignoring blank after RAW");
} else {
this._display.fillRect(x, y, w, h, this._FBU.background);
}
@ -2049,14 +2048,14 @@
for (var i = 0; i < 4; i++) {
if ((resetStreams >> i) & 1) {
this._FBU.zlibs[i].reset();
Util.Info("Reset zlib stream " + i);
Log.Info("Reset zlib stream " + i);
}
}
//var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
var uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected);
/*if (uncompressed.status !== 0) {
Util.Error("Invalid data in zlib stream");
Log.Error("Invalid data in zlib stream");
}*/
//return uncompressed.data;
@ -2432,7 +2431,7 @@
},
Cursor: function () {
Util.Debug(">> set_cursor");
Log.Debug(">> set_cursor");
var x = this._FBU.x; // hotspot-x
var y = this._FBU.y; // hotspot-y
var w = this._FBU.width;
@ -2451,7 +2450,7 @@
this._FBU.bytes = 0;
this._FBU.rects--;
Util.Debug("<< set_cursor");
Log.Debug("<< set_cursor");
return true;
},
@ -2466,11 +2465,10 @@
},
JPEG_quality_lo: function () {
Util.Error("Server sent jpeg_quality pseudo-encoding");
Log.Error("Server sent jpeg_quality pseudo-encoding");
},
compress_lo: function () {
Util.Error("Server sent compress level pseudo-encoding");
Log.Error("Server sent compress level pseudo-encoding");
}
};
})();

View File

@ -1,621 +0,0 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/* jshint white: false, nonstandard: true */
/*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */
var Util = {};
/*
* ------------------------------------------------------
* Namespaced in Util
* ------------------------------------------------------
*/
/*
* Logging/debug routines
*/
Util._log_level = 'warn';
Util.init_logging = function (level) {
"use strict";
if (typeof level === 'undefined') {
level = Util._log_level;
} else {
Util._log_level = level;
}
Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
if (typeof window.console !== "undefined") {
/* jshint -W086 */
switch (level) {
case 'debug':
Util.Debug = console.debug.bind(window.console);
case 'info':
Util.Info = console.info.bind(window.console);
case 'warn':
Util.Warn = console.warn.bind(window.console);
case 'error':
Util.Error = console.error.bind(window.console);
case 'none':
break;
default:
throw new Error("invalid logging type '" + level + "'");
}
/* jshint +W086 */
}
};
Util.get_logging = function () {
return Util._log_level;
};
// Initialize logging level
Util.init_logging();
Util.make_property = function (proto, name, mode, type) {
"use strict";
var getter;
if (type === 'arr') {
getter = function (idx) {
if (typeof idx !== 'undefined') {
return this['_' + name][idx];
} else {
return this['_' + name];
}
};
} else {
getter = function () {
return this['_' + name];
};
}
var make_setter = function (process_val) {
if (process_val) {
return function (val, idx) {
if (typeof idx !== 'undefined') {
this['_' + name][idx] = process_val(val);
} else {
this['_' + name] = process_val(val);
}
};
} else {
return function (val, idx) {
if (typeof idx !== 'undefined') {
this['_' + name][idx] = val;
} else {
this['_' + name] = val;
}
};
}
};
var setter;
if (type === 'bool') {
setter = make_setter(function (val) {
if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
return false;
} else {
return true;
}
});
} else if (type === 'int') {
setter = make_setter(function (val) { return parseInt(val, 10); });
} else if (type === 'float') {
setter = make_setter(parseFloat);
} else if (type === 'str') {
setter = make_setter(String);
} else if (type === 'func') {
setter = make_setter(function (val) {
if (!val) {
return function () {};
} else {
return val;
}
});
} else if (type === 'arr' || type === 'dom' || type == 'raw') {
setter = make_setter();
} else {
throw new Error('Unknown property type ' + type); // some sanity checking
}
// set the getter
if (typeof proto['get_' + name] === 'undefined') {
proto['get_' + name] = getter;
}
// set the setter if needed
if (typeof proto['set_' + name] === 'undefined') {
if (mode === 'rw') {
proto['set_' + name] = setter;
} else if (mode === 'wo') {
proto['set_' + name] = function (val, idx) {
if (typeof this['_' + name] !== 'undefined') {
throw new Error(name + " can only be set once");
}
setter.call(this, val, idx);
};
}
}
// make a special setter that we can use in set defaults
proto['_raw_set_' + name] = function (val, idx) {
setter.call(this, val, idx);
//delete this['_init_set_' + name]; // remove it after use
};
};
Util.make_properties = function (constructor, arr) {
"use strict";
for (var i = 0; i < arr.length; i++) {
Util.make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]);
}
};
Util.set_defaults = function (obj, conf, defaults) {
var defaults_keys = Object.keys(defaults);
var conf_keys = Object.keys(conf);
var keys_obj = {};
var i;
for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; }
for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; }
var keys = Object.keys(keys_obj);
for (i = 0; i < keys.length; i++) {
var setter = obj['_raw_set_' + keys[i]];
if (!setter) {
Util.Warn('Invalid property ' + keys[i]);
continue;
}
if (keys[i] in conf) {
setter.call(obj, conf[keys[i]]);
} else {
setter.call(obj, defaults[keys[i]]);
}
}
};
/*
* Decode from UTF-8
*/
Util.decodeUTF8 = function (utf8string) {
"use strict";
return decodeURIComponent(escape(utf8string));
};
/*
* Cross-browser routines
*/
Util.getPointerEvent = function (e) {
return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e;
};
Util.stopEvent = function (e) {
e.stopPropagation();
e.preventDefault();
};
// Touch detection
Util.isTouchDevice = ('ontouchstart' in document.documentElement) ||
// requried for Chrome debugger
(document.ontouchstart !== undefined) ||
// required for MS Surface
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0);
window.addEventListener('touchstart', function onFirstTouch() {
Util.isTouchDevice = true;
window.removeEventListener('touchstart', onFirstTouch, false);
}, false);
Util._cursor_uris_supported = null;
Util.browserSupportsCursorURIs = function () {
if (Util._cursor_uris_supported === null) {
try {
var target = document.createElement('canvas');
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
if (target.style.cursor) {
Util.Info("Data URI scheme cursor supported");
Util._cursor_uris_supported = true;
} else {
Util.Warn("Data URI scheme cursor not supported");
Util._cursor_uris_supported = false;
}
} catch (exc) {
Util.Error("Data URI scheme cursor test exception: " + exc);
Util._cursor_uris_supported = false;
}
}
return Util._cursor_uris_supported;
};
// Set browser engine versions. Based on mootools.
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
(function () {
"use strict";
// 'presto': (function () { return (!window.opera) ? false : true; }()),
var detectPresto = function () {
return !!window.opera;
};
// 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
var detectTrident = function () {
if (!window.ActiveXObject) {
return false;
} else {
if (window.XMLHttpRequest) {
return (document.querySelectorAll) ? 6 : 5;
} else {
return 4;
}
}
};
// 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
var detectInitialWebkit = function () {
try {
if (navigator.taintEnabled) {
return false;
} else {
if (Util.Features.xpath) {
return (Util.Features.query) ? 525 : 420;
} else {
return 419;
}
}
} catch (e) {
return false;
}
};
var detectActualWebkit = function (initial_ver) {
var re = /WebKit\/([0-9\.]*) /;
var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1];
return parseFloat(str_ver, 10);
};
// 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
var detectGecko = function () {
/* jshint -W041 */
if (!document.getBoxObjectFor && window.mozInnerScreenX == null) {
return false;
} else {
return (document.getElementsByClassName) ? 19 : 18;
}
/* jshint +W041 */
};
Util.Engine = {
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
//'presto': (function() {
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
'presto': detectPresto(),
'trident': detectTrident(),
'webkit': detectInitialWebkit(),
'gecko': detectGecko()
};
if (Util.Engine.webkit) {
// Extract actual webkit version if available
Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit);
}
})();
Util.Flash = (function () {
"use strict";
var v, version;
try {
v = navigator.plugins['Shockwave Flash'].description;
} catch (err1) {
try {
v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
} catch (err2) {
v = '0 r0';
}
}
version = v.match(/\d+/g);
return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
}());
Util.Localisation = {
// Currently configured language
language: 'en',
// Configure suitable language based on user preferences
setup: function (supportedLanguages) {
var userLanguages;
Util.Localisation.language = 'en'; // Default: US English
/*
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
* Fall back to navigator.language for other browsers
*/
if (typeof window.navigator.languages == 'object') {
userLanguages = window.navigator.languages;
} else {
userLanguages = [navigator.language || navigator.userLanguage];
}
for (var i = 0;i < userLanguages.length;i++) {
var userLang = userLanguages[i];
userLang = userLang.toLowerCase();
userLang = userLang.replace("_", "-");
userLang = userLang.split("-");
// Built-in default?
if ((userLang[0] === 'en') &&
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
return;
}
// First pass: perfect match
for (var j = 0;j < supportedLanguages.length;j++) {
var supLang = supportedLanguages[j];
supLang = supLang.toLowerCase();
supLang = supLang.replace("_", "-");
supLang = supLang.split("-");
if (userLang[0] !== supLang[0])
continue;
if (userLang[1] !== supLang[1])
continue;
Util.Localisation.language = supportedLanguages[j];
return;
}
// Second pass: fallback
for (var j = 0;j < supportedLanguages.length;j++) {
supLang = supportedLanguages[j];
supLang = supLang.toLowerCase();
supLang = supLang.replace("_", "-");
supLang = supLang.split("-");
if (userLang[0] !== supLang[0])
continue;
if (supLang[1] !== undefined)
continue;
Util.Localisation.language = supportedLanguages[j];
return;
}
}
},
// Retrieve localised text
get: function (id) {
if (typeof Language !== 'undefined' && Language[id]) {
return Language[id];
} else {
return id;
}
},
// Traverses the DOM and translates relevant fields
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
translateDOM: function () {
function process(elem, enabled) {
function isAnyOf(searchElement, items) {
return items.indexOf(searchElement) !== -1;
}
function translateAttribute(elem, attr) {
var str = elem.getAttribute(attr);
str = Util.Localisation.get(str);
elem.setAttribute(attr, str);
}
function translateTextNode(node) {
var str = node.data.trim();
str = Util.Localisation.get(str);
node.data = str;
}
if (elem.hasAttribute("translate")) {
if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
enabled = true;
} else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
enabled = false;
}
}
if (enabled) {
if (elem.hasAttribute("abbr") &&
elem.tagName === "TH") {
translateAttribute(elem, "abbr");
}
if (elem.hasAttribute("alt") &&
isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
translateAttribute(elem, "alt");
}
if (elem.hasAttribute("download") &&
isAnyOf(elem.tagName, ["A", "AREA"])) {
translateAttribute(elem, "download");
}
if (elem.hasAttribute("label") &&
isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
"OPTION", "TRACK"])) {
translateAttribute(elem, "label");
}
// FIXME: Should update "lang"
if (elem.hasAttribute("placeholder") &&
isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) {
translateAttribute(elem, "placeholder");
}
if (elem.hasAttribute("title")) {
translateAttribute(elem, "title");
}
if (elem.hasAttribute("value") &&
elem.tagName === "INPUT" &&
isAnyOf(elem.getAttribute("type"), ["reset", "button"])) {
translateAttribute(elem, "value");
}
}
for (var i = 0;i < elem.childNodes.length;i++) {
node = elem.childNodes[i];
if (node.nodeType === node.ELEMENT_NODE) {
process(node, enabled);
} else if (node.nodeType === node.TEXT_NODE && enabled) {
translateTextNode(node);
}
}
}
process(document.body, true);
},
};
// Emulate Element.setCapture() when not supported
Util._captureRecursion = false;
Util._captureProxy = function (e) {
// Recursion protection as we'll see our own event
if (Util._captureRecursion) return;
// Clone the event as we cannot dispatch an already dispatched event
var newEv = new e.constructor(e.type, e);
Util._captureRecursion = true;
Util._captureElem.dispatchEvent(newEv);
Util._captureRecursion = false;
// Avoid double events
e.stopPropagation();
// Respect the wishes of the redirected event handlers
if (newEv.defaultPrevented) {
e.preventDefault();
}
// Implicitly release the capture on button release
if ((e.type === "mouseup") || (e.type === "touchend")) {
Util.releaseCapture();
}
};
// Follow cursor style of target element
Util._captureElemChanged = function() {
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
captureElem.style.cursor = window.getComputedStyle(Util._captureElem).cursor;
};
Util._captureObserver = new MutationObserver(Util._captureElemChanged);
Util._captureIndex = 0;
Util.setCapture = function (elem) {
if (elem.setCapture) {
elem.setCapture();
// IE releases capture on 'click' events which might not trigger
elem.addEventListener('mouseup', Util.releaseCapture);
elem.addEventListener('touchend', Util.releaseCapture);
} else {
// Release any existing capture in case this method is
// called multiple times without coordination
Util.releaseCapture();
// Safari on iOS 9 has a broken constructor for TouchEvent.
// We are fine in this case however, since Safari seems to
// have some sort of implicit setCapture magic anyway.
if (window.TouchEvent !== undefined) {
try {
new TouchEvent("touchstart");
} catch (TypeError) {
return;
}
}
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
if (captureElem === null) {
captureElem = document.createElement("div");
captureElem.id = "noVNC_mouse_capture_elem";
captureElem.style.position = "fixed";
captureElem.style.top = "0px";
captureElem.style.left = "0px";
captureElem.style.width = "100%";
captureElem.style.height = "100%";
captureElem.style.zIndex = 10000;
captureElem.style.display = "none";
document.body.appendChild(captureElem);
// This is to make sure callers don't get confused by having
// our blocking element as the target
captureElem.addEventListener('contextmenu', Util._captureProxy);
captureElem.addEventListener('mousemove', Util._captureProxy);
captureElem.addEventListener('mouseup', Util._captureProxy);
captureElem.addEventListener('touchmove', Util._captureProxy);
captureElem.addEventListener('touchend', Util._captureProxy);
}
Util._captureElem = elem;
Util._captureIndex++;
// Track cursor and get initial cursor
Util._captureObserver.observe(elem, {attributes:true});
Util._captureElemChanged();
captureElem.style.display = null;
// We listen to events on window in order to keep tracking if it
// happens to leave the viewport
window.addEventListener('mousemove', Util._captureProxy);
window.addEventListener('mouseup', Util._captureProxy);
window.addEventListener('touchmove', Util._captureProxy);
window.addEventListener('touchend', Util._captureProxy);
}
};
Util.releaseCapture = function () {
if (document.releaseCapture) {
document.releaseCapture();
} else {
if (!Util._captureElem) {
return;
}
// There might be events already queued, so we need to wait for
// them to flush. E.g. contextmenu in Microsoft Edge
window.setTimeout(function(expected) {
// Only clear it if it's the expected grab (i.e. no one
// else has initiated a new grab)
if (Util._captureIndex === expected) {
Util._captureElem = null;
}
}, 0, Util._captureIndex);
Util._captureObserver.disconnect();
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
captureElem.style.display = "none";
window.removeEventListener('mousemove', Util._captureProxy);
window.removeEventListener('mouseup', Util._captureProxy);
window.removeEventListener('touchmove', Util._captureProxy);
window.removeEventListener('touchend', Util._captureProxy);
}
};
/* [module] export default Util; */

120
core/util/browsers.js Normal file
View File

@ -0,0 +1,120 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
import * as Log from './logging.js';
// Set browser engine versions. Based on mootools.
const Features = {xpath: !!(document.evaluate), query: !!(document.querySelector)};
// 'presto': (function () { return (!window.opera) ? false : true; }()),
var detectPresto = function () {
return !!window.opera;
};
// 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
var detectTrident = function () {
if (!window.ActiveXObject) {
return false;
} else {
if (window.XMLHttpRequest) {
return (document.querySelectorAll) ? 6 : 5;
} else {
return 4;
}
}
};
// 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Features.xpath) ? ((Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
var detectInitialWebkit = function () {
try {
if (navigator.taintEnabled) {
return false;
} else {
if (Features.xpath) {
return (Features.query) ? 525 : 420;
} else {
return 419;
}
}
} catch (e) {
return false;
}
};
var detectActualWebkit = function (initial_ver) {
var re = /WebKit\/([0-9\.]*) /;
var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1];
return parseFloat(str_ver, 10);
};
// 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
var detectGecko = function () {
/* jshint -W041 */
if (!document.getBoxObjectFor && window.mozInnerScreenX == null) {
return false;
} else {
return (document.getElementsByClassName) ? 19 : 18;
}
/* jshint +W041 */
};
const isWebkitInitial = detectInitialWebkit();
export const Engine = {
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
//'presto': (function() {
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
'presto': detectPresto(),
'trident': detectTrident(),
'webkit': isWebkitInitial ? detectActualWebkit(isWebkitInitial) : false,
'gecko': detectGecko()
};
// Touch detection
export var isTouchDevice = ('ontouchstart' in document.documentElement) ||
// requried for Chrome debugger
(document.ontouchstart !== undefined) ||
// required for MS Surface
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0);
window.addEventListener('touchstart', function onFirstTouch() {
isTouchDevice = true;
window.removeEventListener('touchstart', onFirstTouch, false);
}, false);
var _cursor_uris_supported = null;
export function browserSupportsCursorURIs () {
if (_cursor_uris_supported === null) {
try {
var target = document.createElement('canvas');
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
if (target.style.cursor) {
Log.Info("Data URI scheme cursor supported");
_cursor_uris_supported = true;
} else {
Log.Warn("Data URI scheme cursor not supported");
_cursor_uris_supported = false;
}
} catch (exc) {
Log.Error("Data URI scheme cursor test exception: " + exc);
_cursor_uris_supported = false;
}
}
return _cursor_uris_supported;
};
export function _forceCursorURIs(enabled) {
if (enabled === undefined || enabled) {
_cursor_uris_supported = true;
} else {
_cursor_uris_supported = false;
}
}

161
core/util/events.js Normal file
View File

@ -0,0 +1,161 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*
* Cross-browser event and position routines
*/
import * as Log from './logging.js';
export function getPointerEvent (e) {
return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e;
};
export function stopEvent (e) {
e.stopPropagation();
e.preventDefault();
};
// Emulate Element.setCapture() when not supported
var _captureRecursion = false;
var _captureElem = null;
const _captureProxy = function (e) {
// Recursion protection as we'll see our own event
if (_captureRecursion) return;
// Clone the event as we cannot dispatch an already dispatched event
var newEv = new e.constructor(e.type, e);
_captureRecursion = true;
_captureElem.dispatchEvent(newEv);
_captureRecursion = false;
// Avoid double events
e.stopPropagation();
// Respect the wishes of the redirected event handlers
if (newEv.defaultPrevented) {
e.preventDefault();
}
// Implicitly release the capture on button release
if ((e.type === "mouseup") || (e.type === "touchend")) {
releaseCapture();
}
};
// Follow cursor style of target element
const _captureElemChanged = function() {
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
};
const _captureObserver = new MutationObserver(_captureElemChanged);
var _captureIndex = 0;
export function setCapture (elem) {
if (elem.setCapture) {
elem.setCapture();
// IE releases capture on 'click' events which might not trigger
elem.addEventListener('mouseup', releaseCapture);
elem.addEventListener('touchend', releaseCapture);
} else {
// Release any existing capture in case this method is
// called multiple times without coordination
releaseCapture();
// Safari on iOS 9 has a broken constructor for TouchEvent.
// We are fine in this case however, since Safari seems to
// have some sort of implicit setCapture magic anyway.
if (window.TouchEvent !== undefined) {
try {
new TouchEvent("touchstart");
} catch (TypeError) {
return;
}
}
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
if (captureElem === null) {
captureElem = document.createElement("div");
captureElem.id = "noVNC_mouse_capture_elem";
captureElem.style.position = "fixed";
captureElem.style.top = "0px";
captureElem.style.left = "0px";
captureElem.style.width = "100%";
captureElem.style.height = "100%";
captureElem.style.zIndex = 10000;
captureElem.style.display = "none";
document.body.appendChild(captureElem);
// This is to make sure callers don't get confused by having
// our blocking element as the target
captureElem.addEventListener('contextmenu', _captureProxy);
captureElem.addEventListener('mousemove', _captureProxy);
captureElem.addEventListener('mouseup', _captureProxy);
captureElem.addEventListener('touchmove', _captureProxy);
captureElem.addEventListener('touchend', _captureProxy);
}
_captureElem = elem;
_captureIndex++;
// Track cursor and get initial cursor
_captureObserver.observe(elem, {attributes:true});
_captureElemChanged();
captureElem.style.display = null;
// We listen to events on window in order to keep tracking if it
// happens to leave the viewport
window.addEventListener('mousemove', _captureProxy);
window.addEventListener('mouseup', _captureProxy);
window.addEventListener('touchmove', _captureProxy);
window.addEventListener('touchend', _captureProxy);
}
};
export function releaseCapture () {
if (document.releaseCapture) {
document.releaseCapture();
} else {
if (!_captureElem) {
return;
}
// There might be events already queued, so we need to wait for
// them to flush. E.g. contextmenu in Microsoft Edge
window.setTimeout(function(expected) {
// Only clear it if it's the expected grab (i.e. no one
// else has initiated a new grab)
if (_captureIndex === expected) {
_captureElem = null;
}
}, 0, _captureIndex);
_captureObserver.disconnect();
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
captureElem.style.display = "none";
window.removeEventListener('mousemove', _captureProxy);
window.removeEventListener('mouseup', _captureProxy);
window.removeEventListener('touchmove', _captureProxy);
window.removeEventListener('touchend', _captureProxy);
}
};

170
core/util/localization.js Normal file
View File

@ -0,0 +1,170 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*
* Localization Utilities
*/
export function Localizer() {
// Currently configured language
this.language = 'en';
// Current dictionary of translations
this.dictionary = undefined;
}
Localizer.prototype = {
// Configure suitable language based on user preferences
setup: function (supportedLanguages) {
var userLanguages;
this.language = 'en'; // Default: US English
/*
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
* Fall back to navigator.language for other browsers
*/
if (typeof window.navigator.languages == 'object') {
userLanguages = window.navigator.languages;
} else {
userLanguages = [navigator.language || navigator.userLanguage];
}
for (var i = 0;i < userLanguages.length;i++) {
var userLang = userLanguages[i];
userLang = userLang.toLowerCase();
userLang = userLang.replace("_", "-");
userLang = userLang.split("-");
// Built-in default?
if ((userLang[0] === 'en') &&
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
return;
}
// First pass: perfect match
for (var j = 0;j < supportedLanguages.length;j++) {
var supLang = supportedLanguages[j];
supLang = supLang.toLowerCase();
supLang = supLang.replace("_", "-");
supLang = supLang.split("-");
if (userLang[0] !== supLang[0])
continue;
if (userLang[1] !== supLang[1])
continue;
this.language = supportedLanguages[j];
return;
}
// Second pass: fallback
for (var j = 0;j < supportedLanguages.length;j++) {
supLang = supportedLanguages[j];
supLang = supLang.toLowerCase();
supLang = supLang.replace("_", "-");
supLang = supLang.split("-");
if (userLang[0] !== supLang[0])
continue;
if (supLang[1] !== undefined)
continue;
this.language = supportedLanguages[j];
return;
}
}
},
// Retrieve localised text
get: function (id) {
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
return this.dictionary[id];
} else {
return id;
}
},
// Traverses the DOM and translates relevant fields
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
translateDOM: function () {
var self = this;
function process(elem, enabled) {
function isAnyOf(searchElement, items) {
return items.indexOf(searchElement) !== -1;
}
function translateAttribute(elem, attr) {
var str = elem.getAttribute(attr);
str = self.get(str);
elem.setAttribute(attr, str);
}
function translateTextNode(node) {
var str = node.data.trim();
str = self.get(str);
node.data = str;
}
if (elem.hasAttribute("translate")) {
if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
enabled = true;
} else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
enabled = false;
}
}
if (enabled) {
if (elem.hasAttribute("abbr") &&
elem.tagName === "TH") {
translateAttribute(elem, "abbr");
}
if (elem.hasAttribute("alt") &&
isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
translateAttribute(elem, "alt");
}
if (elem.hasAttribute("download") &&
isAnyOf(elem.tagName, ["A", "AREA"])) {
translateAttribute(elem, "download");
}
if (elem.hasAttribute("label") &&
isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
"OPTION", "TRACK"])) {
translateAttribute(elem, "label");
}
// FIXME: Should update "lang"
if (elem.hasAttribute("placeholder") &&
isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) {
translateAttribute(elem, "placeholder");
}
if (elem.hasAttribute("title")) {
translateAttribute(elem, "title");
}
if (elem.hasAttribute("value") &&
elem.tagName === "INPUT" &&
isAnyOf(elem.getAttribute("type"), ["reset", "button"])) {
translateAttribute(elem, "value");
}
}
for (var i = 0;i < elem.childNodes.length;i++) {
let node = elem.childNodes[i];
if (node.nodeType === node.ELEMENT_NODE) {
process(node, enabled);
} else if (node.nodeType === node.TEXT_NODE && enabled) {
translateTextNode(node);
}
}
}
process(document.body, true);
},
}
export const l10n = new Localizer();
export default l10n.get.bind(l10n);

53
core/util/logging.js Normal file
View File

@ -0,0 +1,53 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*
* Logging/debug routines
*/
var _log_level = 'warn';
var Debug = function (msg) {};
var Info = function (msg) {};
var Warn = function (msg) {};
var Error = function (msg) {};
export function init_logging (level) {
if (typeof level === 'undefined') {
level = _log_level;
} else {
_log_level = level;
}
Debug = Info = Warn = Error = function (msg) {};
if (typeof window.console !== "undefined") {
/* jshint -W086 */
switch (level) {
case 'debug':
Debug = console.debug.bind(window.console);
case 'info':
Info = console.info.bind(window.console);
case 'warn':
Warn = console.warn.bind(window.console);
case 'error':
Error = console.error.bind(window.console);
case 'none':
break;
default:
throw new Error("invalid logging type '" + level + "'");
}
/* jshint +W086 */
}
};
export function get_logging () {
return _log_level;
};
export { Debug, Info, Warn, Error };
// Initialize logging level
init_logging();

138
core/util/properties.js Normal file
View File

@ -0,0 +1,138 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*
* Getter/Setter Creation Utilities
*/
import * as Log from './logging.js';
function make_property (proto, name, mode, type) {
"use strict";
var getter;
if (type === 'arr') {
getter = function (idx) {
if (typeof idx !== 'undefined') {
return this['_' + name][idx];
} else {
return this['_' + name];
}
};
} else {
getter = function () {
return this['_' + name];
};
}
var make_setter = function (process_val) {
if (process_val) {
return function (val, idx) {
if (typeof idx !== 'undefined') {
this['_' + name][idx] = process_val(val);
} else {
this['_' + name] = process_val(val);
}
};
} else {
return function (val, idx) {
if (typeof idx !== 'undefined') {
this['_' + name][idx] = val;
} else {
this['_' + name] = val;
}
};
}
};
var setter;
if (type === 'bool') {
setter = make_setter(function (val) {
if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
return false;
} else {
return true;
}
});
} else if (type === 'int') {
setter = make_setter(function (val) { return parseInt(val, 10); });
} else if (type === 'float') {
setter = make_setter(parseFloat);
} else if (type === 'str') {
setter = make_setter(String);
} else if (type === 'func') {
setter = make_setter(function (val) {
if (!val) {
return function () {};
} else {
return val;
}
});
} else if (type === 'arr' || type === 'dom' || type == 'raw') {
setter = make_setter();
} else {
throw new Error('Unknown property type ' + type); // some sanity checking
}
// set the getter
if (typeof proto['get_' + name] === 'undefined') {
proto['get_' + name] = getter;
}
// set the setter if needed
if (typeof proto['set_' + name] === 'undefined') {
if (mode === 'rw') {
proto['set_' + name] = setter;
} else if (mode === 'wo') {
proto['set_' + name] = function (val, idx) {
if (typeof this['_' + name] !== 'undefined') {
throw new Error(name + " can only be set once");
}
setter.call(this, val, idx);
};
}
}
// make a special setter that we can use in set defaults
proto['_raw_set_' + name] = function (val, idx) {
setter.call(this, val, idx);
//delete this['_init_set_' + name]; // remove it after use
};
};
export function make_properties (constructor, arr) {
"use strict";
for (var i = 0; i < arr.length; i++) {
make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]);
}
};
export function set_defaults (obj, conf, defaults) {
var defaults_keys = Object.keys(defaults);
var conf_keys = Object.keys(conf);
var keys_obj = {};
var i;
for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; }
for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; }
var keys = Object.keys(keys_obj);
for (i = 0; i < keys.length; i++) {
var setter = obj['_raw_set_' + keys[i]];
if (!setter) {
Log.Warn('Invalid property ' + keys[i]);
continue;
}
if (keys[i] in conf) {
setter.call(obj, conf[keys[i]]);
} else {
setter.call(obj, defaults[keys[i]]);
}
}
};

15
core/util/strings.js Normal file
View File

@ -0,0 +1,15 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*
* Decode from UTF-8
*/
export function decodeUTF8 (utf8string) {
"use strict";
return decodeURIComponent(escape(utf8string));
};

View File

@ -12,14 +12,12 @@
* read binary data off of the receive queue.
*/
/* [module]
* import Util from "./util";
*/
import * as Log from './util/logging.js';
/*jslint browser: true, bitwise: true */
/*global Util*/
/* [module] export default */ function Websock() {
export default function Websock() {
"use strict";
this._websocket = null; // WebSocket object
@ -44,8 +42,6 @@
};
};
(function () {
"use strict";
// this has performance issues in some versions Chromium, and
// doesn't gain a tremendous amount of performance increase in Firefox
// at the moment. It may be valuable to turn it on in the future.
@ -173,7 +169,7 @@
flush: function () {
if (this._websocket.bufferedAmount !== 0) {
Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
Log.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
}
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
@ -223,23 +219,23 @@
this._websocket.onmessage = this._recv_message.bind(this);
this._websocket.onopen = (function () {
Util.Debug('>> WebSock.onopen');
Log.Debug('>> WebSock.onopen');
if (this._websocket.protocol) {
Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
Log.Info("Server choose sub-protocol: " + this._websocket.protocol);
}
this._eventHandlers.open();
Util.Debug("<< WebSock.onopen");
Log.Debug("<< WebSock.onopen");
}).bind(this);
this._websocket.onclose = (function (e) {
Util.Debug(">> WebSock.onclose");
Log.Debug(">> WebSock.onclose");
this._eventHandlers.close(e);
Util.Debug("<< WebSock.onclose");
Log.Debug("<< WebSock.onclose");
}).bind(this);
this._websocket.onerror = (function (e) {
Util.Debug(">> WebSock.onerror: " + e);
Log.Debug(">> WebSock.onerror: " + e);
this._eventHandlers.error(e);
Util.Debug("<< WebSock.onerror: " + e);
Log.Debug("<< WebSock.onerror: " + e);
}).bind(this);
},
@ -247,7 +243,7 @@
if (this._websocket) {
if ((this._websocket.readyState === WebSocket.OPEN) ||
(this._websocket.readyState === WebSocket.CONNECTING)) {
Util.Info("Closing WebSocket connection");
Log.Info("Closing WebSocket connection");
this._websocket.close();
}
@ -322,7 +318,7 @@
this._expand_compact_rQ();
}
} else {
Util.Debug("Ignoring empty message");
Log.Debug("Ignoring empty message");
}
} catch (exc) {
var exception_str = "";
@ -340,9 +336,9 @@
}
if (exception_str.length > 0) {
Util.Error("recv_message, caught exception: " + exception_str);
Log.Error("recv_message, caught exception: " + exception_str);
} else {
Util.Error("recv_message, caught exception: " + exc);
Log.Error("recv_message, caught exception: " + exc);
}
if (typeof exc.name !== 'undefined') {
@ -353,4 +349,3 @@
}
}
};
})();

View File

@ -1,56 +1,6 @@
// Karma configuration
module.exports = function(config) {
/*var customLaunchers = {
sl_chrome_win7: {
base: 'SauceLabs',
browserName: 'chrome',
platform: 'Windows 7'
},
sl_firefox30_linux: {
base: 'SauceLabs',
browserName: 'firefox',
version: '30',
platform: 'Linux'
},
sl_firefox26_linux: {
base: 'SauceLabs',
browserName: 'firefox',
version: 26,
platform: 'Linux'
},
sl_windows7_ie10: {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 7',
version: '10'
},
sl_windows81_ie11: {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
sl_osxmavericks_safari7: {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
},
sl_osxmtnlion_safari6: {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.8',
version: '6'
}
};*/
var customLaunchers = {};
var browsers = [];
var useSauce = false;
@ -93,7 +43,8 @@ module.exports = function(config) {
browsers = Object.keys(customLaunchers);
} else {
useSauce = false;
browsers = ['PhantomJS'];
//browsers = ['PhantomJS'];
browsers = [];
}
var my_conf = {
@ -103,39 +54,30 @@ module.exports = function(config) {
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'sinon', 'chai', 'sinon-chai'],
frameworks: ['requirejs', 'mocha', 'chai'],
// list of files / patterns to load in the browser (loaded in order)
files: [
'tests/fake.*.js',
'tests/assertions.js',
'core/util.js', // load first to avoid issues, since methods are called immediately
//'../core/*.js',
'core/base64.js',
'core/input/keysym.js',
'core/input/keysymdef.js',
'core/input/xtscancodes.js',
'core/input/util.js',
'core/input/devices.js',
'core/websock.js',
'core/rfb.js',
'core/des.js',
'core/display.js',
'core/inflator.js',
'tests/test.*.js'
{ pattern: 'vendor/sinon.js', included: false },
{ pattern: 'node_modules/sinon-chai/lib/sinon-chai.js', included: false },
{ pattern: 'core/**/*.js', included: false },
{ pattern: 'vendor/pako/**/*.js', included: false },
{ pattern: 'tests/test.*.js', included: false },
{ pattern: 'tests/fake.*.js', included: false },
{ pattern: 'tests/assertions.js', included: false },
'tests/karma-test-main.js',
],
client: {
mocha: {
// replace Karma debug page with mocha display
'reporter': 'html',
'ui': 'bdd'
}
},
// list of files to exclude
exclude: [
'../tests/playback.js',
'../app/ui.js'
],
customLaunchers: customLaunchers,
@ -147,14 +89,24 @@ module.exports = function(config) {
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'core/**/*.js': ['babel'],
'tests/test.*.js': ['babel'],
'tests/fake.*.js': ['babel'],
'tests/assertions.js': ['babel'],
'vendor/pako/**/*.js': ['babel'],
},
babelPreprocessor: {
options: {
plugins: ['transform-es2015-modules-amd', 'syntax-dynamic-import'],
sourceMap: 'inline',
},
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['mocha', 'saucelabs'],
reporters: ['mocha'],
// web server port
@ -186,6 +138,7 @@ module.exports = function(config) {
};
if (useSauce) {
my_conf.reporters.push('saucelabs');
my_conf.captureTimeout = 0; // use SL timeout
my_conf.sauceLabs = {
testName: 'noVNC Tests (all)',

View File

@ -9,8 +9,7 @@
},
"scripts": {
"test": "PATH=$PATH:node_modules/karma/bin karma start karma.conf.js",
"prepublish": "node ./utils/use_require.js --as-require",
"build-es6": "node ./utils/use_require.js"
"prepublish": "node ./utils/use_require.js --as commonjs"
},
"repository": {
"type": "git",
@ -28,36 +27,33 @@
},
"homepage": "https://github.com/kanaka/noVNC",
"devDependencies": {
"ansi": "^0.3.1",
"babel-core": "^6.22.1",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-es2015-modules-amd": "^6.22.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.18.0",
"babel-plugin-transform-es2015-modules-systemjs": "^6.22.0",
"babel-plugin-transform-es2015-modules-umd": "^6.22.0",
"babelify": "^7.3.0",
"browserify": "^13.1.0",
"casperjs": "^1.1.3",
"chai": "^3.5.0",
"commander": "^2.9.0",
"es-module-loader": "^2.1.0",
"fs-extra": "^1.0.0",
"jsdom": "*",
"karma": "^1.3.0",
"karma-babel-preprocessor": "^6.0.1",
"karma-chai": "^0.1.0",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.0",
"karma-phantomjs-launcher": "^1.0.2",
"karma-requirejs": "^1.1.0",
"karma-sauce-launcher": "^1.0.0",
"karma-sinon": "^1.0.5",
"karma-sinon-chai-latest": "^0.1.0",
"mocha": "^3.1.2",
"node-getopt": "*",
"open": "^0.0.5",
"phantomjs-prebuilt": "^2.1.13",
"po2json": "*",
"sinon": "^1.17.6",
"sinon-chai": "^2.8.0",
"spooky": "^0.2.5",
"temp": "^0.8.3",
"through2": "^2.0.1"
},
"dependencies": {
"pako": "^1.0.3"
"requirejs": "^2.3.2",
"rollup": "^0.41.4",
"rollup-plugin-node-resolve": "^2.0.0",
"sinon-chai": "^2.8.0"
}
}

View File

@ -6,14 +6,14 @@ LINGUAS := de el nl sv
VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)
POFILES := $(addsuffix .po,$(LINGUAS))
JSFILES := $(addprefix ../app/locale/,$(addsuffix .js,$(LINGUAS)))
JSONFILES := $(addprefix ../app/locale/,$(addsuffix .json,$(LINGUAS)))
update-po: $(POFILES)
update-js: $(JSFILES)
update-js: $(JSONFILES)
%.po: noVNC.pot
msgmerge --update --lang=$* $@ $<
../app/locale/%.js: %.po
../app/locale/%.json: %.po
./po2js $< $@
update-pot:

View File

@ -32,25 +32,12 @@ if (opt.argv.length != 2) {
var data = po2json.parseFileSync(opt.argv[0]);
var output =
"/*\n" +
" * Translations for " + data[""]["language"] + "\n" +
" *\n" +
" * This file was autotomatically generated from " + opt.argv[0] + "\n" +
" * DO NOT EDIT!\n" +
" */\n" +
"\n" +
"Language = {\n";
var bodyPart = Object.keys(data).filter((msgid) => msgid !== "").map((msgid) => {
if (msgid === "") return;
var msgstr = data[msgid][1];
return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr);
}).join(",\n");
for (msgid in data) {
if (msgid === "")
continue;
msgstr = data[msgid][1];
output += " " + JSON.stringify(msgid) + ": " +
JSON.stringify(msgstr) + ",\n";
}
output += "};\n";
var output = "{\n" + bodyPart + "\n}";
fs.writeFileSync(opt.argv[1], output);

View File

@ -1,6 +1,3 @@
var FakeWebSocket;
(function () {
// PhantomJS can't create Event objects directly, so we need to use this
function make_event(name, props) {
var evt = document.createEvent('Event');
@ -13,7 +10,7 @@ var FakeWebSocket;
return evt;
}
FakeWebSocket = function (uri, protocols) {
export default function FakeWebSocket (uri, protocols) {
this.url = uri;
this.binaryType = "arraybuffer";
this.extensions = "";
@ -88,4 +85,3 @@ var FakeWebSocket;
WebSocket = WebSocket.__real_version;
}
};
})();

18
tests/karma-test-main.js Normal file
View File

@ -0,0 +1,18 @@
var TEST_REGEXP = /test\..*\.js/;
var allTestFiles = [];
Object.keys(window.__karma__.files).forEach(function (file) {
if (TEST_REGEXP.test(file)) {
// TODO: normalize?
allTestFiles.push(file);
}
});
require.config({
baseUrl: '/base',
deps: allTestFiles,
callback: window.__karma__.start,
paths: {
'sinon': 'vendor/sinon',
},
});

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)
*/
"use strict";
/*jslint browser: true, white: false */
/*global Util, VNC_frame_data, finish */
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
};
import RFB from '../core/rfb.js';
import * as Log from '../core/util/logging.js';
import Base64 from '../core/base64.js';
// Immediate polyfill
if (window.setImmediate === undefined) {
if (setImmediate === undefined) {
var _immediateIdCounter = 1;
var _immediateFuncs = {};
window.setImmediate = function (func) {
var index = Util._immediateIdCounter++;
var setImmediate = function (func) {
var index = _immediateIdCounter++;
_immediateFuncs[index] = func;
window.postMessage("noVNC immediate trigger:" + index, "*");
return index;
@ -56,12 +44,67 @@ if (window.setImmediate === undefined) {
window.addEventListener("message", _onMessage);
}
enable_test_mode = function () {
rfb._sock.send = send_array;
rfb._sock.close = function () {};
rfb._sock.flush = function () {};
rfb._checkEvents = function () {};
rfb.connect = function (host, port, password, path) {
export default function RecordingPlayer (frames, encoding, disconnected, notification) {
this._frames = frames;
this._encoding = encoding;
this._disconnected = disconnected;
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_port = port;
this._rfb_password = (password !== undefined) ? password : "";
@ -70,129 +113,91 @@ enable_test_mode = function () {
this._rfb_connection_state = 'connecting';
this._rfb_init_state = 'ProtocolVersion';
};
};
},
next_iteration = function () {
rfb = new RFB({'target': document.getElementById('VNC_canvas'),
'view_only': true,
'onDisconnected': disconnected,
'onNotification': notification});
enable_test_mode();
_queueNextPacket: function () {
if (!this._running) { return; }
// Missing in older recordings
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;
}
var frame = this._frames[this._frame_index];
if (iteration === 0) {
frame_length = VNC_frame_data.length;
test_state = 'running';
}
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];
// skip send frames
while (this._frame_index < this._frame_length && frame.charAt(0) === "}") {
this._frame_index++;
frame = this._frames[this._frame_index];
}
if (frame === 'EOF') {
Util.Debug("Finished, found EOF");
end_iteration();
return;
}
if (frame_idx >= frame_length) {
Util.Debug("Finished, no more frames");
end_iteration();
Log.Debug('Finished, found EOF');
this._finish();
return;
}
if (mode === 'realtime') {
foffset = frame.slice(1, frame.indexOf('{', 1));
toffset = (new Date()).getTime() - istart_time;
delay = foffset - toffset;
if (delay < 1) {
delay = 1;
if (this._frame_index >= this._frame_length) {
Log.Debug('Finished, no more frames');
this._finish();
return;
}
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 {
window.setImmediate(do_packet);
setImmediate(this._doPacket.bind(this));
}
};
},
var bytes_processed = 0;
do_packet = function () {
// Avoid having an excessive queue buildup
if (rfb._flushing && (mode !== 'realtime')) {
rfb._display.set_onFlush(function () {
rfb._display.set_onFlush(rfb._onFlush.bind(rfb));
rfb._onFlush();
do_packet();
_doPacket: function () {
// Avoid having excessive queue buildup in non-realtime mode
if (!this._trafficManagement && this._rfb._flushing) {
let player = this;
this._rfb.display.set_onFlush(function () {
this._rfb._display.set_onFlush(this._rfb._onFlush.bind(this._rfb));
this._rfb._onFlush();
player._doPacket();
});
return;
}
//Util.Debug("Processing frame: " + frame_idx);
var frame = VNC_frame_data[frame_idx],
start = frame.indexOf('{', 1) + 1;
var u8;
if (encoding === 'base64') {
u8 = Base64.decode(frame.slice(start));
const frame = this._frames[this._frame_index];
var start = frame.indexOf('{', 1) + 1;
if (this._encoding === 'base64') {
var u8 = Base64.decode(frame.slice(start));
start = 0;
} else {
u8 = new Uint8Array(frame.length - start);
for (var i = 0; i < frame.length - start; i++) {
var u8 = new Uint8Array(frame.length - start);
for (let i = 0; i < frame.length - 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

@ -1,114 +0,0 @@
var Spooky = require('spooky');
var path = require('path');
var phantom_path = require('phantomjs-prebuilt').path;
var casper_path = path.resolve(__dirname, '../node_modules/casperjs/bin/casperjs');
process.env.PHANTOMJS_EXECUTABLE = phantom_path;
var casper_opts = {
child: {
transport: 'http',
command: casper_path
},
casper: {
logLevel: 'debug',
verbose: true
}
};
var provide_emitter = function(file_paths, debug_port) {
if (debug_port) {
casper_opts.child['remote-debugger-port'] = debug_port;
var debug_url = ('https://localhost:' + debug_port +
'/webkit/inspector/inspector.html?page=');
console.info('[remote-debugger] Navigate to ' + debug_url + '1 and ' +
'run `__run();` in the console to continue loading.' +
'\n[remote-debugger] Navigate to ' + debug_url + '2 to ' +
'view the actual page source.\n' +
'[remote-debugger] Use the `debugger;` statement to ' +
'trigger an initial breakpoint.');
}
var spooky = new Spooky(casper_opts, function(err) {
if (err) {
if (err.stack) console.warn(err.stack);
else console.warn(err);
return;
}
spooky.start('about:blank');
file_paths.forEach(function(file_path, path_ind) {
spooky.thenOpen('file://'+file_path);
spooky.waitFor(function() {
return this.getGlobal('__mocha_done') === true;
},
[{ path_ind: path_ind }, function() {
var res_json = {
file_ind: path_ind
};
res_json.num_tests = this.evaluate(function() { return document.querySelectorAll('li.test').length; });
res_json.num_passes = this.evaluate(function() { return document.querySelectorAll('li.test.pass').length; });
res_json.num_fails = this.evaluate(function() { return document.querySelectorAll('li.test.fail').length; });
res_json.num_slow = this.evaluate(function() { return document.querySelectorAll('li.test.pass:not(.fast):not(.pending)').length; });
res_json.num_skipped = this.evaluate(function () { return document.querySelectorAll('li.test.pending').length; });
res_json.duration = this.evaluate(function() { return document.querySelector('li.duration em').textContent; });
res_json.suites = this.evaluate(function() {
var traverse_node = function(elem) {
var res;
if (elem.classList.contains('suite')) {
res = {
type: 'suite',
name: elem.querySelector('h1').textContent,
has_subfailures: elem.querySelectorAll('li.test.fail').length > 0,
};
var child_elems = elem.querySelector('ul').children;
res.children = Array.prototype.map.call(child_elems, traverse_node);
return res;
}
else {
var h2_content = elem.querySelector('h2').childNodes;
res = {
type: 'test',
text: h2_content[0].textContent,
};
if (elem.classList.contains('pass')) {
res.pass = true;
if (elem.classList.contains('pending')) {
res.slow = false;
res.skipped = true;
}
else {
res.slow = !elem.classList.contains('fast');
res.skipped = false;
res.duration = h2_content[1].textContent;
}
}
else {
res.error = elem.querySelector('pre.error').textContent;
}
return res;
}
};
var top_suites = document.querySelectorAll('#mocha-report > li.suite');
return Array.prototype.map.call(top_suites, traverse_node);
});
res_json.replay = this.evaluate(function() { return document.querySelector('a.replay').textContent; });
this.emit('test_ready', res_json);
}]);
});
spooky.run();
});
return spooky;
};
module.exports = {
provide_emitter: provide_emitter,
name: 'SpookyJS (CapserJS on PhantomJS)'
};

View File

@ -1,360 +0,0 @@
#!/usr/bin/env node
var ansi = require('ansi');
var program = require('commander');
var path = require('path');
var fs = require('fs');
var make_list = function(val) {
return val.split(',');
};
program
.option('-t, --tests <testlist>', 'Run the specified html-file-based test(s). \'testlist\' should be a comma-separated list', make_list, [])
.option('-a, --print-all', 'Print all tests, not just the failures')
.option('--disable-color', 'Explicitly disable color')
.option('-c, --color', 'Explicitly enable color (default is to use color when not outputting to a pipe)')
.option('-i, --auto-inject <includefiles>', 'Treat the test list as a set of mocha JS files, and automatically generate HTML files with which to test test. \'includefiles\' should be a comma-separated list of paths to javascript files to include in each of the generated HTML files', make_list, null)
.option('-p, --provider <name>', 'Use the given provider (defaults to "casper"). Currently, may be "casper" or "zombie"', 'casper')
.option('-g, --generate-html', 'Instead of running the tests, just return the path to the generated HTML file, then wait for user interaction to exit (should be used with .js tests).')
.option('-o, --open-in-browser', 'Open the generated HTML files in a web browser using the "open" module (must be used with the "-g"/"--generate-html" option).')
.option('--output-html', 'Instead of running the tests, just output the generated HTML source to STDOUT (should be used with .js tests)')
.option('-d, --debug', 'Show debug output (the "console" event) from the provider')
.option('-r, --relative', 'Use relative paths in the generated HTML file')
.option('--debugger <port>', 'Enable the remote debugger for CasperJS')
.parse(process.argv);
if (program.tests.length === 0) {
program.tests = fs.readdirSync(__dirname).filter(function(f) { return (/^test\.(\w|\.|-)+\.js$/).test(f); });
program.tests = program.tests.map(function (f) { return path.resolve(__dirname, f); }); // add full paths in
console.log('using files %s', program.tests);
}
var file_paths = [];
var all_js = program.tests.reduce(function(a,e) { return a && e.slice(-3) == '.js'; }, true);
var get_path = function (/* arguments */) {
if (program.relative) {
return path.join.apply(null, arguments);
} else {
var args = Array.prototype.slice.call(arguments);
args.unshift(__dirname, '..');
return path.resolve.apply(null, args);
}
};
var get_path_cwd = function (/* arguments */) {
if (program.relative) {
var part_path = path.join.apply(null, arguments);
return path.relative(path.join(__dirname, '..'), path.resolve(process.cwd(), part_path));
} else {
var args = Array.prototype.slice.call(arguments);
args.unshift(process.cwd());
return path.resolve.apply(null, args);
}
};
if (all_js && !program.autoInject) {
var all_modules = {};
// uses the first instance of the string 'requires local modules: '
program.tests.forEach(function (testname) {
var full_path = path.resolve(process.cwd(), testname);
var content = fs.readFileSync(full_path).toString();
var ind = content.indexOf('requires local modules: ');
if (ind > -1) {
ind += 'requires local modules: '.length;
var eol = content.indexOf('\n', ind);
var modules = content.slice(ind, eol).split(/,\s*/);
modules.forEach(function (mod) {
all_modules[get_path('core/', mod) + '.js'] = 1;
});
}
var fakes_ind = content.indexOf('requires test modules: ');
if (fakes_ind > -1) {
fakes_ind += 'requires test modules: '.length;
var fakes_eol = content.indexOf('\n', fakes_ind);
var fakes_modules = content.slice(fakes_ind, fakes_eol).split(/,\s*/);
fakes_modules.forEach(function (mod) {
all_modules[get_path('tests/', mod) + '.js'] = 1;
});
}
});
program.autoInject = Object.keys(all_modules);
}
if (program.autoInject) {
var temp = require('temp');
temp.track();
var template = {
header: "<html>\n<head>\n<meta charset='utf-8' />\n<link rel='stylesheet' href='" + get_path('node_modules/mocha/mocha.css') + "'/>\n</head>\n<body><div id='mocha'></div>",
script_tag: function(p) { return "<script src='" + p + "'></script>"; },
footer: "<script>\nmocha.checkLeaks();\nmocha.globals(['navigator', 'create', 'ClientUtils', '__utils__', 'requestAnimationFrame', 'WebSocket']);\nmocha.run(function () { window.__mocha_done = true; });\n</script>\n</body>\n</html>"
};
template.header += "\n" + template.script_tag(get_path('node_modules/chai/chai.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/mocha/mocha.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/sinon/pkg/sinon.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
template.header += "\n<script>mocha.setup('bdd');</script>";
template.header = program.autoInject.reduce(function(acc, sn) {
return acc + "\n" + template.script_tag(get_path_cwd(sn));
}, template.header);
file_paths = program.tests.map(function(jsn, ind) {
var templ = template.header;
templ += "\n";
templ += template.script_tag(get_path_cwd(jsn));
templ += template.footer;
var tempfile = temp.openSync({ prefix: 'novnc-zombie-inject-', suffix: '-file_num-'+ind+'.html' });
fs.writeSync(tempfile.fd, templ);
fs.closeSync(tempfile.fd);
return tempfile.path;
});
}
else {
file_paths = program.tests.map(function(fn) {
return path.resolve(process.cwd(), fn);
});
}
var use_ansi = false;
if (program.color) use_ansi = true;
else if (program.disableColor) use_ansi = false;
else if (process.stdout.isTTY) use_ansi = true;
var cursor = ansi(process.stdout, { enabled: use_ansi });
if (program.outputHtml) {
file_paths.forEach(function(path, path_ind) {
fs.readFile(path, function(err, data) {
if (err) {
console.warn(error.stack);
return;
}
if (use_ansi) {
cursor
.bold()
.write(program.tests[path_ind])
.reset()
.write("\n")
.write(Array(program.tests[path_ind].length+1).join('='))
.write("\n\n");
}
cursor
.write(data)
.write("\n\n");
});
});
}
if (program.generateHtml) {
var open_browser;
if (program.openInBrowser) {
open_browser = require('open');
}
file_paths.forEach(function(path, path_ind) {
cursor
.bold()
.write(program.tests[path_ind])
.write(": ")
.reset()
.write(path)
.write("\n");
if (program.openInBrowser) {
open_browser(path);
}
});
console.log('');
}
if (program.generateHtml) {
process.stdin.resume(); // pause until C-c
process.on('SIGINT', function() {
process.stdin.pause(); // exit
});
}
if (!program.outputHtml && !program.generateHtml) {
var failure_count = 0;
var prov = require(path.resolve(__dirname, 'run_from_console.'+program.provider+'.js'));
cursor
.write("Running tests ")
.bold()
.write(program.tests.join(', '))
.reset()
.grey()
.write(' using provider '+prov.name)
.reset()
.write("\n");
//console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name);
var provider = prov.provide_emitter(file_paths, program.debugger);
provider.on('test_ready', function(test_json) {
console.log('');
filename = program.tests[test_json.file_ind];
cursor.bold();
console.log('Results for %s:', filename);
console.log(Array('Results for :'.length+filename.length+1).join('='));
cursor.reset();
console.log('');
cursor
.write(''+test_json.num_tests+' tests run, ')
.green()
.write(''+test_json.num_passes+' passed');
if (test_json.num_slow > 0) {
cursor
.reset()
.write(' (');
cursor
.yellow()
.write(''+test_json.num_slow+' slow')
.reset()
.write(')');
}
cursor
.reset()
.write(', ');
cursor
.red()
.write(''+test_json.num_fails+' failed');
if (test_json.num_skipped > 0) {
cursor
.reset()
.write(', ')
.grey()
.write(''+test_json.num_skipped+' skipped');
}
cursor
.reset()
.write(' -- duration: '+test_json.duration+"s\n");
console.log('');
if (test_json.num_fails > 0 || program.printAll) {
var extract_error_lines = function (err) {
// the split is to avoid a weird thing where in PhantomJS where we get a stack trace too
var err_lines = err.split('\n');
if (err_lines.length == 1) {
return err_lines[0];
} else {
var ind;
for (ind = 0; ind < err_lines.length; ind++) {
var at_ind = err_lines[ind].trim().indexOf('at ');
if (at_ind === 0) {
break;
}
}
return err_lines.slice(0, ind).join('\n');
}
};
var traverse_tree = function(indentation, node) {
if (node.type == 'suite') {
if (!node.has_subfailures && !program.printAll) return;
if (indentation === 0) {
cursor.bold();
console.log(node.name);
console.log(Array(node.name.length+1).join('-'));
cursor.reset();
}
else {
cursor
.write(Array(indentation+3).join('#'))
.bold()
.write(' '+node.name+' ')
.reset()
.write(Array(indentation+3).join('#'))
.write("\n");
}
console.log('');
for (var i = 0; i < node.children.length; i++) {
traverse_tree(indentation+1, node.children[i]);
}
}
else {
if (!node.pass) {
cursor.magenta();
console.log('- failed: '+node.text+test_json.replay);
cursor.red();
console.log(' '+extract_error_lines(node.error));
cursor.reset();
console.log('');
}
else if (program.printAll) {
if (node.skipped) {
cursor
.grey()
.write('- skipped: '+node.text);
}
else {
if (node.slow) cursor.yellow();
else cursor.green();
cursor
.write('- pass: '+node.text)
.grey()
.write(' ('+node.duration+') ');
}
/*if (node.slow) cursor.yellow();
else cursor.green();*/
cursor
//.write(test_json.replay)
.reset()
.write("\n");
console.log('');
}
}
};
for (var i = 0; i < test_json.suites.length; i++) {
traverse_tree(0, test_json.suites[i]);
}
}
if (test_json.num_fails === 0) {
cursor.fg.green();
console.log('all tests passed :-)');
cursor.reset();
}
});
if (program.debug) {
provider.on('console', function(line) {
// log to stderr
console.error(line);
});
}
provider.on('error', function(line) {
// log to stderr
console.error('ERROR: ' + line);
});
/*gprom.finally(function(ph) {
ph.exit();
// exit with a status code that actually gives information
if (program.exitWithFailureCount) process.exit(failure_count);
});*/
}

View File

@ -1,82 +0,0 @@
var Browser = require('zombie');
var path = require('path');
var EventEmitter = require('events').EventEmitter;
var Q = require('q');
var provide_emitter = function(file_paths) {
var emitter = new EventEmitter();
file_paths.reduce(function(prom, file_path, path_ind) {
return prom.then(function(browser) {
browser.visit('file://'+file_path, function() {
if (browser.error) throw new Error(browser.errors);
var res_json = {};
res_json.file_ind = path_ind;
res_json.num_tests = browser.querySelectorAll('li.test').length;
res_json.num_fails = browser.querySelectorAll('li.test.fail').length;
res_json.num_passes = browser.querySelectorAll('li.test.pass').length;
res_json.num_slow = browser.querySelectorAll('li.test.pass:not(.fast)').length;
res_json.num_skipped = browser.querySelectorAll('li.test.pending').length;
res_json.duration = browser.text('li.duration em');
var traverse_node = function(elem) {
var classList = elem.className.split(' ');
var res;
if (classList.indexOf('suite') > -1) {
res = {
type: 'suite',
name: elem.querySelector('h1').textContent,
has_subfailures: elem.querySelectorAll('li.test.fail').length > 0
};
var child_elems = elem.querySelector('ul').children;
res.children = Array.prototype.map.call(child_elems, traverse_node);
return res;
}
else {
var h2_content = elem.querySelector('h2').childNodes;
res = {
type: 'test',
text: h2_content[0].textContent
};
if (classList.indexOf('pass') > -1) {
res.pass = true;
if (classList.indexOf('pending') > -1) {
res.slow = false;
res.skipped = true;
}
else {
res.slow = classList.indexOf('fast') < 0;
res.skipped = false;
res.duration = h2_content[1].textContent;
}
}
else {
res.error = elem.querySelector('pre.error').textContent;
}
return res;
}
};
var top_suites = browser.querySelectorAll('#mocha-report > li.suite');
res_json.suites = Array.prototype.map.call(top_suites, traverse_node);
res_json.replay = browser.querySelector('a.replay').textContent;
emitter.emit('test_ready', res_json);
});
return new Browser();
});
}, Q(new Browser()));
return emitter;
};
module.exports = {
provide_emitter: provide_emitter,
name: 'ZombieJS'
};

View File

@ -1,7 +1,8 @@
// requires local modules: base64
var assert = chai.assert;
var expect = chai.expect;
import Base64 from '../core/base64.js';
describe('Base64 Tools', function() {
"use strict";

View File

@ -1,8 +1,15 @@
// requires local modules: util, base64, display
// requires test modules: assertions
/* jshint expr: true */
var expect = chai.expect;
import Base64 from '../core/base64.js';
import Display from '../core/display.js';
import { _forceCursorURIs, browserSupportsCursorURIs } from '../core/util/browsers.js';
import './assertions.js';
import 'sinon';
import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js'
chai.use(sinonChai);
describe('Display/Canvas Helper', function () {
var checked_data = [
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@ -34,31 +41,23 @@ describe('Display/Canvas Helper', function () {
}
describe('checking for cursor uri support', function () {
beforeEach(function () {
this._old_browser_supports_cursor_uris = Util.browserSupportsCursorURIs;
});
it('should disable cursor URIs if there is no support', function () {
Util.browserSupportsCursorURIs = function () { return false; };
_forceCursorURIs(false);
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
expect(display._cursor_uri).to.be.false;
});
it('should enable cursor URIs if there is support', function () {
Util.browserSupportsCursorURIs = function () { return true; };
_forceCursorURIs(true);
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
expect(display._cursor_uri).to.be.true;
});
it('respect the cursor_uri option if there is support', function () {
Util.browserSupportsCursorURIs = function () { return false; };
_forceCursorURIs(false);
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false });
expect(display._cursor_uri).to.be.false;
});
afterEach(function () {
Util.browserSupportsCursorURIs = this._old_browser_supports_cursor_uris;
});
});
describe('viewport handling', function () {

View File

@ -1,8 +1,9 @@
// requires local modules: input/keysym, input/keysymdef, input/util
var assert = chai.assert;
var assert = chai.assert;
var expect = chai.expect;
import keysyms from '../core/input/keysymdef.js';
import * as KeyboardUtil from "../core/input/util.js";
describe('Helpers', function() {
"use strict";
describe('keysymFromKeyCode', function() {

View File

@ -1,7 +1,9 @@
// requires local modules: input/devices, input/util, input/keysymdef, input/keysym
var assert = chai.assert;
var expect = chai.expect;
import keysyms from '../core/input/keysymdef.js';
import * as KeyboardUtil from '../core/input/util.js';
/* jshint newcap: false, expr: true */
describe('Key Event Pipeline Stages', function() {
"use strict";

View File

@ -1,9 +1,16 @@
// requires local modules: util, websock, rfb, input/util, input/keysym, input/keysymdef, input/devices, inflator, des, display
// requires test modules: fake.websocket, assertions
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
import RFB from '../core/rfb.js';
import Websock from '../core/websock.js';
import FakeWebSocket from './fake.websocket.js';
import './assertions';
import 'sinon';
import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js'
chai.use(sinonChai);
function make_rfb (extra_opts) {
if (!extra_opts) {
extra_opts = {};

View File

@ -1,9 +1,15 @@
// requires local modules: util
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
import * as Log from '../core/util/logging.js';
import l10nGet, { l10n } from '../core/util/localization.js';
import 'sinon';
import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js'
chai.use(sinonChai);
describe('Utils', function() {
"use strict";
@ -25,34 +31,34 @@ describe('Utils', function() {
});
it('should use noop for levels lower than the min level', function () {
Util.init_logging('warn');
Util.Debug('hi');
Util.Info('hello');
Log.init_logging('warn');
Log.Debug('hi');
Log.Info('hello');
expect(console.log).to.not.have.been.called;
});
it('should use console.debug for Debug', function () {
Util.init_logging('debug');
Util.Debug('dbg');
Log.init_logging('debug');
Log.Debug('dbg');
expect(console.debug).to.have.been.calledWith('dbg');
});
it('should use console.info for Info', function () {
Util.init_logging('debug');
Util.Info('inf');
Log.init_logging('debug');
Log.Info('inf');
expect(console.info).to.have.been.calledWith('inf');
});
it('should use console.warn for Warn', function () {
Util.init_logging('warn');
Util.Warn('wrn');
Log.init_logging('warn');
Log.Warn('wrn');
expect(console.warn).to.have.been.called;
expect(console.warn).to.have.been.calledWith('wrn');
});
it('should use console.error for Error', function () {
Util.init_logging('error');
Util.Error('err');
Log.init_logging('error');
Log.Error('err');
expect(console.error).to.have.been.called;
expect(console.error).to.have.been.calledWith('err');
});
@ -85,42 +91,42 @@ describe('Utils', function() {
});
it('should use English by default', function() {
expect(Util.Localisation.language).to.equal('en');
expect(l10n.language).to.equal('en');
});
it('should use English if no user language matches', function() {
window.navigator.languages = ["nl", "de"];
Util.Localisation.setup(["es", "fr"]);
expect(Util.Localisation.language).to.equal('en');
l10n.setup(["es", "fr"]);
expect(l10n.language).to.equal('en');
});
it('should use the most preferred user language', function() {
window.navigator.languages = ["nl", "de", "fr"];
Util.Localisation.setup(["es", "fr", "de"]);
expect(Util.Localisation.language).to.equal('de');
l10n.setup(["es", "fr", "de"]);
expect(l10n.language).to.equal('de');
});
it('should prefer sub-languages languages', function() {
window.navigator.languages = ["pt-BR"];
Util.Localisation.setup(["pt", "pt-BR"]);
expect(Util.Localisation.language).to.equal('pt-BR');
l10n.setup(["pt", "pt-BR"]);
expect(l10n.language).to.equal('pt-BR');
});
it('should fall back to language "parents"', function() {
window.navigator.languages = ["pt-BR"];
Util.Localisation.setup(["fr", "pt", "de"]);
expect(Util.Localisation.language).to.equal('pt');
l10n.setup(["fr", "pt", "de"]);
expect(l10n.language).to.equal('pt');
});
it('should not use specific language when user asks for a generic language', function() {
window.navigator.languages = ["pt", "de"];
Util.Localisation.setup(["fr", "pt-BR", "de"]);
expect(Util.Localisation.language).to.equal('de');
l10n.setup(["fr", "pt-BR", "de"]);
expect(l10n.language).to.equal('de');
});
it('should handle underscore as a separator', function() {
window.navigator.languages = ["pt-BR"];
Util.Localisation.setup(["pt_BR"]);
expect(Util.Localisation.language).to.equal('pt_BR');
l10n.setup(["pt_BR"]);
expect(l10n.language).to.equal('pt_BR');
});
it('should handle difference in case', function() {
window.navigator.languages = ["pt-br"];
Util.Localisation.setup(["pt-BR"]);
expect(Util.Localisation.language).to.equal('pt-BR');
l10n.setup(["pt-BR"]);
expect(l10n.language).to.equal('pt-BR');
});
});

View File

@ -1,9 +1,15 @@
// requires local modules: websock, util
// requires test modules: fake.websocket, assertions
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
import Websock from '../core/websock.js';
import FakeWebSocket from './fake.websocket.js';
import './assertions';
import 'sinon';
import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js'
chai.use(sinonChai);
describe('Websock', function() {
"use strict";

View File

@ -2,6 +2,8 @@
<html>
<head>
<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>
<body>
@ -9,8 +11,7 @@
Perftest:<input type='radio' id='mode1' name='mode' checked>&nbsp;
Realtime:<input type='radio' id='mode2' name='mode'>&nbsp;&nbsp;
<input id='startButton' type='button' value='Start' style='width:100px'
onclick="start();" disabled>&nbsp;
<input id='startButton' type='button' value='Start' style='width:100px' disabled>&nbsp;
<br><br>
@ -37,98 +38,5 @@
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<script type="text/javascript">
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>
<script type="module" src="./playback-ui.js">
</html>

View File

@ -1,25 +0,0 @@
var through = require('through2');
var singleLineRe = /\/\* \[module\] ((.(?!\*\/))+) \*\//g;
var multiLineRe = /\/\* \[module\]\n(( * .+\n)+) \*\//g;
var skipAsModule = /\/\* \[begin skip-as-module\] \*\/(.|\n)+\/\* \[end skip-as-module\] \*\//g;
module.exports = function (file) {
var stream = through(function (buf, enc, next) {
var bufStr = buf.toString('utf8');
bufStr = bufStr.replace(singleLineRe, "$1");
bufStr = bufStr.replace(multiLineRe, function (match, mainLines) {
return mainLines.split(" * ").join("");
});
bufStr = bufStr.replace(skipAsModule, "");
this.push(bufStr);
next();
});
stream._is_make_module = true;
return stream;
};

View File

@ -4,118 +4,170 @@ var path = require('path');
var program = require('commander');
var fs = require('fs');
var fse = require('fs-extra');
var browserify = require('browserify');
var make_modules_transform = require('./make-module-transform');
var babelify = require("babelify");
const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']);
program
.option('-b, --browserify', 'create a browserify bundled app')
.option('--as-require', 'output files using "require" instead of ES6 import and export')
.option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`)
.option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')
.option('--with-app', 'process app files as well as core files')
.parse(process.argv);
// the various important paths
var core_path = path.resolve(__dirname, '..', 'core');
var app_path = path.resolve(__dirname, '..', 'app');
var out_dir_base = path.resolve(__dirname, '..', 'build');
var lib_dir_base = path.resolve(__dirname, '..', 'lib');
var make_browserify = function (src_files, opts) {
// change to the root noVNC directory
process.chdir(path.resolve(__dirname, '..'));
var b = browserify(src_files, opts);
// register the transforms
b.transform(make_modules_transform);
b.transform(babelify,
{ plugins: ["add-module-exports", "transform-es2015-modules-commonjs"] });
return b;
const paths = {
main: path.resolve(__dirname, '..'),
core: path.resolve(__dirname, '..', 'core'),
app: path.resolve(__dirname, '..', 'app'),
vendor: path.resolve(__dirname, '..', 'vendor'),
out_dir_base: path.resolve(__dirname, '..', 'build'),
lib_dir_base: path.resolve(__dirname, '..', 'lib'),
};
var make_full_app = function () {
// make sure the output directory exists
fse.ensureDir(out_dir_base);
const no_copy_files = new Set([
// skip these -- they don't belong in the processed application
path.join(paths.vendor, 'sinon.js'),
path.join(paths.vendor, 'browser-es-module-loader'),
path.join(paths.vendor, 'promise.js'),
]);
// actually bundle the files into a browserified bundled
var ui_file = path.join(app_path, 'ui.js');
var b = make_browserify(ui_file, {});
var app_file = path.join(out_dir_base, 'app.js');
b.bundle().pipe(fs.createWriteStream(app_file));
const no_transform_files = new Set([
// don't transform this -- we want it imported as-is to properly catch loading errors
path.join(paths.app, 'error-handler.js'),
]);
// copy over app-related resources (images, styles, etc)
var src_dir_app = path.join(__dirname, '..', 'app');
fs.readdir(src_dir_app, function (err, files) {
if (err) { throw err; }
no_copy_files.forEach((file) => no_transform_files.add(file));
files.forEach(function (src_file) {
var src_file_path = path.resolve(src_dir_app, src_file);
var out_file_path = path.resolve(out_dir_base, src_file);
var ext = path.extname(src_file);
if (ext === '.js' || ext === '.html') return;
fse.copy(src_file_path, out_file_path, function (err) {
if (err) { throw err; }
console.log("Copied file(s) from " + src_file_path + " to " + out_file_path);
// walkDir *recursively* walks directories trees,
// calling the callback for all normal files found.
var walkDir = function (base_path, cb, filter) {
fs.readdir(base_path, (err, files) => {
if (err) throw err;
files.map((filename) => path.join(base_path, filename)).forEach((filepath) => {
fs.lstat(filepath, (err, stats) => {
if (err) throw err;
if (filter !== undefined && !filter(filepath, stats)) return;
if (stats.isSymbolicLink()) return;
if (stats.isFile()) cb(filepath);
if (stats.isDirectory()) walkDir(filepath, cb, filter);
});
});
});
};
var transform_html = function (new_script) {
// write out the modified vnc.html file that works with the bundle
var src_html_path = path.resolve(__dirname, '..', 'vnc.html');
var out_html_path = path.resolve(out_dir_base, 'vnc.html');
fs.readFile(src_html_path, function (err, contents_raw) {
var out_html_path = path.resolve(paths.out_dir_base, 'vnc.html');
fs.readFile(src_html_path, (err, contents_raw) => {
if (err) { throw err; }
var contents = contents_raw.toString();
contents = contents.replace(/="app\//g, '="');
var start_marker = '<!-- begin scripts -->\n';
var end_marker = '<!-- end scripts -->';
var start_ind = contents.indexOf(start_marker) + start_marker.length;
var end_ind = contents.indexOf(end_marker, start_ind);
contents = contents.slice(0, start_ind) + '<script src="app.js"></script>\n' + contents.slice(end_ind);
contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind);
console.log(`Writing ${out_html_path}`);
fs.writeFile(out_html_path, contents, function (err) {
if (err) { throw err; }
console.log("Wrote " + out_html_path);
});
});
};
var make_lib_files = function (use_require) {
// make sure the output directory exists
fse.ensureDir(lib_dir_base);
var through = require('through2');
var deps = {};
var rfb_file = path.join(core_path, 'rfb.js');
var b = make_browserify(rfb_file, {});
b.on('transform', function (tr, file) {
if (tr._is_make_module) {
var new_path = path.join(lib_dir_base, path.relative(core_path, file));
fse.ensureDir(path.dirname(new_path));
console.log("Writing " + new_path)
var fileStream = fs.createWriteStream(new_path);
if (use_require) {
var babelificate = babelify(file,
{ plugins: ["add-module-exports", "transform-es2015-modules-commonjs"] });
tr.pipe(babelificate);
tr = babelificate;
}
tr.pipe(fileStream);
var make_lib_files = function (import_format, source_maps, with_app_dir) {
if (!import_format) {
throw new Error("you must specify an import format to generate compiled noVNC libraries");
} else if (!SUPPORTED_FORMATS.has(import_format)) {
throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`);
}
// NB: we need to make a copy of babel_opts, since babel sets some defaults on it
const babel_opts = () => ({
plugins: [`transform-es2015-modules-${import_format}`],
ast: false,
sourceMaps: source_maps,
});
const babel = require('babel-core');
b.bundle();
};
if (program.browserify) {
make_full_app();
var in_path;
if (with_app_dir) {
var out_path_base = paths.out_dir_base;
in_path = paths.main;
} else {
make_lib_files(program.asRequire);
var out_path_base = paths.lib_dir_base;
}
fse.ensureDirSync(out_path_base);
const helpers = require('./use_require_helpers');
const helper = helpers[import_format];
var handleDir = (js_only, in_path_base, filename) => {
if (no_copy_files.has(filename)) return;
const out_path = path.join(out_path_base, path.relative(in_path_base, filename));
if(path.extname(filename) !== '.js') {
if (!js_only) {
console.log(`Writing ${out_path}`);
fse.copy(filename, out_path, (err) => { if (err) throw err; });
}
return; // skip non-javascript files
}
fse.ensureDir(path.dirname(out_path), () => {
if (no_transform_files.has(filename)) {
console.log(`Writing ${out_path}`);
fse.copy(filename, out_path, (err) => { if (err) throw err; });
return;
}
const opts = babel_opts();
if (helper && helpers.optionsOverride) {
helper.optionsOverride(opts);
}
babel.transformFile(filename, babel_opts(), (err, res) => {
console.log(`Writing ${out_path}`);
if (err) throw err;
var {code, map, ast} = res;
if (source_maps === true) {
// append URL for external source map
code += `\n//# sourceMappingURL=${path.basename(out_path)}.map\n`;
}
fs.writeFile(out_path, code, (err) => { if (err) throw err; });
if (source_maps === true || source_maps === 'both') {
console.log(` and ${out_path}.map`);
fs.writeFile(`${out_path}.map`, JSON.stringify(map), (err) => { if (err) throw err; });
}
});
});
};
if (with_app_dir && helper && helper.noCopyOverride) {
helper.noCopyOverride(paths, no_copy_files);
}
walkDir(paths.core, handleDir.bind(null, true, in_path || paths.core), (filename, stats) => !no_copy_files.has(filename));
walkDir(paths.vendor, handleDir.bind(null, true, in_path || paths.main), (filename, stats) => !no_copy_files.has(filename));
if (with_app_dir) {
walkDir(paths.app, handleDir.bind(null, false, in_path || paths.app), (filename, stats) => !no_copy_files.has(filename));
const out_app_path = path.join(out_path_base, 'app.js');
if (helper && helper.appWriter) {
console.log(`Writing ${out_app_path}`);
let out_script = helper.appWriter(out_path_base, out_app_path);
transform_html(out_script);
} else {
console.error(`Unable to generate app for the ${import_format} format!`);
}
}
};
make_lib_files(program.as, program.withSourceMaps, program.withApp);

View File

@ -0,0 +1,46 @@
// writes helpers require for vnc.html (they should output app.js)
var fs = require('fs');
var fse = require('fs-extra');
var path = require('path');
module.exports = {
'amd': {
appWriter: (base_out_path, out_path) => {
// setup for requirejs
fs.writeFile(out_path, 'requirejs(["app/ui"], function (ui) {});', (err) => { if (err) throw err; });
console.log(`Please place RequireJS in ${path.join(base_out_path, 'require.js')}`);
return `<script src="require.js" data-main="${path.relative(base_out_path, out_path)}"></script>`;
},
noCopyOverride: () => {},
},
'commonjs': {
optionsOverride: (opts) => {
// CommonJS supports properly shifting the default export to work as normal
opts.plugins.unshift("add-module-exports");
},
appWriter: (base_out_path, out_path) => {
var browserify = require('browserify');
var b = browserify(path.join(base_out_path, 'app/ui.js'), {});
b.bundle().pipe(fs.createWriteStream(out_path));
return `<script src="${path.relative(base_out_path, out_path)}"></script>`;
},
noCopyOverride: () => {},
},
'systemjs': {
appWriter: (base_out_path, out_path) => {
fs.writeFile(out_path, 'SystemJS.import("./app/ui.js");', (err) => { if (err) throw err; });
console.log(`Please place SystemJS in ${path.join(base_out_path, 'system-production.js')}`);
return `<script src="vendor/promise.js"></script>
<script src="system-production.js"></script>\n<script src="${path.relative(base_out_path, out_path)}"></script>`;
},
noCopyOverride: (paths, no_copy_files) => {
no_copy_files.delete(path.join(paths.vendor, 'promise.js'));
},
},
'umd': {
optionsOverride: (opts) => {
// umd supports properly shifting the default export to work as normal
opts.plugins.unshift("add-module-exports");
},
},
}

View File

View File

@ -0,0 +1,15 @@
Custom Browser ES Module Loader
===============================
This is a module loader using babel and the ES Module Loader polyfill.
It's based heavily on
https://github.com/ModuleLoader/browser-es-module-loader, but uses
WebWorkers to compile the modules in the background.
To generate, run `rollup -c` in this directory, and then run `browserify
src/babel-worker.js > dist/babel-worker.js`.
LICENSE
-------
MIT

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
import nodeResolve from 'rollup-plugin-node-resolve';
export default {
entry: 'src/browser-es-module-loader.js',
dest: 'dist/browser-es-module-loader.js',
format: 'umd',
moduleName: 'BrowserESModuleLoader',
plugins: [
nodeResolve(),
],
// skip rollup warnings (specifically the eval warning)
onwarn: function() {}
};

View File

@ -0,0 +1,23 @@
/*import { transform as babelTransform } from 'babel-core';
import babelTransformDynamicImport from 'babel-plugin-syntax-dynamic-import';
import babelTransformES2015ModulesSystemJS from 'babel-plugin-transform-es2015-modules-systemjs';*/
// sadly, due to how rollup works, we can't use es6 imports here
var babelTransform = require('babel-core').transform;
var babelTransformDynamicImport = require('babel-plugin-syntax-dynamic-import');
var babelTransformES2015ModulesSystemJS = require('babel-plugin-transform-es2015-modules-systemjs');
self.onmessage = function (evt) {
// transform source with Babel
var output = babelTransform(evt.data.source, {
compact: false,
filename: evt.data.key + '!transpiled',
sourceFileName: evt.data.key,
moduleIds: false,
sourceMaps: 'inline',
babelrc: false,
plugins: [babelTransformDynamicImport, babelTransformES2015ModulesSystemJS],
});
self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source});
};

View File

@ -0,0 +1,247 @@
import RegisterLoader from 'es-module-loader/core/register-loader.js';
import { InternalModuleNamespace as ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js';
import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
var loader;
// <script type="module"> support
var anonSources = {};
if (typeof document != 'undefined' && document.getElementsByTagName) {
function ready() {
document.removeEventListener('DOMContentLoaded', ready, false );
var anonCnt = 0;
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
if (script.type == 'module' && !script.loaded) {
script.loaded = true;
if (script.src) {
loader.import(script.src).catch(function(err) {
// dispatch an error event so that we can display in errors in browsers
// that don't yet support unhandledrejection
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
evt.error = err;
window.dispatchEvent(evt);
// throw so it still shows up in the console
throw err;
});
}
// anonymous modules supported via a custom naming scheme and registry
else {
var uri = './<anon' + ++anonCnt + '>';
if (script.id !== ""){
uri = "./" + script.id;
}
var anonName = resolveIfNotPlain(uri, baseURI);
anonSources[anonName] = script.innerHTML;
loader.import(anonName).catch(function(err) {
// dispatch an error event so that we can display in errors in browsers
// that don't yet support unhandledrejection
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
evt.error = err;
window.dispatchEvent(evt);
// throw so it still shows up in the console
throw err;
});
}
}
}
}
// simple DOM ready
if (document.readyState === 'complete')
setTimeout(ready);
else
document.addEventListener('DOMContentLoaded', ready, false);
}
function BrowserESModuleLoader(baseKey) {
if (baseKey)
this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI);
RegisterLoader.call(this);
var loader = this;
// ensure System.register is available
global.System = global.System || {};
if (typeof global.System.register == 'function')
var prevRegister = global.System.register;
global.System.register = function() {
loader.register.apply(loader, arguments);
if (prevRegister)
prevRegister.apply(this, arguments);
};
}
BrowserESModuleLoader.prototype = Object.create(RegisterLoader.prototype);
// normalize is never given a relative name like "./x", that part is already handled
BrowserESModuleLoader.prototype[RegisterLoader.resolve] = function(key, parent) {
var resolved = RegisterLoader.prototype[RegisterLoader.resolve].call(this, key, parent || this.baseKey) || key;
if (!resolved)
throw new RangeError('ES module loader does not resolve plain module names, resolving "' + key + '" to ' + parent);
return resolved;
};
function xhrFetch(url, resolve, reject) {
var xhr = new XMLHttpRequest();
function load(source) {
resolve(xhr.responseText);
}
function error() {
reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url));
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// in Chrome on file:/// URLs, status is 0
if (xhr.status == 0) {
if (xhr.responseText) {
load();
}
else {
// when responseText is empty, wait for load or error event
// to inform if it is a 404 or empty file
xhr.addEventListener('error', error);
xhr.addEventListener('load', load);
}
}
else if (xhr.status === 200) {
load();
}
else {
error();
}
}
};
xhr.open("GET", url, true);
xhr.send(null);
}
var WorkerPool = function (script, size) {
this._workers = new Array(size);
this._ind = 0;
this._size = size;
this._jobs = 0;
this.onmessage = undefined;
this._stopTimeout = undefined;
for (let i = 0; i < size; i++) {
let wrkr = new Worker(script);
wrkr._count = 0;
wrkr._ind = i;
wrkr.onmessage = this._onmessage.bind(this, wrkr);
this._workers[i] = wrkr;
}
this._checkJobs();
};
WorkerPool.prototype = {
postMessage: function (msg) {
if (this._stopTimeout !== undefined) {
clearTimeout(this._stopTimeout);
this._stopTimeout = undefined;
}
let wrkr = this._workers[this._ind % this._size];
wrkr._count++;
this._jobs++;
wrkr.postMessage(msg);
this._ind++;
},
_onmessage: function (wrkr, evt) {
wrkr._count--;
this._jobs--;
this.onmessage(evt, wrkr);
this._checkJobs();
},
_checkJobs: function () {
if (this._jobs === 0 && this._stopTimeout === undefined) {
// wait for 2s of inactivity before stopping (that should be enough for local loading)
this._stopTimeout = setTimeout(this._stop.bind(this), 2000);
}
},
_stop: function () {
this._workers.forEach(function(wrkr) {
wrkr.terminate();
});
}
};
var promiseMap = new Map();
var babelWorker = new WorkerPool('/vendor/browser-es-module-loader/dist/babel-worker.js', 3);
babelWorker.onmessage = function (evt) {
var promFuncs = promiseMap.get(evt.data.key);
promFuncs.resolve(evt.data);
promiseMap.delete(evt.data.key);
};
// instantiate just needs to run System.register
// so we fetch the source, convert into the Babel System module format, then evaluate it
BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, processAnonRegister) {
var loader = this;
// load as ES with Babel converting into System.register
return new Promise(function(resolve, reject) {
// anonymous module
if (anonSources[key]) {
resolve(anonSources[key])
anonSources[key] = undefined;
}
// otherwise we fetch
else {
xhrFetch(key, resolve, reject);
}
})
.then(function(source) {
// check our cache first
const cacheEntryTrans = localStorage.getItem(key+'!transpiled');
if (cacheEntryTrans) {
const cacheEntryRaw = localStorage.getItem(key+'!raw');
// TODO: store a hash instead
if (cacheEntryRaw === source) {
return Promise.resolve({key: key, code: cacheEntryTrans, source: source});
}
}
return new Promise(function (resolve, reject) {
promiseMap.set(key, {resolve: resolve, reject: reject});
babelWorker.postMessage({key: key, source: source});
});
}).then(function (data) {
// evaluate without require, exports and module variables
// 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(data.key+'!transpiled', data.code);
}
(0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
processAnonRegister();
});
};
// create a default loader instance in the browser
if (isBrowser)
loader = new BrowserESModuleLoader();
export default BrowserESModuleLoader;

21
vendor/pako/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
(The MIT License)
Copyright (C) 2014-2016 by Vitaly Puzrin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

6
vendor/pako/README.md vendored Normal file
View File

@ -0,0 +1,6 @@
This is an ES6-modules-compatible version of
https://github.com/nodeca/pako, based on pako version 1.0.3.
It's more-or-less a direct translation of the original, with unused parts
removed, and the dynamic support for non-typed arrays removed (since ES6
modules don't work well with dynamic exports).

45
vendor/pako/lib/utils/common.js vendored Normal file
View File

@ -0,0 +1,45 @@
// reduce buffer size, avoiding mem copy
export function shrinkBuf (buf, size) {
if (buf.length === size) { return buf; }
if (buf.subarray) { return buf.subarray(0, size); }
buf.length = size;
return buf;
};
export function arraySet (dest, src, src_offs, len, dest_offs) {
if (src.subarray && dest.subarray) {
dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
return;
}
// Fallback to ordinary array
for (var i = 0; i < len; i++) {
dest[dest_offs + i] = src[src_offs + i];
}
}
// Join array of chunks to single array.
export function flattenChunks (chunks) {
var i, l, len, pos, chunk, result;
// calculate data length
len = 0;
for (i = 0, l = chunks.length; i < l; i++) {
len += chunks[i].length;
}
// join chunks
result = new Uint8Array(len);
pos = 0;
for (i = 0, l = chunks.length; i < l; i++) {
chunk = chunks[i];
result.set(chunk, pos);
pos += chunk.length;
}
return result;
}
export const Buf8 = Uint8Array;
export const Buf16 = Uint16Array;
export const Buf32 = Int32Array;

27
vendor/pako/lib/zlib/adler32.js vendored Normal file
View File

@ -0,0 +1,27 @@
// Note: adler32 takes 12% for level 0 and 2% for level 6.
// It doesn't worth to make additional optimizationa as in original.
// Small size is preferable.
export default function adler32(adler, buf, len, pos) {
var s1 = (adler & 0xffff) |0,
s2 = ((adler >>> 16) & 0xffff) |0,
n = 0;
while (len !== 0) {
// Set limit ~ twice less than 5552, to keep
// s2 in 31-bits, because we force signed ints.
// in other case %= will fail.
n = len > 2000 ? 2000 : len;
len -= n;
do {
s1 = (s1 + buf[pos++]) |0;
s2 = (s2 + s1) |0;
} while (--n);
s1 %= 65521;
s2 %= 65521;
}
return (s1 | (s2 << 16)) |0;
}

47
vendor/pako/lib/zlib/constants.js vendored Normal file
View File

@ -0,0 +1,47 @@
export default {
/* Allowed flush values; see deflate() and inflate() below for details */
Z_NO_FLUSH: 0,
Z_PARTIAL_FLUSH: 1,
Z_SYNC_FLUSH: 2,
Z_FULL_FLUSH: 3,
Z_FINISH: 4,
Z_BLOCK: 5,
Z_TREES: 6,
/* Return codes for the compression/decompression functions. Negative values
* are errors, positive values are used for special but normal events.
*/
Z_OK: 0,
Z_STREAM_END: 1,
Z_NEED_DICT: 2,
Z_ERRNO: -1,
Z_STREAM_ERROR: -2,
Z_DATA_ERROR: -3,
//Z_MEM_ERROR: -4,
Z_BUF_ERROR: -5,
//Z_VERSION_ERROR: -6,
/* compression levels */
Z_NO_COMPRESSION: 0,
Z_BEST_SPEED: 1,
Z_BEST_COMPRESSION: 9,
Z_DEFAULT_COMPRESSION: -1,
Z_FILTERED: 1,
Z_HUFFMAN_ONLY: 2,
Z_RLE: 3,
Z_FIXED: 4,
Z_DEFAULT_STRATEGY: 0,
/* Possible values of the data_type field (though see inflate()) */
Z_BINARY: 0,
Z_TEXT: 1,
//Z_ASCII: 1, // = Z_TEXT (deprecated)
Z_UNKNOWN: 2,
/* The deflate compression method */
Z_DEFLATED: 8
//Z_NULL: null // Use -1 or null inline, depending on var type
};

36
vendor/pako/lib/zlib/crc32.js vendored Normal file
View File

@ -0,0 +1,36 @@
// Note: we can't get significant speed boost here.
// So write code to minimize size - no pregenerated tables
// and array tools dependencies.
// Use ordinary array, since untyped makes no boost here
export default function makeTable() {
var c, table = [];
for (var n = 0; n < 256; n++) {
c = n;
for (var k = 0; k < 8; k++) {
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
}
table[n] = c;
}
return table;
}
// Create table on load. Just 255 signed longs. Not a problem.
var crcTable = makeTable();
function crc32(crc, buf, len, pos) {
var t = crcTable,
end = pos + len;
crc ^= -1;
for (var i = pos; i < end; i++) {
crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
}
return (crc ^ (-1)); // >>> 0;
}

1846
vendor/pako/lib/zlib/deflate.js vendored Normal file

File diff suppressed because it is too large Load Diff

35
vendor/pako/lib/zlib/gzheader.js vendored Normal file
View File

@ -0,0 +1,35 @@
export default function GZheader() {
/* true if compressed data believed to be text */
this.text = 0;
/* modification time */
this.time = 0;
/* extra flags (not used when writing a gzip file) */
this.xflags = 0;
/* operating system */
this.os = 0;
/* pointer to extra field or Z_NULL if none */
this.extra = null;
/* extra field length (valid if extra != Z_NULL) */
this.extra_len = 0; // Actually, we don't need it in JS,
// but leave for few code modifications
//
// Setup limits is not necessary because in js we should not preallocate memory
// for inflate use constant limit in 65536 bytes
//
/* space at extra (only when reading header) */
// this.extra_max = 0;
/* pointer to zero-terminated file name or Z_NULL */
this.name = '';
/* space at name (only when reading header) */
// this.name_max = 0;
/* pointer to zero-terminated comment or Z_NULL */
this.comment = '';
/* space at comment (only when reading header) */
// this.comm_max = 0;
/* true if there was or will be a header crc */
this.hcrc = 0;
/* true when done reading gzip header (not used when writing a gzip file) */
this.done = false;
}

324
vendor/pako/lib/zlib/inffast.js vendored Normal file
View File

@ -0,0 +1,324 @@
// See state defs from inflate.js
var BAD = 30; /* got a data error -- remain here until reset */
var TYPE = 12; /* i: waiting for type bits, including last-flag bit */
/*
Decode literal, length, and distance codes and write out the resulting
literal and match bytes until either not enough input or output is
available, an end-of-block is encountered, or a data error is encountered.
When large enough input and output buffers are supplied to inflate(), for
example, a 16K input buffer and a 64K output buffer, more than 95% of the
inflate execution time is spent in this routine.
Entry assumptions:
state.mode === LEN
strm.avail_in >= 6
strm.avail_out >= 258
start >= strm.avail_out
state.bits < 8
On return, state.mode is one of:
LEN -- ran out of enough output space or enough available input
TYPE -- reached end of block code, inflate() to interpret next block
BAD -- error in block data
Notes:
- The maximum input bits used by a length/distance pair is 15 bits for the
length code, 5 bits for the length extra, 15 bits for the distance code,
and 13 bits for the distance extra. This totals 48 bits, or six bytes.
Therefore if strm.avail_in >= 6, then there is enough input to avoid
checking for available input while decoding.
- The maximum bytes that a single length/distance pair can output is 258
bytes, which is the maximum length that can be coded. inflate_fast()
requires strm.avail_out >= 258 for each loop to avoid checking for
output space.
*/
export default function inflate_fast(strm, start) {
var state;
var _in; /* local strm.input */
var last; /* have enough input while in < last */
var _out; /* local strm.output */
var beg; /* inflate()'s initial strm.output */
var end; /* while out < end, enough space available */
//#ifdef INFLATE_STRICT
var dmax; /* maximum distance from zlib header */
//#endif
var wsize; /* window size or zero if not using window */
var whave; /* valid bytes in the window */
var wnext; /* window write index */
// Use `s_window` instead `window`, avoid conflict with instrumentation tools
var s_window; /* allocated sliding window, if wsize != 0 */
var hold; /* local strm.hold */
var bits; /* local strm.bits */
var lcode; /* local strm.lencode */
var dcode; /* local strm.distcode */
var lmask; /* mask for first level of length codes */
var dmask; /* mask for first level of distance codes */
var here; /* retrieved table entry */
var op; /* code bits, operation, extra bits, or */
/* window position, window bytes to copy */
var len; /* match length, unused bytes */
var dist; /* match distance */
var from; /* where to copy match from */
var from_source;
var input, output; // JS specific, because we have no pointers
/* copy state to local variables */
state = strm.state;
//here = state.here;
_in = strm.next_in;
input = strm.input;
last = _in + (strm.avail_in - 5);
_out = strm.next_out;
output = strm.output;
beg = _out - (start - strm.avail_out);
end = _out + (strm.avail_out - 257);
//#ifdef INFLATE_STRICT
dmax = state.dmax;
//#endif
wsize = state.wsize;
whave = state.whave;
wnext = state.wnext;
s_window = state.window;
hold = state.hold;
bits = state.bits;
lcode = state.lencode;
dcode = state.distcode;
lmask = (1 << state.lenbits) - 1;
dmask = (1 << state.distbits) - 1;
/* decode literals and length/distances until end-of-block or not enough
input data or output space */
top:
do {
if (bits < 15) {
hold += input[_in++] << bits;
bits += 8;
hold += input[_in++] << bits;
bits += 8;
}
here = lcode[hold & lmask];
dolen:
for (;;) { // Goto emulation
op = here >>> 24/*here.bits*/;
hold >>>= op;
bits -= op;
op = (here >>> 16) & 0xff/*here.op*/;
if (op === 0) { /* literal */
//Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
// "inflate: literal '%c'\n" :
// "inflate: literal 0x%02x\n", here.val));
output[_out++] = here & 0xffff/*here.val*/;
}
else if (op & 16) { /* length base */
len = here & 0xffff/*here.val*/;
op &= 15; /* number of extra bits */
if (op) {
if (bits < op) {
hold += input[_in++] << bits;
bits += 8;
}
len += hold & ((1 << op) - 1);
hold >>>= op;
bits -= op;
}
//Tracevv((stderr, "inflate: length %u\n", len));
if (bits < 15) {
hold += input[_in++] << bits;
bits += 8;
hold += input[_in++] << bits;
bits += 8;
}
here = dcode[hold & dmask];
dodist:
for (;;) { // goto emulation
op = here >>> 24/*here.bits*/;
hold >>>= op;
bits -= op;
op = (here >>> 16) & 0xff/*here.op*/;
if (op & 16) { /* distance base */
dist = here & 0xffff/*here.val*/;
op &= 15; /* number of extra bits */
if (bits < op) {
hold += input[_in++] << bits;
bits += 8;
if (bits < op) {
hold += input[_in++] << bits;
bits += 8;
}
}
dist += hold & ((1 << op) - 1);
//#ifdef INFLATE_STRICT
if (dist > dmax) {
strm.msg = 'invalid distance too far back';
state.mode = BAD;
break top;
}
//#endif
hold >>>= op;
bits -= op;
//Tracevv((stderr, "inflate: distance %u\n", dist));
op = _out - beg; /* max distance in output */
if (dist > op) { /* see if copy from window */
op = dist - op; /* distance back in window */
if (op > whave) {
if (state.sane) {
strm.msg = 'invalid distance too far back';
state.mode = BAD;
break top;
}
// (!) This block is disabled in zlib defailts,
// don't enable it for binary compatibility
//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
// if (len <= op - whave) {
// do {
// output[_out++] = 0;
// } while (--len);
// continue top;
// }
// len -= op - whave;
// do {
// output[_out++] = 0;
// } while (--op > whave);
// if (op === 0) {
// from = _out - dist;
// do {
// output[_out++] = output[from++];
// } while (--len);
// continue top;
// }
//#endif
}
from = 0; // window index
from_source = s_window;
if (wnext === 0) { /* very common case */
from += wsize - op;
if (op < len) { /* some from window */
len -= op;
do {
output[_out++] = s_window[from++];
} while (--op);
from = _out - dist; /* rest from output */
from_source = output;
}
}
else if (wnext < op) { /* wrap around window */
from += wsize + wnext - op;
op -= wnext;
if (op < len) { /* some from end of window */
len -= op;
do {
output[_out++] = s_window[from++];
} while (--op);
from = 0;
if (wnext < len) { /* some from start of window */
op = wnext;
len -= op;
do {
output[_out++] = s_window[from++];
} while (--op);
from = _out - dist; /* rest from output */
from_source = output;
}
}
}
else { /* contiguous in window */
from += wnext - op;
if (op < len) { /* some from window */
len -= op;
do {
output[_out++] = s_window[from++];
} while (--op);
from = _out - dist; /* rest from output */
from_source = output;
}
}
while (len > 2) {
output[_out++] = from_source[from++];
output[_out++] = from_source[from++];
output[_out++] = from_source[from++];
len -= 3;
}
if (len) {
output[_out++] = from_source[from++];
if (len > 1) {
output[_out++] = from_source[from++];
}
}
}
else {
from = _out - dist; /* copy direct from output */
do { /* minimum length is three */
output[_out++] = output[from++];
output[_out++] = output[from++];
output[_out++] = output[from++];
len -= 3;
} while (len > 2);
if (len) {
output[_out++] = output[from++];
if (len > 1) {
output[_out++] = output[from++];
}
}
}
}
else if ((op & 64) === 0) { /* 2nd level distance code */
here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
continue dodist;
}
else {
strm.msg = 'invalid distance code';
state.mode = BAD;
break top;
}
break; // need to emulate goto via "continue"
}
}
else if ((op & 64) === 0) { /* 2nd level length code */
here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
continue dolen;
}
else if (op & 32) { /* end-of-block */
//Tracevv((stderr, "inflate: end of block\n"));
state.mode = TYPE;
break top;
}
else {
strm.msg = 'invalid literal/length code';
state.mode = BAD;
break top;
}
break; // need to emulate goto via "continue"
}
} while (_in < last && _out < end);
/* return unused bytes (on entry, bits < 8, so in won't go too far back) */
len = bits >> 3;
_in -= len;
bits -= len << 3;
hold &= (1 << bits) - 1;
/* update state and return */
strm.next_in = _in;
strm.next_out = _out;
strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
state.hold = hold;
state.bits = bits;
return;
};

1527
vendor/pako/lib/zlib/inflate.js vendored Normal file

File diff suppressed because it is too large Load Diff

322
vendor/pako/lib/zlib/inftrees.js vendored Normal file
View File

@ -0,0 +1,322 @@
import * as utils from "../utils/common.js";
var MAXBITS = 15;
var ENOUGH_LENS = 852;
var ENOUGH_DISTS = 592;
//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
var CODES = 0;
var LENS = 1;
var DISTS = 2;
var lbase = [ /* Length codes 257..285 base */
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
];
var lext = [ /* Length codes 257..285 extra */
16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
];
var dbase = [ /* Distance codes 0..29 base */
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577, 0, 0
];
var dext = [ /* Distance codes 0..29 extra */
16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
28, 28, 29, 29, 64, 64
];
export default function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts)
{
var bits = opts.bits;
//here = opts.here; /* table entry for duplication */
var len = 0; /* a code's length in bits */
var sym = 0; /* index of code symbols */
var min = 0, max = 0; /* minimum and maximum code lengths */
var root = 0; /* number of index bits for root table */
var curr = 0; /* number of index bits for current table */
var drop = 0; /* code bits to drop for sub-table */
var left = 0; /* number of prefix codes available */
var used = 0; /* code entries in table used */
var huff = 0; /* Huffman code */
var incr; /* for incrementing code, index */
var fill; /* index for replicating entries */
var low; /* low bits for current root entry */
var mask; /* mask for low root bits */
var next; /* next available space in table */
var base = null; /* base value table to use */
var base_index = 0;
// var shoextra; /* extra bits table to use */
var end; /* use base and extra for symbol > end */
var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */
var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */
var extra = null;
var extra_index = 0;
var here_bits, here_op, here_val;
/*
Process a set of code lengths to create a canonical Huffman code. The
code lengths are lens[0..codes-1]. Each length corresponds to the
symbols 0..codes-1. The Huffman code is generated by first sorting the
symbols by length from short to long, and retaining the symbol order
for codes with equal lengths. Then the code starts with all zero bits
for the first code of the shortest length, and the codes are integer
increments for the same length, and zeros are appended as the length
increases. For the deflate format, these bits are stored backwards
from their more natural integer increment ordering, and so when the
decoding tables are built in the large loop below, the integer codes
are incremented backwards.
This routine assumes, but does not check, that all of the entries in
lens[] are in the range 0..MAXBITS. The caller must assure this.
1..MAXBITS is interpreted as that code length. zero means that that
symbol does not occur in this code.
The codes are sorted by computing a count of codes for each length,
creating from that a table of starting indices for each length in the
sorted table, and then entering the symbols in order in the sorted
table. The sorted table is work[], with that space being provided by
the caller.
The length counts are used for other purposes as well, i.e. finding
the minimum and maximum length codes, determining if there are any
codes at all, checking for a valid set of lengths, and looking ahead
at length counts to determine sub-table sizes when building the
decoding tables.
*/
/* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
for (len = 0; len <= MAXBITS; len++) {
count[len] = 0;
}
for (sym = 0; sym < codes; sym++) {
count[lens[lens_index + sym]]++;
}
/* bound code lengths, force root to be within code lengths */
root = bits;
for (max = MAXBITS; max >= 1; max--) {
if (count[max] !== 0) { break; }
}
if (root > max) {
root = max;
}
if (max === 0) { /* no symbols to code at all */
//table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */
//table.bits[opts.table_index] = 1; //here.bits = (var char)1;
//table.val[opts.table_index++] = 0; //here.val = (var short)0;
table[table_index++] = (1 << 24) | (64 << 16) | 0;
//table.op[opts.table_index] = 64;
//table.bits[opts.table_index] = 1;
//table.val[opts.table_index++] = 0;
table[table_index++] = (1 << 24) | (64 << 16) | 0;
opts.bits = 1;
return 0; /* no symbols, but wait for decoding to report error */
}
for (min = 1; min < max; min++) {
if (count[min] !== 0) { break; }
}
if (root < min) {
root = min;
}
/* check for an over-subscribed or incomplete set of lengths */
left = 1;
for (len = 1; len <= MAXBITS; len++) {
left <<= 1;
left -= count[len];
if (left < 0) {
return -1;
} /* over-subscribed */
}
if (left > 0 && (type === CODES || max !== 1)) {
return -1; /* incomplete set */
}
/* generate offsets into symbol table for each length for sorting */
offs[1] = 0;
for (len = 1; len < MAXBITS; len++) {
offs[len + 1] = offs[len] + count[len];
}
/* sort symbols by length, by symbol order within each length */
for (sym = 0; sym < codes; sym++) {
if (lens[lens_index + sym] !== 0) {
work[offs[lens[lens_index + sym]]++] = sym;
}
}
/*
Create and fill in decoding tables. In this loop, the table being
filled is at next and has curr index bits. The code being used is huff
with length len. That code is converted to an index by dropping drop
bits off of the bottom. For codes where len is less than drop + curr,
those top drop + curr - len bits are incremented through all values to
fill the table with replicated entries.
root is the number of index bits for the root table. When len exceeds
root, sub-tables are created pointed to by the root entry with an index
of the low root bits of huff. This is saved in low to check for when a
new sub-table should be started. drop is zero when the root table is
being filled, and drop is root when sub-tables are being filled.
When a new sub-table is needed, it is necessary to look ahead in the
code lengths to determine what size sub-table is needed. The length
counts are used for this, and so count[] is decremented as codes are
entered in the tables.
used keeps track of how many table entries have been allocated from the
provided *table space. It is checked for LENS and DIST tables against
the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
the initial root table size constants. See the comments in inftrees.h
for more information.
sym increments through all symbols, and the loop terminates when
all codes of length max, i.e. all codes, have been processed. This
routine permits incomplete codes, so another loop after this one fills
in the rest of the decoding tables with invalid code markers.
*/
/* set up for code type */
// poor man optimization - use if-else instead of switch,
// to avoid deopts in old v8
if (type === CODES) {
base = extra = work; /* dummy value--not used */
end = 19;
} else if (type === LENS) {
base = lbase;
base_index -= 257;
extra = lext;
extra_index -= 257;
end = 256;
} else { /* DISTS */
base = dbase;
extra = dext;
end = -1;
}
/* initialize opts for loop */
huff = 0; /* starting code */
sym = 0; /* starting code symbol */
len = min; /* starting code length */
next = table_index; /* current table to fill in */
curr = root; /* current table index bits */
drop = 0; /* current bits to drop from code for index */
low = -1; /* trigger new sub-table when len > root */
used = 1 << root; /* use root table entries */
mask = used - 1; /* mask for comparing low */
/* check available table space */
if ((type === LENS && used > ENOUGH_LENS) ||
(type === DISTS && used > ENOUGH_DISTS)) {
return 1;
}
/* process all codes and make table entries */
for (;;) {
/* create table entry */
here_bits = len - drop;
if (work[sym] < end) {
here_op = 0;
here_val = work[sym];
}
else if (work[sym] > end) {
here_op = extra[extra_index + work[sym]];
here_val = base[base_index + work[sym]];
}
else {
here_op = 32 + 64; /* end of block */
here_val = 0;
}
/* replicate for those indices with low len bits equal to huff */
incr = 1 << (len - drop);
fill = 1 << curr;
min = fill; /* save offset to next table */
do {
fill -= incr;
table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
} while (fill !== 0);
/* backwards increment the len-bit code huff */
incr = 1 << (len - 1);
while (huff & incr) {
incr >>= 1;
}
if (incr !== 0) {
huff &= incr - 1;
huff += incr;
} else {
huff = 0;
}
/* go to next symbol, update count, len */
sym++;
if (--count[len] === 0) {
if (len === max) { break; }
len = lens[lens_index + work[sym]];
}
/* create new sub-table if needed */
if (len > root && (huff & mask) !== low) {
/* if first time, transition to sub-tables */
if (drop === 0) {
drop = root;
}
/* increment past last table */
next += min; /* here min is 1 << curr */
/* determine length of next table */
curr = len - drop;
left = 1 << curr;
while (curr + drop < max) {
left -= count[curr + drop];
if (left <= 0) { break; }
curr++;
left <<= 1;
}
/* check for enough space */
used += 1 << curr;
if ((type === LENS && used > ENOUGH_LENS) ||
(type === DISTS && used > ENOUGH_DISTS)) {
return 1;
}
/* point entry in root table to sub-table */
low = huff & mask;
/*table.op[low] = curr;
table.bits[low] = root;
table.val[low] = next - opts.table_index;*/
table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
}
}
/* fill in remaining table entry if code is incomplete (guaranteed to have
at most one remaining entry, since if the code is incomplete, the
maximum code length that was allowed to get this far is one bit) */
if (huff !== 0) {
//table.op[next + huff] = 64; /* invalid code marker */
//table.bits[next + huff] = len - drop;
//table.val[next + huff] = 0;
table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
}
/* set return parameters */
//opts.table_index += used;
opts.bits = root;
return 0;
};

11
vendor/pako/lib/zlib/messages.js vendored Normal file
View File

@ -0,0 +1,11 @@
export default {
2: 'need dictionary', /* Z_NEED_DICT 2 */
1: 'stream end', /* Z_STREAM_END 1 */
0: '', /* Z_OK 0 */
'-1': 'file error', /* Z_ERRNO (-1) */
'-2': 'stream error', /* Z_STREAM_ERROR (-2) */
'-3': 'data error', /* Z_DATA_ERROR (-3) */
'-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */
'-5': 'buffer error', /* Z_BUF_ERROR (-5) */
'-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */
};

1195
vendor/pako/lib/zlib/trees.js vendored Normal file

File diff suppressed because it is too large Load Diff

24
vendor/pako/lib/zlib/zstream.js vendored Normal file
View File

@ -0,0 +1,24 @@
export default function ZStream() {
/* next input byte */
this.input = null; // JS specific, because we have no pointers
this.next_in = 0;
/* number of bytes available at input */
this.avail_in = 0;
/* total number of input bytes read so far */
this.total_in = 0;
/* next output byte should be put there */
this.output = null; // JS specific, because we have no pointers
this.next_out = 0;
/* remaining free space at output */
this.avail_out = 0;
/* total number of bytes output so far */
this.total_out = 0;
/* last error message, NULL if no error */
this.msg = ''/*Z_NULL*/;
/* not visible by applications */
this.state = null;
/* best guess about the data type: binary or text */
this.data_type = 2/*Z_UNKNOWN*/;
/* adler32 value of the uncompressed data */
this.adler = 0;
}

255
vendor/promise.js vendored Normal file
View File

@ -0,0 +1,255 @@
/* Copyright (c) 2014 Taylor Hakes
* Copyright (c) 2014 Forbes Lindesay
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function (root) {
// Store setTimeout reference so promise-polyfill will be unaffected by
// other code modifying setTimeout (like sinon.useFakeTimers())
var setTimeoutFunc = setTimeout;
function noop() {}
// Polyfill for Function.prototype.bind
function bind(fn, thisArg) {
return function () {
fn.apply(thisArg, arguments);
};
}
function Promise(fn) {
if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
if (typeof fn !== 'function') throw new TypeError('not a function');
this._state = 0;
this._handled = false;
this._value = undefined;
this._deferreds = [];
doResolve(fn, this);
}
function handle(self, deferred) {
while (self._state === 3) {
self = self._value;
}
if (self._state === 0) {
self._deferreds.push(deferred);
return;
}
self._handled = true;
Promise._immediateFn(function () {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
return;
}
var ret;
try {
ret = cb(self._value);
} catch (e) {
reject(deferred.promise, e);
return;
}
resolve(deferred.promise, ret);
});
}
function resolve(self, newValue) {
try {
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (newValue instanceof Promise) {
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
doResolve(bind(then, newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
} catch (e) {
reject(self, e);
}
}
function reject(self, newValue) {
self._state = 2;
self._value = newValue;
finale(self);
}
function finale(self) {
if (self._state === 2 && self._deferreds.length === 0) {
Promise._immediateFn(function() {
if (!self._handled) {
Promise._unhandledRejectionFn(self._value);
}
});
}
for (var i = 0, len = self._deferreds.length; i < len; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*/
function doResolve(fn, self) {
var done = false;
try {
fn(function (value) {
if (done) return;
done = true;
resolve(self, value);
}, function (reason) {
if (done) return;
done = true;
reject(self, reason);
});
} catch (ex) {
if (done) return;
done = true;
reject(self, ex);
}
}
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
Promise.prototype.then = function (onFulfilled, onRejected) {
var prom = new (this.constructor)(noop);
handle(this, new Handler(onFulfilled, onRejected, prom));
return prom;
};
Promise.all = function (arr) {
var args = Array.prototype.slice.call(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if (typeof then === 'function') {
then.call(val, function (val) {
res(i, val);
}, reject);
return;
}
}
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
} catch (ex) {
reject(ex);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
Promise.resolve = function (value) {
if (value && typeof value === 'object' && value.constructor === Promise) {
return value;
}
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
for (var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
};
// Use polyfill for setImmediate for performance gains
Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) ||
function (fn) {
setTimeoutFunc(fn, 0);
};
Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
if (typeof console !== 'undefined' && console) {
console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
}
};
/**
* Set the immediate function to execute callbacks
* @param fn {function} Function to execute
* @deprecated
*/
Promise._setImmediateFn = function _setImmediateFn(fn) {
Promise._immediateFn = fn;
};
/**
* Change the function to execute on unhandled rejection
* @param {function} fn Function to execute on unhandled rejection
* @deprecated
*/
Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) {
Promise._unhandledRejectionFn = fn;
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = Promise;
} else if (!root.Promise) {
root.Promise = Promise;
}
})(this);

6463
vendor/sinon.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html class="noVNC_loading">
<head>
<!--
@ -59,6 +59,15 @@
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<!-- this is included as a normal file in order to catch script-loading errors as well -->
<script type="text/javascript" src="app/error-handler.js"></script>
<!-- begin scripts -->
<!-- promise polyfills promises for IE11 -->
<script src="vendor/promise.js"></script>
<script src="vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
<script type="module" src="app/ui.js"></script>
<!-- end scripts -->
</head>
<body>
@ -320,12 +329,5 @@
<source src="app/sounds/bell.oga" type="audio/ogg">
<source src="app/sounds/bell.mp3" type="audio/mpeg">
</audio>
<!-- begin scripts -->
<script src="core/util.js"></script>
<script src="app/webutil.js"></script>
<script src="app/ui.js"></script>
<!-- end scripts -->
</body>
</html>

View File

@ -22,18 +22,33 @@
Remove this if you use the .htaccess -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<!-- Icons (see Makefile for what the sizes are for) -->
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png">
<link rel="icon" sizes="24x24" type="image/png" href="app/images/icons/novnc-24x24.png">
<link rel="icon" sizes="32x32" type="image/png" href="app/images/icons/novnc-32x32.png">
<link rel="icon" sizes="48x48" type="image/png" href="app/images/icons/novnc-48x48.png">
<link rel="icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png">
<link rel="icon" sizes="64x64" type="image/png" href="app/images/icons/novnc-64x64.png">
<link rel="icon" sizes="72x72" type="image/png" href="app/images/icons/novnc-72x72.png">
<link rel="icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png">
<link rel="icon" sizes="96x96" type="image/png" href="app/images/icons/novnc-96x96.png">
<link rel="icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png">
<link rel="icon" sizes="144x144" type="image/png" href="app/images/icons/novnc-144x144.png">
<link rel="icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png">
<link rel="icon" sizes="192x192" type="image/png" href="app/images/icons/novnc-192x192.png">
<link rel="icon" sizes="any" type="image/svg+xml" href="app/images/icons/novnc-icon.svg">
<!-- Repeated last so that legacy handling will pick this -->
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png">
<!-- Apple iOS Safari settings -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<!-- App Start Icon -->
<link rel="apple-touch-startup-image" href="app/images/screen_320x460.png" />
<!-- For iOS devices set the icon to use if user bookmarks app on their homescreen -->
<link rel="apple-touch-icon" href="app/images/screen_57x57.png">
<!--
<link rel="apple-touch-icon-precomposed" href="app/images/screen_57x57.png" />
-->
<!-- Home Screen Icons (favourites and bookmarks use the normal icons) -->
<link rel="apple-touch-icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png">
<link rel="apple-touch-icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png">
<link rel="apple-touch-icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png">
<link rel="apple-touch-icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png">
<!-- Stylesheets -->
<link rel="stylesheet" href="app/styles/auto.css">
@ -42,52 +57,19 @@
<script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<script src="core/util.js"></script>
<script src="app/webutil.js"></script>
</head>
<body style="margin: 0px;">
<div id="noVNC_container">
<div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
<table border=0 width="100%"><tr>
<td><div id="noVNC_status" style="position: relative; height: auto;">
Loading
</div></td>
<td width="1%"><div id="noVNC_buttons">
<input type=button value="Send CtrlAltDel"
id="sendCtrlAltDelButton">
<span id="noVNC_xvp_buttons">
<input type=button value="Shutdown"
id="xvpShutdownButton">
<input type=button value="Reboot"
id="xvpRebootButton">
<input type=button value="Reset"
id="xvpResetButton">
</span>
</div></td>
</tr></table>
</div>
<canvas id="noVNC_canvas" width="640px" height="20px">
Canvas not supported.
</canvas>
</div>
<script>
/*jslint white: false */
/*global window, $, Util, RFB, */
"use strict";
<!-- promise polyfills promises for IE11 -->
<script src="vendor/promise.js"></script>
<script src="vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
<script type="module">
// Load supporting scripts
WebUtil.load_scripts({
'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
"input/xtscancodes.js", "input/util.js", "input/devices.js",
"display.js", "inflator.js", "rfb.js", "input/keysym.js"]});
import * as WebUtil from './app/webutil.js';
import RFB from './core/rfb.js';
var rfb;
var resizeTimeout;
var desktopName;
function UIresize() {
if (WebUtil.getConfigVar('resize', false)) {
var innerW = window.innerWidth;
@ -109,15 +91,17 @@
msg = 'Password Required: ';
}
var html;
html = '<form onsubmit="return setPassword();"';
html += ' style="margin-bottom: 0px">';
html += '<label></label>'
html += '<input type=password size=10 id="password_input" class="noVNC_status">';
html += '<\/form>';
var form = document.createElement('form');
form.style = 'margin-bottom: 0px';
form.innerHTML = '<label></label>'
form.innerHTML += '<input type=password size=10 id="password_input" class="noVNC_status">';
form.onsubmit = setPassword;
// bypass status() because it sets text content
document.getElementById('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
document.getElementById('noVNC_status').innerHTML = html;
document.getElementById('noVNC_status').innerHTML = '';
document.getElementById('noVNC_status').appendChild(form);
document.getElementById('noVNC_status').querySelector('label').textContent = msg;
}
function setPassword() {
@ -215,9 +199,6 @@
}
}
window.onscriptsload = function () {
var host, port, password, path, token;
document.getElementById('sendCtrlAltDelButton').style.display = "inline";
document.getElementById('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
document.getElementById('xvpShutdownButton').onclick = xvpShutdown;
@ -227,8 +208,8 @@
WebUtil.init_logging(WebUtil.getConfigVar('logging', 'warn'));
document.title = unescape(WebUtil.getConfigVar('title', 'noVNC'));
// By default, use the host and port of server that served this file
host = WebUtil.getConfigVar('host', window.location.hostname);
port = WebUtil.getConfigVar('port', window.location.port);
var host = WebUtil.getConfigVar('host', window.location.hostname);
var port = WebUtil.getConfigVar('port', window.location.port);
// if port == 80 (or 443) then it won't be present and should be
// set manually
@ -241,23 +222,23 @@
}
}
password = WebUtil.getConfigVar('password', '');
path = WebUtil.getConfigVar('path', 'websockify');
var password = WebUtil.getConfigVar('password', '');
var path = WebUtil.getConfigVar('path', 'websockify');
// If a token variable is passed in, set the parameter in a cookie.
// This is used by nova-novncproxy.
token = WebUtil.getConfigVar('token', null);
var token = WebUtil.getConfigVar('token', null);
if (token) {
// if token is already present in the path we should use it
path = WebUtil.injectParamIfMissing(path, "token", token);
WebUtil.createCookie('token', token, 1)
}
(function() {
if ((!host) || (!port)) {
status('Must specify host and port in URL', 'error');
return;
}
try {
@ -282,8 +263,35 @@
}
rfb.connect(host, port, password, path);
};
})();
</script>
</head>
<body style="margin: 0px;">
<div id="noVNC_container">
<div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
<table border=0 width="100%"><tr>
<td><div id="noVNC_status" style="position: relative; height: auto;">
Loading
</div></td>
<td width="1%"><div id="noVNC_buttons">
<input type=button value="Send CtrlAltDel"
id="sendCtrlAltDelButton">
<span id="noVNC_xvp_buttons">
<input type=button value="Shutdown"
id="xvpShutdownButton">
<input type=button value="Reboot"
id="xvpRebootButton">
<input type=button value="Reset"
id="xvpResetButton">
</span>
</div></td>
</tr></table>
</div>
<canvas id="noVNC_canvas" width="640px" height="20px">
Canvas not supported.
</canvas>
</div>
</body>
</html>