Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(122)

Side by Side Diff: remoting/webapp/crd/js/desktop_viewport.js

Issue 918783002: CRD Viewport Management refactor (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase + plumbing remoting.Host into clientSession Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
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 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;
52
53 this.eventHooks_ = new base.Disposables(
54 new base.EventHook(
55 this.hostDesktop_, remoting.HostDesktop.Events.sizeChanged,
56 this.onDesktopSizeChanged_.bind(this)),
57 new base.EventHook(
58 this.hostDesktop_, remoting.HostDesktop.Events.shapeChanged,
59 remoting.windowShape.setDesktopRects));
Jamie 2015/02/18 00:33:10 This is missing a bind(). Also, having a global de
kelvinp 2015/02/20 23:24:16 I acknowledged that this is not ideal, but don't w
60
61 if (this.hostOptions_.resizeToClient) {
62 this.resizeHostDesktop();
63 } else {
64 this.onDesktopSizeChanged_();
65 }
66 };
67
68 remoting.DesktopViewport.prototype.dispose = function() {
69 base.dispose(this.eventHooks_);
70 this.eventHooks_ = null;
71 base.dispose(this.bumpScroller_);
72 this.bumpScroller_ = null;
73 };
74
75 /**
76 * @return {boolean} True if shrink-to-fit is enabled; false otherwise.
77 */
78 remoting.DesktopViewport.prototype.getShrinkToFit = function() {
79 return this.hostOptions_.shrinkToFit;
80 };
81
82 /**
83 * @return {boolean} True if resize-to-client is enabled; false otherwise.
84 */
85 remoting.DesktopViewport.prototype.getResizeToClient = function() {
86 return this.hostOptions_.resizeToClient;
87 };
88
89 /**
90 * @return {{top:number, left:number}} The top-left corner of the plugin.
91 */
92 remoting.DesktopViewport.prototype.getPluginPositionForTesting = function() {
93 var style = this.pluginContainer_.style;
94 return {
95 top: parseFloat(style.marginTop),
96 left: parseFloat(style.marginLeft)
97 };
98 };
99
100 /**
101 * @return {remoting.BumpScroller}
102 */
103 remoting.DesktopViewport.prototype.getBumpScrollerForTesting = function() {
104 return this.bumpScroller_;
105 };
106
107 /**
108 * Set the shrink-to-fit and resize-to-client flags and save them if this is
109 * a Me2Me connection.
110 *
111 * @param {boolean} shrinkToFit True if the remote desktop should be scaled
112 * down if it is larger than the client window; false if scroll-bars
113 * should be added in this case.
114 * @param {boolean} resizeToClient True if window resizes should cause the
115 * host to attempt to resize its desktop to match the client window size;
116 * false to disable this behaviour for subsequent window resizes--the
117 * current host desktop size is not restored in this case.
118 * @return {void} Nothing.
119 */
120 remoting.DesktopViewport.prototype.setScreenMode =
121 function(shrinkToFit, resizeToClient) {
122 if (resizeToClient && !this.hostOptions_.resizeToClient) {
123 this.resizeHostDesktop();
124 }
125
126 // If enabling shrink, reset bump-scroll offsets.
127 var needsScrollReset = shrinkToFit && !this.hostOptions_.shrinkToFit;
128
129 this.hostOptions_.shrinkToFit = shrinkToFit;
130 this.hostOptions_.resizeToClient = resizeToClient;
131 this.hostOptions_.save();
132 this.updateScrollbarVisibility_();
133
134 this.updateDimensions_();
135 if (needsScrollReset) {
136 this.resetScroll_();
137 }
138 };
139
140 /**
141 * Scroll the client plugin by the specified amount, keeping it visible.
142 * Note that this is only used in content full-screen mode (not windowed or
143 * browser full-screen modes), where window.scrollBy and the scrollTop and
144 * scrollLeft properties don't work.
Jamie 2015/02/18 00:33:10 Off-topic for this CL, but this may no longer be t
kelvinp 2015/02/20 23:24:16 Acknowledged.
145 *
146 * @param {number} dx The amount by which to scroll horizontally. Positive to
147 * scroll right; negative to scroll left.
148 * @param {number} dy The amount by which to scroll vertically. Positive to
149 * scroll down; negative to scroll up.
150 * @return {boolean} True if the requested scroll had no effect because both
151 * vertical and horizontal edges of the screen have been reached.
152 */
153 remoting.DesktopViewport.prototype.scroll = function(dx, dy) {
154 /**
155 * Helper function for x- and y-scrolling
156 * @param {number|string} curr The current margin, eg. "10px".
157 * @param {number} delta The requested scroll amount.
158 * @param {number} windowBound The size of the window, in pixels.
159 * @param {number} pluginBound The size of the plugin, in pixels.
160 * @param {{stop: boolean}} stop Reference parameter used to indicate when
161 * the scroll has reached one of the edges and can be stopped in that
162 * direction.
163 * @return {string} The new margin value.
164 */
165 var adjustMargin = function(curr, delta, windowBound, pluginBound, stop) {
166 var minMargin = Math.min(0, windowBound - pluginBound);
167 var result = (curr ? parseFloat(curr) : 0) - delta;
168 result = Math.min(0, Math.max(minMargin, result));
169 stop.stop = (result === 0 || result == minMargin);
170 return result + 'px';
171 };
172
173 var style = this.pluginContainer_.style;
174
175 var stopX = { stop: false };
176 var clientArea = this.getClientArea();
177 style.marginLeft = adjustMargin(
178 style.marginLeft, dx, clientArea.width,
179 this.pluginWidthForBumpScrollTesting || this.pluginElement_.clientWidth,
180 stopX);
181
182 var stopY = { stop: false };
183 style.marginTop = adjustMargin(
184 style.marginTop, dy, clientArea.height,
185 this.pluginHeightForBumpScrollTesting || this.pluginElement_.clientHeight,
186 stopY);
187 return stopX.stop && stopY.stop;
188 };
189
190 /**
191 * Enable or disable bump-scrolling. When disabling bump scrolling, also reset
192 * the scroll offsets to (0, 0).
193 * @param {boolean=} enable True to enable bump-scrolling, false to disable it.
Jamie 2015/02/18 00:33:10 This parameter is not optional.
kelvinp 2015/02/20 23:24:16 Done.
194 */
195 remoting.DesktopViewport.prototype.enableBumpScroll = function(enable) {
196 if (enable) {
197 this.bumpScroller_ = new remoting.BumpScroller(this);
198 } else {
199 base.dispose(this.bumpScroller_);
200 this.bumpScroller_ = null;
201 this.resetScroll_();
202 }
203 };
204
205 /**
206 * This is a callback that gets called when the window is resized.
207 *
208 * @return {void} Nothing.
209 */
210 remoting.DesktopViewport.prototype.onResize = function() {
211 this.updateDimensions_();
212
213 if (this.resizeTimer_) {
214 window.clearTimeout(this.resizeTimer_);
215 this.resizeTimer_ = null;
216 }
217
218 // Defer notifying the host of the change until the window stops resizing, to
219 // avoid overloading the control channel with notifications.
220 if (this.hostOptions_.resizeToClient) {
221 var kResizeRateLimitMs = 1000;
222 if (this.hostDesktop_.hasResizeRateLimit) {
223 kResizeRateLimitMs = 250;
224 }
225 var clientArea = this.getClientArea();
226 this.resizeTimer_ = window.setTimeout(this.resizeHostDesktop.bind(this),
227 kResizeRateLimitMs);
228 }
229
230 // If bump-scrolling is enabled, adjust the plugin margins to fully utilize
231 // the new window area.
232 this.resetScroll_();
233 this.updateScrollbarVisibility_();
234 };
235
236 /**
237 * @return {{width:number, height:number}} The height of the window's client
238 * area. This differs between apps v1 and apps v2 due to the custom window
239 * borders used by the latter.
240 */
241 remoting.DesktopViewport.prototype.getClientArea = function() {
242 return remoting.windowFrame ?
243 remoting.windowFrame.getClientArea() :
244 { 'width': window.innerWidth, 'height': window.innerHeight };
245 };
246
247 /**
248 * Notifies the host of the client's current dimensions and DPI.
249 * Also takes into account per-host scaling factor, if configured.
250 */
251 remoting.DesktopViewport.prototype.resizeHostDesktop = function() {
Jamie 2015/02/18 00:33:10 This doesn't feel like it belongs on the DesktopVi
kelvinp 2015/02/20 23:24:16 Removed. The desktopViewport is only created when
Jamie 2015/02/21 02:01:32 It doesn't look like you've removed this.
kelvinp 2015/02/23 17:56:00 I made the function private in the latest iteratio
252 var clientArea = this.getClientArea();
253 this.hostDesktop_.resize(clientArea.width * this.hostOptions_.desktopScale,
254 clientArea.height * this.hostOptions_.desktopScale,
255 window.devicePixelRatio);
256 };
257
258 /**
259 * This is a callback that gets called when the plugin notifies us of a change
260 * in the size of the remote desktop.
261 *
262 * @return {void} Nothing.
263 * @private
264 */
265 remoting.DesktopViewport.prototype.onDesktopSizeChanged_ = function() {
266 var dimensions = this.hostDesktop_.getDimensions();
267 console.log('desktop size changed: ' +
268 dimensions.width + 'x' +
269 dimensions.height +' @ ' +
270 dimensions.xDpi + 'x' +
271 dimensions.yDpi + ' DPI');
272 this.updateDimensions_();
273 this.updateScrollbarVisibility_();
274 };
275
276 /**
277 * Called when the window or desktop size or the scaling settings change,
278 * to set the scroll-bar visibility.
279 *
280 * TODO(jamiewalch): crbug.com/252796: Remove this once crbug.com/240772 is
281 * fixed.
282 */
283 remoting.DesktopViewport.prototype.updateScrollbarVisibility_ = function() {
284 // TODO(kelvinp): Remove the check once looking glass no longer depends on
285 // this.
286 if (!this.rootElement_) {
287 return;
288 }
289
290 var needsScrollY = false;
291 var needsScrollX = false;
292 if (!this.hostOptions_.shrinkToFit) {
293 // Determine whether or not horizontal or vertical scrollbars are
294 // required, taking into account their width.
295 var clientArea = this.getClientArea();
296 var hostDesktop = this.hostDesktop_.getDimensions();
297 needsScrollY = clientArea.height < hostDesktop.height;
298 needsScrollX = clientArea.width < hostDesktop.width;
299 var kScrollBarWidth = 16;
300 if (needsScrollX && !needsScrollY) {
301 needsScrollY = clientArea.height - kScrollBarWidth < hostDesktop.height;
302 } else if (!needsScrollX && needsScrollY) {
303 needsScrollX = clientArea.width - kScrollBarWidth < hostDesktop.width;
304 }
305 }
306
307 this.rootElement_.classList.toggle('no-horizontal-scroll', !needsScrollX);
308 this.rootElement_.classList.toggle('no-vertical-scroll', !needsScrollY);
309 };
310
311 remoting.DesktopViewport.prototype.updateDimensions_ = function() {
312 var dimensions = this.hostDesktop_.getDimensions();
313 if (dimensions.width === 0 ||
314 dimensions.height === 0) {
Jamie 2015/02/18 00:33:10 No need for a new line here.
kelvinp 2015/02/20 23:24:16 Done.
315 return;
316 }
317
318 var desktopSize = { width: dimensions.width,
319 height: dimensions.height };
320 var desktopDpi = { x: dimensions.xDpi,
321 y: dimensions.yDpi };
322 var newSize = remoting.DesktopViewport.choosePluginSize(
323 this.getClientArea(), window.devicePixelRatio,
324 desktopSize, desktopDpi, this.hostOptions_.desktopScale,
325 remoting.fullscreen.isActive(), this.hostOptions_.shrinkToFit);
326
327 // Resize the plugin if necessary.
328 console.log('plugin dimensions:' + newSize.width + 'x' + newSize.height);
329 this.pluginElement_.style.width = newSize.width + 'px';
330 this.pluginElement_.style.height = newSize.height + 'px';
331
332 // When we receive the first plugin dimensions from the host, we know that
333 // remote host has started.
334 remoting.app.onVideoStreamingStarted();
Jamie 2015/02/18 00:33:10 Can we eliminate this global dependency?
kelvinp 2015/02/20 23:24:16 Yes, that would be the end goal. We will expose e
335 };
336
337 /**
338 * Helper function accepting client and host dimensions, and returning a chosen
339 * size for the plugin element, in DIPs.
340 *
341 * @param {{width: number, height: number}} clientSizeDips Available client
342 * dimensions, in DIPs.
343 * @param {number} clientPixelRatio Number of physical pixels per client DIP.
344 * @param {{width: number, height: number}} desktopSize Size of the host desktop
345 * in physical pixels.
346 * @param {{x: number, y: number}} desktopDpi DPI of the host desktop in both
347 * dimensions.
348 * @param {number} desktopScale The scale factor configured for the host.
349 * @param {boolean} isFullscreen True if full-screen mode is active.
350 * @param {boolean} shrinkToFit True if shrink-to-fit should be applied.
351 * @return {{width: number, height: number}} Chosen plugin dimensions, in DIPs.
352 */
353 remoting.DesktopViewport.choosePluginSize = function(
354 clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale,
355 isFullscreen, shrinkToFit) {
356 base.debug.assert(clientSizeDips.width > 0);
357 base.debug.assert(clientSizeDips.height > 0);
358 base.debug.assert(clientPixelRatio >= 1.0);
359 base.debug.assert(desktopSize.width > 0);
360 base.debug.assert(desktopSize.height > 0);
361 base.debug.assert(desktopDpi.x > 0);
362 base.debug.assert(desktopDpi.y > 0);
363 base.debug.assert(desktopScale > 0);
364
365 // We have the following goals in sizing the desktop display at the client:
366 // 1. Avoid losing detail by down-scaling beyond 1:1 host:device pixels.
367 // 2. Avoid up-scaling if that will cause the client to need scrollbars.
368 // 3. Avoid introducing blurriness with non-integer up-scaling factors.
369 // 4. Avoid having huge "letterboxes" around the desktop, if it's really
370 // small.
371 // 5. Compensate for mismatched DPIs, so that the behaviour of features like
372 // shrink-to-fit matches their "natural" rather than their pixel size.
373 // e.g. with shrink-to-fit active a 1024x768 low-DPI host on a 640x480
374 // high-DPI client will be up-scaled to 1280x960, rather than displayed
375 // at 1:1 host:physical client pixels.
376 //
377 // To determine the ideal size we follow a four-stage process:
378 // 1. Determine the "natural" size at which to display the desktop.
379 // a. Initially assume 1:1 mapping of desktop to client device pixels.
380 // b. If host DPI is less than the client's then up-scale accordingly.
381 // c. If desktopScale is configured for the host then allow that to
382 // reduce the amount of up-scaling from (b). e.g. if the client:host
383 // DPIs are 2:1 then a desktopScale of 1.5 would reduce the up-scale
384 // to 4:3, while a desktopScale of 3.0 would result in no up-scaling.
385 // 2. If the natural size of the desktop is smaller than the client device
386 // then apply up-scaling by an integer scale factor to avoid excessive
387 // letterboxing.
388 // 3. If shrink-to-fit is configured then:
389 // a. If the natural size exceeds the client size then apply down-scaling
390 // by an arbitrary scale factor.
391 // b. If we're in full-screen mode and the client & host aspect-ratios
392 // are radically different (e.g. the host is actually multi-monitor)
393 // then shrink-to-fit to the shorter dimension, rather than leaving
394 // huge letterboxes; the user can then bump-scroll around the desktop.
395 // 4. If the overall scale factor is fractionally over an integer factor
396 // then reduce it to that integer factor, to avoid blurring.
397
398 // All calculations are performed in device pixels.
399 var clientWidth = clientSizeDips.width * clientPixelRatio;
400 var clientHeight = clientSizeDips.height * clientPixelRatio;
401
402 // 1. Determine a "natural" size at which to display the desktop.
403 var scale = 1.0;
404
405 // Determine the effective host device pixel ratio.
406 // Note that we round up or down to the closest integer pixel ratio.
407 var hostPixelRatioX = Math.round(desktopDpi.x / 96);
408 var hostPixelRatioY = Math.round(desktopDpi.y / 96);
409 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
410
411 // Allow up-scaling to account for DPI.
412 scale = Math.max(scale, clientPixelRatio / hostPixelRatio);
413
414 // Allow some or all of the up-scaling to be cancelled by the desktopScale.
415 if (desktopScale > 1.0) {
416 scale = Math.max(1.0, scale / desktopScale);
417 }
418
419 // 2. If the host is still much smaller than the client, then up-scale to
420 // avoid wasting space, but only by an integer factor, to avoid blurring.
421 if (desktopSize.width * scale <= clientWidth &&
422 desktopSize.height * scale <= clientHeight) {
423 var scaleX = Math.floor(clientWidth / desktopSize.width);
424 var scaleY = Math.floor(clientHeight / desktopSize.height);
425 scale = Math.min(scaleX, scaleY);
426 base.debug.assert(scale >= 1.0);
427 }
428
429 // 3. Apply shrink-to-fit, if configured.
430 if (shrinkToFit) {
431 var scaleFitWidth = Math.min(scale, clientWidth / desktopSize.width);
432 var scaleFitHeight = Math.min(scale, clientHeight / desktopSize.height);
433 scale = Math.min(scaleFitHeight, scaleFitWidth);
434
435 // If we're running full-screen then try to handle common side-by-side
436 // multi-monitor combinations more intelligently.
437 if (isFullscreen) {
438 // If the host has two monitors each the same size as the client then
439 // scale-to-fit will have the desktop occupy only 50% of the client area,
440 // in which case it would be preferable to down-scale less and let the
441 // user bump-scroll around ("scale-and-pan").
442 // Triggering scale-and-pan if less than 65% of the client area would be
443 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to
444 // a (2x1280)x1024 host nicely.
445 // Note that we don't need to account for scrollbars while fullscreen.
446 if (scale <= scaleFitHeight * 0.65) {
447 scale = scaleFitHeight;
448 }
449 if (scale <= scaleFitWidth * 0.65) {
450 scale = scaleFitWidth;
451 }
452 }
453 }
454
455 // 4. Avoid blurring for close-to-integer up-scaling factors.
456 if (scale > 1.0) {
457 var scaleBlurriness = scale / Math.floor(scale);
458 if (scaleBlurriness < 1.1) {
459 scale = Math.floor(scale);
460 }
461 }
462
463 // Return the necessary plugin dimensions in DIPs.
464 scale = scale / clientPixelRatio;
465 var pluginWidth = Math.round(desktopSize.width * scale);
466 var pluginHeight = Math.round(desktopSize.height * scale);
467 return { width: pluginWidth, height: pluginHeight };
468 };
469
470 /** @private */
471 remoting.DesktopViewport.prototype.resetScroll_ = function() {
472 this.pluginContainer_.style.marginTop = '0px';
473 this.pluginContainer_.style.marginLeft = '0px';
474 };
475
476 }());
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698