var remoting = remoting || {};
/**
+ * @param {HTMLElement} container Container element for the client view.
+ * @param {string} hostDisplayName A human-readable name for the host.
* @param {string} accessCode The IT2Me access code. Blank for Me2Me.
* @param {function(boolean, function(string): void): void} fetchPin
* Called by Me2Me connections when a PIN needs to be obtained
* @param {string} clientPairedSecret For paired Me2Me connections, the
* paired secret for this client, as issued by the host.
* @constructor
+ * @extends {base.EventSource}
*/
-remoting.ClientSession = function(accessCode, fetchPin, fetchThirdPartyToken,
- authenticationMethods,
- hostId, hostJid, hostPublicKey, mode,
- clientPairingId, clientPairedSecret) {
+remoting.ClientSession = function(container, hostDisplayName, accessCode,
+ fetchPin, fetchThirdPartyToken,
+ authenticationMethods, hostId, hostJid,
+ hostPublicKey, mode, clientPairingId,
+ clientPairedSecret) {
/** @private */
this.state_ = remoting.ClientSession.State.CREATED;
/** @private */
this.error_ = remoting.Error.NONE;
+ /** @type {HTMLElement}
+ * @private */
+ this.container_ = container;
+
+ /** @private */
+ this.hostDisplayName_ = hostDisplayName;
/** @private */
this.hostJid_ = hostJid;
/** @private */
/** @private */
this.hasReceivedFrame_ = false;
this.logToServer = new remoting.LogToServer();
- /** @type {?function(remoting.ClientSession.State,
- remoting.ClientSession.State):void} */
- this.onStateChange_ = null;
/** @type {number?} @private */
this.notifyClientResolutionTimer_ = null;
/** @type {number?} @private */
this.bumpScrollTimer_ = null;
+ // Bump-scroll test variables. Override to use a fake value for the width
+ // and height of the client plugin so that bump-scrolling can be tested
+ // without relying on the actual size of the host desktop.
+ /** @type {number} @private */
+ this.pluginWidthForBumpScrollTesting = 0;
+ /** @type {number} @private */
+ this.pluginHeightForBumpScrollTesting = 0;
+
/**
* Allow host-offline error reporting to be suppressed in situations where it
* would not be useful, for example, when using a cached host JID.
/** @private */
this.callPluginGotFocus_ = this.pluginGotFocus_.bind(this);
/** @private */
- this.callSetScreenMode_ = this.onSetScreenMode_.bind(this);
+ this.callToggleFullScreen_ = remoting.fullscreen.toggle.bind(
+ remoting.fullscreen);
/** @private */
- this.callToggleFullScreen_ = this.toggleFullScreen_.bind(this);
+ this.callOnFullScreenChanged_ = this.onFullScreenChanged_.bind(this)
/** @private */
this.screenOptionsMenu_ = new remoting.MenuButton(
document.getElementById('send-keys-menu')
);
+ /** @type {HTMLMediaElement} @private */
+ this.video_ = null;
+
+ /** @type {Element} @private */
+ this.mouseCursorOverlay_ =
+ this.container_.querySelector('.mouse-cursor-overlay');
+
+ /** @type {Element} */
+ var img = this.mouseCursorOverlay_;
+ /** @param {Event} event @private */
+ this.updateMouseCursorPosition_ = function(event) {
+ img.style.top = event.y + 'px';
+ img.style.left = event.x + 'px';
+ };
+
/** @type {HTMLElement} @private */
this.resizeToClientButton_ =
document.getElementById('screen-resize-to-client');
/** @type {HTMLElement} @private */
this.fullScreenButton_ = document.getElementById('toggle-full-screen');
+ /** @type {remoting.GnubbyAuthHandler} @private */
+ this.gnubbyAuthHandler_ = null;
+
if (this.mode_ == remoting.ClientSession.Mode.IT2ME) {
// Resize-to-client is not supported for IT2Me hosts.
this.resizeToClientButton_.hidden = true;
} else {
this.resizeToClientButton_.hidden = false;
- this.resizeToClientButton_.addEventListener(
- 'click', this.callSetScreenMode_, false);
}
- this.shrinkToFitButton_.addEventListener(
- 'click', this.callSetScreenMode_, false);
this.fullScreenButton_.addEventListener(
'click', this.callToggleFullScreen_, false);
+ this.defineEvents(Object.keys(remoting.ClientSession.Events));
+};
+
+base.extend(remoting.ClientSession, base.EventSource);
+
+/** @enum {string} */
+remoting.ClientSession.Events = {
+ stateChanged: 'stateChanged',
+ videoChannelStateChanged: 'videoChannelStateChanged',
+ bumpScrollStarted: 'bumpScrollStarted',
+ bumpScrollStopped: 'bumpScrollStopped'
};
/**
- * @param {?function(remoting.ClientSession.State,
- remoting.ClientSession.State):void} onStateChange
- * The callback to invoke when the session changes state.
+ * Get host display name.
+ *
+ * @return {string}
*/
-remoting.ClientSession.prototype.setOnStateChange = function(onStateChange) {
- this.onStateChange_ = onStateChange;
+remoting.ClientSession.prototype.getHostDisplayName = function() {
+ return this.hostDisplayName_;
};
/**
if (!this.shrinkToFit_) {
// Determine whether or not horizontal or vertical scrollbars are
// required, taking into account their width.
- needsVerticalScroll = window.innerHeight < this.plugin_.desktopHeight;
- needsHorizontalScroll = window.innerWidth < this.plugin_.desktopWidth;
+ var clientArea = this.getClientArea_();
+ needsVerticalScroll = clientArea.height < this.plugin_.desktopHeight;
+ needsHorizontalScroll = clientArea.width < this.plugin_.desktopWidth;
var kScrollBarWidth = 16;
if (needsHorizontalScroll && !needsVerticalScroll) {
needsVerticalScroll =
- window.innerHeight - kScrollBarWidth < this.plugin_.desktopHeight;
+ clientArea.height - kScrollBarWidth < this.plugin_.desktopHeight;
} else if (!needsHorizontalScroll && needsVerticalScroll) {
needsHorizontalScroll =
- window.innerWidth - kScrollBarWidth < this.plugin_.desktopWidth;
+ clientArea.width - kScrollBarWidth < this.plugin_.desktopWidth;
}
}
- var htmlNode = /** @type {HTMLElement} */ (document.body.parentNode);
+ var scroller = document.getElementById('scroller');
if (needsHorizontalScroll) {
- htmlNode.classList.remove('no-horizontal-scroll');
+ scroller.classList.remove('no-horizontal-scroll');
} else {
- htmlNode.classList.add('no-horizontal-scroll');
+ scroller.classList.add('no-horizontal-scroll');
}
if (needsVerticalScroll) {
- htmlNode.classList.remove('no-vertical-scroll');
+ scroller.classList.remove('no-vertical-scroll');
} else {
- htmlNode.classList.add('no-vertical-scroll');
+ scroller.classList.add('no-vertical-scroll');
}
};
+/**
+ * @return {boolean} True if shrink-to-fit is enabled; false otherwise.
+ */
+remoting.ClientSession.prototype.getShrinkToFit = function() {
+ return this.shrinkToFit_;
+};
+
+/**
+ * @return {boolean} True if resize-to-client is enabled; false otherwise.
+ */
+remoting.ClientSession.prototype.getResizeToClient = function() {
+ return this.resizeToClient_;
+};
+
// Note that the positive values in both of these enums are copied directly
// from chromoting_scriptable_object.h and must be kept in sync. The negative
// values represent state transitions that occur within the web-app that have
FAILED: 5
};
+/**
+ * @param {string} state The state name.
+ * @return {remoting.ClientSession.State} The session state enum value.
+ */
+remoting.ClientSession.State.fromString = function(state) {
+ if (!remoting.ClientSession.State.hasOwnProperty(state)) {
+ throw "Invalid ClientSession.State: " + state;
+ }
+ return remoting.ClientSession.State[state];
+};
+
+/**
+ @constructor
+ @param {remoting.ClientSession.State} current
+ @param {remoting.ClientSession.State} previous
+*/
+remoting.ClientSession.StateEvent = function(current, previous) {
+ /** @type {remoting.ClientSession.State} */
+ this.previous = previous
+
+ /** @type {remoting.ClientSession.State} */
+ this.current = current;
+};
+
/** @enum {number} */
remoting.ClientSession.ConnectionError = {
UNKNOWN: -1,
HOST_OVERLOAD: 5
};
+/**
+ * @param {string} error The connection error name.
+ * @return {remoting.ClientSession.ConnectionError} The connection error enum.
+ */
+remoting.ClientSession.ConnectionError.fromString = function(error) {
+ if (!remoting.ClientSession.ConnectionError.hasOwnProperty(error)) {
+ console.error('Unexpected ClientSession.ConnectionError string: ', error);
+ return remoting.ClientSession.ConnectionError.UNKNOWN;
+ }
+ return remoting.ClientSession.ConnectionError[error];
+}
+
// The mode of this session.
/** @enum {number} */
remoting.ClientSession.Mode = {
remoting.ClientSession.KEY_SHRINK_TO_FIT = 'shrinkToFit';
/**
- * The id of the client plugin
- *
- * @const
- */
-remoting.ClientSession.prototype.PLUGIN_ID = 'session-client-plugin';
-
-/**
* Set of capabilities for which hasCapability_() can be used to test.
*
* @enum {string}
// resolution to the host once connection has been established. See
// this.plugin_.notifyClientResolution().
SEND_INITIAL_RESOLUTION: 'sendInitialResolution',
- RATE_LIMIT_RESIZE_REQUESTS: 'rateLimitResizeRequests'
+ RATE_LIMIT_RESIZE_REQUESTS: 'rateLimitResizeRequests',
+ VIDEO_RECORDER: 'videoRecorder'
};
/**
};
/**
- * @param {Element} container The element to add the plugin to.
- * @param {string} id Id to use for the plugin element .
- * @return {remoting.ClientPlugin} Create plugin object for the locally
- * installed plugin.
- */
-remoting.ClientSession.prototype.createClientPlugin_ = function(container, id) {
- var plugin = /** @type {remoting.ViewerPlugin} */
- document.createElement('embed');
-
- plugin.id = id;
- plugin.src = 'about://none';
- plugin.type = 'application/vnd.chromium.remoting-viewer';
- plugin.width = 0;
- plugin.height = 0;
- plugin.tabIndex = 0; // Required, otherwise focus() doesn't work.
- container.appendChild(plugin);
-
- return new remoting.ClientPlugin(plugin);
-};
-
-/**
* Callback function called when the plugin element gets focus.
*/
remoting.ClientSession.prototype.pluginGotFocus_ = function() {
this.plugin_.releaseAllKeys();
if (this.plugin_.element()) {
// Focus should stay on the element, not (for example) the toolbar.
- this.plugin_.element().focus();
+ // Due to crbug.com/246335, we can't restore the focus immediately,
+ // otherwise the plugin gets confused about whether or not it has focus.
+ window.setTimeout(
+ this.plugin_.element().focus.bind(this.plugin_.element()), 0);
}
}
};
/**
* Adds <embed> element to |container| and readies the sesion object.
*
- * @param {Element} container The element to add the plugin to.
+ * @param {function(string, string):boolean} onExtensionMessage The handler for
+ * protocol extension messages. Returns true if a message is recognized;
+ * false otherwise.
*/
remoting.ClientSession.prototype.createPluginAndConnect =
- function(container) {
- this.plugin_ = this.createClientPlugin_(container, this.PLUGIN_ID);
+ function(onExtensionMessage) {
+ this.plugin_ = new remoting.ClientPlugin(
+ this.container_.querySelector('.client-plugin-container'),
+ onExtensionMessage);
remoting.HostSettings.load(this.hostId_,
this.onHostSettingsLoaded_.bind(this));
};
*/
remoting.ClientSession.prototype.resetWithError_ = function(error) {
this.plugin_.cleanup();
- delete this.plugin_;
+ this.plugin_ = null;
this.error_ = error;
this.setState_(remoting.ClientSession.State.FAILED);
}
this.applyRemapKeys_(true);
}
+
+ // Enable MediaSource-based rendering on Chrome 37 and above.
+ var chromeVersionMajor =
+ parseInt((remoting.getChromeVersion() || '0').split('.')[0], 10);
+ if (chromeVersionMajor >= 37 &&
+ this.plugin_.hasFeature(
+ remoting.ClientPlugin.Feature.MEDIA_SOURCE_RENDERING)) {
+ this.video_ = /** @type {HTMLMediaElement} */(
+ this.container_.querySelector('video'));
+ // Make sure that the <video> element is hidden until we get the first
+ // frame.
+ this.video_.style.width = '0px';
+ this.video_.style.height = '0px';
+
+ var renderer = new remoting.MediaSourceRenderer(this.video_);
+ this.plugin_.enableMediaSourceRendering(renderer);
+ this.container_.classList.add('mediasource-rendering');
+ } else {
+ this.container_.classList.remove('mediasource-rendering');
+ }
+
/** @param {string} msg The IQ stanza to send. */
this.plugin_.onOutgoingIqHandler = this.sendIq_.bind(this);
/** @param {string} msg The message to log. */
this.plugin_.onDebugMessageHandler = function(msg) {
- console.log('plugin: ' + msg);
+ console.log('plugin: ' + msg.trimRight());
};
this.plugin_.onConnectionStatusUpdateHandler =
this.onConnectionStatusUpdate_.bind(this);
- this.plugin_.onConnectionReadyHandler =
- this.onConnectionReady_.bind(this);
+ this.plugin_.onConnectionReadyHandler = this.onConnectionReady_.bind(this);
this.plugin_.onDesktopSizeUpdateHandler =
this.onDesktopSizeChanged_.bind(this);
- this.plugin_.onSetCapabilitiesHandler =
- this.onSetCapabilities_.bind(this);
+ this.plugin_.onSetCapabilitiesHandler = this.onSetCapabilities_.bind(this);
+ this.plugin_.onGnubbyAuthHandler = this.processGnubbyAuthMessage_.bind(this);
+ this.plugin_.updateMouseCursorImage = this.updateMouseCursorImage_.bind(this);
this.initiateConnection_();
};
}
// Delete event handlers that aren't relevent when not connected.
- this.resizeToClientButton_.removeEventListener(
- 'click', this.callSetScreenMode_, false);
- this.shrinkToFitButton_.removeEventListener(
- 'click', this.callSetScreenMode_, false);
this.fullScreenButton_.removeEventListener(
'click', this.callToggleFullScreen_, false);
- // In case the user had selected full-screen mode, cancel it now.
- document.webkitCancelFullScreen();
+ // Leave full-screen mode, and stop listening for related events.
+ var listener = this.callOnFullScreenChanged_;
+ remoting.fullscreen.syncWithMaximize(false);
+ remoting.fullscreen.activate(
+ false,
+ function() {
+ remoting.fullscreen.removeListener(listener);
+ });
+ if (remoting.windowFrame) {
+ remoting.windowFrame.setClientSession(null);
+ } else {
+ remoting.toolbar.setClientSession(null);
+ }
+
+ // Remove mediasource-rendering class from the container - this will also
+ // hide the <video> element.
+ this.container_.classList.remove('mediasource-rendering');
+
+ this.container_.removeEventListener('mousemove',
+ this.updateMouseCursorPosition_,
+ true);
+};
+
+/**
+ * Disconnect the current session with a particular |error|. The session will
+ * raise a |stateChanged| event in response to it. The caller should then call
+ * |cleanup| to remove and destroy the <embed> element.
+ *
+ * @param {remoting.Error} error The reason for the disconnection. Use
+ * remoting.Error.NONE if there is no error.
+ * @return {void} Nothing.
+ */
+remoting.ClientSession.prototype.disconnect = function(error) {
+ var state = (error == remoting.Error.NONE) ?
+ remoting.ClientSession.State.CLOSED :
+ remoting.ClientSession.State.FAILED;
+
+ // The plugin won't send a state change notification, so we explicitly log
+ // the fact that the connection has closed.
+ this.logToServer.logClientSessionStateChange(state, error, this.mode_);
+ this.error_ = error;
+ this.setState_(state);
};
/**
* Deletes the <embed> element from the container and disconnects.
*
- * @param {boolean} isUserInitiated True for user-initiated disconnects, False
- * for disconnects due to connection failures.
* @return {void} Nothing.
*/
-remoting.ClientSession.prototype.disconnect = function(isUserInitiated) {
- if (isUserInitiated) {
- // The plugin won't send a state change notification, so we explicitly log
- // the fact that the connection has closed.
- this.logToServer.logClientSessionStateChange(
- remoting.ClientSession.State.CLOSED, remoting.Error.NONE, this.mode_);
- }
+remoting.ClientSession.prototype.cleanup = function() {
remoting.wcsSandbox.setOnIq(null);
this.sendIq_(
'<cli:iq ' +
* @return {void} Nothing.
*/
remoting.ClientSession.prototype.sendCtrlAltDel = function() {
+ console.log('Sending Ctrl-Alt-Del.');
this.sendKeyCombination_([0x0700e0, 0x0700e2, 0x07004c]);
}
* @return {void} Nothing.
*/
remoting.ClientSession.prototype.sendPrintScreen = function() {
+ console.log('Sending Print Screen.');
this.sendKeyCombination_([0x070046]);
}
remapKeys = '0x0700e4>0x0700e7';
}
+ if (remapKeys == '') {
+ return;
+ }
+
var remappings = remapKeys.split(',');
for (var i = 0; i < remappings.length; ++i) {
var keyCodes = remappings[i].split('>');
}
/**
- * Callback for the two "screen mode" related menu items: Resize desktop to
- * fit and Shrink to fit.
- *
- * @param {Event} event The click event indicating which mode was selected.
- * @return {void} Nothing.
- * @private
- */
-remoting.ClientSession.prototype.onSetScreenMode_ = function(event) {
- var shrinkToFit = this.shrinkToFit_;
- var resizeToClient = this.resizeToClient_;
- if (event.target == this.shrinkToFitButton_) {
- shrinkToFit = !shrinkToFit;
- }
- if (event.target == this.resizeToClientButton_) {
- resizeToClient = !resizeToClient;
- }
- this.setScreenMode_(shrinkToFit, resizeToClient);
-};
-
-/**
* Set the shrink-to-fit and resize-to-client flags and save them if this is
* a Me2Me connection.
*
* false to disable this behaviour for subsequent window resizes--the
* current host desktop size is not restored in this case.
* @return {void} Nothing.
- * @private
*/
-remoting.ClientSession.prototype.setScreenMode_ =
+remoting.ClientSession.prototype.setScreenMode =
function(shrinkToFit, resizeToClient) {
if (resizeToClient && !this.resizeToClient_) {
- this.plugin_.notifyClientResolution(window.innerWidth,
- window.innerHeight,
- window.devicePixelRatio);
+ var clientArea = this.getClientArea_();
+ this.plugin_.notifyClientResolution(clientArea.width,
+ clientArea.height,
+ window.devicePixelRatio);
}
// If enabling shrink, reset bump-scroll offsets.
this.updateDimensions();
if (needsScrollReset) {
- this.scroll_(0, 0);
+ this.resetScroll_();
}
}
this.setFocusHandlers_();
this.onDesktopSizeChanged_();
if (this.resizeToClient_) {
- this.plugin_.notifyClientResolution(window.innerWidth,
- window.innerHeight,
- window.devicePixelRatio);
+ var clientArea = this.getClientArea_();
+ this.plugin_.notifyClientResolution(clientArea.width,
+ clientArea.height,
+ window.devicePixelRatio);
+ }
+ // Activate full-screen related UX.
+ remoting.fullscreen.addListener(this.callOnFullScreenChanged_);
+ remoting.fullscreen.syncWithMaximize(true);
+ if (remoting.windowFrame) {
+ remoting.windowFrame.setClientSession(this);
+ } else {
+ remoting.toolbar.setClientSession(this);
}
+
+ this.container_.addEventListener('mousemove',
+ this.updateMouseCursorPosition_,
+ true);
+
} else if (status == remoting.ClientSession.State.FAILED) {
switch (error) {
case remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE:
*/
remoting.ClientSession.prototype.onConnectionReady_ = function(ready) {
if (!ready) {
- this.plugin_.element().classList.add("session-client-inactive");
+ this.container_.classList.add('session-client-inactive');
} else {
- this.plugin_.element().classList.remove("session-client-inactive");
+ this.container_.classList.remove('session-client-inactive');
}
+
+ this.raiseEvent(remoting.ClientSession.Events.videoChannelStateChanged,
+ ready);
};
/**
this.capabilities_ = capabilities;
if (this.hasCapability_(
remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION)) {
- this.plugin_.notifyClientResolution(window.innerWidth,
- window.innerHeight,
- window.devicePixelRatio);
+ var clientArea = this.getClientArea_();
+ this.plugin_.notifyClientResolution(clientArea.width,
+ clientArea.height,
+ window.devicePixelRatio);
}
};
state = remoting.ClientSession.State.CONNECTION_DROPPED;
}
this.logToServer.logClientSessionStateChange(state, this.error_, this.mode_);
- if (this.onStateChange_) {
- this.onStateChange_(oldState, newState);
+ if (this.state_ == remoting.ClientSession.State.CONNECTED) {
+ this.createGnubbyAuthHandler_();
}
+
+ this.raiseEvent(remoting.ClientSession.Events.stateChanged,
+ new remoting.ClientSession.StateEvent(newState, oldState)
+ );
};
/**
remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS)) {
kResizeRateLimitMs = 250;
}
+ var clientArea = this.getClientArea_();
this.notifyClientResolutionTimer_ = window.setTimeout(
this.plugin_.notifyClientResolution.bind(this.plugin_,
- window.innerWidth,
- window.innerHeight,
+ clientArea.width,
+ clientArea.height,
window.devicePixelRatio),
kResizeRateLimitMs);
}
// If bump-scrolling is enabled, adjust the plugin margins to fully utilize
// the new window area.
- this.scroll_(0, 0);
+ this.resetScroll_();
this.updateScrollbarVisibility();
};
*/
remoting.ClientSession.prototype.pauseVideo = function(pause) {
if (this.plugin_) {
- this.plugin_.pauseVideo(pause)
+ this.plugin_.pauseVideo(pause);
}
-}
+};
/**
* Requests that the host pause or resume audio.
return;
}
- var windowWidth = window.innerWidth;
- var windowHeight = window.innerHeight;
+ var clientArea = this.getClientArea_();
var desktopWidth = this.plugin_.desktopWidth;
var desktopHeight = this.plugin_.desktopHeight;
if (this.shrinkToFit_) {
// Reduce the scale, if necessary, to fit the whole desktop in the window.
- var scaleFitWidth = Math.min(scale, 1.0 * windowWidth / desktopWidth);
- var scaleFitHeight = Math.min(scale, 1.0 * windowHeight / desktopHeight);
+ var scaleFitWidth = Math.min(scale, 1.0 * clientArea.width / desktopWidth);
+ var scaleFitHeight =
+ Math.min(scale, 1.0 * clientArea.height / desktopHeight);
scale = Math.min(scaleFitHeight, scaleFitWidth);
// If we're running full-screen then try to handle common side-by-side
// multi-monitor combinations more intelligently.
- if (document.webkitIsFullScreen) {
+ if (remoting.fullscreen.isActive()) {
// If the host has two monitors each the same size as the client then
// scale-to-fit will have the desktop occupy only 50% of the client area,
// in which case it would be preferable to down-scale less and let the
}
}
- var pluginWidth = desktopWidth * scale;
- var pluginHeight = desktopHeight * scale;
+ var pluginWidth = Math.round(desktopWidth * scale);
+ var pluginHeight = Math.round(desktopHeight * scale);
+
+ if (this.video_) {
+ this.video_.style.width = pluginWidth + 'px';
+ this.video_.style.height = pluginHeight + 'px';
+ }
// Resize the plugin if necessary.
// TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089).
- this.plugin_.element().width = pluginWidth;
- this.plugin_.element().height = pluginHeight;
+ this.plugin_.element().style.width = pluginWidth + 'px';
+ this.plugin_.element().style.height = pluginHeight + 'px';
// Position the container.
// Note that clientWidth/Height take into account scrollbars.
};
/**
- * Toggles between full-screen and windowed mode.
- * @return {void} Nothing.
+ * Called when the full-screen status has changed, either via the
+ * remoting.Fullscreen class, or via a system event such as the Escape key
+ *
+ * @param {boolean} fullscreen True if the app is entering full-screen mode;
+ * false if it is leaving it.
* @private
*/
-remoting.ClientSession.prototype.toggleFullScreen_ = function() {
- var htmlNode = /** @type {HTMLElement} */ (document.body.parentNode);
- if (document.webkitIsFullScreen) {
- document.webkitCancelFullScreen();
- this.enableBumpScroll_(false);
- htmlNode.classList.remove('full-screen');
- } else {
- document.body.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
- // Don't enable bump scrolling immediately because it can result in
- // onMouseMove firing before the webkitIsFullScreen property can be
- // read safely (crbug.com/132180).
- window.setTimeout(this.enableBumpScroll_.bind(this, true), 0);
+remoting.ClientSession.prototype.onFullScreenChanged_ = function (fullscreen) {
+ var htmlNode = /** @type {HTMLElement} */ (document.documentElement);
+ this.enableBumpScroll_(fullscreen);
+ if (fullscreen) {
htmlNode.classList.add('full-screen');
+ } else {
+ htmlNode.classList.remove('full-screen');
}
};
remoting.MenuButton.select(this.resizeToClientButton_, this.resizeToClient_);
remoting.MenuButton.select(this.shrinkToFitButton_, this.shrinkToFit_);
remoting.MenuButton.select(this.fullScreenButton_,
- document.webkitIsFullScreen);
+ remoting.fullscreen.isActive());
};
/**
* @private
*/
remoting.ClientSession.prototype.scroll_ = function(dx, dy) {
- var plugin = this.plugin_.element();
- var style = plugin.style;
-
/**
* Helper function for x- and y-scrolling
* @param {number|string} curr The current margin, eg. "10px".
var result = (curr ? parseFloat(curr) : 0) - delta;
result = Math.min(0, Math.max(minMargin, result));
stop.stop = (result == 0 || result == minMargin);
- return result + "px";
+ return result + 'px';
};
+ var plugin = this.plugin_.element();
+ var style = this.container_.style;
+
var stopX = { stop: false };
- style.marginLeft = adjustMargin(style.marginLeft, dx,
- window.innerWidth, plugin.width, stopX);
+ var clientArea = this.getClientArea_();
+ style.marginLeft = adjustMargin(style.marginLeft, dx, clientArea.width,
+ this.pluginWidthForBumpScrollTesting || plugin.clientWidth, stopX);
+
var stopY = { stop: false };
- style.marginTop = adjustMargin(style.marginTop, dy,
- window.innerHeight, plugin.height, stopY);
+ style.marginTop = adjustMargin(
+ style.marginTop, dy, clientArea.height,
+ this.pluginHeightForBumpScrollTesting || plugin.clientHeight, stopY);
return stopX.stop && stopY.stop;
-}
+};
+
+remoting.ClientSession.prototype.resetScroll_ = function() {
+ this.container_.style.marginTop = '0px';
+ this.container_.style.marginLeft = '0px';
+};
/**
* Enable or disable bump-scrolling. When disabling bump scrolling, also reset
* @param {boolean} enable True to enable bump-scrolling, false to disable it.
*/
remoting.ClientSession.prototype.enableBumpScroll_ = function(enable) {
+ var element = /*@type{HTMLElement} */ document.documentElement;
if (enable) {
/** @type {null|function(Event):void} */
this.onMouseMoveRef_ = this.onMouseMove_.bind(this);
- this.plugin_.element().addEventListener(
- 'mousemove', this.onMouseMoveRef_, false);
+ element.addEventListener('mousemove', this.onMouseMoveRef_, false);
} else {
- this.plugin_.element().removeEventListener(
- 'mousemove', this.onMouseMoveRef_, false);
+ element.removeEventListener('mousemove', this.onMouseMoveRef_, false);
this.onMouseMoveRef_ = null;
- this.plugin_.element().style.marginLeft = 0;
- this.plugin_.element().style.marginTop = 0;
+ this.resetScroll_();
}
};
window.clearTimeout(this.bumpScrollTimer_);
this.bumpScrollTimer_ = null;
}
- // It's possible to leave content full-screen mode without using the Screen
- // Options menu, so we disable bump scrolling as soon as we detect this.
- if (!document.webkitIsFullScreen) {
- this.enableBumpScroll_(false);
- }
/**
* Compute the scroll speed based on how close the mouse is to the edge.
return 0;
};
- var dx = computeDelta(event.x, window.innerWidth);
- var dy = computeDelta(event.y, window.innerHeight);
+ var clientArea = this.getClientArea_();
+ var dx = computeDelta(event.x, clientArea.width);
+ var dy = computeDelta(event.y, clientArea.height);
if (dx != 0 || dy != 0) {
+ this.raiseEvent(remoting.ClientSession.Events.bumpScrollStarted);
/** @type {remoting.ClientSession} */
var that = this;
/**
/** @type {number} */
var timeout = 10;
var lateAdjustment = 1 + (now - expected) / timeout;
- if (!that.scroll_(lateAdjustment * dx, lateAdjustment * dy)) {
+ if (that.scroll_(lateAdjustment * dx, lateAdjustment * dy)) {
+ that.raiseEvent(remoting.ClientSession.Events.bumpScrollStopped);
+ } else {
that.bumpScrollTimer_ = window.setTimeout(
function() { repeatScroll(now + timeout); },
timeout);
remoting.ClientSession.prototype.sendClipboardItem = function(mimeType, item) {
if (!this.plugin_)
return;
- this.plugin_.sendClipboardItem(mimeType, item)
+ this.plugin_.sendClipboardItem(mimeType, item);
+};
+
+/**
+ * Send a gnubby-auth extension message to the host.
+ * @param {Object} data The gnubby-auth message data.
+ */
+remoting.ClientSession.prototype.sendGnubbyAuthMessage = function(data) {
+ if (!this.plugin_)
+ return;
+ this.plugin_.sendClientMessage('gnubby-auth', JSON.stringify(data));
+};
+
+/**
+ * Process a remote gnubby auth request.
+ * @param {string} data Remote gnubby request data.
+ * @private
+ */
+remoting.ClientSession.prototype.processGnubbyAuthMessage_ = function(data) {
+ if (this.gnubbyAuthHandler_) {
+ try {
+ this.gnubbyAuthHandler_.onMessage(data);
+ } catch (err) {
+ console.error('Failed to process gnubby message: ',
+ /** @type {*} */ (err));
+ }
+ } else {
+ console.error('Received unexpected gnubby message');
+ }
+};
+
+/**
+ * Create a gnubby auth handler and inform the host that gnubby auth is
+ * supported.
+ * @private
+ */
+remoting.ClientSession.prototype.createGnubbyAuthHandler_ = function() {
+ if (this.mode_ == remoting.ClientSession.Mode.ME2ME) {
+ this.gnubbyAuthHandler_ = new remoting.GnubbyAuthHandler(this);
+ // TODO(psj): Move to more generic capabilities mechanism.
+ this.sendGnubbyAuthMessage({'type': 'control', 'option': 'auth-v1'});
+ }
+};
+
+/**
+ * @return {{width: number, height: number}} The height of the window's client
+ * area. This differs between apps v1 and apps v2 due to the custom window
+ * borders used by the latter.
+ * @private
+ */
+remoting.ClientSession.prototype.getClientArea_ = function() {
+ return remoting.windowFrame ?
+ remoting.windowFrame.getClientArea() :
+ { 'width': window.innerWidth, 'height': window.innerHeight };
+};
+
+/**
+ * @param {string} url
+ * @param {number} hotspotX
+ * @param {number} hotspotY
+ */
+remoting.ClientSession.prototype.updateMouseCursorImage_ =
+ function(url, hotspotX, hotspotY) {
+ this.mouseCursorOverlay_.hidden = !url;
+ if (url) {
+ this.mouseCursorOverlay_.style.marginLeft = '-' + hotspotX + 'px';
+ this.mouseCursorOverlay_.style.marginTop = '-' + hotspotY + 'px';
+ this.mouseCursorOverlay_.src = url;
+ }
+};
+
+/**
+ * @return {{top: number, left:number}} The top-left corner of the plugin.
+ */
+remoting.ClientSession.prototype.getPluginPositionForTesting = function() {
+ var style = this.container_.style;
+ return {
+ top: parseFloat(style.marginTop),
+ left: parseFloat(style.marginLeft)
+ };
};