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

Side by Side Diff: remoting/webapp/crd/js/desktop_connected_view.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
1 // Copyright 2015 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview 6 * @fileoverview
7 * Class handling user-facing aspects of the client session. 7 * Class handling user-facing aspects of the client session.
8 */ 8 */
9 9
10 'use strict'; 10 'use strict';
11 11
12 /** @suppress {duplicate} */ 12 /** @suppress {duplicate} */
13 var remoting = remoting || {}; 13 var remoting = remoting || {};
14 14
15 /** 15 /**
16 * True to enable mouse lock. 16 * True to enable mouse lock.
17 * This is currently disabled because the current client plugin does not 17 * This is currently disabled because the current client plugin does not
18 * properly handle mouse lock and delegated large cursors at the same time. 18 * properly handle mouse lock and delegated large cursors at the same time.
19 * This should be re-enabled (by removing this flag) once a version of 19 * This should be re-enabled (by removing this flag) once a version of
20 * the plugin that supports both has reached Chrome Stable channel. 20 * the plugin that supports both has reached Chrome Stable channel.
21 * (crbug.com/429322). 21 * (crbug.com/429322).
22 * 22 *
23 * @type {boolean} 23 * @type {boolean}
24 */ 24 */
25 remoting.enableMouseLock = false; 25 remoting.enableMouseLock = false;
26 26
27 /** 27 /**
28 * @param {remoting.ClientSession} session 28 * @param {remoting.ClientSession} session
29 * @param {HTMLElement} container 29 * @param {HTMLElement} container
30 * @param {string} hostDisplayName A human-readable name for the host. 30 * @param {remoting.Host} host
31 * @param {string} hostId The host identifier for Me2Me, or empty for IT2Me.
32 * Mixed into authentication hashes for some authentication methods.
33 * @param {remoting.DesktopConnectedView.Mode} mode The mode of this connection. 31 * @param {remoting.DesktopConnectedView.Mode} mode The mode of this connection.
34 * @param {string} defaultRemapKeys The default set of remap keys, to use 32 * @param {string} defaultRemapKeys The default set of remap keys, to use
35 * when the client doesn't define any. 33 * when the client doesn't define any.
36 * @param {function(remoting.Error, remoting.ClientPlugin): void} onInitialized 34 * @param {function(remoting.Error, remoting.ClientPlugin): void} onInitialized
37 * @constructor 35 * @constructor
38 * @extends {base.EventSourceImpl} 36 * @extends {base.EventSourceImpl}
39 */ 37 */
40 remoting.DesktopConnectedView = function(session, container, hostDisplayName, 38 remoting.DesktopConnectedView = function(session, container, host, mode,
41 hostId, mode, defaultRemapKeys, 39 defaultRemapKeys, onInitialized) {
42 onInitialized) {
43 this.session_ = session; 40 this.session_ = session;
44 41
45 /** @type {HTMLElement} @private */ 42 /** @type {HTMLElement} @private */
46 this.container_ = container; 43 this.container_ = container;
47 44
48 /** @type {remoting.ClientPlugin} @private */ 45 /** @type {remoting.ClientPlugin} @private */
49 this.plugin_ = null; 46 this.plugin_ = null;
50 47
51 /** @private */ 48 /** @private */
52 this.hostDisplayName_ = hostDisplayName; 49 this.host_ = host;
53
54 /** @private */
55 this.hostId_ = hostId;
56 50
57 /** @private */ 51 /** @private */
58 this.mode_ = mode; 52 this.mode_ = mode;
59 53
60 /** @type {boolean} @private */
61 this.shrinkToFit_ = true;
62
63 /** @type {boolean} @private */
64 this.resizeToClient_ = true;
65
66 /** @type {number} @private */
67 this.desktopScale_ = 1.0;
68
69 /** @type {string} @private */ 54 /** @type {string} @private */
70 this.defaultRemapKeys_ = defaultRemapKeys; 55 this.defaultRemapKeys_ = defaultRemapKeys;
71 56
72 /** @type {string} @private */ 57 /** @type {string} @private */
73 this.remapKeys_ = ''; 58 this.remapKeys_ = '';
74 59
75 /** 60 /**
76 * Called when the UI is finished initializing. 61 * Called when the UI is finished initializing.
77 * @type {function(remoting.Error, remoting.ClientPlugin):void} 62 * @type {function(remoting.Error, remoting.ClientPlugin):void}
78 */ 63 */
79 this.onInitialized_ = onInitialized; 64 this.onInitialized_ = onInitialized;
80 65
81 /** @type {function(boolean=):void} @private */ 66 /** @type {function(boolean=):void} @private */
82 this.callOnFullScreenChanged_ = this.onFullScreenChanged_.bind(this) 67 this.callOnFullScreenChanged_ = this.onFullScreenChanged_.bind(this)
83 68
84 /** @private */ 69 /** @private */
85 this.callPluginLostFocus_ = this.pluginLostFocus_.bind(this); 70 this.callPluginLostFocus_ = this.pluginLostFocus_.bind(this);
86 /** @private */ 71 /** @private */
87 this.callPluginGotFocus_ = this.pluginGotFocus_.bind(this); 72 this.callPluginGotFocus_ = this.pluginGotFocus_.bind(this);
88 73
89 /** @type {number?} @private */
90 this.notifyClientResolutionTimer_ = null;
91
92 /** @type {Element} @private */ 74 /** @type {Element} @private */
93 this.mouseCursorOverlay_ = 75 this.mouseCursorOverlay_ =
94 this.container_.querySelector('.mouse-cursor-overlay'); 76 this.container_.querySelector('.mouse-cursor-overlay');
95 77
78 /** @private {remoting.DesktopViewport} */
79 this.viewport_ = null;
80
96 /** @type {Element} */ 81 /** @type {Element} */
97 var img = this.mouseCursorOverlay_; 82 var img = this.mouseCursorOverlay_;
98 /** @param {Event} event @private */ 83 /** @param {Event} event @private */
99 this.updateMouseCursorPosition_ = function(event) { 84 this.updateMouseCursorPosition_ = function(event) {
100 img.style.top = event.y + 'px'; 85 img.style.top = event.y + 'px';
101 img.style.left = event.x + 'px'; 86 img.style.left = event.x + 'px';
102 }; 87 };
103 88
104 /** @type {number?} @private */
105 this.bumpScrollTimer_ = null;
106
107 // Bump-scroll test variables. Override to use a fake value for the width
108 // and height of the client plugin so that bump-scrolling can be tested
109 // without relying on the actual size of the host desktop.
110 /** @type {number} @private */
111 this.pluginWidthForBumpScrollTesting = 0;
112 /** @type {number} @private */
113 this.pluginHeightForBumpScrollTesting = 0;
114
115 /** @type {remoting.VideoFrameRecorder} @private */ 89 /** @type {remoting.VideoFrameRecorder} @private */
116 this.videoFrameRecorder_ = null; 90 this.videoFrameRecorder_ = null;
117 }; 91 };
118 92
119 base.extend(remoting.DesktopConnectedView, base.EventSourceImpl); 93 base.extend(remoting.DesktopConnectedView, base.EventSourceImpl);
120 94
121 /** @enum {string} */
122 remoting.DesktopConnectedView.Events = {
123 bumpScrollStarted: 'bumpScrollStarted',
124 bumpScrollStopped: 'bumpScrollStopped'
125 };
126
127 // The mode of this session. 95 // The mode of this session.
128 /** @enum {number} */ 96 /** @enum {number} */
129 remoting.DesktopConnectedView.Mode = { 97 remoting.DesktopConnectedView.Mode = {
130 IT2ME: 0, 98 IT2ME: 0,
131 ME2ME: 1, 99 ME2ME: 1,
132 APP_REMOTING: 2 100 APP_REMOTING: 2
133 }; 101 };
134 102
135 // Keys for per-host settings. 103 // Keys for per-host settings.
136 remoting.DesktopConnectedView.KEY_REMAP_KEYS = 'remapKeys'; 104 remoting.DesktopConnectedView.KEY_REMAP_KEYS = 'remapKeys';
137 remoting.DesktopConnectedView.KEY_RESIZE_TO_CLIENT = 'resizeToClient'; 105 remoting.DesktopConnectedView.KEY_RESIZE_TO_CLIENT = 'resizeToClient';
138 remoting.DesktopConnectedView.KEY_SHRINK_TO_FIT = 'shrinkToFit'; 106 remoting.DesktopConnectedView.KEY_SHRINK_TO_FIT = 'shrinkToFit';
139 remoting.DesktopConnectedView.KEY_DESKTOP_SCALE = 'desktopScale'; 107 remoting.DesktopConnectedView.KEY_DESKTOP_SCALE = 'desktopScale';
140 108
141 /** 109 /**
142 * Get host display name. 110 * Get host display name.
143 * 111 *
144 * @return {string} 112 * @return {string}
145 */ 113 */
146 remoting.DesktopConnectedView.prototype.getHostDisplayName = function() { 114 remoting.DesktopConnectedView.prototype.getHostDisplayName = function() {
147 return this.hostDisplayName_; 115 return this.host_.hostName;
148 }; 116 };
149 117
150 /** 118 /**
151 * @return {remoting.DesktopConnectedView.Mode} The current state. 119 * @return {remoting.DesktopConnectedView.Mode} The current state.
152 */ 120 */
153 remoting.DesktopConnectedView.prototype.getMode = function() { 121 remoting.DesktopConnectedView.prototype.getMode = function() {
154 return this.mode_; 122 return this.mode_;
155 }; 123 };
156 124
157 /** 125 /**
158 * @return {boolean} True if shrink-to-fit is enabled; false otherwise. 126 * @return {boolean} True if shrink-to-fit is enabled; false otherwise.
159 */ 127 */
160 remoting.DesktopConnectedView.prototype.getShrinkToFit = function() { 128 remoting.DesktopConnectedView.prototype.getShrinkToFit = function() {
161 return this.shrinkToFit_; 129 if (this.viewport_) {
130 return this.viewport_.getShrinkToFit();
131 }
162 }; 132 };
163 133
164 /** 134 /**
165 * @return {boolean} True if resize-to-client is enabled; false otherwise. 135 * @return {boolean} True if resize-to-client is enabled; false otherwise.
166 */ 136 */
167 remoting.DesktopConnectedView.prototype.getResizeToClient = function() { 137 remoting.DesktopConnectedView.prototype.getResizeToClient = function() {
168 return this.resizeToClient_; 138 if (this.viewport_) {
139 return this.viewport_.getResizeToClient();
140 }
169 }; 141 };
170 142
171 /** 143 /**
172 * @return {Element} The element that should host the plugin. 144 * @return {Element} The element that should host the plugin.
145 * @private
173 */ 146 */
174 remoting.DesktopConnectedView.prototype.getPluginContainer = function() { 147 remoting.DesktopConnectedView.prototype.getPluginContainer_ = function() {
175 return this.container_.querySelector('.client-plugin-container') 148 return this.container_.querySelector('.client-plugin-container');
176 }; 149 };
177 150
178 /** 151 /**
179 * @return {{width: number, height: number}} The height of the window's client
180 * area. This differs between apps v1 and apps v2 due to the custom window
181 * borders used by the latter.
182 * TODO: make private
183 */
184 remoting.DesktopConnectedView.prototype.getClientArea_ = function() {
185 return remoting.windowFrame ?
186 remoting.windowFrame.getClientArea() :
187 { 'width': window.innerWidth, 'height': window.innerHeight };
188 };
189
190 /**
191 * Notifies the host of the client's current dimensions and DPI. 152 * Notifies the host of the client's current dimensions and DPI.
192 * Also takes into account per-host scaling factor, if configured. 153 * Also takes into account per-host scaling factor, if configured.
193 * TODO: private 154 * TODO: private
194 */ 155 */
195 remoting.DesktopConnectedView.prototype.notifyClientResolution_ = function() { 156 remoting.DesktopConnectedView.prototype.notifyClientResolution_ = function() {
196 var clientArea = this.getClientArea_(); 157 if (this.viewport_) {
197 this.plugin_.notifyClientResolution(clientArea.width * this.desktopScale_, 158 this.viewport_.resizeHostDesktop();
198 clientArea.height * this.desktopScale_, 159 }
199 window.devicePixelRatio); 160 };
161
162 /** @return {remoting.DesktopViewport} */
163 remoting.DesktopConnectedView.prototype.getViewportForTesting = function() {
164 return this.viewport_;
200 }; 165 };
201 166
202 /** 167 /**
203 * Adds <embed> element to the UI container and readies the session object. 168 * Adds <embed> element to the UI container and readies the session object.
204 * 169 *
205 * @param {function(string, string):boolean} onExtensionMessage The handler for 170 * @param {function(string, string):boolean} onExtensionMessage The handler for
206 * protocol extension messages. Returns true if a message is recognized; 171 * protocol extension messages. Returns true if a message is recognized;
207 * false otherwise. 172 * false otherwise.
208 * @param {Array<string>} requiredCapabilities A list of capabilities 173 * @param {Array.<string>} requiredCapabilities A list of capabilities
209 * required by this application. 174 * required by this application.
210 */ 175 */
211 remoting.DesktopConnectedView.prototype.createPluginAndConnect = 176 remoting.DesktopConnectedView.prototype.createPluginAndConnect =
212 function(onExtensionMessage, requiredCapabilities) { 177 function(onExtensionMessage, requiredCapabilities) {
213 this.plugin_ = remoting.ClientPlugin.factory.createPlugin( 178 this.plugin_ = remoting.ClientPlugin.factory.createPlugin(
214 this.getPluginContainer(), 179 this.getPluginContainer_(),
215 onExtensionMessage, requiredCapabilities); 180 onExtensionMessage, requiredCapabilities);
216 remoting.HostSettings.load(this.hostId_, 181 var that = this;
217 this.onHostSettingsLoaded_.bind(this)); 182 this.host_.options().load().then(function(){
183 that.plugin_.initialize(that.onPluginInitialized_.bind(that));
184 });
218 }; 185 };
219 186
220 /** 187 /**
221 * @param {Object<string|boolean|number>} options The current options for the
222 * host, or {} if this client has no saved settings for the host.
223 * @private
224 */
225 remoting.DesktopConnectedView.prototype.onHostSettingsLoaded_ = function(
226 options) {
227 if (remoting.DesktopConnectedView.KEY_REMAP_KEYS in options &&
228 typeof(options[remoting.DesktopConnectedView.KEY_REMAP_KEYS]) ==
229 'string') {
230 this.remapKeys_ = /** @type {string} */
231 (options[remoting.DesktopConnectedView.KEY_REMAP_KEYS]);
232 }
233 if (remoting.DesktopConnectedView.KEY_RESIZE_TO_CLIENT in options &&
234 typeof(options[remoting.DesktopConnectedView.KEY_RESIZE_TO_CLIENT]) ==
235 'boolean') {
236 this.resizeToClient_ = /** @type {boolean} */
237 (options[remoting.DesktopConnectedView.KEY_RESIZE_TO_CLIENT]);
238 }
239 if (remoting.DesktopConnectedView.KEY_SHRINK_TO_FIT in options &&
240 typeof(options[remoting.DesktopConnectedView.KEY_SHRINK_TO_FIT]) ==
241 'boolean') {
242 this.shrinkToFit_ = /** @type {boolean} */
243 (options[remoting.DesktopConnectedView.KEY_SHRINK_TO_FIT]);
244 }
245 if (remoting.DesktopConnectedView.KEY_DESKTOP_SCALE in options &&
246 typeof(options[remoting.DesktopConnectedView.KEY_DESKTOP_SCALE]) ==
247 'number') {
248 this.desktopScale_ = /** @type {number} */
249 (options[remoting.DesktopConnectedView.KEY_DESKTOP_SCALE]);
250 }
251
252 /** @param {boolean} result */
253 this.plugin_.initialize(this.onPluginInitialized_.bind(this));
254 };
255
256 /**
257 * @param {boolean} initialized 188 * @param {boolean} initialized
258 */ 189 */
259 remoting.DesktopConnectedView.prototype.onPluginInitialized_ = function( 190 remoting.DesktopConnectedView.prototype.onPluginInitialized_ = function(
260 initialized) { 191 initialized) {
261 if (!initialized) { 192 if (!initialized) {
262 console.error('ERROR: remoting plugin not loaded'); 193 console.error('ERROR: remoting plugin not loaded');
263 this.onInitialized_(remoting.Error.MISSING_PLUGIN, this.plugin_); 194 this.onInitialized_(remoting.Error.MISSING_PLUGIN, this.plugin_);
264 return; 195 return;
265 } 196 }
266 197
(...skipping 19 matching lines...) Expand all
286 this.applyRemapKeys_(true); 217 this.applyRemapKeys_(true);
287 } 218 }
288 219
289 // TODO(wez): Only allow mouse lock if the app has the pointerLock permission. 220 // TODO(wez): Only allow mouse lock if the app has the pointerLock permission.
290 // Enable automatic mouse-lock. 221 // Enable automatic mouse-lock.
291 if (remoting.enableMouseLock && 222 if (remoting.enableMouseLock &&
292 this.plugin_.hasFeature(remoting.ClientPlugin.Feature.ALLOW_MOUSE_LOCK)) { 223 this.plugin_.hasFeature(remoting.ClientPlugin.Feature.ALLOW_MOUSE_LOCK)) {
293 this.plugin_.allowMouseLock(); 224 this.plugin_.allowMouseLock();
294 } 225 }
295 226
296 this.plugin_.setDesktopShapeUpdateHandler(
297 this.onDesktopShapeChanged_.bind(this));
298 this.plugin_.setDesktopSizeUpdateHandler(
299 this.onDesktopSizeChanged_.bind(this));
300 this.plugin_.setMouseCursorHandler(this.updateMouseCursorImage_.bind(this)); 227 this.plugin_.setMouseCursorHandler(this.updateMouseCursorImage_.bind(this));
301 228
302 this.onInitialized_(remoting.Error.NONE, this.plugin_); 229 this.onInitialized_(remoting.Error.NONE, this.plugin_);
303 }; 230 };
304 231
305 /** 232 /**
306 * This is a callback that gets called when the plugin notifies us of a change
307 * in the size of the remote desktop.
308 *
309 * @return {void} Nothing.
310 * @private
311 */
312 remoting.DesktopConnectedView.prototype.onDesktopSizeChanged_ = function() {
313 console.log('desktop size changed: ' +
314 this.plugin_.getDesktopWidth() + 'x' +
315 this.plugin_.getDesktopHeight() +' @ ' +
316 this.plugin_.getDesktopXDpi() + 'x' +
317 this.plugin_.getDesktopYDpi() + ' DPI');
318 this.updateDimensions();
319 this.updateScrollbarVisibility();
320 };
321
322 /**
323 * Sets the non-click-through area of the client in response to notifications
324 * from the plugin of desktop shape changes.
325 *
326 * @param {Array<Array<number>>} rects List of rectangles comprising the
327 * desktop shape.
328 * @return {void} Nothing.
329 * @private
330 */
331 remoting.DesktopConnectedView.prototype.onDesktopShapeChanged_ = function(
332 rects) {
333 // Build the list of rects for the input region.
334 var inputRegion = [];
335 for (var i = 0; i < rects.length; ++i) {
336 var rect = {};
337 rect.left = rects[i][0];
338 rect.top = rects[i][1];
339 rect.width = rects[i][2];
340 rect.height = rects[i][3];
341 inputRegion.push(rect);
342 }
343
344 remoting.windowShape.setDesktopRects(inputRegion);
345 };
346
347 /**
348 * This is a callback that gets called when the window is resized. 233 * This is a callback that gets called when the window is resized.
349 * 234 *
350 * @return {void} Nothing. 235 * @return {void} Nothing.
351 */ 236 */
352 remoting.DesktopConnectedView.prototype.onResize = function() { 237 remoting.DesktopConnectedView.prototype.onResize = function() {
353 this.updateDimensions(); 238 if (this.viewport_) {
354 239 this.viewport_.onResize();
355 if (this.notifyClientResolutionTimer_) {
356 window.clearTimeout(this.notifyClientResolutionTimer_);
357 this.notifyClientResolutionTimer_ = null;
358 } 240 }
359
360 // Defer notifying the host of the change until the window stops resizing, to
361 // avoid overloading the control channel with notifications.
362 if (this.resizeToClient_) {
363 var kResizeRateLimitMs = 1000;
364 if (this.session_.hasCapability(
365 remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS)) {
366 kResizeRateLimitMs = 250;
367 }
368 var clientArea = this.getClientArea_();
369 this.notifyClientResolutionTimer_ = window.setTimeout(
370 this.notifyClientResolution_.bind(this),
371 kResizeRateLimitMs);
372 }
373
374 // If bump-scrolling is enabled, adjust the plugin margins to fully utilize
375 // the new window area.
376 this.resetScroll_();
377
378 this.updateScrollbarVisibility();
379 }; 241 };
380 242
381 /** 243 /**
382 * Callback that the plugin invokes to indicate when the connection is 244 * Callback that the plugin invokes to indicate when the connection is
383 * ready. 245 * ready.
384 * 246 *
385 * @param {boolean} ready True if the connection is ready. 247 * @param {boolean} ready True if the connection is ready.
386 */ 248 */
387 remoting.DesktopConnectedView.prototype.onConnectionReady = function(ready) { 249 remoting.DesktopConnectedView.prototype.onConnectionReady = function(ready) {
388 if (!ready) { 250 if (!ready) {
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
428 if (remoting.optionsMenu) { 290 if (remoting.optionsMenu) {
429 remoting.optionsMenu.setDesktopConnectedView(null); 291 remoting.optionsMenu.setDesktopConnectedView(null);
430 } 292 }
431 293
432 document.body.classList.remove('connected'); 294 document.body.classList.remove('connected');
433 this.container_.removeEventListener( 295 this.container_.removeEventListener(
434 'mousemove', this.updateMouseCursorPosition_, true); 296 'mousemove', this.updateMouseCursorPosition_, true);
435 // Stop listening for full-screen events. 297 // Stop listening for full-screen events.
436 remoting.fullscreen.removeListener(this.callOnFullScreenChanged_); 298 remoting.fullscreen.removeListener(this.callOnFullScreenChanged_);
437 299
300 base.dispose(this.viewport_);
301 this.viewport_ = null;
438 } else { 302 } else {
303 this.viewport_ = new remoting.DesktopViewport(
304 document.getElementById('scroller'),
305 this.plugin_.hostDesktop(),
306 this.host_.options());
439 if (remoting.windowFrame) { 307 if (remoting.windowFrame) {
440 remoting.windowFrame.setDesktopConnectedView(this); 308 remoting.windowFrame.setDesktopConnectedView(this);
441 } 309 }
442 if (remoting.toolbar) { 310 if (remoting.toolbar) {
443 remoting.toolbar.setDesktopConnectedView(this); 311 remoting.toolbar.setDesktopConnectedView(this);
444 } 312 }
445 if (remoting.optionsMenu) { 313 if (remoting.optionsMenu) {
446 remoting.optionsMenu.setDesktopConnectedView(this); 314 remoting.optionsMenu.setDesktopConnectedView(this);
447 } 315 }
448 316
449 if (this.resizeToClient_) {
450 this.notifyClientResolution_();
451 }
452
453 document.body.classList.add('connected'); 317 document.body.classList.add('connected');
454 this.container_.addEventListener( 318 this.container_.addEventListener(
455 'mousemove', this.updateMouseCursorPosition_, true); 319 'mousemove', this.updateMouseCursorPosition_, true);
456 // Activate full-screen related UX. 320 // Activate full-screen related UX.
457 remoting.fullscreen.addListener(this.callOnFullScreenChanged_); 321 remoting.fullscreen.addListener(this.callOnFullScreenChanged_);
458 this.onDesktopSizeChanged_();
459 this.setFocusHandlers_(); 322 this.setFocusHandlers_();
460 } 323 }
461 }; 324 };
462 325
463 /** 326 /**
464 * @return {{top: number, left:number}} The top-left corner of the plugin.
465 */
466 remoting.DesktopConnectedView.prototype.getPluginPositionForTesting = function(
467 ) {
468 var style = this.container_.style;
469 return {
470 top: parseFloat(style.marginTop),
471 left: parseFloat(style.marginLeft)
472 };
473 };
474
475 /**
476 * Constrains the focus to the plugin element. 327 * Constrains the focus to the plugin element.
477 * @private 328 * @private
478 */ 329 */
479 remoting.DesktopConnectedView.prototype.setFocusHandlers_ = function() { 330 remoting.DesktopConnectedView.prototype.setFocusHandlers_ = function() {
480 this.plugin_.element().addEventListener( 331 this.plugin_.element().addEventListener(
481 'focus', this.callPluginGotFocus_, false); 332 'focus', this.callPluginGotFocus_, false);
482 this.plugin_.element().addEventListener( 333 this.plugin_.element().addEventListener(
483 'blur', this.callPluginLostFocus_, false); 334 'blur', this.callPluginLostFocus_, false);
484 this.plugin_.element().focus(); 335 this.plugin_.element().focus();
485 }; 336 };
486 337
487 /** 338 /**
488 * Set the shrink-to-fit and resize-to-client flags and save them if this is 339 * Set the shrink-to-fit and resize-to-client flags and save them if this is
489 * a Me2Me connection. 340 * a Me2Me connection.
490 * 341 *
491 * @param {boolean} shrinkToFit True if the remote desktop should be scaled 342 * @param {boolean} shrinkToFit True if the remote desktop should be scaled
492 * down if it is larger than the client window; false if scroll-bars 343 * down if it is larger than the client window; false if scroll-bars
493 * should be added in this case. 344 * should be added in this case.
494 * @param {boolean} resizeToClient True if window resizes should cause the 345 * @param {boolean} resizeToClient True if window resizes should cause the
495 * host to attempt to resize its desktop to match the client window size; 346 * host to attempt to resize its desktop to match the client window size;
496 * false to disable this behaviour for subsequent window resizes--the 347 * false to disable this behaviour for subsequent window resizes--the
497 * current host desktop size is not restored in this case. 348 * current host desktop size is not restored in this case.
498 * @return {void} Nothing. 349 * @return {void} Nothing.
499 */ 350 */
500 remoting.DesktopConnectedView.prototype.setScreenMode = 351 remoting.DesktopConnectedView.prototype.setScreenMode =
501 function(shrinkToFit, resizeToClient) { 352 function(shrinkToFit, resizeToClient) {
502 if (resizeToClient && !this.resizeToClient_) { 353 this.viewport_.setScreenMode(shrinkToFit, resizeToClient);
503 this.notifyClientResolution_();
504 }
505
506 // If enabling shrink, reset bump-scroll offsets.
507 var needsScrollReset = shrinkToFit && !this.shrinkToFit_;
508
509 this.shrinkToFit_ = shrinkToFit;
510 this.resizeToClient_ = resizeToClient;
511 this.updateScrollbarVisibility();
512
513 if (this.hostId_ != '') {
514 var options = {};
515 options[remoting.DesktopConnectedView.KEY_SHRINK_TO_FIT] =
516 this.shrinkToFit_;
517 options[remoting.DesktopConnectedView.KEY_RESIZE_TO_CLIENT] =
518 this.resizeToClient_;
519 remoting.HostSettings.save(this.hostId_, options);
520 }
521
522 this.updateDimensions();
523 if (needsScrollReset) {
524 this.resetScroll_();
525 }
526 }; 354 };
527 355
528 /** 356 /**
529 * Sets and stores the scale factor to apply to host sizing requests.
530 * The desktopScale applies to the dimensions reported to the host, not
531 * to the client DPI reported to it.
532 *
533 * @param {number} desktopScale Scale factor to apply.
534 */
535 remoting.DesktopConnectedView.prototype.setDesktopScale = function(
536 desktopScale) {
537 this.desktopScale_ = desktopScale;
538
539 // onResize() will update the plugin size and scrollbars for the new
540 // scaled plugin dimensions, and send a client resolution notification.
541 this.onResize();
542
543 // Save the new desktop scale setting.
544 var options = {};
545 options[remoting.DesktopConnectedView.KEY_DESKTOP_SCALE] = this.desktopScale_;
546 remoting.HostSettings.save(this.hostId_, options);
547 };
548
549 /**
550 * Called when the full-screen status has changed, either via the 357 * Called when the full-screen status has changed, either via the
551 * remoting.Fullscreen class, or via a system event such as the Escape key 358 * remoting.Fullscreen class, or via a system event such as the Escape key
552 * 359 *
553 * @param {boolean=} fullscreen True if the app is entering full-screen mode; 360 * @param {boolean=} fullscreen True if the app is entering full-screen mode;
554 * false if it is leaving it. 361 * false if it is leaving it.
555 * @private 362 * @private
556 */ 363 */
557 remoting.DesktopConnectedView.prototype.onFullScreenChanged_ = function ( 364 remoting.DesktopConnectedView.prototype.onFullScreenChanged_ = function (
558 fullscreen) { 365 fullscreen) {
559 this.enableBumpScroll_(fullscreen); 366 if (this.viewport_) {
367 this.viewport_.enableBumpScroll(fullscreen);
368 }
560 }; 369 };
561 370
562 /** 371 /**
563 * Callback function called when the plugin element gets focus. 372 * Callback function called when the plugin element gets focus.
564 */ 373 */
565 remoting.DesktopConnectedView.prototype.pluginGotFocus_ = function() { 374 remoting.DesktopConnectedView.prototype.pluginGotFocus_ = function() {
566 remoting.clipboard.initiateToHost(); 375 remoting.clipboard.initiateToHost();
567 }; 376 };
568 377
569 /** 378 /**
(...skipping 22 matching lines...) Expand all
592 function(url, hotspotX, hotspotY) { 401 function(url, hotspotX, hotspotY) {
593 this.mouseCursorOverlay_.hidden = !url; 402 this.mouseCursorOverlay_.hidden = !url;
594 if (url) { 403 if (url) {
595 this.mouseCursorOverlay_.style.marginLeft = '-' + hotspotX + 'px'; 404 this.mouseCursorOverlay_.style.marginLeft = '-' + hotspotX + 'px';
596 this.mouseCursorOverlay_.style.marginTop = '-' + hotspotY + 'px'; 405 this.mouseCursorOverlay_.style.marginTop = '-' + hotspotY + 'px';
597 this.mouseCursorOverlay_.src = url; 406 this.mouseCursorOverlay_.src = url;
598 } 407 }
599 }; 408 };
600 409
601 /** 410 /**
602 * Enable or disable bump-scrolling. When disabling bump scrolling, also reset
603 * the scroll offsets to (0, 0).
604 * @param {boolean=} enable True to enable bump-scrolling, false to disable it.
605 * @private
606 */
607 remoting.DesktopConnectedView.prototype.enableBumpScroll_ = function(enable) {
608 var element = /** @type{HTMLElement} */ (document.documentElement);
609 if (enable) {
610 /** @type {null|function(Event):void} */
611 this.onMouseMoveRef_ = this.onMouseMove_.bind(this);
612 element.addEventListener('mousemove', this.onMouseMoveRef_, false);
613 } else {
614 element.removeEventListener('mousemove', this.onMouseMoveRef_, false);
615 this.onMouseMoveRef_ = null;
616 this.resetScroll_();
617 }
618 };
619
620 remoting.DesktopConnectedView.prototype.resetScroll_ = function() {
621 this.container_.style.marginTop = '0px';
622 this.container_.style.marginLeft = '0px';
623 };
624
625 /**
626 * @param {Event} event The mouse event.
627 * @private
628 */
629 remoting.DesktopConnectedView.prototype.onMouseMove_ = function(event) {
630 if (this.bumpScrollTimer_) {
631 window.clearTimeout(this.bumpScrollTimer_);
632 this.bumpScrollTimer_ = null;
633 }
634
635 /**
636 * Compute the scroll speed based on how close the mouse is to the edge.
637 * @param {number} mousePos The mouse x- or y-coordinate
638 * @param {number} size The width or height of the content area.
639 * @return {number} The scroll delta, in pixels.
640 */
641 var computeDelta = function(mousePos, size) {
642 var threshold = 10;
643 if (mousePos >= size - threshold) {
644 return 1 + 5 * (mousePos - (size - threshold)) / threshold;
645 } else if (mousePos <= threshold) {
646 return -1 - 5 * (threshold - mousePos) / threshold;
647 }
648 return 0;
649 };
650
651 var clientArea = this.getClientArea_();
652 var dx = computeDelta(event.x, clientArea.width);
653 var dy = computeDelta(event.y, clientArea.height);
654
655 if (dx != 0 || dy != 0) {
656 this.raiseEvent(remoting.DesktopConnectedView.Events.bumpScrollStarted);
657 /** @type {remoting.DesktopConnectedView} */
658 var that = this;
659 /**
660 * Scroll the view, and schedule a timer to do so again unless we've hit
661 * the edges of the screen. This timer is cancelled when the mouse moves.
662 * @param {number} expected The time at which we expect to be called.
663 */
664 var repeatScroll = function(expected) {
665 /** @type {number} */
666 var now = new Date().getTime();
667 /** @type {number} */
668 var timeout = 10;
669 var lateAdjustment = 1 + (now - expected) / timeout;
670 if (that.scroll_(lateAdjustment * dx, lateAdjustment * dy)) {
671 that.raiseEvent(remoting.DesktopConnectedView.Events.bumpScrollStopped);
672 } else {
673 that.bumpScrollTimer_ = window.setTimeout(
674 function() { repeatScroll(now + timeout); },
675 timeout);
676 }
677 };
678 repeatScroll(new Date().getTime());
679 }
680 };
681
682 /**
683 * Scroll the client plugin by the specified amount, keeping it visible.
684 * Note that this is only used in content full-screen mode (not windowed or
685 * browser full-screen modes), where window.scrollBy and the scrollTop and
686 * scrollLeft properties don't work.
687 * @param {number} dx The amount by which to scroll horizontally. Positive to
688 * scroll right; negative to scroll left.
689 * @param {number} dy The amount by which to scroll vertically. Positive to
690 * scroll down; negative to scroll up.
691 * @return {boolean} True if the requested scroll had no effect because both
692 * vertical and horizontal edges of the screen have been reached.
693 * @private
694 */
695 remoting.DesktopConnectedView.prototype.scroll_ = function(dx, dy) {
696 /**
697 * Helper function for x- and y-scrolling
698 * @param {number|string} curr The current margin, eg. "10px".
699 * @param {number} delta The requested scroll amount.
700 * @param {number} windowBound The size of the window, in pixels.
701 * @param {number} pluginBound The size of the plugin, in pixels.
702 * @param {{stop: boolean}} stop Reference parameter used to indicate when
703 * the scroll has reached one of the edges and can be stopped in that
704 * direction.
705 * @return {string} The new margin value.
706 */
707 var adjustMargin = function(curr, delta, windowBound, pluginBound, stop) {
708 var minMargin = Math.min(0, windowBound - pluginBound);
709 var result = (curr ? parseFloat(curr) : 0) - delta;
710 result = Math.min(0, Math.max(minMargin, result));
711 stop.stop = (result == 0 || result == minMargin);
712 return result + 'px';
713 };
714
715 var plugin = this.plugin_.element();
716 var style = this.container_.style;
717
718 var stopX = { stop: false };
719 var clientArea = this.getClientArea_();
720 style.marginLeft = adjustMargin(style.marginLeft, dx, clientArea.width,
721 this.pluginWidthForBumpScrollTesting || plugin.clientWidth, stopX);
722
723 var stopY = { stop: false };
724 style.marginTop = adjustMargin(
725 style.marginTop, dy, clientArea.height,
726 this.pluginHeightForBumpScrollTesting || plugin.clientHeight, stopY);
727 return stopX.stop && stopY.stop;
728 };
729
730 /**
731 * Refreshes the plugin's dimensions, taking into account the sizes of the
732 * remote desktop and client window, and the current scale-to-fit setting.
733 *
734 * @return {void} Nothing.
735 */
736 remoting.DesktopConnectedView.prototype.updateDimensions = function() {
737 if (this.plugin_.getDesktopWidth() == 0 ||
738 this.plugin_.getDesktopHeight() == 0) {
739 return;
740 }
741
742 var desktopSize = { width: this.plugin_.getDesktopWidth(),
743 height: this.plugin_.getDesktopHeight() };
744 var desktopDpi = { x: this.plugin_.getDesktopXDpi(),
745 y: this.plugin_.getDesktopYDpi() };
746 var newSize = remoting.DesktopConnectedView.choosePluginSize(
747 this.getClientArea_(), window.devicePixelRatio,
748 desktopSize, desktopDpi, this.desktopScale_,
749 remoting.fullscreen.isActive(), this.shrinkToFit_);
750
751 // Resize the plugin if necessary.
752 console.log('plugin dimensions:' + newSize.width + 'x' + newSize.height);
753 this.plugin_.element().style.width = newSize.width + 'px';
754 this.plugin_.element().style.height = newSize.height + 'px';
755
756 // When we receive the first plugin dimensions from the host, we know that
757 // remote host has started.
758 remoting.app.onVideoStreamingStarted();
759 }
760
761 /**
762 * Helper function accepting client and host dimensions, and returning a chosen
763 * size for the plugin element, in DIPs.
764 *
765 * @param {{width: number, height: number}} clientSizeDips Available client
766 * dimensions, in DIPs.
767 * @param {number} clientPixelRatio Number of physical pixels per client DIP.
768 * @param {{width: number, height: number}} desktopSize Size of the host desktop
769 * in physical pixels.
770 * @param {{x: number, y: number}} desktopDpi DPI of the host desktop in both
771 * dimensions.
772 * @param {number} desktopScale The scale factor configured for the host.
773 * @param {boolean} isFullscreen True if full-screen mode is active.
774 * @param {boolean} shrinkToFit True if shrink-to-fit should be applied.
775 * @return {{width: number, height: number}} Chosen plugin dimensions, in DIPs.
776 */
777 remoting.DesktopConnectedView.choosePluginSize = function(
778 clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale,
779 isFullscreen, shrinkToFit) {
780 base.debug.assert(clientSizeDips.width > 0);
781 base.debug.assert(clientSizeDips.height > 0);
782 base.debug.assert(clientPixelRatio >= 1.0);
783 base.debug.assert(desktopSize.width > 0);
784 base.debug.assert(desktopSize.height > 0);
785 base.debug.assert(desktopDpi.x > 0);
786 base.debug.assert(desktopDpi.y > 0);
787 base.debug.assert(desktopScale > 0);
788
789 // We have the following goals in sizing the desktop display at the client:
790 // 1. Avoid losing detail by down-scaling beyond 1:1 host:device pixels.
791 // 2. Avoid up-scaling if that will cause the client to need scrollbars.
792 // 3. Avoid introducing blurriness with non-integer up-scaling factors.
793 // 4. Avoid having huge "letterboxes" around the desktop, if it's really
794 // small.
795 // 5. Compensate for mismatched DPIs, so that the behaviour of features like
796 // shrink-to-fit matches their "natural" rather than their pixel size.
797 // e.g. with shrink-to-fit active a 1024x768 low-DPI host on a 640x480
798 // high-DPI client will be up-scaled to 1280x960, rather than displayed
799 // at 1:1 host:physical client pixels.
800 //
801 // To determine the ideal size we follow a four-stage process:
802 // 1. Determine the "natural" size at which to display the desktop.
803 // a. Initially assume 1:1 mapping of desktop to client device pixels.
804 // b. If host DPI is less than the client's then up-scale accordingly.
805 // c. If desktopScale is configured for the host then allow that to
806 // reduce the amount of up-scaling from (b). e.g. if the client:host
807 // DPIs are 2:1 then a desktopScale of 1.5 would reduce the up-scale
808 // to 4:3, while a desktopScale of 3.0 would result in no up-scaling.
809 // 2. If the natural size of the desktop is smaller than the client device
810 // then apply up-scaling by an integer scale factor to avoid excessive
811 // letterboxing.
812 // 3. If shrink-to-fit is configured then:
813 // a. If the natural size exceeds the client size then apply down-scaling
814 // by an arbitrary scale factor.
815 // b. If we're in full-screen mode and the client & host aspect-ratios
816 // are radically different (e.g. the host is actually multi-monitor)
817 // then shrink-to-fit to the shorter dimension, rather than leaving
818 // huge letterboxes; the user can then bump-scroll around the desktop.
819 // 4. If the overall scale factor is fractionally over an integer factor
820 // then reduce it to that integer factor, to avoid blurring.
821
822 // All calculations are performed in device pixels.
823 var clientWidth = clientSizeDips.width * clientPixelRatio;
824 var clientHeight = clientSizeDips.height * clientPixelRatio;
825
826 // 1. Determine a "natural" size at which to display the desktop.
827 var scale = 1.0;
828
829 // Determine the effective host device pixel ratio.
830 // Note that we round up or down to the closest integer pixel ratio.
831 var hostPixelRatioX = Math.round(desktopDpi.x / 96);
832 var hostPixelRatioY = Math.round(desktopDpi.y / 96);
833 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
834
835 // Allow up-scaling to account for DPI.
836 scale = Math.max(scale, clientPixelRatio / hostPixelRatio);
837
838 // Allow some or all of the up-scaling to be cancelled by the desktopScale.
839 if (desktopScale > 1.0) {
840 scale = Math.max(1.0, scale / desktopScale);
841 }
842
843 // 2. If the host is still much smaller than the client, then up-scale to
844 // avoid wasting space, but only by an integer factor, to avoid blurring.
845 if (desktopSize.width * scale <= clientWidth &&
846 desktopSize.height * scale <= clientHeight) {
847 var scaleX = Math.floor(clientWidth / desktopSize.width);
848 var scaleY = Math.floor(clientHeight / desktopSize.height);
849 scale = Math.min(scaleX, scaleY);
850 base.debug.assert(scale >= 1.0);
851 }
852
853 // 3. Apply shrink-to-fit, if configured.
854 if (shrinkToFit) {
855 var scaleFitWidth = Math.min(scale, clientWidth / desktopSize.width);
856 var scaleFitHeight = Math.min(scale, clientHeight / desktopSize.height);
857 scale = Math.min(scaleFitHeight, scaleFitWidth);
858
859 // If we're running full-screen then try to handle common side-by-side
860 // multi-monitor combinations more intelligently.
861 if (isFullscreen) {
862 // If the host has two monitors each the same size as the client then
863 // scale-to-fit will have the desktop occupy only 50% of the client area,
864 // in which case it would be preferable to down-scale less and let the
865 // user bump-scroll around ("scale-and-pan").
866 // Triggering scale-and-pan if less than 65% of the client area would be
867 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to
868 // a (2x1280)x1024 host nicely.
869 // Note that we don't need to account for scrollbars while fullscreen.
870 if (scale <= scaleFitHeight * 0.65) {
871 scale = scaleFitHeight;
872 }
873 if (scale <= scaleFitWidth * 0.65) {
874 scale = scaleFitWidth;
875 }
876 }
877 }
878
879 // 4. Avoid blurring for close-to-integer up-scaling factors.
880 if (scale > 1.0) {
881 var scaleBlurriness = scale / Math.floor(scale);
882 if (scaleBlurriness < 1.1) {
883 scale = Math.floor(scale);
884 }
885 }
886
887 // Return the necessary plugin dimensions in DIPs.
888 scale = scale / clientPixelRatio;
889 var pluginWidth = Math.round(desktopSize.width * scale);
890 var pluginHeight = Math.round(desktopSize.height * scale);
891 return { width: pluginWidth, height: pluginHeight };
892 }
893
894 /**
895 * Called when the window or desktop size or the scaling settings change,
896 * to set the scroll-bar visibility.
897 *
898 * TODO(jamiewalch): crbug.com/252796: Remove this once crbug.com/240772 is
899 * fixed.
900 */
901 remoting.DesktopConnectedView.prototype.updateScrollbarVisibility = function() {
902 var scroller = document.getElementById('scroller');
903 if (!scroller) {
904 return;
905 }
906
907 var needsVerticalScroll = false;
908 var needsHorizontalScroll = false;
909 if (!this.shrinkToFit_) {
910 // Determine whether or not horizontal or vertical scrollbars are
911 // required, taking into account their width.
912 var clientArea = this.getClientArea_();
913 needsVerticalScroll = clientArea.height < this.plugin_.getDesktopHeight();
914 needsHorizontalScroll = clientArea.width < this.plugin_.getDesktopWidth();
915 var kScrollBarWidth = 16;
916 if (needsHorizontalScroll && !needsVerticalScroll) {
917 needsVerticalScroll =
918 clientArea.height - kScrollBarWidth < this.plugin_.getDesktopHeight();
919 } else if (!needsHorizontalScroll && needsVerticalScroll) {
920 needsHorizontalScroll =
921 clientArea.width - kScrollBarWidth < this.plugin_.getDesktopWidth();
922 }
923 }
924
925 if (needsHorizontalScroll) {
926 scroller.classList.remove('no-horizontal-scroll');
927 } else {
928 scroller.classList.add('no-horizontal-scroll');
929 }
930 if (needsVerticalScroll) {
931 scroller.classList.remove('no-vertical-scroll');
932 } else {
933 scroller.classList.add('no-vertical-scroll');
934 }
935 };
936
937 /**
938 * Sets and stores the key remapping setting for the current host. 411 * Sets and stores the key remapping setting for the current host.
939 * 412 *
940 * @param {string} remappings Comma separated list of key remappings. 413 * @param {string} remappings Comma separated list of key remappings.
941 */ 414 */
942 remoting.DesktopConnectedView.prototype.setRemapKeys = function(remappings) { 415 remoting.DesktopConnectedView.prototype.setRemapKeys = function(remappings) {
943 // Cancel any existing remappings and apply the new ones. 416 // Cancel any existing remappings and apply the new ones.
944 this.applyRemapKeys_(false); 417 this.applyRemapKeys_(false);
945 this.remapKeys_ = remappings; 418 this.remapKeys_ = remappings;
946 this.applyRemapKeys_(true); 419 this.applyRemapKeys_(true);
947 420
948 // Save the new remapping setting. 421 // Save the new remapping setting.
949 var options = {}; 422 this.host_.options().remapKeys = this.remapKeys_;
950 options[remoting.DesktopConnectedView.KEY_REMAP_KEYS] = this.remapKeys_; 423 this.host_.options().save();
951 remoting.HostSettings.save(this.hostId_, options);
952 }; 424 };
953 425
954 /** 426 /**
955 * Applies the configured key remappings to the session, or resets them. 427 * Applies the configured key remappings to the session, or resets them.
956 * 428 *
957 * @param {boolean} apply True to apply remappings, false to cancel them. 429 * @param {boolean} apply True to apply remappings, false to cancel them.
958 */ 430 */
959 remoting.DesktopConnectedView.prototype.applyRemapKeys_ = function(apply) { 431 remoting.DesktopConnectedView.prototype.applyRemapKeys_ = function(apply) {
960 var remapKeys = this.remapKeys_; 432 var remapKeys = this.remapKeys_;
961 if (remapKeys == '') { 433 if (remapKeys == '') {
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after
1088 * @param {Object} message The parsed extension message data. 560 * @param {Object} message The parsed extension message data.
1089 * @return {boolean} True if the message was recognized, false otherwise. 561 * @return {boolean} True if the message was recognized, false otherwise.
1090 */ 562 */
1091 remoting.DesktopConnectedView.prototype.handleExtensionMessage = 563 remoting.DesktopConnectedView.prototype.handleExtensionMessage =
1092 function(type, message) { 564 function(type, message) {
1093 if (this.videoFrameRecorder_) { 565 if (this.videoFrameRecorder_) {
1094 return this.videoFrameRecorder_.handleMessage(type, message); 566 return this.videoFrameRecorder_.handleMessage(type, message);
1095 } 567 }
1096 return false; 568 return false;
1097 }; 569 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698