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.
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.
13 /** @suppress {duplicate} */
14 var remoting
= remoting
|| {};
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
27 * @implements {base.Disposable}
29 remoting
.DesktopViewport = function(rootElement
, hostDesktop
, hostOptions
) {
31 this.rootElement_
= rootElement
;
33 // TODO(kelvinp): Query the container by class name instead of id.
34 this.pluginContainer_
= rootElement
.querySelector('#client-container');
36 this.pluginElement_
= rootElement
.querySelector('embed');
38 this.hostDesktop_
= hostDesktop
;
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(
55 this.hostDesktop_
, remoting
.HostDesktop
.Events
.sizeChanged
,
56 this.onDesktopSizeChanged_
.bind(this)));
58 if (this.hostOptions_
.resizeToClient
) {
59 this.resizeHostDesktop_();
61 this.onDesktopSizeChanged_();
65 remoting
.DesktopViewport
.prototype.dispose = function() {
66 base
.dispose(this.eventHooks_
);
67 this.eventHooks_
= null;
68 base
.dispose(this.bumpScroller_
);
69 this.bumpScroller_
= null;
73 * @return {boolean} True if shrink-to-fit is enabled; false otherwise.
75 remoting
.DesktopViewport
.prototype.getShrinkToFit = function() {
76 return this.hostOptions_
.shrinkToFit
;
80 * @return {boolean} True if resize-to-client is enabled; false otherwise.
82 remoting
.DesktopViewport
.prototype.getResizeToClient = function() {
83 return this.hostOptions_
.resizeToClient
;
87 * @return {{top:number, left:number}} The top-left corner of the plugin.
89 remoting
.DesktopViewport
.prototype.getPluginPositionForTesting = function() {
91 * @param {number|string} value
94 function toFloat(value
) {
95 var number
= parseFloat(value
);
96 return isNaN(number
) ? 0 : number
;
99 top
: toFloat(this.pluginContainer_
.style
.marginTop
),
100 left
: toFloat(this.pluginContainer_
.style
.marginLeft
)
105 * @param {number} width
106 * @param {number} height
108 remoting
.DesktopViewport
.prototype.setPluginSizeForBumpScrollTesting
=
109 function(width
, height
) {
110 this.pluginWidthForBumpScrollTesting_
= width
;
111 this.pluginHeightForBumpScrollTesting_
= height
;
115 * @return {remoting.BumpScroller}
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.
134 remoting
.DesktopViewport
.prototype.setScreenMode
=
135 function(shrinkToFit
, resizeToClient
) {
136 if (resizeToClient
&& !this.hostOptions_
.resizeToClient
) {
137 this.resizeHostDesktop_();
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
) {
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.
166 remoting
.DesktopViewport
.prototype.scroll = function(dx
, dy
) {
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
176 * @return {string} The new margin value.
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';
186 var style
= this.pluginContainer_
.style
;
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 };
196 adjustMargin(style
.marginLeft
, dx
, clientArea
.width
, pluginWidth
, stopX
);
198 var stopY
= { stop
: false };
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.
209 remoting
.DesktopViewport
.prototype.enableBumpScroll = function(enable
) {
211 this.bumpScroller_
= new remoting
.BumpScroller(this);
213 base
.dispose(this.bumpScroller_
);
214 this.bumpScroller_
= null;
220 * This is a callback that gets called when the window is resized.
222 * @return {void} Nothing.
224 remoting
.DesktopViewport
.prototype.onResize = function() {
225 this.updateDimensions_();
227 if (this.resizeTimer_
) {
228 window
.clearTimeout(this.resizeTimer_
);
229 this.resizeTimer_
= null;
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),
241 // If bump-scrolling is enabled, adjust the plugin margins to fully utilize
242 // the new window area.
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.
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.
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.
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
295 remoting
.DesktopViewport
.prototype.updateScrollbarVisibility_ = function() {
296 // TODO(kelvinp): Remove the check once app-remoting no longer depends on
298 if (!this.rootElement_
) {
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
;
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) {
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';
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.
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.
364 // Save the new desktop scale setting.
365 this.hostOptions_
.save();