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