Merge pull request #950 from samhed/disconnectapi

Abstract information from RFB to the UI
This commit is contained in:
Samuel Mannehed 2017-11-17 11:01:11 +01:00 committed by GitHub
commit db46e36eb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 506 additions and 474 deletions

View File

@ -164,7 +164,7 @@ Localizer.prototype = {
process(document.body, true);
},
}
};
export var l10n = new Localizer();
export default l10n.get.bind(l10n);

183
app/ui.js
View File

@ -12,7 +12,7 @@
/* global window, document.getElementById, Util, WebUtil, RFB, Display */
import * as Log from '../core/util/logging.js';
import _, { l10n } from '../core/util/localization.js';
import _, { l10n } from './localization.js';
import { isTouchDevice } from '../core/util/browsers.js';
import { setCapture, getPointerEvent } from '../core/util/events.js';
import KeyTable from "../core/input/keysym.js";
@ -105,7 +105,7 @@ var UI = {
UI.updateViewClip();
UI.updateVisualState();
UI.updateVisualState('init');
document.documentElement.classList.remove("noVNC_loading");
@ -388,54 +388,41 @@ var UI = {
* VISUAL
* ------v------*/
updateState: function(event) {
var msg;
// Disable/enable controls depending on connection state
updateVisualState: function(state) {
document.documentElement.classList.remove("noVNC_connecting");
document.documentElement.classList.remove("noVNC_connected");
document.documentElement.classList.remove("noVNC_disconnecting");
document.documentElement.classList.remove("noVNC_reconnecting");
switch (event.detail.state) {
let transition_elem = document.getElementById("noVNC_transition_text");
switch (state) {
case 'init':
break;
case 'connecting':
document.getElementById("noVNC_transition_text").textContent = _("Connecting...");
transition_elem.textContent = _("Connecting...");
document.documentElement.classList.add("noVNC_connecting");
break;
case 'connected':
UI.connected = true;
UI.inhibit_reconnect = false;
UI.doneInitialResize = false;
document.documentElement.classList.add("noVNC_connected");
if (UI.getSetting('encrypt')) {
msg = _("Connected (encrypted) to ") + UI.desktopName;
} else {
msg = _("Connected (unencrypted) to ") + UI.desktopName;
}
UI.showStatus(msg);
document.getElementById('noVNC_canvas').focus();
break;
case 'disconnecting':
UI.connected = false;
document.getElementById("noVNC_transition_text").textContent = _("Disconnecting...");
transition_elem.textContent = _("Disconnecting...");
document.documentElement.classList.add("noVNC_disconnecting");
break;
case 'disconnected':
UI.showStatus(_("Disconnected"));
break;
case 'reconnecting':
transition_elem.textContent = _("Reconnecting...");
document.documentElement.classList.add("noVNC_reconnecting");
break;
default:
msg = "Invalid UI state";
Log.Error(msg);
UI.showStatus(msg, 'error');
break;
Log.Error("Invalid visual state: " + state);
UI.showStatus(_("Internal error"), 'error');
return;
}
UI.updateVisualState();
},
// Disable/enable controls depending on connection state
updateVisualState: function() {
//Log.Debug(">> updateVisualState");
UI.enableDisableViewClip();
if (UI.connected) {
@ -481,8 +468,6 @@ var UI = {
// State change also closes the password dialog
document.getElementById('noVNC_password_dlg')
.classList.remove('noVNC_open');
//Log.Debug("<< updateVisualState");
},
showStatus: function(text, status_type, time) {
@ -494,21 +479,40 @@ var UI = {
status_type = 'normal';
}
statusElem.classList.remove("noVNC_status_normal");
statusElem.classList.remove("noVNC_status_warn");
statusElem.classList.remove("noVNC_status_error");
// Don't overwrite more severe visible statuses and never
// errors. Only shows the first error.
let visible_status_type = 'none';
if (statusElem.classList.contains("noVNC_open")) {
if (statusElem.classList.contains("noVNC_status_error")) {
visible_status_type = 'error';
} else if (statusElem.classList.contains("noVNC_status_warn")) {
visible_status_type = 'warn';
} else {
visible_status_type = 'normal';
}
}
if (visible_status_type === 'error' ||
(visible_status_type === 'warn' && status_type === 'normal')) {
return;
}
switch (status_type) {
case 'error':
statusElem.classList.remove("noVNC_status_warn");
statusElem.classList.remove("noVNC_status_normal");
statusElem.classList.add("noVNC_status_error");
break;
case 'warning':
case 'warn':
statusElem.classList.remove("noVNC_status_error");
statusElem.classList.remove("noVNC_status_normal");
statusElem.classList.add("noVNC_status_warn");
break;
case 'error':
statusElem.classList.add("noVNC_status_error");
break;
case 'normal':
case 'info':
default:
statusElem.classList.remove("noVNC_status_error");
statusElem.classList.remove("noVNC_status_warn");
statusElem.classList.add("noVNC_status_normal");
break;
}
@ -532,10 +536,6 @@ var UI = {
document.getElementById('noVNC_status').classList.remove("noVNC_open");
},
notification: function (e) {
UI.showStatus(e.detail.message, e.detail.level);
},
activateControlbar: function(event) {
clearTimeout(UI.idleControlbarTimeout);
// We manipulate the anchor instead of the actual control
@ -1012,16 +1012,19 @@ var UI = {
password = undefined;
}
UI.hideStatus();
if (!host) {
var msg = _("Must set host");
Log.Error(msg);
UI.showStatus(msg, 'error');
Log.Error("Can't connect when host is: " + host);
UI.showStatus(_("Must set host"), 'error');
return;
}
UI.closeAllPanels();
UI.closeConnectPanel();
UI.updateVisualState('connecting');
UI.updateViewOnly();
var url;
@ -1038,10 +1041,10 @@ var UI = {
{ shared: UI.getSetting('shared'),
repeaterID: UI.getSetting('repeaterID'),
credentials: { password: password } });
UI.rfb.addEventListener("notification", UI.notification);
UI.rfb.addEventListener("updatestate", UI.updateState);
UI.rfb.addEventListener("connect", UI.connectFinished);
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); });
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
UI.rfb.addEventListener("bell", UI.bell);
@ -1053,9 +1056,13 @@ var UI = {
UI.closeAllPanels();
UI.rfb.disconnect();
UI.connected = false;
// Disable automatic reconnecting
UI.inhibit_reconnect = true;
UI.updateVisualState('disconnecting');
// Don't display the connection settings until we're actually disconnected
},
@ -1070,33 +1077,76 @@ var UI = {
UI.connect(null, UI.reconnect_password);
},
disconnectFinished: function (e) {
if (typeof e.detail.reason !== 'undefined') {
UI.showStatus(e.detail.reason, 'error');
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
document.getElementById("noVNC_transition_text").textContent = _("Reconnecting...");
document.documentElement.classList.add("noVNC_reconnecting");
var delay = parseInt(UI.getSetting('reconnect_delay'));
UI.reconnect_callback = setTimeout(UI.reconnect, delay);
return;
}
UI.openControlbar();
UI.openConnectPanel();
},
cancelReconnect: function() {
if (UI.reconnect_callback !== null) {
clearTimeout(UI.reconnect_callback);
UI.reconnect_callback = null;
}
document.documentElement.classList.remove("noVNC_reconnecting");
UI.updateVisualState('disconnected');
UI.openControlbar();
UI.openConnectPanel();
},
connectFinished: function (e) {
UI.connected = true;
UI.inhibit_reconnect = false;
UI.doneInitialResize = false;
let msg;
if (UI.getSetting('encrypt')) {
msg = _("Connected (encrypted) to ") + UI.desktopName;
} else {
msg = _("Connected (unencrypted) to ") + UI.desktopName;
}
UI.showStatus(msg);
UI.updateVisualState('connected');
// Do this last because it can only be used on rendered elements
document.getElementById('noVNC_canvas').focus();
},
disconnectFinished: function (e) {
// This variable is ideally set when disconnection starts, but
// when the disconnection isn't clean or if it is initiated by
// the server, we need to do it here as well since
// UI.disconnect() won't be used in those cases.
UI.connected = false;
if (!e.detail.clean) {
UI.updateVisualState('disconnected');
UI.showStatus(_("Something went wrong, connection is closed"),
'error');
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
UI.updateVisualState('reconnecting');
var delay = parseInt(UI.getSetting('reconnect_delay'));
UI.reconnect_callback = setTimeout(UI.reconnect, delay);
return;
} else {
UI.updateVisualState('disconnected');
UI.showStatus(_("Disconnected"), 'normal');
}
UI.openControlbar();
UI.openConnectPanel();
},
securityFailed: function (e) {
let msg = "";
// On security failures we might get a string with a reason
// directly from the server. Note that we can't control if
// this string is translated or not.
if ('reason' in e.detail) {
msg = _("New connection has been rejected with reason: ") +
e.detail.reason;
} else {
msg = _("New connection has been rejected");
}
UI.showStatus(msg, 'error');
},
/* ------^-------
* /CONNECTION
* ==============
@ -1112,9 +1162,8 @@ var UI = {
document.getElementById('noVNC_password_input').focus();
}, 100);
var msg = _("Password is required");
Log.Warn(msg);
UI.showStatus(msg, "warning");
Log.Warn("Server asked for a password");
UI.showStatus(_("Password is required"), "warning");
},
setPassword: function(e) {

View File

@ -26,7 +26,7 @@ export var encodings = {
pseudoEncodingContinuousUpdates: -313,
pseudoEncodingCompressLevel9: -247,
pseudoEncodingCompressLevel0: -256,
}
};
export function encodingName(num) {
switch (num) {

View File

@ -1,7 +1,7 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2016 Samuel Mannehed for Cendio AB
* Copyright (C) 2017 Samuel Mannehed for Cendio AB
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
@ -11,7 +11,6 @@
*/
import * as Log from './util/logging.js';
import _ from './util/localization.js';
import { decodeUTF8 } from './util/strings.js';
import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
import EventTargetMixin from './util/eventtarget.js';
@ -45,7 +44,7 @@ export default function RFB(target, url, options) {
this._url = url;
// Connection details
options = options || {}
options = options || {};
this._rfb_credentials = options.credentials || {};
this._shared = 'shared' in options ? !!options.shared : true;
this._repeaterID = options.repeaterID || '';
@ -54,7 +53,7 @@ export default function RFB(target, url, options) {
this._rfb_connection_state = '';
this._rfb_init_state = '';
this._rfb_auth_scheme = '';
this._rfb_disconnect_reason = "";
this._rfb_clean_disconnect = true;
// Server capabilities
this._rfb_version = 0;
@ -190,38 +189,40 @@ export default function RFB(target, url, options) {
this._rfb_init_state = 'ProtocolVersion';
Log.Debug("Starting VNC handshake");
} else {
this._fail("Unexpected server connection");
this._fail("Unexpected server connection while " +
this._rfb_connection_state);
}
}.bind(this));
this._sock.on('close', function (e) {
Log.Warn("WebSocket on-close event");
var msg = "";
if (e.code) {
msg = " (code: " + e.code;
msg = "(code: " + e.code;
if (e.reason) {
msg += ", reason: " + e.reason;
}
msg += ")";
}
switch (this._rfb_connection_state) {
case 'disconnecting':
this._updateConnectionState('disconnected');
break;
case 'connecting':
this._fail('Failed to connect to server', msg);
this._fail("Connection closed " + msg);
break;
case 'connected':
// Handle disconnects that were initiated server-side
this._updateConnectionState('disconnecting');
this._updateConnectionState('disconnected');
break;
case 'disconnecting':
// Normal disconnection path
this._updateConnectionState('disconnected');
break;
case 'disconnected':
this._fail("Unexpected server disconnect",
"Already disconnected: " + msg);
this._fail("Unexpected server disconnect " +
"when already disconnected " + msg);
break;
default:
this._fail("Unexpected server disconnect",
"Not in any state yet: " + msg);
this._fail("Unexpected server disconnect before connecting " +
msg);
break;
}
this._sock.off('close');
@ -384,9 +385,9 @@ RFB.prototype = {
this._sock.open(this._url, ['binary']);
} catch (e) {
if (e.name === 'SyntaxError') {
this._fail("Invalid host or port value given", e);
this._fail("Invalid host or port (" + e + ")");
} else {
this._fail("Error while connecting", e);
this._fail("Error when opening socket (" + e + ")");
}
}
@ -512,8 +513,6 @@ RFB.prototype = {
// State change actions
this._rfb_connection_state = state;
var event = new CustomEvent("updatestate", { detail: { state: state } });
this.dispatchEvent(event);
var smsg = "New state '" + state + "', was '" + oldstate + "'.";
Log.Debug(smsg);
@ -528,59 +527,54 @@ RFB.prototype = {
}
switch (state) {
case 'disconnected':
// Fire disconnected event after updatestate event since
// we don't know if the UI only displays the latest message
if (this._rfb_disconnect_reason !== "") {
event = new CustomEvent("disconnect",
{ detail: { reason: this._rfb_disconnect_reason } });
} else {
// No reason means clean disconnect
event = new CustomEvent("disconnect", { detail: {} });
}
this.dispatchEvent(event);
break;
case 'connecting':
this._connect();
break;
case 'connected':
var event = new CustomEvent("connect", { detail: {} });
this.dispatchEvent(event);
break;
case 'disconnecting':
this._disconnect();
this._disconnTimer = setTimeout(function () {
this._rfb_disconnect_reason = _("Disconnect timeout");
Log.Error("Disconnection timed out.");
this._updateConnectionState('disconnected');
}.bind(this), DISCONNECT_TIMEOUT * 1000);
break;
case 'disconnected':
event = new CustomEvent(
"disconnect", { detail:
{ clean: this._rfb_clean_disconnect } });
this.dispatchEvent(event);
break;
}
},
/* Print errors and disconnect
*
* The optional parameter 'details' is used for information that
* The parameter 'details' is used for information that
* should be logged but not sent to the user interface.
*/
_fail: function (msg, details) {
var fullmsg = msg;
if (typeof details !== 'undefined') {
fullmsg = msg + " (" + details + ")";
}
_fail: function (details) {
switch (this._rfb_connection_state) {
case 'disconnecting':
Log.Error("Failed when disconnecting: " + fullmsg);
Log.Error("Failed when disconnecting: " + details);
break;
case 'connected':
Log.Error("Failed while connected: " + fullmsg);
Log.Error("Failed while connected: " + details);
break;
case 'connecting':
Log.Error("Failed when connecting: " + fullmsg);
Log.Error("Failed when connecting: " + details);
break;
default:
Log.Error("RFB failure: " + fullmsg);
Log.Error("RFB failure: " + details);
break;
}
this._rfb_disconnect_reason = msg; //This is sent to the UI
this._rfb_clean_disconnect = false; //This is sent to the UI
// Transition to disconnected without waiting for socket to close
this._updateConnectionState('disconnecting');
@ -589,30 +583,6 @@ RFB.prototype = {
return false;
},
/*
* Send a notification to the UI. Valid levels are:
* 'normal'|'warn'|'error'
*
* NOTE: If this function is called multiple times, remember that the
* interface could be only showing the latest notification.
*/
_notification: function(msg, level) {
switch (level) {
case 'normal':
case 'warn':
case 'error':
Log.Debug("Notification[" + level + "]:" + msg);
break;
default:
Log.Error("Invalid notification level: " + level);
return;
}
var event = new CustomEvent("notification",
{ detail: { message: msg, level: level } });
this.dispatchEvent(event);
},
_setCapability: function (cap, val) {
this._capabilities[cap] = val;
var event = new CustomEvent("capabilities",
@ -716,8 +686,7 @@ RFB.prototype = {
_negotiate_protocol_version: function () {
if (this._sock.rQlen() < 12) {
return this._fail("Error while negotiating with server",
"Incomplete protocol version");
return this._fail("Received incomplete protocol version.");
}
var sversion = this._sock.rQshiftStr(12).substr(4, 7);
@ -742,8 +711,7 @@ RFB.prototype = {
this._rfb_version = 3.8;
break;
default:
return this._fail("Unsupported server",
"Invalid server version: " + sversion);
return this._fail("Invalid server version " + sversion);
}
if (is_repeater) {
@ -785,10 +753,7 @@ RFB.prototype = {
if (this._sock.rQwait("security type", num_types, 1)) { return false; }
if (num_types === 0) {
var strlen = this._sock.rQshift32();
var reason = this._sock.rQshiftStr(strlen);
return this._fail("Error while negotiating with server",
"Security failure: " + reason);
return this._handle_security_failure("no security types");
}
var types = this._sock.rQshiftBytes(num_types);
@ -805,8 +770,7 @@ RFB.prototype = {
} else if (includes(2, types)) {
this._rfb_auth_scheme = 2; // VNC Auth
} else {
return this._fail("Unsupported server",
"Unsupported security types: " + types);
return this._fail("Unsupported security types (types: " + types + ")");
}
this._sock.send([this._rfb_auth_scheme]);
@ -822,6 +786,59 @@ RFB.prototype = {
return this._init_msg(); // jump to authentication
},
/*
* Get the security failure reason if sent from the server and
* send the 'securityfailure' event.
*
* - The optional parameter context can be used to add some extra
* context to the log output.
*
* - The optional parameter security_result_status can be used to
* add a custom status code to the event.
*/
_handle_security_failure: function (context, security_result_status) {
if (typeof context === 'undefined') {
context = "";
} else {
context = " on " + context;
}
if (typeof security_result_status === 'undefined') {
security_result_status = 1; // fail
}
if (this._sock.rQwait("reason length", 4)) {
return false;
}
let strlen = this._sock.rQshift32();
let reason = "";
if (strlen > 0) {
if (this._sock.rQwait("reason", strlen, 8)) { return false; }
reason = this._sock.rQshiftStr(strlen);
}
if (reason !== "") {
let event = new CustomEvent(
"securityfailure",
{ detail: { status: security_result_status, reason: reason } });
this.dispatchEvent(event);
return this._fail("Security negotiation failed" + context +
" (reason: " + reason + ")");
} else {
let event = new CustomEvent(
"securityfailure",
{ detail: { status: security_result_status } });
this.dispatchEvent(event);
return this._fail("Security negotiation failed" + context);
}
},
// authentication
_negotiate_xvp_auth: function () {
if (!this._rfb_credentials.username ||
@ -877,15 +894,13 @@ RFB.prototype = {
if (serverSupportedTunnelTypes[0]) {
if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
return this._fail("Unsupported server",
"Client's tunnel type had the incorrect " +
return this._fail("Client's tunnel type had the incorrect " +
"vendor or signature");
}
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
return false; // wait until we receive the sub auth count to continue
} else {
return this._fail("Unsupported server",
"Server wanted tunnels, but doesn't support " +
return this._fail("Server wanted tunnels, but doesn't support " +
"the notunnel type");
}
},
@ -939,24 +954,19 @@ RFB.prototype = {
this._rfb_auth_scheme = 2;
return this._init_msg();
default:
return this._fail("Unsupported server",
"Unsupported tiny auth scheme: " +
authType);
return this._fail("Unsupported tiny auth scheme " +
"(scheme: " + authType + ")");
}
}
}
return this._fail("Unsupported server",
"No supported sub-auth types!");
return this._fail("No supported sub-auth types!");
},
_negotiate_authentication: function () {
switch (this._rfb_auth_scheme) {
case 0: // connection failed
if (this._sock.rQwait("auth reason", 4)) { return false; }
var strlen = this._sock.rQshift32();
var reason = this._sock.rQshiftStr(strlen);
return this._fail("Authentication failure", reason);
return this._handle_security_failure("authentication scheme");
case 1: // no auth
if (this._rfb_version >= 3.8) {
@ -976,33 +986,30 @@ RFB.prototype = {
return this._negotiate_tight_auth();
default:
return this._fail("Unsupported server",
"Unsupported auth scheme: " +
this._rfb_auth_scheme);
return this._fail("Unsupported auth scheme (scheme: " +
this._rfb_auth_scheme + ")");
}
},
_handle_security_result: function () {
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
switch (this._sock.rQshift32()) {
case 0: // OK
this._rfb_init_state = 'ClientInitialisation';
Log.Debug('Authentication OK');
return this._init_msg();
case 1: // failed
if (this._rfb_version >= 3.8) {
var length = this._sock.rQshift32();
if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
var reason = this._sock.rQshiftStr(length);
return this._fail("Authentication failure", reason);
} else {
return this._fail("Authentication failure");
}
case 2:
return this._fail("Too many authentication attempts");
default:
return this._fail("Unsupported server",
"Unknown SecurityResult");
let status = this._sock.rQshift32();
if (status === 0) { // OK
this._rfb_init_state = 'ClientInitialisation';
Log.Debug('Authentication OK');
return this._init_msg();
} else {
if (this._rfb_version >= 3.8) {
return this._handle_security_failure("security result", status);
} else {
let event = new CustomEvent("securityfailure",
{ detail: { status: status } });
this.dispatchEvent(event);
return this._fail("Security handshake failed");
}
}
},
@ -1181,15 +1188,15 @@ RFB.prototype = {
return this._negotiate_server_init();
default:
return this._fail("Internal error", "Unknown init state: " +
this._rfb_init_state);
return this._fail("Unknown init state (state: " +
this._rfb_init_state + ")");
}
},
_handle_set_colour_map_msg: function () {
Log.Debug("SetColorMapEntries");
return this._fail("Protocol error", "Unexpected SetColorMapEntries message");
return this._fail("Unexpected SetColorMapEntries message");
},
_handle_server_cut_text: function () {
@ -1238,8 +1245,7 @@ RFB.prototype = {
*/
if (!(flags & (1<<31))) {
return this._fail("Internal error",
"Unexpected fence response");
return this._fail("Unexpected fence response");
}
// Filter out unsupported flags
@ -1262,8 +1268,7 @@ RFB.prototype = {
switch (xvp_msg) {
case 0: // XVP_FAIL
Log.Error("Operation Failed");
this._notification("XVP Operation Failed", 'error');
Log.Error("XVP Operation Failed");
break;
case 1: // XVP_INIT
this._rfb_xvp_ver = xvp_ver;
@ -1271,8 +1276,7 @@ RFB.prototype = {
this._setCapability("power", true);
break;
default:
this._fail("Unexpected server message",
"Illegal server XVP message " + xvp_msg);
this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
break;
}
@ -1330,7 +1334,7 @@ RFB.prototype = {
return this._handle_xvp_msg();
default:
this._fail("Unexpected server message", "Type:" + msg_type);
this._fail("Unexpected server message (type " + msg_type + ")");
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
return true;
}
@ -1385,9 +1389,8 @@ RFB.prototype = {
(hdr[10] << 8) + hdr[11], 10);
if (!this._encHandlers[this._FBU.encoding]) {
this._fail("Unexpected server message",
"Unsupported encoding " +
this._FBU.encoding);
this._fail("Unsupported encoding (encoding: " +
this._FBU.encoding + ")");
return false;
}
}
@ -1881,8 +1884,8 @@ RFB.encodingHandlers = {
if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
var subencoding = rQ[rQi]; // Peek
if (subencoding > 30) { // Raw
this._fail("Unexpected server message",
"Illegal hextile subencoding: " + subencoding);
this._fail("Illegal hextile subencoding (subencoding: " +
subencoding + ")");
return false;
}
@ -2211,9 +2214,8 @@ RFB.encodingHandlers = {
else if (ctl === 0x0A) cmode = "png";
else if (ctl & 0x04) cmode = "filter";
else if (ctl < 0x04) cmode = "copy";
else return this._fail("Unexpected server message",
"Illegal tight compression received, " +
"ctl: " + ctl);
else return this._fail("Illegal tight compression received (ctl: " +
ctl + ")");
switch (cmode) {
// fill use depth because TPIXELs drop the padding byte
@ -2273,9 +2275,8 @@ RFB.encodingHandlers = {
} else {
// Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
// Filter 2, Gradient is valid but not use if jpeg is enabled
this._fail("Unexpected server message",
"Unsupported tight subencoding received, " +
"filter: " + filterId);
this._fail("Unsupported tight subencoding received " +
"(filter: " + filterId + ")");
}
break;
case "copy":
@ -2350,8 +2351,8 @@ RFB.encodingHandlers = {
msg = "Unknown reason";
break;
}
this._notification("Server did not accept the resize request: "
+ msg, 'normal');
Log.Warn("Server did not accept the resize request: "
+ msg);
} else {
this._resize(this._FBU.width, this._FBU.height);
}

View File

@ -63,13 +63,9 @@ protocol stream.
### Events
[`updatestate`](#updatestate)
- The `updatestate` event is fired when the connection state of the
`RFB` object changes.
[`notification`](#notification)
- The `notification` event is fired when the `RFB` usage has a
message to display to the user.
[`connect`](#connect)
- The `connect` event is fired when the `RFB` object has completed
the connection and handshaking with the server.
[`disconnect`](#disconnected)
- The `disconnect` event is fired when the `RFB` object disconnects.
@ -78,6 +74,10 @@ protocol stream.
- The `credentialsrequired` event is fired when more credentials must
be given to continue.
[`securityfailure`](#securityfailure)
- The `securityfailure` event is fired when the security negotiation
with the server fails.
[`clipboard`](#clipboard)
- The `clipboard` event is fired when clipboard data is received from
the server.
@ -181,49 +181,19 @@ connection to a specified VNC server.
- A `DOMString` specifying the ID to provide to any VNC repeater
encountered.
#### updatestate
#### connect
The `updatestate` event is fired after the noVNC connection state
changes. The `detail` property is an `Object` containg the property
`state` with the new connection state.
Here is a list of the states that are reported:
| connection state | description
| ----------------- | ------------
| `"connecting"` | starting to connect
| `"connected"` | connected normally
| `"disconnecting"` | starting to disconnect
| `"disconnected"` | disconnected
Note that a `RFB` objects can not transition from the disconnected
state in any way, a new instance of the object has to be created for
new connections.
#### notification
The `notification` event is fired when the `RFB` object wants a message
displayed to the user. The `detail` property is an `Object` containing
the following properties:
| Property | Type | Description
| --------- | ----------- | -----------
| `message` | `DOMString` | The message to display
| `level` | `DOMString` | The severity of the message
The following levels are currently defined:
- `"normal"`
- `"warn"`
- `"error"`
The `connect` event is fired after all the handshaking with the server
is completed and the connection is fully established. After this event
the `RFB` object is ready to recieve graphics updates and to send input.
#### disconnect
The `disconnect` event is fired when the connection has been
terminated. The `detail` property is an `Object` the optionally
contains the property `reason`. `reason` is a `DOMString` specifying
the reason in the event of an unexpected termination. `reason` will be
omitted for a clean termination.
terminated. The `detail` property is an `Object` that contains the
property `clean`. `clean` is a `boolean` indicating if the termination
was clean or not. In the event of an unexpected termination or an error
`clean` will be set to false.
#### credentialsrequired
@ -232,6 +202,26 @@ credentials than were specified to [`RFB()`](#rfb-1). The `detail`
property is an `Object` containing the property `types` which is an
`Array` of `DOMString` listing the credentials that are required.
#### securityfailure
The `securityfailure` event is fired when the handshaking process with
the server fails during the security negotiation step. The `detail`
property is an `Object` containing the following properties:
| Property | Type | Description
| -------- | ----------- | -----------
| `status` | `long` | The failure status code
| `reason` | `DOMString` | The **optional** reason for the failure
The property `status` corresponds to the
[SecurityResult](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult)
status code in cases of failure. A status of zero will not be sent in
this event since that indicates a successful security handshaking
process. The optional property `reason` is provided by the server and
thus the language of the string is not known. However most servers will
probably send English strings. The server can choose to not send a
reason and in these cases the `reason` property will be omitted.
#### clipboard
The `clipboard` event is fired when the server has sent clipboard data.

View File

@ -61,6 +61,7 @@ module.exports = function(config) {
files: [
{ pattern: 'vendor/sinon.js', included: false },
{ pattern: 'node_modules/sinon-chai/lib/sinon-chai.js', included: false },
{ pattern: 'app/localization.js', included: false },
{ pattern: 'core/**/*.js', included: false },
{ pattern: 'vendor/pako/**/*.js', included: false },
{ pattern: 'tests/test.*.js', included: false },
@ -90,6 +91,7 @@ module.exports = function(config) {
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'app/localization.js': ['babel'],
'core/**/*.js': ['babel'],
'tests/test.*.js': ['babel'],
'tests/fake.*.js': ['babel'],

View File

@ -52,10 +52,6 @@ function enableUI() {
encoding = VNC_frame_encoding;
}
const notification = function (rfb, mesg, level, options) {
document.getElementById('VNC_status').textContent = mesg;
}
function IterationPlayer (iterations, frames, encoding) {
this._iterations = iterations;
@ -72,7 +68,6 @@ function IterationPlayer (iterations, frames, encoding) {
this.onfinish = function() {};
this.oniterationfinish = function() {};
this.rfbdisconnected = function() {};
this.rfbnotification = function() {};
}
IterationPlayer.prototype = {
@ -87,7 +82,7 @@ IterationPlayer.prototype = {
},
_nextIteration: function () {
const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this), this._notification.bind(this));
const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this));
player.onfinish = this._iterationFinish.bind(this);
if (this._state !== 'running') { return; }
@ -120,26 +115,17 @@ IterationPlayer.prototype = {
this._nextIteration();
},
_disconnected: function (rfb, reason, frame) {
if (reason) {
_disconnected: function (rfb, clean, frame) {
if (!clean) {
this._state = 'failed';
}
var evt = new Event('rfbdisconnected');
evt.reason = reason;
evt.clean = clean;
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() {
@ -167,9 +153,6 @@ function start() {
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)`);

View File

@ -44,12 +44,11 @@ if (setImmediate === undefined) {
window.addEventListener("message", _onMessage);
}
export default function RecordingPlayer (frames, encoding, disconnected, notification) {
export default function RecordingPlayer (frames, encoding, disconnected) {
this._frames = frames;
this._encoding = encoding;
this._disconnected = disconnected;
this._notification = notification;
if (this._encoding === undefined) {
let frame = this._frames[0];
@ -79,8 +78,8 @@ RecordingPlayer.prototype = {
// initialize a new RFB
this._rfb = new RFB(document.getElementById('VNC_canvas'), 'wss://test');
this._rfb.viewOnly = true;
this._rfb.ondisconnected = this._handleDisconnect.bind(this);
this._rfb.onnotification = this._notification;
this._rfb.addEventListener("disconnect",
this._handleDisconnect.bind(this));
this._enablePlaybackMode();
// reset the frame index and timer
@ -188,8 +187,8 @@ RecordingPlayer.prototype = {
}
},
_handleDisconnect(rfb, reason) {
_handleDisconnect(rfb, clean) {
this._running = false;
this._disconnected(rfb, reason, this._frame_index);
this._disconnected(rfb, clean, this._frame_index);
}
};

View File

@ -0,0 +1,76 @@
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
import l10nGet, { l10n } from '../app/localization.js';
describe('Localization', function() {
"use strict";
describe('language selection', function () {
var origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.languages !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.languages = [];
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should use English by default', function() {
expect(l10n.language).to.equal('en');
});
it('should use English if no user language matches', function() {
window.navigator.languages = ["nl", "de"];
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"];
l10n.setup(["es", "fr", "de"]);
expect(l10n.language).to.equal('de');
});
it('should prefer sub-languages languages', function() {
window.navigator.languages = ["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"];
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"];
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"];
l10n.setup(["pt_BR"]);
expect(l10n.language).to.equal('pt_BR');
});
it('should handle difference in case', function() {
window.navigator.languages = ["pt-br"];
l10n.setup(["pt-BR"]);
expect(l10n.language).to.equal('pt-BR');
});
});
});

View File

@ -68,11 +68,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
describe('#RFB', function () {
it('should set the current state to "connecting"', function () {
var client = new RFB(document.createElement('canvas'), 'wss://host:8675');
var spy = sinon.spy();
client.addEventListener("updatestate", spy);
client._rfb_connection_state = '';
this.clock.tick();
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.state).to.equal('connecting');
expect(client._rfb_connection_state).to.equal('connecting');
});
it('should actually connect to the websocket', function () {
@ -90,13 +88,15 @@ describe('Remote Frame Buffer Protocol Client', function() {
client = make_rfb();
});
it('should set the current state to "disconnecting"', function () {
var spy = sinon.spy();
client.addEventListener("updatestate", spy);
it('should go to state "disconnecting" before "disconnected"', function () {
sinon.spy(client, '_updateConnectionState');
client.disconnect();
expect(spy).to.have.been.calledTwice;
expect(spy.args[0][0].detail.state).to.equal('disconnecting');
expect(spy.args[1][0].detail.state).to.equal('disconnected');
expect(client._updateConnectionState).to.have.been.calledTwice;
expect(client._updateConnectionState.getCall(0).args[0])
.to.equal('disconnecting');
expect(client._updateConnectionState.getCall(1).args[0])
.to.equal('disconnected');
expect(client._rfb_connection_state).to.equal('disconnected');
});
it('should unregister error event handler', function () {
@ -320,13 +320,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._disconnTimer).to.be.null;
});
it('should call the updateState callback', function () {
var spy = sinon.spy();
client.addEventListener("updatestate", spy);
client._updateConnectionState('disconnecting');
expect(spy.args[0][0].detail.state).to.equal('disconnecting');
});
it('should set the rfb_connection_state', function () {
client._rfb_connection_state = 'disconnecting';
client._updateConnectionState('disconnected');
@ -340,16 +333,23 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should ignore state changes to the same state', function () {
var spy = sinon.spy();
client.addEventListener("updatestate", spy);
client._rfb_connection_state = 'connecting';
client._updateConnectionState('connecting');
expect(spy).to.not.have.been.called;
var connectSpy = sinon.spy();
var disconnectSpy = sinon.spy();
client.addEventListener("connect", connectSpy);
client.addEventListener("disconnect", disconnectSpy);
client._rfb_connection_state = 'connected';
client._updateConnectionState('connected');
expect(connectSpy).to.not.have.been.called;
client._rfb_connection_state = 'disconnected';
client._updateConnectionState('disconnected');
expect(disconnectSpy).to.not.have.been.called;
});
it('should ignore illegal state changes', function () {
var spy = sinon.spy();
client.addEventListener("updatestate", spy);
client.addEventListener("disconnect", spy);
client._rfb_connection_state = 'connected';
client._updateConnectionState('disconnected');
expect(client._rfb_connection_state).to.not.equal('disconnected');
@ -377,53 +377,60 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._rfb_connection_state).to.equal('disconnected');
});
it('should set disconnect_reason', function () {
it('should set clean_disconnect variable', function () {
client._rfb_clean_disconnect = true;
client._rfb_connection_state = 'connected';
client._fail('a reason');
expect(client._rfb_disconnect_reason).to.equal('a reason');
client._fail();
expect(client._rfb_clean_disconnect).to.be.false;
});
it('should not include details in disconnect_reason', function () {
client._rfb_connection_state = 'connected';
client._fail('a reason', 'details');
expect(client._rfb_disconnect_reason).to.equal('a reason');
});
it('should result in disconnect callback with message when reason given', function () {
it('should result in disconnect event with clean set to false', function () {
client._rfb_connection_state = 'connected';
var spy = sinon.spy();
client.addEventListener("disconnect", spy);
client._fail('a reason');
client._fail();
this.clock.tick(2000);
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.reason).to.equal('a reason');
expect(spy.args[0][0].detail.clean).to.be.false;
});
});
describe('#_notification', function () {
var client;
beforeEach(function () { client = make_rfb(); });
it('should call the notification callback', function () {
var spy = sinon.spy();
client.addEventListener("notification", spy);
client._notification('notify!', 'warn');
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.message).to.equal('notify!');
expect(spy.args[0][0].detail.level).to.equal('warn');
});
it('should not call the notification callback when level is invalid', function () {
var spy = sinon.spy();
client.addEventListener("notification", spy);
client._notification('notify!', 'invalid');
expect(spy).to.not.have.been.called;
});
});
});
describe('Connection States', function () {
describe('connecting', function () {
it('should open the websocket connection', function () {
var client = new RFB(document.createElement('canvas'),
'ws://HOST:8675/PATH');
sinon.spy(client._sock, 'open');
this.clock.tick();
expect(client._sock.open).to.have.been.calledOnce;
});
});
describe('connected', function () {
var client;
beforeEach(function () {
client = make_rfb();
});
it('should result in a connect event if state becomes connected', function () {
var spy = sinon.spy();
client.addEventListener("connect", spy);
client._rfb_connection_state = 'connecting';
client._updateConnectionState('connected');
expect(spy).to.have.been.calledOnce;
});
it('should not result in a connect event if the state is not "connected"', function () {
var spy = sinon.spy();
client.addEventListener("connect", spy);
client._sock._websocket.open = function () {}; // explicitly don't call onopen
client._updateConnectionState('connecting');
expect(spy).to.not.have.been.called;
});
});
describe('disconnecting', function () {
var client;
beforeEach(function () {
@ -459,17 +466,16 @@ describe('Remote Frame Buffer Protocol Client', function() {
var client;
beforeEach(function () { client = make_rfb(); });
it('should call the disconnect callback if the state is "disconnected"', function () {
it('should result in a disconnect event if state becomes "disconnected"', function () {
var spy = sinon.spy();
client.addEventListener("disconnect", spy);
client._rfb_connection_state = 'disconnecting';
client._rfb_disconnect_reason = "error";
client._updateConnectionState('disconnected');
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.reason).to.equal("error");
expect(spy.args[0][0].detail.clean).to.be.true;
});
it('should not call the disconnect callback if the state is not "disconnected"', function () {
it('should not result in a disconnect event if the state is not "disconnected"', function () {
var spy = sinon.spy();
client.addEventListener("disconnect", spy);
client._sock._websocket.close = function () {}; // explicitly don't call onclose
@ -477,7 +483,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(spy).to.not.have.been.called;
});
it('should call the disconnect callback without msg when no reason given', function () {
it('should result in a disconnect event without msg when no reason given', function () {
var spy = sinon.spy();
client.addEventListener("disconnect", spy);
client._rfb_connection_state = 'disconnecting';
@ -486,19 +492,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(spy).to.have.been.calledOnce;
expect(spy.args[0].length).to.equal(1);
});
it('should call the updateState callback before the disconnect callback', function () {
var updateStateSpy = sinon.spy();
var disconnectSpy = sinon.spy();
client.addEventListener("disconnect", disconnectSpy);
client.addEventListener("updatestate", updateStateSpy);
client._rfb_connection_state = 'disconnecting';
client._updateConnectionState('disconnected');
expect(updateStateSpy.calledBefore(disconnectSpy)).to.be.true;
});
});
// NB(directxman12): Connected does *nothing* in updateConnectionState
});
describe('Protocol Initialization States', function () {
@ -653,7 +647,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._fail).to.have.been.calledOnce;
expect(client._fail).to.have.been.calledWith(
'Error while negotiating with server','Security failure: whoops');
'Security negotiation failed on no security types (reason: whoops)');
});
it('should transition to the Authentication state and continue on successful negotiation', function () {
@ -688,7 +682,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
sinon.spy(client, '_fail');
client._sock._websocket._receive_data(new Uint8Array(data));
expect(client._fail).to.have.been.calledWith(
'Authentication failure', 'Whoopsies');
'Security negotiation failed on authentication scheme (reason: Whoopsies)');
});
it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
@ -909,14 +903,53 @@ describe('Remote Frame Buffer Protocol Client', function() {
var failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
client._sock._websocket._receive_data(new Uint8Array(failure_data));
expect(client._fail).to.have.been.calledWith(
'Authentication failure', 'whoops');
'Security negotiation failed on security result (reason: whoops)');
});
it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
sinon.spy(client, '_fail');
client._rfb_version = 3.7;
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
expect(client._fail).to.have.been.calledWith('Authentication failure');
expect(client._fail).to.have.been.calledWith(
'Security handshake failed');
});
it('should result in securityfailure event when receiving a non zero status', function () {
var spy = sinon.spy();
client.addEventListener("securityfailure", spy);
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.status).to.equal(2);
});
it('should include reason when provided in securityfailure event', function () {
client._rfb_version = 3.8;
var spy = sinon.spy();
client.addEventListener("securityfailure", spy);
var failure_data = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
32, 102, 97, 105, 108, 117, 114, 101];
client._sock._websocket._receive_data(new Uint8Array(failure_data));
expect(spy.args[0][0].detail.status).to.equal(1);
expect(spy.args[0][0].detail.reason).to.equal('such failure');
});
it('should not include reason when length is zero in securityfailure event', function () {
client._rfb_version = 3.9;
var spy = sinon.spy();
client.addEventListener("securityfailure", spy);
var failure_data = [0, 0, 0, 1, 0, 0, 0, 0];
client._sock._websocket._receive_data(new Uint8Array(failure_data));
expect(spy.args[0][0].detail.status).to.equal(1);
expect('reason' in spy.args[0][0].detail).to.be.false;
});
it('should not include reason in securityfailure event for version < 3.8', function () {
client._rfb_version = 3.6;
var spy = sinon.spy();
client.addEventListener("securityfailure", spy);
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
expect(spy.args[0][0].detail.status).to.equal(2);
expect('reason' in spy.args[0][0].detail).to.be.false;
});
});
@ -1589,14 +1622,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
describe('XVP Message Handling', function () {
it('should send a notification on XVP_FAIL', function () {
var spy = sinon.spy();
client.addEventListener("notification", spy);
client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0]));
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.message).to.equal('XVP Operation Failed');
});
it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
var spy = sinon.spy();
client.addEventListener("capabilities", spy);

View File

@ -4,7 +4,6 @@ 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 from '../vendor/sinon.js';
@ -63,72 +62,6 @@ describe('Utils', function() {
});
});
describe('language selection', function () {
var origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.languages !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.languages = [];
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should use English by default', function() {
expect(l10n.language).to.equal('en');
});
it('should use English if no user language matches', function() {
window.navigator.languages = ["nl", "de"];
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"];
l10n.setup(["es", "fr", "de"]);
expect(l10n.language).to.equal('de');
});
it('should prefer sub-languages languages', function() {
window.navigator.languages = ["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"];
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"];
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"];
l10n.setup(["pt_BR"]);
expect(l10n.language).to.equal('pt_BR');
});
it('should handle difference in case', function() {
window.navigator.languages = ["pt-br"];
l10n.setup(["pt-BR"]);
expect(l10n.language).to.equal('pt-BR');
});
});
// TODO(directxman12): test the conf_default and conf_defaults methods
// TODO(directxman12): test decodeUTF8
// TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)

View File

@ -85,10 +85,6 @@
}
}
notification = function (rfb, mesg, level, options) {
document.getElementById('VNC_status').textContent = mesg;
}
function do_test() {
document.getElementById('startButton').value = "Running";
document.getElementById('startButton').disabled = true;

View File

@ -147,50 +147,27 @@
document.getElementById('noVNC_status_bar').className = "noVNC_status_" + level;
document.getElementById('noVNC_status').textContent = text;
}
function updateState(e) {
var cad = document.getElementById('sendCtrlAltDelButton');
switch (e.detail.state) {
case 'connecting':
status("Connecting", "normal");
break;
case 'connected':
doneInitialResize = false;
if (WebUtil.getConfigVar('encrypt',
(window.location.protocol === "https:"))) {
status("Connected (encrypted) to " +
desktopName, "normal");
} else {
status("Connected (unencrypted) to " +
desktopName, "normal");
}
break;
case 'disconnecting':
status("Disconnecting", "normal");
break;
case 'disconnected':
status("Disconnected", "normal");
break;
default:
status(e.detail.state, "warn");
break;
}
if (e.detail.state === 'connected') {
cad.disabled = false;
function connected(e) {
document.getElementById('sendCtrlAltDelButton').disabled = false;
doneInitialResize = false;
if (WebUtil.getConfigVar('encrypt',
(window.location.protocol === "https:"))) {
status("Connected (encrypted) to " + desktopName, "normal");
} else {
cad.disabled = true;
updatePowerButtons();
status("Connected (unencrypted) to " + desktopName, "normal");
}
}
}
function disconnect(e) {
if (typeof(e.detail.reason) !== 'undefined') {
status(e.detail.reason, "error");
function disconnected(e) {
document.getElementById('sendCtrlAltDelButton').disabled = true;
updatePowerButtons();
if (e.detail.clean) {
status("Disconnected", "normal");
} else {
status("Something went wrong, connection is closed", "error");
}
}
function notification(e) {
status(e.detail.message, e.detail.level);
}
window.onresize = function () {
// When the window has been resized, wait until the size remains
@ -249,6 +226,8 @@
(function() {
status("Connecting", "normal");
if ((!host) || (!port)) {
status('Must specify host and port in URL', 'error');
}
@ -272,10 +251,9 @@
{ repeaterID: WebUtil.getConfigVar('repeaterID', ''),
shared: WebUtil.getConfigVar('shared', true),
credentials: { password: password } });
rfb.viewOnly = WebUtil.getConfigVar('view_only', false);
rfb.addEventListener("notification", notification);
rfb.addEventListener("updatestate", updateState);
rfb.addEventListener("disconnect", disconnect);
rfb.viewOnly = WebUtil.getConfigVar('view_only', false);
rfb.addEventListener("connect", connected);
rfb.addEventListener("disconnect", disconnected);
rfb.addEventListener("capabilities", function () { updatePowerButtons(); initialResize(); });
rfb.addEventListener("credentialsrequired", credentials);
rfb.addEventListener("desktopname", updateDesktopName);