Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / webapp / crd / js / desktop_viewport.js
blob0eae6052d84859d95aff46d55e5fe9f61503dce8
1 // Copyright 2015 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.
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  */
13 /** @suppress {duplicate} */
14 var remoting = remoting || {};
16 (function() {
18 'use strict';
20 /**
21  * @param {HTMLElement} rootElement The outer element with id=scroller that we
22  *   are showing scrollbars on.
23  * @param {remoting.HostDesktop} hostDesktop
24  * @param {remoting.Host.Options} hostOptions
25  *
26  * @constructor
27  * @implements {base.Disposable}
28  */
29 remoting.DesktopViewport = function(rootElement, hostDesktop, hostOptions) {
30   /** @private */
31   this.rootElement_ = rootElement;
32   /** @private */
33   // TODO(kelvinp): Query the container by class name instead of id.
34   this.pluginContainer_ = rootElement.querySelector('#client-container');
35   /** @private */
36   this.pluginElement_ = rootElement.querySelector('embed');
37   /** @private */
38   this.hostDesktop_ = hostDesktop;
39   /** @private */
40   this.hostOptions_ = hostOptions;
41   /** @private {number?} */
42   this.resizeTimer_ = null;
43   /** @private {remoting.BumpScroller} */
44   this.bumpScroller_ = null;
45   // Bump-scroll test variables. Override to use a fake value for the width
46   // and height of the client plugin so that bump-scrolling can be tested
47   // without relying on the actual size of the host desktop.
48   /** @private {number} */
49   this.pluginWidthForBumpScrollTesting_ = 0;
50   /** @private {number} */
51   this.pluginHeightForBumpScrollTesting_ = 0;
53   this.eventHooks_ = new base.Disposables(
54       new base.EventHook(
55         this.hostDesktop_, remoting.HostDesktop.Events.sizeChanged,
56         this.onDesktopSizeChanged_.bind(this)));
58   if (this.hostOptions_.resizeToClient) {
59     this.resizeHostDesktop_();
60   } else {
61     this.onDesktopSizeChanged_();
62   }
65 remoting.DesktopViewport.prototype.dispose = function() {
66   base.dispose(this.eventHooks_);
67   this.eventHooks_ = null;
68   base.dispose(this.bumpScroller_);
69   this.bumpScroller_ = null;
72 /**
73  * @return {boolean} True if shrink-to-fit is enabled; false otherwise.
74  */
75 remoting.DesktopViewport.prototype.getShrinkToFit = function() {
76   return this.hostOptions_.shrinkToFit;
79 /**
80  * @return {boolean} True if resize-to-client is enabled; false otherwise.
81  */
82 remoting.DesktopViewport.prototype.getResizeToClient = function() {
83   return this.hostOptions_.resizeToClient;
86 /**
87  * @return {{top:number, left:number}} The top-left corner of the plugin.
88  */
89 remoting.DesktopViewport.prototype.getPluginPositionForTesting = function() {
90   /**
91    * @param {number|string} value
92    * @return {number}
93    */
94   function toFloat(value) {
95     var number = parseFloat(value);
96     return isNaN(number) ? 0 : number;
97   }
98   return {
99     top: toFloat(this.pluginContainer_.style.marginTop),
100     left: toFloat(this.pluginContainer_.style.marginLeft)
101   };
105  * @param {number} width
106  * @param {number} height
107  */
108 remoting.DesktopViewport.prototype.setPluginSizeForBumpScrollTesting =
109     function(width, height) {
110   this.pluginWidthForBumpScrollTesting_ = width;
111   this.pluginHeightForBumpScrollTesting_ = height;
115  * @return {remoting.BumpScroller}
116  */
117 remoting.DesktopViewport.prototype.getBumpScrollerForTesting = function() {
118   return this.bumpScroller_;
122  * Set the shrink-to-fit and resize-to-client flags and save them if this is
123  * a Me2Me connection.
125  * @param {boolean} shrinkToFit True if the remote desktop should be scaled
126  *     down if it is larger than the client window; false if scroll-bars
127  *     should be added in this case.
128  * @param {boolean} resizeToClient True if window resizes should cause the
129  *     host to attempt to resize its desktop to match the client window size;
130  *     false to disable this behaviour for subsequent window resizes--the
131  *     current host desktop size is not restored in this case.
132  * @return {void} Nothing.
133  */
134 remoting.DesktopViewport.prototype.setScreenMode =
135     function(shrinkToFit, resizeToClient) {
136   if (resizeToClient && !this.hostOptions_.resizeToClient) {
137     this.resizeHostDesktop_();
138   }
140   // If enabling shrink, reset bump-scroll offsets.
141   var needsScrollReset = shrinkToFit && !this.hostOptions_.shrinkToFit;
142   this.hostOptions_.shrinkToFit = shrinkToFit;
143   this.hostOptions_.resizeToClient = resizeToClient;
144   this.hostOptions_.save();
145   this.updateScrollbarVisibility_();
147   this.updateDimensions_();
148   if (needsScrollReset) {
149     this.resetScroll_();
150   }
154  * Scroll the client plugin by the specified amount, keeping it visible.
155  * Note that this is only used in content full-screen mode (not windowed or
156  * browser full-screen modes), where window.scrollBy and the scrollTop and
157  * scrollLeft properties don't work.
159  * @param {number} dx The amount by which to scroll horizontally. Positive to
160  *     scroll right; negative to scroll left.
161  * @param {number} dy The amount by which to scroll vertically. Positive to
162  *     scroll down; negative to scroll up.
163  * @return {boolean} False if the requested scroll had no effect because both
164  *     vertical and horizontal edges of the screen have been reached.
165  */
166 remoting.DesktopViewport.prototype.scroll = function(dx, dy) {
167   /**
168    * Helper function for x- and y-scrolling
169    * @param {number|string} curr The current margin, eg. "10px".
170    * @param {number} delta The requested scroll amount.
171    * @param {number} windowBound The size of the window, in pixels.
172    * @param {number} pluginBound The size of the plugin, in pixels.
173    * @param {{stop: boolean}} stop Reference parameter used to indicate when
174    *     the scroll has reached one of the edges and can be stopped in that
175    *     direction.
176    * @return {string} The new margin value.
177    */
178   var adjustMargin = function(curr, delta, windowBound, pluginBound, stop) {
179     var minMargin = Math.min(0, windowBound - pluginBound);
180     var result = (curr ? parseFloat(curr) : 0) - delta;
181     result = Math.min(0, Math.max(minMargin, result));
182     stop.stop = (result === 0 || result == minMargin);
183     return result + 'px';
184   };
186   var style = this.pluginContainer_.style;
188   var pluginWidth =
189       this.pluginWidthForBumpScrollTesting_ || this.pluginElement_.clientWidth;
190   var pluginHeight = this.pluginHeightForBumpScrollTesting_ ||
191                      this.pluginElement_.clientHeight;
193   var clientArea = this.getClientArea();
194   var stopX = { stop: false };
195   style.marginLeft =
196       adjustMargin(style.marginLeft, dx, clientArea.width, pluginWidth, stopX);
198   var stopY = { stop: false };
199   style.marginTop =
200       adjustMargin(style.marginTop, dy, clientArea.height, pluginHeight, stopY);
201   return !stopX.stop || !stopY.stop;
205  * Enable or disable bump-scrolling. When disabling bump scrolling, also reset
206  * the scroll offsets to (0, 0).
207  * @param {boolean} enable True to enable bump-scrolling, false to disable it.
208  */
209 remoting.DesktopViewport.prototype.enableBumpScroll = function(enable) {
210   if (enable) {
211     this.bumpScroller_ = new remoting.BumpScroller(this);
212   } else {
213     base.dispose(this.bumpScroller_);
214     this.bumpScroller_ = null;
215     this.resetScroll_();
216   }
220  * This is a callback that gets called when the window is resized.
222  * @return {void} Nothing.
223  */
224 remoting.DesktopViewport.prototype.onResize = function() {
225   this.updateDimensions_();
227   if (this.resizeTimer_) {
228     window.clearTimeout(this.resizeTimer_);
229     this.resizeTimer_ = null;
230   }
232   // Defer notifying the host of the change until the window stops resizing, to
233   // avoid overloading the control channel with notifications.
234   if (this.hostOptions_.resizeToClient) {
235     var kResizeRateLimitMs = 250;
236     var clientArea = this.getClientArea();
237     this.resizeTimer_ = window.setTimeout(this.resizeHostDesktop_.bind(this),
238                                           kResizeRateLimitMs);
239   }
241   // If bump-scrolling is enabled, adjust the plugin margins to fully utilize
242   // the new window area.
243   this.resetScroll_();
244   this.updateScrollbarVisibility_();
248  * @return {{width:number, height:number}} The height of the window's client
249  *     area. This differs between apps v1 and apps v2 due to the custom window
250  *     borders used by the latter.
251  */
252 remoting.DesktopViewport.prototype.getClientArea = function() {
253   return remoting.windowFrame ?
254       remoting.windowFrame.getClientArea() :
255       { 'width': window.innerWidth, 'height': window.innerHeight };
259  * Notifies the host of the client's current dimensions and DPI.
260  * Also takes into account per-host scaling factor, if configured.
261  * @private
262  */
263 remoting.DesktopViewport.prototype.resizeHostDesktop_ = function() {
264   var clientArea = this.getClientArea();
265   this.hostDesktop_.resize(clientArea.width * this.hostOptions_.desktopScale,
266                            clientArea.height * this.hostOptions_.desktopScale,
267                            window.devicePixelRatio);
271  * This is a callback that gets called when the plugin notifies us of a change
272  * in the size of the remote desktop.
274  * @return {void} Nothing.
275  * @private
276  */
277 remoting.DesktopViewport.prototype.onDesktopSizeChanged_ = function() {
278   var dimensions = this.hostDesktop_.getDimensions();
279   console.log('desktop size changed: ' +
280               dimensions.width + 'x' +
281               dimensions.height +' @ ' +
282               dimensions.xDpi + 'x' +
283               dimensions.yDpi + ' DPI');
284   this.updateDimensions_();
285   this.updateScrollbarVisibility_();
289  * Called when the window or desktop size or the scaling settings change,
290  * to set the scroll-bar visibility.
292  * TODO(jamiewalch): crbug.com/252796: Remove this once crbug.com/240772 is
293  * fixed.
294  */
295 remoting.DesktopViewport.prototype.updateScrollbarVisibility_ = function() {
296   // TODO(kelvinp): Remove the check once app-remoting no longer depends on
297   // this.
298   if (!this.rootElement_) {
299     return;
300   }
302   var needsScrollY = false;
303   var needsScrollX = false;
304   if (!this.hostOptions_.shrinkToFit) {
305     // Determine whether or not horizontal or vertical scrollbars are
306     // required, taking into account their width.
307     var clientArea = this.getClientArea();
308     var hostDesktop = this.hostDesktop_.getDimensions();
309     needsScrollY = clientArea.height < hostDesktop.height;
310     needsScrollX = clientArea.width < hostDesktop.width;
311     var kScrollBarWidth = 16;
312     if (needsScrollX && !needsScrollY) {
313       needsScrollY = clientArea.height - kScrollBarWidth < hostDesktop.height;
314     } else if (!needsScrollX && needsScrollY) {
315       needsScrollX = clientArea.width - kScrollBarWidth < hostDesktop.width;
316     }
317   }
319   this.rootElement_.classList.toggle('no-horizontal-scroll', !needsScrollX);
320   this.rootElement_.classList.toggle('no-vertical-scroll', !needsScrollY);
323 remoting.DesktopViewport.prototype.updateDimensions_ = function() {
324   var dimensions = this.hostDesktop_.getDimensions();
325   if (dimensions.width === 0 || dimensions.height === 0) {
326     return;
327   }
329   var desktopSize = { width: dimensions.width,
330                       height: dimensions.height };
331   var desktopDpi = { x: dimensions.xDpi,
332                      y: dimensions.yDpi };
333   var newSize = remoting.Viewport.choosePluginSize(
334       this.getClientArea(), window.devicePixelRatio,
335       desktopSize, desktopDpi, this.hostOptions_.desktopScale,
336       remoting.fullscreen.isActive(), this.hostOptions_.shrinkToFit);
338   // Resize the plugin if necessary.
339   console.log('plugin dimensions:' + newSize.width + 'x' + newSize.height);
340   this.pluginElement_.style.width = newSize.width + 'px';
341   this.pluginElement_.style.height = newSize.height + 'px';
344 /** @private */
345 remoting.DesktopViewport.prototype.resetScroll_ = function() {
346   this.pluginContainer_.style.marginTop = '0px';
347   this.pluginContainer_.style.marginLeft = '0px';
351  * Sets and stores the scale factor to apply to host sizing requests.
352  * The desktopScale applies to the dimensions reported to the host, not
353  * to the client DPI reported to it.
355  * @param {number} desktopScale Scale factor to apply.
356  */
357 remoting.DesktopViewport.prototype.setDesktopScale = function(desktopScale) {
358   this.hostOptions_.desktopScale = desktopScale;
360   // onResize() will update the plugin size and scrollbars for the new
361   // scaled plugin dimensions, and send a client resolution notification.
362   this.onResize();
364   // Save the new desktop scale setting.
365   this.hostOptions_.save();
368 }());