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 |