summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2022-12-27 13:13:48 +0100
committerPierre Ossman <ossman@cendio.se>2022-12-27 13:13:48 +0100
commit1ff2ecd9f0f8804fb0b56ea1349fc12e0e7d3eec (patch)
treebb9b57185bb86faf1fda559f4e039873099cbea7
parente6fce71d6ac3ef4c5aaa22e3078f7f4994ca1db8 (diff)
parent5de478d6e7b70c080a4a64ec6deb590c773ca416 (diff)
downloadnovnc-1ff2ecd9f0f8804fb0b56ea1349fc12e0e7d3eec.tar.gz
Merge branch 'ffscroll' of https://github.com/CendioOssman/noVNC
-rw-r--r--.eslintrc3
-rw-r--r--app/ui.js27
-rw-r--r--app/webutil.js4
-rw-r--r--core/des.js2
-rw-r--r--core/rfb.js15
-rw-r--r--core/util/browser.js63
-rw-r--r--docs/API.md411
-rw-r--r--tests/test.browser.js244
-rw-r--r--tests/test.helper.js20
-rw-r--r--tests/test.keyboard.js49
-rw-r--r--tests/test.localization.js10
-rw-r--r--tests/test.webutil.js9
12 files changed, 565 insertions, 292 deletions
diff --git a/.eslintrc b/.eslintrc
index 7797028..10e15ce 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -26,10 +26,13 @@
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"indent": ["error", 4, { "SwitchCase": 1,
+ "VariableDeclarator": "first",
"FunctionDeclaration": { "parameters": "first" },
+ "FunctionExpression": { "parameters": "first" },
"CallExpression": { "arguments": "first" },
"ArrayExpression": "first",
"ObjectExpression": "first",
+ "ImportDeclaration": "first",
"ignoreComments": true }],
"comma-spacing": ["error"],
"comma-style": ["error"],
diff --git a/app/ui.js b/app/ui.js
index ff0f176..c1f6776 100644
--- a/app/ui.js
+++ b/app/ui.js
@@ -8,7 +8,8 @@
import * as Log from '../core/util/logging.js';
import _, { l10n } from './localization.js';
-import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold }
+import { isTouchDevice, isMac, isIOS, isAndroid, isChromeOS, isSafari,
+ hasScrollbarGutter, dragThreshold }
from '../core/util/browser.js';
import { setCapture, getPointerEvent } from '../core/util/events.js';
import KeyTable from "../core/input/keysym.js";
@@ -1049,6 +1050,7 @@ const UI = {
UI.rfb.addEventListener("serververification", UI.serverVerify);
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
+ UI.rfb.addEventListener("clippingviewport", UI.updateViewDrag);
UI.rfb.addEventListener("capabilities", UI.updatePowerButton);
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
UI.rfb.addEventListener("bell", UI.bell);
@@ -1325,13 +1327,25 @@ const UI = {
const scaling = UI.getSetting('resize') === 'scale';
+ // Some platforms have overlay scrollbars that are difficult
+ // to use in our case, which means we have to force panning
+ // FIXME: Working scrollbars can still be annoying to use with
+ // touch, so we should ideally be able to have both
+ // panning and scrollbars at the same time
+
+ let brokenScrollbars = false;
+
+ if (!hasScrollbarGutter) {
+ if (isIOS() || isAndroid() || isMac() || isChromeOS()) {
+ brokenScrollbars = true;
+ }
+ }
+
if (scaling) {
// Can't be clipping if viewport is scaled to fit
UI.forceSetting('view_clip', false);
UI.rfb.clipViewport = false;
- } else if (!hasScrollbarGutter) {
- // Some platforms have scrollbars that are difficult
- // to use in our case, so we always use our own panning
+ } else if (brokenScrollbars) {
UI.forceSetting('view_clip', true);
UI.rfb.clipViewport = true;
} else {
@@ -1362,7 +1376,8 @@ const UI = {
const viewDragButton = document.getElementById('noVNC_view_drag_button');
- if (!UI.rfb.clipViewport && UI.rfb.dragViewport) {
+ if ((!UI.rfb.clipViewport || !UI.rfb.clippingViewport) &&
+ UI.rfb.dragViewport) {
// We are no longer clipping the viewport. Make sure
// viewport drag isn't active when it can't be used.
UI.rfb.dragViewport = false;
@@ -1379,6 +1394,8 @@ const UI = {
} else {
viewDragButton.classList.add("noVNC_hidden");
}
+
+ viewDragButton.disabled = !UI.rfb.clippingViewport;
},
/* ------^-------
diff --git a/app/webutil.js b/app/webutil.js
index d42b7f2..084c69f 100644
--- a/app/webutil.js
+++ b/app/webutil.js
@@ -32,7 +32,7 @@ export function initLogging(level) {
export function getQueryVar(name, defVal) {
"use strict";
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
- match = ''.concat(document.location.href, window.location.hash).match(re);
+ match = ''.concat(document.location.href, window.location.hash).match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
@@ -46,7 +46,7 @@ export function getQueryVar(name, defVal) {
export function getHashVar(name, defVal) {
"use strict";
const re = new RegExp('.*[&#]' + name + '=([^&]*)'),
- match = document.location.hash.match(re);
+ match = document.location.hash.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
diff --git a/core/des.js b/core/des.js
index d2f807b..ba1ebde 100644
--- a/core/des.js
+++ b/core/des.js
@@ -81,7 +81,7 @@
const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
- totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
+ totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
const z = 0x0;
let a,b,c,d,e,f;
diff --git a/core/rfb.js b/core/rfb.js
index 2ccd61a..6afd7c6 100644
--- a/core/rfb.js
+++ b/core/rfb.js
@@ -287,6 +287,7 @@ export default class RFB extends EventTargetMixin {
this._viewOnly = false;
this._clipViewport = false;
+ this._clippingViewport = false;
this._scaleViewport = false;
this._resizeSession = false;
@@ -318,6 +319,16 @@ export default class RFB extends EventTargetMixin {
get capabilities() { return this._capabilities; }
+ get clippingViewport() { return this._clippingViewport; }
+ _setClippingViewport(on) {
+ if (on === this._clippingViewport) {
+ return;
+ }
+ this._clippingViewport = on;
+ this.dispatchEvent(new CustomEvent("clippingviewport",
+ { detail: this._clippingViewport }));
+ }
+
get touchButton() { return 0; }
set touchButton(button) { Log.Warn("Using old API!"); }
@@ -749,6 +760,10 @@ export default class RFB extends EventTargetMixin {
const size = this._screenSize();
this._display.viewportChangeSize(size.w, size.h);
this._fixScrollbars();
+ this._setClippingViewport(size.w < this._display.width ||
+ size.h < this._display.height);
+ } else {
+ this._setClippingViewport(false);
}
// When changing clipping we might show or hide scrollbars.
diff --git a/core/util/browser.js b/core/util/browser.js
index 24b5e96..bbc9f5c 100644
--- a/core/util/browser.js
+++ b/core/util/browser.js
@@ -77,27 +77,76 @@ export const hasScrollbarGutter = _hasScrollbarGutter;
* It's better to use feature detection than platform detection.
*/
+/* OS */
+
export function isMac() {
- return navigator && !!(/mac/i).exec(navigator.platform);
+ return !!(/mac/i).exec(navigator.platform);
}
export function isWindows() {
- return navigator && !!(/win/i).exec(navigator.platform);
+ return !!(/win/i).exec(navigator.platform);
}
export function isIOS() {
- return navigator &&
- (!!(/ipad/i).exec(navigator.platform) ||
+ return (!!(/ipad/i).exec(navigator.platform) ||
!!(/iphone/i).exec(navigator.platform) ||
!!(/ipod/i).exec(navigator.platform));
}
+export function isAndroid() {
+ /* Android sets navigator.platform to Linux :/ */
+ return !!navigator.userAgent.match('Android ');
+}
+
+export function isChromeOS() {
+ /* ChromeOS sets navigator.platform to Linux :/ */
+ return !!navigator.userAgent.match(' CrOS ');
+}
+
+/* Browser */
+
export function isSafari() {
- return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
- navigator.userAgent.indexOf('Chrome') === -1);
+ return !!navigator.userAgent.match('Safari/...') &&
+ !navigator.userAgent.match('Chrome/...') &&
+ !navigator.userAgent.match('Chromium/...') &&
+ !navigator.userAgent.match('Epiphany/...');
}
export function isFirefox() {
- return navigator && !!(/firefox/i).exec(navigator.userAgent);
+ return !!navigator.userAgent.match('Firefox/...') &&
+ !navigator.userAgent.match('Seamonkey/...');
+}
+
+export function isChrome() {
+ return !!navigator.userAgent.match('Chrome/...') &&
+ !navigator.userAgent.match('Chromium/...') &&
+ !navigator.userAgent.match('Edg/...') &&
+ !navigator.userAgent.match('OPR/...');
+}
+
+export function isChromium() {
+ return !!navigator.userAgent.match('Chromium/...');
}
+export function isOpera() {
+ return !!navigator.userAgent.match('OPR/...');
+}
+
+export function isEdge() {
+ return !!navigator.userAgent.match('Edg/...');
+}
+
+/* Engine */
+
+export function isGecko() {
+ return !!navigator.userAgent.match('Gecko/...');
+}
+
+export function isWebKit() {
+ return !!navigator.userAgent.match('AppleWebKit/...') &&
+ !navigator.userAgent.match('Chrome/...');
+}
+
+export function isBlink() {
+ return !!navigator.userAgent.match('Chrome/...');
+}
diff --git a/docs/API.md b/docs/API.md
index 77e9056..a16799b 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -16,135 +16,137 @@ protocol stream.
### Properties
-`viewOnly`
- - Is a `boolean` indicating if any events (e.g. key presses or mouse
- movement) should be prevented from being sent to the server.
- Disabled by default.
+`background`
+ - Is a valid CSS [background](https://developer.mozilla.org/en-US/docs/Web/CSS/background)
+ style value indicating which background style should be applied
+ to the element containing the remote session screen. The default value is `rgb(40, 40, 40)`
+ (solid gray color).
-`focusOnClick`
- - Is a `boolean` indicating if keyboard focus should automatically be
- moved to the remote session when a `mousedown` or `touchstart`
- event is received. Enabled by default.
+`capabilities` *Read only*
+ - Is an `Object` indicating which optional extensions are available
+ on the server. Some methods may only be called if the corresponding
+ capability is set. The following capabilities are defined:
+
+ | name | type | description
+ | -------- | --------- | -----------
+ | `power` | `boolean` | Machine power control is available
+
+`clippingViewport` *Read only*
+ - Is a `boolean` indicating if the remote session is currently being
+ clipped to its container. Only relevant if `clipViewport` is
+ enabled.
`clipViewport`
- Is a `boolean` indicating if the remote session should be clipped
to its container. When disabled scrollbars will be shown to handle
the resulting overflow. Disabled by default.
+`compressionLevel`
+ - Is an `int` in range `[0-9]` controlling the desired compression
+ level. Value `0` means no compression. Level 1 uses a minimum of CPU
+ resources and achieves weak compression ratios, while level 9 offers
+ best compression but is slow in terms of CPU consumption on the server
+ side. Use high levels with very slow network connections.
+ Default value is `2`.
+
`dragViewport`
- Is a `boolean` indicating if mouse events should control the
relative position of a clipped remote session. Only relevant if
`clipViewport` is enabled. Disabled by default.
-`scaleViewport`
- - Is a `boolean` indicating if the remote session should be scaled
- locally so it fits its container. When disabled it will be centered
- if the remote session is smaller than its container, or handled
- according to `clipViewport` if it is larger. Disabled by default.
+`focusOnClick`
+ - Is a `boolean` indicating if keyboard focus should automatically be
+ moved to the remote session when a `mousedown` or `touchstart`
+ event is received. Enabled by default.
+
+`qualityLevel`
+ - Is an `int` in range `[0-9]` controlling the desired JPEG quality.
+ Value `0` implies low quality and `9` implies high quality.
+ Default value is `6`.
`resizeSession`
- Is a `boolean` indicating if a request to resize the remote session
should be sent whenever the container changes dimensions. Disabled
by default.
+`scaleViewport`
+ - Is a `boolean` indicating if the remote session should be scaled
+ locally so it fits its container. When disabled it will be centered
+ if the remote session is smaller than its container, or handled
+ according to `clipViewport` if it is larger. Disabled by default.
+
`showDotCursor`
- Is a `boolean` indicating whether a dot cursor should be shown
instead of a zero-sized or fully-transparent cursor if the server
sets such invisible cursor. Disabled by default.
-`background`
- - Is a valid CSS [background](https://developer.mozilla.org/en-US/docs/Web/CSS/background)
- style value indicating which background style should be applied
- to the element containing the remote session screen. The default value is `rgb(40, 40, 40)`
- (solid gray color).
+`viewOnly`
+ - Is a `boolean` indicating if any events (e.g. key presses or mouse
+ movement) should be prevented from being sent to the server.
+ Disabled by default.
-`qualityLevel`
- - Is an `int` in range `[0-9]` controlling the desired JPEG quality.
- Value `0` implies low quality and `9` implies high quality.
- Default value is `6`.
+### Events
-`compressionLevel`
- - Is an `int` in range `[0-9]` controlling the desired compression
- level. Value `0` means no compression. Level 1 uses a minimum of CPU
- resources and achieves weak compression ratios, while level 9 offers
- best compression but is slow in terms of CPU consumption on the server
- side. Use high levels with very slow network connections.
- Default value is `2`.
+[`bell`](#bell)
+ - The `bell` event is fired when a audible bell request is received
+ from the server.
-`capabilities` *Read only*
- - Is an `Object` indicating which optional extensions are available
- on the server. Some methods may only be called if the corresponding
- capability is set. The following capabilities are defined:
+[`capabilities`](#capabilities)
+ - The `capabilities` event is fired when `RFB.capabilities` is
+ updated.
- | name | type | description
- | -------- | --------- | -----------
- | `power` | `boolean` | Machine power control is available
+[`clipboard`](#clipboard)
+ - The `clipboard` event is fired when clipboard data is received from
+ the server.
-### Events
+[`clippingviewport`](#clippingviewport)
+ - The `clippingviewport` event is fired when `RFB.clippingViewport` is
+ updated.
[`connect`](#connect)
- The `connect` event is fired when the `RFB` object has completed
the connection and handshaking with the server.
-[`disconnect`](#disconnect)
- - The `disconnect` event is fired when the `RFB` object disconnects.
-
-[`serververification`](#serververification)
- - The `serververification` event is fired when the server identity
- must be confirmed by the user.
-
[`credentialsrequired`](#credentialsrequired)
- 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.
-
-[`bell`](#bell)
- - The `bell` event is fired when a audible bell request is received
- from the server.
-
[`desktopname`](#desktopname)
- The `desktopname` event is fired when the remote desktop name
changes.
-[`capabilities`](#capabilities)
- - The `capabilities` event is fired when `RFB.capabilities` is
- updated.
+[`disconnect`](#disconnect)
+ - The `disconnect` event is fired when the `RFB` object disconnects.
-### Methods
+[`securityfailure`](#securityfailure)
+ - The `securityfailure` event is fired when the security negotiation
+ with the server fails.
-[`RFB.disconnect()`](#rfbdisconnect)
- - Disconnect from the server.
+[`serververification`](#serververification)
+ - The `serververification` event is fired when the server identity
+ must be confirmed by the user.
+
+### Methods
[`RFB.approveServer()`](#rfbapproveserver)
- Proceed connecting to the server. Should be called after the
[`serververification`](#serververification) event has fired and the
user has verified the identity of the server.
-[`RFB.sendCredentials()`](#rfbsendcredentials)
- - Send credentials to server. Should be called after the
- [`credentialsrequired`](#credentialsrequired) event has fired.
+[`RFB.blur()`](#rfbblur)
+ - Remove keyboard focus from the remote session.
-[`RFB.sendKey()`](#rfbsendkey)
- - Send a key event.
+[`RFB.clipboardPasteFrom()`](#rfbclipboardpastefrom)
+ - Send clipboard contents to server.
-[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)
- - Send Ctrl-Alt-Del key sequence.
+[`RFB.disconnect()`](#rfbdisconnect)
+ - Disconnect from the server.
[`RFB.focus()`](#rfbfocus)
- Move keyboard focus to the remote session.
-[`RFB.blur()`](#rfbblur)
- - Remove keyboard focus from the remote session.
-
-[`RFB.machineShutdown()`](#rfbmachineshutdown)
- - Request a shutdown of the remote machine.
+[`RFB.getImageData()`](#rfbgetimagedata)
+ - Return the current content of the screen as an ImageData array.
[`RFB.machineReboot()`](#rfbmachinereboot)
- Request a reboot of the remote machine.
@@ -152,18 +154,25 @@ protocol stream.
[`RFB.machineReset()`](#rfbmachinereset)
- Request a reset of the remote machine.
-[`RFB.clipboardPasteFrom()`](#rfbclipboardpastefrom)
- - Send clipboard contents to server.
+[`RFB.machineShutdown()`](#rfbmachineshutdown)
+ - Request a shutdown of the remote machine.
-[`RFB.getImageData()`](#rfbgetimagedata)
- - Return the current content of the screen as an ImageData array.
+[`RFB.sendCredentials()`](#rfbsendcredentials)
+ - Send credentials to server. Should be called after the
+ [`credentialsrequired`](#credentialsrequired) event has fired.
-[`RFB.toDataURL()`](#rfbtodataurl)
- - Return the current content of the screen as data-url encoded image file.
+[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)
+ - Send Ctrl-Alt-Del key sequence.
+
+[`RFB.sendKey()`](#rfbsendkey)
+ - Send a key event.
[`RFB.toBlob()`](#rfbtoblob)
- Return the current content of the screen as Blob encoded image file.
+[`RFB.toDataURL()`](#rfbtodataurl)
+ - Return the current content of the screen as data-url encoded image file.
+
### Details
#### RFB()
@@ -216,33 +225,28 @@ connection to a specified VNC server.
- An `Array` of `DOMString`s specifying the sub-protocols to use
in the WebSocket connection. Empty by default.
-#### connect
+#### bell
-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.
+The `bell` event is fired when the server has requested an audible
+bell.
-#### disconnect
+#### capabilities
-The `disconnect` event is fired when the connection has been
-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.
+The `capabilities` event is fired whenever an entry is added or removed
+from `RFB.capabilities`. The `detail` property is an `Object` with the
+property `capabilities` containing the new value of `RFB.capabilities`.
-#### serververification
+#### clippingviewport
-The `serververification` event is fired when the server provides
-information that allows the user to verify that it is the correct server
-and protect against a man-in-the-middle attack. The `detail` property is
-an `Object` containing the property `type` which is a `DOMString`
-specifying which type of information the server has provided. Other
-properties are also available, depending on the value of `type`:
+The `clippingviewport` event is fired whenever `RFB.clippingViewport`
+changes between `true` and `false`. The `detail` property is a `boolean`
+with the new value of `RFB.clippingViewport`.
-`"RSA"`
- - The server identity is verified using just a RSA key. The property
- `publickey` is a `Uint8Array` containing the public key in a unsigned
- big endian representation.
+#### clipboard
+
+The `clipboard` event is fired when the server has sent clipboard data.
+The `detail` property is an `Object` containing the property `text`
+which is a `DOMString` with the clipboard data.
#### credentialsrequired
@@ -251,6 +255,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.
+#### connect
+
+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.
+
+#### desktopname
+
+The `desktopname` event is fired when the name of the remote desktop
+changes. The `detail` property is an `Object` with the property `name`
+which is a `DOMString` specifying the new name.
+
+#### disconnect
+
+The `disconnect` event is fired when the connection has been
+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.
+
#### securityfailure
The `securityfailure` event is fired when the handshaking process with
@@ -271,37 +295,19 @@ 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.
-The `detail` property is an `Object` containing the property `text`
-which is a `DOMString` with the clipboard data.
-
-#### bell
-
-The `bell` event is fired when the server has requested an audible
-bell.
-
-#### desktopname
-
-The `desktopname` event is fired when the name of the remote desktop
-changes. The `detail` property is an `Object` with the property `name`
-which is a `DOMString` specifying the new name.
-
-#### capabilities
-
-The `capabilities` event is fired whenever an entry is added or removed
-from `RFB.capabilities`. The `detail` property is an `Object` with the
-property `capabilities` containing the new value of `RFB.capabilities`.
-
-#### RFB.disconnect()
-
-The `RFB.disconnect()` method is used to disconnect from the currently
-connected server.
+#### serververification
-##### Syntax
+The `serververification` event is fired when the server provides
+information that allows the user to verify that it is the correct server
+and protect against a man-in-the-middle attack. The `detail` property is
+an `Object` containing the property `type` which is a `DOMString`
+specifying which type of information the server has provided. Other
+properties are also available, depending on the value of `type`:
- RFB.disconnect( );
+`"RSA"`
+ - The server identity is verified using just a RSA key. The property
+ `publickey` is a `Uint8Array` containing the public key in a unsigned
+ big endian representation.
#### RFB.approveServer()
@@ -313,55 +319,38 @@ and that the connection can continue.
RFB.approveServer( );
-#### RFB.sendCredentials()
+#### RFB.blur()
-The `RFB.sendCredentials()` method is used to provide the missing
-credentials after a `credentialsrequired` event has been fired.
+The `RFB.blur()` method remove keyboard focus on the remote session.
+Keyboard events will no longer be sent to the remote server after this
+point.
##### Syntax
- RFB.sendCredentials( credentials );
-
-###### Parameters
-
-**`credentials`**
- - An `Object` specifying the credentials to provide to the server
- when authenticating. See [`RFB()`](#rfb-1) for details.
+ RFB.blur( );
-#### RFB.sendKey()
+#### RFB.clipboardPasteFrom()
-The `RFB.sendKey()` method is used to send a key event to the server.
+The `RFB.clipboardPasteFrom()` method is used to send clipboard data
+to the remote server.
##### Syntax
- RFB.sendKey( keysym, code [, down] );
+ RFB.clipboardPasteFrom( text );
###### Parameters
-**`keysym`**
- - A `long` specifying the RFB keysym to send. Can be `0` if a valid
- **`code`** is specified.
-
-**`code`**
- - A `DOMString` specifying the physical key to send. Valid values are
- those that can be specified to
- [`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code).
- If the physical key cannot be determined then `null` shall be
- specified.
-
-**`down`** *Optional*
- - A `boolean` specifying if a press or a release event should be
- sent. If omitted then both a press and release event are sent.
+**`text`**
+ - A `DOMString` specifying the clipboard data to send.
-#### RFB.sendCtrlAltDel()
+#### RFB.disconnect()
-The `RFB.sendCtrlAltDel()` method is used to send the key sequence
-*left Control*, *left Alt*, *Delete*. This is a convenience wrapper
-around [`RFB.sendKey()`](#rfbsendkey).
+The `RFB.disconnect()` method is used to disconnect from the currently
+connected server.
##### Syntax
- RFB.sendCtrlAltDel( );
+ RFB.disconnect( );
#### RFB.focus()
@@ -379,25 +368,14 @@ Keyboard events will be sent to the remote server after this point.
performed. Please see [`HTMLElement.focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus)
for available options.
-#### RFB.blur()
-
-The `RFB.blur()` method remove keyboard focus on the remote session.
-Keyboard events will no longer be sent to the remote server after this
-point.
-
-##### Syntax
-
- RFB.blur( );
-
-#### RFB.machineShutdown()
+#### RFB.getImageData()
-The `RFB.machineShutdown()` method is used to request to shut down the
-remote machine. The capability `power` must be set for this method to
-have any effect.
+The `RFB.getImageData()` method is used to return the current content of the
+screen encoded as [`ImageData`](https://developer.mozilla.org/en-US/docs/Web/API/ImageData).
##### Syntax
- RFB.machineShutdown( );
+ RFB.getImageData();
#### RFB.machineReboot()
@@ -419,48 +397,65 @@ to have any effect.
RFB.machineReset( );
-#### RFB.clipboardPasteFrom()
+#### RFB.machineShutdown()
-The `RFB.clipboardPasteFrom()` method is used to send clipboard data
-to the remote server.
+The `RFB.machineShutdown()` method is used to request to shut down the
+remote machine. The capability `power` must be set for this method to
+have any effect.
##### Syntax
- RFB.clipboardPasteFrom( text );
+ RFB.machineShutdown( );
+
+#### RFB.sendCredentials()
+
+The `RFB.sendCredentials()` method is used to provide the missing
+credentials after a `credentialsrequired` event has been fired.
+
+##### Syntax
+
+ RFB.sendCredentials( credentials );
###### Parameters
-**`text`**
- - A `DOMString` specifying the clipboard data to send.
+**`credentials`**
+ - An `Object` specifying the credentials to provide to the server
+ when authenticating. See [`RFB()`](#rfb-1) for details.
-#### RFB.getImageData()
+#### RFB.sendCtrlAltDel()
-The `RFB.getImageData()` method is used to return the current content of the
-screen encoded as [`ImageData`](https://developer.mozilla.org/en-US/docs/Web/API/ImageData).
+The `RFB.sendCtrlAltDel()` method is used to send the key sequence
+*left Control*, *left Alt*, *Delete*. This is a convenience wrapper
+around [`RFB.sendKey()`](#rfbsendkey).
##### Syntax
- RFB.getImageData();
+ RFB.sendCtrlAltDel( );
-#### RFB.toDataURL()
+#### RFB.sendKey()
-The `RFB.toDataURL()` method is used to return the current content of the
-screen encoded as a data URL that could for example be put in the `src` attribute
-of an `img` tag.
+The `RFB.sendKey()` method is used to send a key event to the server.
##### Syntax
- RFB.toDataURL();
- RFB.toDataURL(type);
- RFB.toDataURL(type, encoderOptions);
+ RFB.sendKey( keysym, code [, down] );
###### Parameters
-**`type`** *Optional*
- - A string indicating the requested MIME type of the image
+**`keysym`**
+ - A `long` specifying the RFB keysym to send. Can be `0` if a valid
+ **`code`** is specified.
-**`encoderOptions`** *Optional*
- - A number between 0 and 1 indicating the image quality.
+**`code`**
+ - A `DOMString` specifying the physical key to send. Valid values are
+ those that can be specified to
+ [`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code).
+ If the physical key cannot be determined then `null` shall be
+ specified.
+
+**`down`** *Optional*
+ - A `boolean` specifying if a press or a release event should be
+ sent. If omitted then both a press and release event are sent.
#### RFB.toBlob()
@@ -484,3 +479,23 @@ screen encoded as [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob
**`encoderOptions`** *Optional*
- A number between 0 and 1 indicating the image quality.
+
+#### RFB.toDataURL()
+
+The `RFB.toDataURL()` method is used to return the current content of the
+screen encoded as a data URL that could for example be put in the `src` attribute
+of an `img` tag.
+
+##### Syntax
+
+ RFB.toDataURL();
+ RFB.toDataURL(type);
+ RFB.toDataURL(type, encoderOptions);
+
+###### Parameters
+
+**`type`** *Optional*
+ - A string indicating the requested MIME type of the image
+
+**`encoderOptions`** *Optional*
+ - A number between 0 and 1 indicating the image quality.
diff --git a/tests/test.browser.js b/tests/test.browser.js
new file mode 100644
index 0000000..3b2299f
--- /dev/null
+++ b/tests/test.browser.js
@@ -0,0 +1,244 @@
+/* eslint-disable no-console */
+const expect = chai.expect;
+
+import { isMac, isWindows, isIOS, isAndroid, isChromeOS,
+ isSafari, isFirefox, isChrome, isChromium, isOpera, isEdge,
+ isGecko, isWebKit, isBlink } from '../core/util/browser.js';
+
+describe('OS detection', function () {
+ let 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");
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ });
+
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should handle macOS', function () {
+ const platforms = [
+ "MacIntel",
+ "MacPPC",
+ ];
+
+ navigator.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15";
+ platforms.forEach((platform) => {
+ navigator.platform = platform;
+ expect(isMac()).to.be.true;
+ expect(isWindows()).to.be.false;
+ expect(isIOS()).to.be.false;
+ expect(isAndroid()).to.be.false;
+ expect(isChromeOS()).to.be.false;
+ });
+ });
+
+ it('should handle Windows', function () {
+ const platforms = [
+ "Win32",
+ "Win64",
+ ];
+
+ navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36";
+ platforms.forEach((platform) => {
+ navigator.platform = platform;
+ expect(isMac()).to.be.false;
+ expect(isWindows()).to.be.true;
+ expect(isIOS()).to.be.false;
+ expect(isAndroid()).to.be.false;
+ expect(isChromeOS()).to.be.false;
+ });
+ });
+
+ it('should handle iOS', function () {
+ const platforms = [
+ "iPhone",
+ "iPod",
+ "iPad",
+ ];
+
+ navigator.userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Mobile/15E148 Safari/604.1";
+ platforms.forEach((platform) => {
+ navigator.platform = platform;
+ expect(isMac()).to.be.false;
+ expect(isWindows()).to.be.false;
+ expect(isIOS()).to.be.true;
+ expect(isAndroid()).to.be.false;
+ expect(isChromeOS()).to.be.false;
+ });
+ });
+
+ it('should handle Android', function () {
+ let userAgents = [
+ "Mozilla/5.0 (Linux; Android 13; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.128 Mobile Safari/537.36",
+ "Mozilla/5.0 (Android 13; Mobile; LG-M255; rv:108.0) Gecko/108.0 Firefox/108.0",
+ ];
+
+ navigator.platform = "Linux x86_64";
+ userAgents.forEach((ua) => {
+ navigator.userAgent = ua;
+ expect(isMac()).to.be.false;
+ expect(isWindows()).to.be.false;
+ expect(isIOS()).to.be.false;
+ expect(isAndroid()).to.be.true;
+ expect(isChromeOS()).to.be.false;
+ });
+ });
+
+ it('should handle ChromeOS', function () {
+ let userAgents = [
+ "Mozilla/5.0 (X11; CrOS x86_64 15183.59.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.75 Safari/537.36",
+ "Mozilla/5.0 (X11; CrOS aarch64 15183.59.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.75 Safari/537.36",
+ ];
+
+ navigator.platform = "Linux x86_64";
+ userAgents.forEach((ua) => {
+ navigator.userAgent = ua;
+ expect(isMac()).to.be.false;
+ expect(isWindows()).to.be.false;
+ expect(isIOS()).to.be.false;
+ expect(isAndroid()).to.be.false;
+ expect(isChromeOS()).to.be.true;
+ });
+ });
+});
+
+describe('Browser detection', function () {
+ let 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");
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ });
+
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should handle Chrome', function () {
+ navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36";
+
+ expect(isSafari()).to.be.false;
+ expect(isFirefox()).to.be.false;
+ expect(isChrome()).to.be.true;
+ expect(isChromium()).to.be.false;
+ expect(isOpera()).to.be.false;
+ expect(isEdge()).to.be.false;
+
+ expect(isGecko()).to.be.false;
+ expect(isWebKit()).to.be.false;
+ expect(isBlink()).to.be.true;
+ });
+
+ it('should handle Chromium', function () {
+ navigator.userAgent = "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 Chrome/74.0.3729.157 Safari/537.36";
+
+ expect(isSafari()).to.be.false;
+ expect(isFirefox()).to.be.false;
+ expect(isChrome()).to.be.false;
+ expect(isChromium()).to.be.true;
+ expect(isOpera()).to.be.false;
+ expect(isEdge()).to.be.false;
+
+ expect(isGecko()).to.be.false;
+ expect(isWebKit()).to.be.false;
+ expect(isBlink()).to.be.true;
+ });
+
+ it('should handle Firefox', function () {
+ navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0";
+
+ expect(isSafari()).to.be.false;
+ expect(isFirefox()).to.be.true;
+ expect(isChrome()).to.be.false;
+ expect(isChromium()).to.be.false;
+ expect(isOpera()).to.be.false;
+ expect(isEdge()).to.be.false;
+
+ expect(isGecko()).to.be.true;
+ expect(isWebKit()).to.be.false;
+ expect(isBlink()).to.be.false;
+ });
+
+ it('should handle Seamonkey', function () {
+ navigator.userAgent = "Mozilla/5.0 (Windows NT 6.1; rv:36.0) Gecko/20100101 Firefox/36.0 Seamonkey/2.33.1";
+
+ expect(isSafari()).to.be.false;
+ expect(isFirefox()).to.be.false;
+ expect(isChrome()).to.be.false;
+ expect(isChromium()).to.be.false;
+ expect(isOpera()).to.be.false;
+ expect(isEdge()).to.be.false;
+
+ expect(isGecko()).to.be.true;
+ expect(isWebKit()).to.be.false;
+ expect(isBlink()).to.be.false;
+ });
+
+ it('should handle Safari', function () {
+ navigator.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15";
+
+ expect(isSafari()).to.be.true;
+ expect(isFirefox()).to.be.false;
+ expect(isChrome()).to.be.false;
+ expect(isChromium()).to.be.false;
+ expect(isOpera()).to.be.false;
+ expect(isEdge()).to.be.false;
+
+ expect(isGecko()).to.be.false;
+ expect(isWebKit()).to.be.true;
+ expect(isBlink()).to.be.false;
+ });
+
+ it('should handle Edge', function () {
+ navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.34";
+
+ expect(isSafari()).to.be.false;
+ expect(isFirefox()).to.be.false;
+ expect(isChrome()).to.be.false;
+ expect(isChromium()).to.be.false;
+ expect(isOpera()).to.be.false;
+ expect(isEdge()).to.be.true;
+
+ expect(isGecko()).to.be.false;
+ expect(isWebKit()).to.be.false;
+ expect(isBlink()).to.be.true;
+ });
+
+ it('should handle Opera', function () {
+ navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 OPR/91.0.4516.20";
+
+ expect(isSafari()).to.be.false;
+ expect(isFirefox()).to.be.false;
+ expect(isChrome()).to.be.false;
+ expect(isChromium()).to.be.false;
+ expect(isOpera()).to.be.true;
+ expect(isEdge()).to.be.false;
+
+ expect(isGecko()).to.be.false;
+ expect(isWebKit()).to.be.false;
+ expect(isBlink()).to.be.true;
+ });
+
+ it('should handle Epiphany', function () {
+ navigator.userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Safari/605.1.15 Epiphany/605.1.15";
+
+ expect(isSafari()).to.be.false;
+ expect(isFirefox()).to.be.false;
+ expect(isChrome()).to.be.false;
+ expect(isChromium()).to.be.false;
+ expect(isOpera()).to.be.false;
+ expect(isEdge()).to.be.false;
+
+ expect(isGecko()).to.be.false;
+ expect(isWebKit()).to.be.true;
+ expect(isBlink()).to.be.false;
+ });
+});
diff --git a/tests/test.helper.js b/tests/test.helper.js
index ed65770..ff83c53 100644
--- a/tests/test.helper.js
+++ b/tests/test.helper.js
@@ -71,18 +71,10 @@ describe('Helpers', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
- if (window.navigator.platform !== undefined) {
- // Object.defineProperty() doesn't work properly in old
- // versions of Chrome
- this.skip();
- }
-
window.navigator.platform = "Mac x86_64";
});
afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
+ Object.defineProperty(window, "navigator", origNavigator);
});
it('should respect ContextMenu on modern browser', function () {
@@ -196,19 +188,11 @@ describe('Helpers', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
- if (window.navigator.platform !== undefined) {
- // Object.defineProperty() doesn't work properly in old
- // versions of Chrome
- this.skip();
- }
-
window.navigator.platform = "Windows";
});
afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
+ Object.defineProperty(window, "navigator", origNavigator);
});
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js
index 6b59cde..0d8cac6 100644
--- a/tests/test.keyboard.js
+++ b/tests/test.keyboard.js
@@ -144,18 +144,10 @@ describe('Key Event Handling', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
- if (window.navigator.platform !== undefined) {
- // Object.defineProperty() doesn't work properly in old
- // versions of Chrome
- this.skip();
- }
-
window.navigator.platform = "Mac x86_64";
});
afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
+ Object.defineProperty(window, "navigator", origNavigator);
});
it('should change Alt to AltGraph', function () {
@@ -267,17 +259,10 @@ describe('Key Event Handling', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
- if (window.navigator.platform !== undefined) {
- // Object.defineProperty() doesn't work properly in old
- // versions of Chrome
- this.skip();
- }
});
afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
+ Object.defineProperty(window, "navigator", origNavigator);
});
it('should toggle caps lock on key press on iOS', function () {
@@ -334,19 +319,11 @@ describe('Key Event Handling', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
- if (window.navigator.platform !== undefined) {
- // Object.defineProperty() doesn't work properly in old
- // versions of Chrome
- this.skip();
- }
-
window.navigator.platform = "Windows";
});
afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
+ Object.defineProperty(window, "navigator", origNavigator);
});
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
@@ -375,20 +352,12 @@ describe('Key Event Handling', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
- if (window.navigator.platform !== undefined) {
- // Object.defineProperty() doesn't work properly in old
- // versions of Chrome
- this.skip();
- }
-
window.navigator.platform = "Windows x86_64";
this.clock = sinon.useFakeTimers();
});
afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
+ Object.defineProperty(window, "navigator", origNavigator);
if (this.clock !== undefined) {
this.clock.restore();
}
@@ -520,20 +489,12 @@ describe('Key Event Handling', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
- if (window.navigator.platform !== undefined) {
- // Object.defineProperty() doesn't work properly in old
- // versions of Chrome
- this.skip();
- }
-
window.navigator.platform = "Windows x86_64";
this.clock = sinon.useFakeTimers();
});
afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
+ Object.defineProperty(window, "navigator", origNavigator);
if (this.clock !== undefined) {
this.clock.restore();
}
diff --git a/tests/test.localization.js b/tests/test.localization.js
index 311353a..7e8e6c1 100644
--- a/tests/test.localization.js
+++ b/tests/test.localization.js
@@ -13,18 +13,10 @@ describe('Localization', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
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 () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
+ Object.defineProperty(window, "navigator", origNavigator);
});
it('should use English by default', function () {
diff --git a/tests/test.webutil.js b/tests/test.webutil.js
index 6681b3c..76aa763 100644
--- a/tests/test.webutil.js
+++ b/tests/test.webutil.js
@@ -66,11 +66,6 @@ describe('WebUtil', function () {
origLocalStorage = Object.getOwnPropertyDescriptor(window, "localStorage");
Object.defineProperty(window, "localStorage", {value: {}});
- if (window.localStorage.setItem !== undefined) {
- // Object.defineProperty() doesn't work properly in old
- // versions of Chrome
- this.skip();
- }
window.localStorage.setItem = sinon.stub();
window.localStorage.getItem = sinon.stub();
@@ -79,9 +74,7 @@ describe('WebUtil', function () {
return WebUtil.initSettings();
});
afterEach(function () {
- if (origLocalStorage !== undefined) {
- Object.defineProperty(window, "localStorage", origLocalStorage);
- }
+ Object.defineProperty(window, "localStorage", origLocalStorage);
});
describe('writeSetting', function () {