Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * @fileoverview | |
| 7 * Provides view port management utilities below for a desktop remoting session. | |
| 8 * - Enabling bump scrolling | |
| 9 * - Resizing the viewport to fit the host desktop | |
| 10 * - Resizing the host desktop to fit the client viewport. | |
| 11 */ | |
| 12 | |
| 13 /** @suppress {duplicate} */ | |
| 14 var remoting = remoting || {}; | |
| 15 | |
| 16 (function() { | |
| 17 | |
| 18 'use strict'; | |
| 19 | |
| 20 /** | |
| 21 * @param {HTMLElement} rootElement (The scroller element) | |
|
garykac
2015/02/12 18:18:22
scroller element?
kelvinp
2015/02/12 23:26:41
Done.
| |
| 22 * @param {remoting.ClientPlugin.HostDesktop} hostDesktop | |
| 23 * @param {string} hostId ID of the host. | |
| 24 * | |
| 25 * TODO(kelvinp): Remove hostId from the constructor params once we can | |
| 26 * better encapsulate the host setting interface. | |
| 27 * @constructor | |
| 28 * @implements {base.Disposable} | |
| 29 */ | |
| 30 remoting.DesktopViewport = function(rootElement, hostDesktop, hostId) { | |
| 31 /** @private */ | |
| 32 this.rootElement_ = rootElement; | |
| 33 /** @private */ | |
| 34 // TODO(kelvinp): Query the container by class name instead of id. | |
| 35 this.pluginContainer_ = rootElement.querySelector('#client-container'); | |
| 36 /** @private */ | |
| 37 this.pluginElement_ = rootElement.querySelector('embed'); | |
| 38 /** @private */ | |
| 39 this.hostDesktop_ = hostDesktop; | |
| 40 /** @private */ | |
| 41 this.shrinkToFit_ = true; | |
| 42 /** @private */ | |
| 43 this.resizeToClient_ = true; | |
| 44 /** @private */ | |
| 45 this.desktopScale_ = 1.0; | |
| 46 /** @private */ | |
| 47 this.hostId_ = hostId; | |
| 48 /** @private {number?} */ | |
| 49 this.resizeTimer_ = null; | |
| 50 /** @private {remoting.BumpScroller} */ | |
| 51 this.bumpScroller_ = null; | |
| 52 // Bump-scroll test variables. Override to use a fake value for the width | |
| 53 // and height of the client plugin so that bump-scrolling can be tested | |
| 54 // without relying on the actual size of the host desktop. | |
| 55 /** @private {number} */ | |
| 56 this.pluginWidthForBumpScrollTesting = 0; | |
| 57 /** @private {number} */ | |
| 58 this.pluginHeightForBumpScrollTesting = 0; | |
| 59 | |
| 60 this.eventHook_ = new base.EventHook( | |
| 61 this.hostDesktop_, remoting.ClientPlugin.HostDesktop.Events.sizeChanged, | |
| 62 this.onDesktopSizeChanged_.bind(this)); | |
| 63 }; | |
| 64 | |
| 65 remoting.DesktopViewport.prototype.dispose = function() { | |
| 66 base.dispose(this.eventHook_); | |
| 67 this.eventHook_ = null; | |
| 68 }; | |
| 69 | |
| 70 /** | |
| 71 * @return {boolean} True if shrink-to-fit is enabled; false otherwise. | |
| 72 */ | |
| 73 remoting.DesktopViewport.prototype.getShrinkToFit = function() { | |
| 74 return this.shrinkToFit_; | |
| 75 }; | |
| 76 | |
| 77 /** | |
| 78 * @return {boolean} True if resize-to-client is enabled; false otherwise. | |
| 79 */ | |
| 80 remoting.DesktopViewport.prototype.getResizeToClient = function() { | |
| 81 return this.resizeToClient_; | |
| 82 }; | |
| 83 | |
| 84 /** | |
| 85 * Set the shrink-to-fit and resize-to-client flags and save them if this is | |
| 86 * a Me2Me connection. | |
| 87 * | |
| 88 * @param {boolean} shrinkToFit True if the remote desktop should be scaled | |
| 89 * down if it is larger than the client window; false if scroll-bars | |
| 90 * should be added in this case. | |
| 91 * @param {boolean} resizeToClient True if window resizes should cause the | |
| 92 * host to attempt to resize its desktop to match the client window size; | |
| 93 * false to disable this behaviour for subsequent window resizes--the | |
| 94 * current host desktop size is not restored in this case. | |
| 95 * @return {void} Nothing. | |
| 96 */ | |
| 97 remoting.DesktopViewport.prototype.setScreenMode = | |
| 98 function(shrinkToFit, resizeToClient) { | |
| 99 if (resizeToClient && !this.resizeToClient_) { | |
| 100 this.resizeHostDesktop_(); | |
| 101 } | |
| 102 | |
| 103 // If enabling shrink, reset bump-scroll offsets. | |
| 104 var needsScrollReset = shrinkToFit && !this.shrinkToFit_; | |
| 105 | |
| 106 this.shrinkToFit_ = shrinkToFit; | |
| 107 this.resizeToClient_ = resizeToClient; | |
| 108 this.updateScrollbarVisibility_(); | |
| 109 | |
| 110 if (this.hostId_ !== '') { | |
| 111 var options = {}; | |
| 112 options[remoting.ClientSession.KEY_SHRINK_TO_FIT] = this.shrinkToFit_; | |
| 113 options[remoting.ClientSession.KEY_RESIZE_TO_CLIENT] = this.resizeToClient_; | |
| 114 remoting.HostSettings.save(this.hostId_, options); | |
| 115 } | |
| 116 | |
| 117 this.updateDimensions_(); | |
| 118 if (needsScrollReset) { | |
| 119 this.resetScroll_(); | |
| 120 } | |
| 121 }; | |
| 122 | |
| 123 /** | |
| 124 * Scroll the client plugin by the specified amount, keeping it visible. | |
| 125 * Note that this is only used in content full-screen mode (not windowed or | |
| 126 * browser full-screen modes), where window.scrollBy and the scrollTop and | |
| 127 * scrollLeft properties don't work. | |
| 128 * @param {number} dx The amount by which to scroll horizontally. Positive to | |
| 129 * scroll right; negative to scroll left. | |
| 130 * @param {number} dy The amount by which to scroll vertically. Positive to | |
| 131 * scroll down; negative to scroll up. | |
| 132 * @return {boolean} True if the requested scroll had no effect because both | |
| 133 * vertical and horizontal edges of the screen have been reached. | |
| 134 */ | |
| 135 remoting.DesktopViewport.prototype.scroll = function(dx, dy) { | |
| 136 /** | |
| 137 * Helper function for x- and y-scrolling | |
| 138 * @param {number|string} curr The current margin, eg. "10px". | |
| 139 * @param {number} delta The requested scroll amount. | |
| 140 * @param {number} windowBound The size of the window, in pixels. | |
| 141 * @param {number} pluginBound The size of the plugin, in pixels. | |
| 142 * @param {{stop: boolean}} stop Reference parameter used to indicate when | |
| 143 * the scroll has reached one of the edges and can be stopped in that | |
| 144 * direction. | |
| 145 * @return {string} The new margin value. | |
| 146 */ | |
| 147 var adjustMargin = function(curr, delta, windowBound, pluginBound, stop) { | |
| 148 var minMargin = Math.min(0, windowBound - pluginBound); | |
| 149 var result = (curr ? parseFloat(curr) : 0) - delta; | |
| 150 result = Math.min(0, Math.max(minMargin, result)); | |
| 151 stop.stop = (result === 0 || result == minMargin); | |
| 152 return result + 'px'; | |
| 153 }; | |
| 154 | |
| 155 var style = this.pluginContainer_.style; | |
| 156 | |
| 157 var stopX = { stop: false }; | |
| 158 var clientArea = this.getClientArea(); | |
| 159 style.marginLeft = adjustMargin( | |
| 160 style.marginLeft, dx, clientArea.width, | |
| 161 this.pluginWidthForBumpScrollTesting || this.pluginElement_.clientWidth, | |
| 162 stopX); | |
| 163 | |
| 164 var stopY = { stop: false }; | |
| 165 style.marginTop = adjustMargin( | |
| 166 style.marginTop, dy, clientArea.height, | |
| 167 this.pluginHeightForBumpScrollTesting || this.pluginElement_.clientHeight, | |
| 168 stopY); | |
| 169 return stopX.stop && stopY.stop; | |
| 170 }; | |
| 171 | |
| 172 remoting.DesktopViewport.prototype.resetScroll_ = function() { | |
| 173 this.pluginContainer_.style.marginTop = '0px'; | |
| 174 this.pluginContainer_.style.marginLeft = '0px'; | |
| 175 }; | |
| 176 | |
| 177 /** | |
| 178 * Enable or disable bump-scrolling. When disabling bump scrolling, also reset | |
| 179 * the scroll offsets to (0, 0). | |
| 180 * @param {boolean=} enable True to enable bump-scrolling, false to disable it. | |
| 181 * @private | |
| 182 */ | |
| 183 remoting.DesktopViewport.prototype.enableBumpScroll = function(enable) { | |
| 184 if (enable) { | |
| 185 this.bumpScroller_ = new remoting.BumpScroller(this); | |
| 186 } else { | |
| 187 base.dispose(this.bumpScroller_); | |
| 188 this.bumpScroller_ = null; | |
| 189 this.resetScroll_(); | |
| 190 } | |
| 191 }; | |
| 192 | |
| 193 /** | |
| 194 * This is a callback that gets called when the window is resized. | |
| 195 * | |
| 196 * @return {void} Nothing. | |
| 197 */ | |
| 198 remoting.DesktopViewport.prototype.onResize = function() { | |
| 199 this.updateDimensions_(); | |
| 200 | |
| 201 if (this.resizeTimer_) { | |
| 202 window.clearTimeout(this.resizeTimer_); | |
| 203 this.resizeTimer_ = null; | |
| 204 } | |
| 205 | |
| 206 // Defer notifying the host of the change until the window stops resizing, to | |
| 207 // avoid overloading the control channel with notifications. | |
| 208 if (this.resizeToClient_) { | |
| 209 var kResizeRateLimitMs = 1000; | |
| 210 if (this.hostDesktop_.hasResizeRateLimit) { | |
| 211 kResizeRateLimitMs = 250; | |
| 212 } | |
| 213 var clientArea = this.getClientArea(); | |
| 214 this.resizeTimer_ = window.setTimeout(this.resizeHostDesktop_.bind(this), | |
| 215 kResizeRateLimitMs); | |
| 216 } | |
| 217 | |
| 218 // If bump-scrolling is enabled, adjust the plugin margins to fully utilize | |
| 219 // the new window area. | |
| 220 this.resetScroll_(); | |
| 221 this.updateScrollbarVisibility_(); | |
| 222 }; | |
| 223 | |
| 224 /** | |
| 225 * This is a callback that gets called when the plugin notifies us of a change | |
| 226 * in the size of the remote desktop. | |
| 227 * | |
| 228 * @return {void} Nothing. | |
| 229 * @private | |
| 230 */ | |
| 231 remoting.DesktopViewport.prototype.onDesktopSizeChanged_ = function() { | |
| 232 var dimensions = this.hostDesktop_.getDimensions(); | |
| 233 console.log('desktop size changed: ' + | |
| 234 dimensions.width + 'x' + | |
| 235 dimensions.height +' @ ' + | |
| 236 dimensions.xDpi + 'x' + | |
| 237 dimensions.yDpi + ' DPI'); | |
| 238 this.updateDimensions_(); | |
| 239 this.updateScrollbarVisibility_(); | |
| 240 }; | |
| 241 | |
| 242 /** | |
| 243 * Called when the window or desktop size or the scaling settings change, | |
| 244 * to set the scroll-bar visibility. | |
| 245 * | |
| 246 * TODO(jamiewalch): crbug.com/252796: Remove this once crbug.com/240772 is | |
| 247 * fixed. | |
| 248 */ | |
| 249 remoting.DesktopViewport.prototype.updateScrollbarVisibility_ = function() { | |
| 250 // TODO(kelvinp): Remove the check once looking glass no longer depends on | |
| 251 // this. | |
| 252 if (!this.rootElement_) { | |
| 253 return; | |
| 254 } | |
| 255 | |
| 256 var needsScrollY = false; | |
| 257 var needsScrollX = false; | |
| 258 if (!this.shrinkToFit_) { | |
| 259 // Determine whether or not horizontal or vertical scrollbars are | |
| 260 // required, taking into account their width. | |
| 261 var clientArea = this.getClientArea(); | |
| 262 var hostDesktop = this.hostDesktop_.getDimensions(); | |
| 263 needsScrollY = clientArea.height < hostDesktop.height; | |
| 264 needsScrollX = clientArea.width < hostDesktop.width; | |
| 265 var kScrollBarWidth = 16; | |
| 266 if (needsScrollX && !needsScrollY) { | |
| 267 needsScrollY = clientArea.height - kScrollBarWidth < hostDesktop.height; | |
| 268 } else if (!needsScrollX && needsScrollY) { | |
| 269 needsScrollX = clientArea.width - kScrollBarWidth < hostDesktop.width; | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 this.rootElement_.classList.toggle('no-horizontal-scroll', !needsScrollX); | |
| 274 this.rootElement_.classList.toggle('no-vertical-scroll', !needsScrollY); | |
| 275 }; | |
| 276 | |
| 277 /** | |
| 278 * Refreshes the plugin's dimensions, taking into account the sizes of the | |
| 279 * remote desktop and client window, and the current scale-to-fit setting. | |
| 280 * | |
| 281 * @return {void} Nothing. | |
| 282 */ | |
| 283 remoting.DesktopViewport.prototype.updateDimensions_ = function() { | |
| 284 var hostDesktop = this.hostDesktop_.getDimensions(); | |
| 285 if (hostDesktop.width === 0 || hostDesktop.height === 0) { | |
| 286 return; | |
| 287 } | |
| 288 | |
| 289 var clientArea = this.getClientArea(); | |
| 290 | |
| 291 // When configured to display a host at its original size, we aim to display | |
| 292 // it as close to its physical size as possible, without losing data: | |
| 293 // - If client and host have matching DPI, render the host pixel-for-pixel. | |
| 294 // - If the host has higher DPI then still render pixel-for-pixel. | |
| 295 // - If the host has lower DPI then let Chrome up-scale it to natural size. | |
| 296 | |
| 297 // We specify the plugin dimensions in Density-Independent Pixels, so to | |
| 298 // render pixel-for-pixel we need to down-scale the host dimensions by the | |
| 299 // devicePixelRatio of the client. To match the host pixel density, we choose | |
| 300 // an initial scale factor based on the client devicePixelRatio and host DPI. | |
| 301 | |
| 302 // Determine the effective device pixel ratio of the host, based on DPI. | |
| 303 var hostPixelRatioX = Math.ceil(hostDesktop.xDpi / 96); | |
| 304 var hostPixelRatioY = Math.ceil(hostDesktop.yDpi / 96); | |
| 305 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY); | |
| 306 | |
| 307 // Include the desktopScale in the hostPixelRatio before comparing it with | |
| 308 // the client devicePixelRatio to determine the "natural" scale to use. | |
| 309 hostPixelRatio *= this.desktopScale_; | |
| 310 | |
| 311 // Down-scale by the smaller of the client and host ratios. | |
| 312 var scale = 1.0 / Math.min(window.devicePixelRatio, hostPixelRatio); | |
| 313 | |
| 314 if (this.shrinkToFit_) { | |
| 315 // Reduce the scale, if necessary, to fit the whole desktop in the window. | |
| 316 var scaleFitWidth = | |
| 317 Math.min(scale, 1.0 * clientArea.width / hostDesktop.width); | |
| 318 var scaleFitHeight = | |
| 319 Math.min(scale, 1.0 * clientArea.height / hostDesktop.height); | |
| 320 scale = Math.min(scaleFitHeight, scaleFitWidth); | |
| 321 | |
| 322 // If we're running full-screen then try to handle common side-by-side | |
| 323 // multi-monitor combinations more intelligently. | |
| 324 if (remoting.fullscreen.isActive()) { | |
| 325 // If the host has two monitors each the same size as the client then | |
| 326 // scale-to-fit will have the desktop occupy only 50% of the client area, | |
| 327 // in which case it would be preferable to down-scale less and let the | |
| 328 // user bump-scroll around ("scale-and-pan"). | |
| 329 // Triggering scale-and-pan if less than 65% of the client area would be | |
| 330 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to | |
| 331 // a (2x1280)x1024 host nicely. | |
| 332 // Note that we don't need to account for scrollbars while fullscreen. | |
| 333 if (scale <= scaleFitHeight * 0.65) { | |
| 334 scale = scaleFitHeight; | |
| 335 } | |
| 336 if (scale <= scaleFitWidth * 0.65) { | |
| 337 scale = scaleFitWidth; | |
| 338 } | |
| 339 } | |
| 340 } | |
| 341 | |
| 342 var pluginWidth = Math.round(hostDesktop.width * scale); | |
| 343 var pluginHeight = Math.round(hostDesktop.height * scale); | |
| 344 | |
| 345 // Resize the plugin if necessary. | |
| 346 // TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089). | |
| 347 this.pluginElement_.style.width = pluginWidth + 'px'; | |
| 348 this.pluginElement_.style.height = pluginHeight + 'px'; | |
| 349 | |
| 350 // Position the container. | |
| 351 var parentNode = this.pluginElement_.parentNode; | |
| 352 | |
| 353 console.log('plugin dimensions: ' + | |
| 354 parentNode.style.left + ',' + | |
| 355 parentNode.style.top + '-' + | |
| 356 pluginWidth + 'x' + pluginHeight + '.'); | |
| 357 }; | |
| 358 | |
| 359 /** | |
| 360 * @return {{width:number, height:number}} The height of the window's client | |
| 361 * area. This differs between apps v1 and apps v2 due to the custom window | |
| 362 * borders used by the latter. | |
| 363 */ | |
| 364 remoting.DesktopViewport.prototype.getClientArea = function() { | |
| 365 return remoting.windowFrame ? | |
| 366 remoting.windowFrame.getClientArea() : | |
| 367 { 'width': window.innerWidth, 'height': window.innerHeight }; | |
| 368 }; | |
| 369 | |
| 370 /** | |
| 371 * Notifies the host of the client's current dimensions and DPI. | |
| 372 * Also takes into account per-host scaling factor, if configured. | |
| 373 * @private | |
| 374 */ | |
| 375 remoting.DesktopViewport.prototype.resizeHostDesktop_ = function() { | |
| 376 var clientArea = this.getClientArea(); | |
| 377 this.hostDesktop_.resize(clientArea.width * this.desktopScale_, | |
| 378 clientArea.height * this.desktopScale_, | |
| 379 window.devicePixelRatio); | |
| 380 }; | |
| 381 | |
| 382 }()); | |
| OLD | NEW |