]> git.proxmox.com Git - mirror_novnc.git/commitdiff
Move resize handling in to RFB object
authorPierre Ossman <ossman@cendio.se>
Fri, 24 Nov 2017 14:25:23 +0000 (15:25 +0100)
committerPierre Ossman <ossman@cendio.se>
Wed, 6 Dec 2017 10:29:57 +0000 (11:29 +0100)
Makes the API simpler and makes it easier for other frontends to
get this functionality.

app/styles/base.css
app/styles/lite.css
app/ui.js
core/display.js
core/rfb.js
docs/API-internal.md
docs/API.md
tests/test.display.js
tests/test.rfb.js
vnc.html
vnc_lite.html

index b8ce81bd0f639ba725260a032d38081d109dc242..344db9b288ce4dfb6cc8785ff329e093adfdadbc 100644 (file)
@@ -854,30 +854,6 @@ select:active {
   ime-mode: disabled;
 }
 
-/* HTML5 Canvas */
-#noVNC_screen {
-  display: flex;
-  width: 100%;
-  height: 100%;
-  overflow: auto;
-  background-color: rgb(40, 40, 40);
-}
-:root:not(.noVNC_connected) #noVNC_screen {
-  display: none;
-}
-
-/* Do not set width/height for VNC_canvas or incorrect
- * scaling will occur. Canvas size depends on remote VNC
- * settings and noVNC settings. */
-#noVNC_canvas {
-  margin: auto;
-  /* IE miscalculates width without this :( */
-  flex-shrink: 0;
-}
-#noVNC_canvas:focus {
-  outline: none;
-}
-
 /*Default noVNC logo.*/
 /* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
 @font-face {
index b7df1e39b188e864af7954de22417e7f63d16541..13e11c7ec903867fc024cd40108946a349f7b535 100644 (file)
@@ -61,10 +61,3 @@ html {
   display: flex;
   justify-content: flex-end;
 }
-
-/* Do not set width/height for VNC_canvas or incorrect
- * scaling will occur. Canvas size depends on remote VNC
- * settings and noVNC settings. */
-#noVNC_canvas {
-  margin: auto;
-}
index d9da9a8805f5ab6db9dd8ddfcb31411dac11ffca..3c909cd277073cb538f36368e2d790424e81cf2a 100644 (file)
--- a/app/ui.js
+++ b/app/ui.js
@@ -27,7 +27,6 @@ var UI = {
     connected: false,
     desktopName: "",
 
-    resizeTimeout: null,
     statusTimeout: null,
     hideKeyboardTimeout: null,
     idleControlbarTimeout: null,
@@ -87,7 +86,6 @@ var UI = {
         UI.initFullscreen();
 
         // Setup event handlers
-        UI.addResizeHandlers();
         UI.addControlbarHandlers();
         UI.addTouchSpecificHandlers();
         UI.addExtraKeysHandlers();
@@ -103,8 +101,6 @@ var UI = {
 
         UI.openControlbar();
 
-        UI.updateViewClip();
-
         UI.updateVisualState('init');
 
         document.documentElement.classList.remove("noVNC_loading");
@@ -205,11 +201,6 @@ var UI = {
 * EVENT HANDLERS
 * ------v------*/
 
-    addResizeHandlers: function() {
-        window.addEventListener('resize', UI.applyResizeMode);
-        window.addEventListener('resize', UI.updateViewClip);
-    },
-
     addControlbarHandlers: function() {
         document.getElementById("noVNC_control_bar")
             .addEventListener('mousemove', UI.activateControlbar);
@@ -432,7 +423,6 @@ var UI = {
             UI.disableSetting('port');
             UI.disableSetting('path');
             UI.disableSetting('repeaterID');
-            UI.updateViewClip();
             UI.setMouseButton(1);
 
             // Hide the controlbar after 2 seconds
@@ -1037,7 +1027,7 @@ var UI = {
         }
         url += '/' + path;
 
-        UI.rfb = new RFB(document.getElementById('noVNC_canvas'), url,
+        UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
                          { shared: UI.getSetting('shared'),
                            repeaterID: UI.getSetting('repeaterID'),
                            credentials: { password: password } });
@@ -1045,11 +1035,13 @@ var UI = {
         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("capabilities", function () { UI.updatePowerButton(); });
         UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
         UI.rfb.addEventListener("bell", UI.bell);
-        UI.rfb.addEventListener("fbresize", UI.updateSessionSize);
         UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
+        UI.rfb.clipViewport = UI.getSetting('view_clip');
+        UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
+        UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
     },
 
     disconnect: function() {
@@ -1092,7 +1084,6 @@ var UI = {
     connectFinished: function (e) {
         UI.connected = true;
         UI.inhibit_reconnect = false;
-        UI.doneInitialResize = false;
 
         let msg;
         if (UI.getSetting('encrypt')) {
@@ -1104,7 +1095,7 @@ var UI = {
         UI.updateVisualState('connected');
 
         // Do this last because it can only be used on rendered elements
-        document.getElementById('noVNC_canvas').focus();
+        UI.rfb.focus();
     },
 
     disconnectFinished: function (e) {
@@ -1238,74 +1229,8 @@ var UI = {
     applyResizeMode: function() {
         if (!UI.rfb) return;
 
-        var screen = UI.screenSize();
-
-        if (screen && UI.connected) {
-
-            var resizeMode = UI.getSetting('resize');
-            UI.rfb.viewportScale = 1.0;
-
-            // Make sure the viewport is adjusted first
-            UI.updateViewClip();
-
-            if (resizeMode === 'remote') {
-
-                // Request changing the resolution of the remote display to
-                // the size of the local browser viewport.
-
-                // In order to not send multiple requests before the browser-resize
-                // is finished we wait 0.5 seconds before sending the request.
-                clearTimeout(UI.resizeTimeout);
-                UI.resizeTimeout = setTimeout(function(){
-                    // Request a remote size covering the viewport
-                    if (UI.rfb.requestDesktopSize(screen.w, screen.h)) {
-                        Log.Debug('Requested new desktop size: ' +
-                                   screen.w + 'x' + screen.h);
-                    }
-                }, 500);
-
-            } else {
-                UI.updateScaling();
-            }
-        }
-    },
-
-    // Re-calculate local scaling
-    updateScaling: function() {
-        if (!UI.rfb) return;
-
-        var resizeMode = UI.getSetting('resize');
-        if (resizeMode !== 'scale') {
-            return;
-        }
-
-        var screen = UI.screenSize();
-
-        if (!screen || !UI.connected) {
-            return;
-        }
-
-        UI.rfb.autoscale(screen.w, screen.h);
-        UI.fixScrollbars();
-    },
-
-    // Gets the the size of the available viewport in the browser window
-    screenSize: function() {
-        var screen = document.getElementById('noVNC_screen');
-        return {w: screen.offsetWidth, h: screen.offsetHeight};
-    },
-
-    // Normally we only apply the current resize mode after a window resize
-    // event. This means that when a new connection is opened, there is no
-    // resize mode active.
-    // We have to wait until we know the capabilities of the server as
-    // some calls later in the chain is dependant on knowing the
-    // server-capabilities.
-    initialResize: function() {
-        if (UI.doneInitialResize) return;
-
-        UI.applyResizeMode();
-        UI.doneInitialResize = true;
+        UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
+        UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
     },
 
 /* ------^-------
@@ -1314,12 +1239,6 @@ var UI = {
  * VIEW CLIPPING
  * ------v------*/
 
-    // Set and configure viewport clipping
-    setViewClip: function(clip) {
-        UI.updateSetting('view_clip', clip);
-        UI.updateViewClip();
-    },
-
     // Update parameters that depend on the viewport clip setting
     updateViewClip: function() {
         if (!UI.rfb) return;
@@ -1327,11 +1246,7 @@ var UI = {
         var cur_clip = UI.rfb.clipViewport;
         var new_clip = UI.getSetting('view_clip');
 
-        var resizeSetting = UI.getSetting('resize');
-        if (resizeSetting === 'scale') {
-            // Disable viewport clipping if we are scaling
-            new_clip = false;
-        } else if (isTouchDevice) {
+        if (isTouchDevice) {
             // Touch devices usually have shit scrollbars
             new_clip = true;
         }
@@ -1340,15 +1255,6 @@ var UI = {
             UI.rfb.clipViewport = new_clip;
         }
 
-        var size = UI.screenSize();
-
-        if (new_clip && size) {
-            // When clipping is enabled, the screen is limited to
-            // the size of the browser window.
-            UI.rfb.viewportChangeSize(size.w, size.h);
-            UI.fixScrollbars();
-        }
-
         // Changing the viewport may change the state of
         // the dragging button
         UI.updateViewDrag();
@@ -1389,23 +1295,13 @@ var UI = {
     },
 
     updateViewDrag: function() {
-        var clipping = false;
-
         if (!UI.connected) return;
 
-        // Check if viewport drag is possible. It is only possible
-        // if the remote display is clipping the client display.
-        if (UI.rfb.clipViewport && UI.rfb.isClipped) {
-            clipping = true;
-        }
-
         var viewDragButton = document.getElementById('noVNC_view_drag_button');
 
-        if (!clipping &&
-            UI.rfb.dragViewport) {
-            // The size of the remote display is the same or smaller
-            // than the client display. Make sure viewport drag isn't
-            // active when it can't be used.
+        if (!UI.rfb.clipViewport && 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;
         }
 
@@ -1420,7 +1316,7 @@ var UI = {
         if (isTouchDevice) {
             viewDragButton.classList.remove("noVNC_hidden");
 
-            if (clipping) {
+            if (UI.rfb.clipViewport) {
                 viewDragButton.disabled = false;
             } else {
                 viewDragButton.disabled = true;
@@ -1428,7 +1324,7 @@ var UI = {
         } else {
             viewDragButton.disabled = false;
 
-            if (clipping) {
+            if (UI.rfb.clipViewport) {
                 viewDragButton.classList.remove("noVNC_hidden");
             } else {
                 viewDragButton.classList.add("noVNC_hidden");
@@ -1703,24 +1599,6 @@ var UI = {
         WebUtil.init_logging(UI.getSetting('logging'));
     },
 
-    updateSessionSize: function(e) {
-        UI.updateViewClip();
-        UI.updateScaling();
-        UI.fixScrollbars();
-    },
-
-    fixScrollbars: function() {
-        // This is a hack because Chrome screws up the calculation
-        // for when scrollbars are needed. So to fix it we temporarily
-        // toggle them off and on.
-        var screen = document.getElementById('noVNC_screen');
-        screen.style.overflow = 'hidden';
-        // Force Chrome to recalculate the layout by asking for
-        // an element's dimensions
-        screen.getBoundingClientRect();
-        screen.style.overflow = "";
-    },
-
     updateDesktopName: function(e) {
         UI.desktopName = e.detail.name;
         // Display the desktop name in the document title
index e61802a6d22e7ca6e719c8083d2114cd3823a7e4..b252f99eff2ddbbbc794e0094578d116c5da58b5 100644 (file)
@@ -106,11 +106,6 @@ Display.prototype = {
         return this._fb_height;
     },
 
-    get isClipped() {
-        var vp = this._viewportLoc;
-        return this._fb_width > vp.w || this._fb_height > vp.h;
-    },
-
     logo: null,
 
     // ===== EVENT HANDLERS =====
index a5bd843e7d4f0c6dd77a7f2894c3bc2ec9cc406d..63c6c6baf8fc60a2ccdb21fc0f1cd04585185538 100644 (file)
@@ -66,7 +66,7 @@ export default function RFB(target, url, options) {
 
     this._fb_name = "";
 
-    this._capabilities = { power: false, resize: false };
+    this._capabilities = { power: false };
 
     this._supportsFence = false;
 
@@ -88,6 +88,7 @@ export default function RFB(target, url, options) {
 
     // Timers
     this._disconnTimer = null;      // disconnection timer
+    this._resizeTimeout = null;     // resize rate limiting
 
     // Decoder states and stats
     this._encHandlers = {};
@@ -140,15 +141,29 @@ export default function RFB(target, url, options) {
     // Bound event handlers
     this._eventHandlers = {
         focusCanvas: this._focusCanvas.bind(this),
+        windowResize: this._windowResize.bind(this),
     };
 
     // main setup
     Log.Debug(">> RFB.constructor");
 
-    // Target canvas must be able to have focus
-    if (!this._target.hasAttribute('tabindex')) {
-        this._target.tabIndex = -1;
-    }
+    // Create DOM elements
+    this._screen = document.createElement('div');
+    this._screen.style.display = 'flex';
+    this._screen.style.width = '100%';
+    this._screen.style.height = '100%';
+    this._screen.style.overflow = 'auto';
+    this._screen.style.backgroundColor = 'rgb(40, 40, 40)';
+    this._canvas = document.createElement('canvas');
+    this._canvas.style.margin = 'auto';
+    // Some browsers add an outline on focus
+    this._canvas.style.outline = 'none';
+    // IE miscalculates width without this :(
+    this._canvas.style.flexShrink = '0';
+    this._canvas.width = 0;
+    this._canvas.height = 0;
+    this._canvas.tabIndex = -1;
+    this._screen.appendChild(this._canvas);
 
     // populate encHandlers with bound versions
     this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this);
@@ -166,7 +181,7 @@ export default function RFB(target, url, options) {
     // NB: nothing that needs explicit teardown should be done
     // before this point, since this can throw an exception
     try {
-        this._display = new Display(this._target);
+        this._display = new Display(this._canvas);
     } catch (exc) {
         Log.Error("Display exception: " + exc);
         throw exc;
@@ -174,10 +189,10 @@ export default function RFB(target, url, options) {
     this._display.onflush = this._onFlush.bind(this);
     this._display.clear();
 
-    this._keyboard = new Keyboard(this._target);
+    this._keyboard = new Keyboard(this._canvas);
     this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
 
-    this._mouse = new Mouse(this._target);
+    this._mouse = new Mouse(this._canvas);
     this._mouse.onmousebutton = this._handleMouseButton.bind(this);
     this._mouse.onmousemove = this._handleMouseMove.bind(this);
 
@@ -266,13 +281,36 @@ RFB.prototype = {
     get touchButton() { return this._mouse.touchButton; },
     set touchButton(button) { this._mouse.touchButton = button; },
 
-    get viewportScale() { return this._display.scale; },
-    set viewportScale(scale) { this._display.scale = scale; },
+    _clipViewport: false,
+    get clipViewport() { return this._clipViewport; },
+    set clipViewport(viewport) {
+        this._clipViewport = viewport;
+        this._updateClip();
+    },
 
-    get clipViewport() { return this._display.clipViewport; },
-    set clipViewport(viewport) { this._display.clipViewport = viewport; },
+    _scaleViewport: false,
+    get scaleViewport() { return this._scaleViewport; },
+    set scaleViewport(scale) {
+        this._scaleViewport = scale;
+        // Scaling trumps clipping, so we may need to adjust
+        // clipping when enabling or disabling scaling
+        if (scale && this._clipViewport) {
+            this._updateClip();
+        }
+        this._updateScale();
+        if (!scale && this._clipViewport) {
+            this._updateClip();
+        }
+    },
 
-    get isClipped() { return this._display.isClipped; },
+    _resizeSession: false,
+    get resizeSession() { return this._resizeSession; },
+    set resizeSession(resize) {
+        this._resizeSession = resize;
+        if (resize) {
+            this._requestRemoteResize();
+        }
+    },
 
     // ===== PUBLIC METHODS =====
 
@@ -341,38 +379,19 @@ RFB.prototype = {
         }
     },
 
-    clipboardPasteFrom: function (text) {
-        if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
-        RFB.messages.clientCutText(this._sock, text);
-    },
-
-    autoscale: function (width, height) {
-        if (this._rfb_connection_state !== 'connected') { return; }
-        this._display.autoscale(width, height);
+    focus: function () {
+        this._canvas.focus();
     },
 
-    viewportChangeSize: function(width, height) {
-        if (this._rfb_connection_state !== 'connected') { return; }
-        this._display.viewportChangeSize(width, height);
+    blur: function () {
+        this._canvas.blur();
     },
 
-    // Requests a change of remote desktop size. This message is an extension
-    // and may only be sent if we have received an ExtendedDesktopSize message
-    requestDesktopSize: function (width, height) {
-        if (this._rfb_connection_state !== 'connected' ||
-            this._viewOnly) {
-            return;
-        }
-
-        if (!this._supportsSetDesktopSize) {
-            return;
-        }
-
-        RFB.messages.setDesktopSize(this._sock, width, height,
-                                    this._screen_id, this._screen_flags);
+    clipboardPasteFrom: function (text) {
+        if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
+        RFB.messages.clientCutText(this._sock, text);
     },
 
-
     // ===== PRIVATE METHODS =====
 
     _connect: function () {
@@ -391,20 +410,31 @@ RFB.prototype = {
             }
         }
 
+        // Make our elements part of the page
+        this._target.appendChild(this._screen);
+
+        // Monitor size changes of the screen
+        // FIXME: Use ResizeObserver, or hidden overflow
+        window.addEventListener('resize', this._eventHandlers.windowResize);
+
         // Always grab focus on some kind of click event
-        this._target.addEventListener("mousedown", this._eventHandlers.focusCanvas);
-        this._target.addEventListener("touchstart", this._eventHandlers.focusCanvas);
+        this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
+        this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
 
         Log.Debug("<< RFB.connect");
     },
 
     _disconnect: function () {
         Log.Debug(">> RFB.disconnect");
-        this._target.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
-        this._target.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
-        this._cleanup();
+        this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
+        this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
+        window.removeEventListener('resize', this._eventHandlers.windowResize);
+        this._keyboard.ungrab();
+        this._mouse.ungrab();
         this._sock.close();
         this._print_stats();
+        this._target.removeChild(this._screen);
+        clearTimeout(this._resizeTimeout);
         Log.Debug("<< RFB.disconnect");
     },
 
@@ -426,17 +456,6 @@ RFB.prototype = {
         });
     },
 
-    _cleanup: function () {
-        if (!this._viewOnly) { this._keyboard.ungrab(); }
-        if (!this._viewOnly) { this._mouse.ungrab(); }
-        this._display.defaultCursor();
-        if (Log.get_logging() !== 'debug') {
-            // Show noVNC logo when disconnected, unless in
-            // debug mode
-            this._display.clear();
-        }
-    },
-
     _focusCanvas: function(event) {
         // Respect earlier handlers' request to not do side-effects
         if (event.defaultPrevented) {
@@ -447,7 +466,97 @@ RFB.prototype = {
             return;
         }
 
-        this._target.focus();
+        this.focus();
+    },
+
+    _windowResize: function (event) {
+        // If the window resized then our screen element might have
+        // as well. Update the viewport dimensions.
+        window.requestAnimationFrame(function () {
+            this._updateClip();
+            this._updateScale();
+        }.bind(this));
+
+        if (this._resizeSession) {
+            // Request changing the resolution of the remote display to
+            // the size of the local browser viewport.
+
+            // In order to not send multiple requests before the browser-resize
+            // is finished we wait 0.5 seconds before sending the request.
+            clearTimeout(this._resizeTimeout);
+            this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
+        }
+    },
+
+    // Update state of clipping in Display object, and make sure the
+    // configured viewport matches the current screen size
+    _updateClip: function () {
+        var cur_clip = this._display.clipViewport;
+        var new_clip = this._clipViewport;
+
+        if (this._scaleViewport) {
+            // Disable viewport clipping if we are scaling
+            new_clip = false;
+        }
+
+        if (cur_clip !== new_clip) {
+            this._display.clipViewport = new_clip;
+        }
+
+        if (new_clip) {
+            // When clipping is enabled, the screen is limited to
+            // the size of the container.
+            let size = this._screenSize();
+            this._display.viewportChangeSize(size.w, size.h);
+            this._fixScrollbars();
+        }
+    },
+
+    _updateScale: function () {
+        if (!this._scaleViewport) {
+            this._display.scale = 1.0;
+        } else {
+            let size = this._screenSize();
+            this._display.autoscale(size.w, size.h);
+        }
+        this._fixScrollbars();
+    },
+
+    // Requests a change of remote desktop size. This message is an extension
+    // and may only be sent if we have received an ExtendedDesktopSize message
+    _requestRemoteResize: function () {
+        clearTimeout(this._resizeTimeout);
+        this._resizeTimeout = null;
+
+        if (!this._resizeSession || this._viewOnly ||
+            !this._supportsSetDesktopSize) {
+            return;
+        }
+
+        let size = this._screenSize();
+        RFB.messages.setDesktopSize(this._sock, size.w, size.h,
+                                    this._screen_id, this._screen_flags);
+
+        Log.Debug('Requested new desktop size: ' +
+                   size.w + 'x' + size.h);
+    },
+
+    // Gets the the size of the available screen
+    _screenSize: function () {
+        return { w: this._screen.offsetWidth,
+                 h: this._screen.offsetHeight };
+    },
+
+    _fixScrollbars: function () {
+        // This is a hack because Chrome screws up the calculation
+        // for when scrollbars are needed. So to fix it we temporarily
+        // toggle them off and on.
+        var orig = this._screen.style.overflow;
+        this._screen.style.overflow = 'hidden';
+        // Force Chrome to recalculate the layout by asking for
+        // an element's dimensions
+        this._screen.getBoundingClientRect();
+        this._screen.style.overflow = orig;
     },
 
     /*
@@ -1467,10 +1576,9 @@ RFB.prototype = {
 
         this._display.resize(this._fb_width, this._fb_height);
 
-        var event = new CustomEvent("fbresize",
-                                    { detail: { width: this._fb_width,
-                                                height: this._fb_height } });
-        this.dispatchEvent(event);
+        // Adjust the visible viewport based on the new dimensions
+        this._updateClip();
+        this._updateScale();
 
         this._timing.fbu_rt_start = (new Date()).getTime();
         this._updateContinuousUpdates();
@@ -2308,8 +2416,16 @@ RFB.encodingHandlers = {
         this._FBU.bytes = 1;
         if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
 
+        var firstUpdate = !this._supportsSetDesktopSize;
         this._supportsSetDesktopSize = true;
-        this._setCapability("resize", true);
+
+        // Normally we only apply the current resize mode after a
+        // window resize event. However there is no such trigger on the
+        // initial connect. And we don't know if the server supports
+        // resizing until we've gotten here.
+        if (firstUpdate) {
+            this._requestRemoteResize();
+        }
 
         var number_of_screens = this._sock.rQpeek8();
 
index f030dc38684f2dc4e4973209061c7a6a300789d7..4943c1a107555c0172955ecabb6a0fbd867d6087 100644 (file)
@@ -89,7 +89,6 @@ None
 | clipViewport | bool  | RW   | false   | Use viewport clipping
 | width        | int   | RO   |         | Display area width
 | height       | int   | RO   |         | Display area height
-| isClipped    | bool  | RO   |         | Is the remote display is larger than the client display
 
 ### 2.3.2 Methods
 
index 4d4f4d51251284b2f94bd3657eaf092cbb5284d9..c5923e3fbbd97532705e78f8bab5bdcfc45c0abc 100644 (file)
@@ -23,8 +23,8 @@ protocol stream.
 
 `focusOnClick`
   - Is a `boolean` indicating if keyboard focus should automatically be
-    moved to the canvas when a `mousedown` or `touchstart` event is
-    received.
+    moved to the remote session when a `mousedown` or `touchstart`
+    event is received.
 
 `touchButton`
   - Is a `long` controlling the button mask that should be simulated
@@ -32,24 +32,26 @@ protocol stream.
     [`MouseEvent.button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
     Is set to `1` by default.
 
-`viewportScale`
-  - Is a `double` indicating how the framebuffer contents should be
-    scaled before being rendered on to the canvas. See also
-    [`RFB.autoscale()`](#rfbautoscale). Is set to `1.0` by default.
-
 `clipViewport`
-  - Is a `boolean` indicating if the canvas should be clipped to its
-    container. When disabled the container must be able to handle the
-    resulting overflow. Disabled by default.
+  - 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.
 
 `dragViewport`
   - Is a `boolean` indicating if mouse events should control the
-    relative position of a clipped canvas. Only relevant if
+    relative position of a clipped remote session. Only relevant if
     `clipViewport` is enabled. Disabled by default.
 
-`isClipped` *Read only*
-  - Is a `boolean` indicating if the framebuffer is larger than the
-    current canvas, i.e. it is being clipped.
+`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.
+
+`resizeSession`
+  - Is a `boolean` indicating if a request to resize the remote session
+    should be sent whenever the container changes dimensions. Disabled
+    by default.
 
 `capabilities` *Read only*
   - Is an `Object` indicating which optional extensions are available
@@ -59,7 +61,6 @@ protocol stream.
     | name     | type      | description
     | -------- | --------- | -----------
     | `power`  | `boolean` | Machine power control is available
-    | `resize` | `boolean` | The framebuffer can be resized
 
 ### Events
 
@@ -86,9 +87,6 @@ protocol stream.
   - The `bell` event is fired when a audible bell request is received
     from the server.
 
-[`fbresize`](#fbresize)
-  - The `fbresize` event is fired when the framebuffer size is changed.
-
 [`desktopname`](#desktopname)
   - The `desktopname` event is fired when the remote desktop name
     changes.
@@ -112,6 +110,12 @@ protocol stream.
 [`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)
   - Send Ctrl-Alt-Del key sequence.
 
+[`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.
 
@@ -124,16 +128,6 @@ protocol stream.
 [`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom)
   - Send clipboard contents to server.
 
-[`RFB.autoscale()`](#rfbautoscale)
-  - Set `RFB.viewportScale` so that the framebuffer fits a specified
-    container.
-
-[`RFB.requestDesktopSize()`](#rfbrequestDesktopSize)
-  - Send a request to change the remote desktop size.
-
-[`RFB.viewportChangeSize()`](#rfbviewportChangeSize)
-  - Change size of the viewport.
-
 ### Details
 
 #### RFB()
@@ -148,9 +142,10 @@ connection to a specified VNC server.
 ###### Parameters
 
 **`target`**
-  - A [`HTMLCanvasElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement)
-    that specifies where graphics should be rendered and input events
-    should be monitored.
+  - A block [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement)
+    that specifies where the `RFB` object should attach itself. The
+    existing contents of the `HTMLElement` will be untouched, but new
+    elements will be added during the lifetime of the `RFB` object.
 
 **`url`**
   - A `DOMString` specifying the VNC server to connect to. This must be
@@ -233,12 +228,6 @@ which is a `DOMString` with the clipboard data.
 The `bell` event is fired when the server has requested an audible
 bell.
 
-#### fbresize
-
-The `fbresize` event is fired when the framebuffer has changed
-dimensions. The `detail` property is an `Object` with the properties
-`width` and `height` specifying the new dimensions.
-
 #### desktopname
 
 The `desktopname` event is fired when the name of the remote desktop
@@ -310,6 +299,25 @@ around [`RFB.sendKey()`](#rfbsendkey).
 
     RFB.sendCtrlAltDel( );
 
+#### RFB.focus()
+
+The `RFB.focus()` method sets the keyboard focus on the remote session.
+Keyboard events will be sent to the remote server after this point.
+
+##### Syntax
+
+    RFB.focus( );
+
+#### 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()
 
 The `RFB.machineShutdown()` method is used to request to shut down the
@@ -354,61 +362,3 @@ to the remote server.
 **`text`**
   - A `DOMString` specifying the clipboard data to send. Currently only
   characters from ISO 8859-1 are supported.
-
-#### RFB.autoscale()
-
-The `RFB.autoscale()` method is used to automatically adjust
-`RFB.viewportScale` to fit given dimensions.
-
-##### Syntax
-
-    RFB.autoscale( width, height );
-
-###### Parameters
-
-**`width`**
-  - A `long` specifying the maximum width of the canvas in CSS pixels.
-
-**`height`**
-  - A `long` specifying the maximum height of the canvas in CSS pixels.
-
-#### RFB.requestDesktopSize()
-
-The `RFB.requestDesktopSize()` method is used to request a change of
-the framebuffer. The capability `resize` must be set for this method to
-have any effect.
-
-Note that this is merely a request and the server may deny it.
-The [`fbresize`](#fbresize) event will be fired when the framebuffer
-actually changes dimensions.
-
-##### Syntax
-
-    RFB.requestDesktopSize( width, height );
-
-###### Parameters
-
-**`width`**
-  - A `long` specifying the new requested width in CSS pixels.
-
-**`height`**
-  - A `long` specifying the new requested height in CSS pixels.
-
-#### RFB.viewportChangeSize()
-
-The `RFB.viewportChangeSize()` method is used to change the size of the
-canvas rather than the underlying framebuffer.
-
-This method has no effect if `RFB.clipViewport` is set to `false`.
-
-##### Syntax
-
-    RFB.viewportChangeSize( width, height );
-
-###### Parameters
-
-**`width`**
-  - A `long` specifying the new width in CSS pixels.
-
-**`height`**
-  - A `long` specifying the new height in CSS pixels.
index b8e9b51f5492a7d78df4d911dfd6b4fe6537b587..9e6f0491b151e096f842c1d892b783b896c072f6 100644 (file)
@@ -91,15 +91,6 @@ describe('Display/Canvas Helper', function () {
             expect(display.flip).to.have.been.calledOnce;
         });
 
-        it('should report clipping when framebuffer > viewport', function () {
-            expect(display.isClipped).to.be.true;
-        });
-
-        it('should report not clipping when framebuffer = viewport', function () {
-            display.viewportChangeSize(5, 5);
-            expect(display.isClipped).to.be.false;
-        });
-
         it('should show the entire framebuffer when disabling the viewport', function() {
             display.clipViewport = false;
             expect(display.absX(0)).to.equal(0);
index 542ce514b026b9166782ff3ca6608dc712cc69c6..31a7f2d58f0f79e2a99fd24410a57d3a7641cef3 100644 (file)
@@ -9,6 +9,22 @@ import { encodings } from '../core/encodings.js';
 import FakeWebSocket from './fake.websocket.js';
 import sinon from '../vendor/sinon.js';
 
+/* UIEvent constructor polyfill for IE */
+(function () {
+    if (typeof window.UIEvent === "function") return;
+
+    function UIEvent ( event, params ) {
+        params = params || { bubbles: false, cancelable: false, view: window, detail: undefined };
+        var evt = document.createEvent( 'UIEvent' );
+        evt.initUIEvent( event, params.bubbles, params.cancelable, params.view, params.detail );
+        return evt;
+    }
+
+    UIEvent.prototype = window.UIEvent.prototype;
+
+    window.UIEvent = UIEvent;
+})();
+
 var push8 = function (arr, num) {
     "use strict";
     arr.push(num & 0xFF);
@@ -30,12 +46,16 @@ var push32 = function (arr, num) {
 
 describe('Remote Frame Buffer Protocol Client', function() {
     var clock;
+    var raf;
 
     before(FakeWebSocket.replace);
     after(FakeWebSocket.restore);
 
     before(function () {
         this.clock = clock = sinon.useFakeTimers();
+        // sinon doesn't support this yet
+        raf = window.requestAnimationFrame;
+        window.requestAnimationFrame = setTimeout;
         // Use a single set of buffers instead of reallocating to
         // speed up tests
         var sock = new Websock();
@@ -53,12 +73,20 @@ describe('Remote Frame Buffer Protocol Client', function() {
     after(function () {
         Websock.prototype._allocate_buffers = Websock.prototype._old_allocate_buffers;
         this.clock.restore();
+        window.requestAnimationFrame = raf;
     });
 
+    var container;
     var rfbs;
 
     beforeEach(function () {
-        // Track all created RFB objects
+        // Create a container element for all RFB objects to attach to
+        container = document.createElement('div');
+        container.style.width = "100%";
+        container.style.height = "100%";
+        document.body.appendChild(container);
+
+        // And track all created RFB objects
         rfbs = [];
     });
     afterEach(function () {
@@ -69,11 +97,14 @@ describe('Remote Frame Buffer Protocol Client', function() {
             expect(rfb._disconnect).to.have.been.called;
         });
         rfbs = [];
+
+        document.body.removeChild(container);
+        container = null;
     });
 
     function make_rfb (url, options) {
         url = url || 'wss://host:8675';
-        var rfb = new RFB(document.createElement('canvas'), url, options);
+        var rfb = new RFB(container, url, options);
         clock.tick();
         rfb._sock._websocket._open();
         rfb._rfb_connection_state = 'connected';
@@ -85,14 +116,14 @@ describe('Remote Frame Buffer Protocol Client', function() {
     describe('Connecting/Disconnecting', function () {
         describe('#RFB', function () {
             it('should set the current state to "connecting"', function () {
-                var client = new RFB(document.createElement('canvas'), 'wss://host:8675');
+                var client = new RFB(document.createElement('div'), 'wss://host:8675');
                 client._rfb_connection_state = '';
                 this.clock.tick();
                 expect(client._rfb_connection_state).to.equal('connecting');
             });
 
             it('should actually connect to the websocket', function () {
-                var client = new RFB(document.createElement('canvas'), 'ws://HOST:8675/PATH');
+                var client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
                 sinon.spy(client._sock, 'open');
                 this.clock.tick();
                 expect(client._sock.open).to.have.been.calledOnce;
@@ -239,6 +270,22 @@ describe('Remote Frame Buffer Protocol Client', function() {
             });
         });
 
+        describe('#focus', function () {
+            it('should move focus to canvas object', function () {
+                client._canvas.focus = sinon.spy();
+                client.focus();
+                expect(client._canvas.focus).to.have.been.called.once;
+            });
+        });
+
+        describe('#blur', function () {
+            it('should remove focus from canvas object', function () {
+                client._canvas.blur = sinon.spy();
+                client.blur();
+                expect(client._canvas.blur).to.have.been.called.once;
+            });
+        });
+
         describe('#clipboardPasteFrom', function () {
             it('should send the given text in a paste event', function () {
                 var expected = {_sQ: new Uint8Array(11), _sQlen: 0, flush: function () {}};
@@ -255,44 +302,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
             });
         });
 
-        describe("#requestDesktopSize", function () {
-            beforeEach(function() {
-                client._supportsSetDesktopSize = true;
-            });
-
-            it('should send the request with the given width and height', function () {
-                var expected = [251];
-                push8(expected, 0);  // padding
-                push16(expected, 1); // width
-                push16(expected, 2); // height
-                push8(expected, 1);  // number-of-screens
-                push8(expected, 0);  // padding before screen array
-                push32(expected, 0); // id
-                push16(expected, 0); // x-position
-                push16(expected, 0); // y-position
-                push16(expected, 1); // width
-                push16(expected, 2); // height
-                push32(expected, 0); // flags
-
-                client.requestDesktopSize(1, 2);
-                expect(client._sock).to.have.sent(new Uint8Array(expected));
-            });
-
-            it('should not send the request if the client has not recieved a ExtendedDesktopSize rectangle', function () {
-                sinon.spy(client._sock, 'flush');
-                client._supportsSetDesktopSize = false;
-                client.requestDesktopSize(1,2);
-                expect(client._sock.flush).to.not.have.been.called;
-            });
-
-            it('should not send the request if we are not in a normal state', function () {
-                sinon.spy(client._sock, 'flush');
-                client._rfb_connection_state = "connecting";
-                client.requestDesktopSize(1,2);
-                expect(client._sock.flush).to.not.have.been.called;
-            });
-        });
-
         describe("XVP operations", function () {
             beforeEach(function () {
                 client._rfb_xvp_ver = 1;
@@ -321,6 +330,394 @@ describe('Remote Frame Buffer Protocol Client', function() {
         });
     });
 
+    describe('Clipping', function () {
+        var client;
+        beforeEach(function () {
+            client = make_rfb();
+            container.style.width = '70px';
+            container.style.height = '80px';
+            client.clipViewport = true;
+        });
+
+        it('should update display clip state when changing the property', function () {
+            var spy = sinon.spy(client._display, "clipViewport", ["set"]);
+
+            client.clipViewport = false;
+            expect(spy.set).to.have.been.calledOnce;
+            expect(spy.set).to.have.been.calledWith(false);
+            spy.set.reset();
+
+            client.clipViewport = true;
+            expect(spy.set).to.have.been.calledOnce;
+            expect(spy.set).to.have.been.calledWith(true);
+        });
+
+        it('should update the viewport when the container size changes', function () {
+            sinon.spy(client._display, "viewportChangeSize");
+
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick();
+
+            expect(client._display.viewportChangeSize).to.have.been.calledOnce;
+            expect(client._display.viewportChangeSize).to.have.been.calledWith(40, 50);
+        });
+
+        it('should update the viewport when the remote session resizes', function () {
+            // Simple ExtendedDesktopSize FBU message
+            var incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+                             0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
+                             0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                             0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
+                             0x00, 0x00, 0x00, 0x00 ];
+
+            sinon.spy(client._display, "viewportChangeSize");
+
+            client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+            // FIXME: Display implicitly calls viewportChangeSize() when
+            //        resizing the framebuffer, hence calledTwice.
+            expect(client._display.viewportChangeSize).to.have.been.calledTwice;
+            expect(client._display.viewportChangeSize).to.have.been.calledWith(70, 80);
+        });
+
+        it('should not update the viewport if not clipping', function () {
+            client.clipViewport = false;
+            sinon.spy(client._display, "viewportChangeSize");
+
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick();
+
+            expect(client._display.viewportChangeSize).to.not.have.been.called;
+        });
+
+        it('should not update the viewport if scaling', function () {
+            client.scaleViewport = true;
+            sinon.spy(client._display, "viewportChangeSize");
+
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick();
+
+            expect(client._display.viewportChangeSize).to.not.have.been.called;
+        });
+
+        describe('Dragging', function () {
+            beforeEach(function () {
+                client.dragViewport = true;
+                sinon.spy(RFB.messages, "pointerEvent");
+            });
+
+            afterEach(function () {
+                RFB.messages.pointerEvent.restore();
+            });
+
+            it('should not send button messages when initiating viewport dragging', function () {
+                client._handleMouseButton(13, 9, 0x001);
+                expect(RFB.messages.pointerEvent).to.not.have.been.called;
+            });
+
+            it('should send button messages when release without movement', function () {
+                // Just up and down
+                client._handleMouseButton(13, 9, 0x001);
+                client._handleMouseButton(13, 9, 0x000);
+                expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
+
+                RFB.messages.pointerEvent.reset();
+
+                // Small movement
+                client._handleMouseButton(13, 9, 0x001);
+                client._handleMouseMove(15, 14);
+                client._handleMouseButton(15, 14, 0x000);
+                expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
+            });
+
+            it('should send button message directly when drag is disabled', function () {
+                client.dragViewport = false;
+                client._handleMouseButton(13, 9, 0x001);
+                expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
+            });
+
+            it('should be initiate viewport dragging on sufficient movement', function () {
+                sinon.spy(client._display, "viewportChangePos");
+
+                // Too small movement
+
+                client._handleMouseButton(13, 9, 0x001);
+                client._handleMouseMove(18, 9);
+
+                expect(RFB.messages.pointerEvent).to.not.have.been.called;
+                expect(client._display.viewportChangePos).to.not.have.been.called;
+
+                // Sufficient movement
+
+                client._handleMouseMove(43, 9);
+
+                expect(RFB.messages.pointerEvent).to.not.have.been.called;
+                expect(client._display.viewportChangePos).to.have.been.calledOnce;
+                expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0);
+
+                client._display.viewportChangePos.reset();
+
+                // Now a small movement should move right away
+
+                client._handleMouseMove(43, 14);
+
+                expect(RFB.messages.pointerEvent).to.not.have.been.called;
+                expect(client._display.viewportChangePos).to.have.been.calledOnce;
+                expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5);
+            });
+
+            it('should not send button messages when dragging ends', function () {
+                // First the movement
+
+                client._handleMouseButton(13, 9, 0x001);
+                client._handleMouseMove(43, 9);
+                client._handleMouseButton(43, 9, 0x000);
+
+                expect(RFB.messages.pointerEvent).to.not.have.been.called;
+            });
+
+            it('should terminate viewport dragging on a button up event', function () {
+                // First the dragging movement
+
+                client._handleMouseButton(13, 9, 0x001);
+                client._handleMouseMove(43, 9);
+                client._handleMouseButton(43, 9, 0x000);
+
+                // Another movement now should not move the viewport
+
+                sinon.spy(client._display, "viewportChangePos");
+
+                client._handleMouseMove(43, 59);
+
+                expect(client._display.viewportChangePos).to.not.have.been.called;
+            });
+        });
+    });
+
+    describe('Scaling', function () {
+        var client;
+        beforeEach(function () {
+            client = make_rfb();
+            container.style.width = '70px';
+            container.style.height = '80px';
+            client.scaleViewport = true;
+        });
+
+        it('should update display scale factor when changing the property', function () {
+            var spy = sinon.spy(client._display, "scale", ["set"]);
+            sinon.spy(client._display, "autoscale");
+
+            client.scaleViewport = false;
+            expect(spy.set).to.have.been.calledOnce;
+            expect(spy.set).to.have.been.calledWith(1.0);
+            expect(client._display.autoscale).to.not.have.been.called;
+
+            client.scaleViewport = true;
+            expect(client._display.autoscale).to.have.been.calledOnce;
+            expect(client._display.autoscale).to.have.been.calledWith(70, 80);
+        });
+
+        it('should update the clipping setting when changing the property', function () {
+            client.clipViewport = true;
+
+            var spy = sinon.spy(client._display, "clipViewport", ["set"]);
+
+            client.scaleViewport = false;
+            expect(spy.set).to.have.been.calledOnce;
+            expect(spy.set).to.have.been.calledWith(true);
+
+            spy.set.reset();
+
+            client.scaleViewport = true;
+            expect(spy.set).to.have.been.calledOnce;
+            expect(spy.set).to.have.been.calledWith(false);
+        });
+
+        it('should update the scaling when the container size changes', function () {
+            sinon.spy(client._display, "autoscale");
+
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick();
+
+            expect(client._display.autoscale).to.have.been.calledOnce;
+            expect(client._display.autoscale).to.have.been.calledWith(40, 50);
+        });
+
+        it('should update the scaling when the remote session resizes', function () {
+            // Simple ExtendedDesktopSize FBU message
+            var incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+                             0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
+                             0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                             0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
+                             0x00, 0x00, 0x00, 0x00 ];
+
+            sinon.spy(client._display, "autoscale");
+
+            client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+            expect(client._display.autoscale).to.have.been.calledOnce;
+            expect(client._display.autoscale).to.have.been.calledWith(70, 80);
+        });
+
+        it('should not update the display scale factor if not scaling', function () {
+            client.scaleViewport = false;
+
+            sinon.spy(client._display, "autoscale");
+
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick();
+
+            expect(client._display.autoscale).to.not.have.been.called;
+        });
+    });
+
+    describe('Remote resize', function () {
+        var client;
+        beforeEach(function () {
+            client = make_rfb();
+            client._supportsSetDesktopSize = true;
+            client.resizeSession = true;
+            container.style.width = '70px';
+            container.style.height = '80px';
+            sinon.spy(RFB.messages, "setDesktopSize");
+        });
+
+        afterEach(function () {
+            RFB.messages.setDesktopSize.restore();
+        });
+
+        it('should only request a resize when turned on', function () {
+            client.resizeSession = false;
+            expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+            client.resizeSession = true;
+            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
+        });
+
+        it('should request a resize when initially connecting', function () {
+            // Simple ExtendedDesktopSize FBU message
+            var incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+                             0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
+                             0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                             0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
+                             0x00, 0x00, 0x00, 0x00 ];
+
+            // First message should trigger a resize
+
+            client._supportsSetDesktopSize = false;
+
+            client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
+            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 70, 80, 0, 0);
+
+            RFB.messages.setDesktopSize.reset();
+
+            // Second message should not trigger a resize
+
+            client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+            expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+        });
+
+        it('should request a resize when the container resizes', function () {
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick(1000);
+
+            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
+            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
+        });
+
+        it('should not resize until the container size is stable', function () {
+            container.style.width = '20px';
+            container.style.height = '30px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick(400);
+
+            expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick(400);
+
+            expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+
+            clock.tick(200);
+
+            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
+            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
+        });
+
+        it('should not resize when resize is disabled', function () {
+            client._resizeSession = false;
+
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick(1000);
+
+            expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+        });
+
+        it('should not resize when resize is not supported', function () {
+            client._supportsSetDesktopSize = false;
+
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick(1000);
+
+            expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+        });
+
+        it('should not resize when in view only mode', function () {
+            client._viewOnly = true;
+
+            container.style.width = '40px';
+            container.style.height = '50px';
+            var event = new UIEvent('resize');
+            window.dispatchEvent(event);
+            clock.tick(1000);
+
+            expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+        });
+
+        it('should not try to override a server resize', function () {
+            // Simple ExtendedDesktopSize FBU message
+            var incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+                             0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
+                             0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                             0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
+                             0x00, 0x00, 0x00, 0x00 ];
+
+            client._sock._websocket._receive_data(new Uint8Array(incoming));
+
+            expect(RFB.messages.setDesktopSize).to.not.have.been.called;
+        });
+    });
+
     describe('Misc Internals', function () {
         describe('#_updateConnectionState', function () {
             var client;
@@ -421,7 +818,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
     describe('Connection States', function () {
         describe('connecting', function () {
             it('should open the websocket connection', function () {
-                var client = new RFB(document.createElement('canvas'),
+                var client = new RFB(document.createElement('div'),
                                      'ws://HOST:8675/PATH');
                 sinon.spy(client._sock, 'open');
                 this.clock.tick();
@@ -494,7 +891,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
         describe('disconnected', function () {
             var client;
             beforeEach(function () {
-                client = new RFB(document.createElement('canvas'), 'ws://HOST:8675/PATH');
+                client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
             });
 
             it('should result in a disconnect event if state becomes "disconnected"', function () {
@@ -1082,17 +1479,12 @@ describe('Remote Frame Buffer Protocol Client', function() {
                 expect(client._rfb_connection_state).to.equal('connected');
             });
 
-            it('should call the resize callback and resize the display', function () {
-                var spy = sinon.spy();
-                client.addEventListener("fbresize", spy);
+            it('should resize the display', function () {
                 sinon.spy(client._display, 'resize');
                 send_server_init({ width: 27, height: 32 }, client);
 
                 expect(client._display.resize).to.have.been.calledOnce;
                 expect(client._display.resize).to.have.been.calledWith(27, 32);
-                expect(spy).to.have.been.calledOnce;
-                expect(spy.args[0][0].detail.width).to.equal(27);
-                expect(spy.args[0][0].detail.height).to.equal(32);
             });
 
             it('should grab the mouse and keyboard', function () {
@@ -1493,14 +1885,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
 
                 it('should handle the DesktopSize pseduo-encoding', function () {
                     var spy = sinon.spy();
-                    client.addEventListener("fbresize", spy);
                     sinon.spy(client._display, 'resize');
                     send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
 
-                    expect(spy).to.have.been.calledOnce;
-                    expect(spy.args[0][0].detail.width).to.equal(20);
-                    expect(spy.args[0][0].detail.height).to.equal(50);
-
                     expect(client._fb_width).to.equal(20);
                     expect(client._fb_height).to.equal(50);
 
@@ -1512,14 +1899,12 @@ describe('Remote Frame Buffer Protocol Client', function() {
                     var resizeSpy;
 
                     beforeEach(function () {
-                        client._supportsSetDesktopSize = false;
                         // a really small frame
                         client._fb_width = 4;
                         client._fb_height = 4;
                         client._display.resize(4, 4);
                         sinon.spy(client._display, 'resize');
                         resizeSpy = sinon.spy();
-                        client.addEventListener("fbresize", resizeSpy);
                     });
 
                     function make_screen_data (nr_of_screens) {
@@ -1538,26 +1923,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
                         return data;
                     }
 
-                    it('should call callback when resize is supported', function () {
-                        var spy = sinon.spy();
-                        client.addEventListener("capabilities", spy);
-
-                        expect(client._supportsSetDesktopSize).to.be.false;
-                        expect(client.capabilities.resize).to.be.false;
-
-                        var reason_for_change = 0; // server initiated
-                        var status_code       = 0; // No error
-
-                        send_fbu_msg([{ x: reason_for_change, y: status_code,
-                                        width: 4, height: 4, encoding: -308 }],
-                                     make_screen_data(1), client);
-
-                        expect(client._supportsSetDesktopSize).to.be.true;
-                        expect(spy).to.have.been.calledOnce;
-                        expect(spy.args[0][0].detail.capabilities.resize).to.be.true;
-                        expect(client.capabilities.resize).to.be.true;
-                    }),
-
                     it('should handle a resize requested by this client', function () {
                         var reason_for_change = 1; // requested by this client
                         var status_code       = 0; // No error
@@ -1571,10 +1936,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
 
                         expect(client._display.resize).to.have.been.calledOnce;
                         expect(client._display.resize).to.have.been.calledWith(20, 50);
-
-                        expect(resizeSpy).to.have.been.calledOnce;
-                        expect(resizeSpy.args[0][0].detail.width).to.equal(20);
-                        expect(resizeSpy.args[0][0].detail.height).to.equal(50);
                     });
 
                     it('should handle a resize requested by another client', function () {
@@ -1590,10 +1951,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
 
                         expect(client._display.resize).to.have.been.calledOnce;
                         expect(client._display.resize).to.have.been.calledWith(20, 50);
-
-                        expect(resizeSpy).to.have.been.calledOnce;
-                        expect(resizeSpy.args[0][0].detail.width).to.equal(20);
-                        expect(resizeSpy.args[0][0].detail.height).to.equal(50);
                     });
 
                     it('should be able to recieve requests which contain data for multiple screens', function () {
@@ -1609,10 +1966,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
 
                         expect(client._display.resize).to.have.been.calledOnce;
                         expect(client._display.resize).to.have.been.calledWith(60, 50);
-
-                        expect(resizeSpy).to.have.been.calledOnce;
-                        expect(resizeSpy.args[0][0].detail.width).to.equal(60);
-                        expect(resizeSpy.args[0][0].detail.height).to.equal(50);
                     });
 
                     it('should not handle a failed request', function () {
@@ -1627,8 +1980,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
                         expect(client._fb_height).to.equal(4);
 
                         expect(client._display.resize).to.not.have.been.called;
-
-                        expect(resizeSpy).to.not.have.been.called;
                     });
                 });
 
@@ -1808,60 +2159,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
                 RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
                 expect(client._sock).to.have.sent(pointer_msg._sQ);
             });
-
-            // NB(directxman12): we don't need to test not sending messages in
-            //                   non-normal modes, since we haven't grabbed input
-            //                   yet (grabbing input should be checked in the lifecycle tests).
-
-            it('should not send movement messages when viewport dragging', function () {
-                client._viewportDragging = true;
-                client._display.viewportChangePos = sinon.spy();
-                sinon.spy(client._sock, 'flush');
-                client._handleMouseMove(13, 9);
-                expect(client._sock.flush).to.not.have.been.called;
-            });
-
-            it('should not send button messages when initiating viewport dragging', function () {
-                client.dragViewport = true;
-                sinon.spy(client._sock, 'flush');
-                client._handleMouseButton(13, 9, 0x001);
-                expect(client._sock.flush).to.not.have.been.called;
-            });
-
-            it('should be initiate viewport dragging on a button down event, if enabled', function () {
-                client.dragViewport = true;
-                client._handleMouseButton(13, 9, 0x001);
-                expect(client._viewportDragging).to.be.true;
-                expect(client._viewportDragPos).to.deep.equal({ x: 13, y: 9 });
-            });
-
-            it('should terminate viewport dragging on a button up event, if enabled', function () {
-                client.dragViewport = true;
-                client._viewportDragging = true;
-                client._handleMouseButton(13, 9, 0x000);
-                expect(client._viewportDragging).to.be.false;
-            });
-
-            it('if enabled, viewportDragging should occur on mouse movement while a button is down', function () {
-                var oldX = 123;
-                var oldY = 109;
-                var newX = 123 + 11 * window.devicePixelRatio;
-                var newY = 109 + 4 * window.devicePixelRatio;
-
-                client.dragViewport = true;
-                client._viewportDragging = true;
-                client._viewportHasMoved = false;
-                client._viewportDragPos = { x: oldX, y: oldY };
-                client._display.viewportChangePos = sinon.spy();
-
-                client._handleMouseMove(newX, newY);
-
-                expect(client._viewportDragging).to.be.true;
-                expect(client._viewportHasMoved).to.be.true;
-                expect(client._viewportDragPos).to.deep.equal({ x: newX, y: newY });
-                expect(client._display.viewportChangePos).to.have.been.calledOnce;
-                expect(client._display.viewportChangePos).to.have.been.calledWith(oldX - newX, oldY - newY);
-            });
         });
 
         describe('Keyboard Event Handlers', function () {
@@ -1912,7 +2209,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
 
             // open events
             it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
-                client = new RFB(document.createElement('canvas'), 'wss://host:8675');
+                client = new RFB(document.createElement('div'), 'wss://host:8675');
                 this.clock.tick();
                 client._sock._websocket._open();
                 expect(client._rfb_init_state).to.equal('ProtocolVersion');
index fba59b1b27d45921aacb538e8ba40668f04b880d..9c945efbb27cf145df33afacafaba7d02d06e9d5 100644 (file)
--- a/vnc.html
+++ b/vnc.html
         <div class="noVNC_spinner"></div>
     </div>
 
+    <!-- This is where the RFB elements will attach -->
     <div id="noVNC_container">
-        <!-- HTML5 Canvas -->
-        <div id="noVNC_screen">
-            <!-- Note that Google Chrome on Android doesn't respect any of these,
-                 html attributes which attempt to disable text suggestions on the
-                 on-screen keyboard. Let's hope Chrome implements the ime-mode
-                 style for example -->
-            <textarea id="noVNC_keyboardinput" autocapitalize="off"
-                autocorrect="off" autocomplete="off" spellcheck="false"
-                mozactionhint="Enter" tabindex="-1"></textarea>
-
-            <canvas id="noVNC_canvas" width="0" height="0">
-                        Canvas not supported.
-            </canvas>
-        </div>
-
+        <!-- Note that Google Chrome on Android doesn't respect any of these,
+             html attributes which attempt to disable text suggestions on the
+             on-screen keyboard. Let's hope Chrome implements the ime-mode
+             style for example -->
+        <textarea id="noVNC_keyboardinput" autocapitalize="off"
+            autocorrect="off" autocomplete="off" spellcheck="false"
+            mozactionhint="Enter" tabindex="-1"></textarea>
     </div>
 
     <audio id="noVNC_bell">
index d5a58744996fe6b87262ecf9ca52be7a906421f0..bac4ae06fb80fa3494f0bcde5e249c54e7437eac 100644 (file)
         import RFB from './core/rfb.js';
 
         var rfb;
-        var doneInitialResize;
-        var resizeTimeout;
         var desktopName;
 
-        function UIresize() {
-            if (WebUtil.getConfigVar('resize', false)) {
-                var innerW = window.innerWidth;
-                var innerH = window.innerHeight;
-                var controlbarH = document.getElementById('noVNC_status_bar').offsetHeight;
-                if (innerW !== undefined && innerH !== undefined)
-                    rfb.requestDesktopSize(innerW, innerH - controlbarH);
-            }
-        }
-        function initialResize() {
-            if (doneInitialResize) return;
-            UIresize();
-            doneInitialResize = true;
-        }
         function updateDesktopName(e) {
             desktopName = e.detail.name;
         }
 
         function connected(e) {
             document.getElementById('sendCtrlAltDelButton').disabled = false;
-            doneInitialResize = false;
             if (WebUtil.getConfigVar('encrypt',
                                      (window.location.protocol === "https:"))) {
                 status("Connected (encrypted) to " + desktopName, "normal");
             }
         }
 
-        window.onresize = function () {
-            // When the window has been resized, wait until the size remains
-            // the same for 0.5 seconds before sending the request for changing
-            // the resolution of the session
-            clearTimeout(resizeTimeout);
-            resizeTimeout = setTimeout(function(){
-                UIresize();
-            }, 500);
-        };
-
         function updatePowerButtons() {
             var powerbuttons;
             powerbuttons = document.getElementById('noVNC_power_buttons');
             }
             url += '/' + path;
 
-            rfb = new RFB(document.getElementById('noVNC_canvas'), url,
+            rfb = new RFB(document.body, url,
                           { repeaterID: WebUtil.getConfigVar('repeaterID', ''),
                             shared: WebUtil.getConfigVar('shared', true),
                             credentials: { password: password } });
             rfb.viewOnly = WebUtil.getConfigVar('view_only', false);
             rfb.addEventListener("connect",  connected);
             rfb.addEventListener("disconnect", disconnected);
-            rfb.addEventListener("capabilities", function () { updatePowerButtons(); initialResize(); });
+            rfb.addEventListener("capabilities", function () { updatePowerButtons(); });
             rfb.addEventListener("credentialsrequired", credentials);
             rfb.addEventListener("desktopname", updateDesktopName);
+            rfb.resizeSession = WebUtil.getConfigVar('resize', false);
         })();
     </script>
 </head>
       </span>
     </div>
   </div>
-  <canvas id="noVNC_canvas" width="0" height="0">
-    Canvas not supported.
-  </canvas>
-
 </body>
 </html>