summaryrefslogtreecommitdiffstats
path: root/remoting/webapp
diff options
context:
space:
mode:
authorjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-14 06:10:16 +0000
committerjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-14 06:10:16 +0000
commitf733f2680c94fad910ce9686aa2645bc90ebc160 (patch)
tree1b43738a891b6713022c2d648b91c33733955d6a /remoting/webapp
parent79e70d0fe37628603f793961269e74b9c6117e8b (diff)
downloadchromium_src-f733f2680c94fad910ce9686aa2645bc90ebc160.zip
chromium_src-f733f2680c94fad910ce9686aa2645bc90ebc160.tar.gz
chromium_src-f733f2680c94fad910ce9686aa2645bc90ebc160.tar.bz2
Implement apps v2 custom window frame.
The default apps v2 container is pretty basic. We want to provide something that looks prettier and also implements some functionality specific to our use-case: * When connected to a host, a disconnect icon is added to the window controls (it's therefore no longer needed in the tool-bar). * When connected to a host, maximize == full-screen. * In full-screen mode, the window controls are still accessible, but are auto-hidden near the top-left corner (but not obscuring it, since it's often a hot-spot on the server). * For touch-screen devices with no concept of hover, clicking the "stub" will also reveal the controls. There should be no change to the v1 UX, but I don't plan on landing this CL before the M36 branch point, just in case. BUG=134213 Review URL: https://codereview.chromium.org/265393005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@270342 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/webapp')
-rw-r--r--remoting/webapp/background.js3
-rw-r--r--remoting/webapp/client_session.js86
-rw-r--r--remoting/webapp/event_handlers.js6
-rw-r--r--remoting/webapp/fullscreen_v2.js2
-rw-r--r--remoting/webapp/html/template_main.html92
-rw-r--r--remoting/webapp/html/toolbar.html12
-rw-r--r--remoting/webapp/html/window_frame.html29
-rw-r--r--remoting/webapp/js_proto/chrome_proto.js1
-rw-r--r--remoting/webapp/main.css4
-rw-r--r--remoting/webapp/remoting.js2
-rw-r--r--remoting/webapp/toolbar.css2
-rw-r--r--remoting/webapp/window_frame.css174
-rw-r--r--remoting/webapp/window_frame.js175
13 files changed, 508 insertions, 80 deletions
diff --git a/remoting/webapp/background.js b/remoting/webapp/background.js
index 118b7e57..1976b91 100644
--- a/remoting/webapp/background.js
+++ b/remoting/webapp/background.js
@@ -8,7 +8,8 @@ var kNewWindowId = 'new-window';
function createWindow() {
chrome.app.window.create('main.html', {
'width': 800,
- 'height': 600
+ 'height': 600,
+ 'frame': 'none'
});
};
diff --git a/remoting/webapp/client_session.js b/remoting/webapp/client_session.js
index 7b0d80e..23c8814 100644
--- a/remoting/webapp/client_session.js
+++ b/remoting/webapp/client_session.js
@@ -176,28 +176,29 @@ remoting.ClientSession.prototype.updateScrollbarVisibility = function() {
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.documentElement);
+ 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');
}
};
@@ -568,6 +569,9 @@ remoting.ClientSession.prototype.removePlugin = function() {
function() {
remoting.fullscreen.removeListener(listener);
});
+ if (remoting.windowFrame) {
+ remoting.windowFrame.setConnected(false);
+ }
// Remove mediasource-rendering class from video-contained - this will also
// hide the <video> element.
@@ -766,9 +770,10 @@ remoting.ClientSession.prototype.onSetScreenMode_ = function(event) {
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.
@@ -953,13 +958,18 @@ remoting.ClientSession.prototype.onConnectionStatusUpdate_ =
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);
}
- // Start listening for full-screen related events.
+ // Activate full-screen related UX.
remoting.fullscreen.addListener(this.callOnFullScreenChanged_);
remoting.fullscreen.syncWithMaximize(true);
+ if (remoting.windowFrame) {
+ remoting.windowFrame.setConnected(true);
+ }
+
} else if (status == remoting.ClientSession.State.FAILED) {
switch (error) {
case remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE:
@@ -1019,9 +1029,10 @@ remoting.ClientSession.prototype.onSetCapabilities_ = function(capabilities) {
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);
}
};
@@ -1080,10 +1091,11 @@ remoting.ClientSession.prototype.onResize = function() {
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);
}
@@ -1148,8 +1160,7 @@ remoting.ClientSession.prototype.updateDimensions = function() {
return;
}
- var windowWidth = window.innerWidth;
- var windowHeight = window.innerHeight;
+ var clientArea = this.getClientArea_();
var desktopWidth = this.plugin_.desktopWidth;
var desktopHeight = this.plugin_.desktopHeight;
@@ -1174,8 +1185,9 @@ remoting.ClientSession.prototype.updateDimensions = function() {
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
@@ -1334,12 +1346,13 @@ remoting.ClientSession.prototype.scroll_ = function(dx, dy) {
};
var stopX = { stop: false };
+ var clientArea = this.getClientArea_();
style.marginLeft = adjustMargin(style.marginLeft, dx,
- window.innerWidth, plugin.clientWidth, stopX);
+ clientArea.width, plugin.clientWidth, stopX);
var stopY = { stop: false };
- style.marginTop = adjustMargin(style.marginTop, dy,
- window.innerHeight, plugin.clientHeight, stopY);
+ style.marginTop = adjustMargin(
+ style.marginTop, dy, clientArea.height, plugin.clientHeight, stopY);
return stopX.stop && stopY.stop;
};
@@ -1396,8 +1409,9 @@ remoting.ClientSession.prototype.onMouseMove_ = function(event) {
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) {
/** @type {remoting.ClientSession} */
@@ -1475,3 +1489,15 @@ remoting.ClientSession.prototype.createGnubbyAuthHandler_ = function() {
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 };
+} \ No newline at end of file
diff --git a/remoting/webapp/event_handlers.js b/remoting/webapp/event_handlers.js
index bcbc201..e864c2d 100644
--- a/remoting/webapp/event_handlers.js
+++ b/remoting/webapp/event_handlers.js
@@ -101,6 +101,12 @@ function onLoad() {
remoting.init();
window.addEventListener('resize', remoting.onResize, false);
+ // When a window goes full-screen, a resize event is triggered, but the
+ // Fullscreen.isActive call is not guaranteed to return true until the
+ // full-screen event is triggered. In apps v2, the size of the window's
+ // client area is calculated differently in full-screen mode, so register
+ // for both events.
+ remoting.fullscreen.addListener(remoting.onResize);
if (!remoting.isAppsV2) {
window.addEventListener('beforeunload', remoting.promptClose, false);
window.addEventListener('unload', remoting.disconnect, false);
diff --git a/remoting/webapp/fullscreen_v2.js b/remoting/webapp/fullscreen_v2.js
index 4e19f34..3aaaab16 100644
--- a/remoting/webapp/fullscreen_v2.js
+++ b/remoting/webapp/fullscreen_v2.js
@@ -102,6 +102,7 @@ remoting.FullscreenAppsV2.prototype.syncWithMaximize = function(sync) {
remoting.FullscreenAppsV2.prototype.onFullscreened_ = function() {
this.notifyCallbacksOnRestore_ = true;
this.eventSource_.raiseEvent(this.kEventName_, true);
+ document.body.classList.add('fullscreen');
};
remoting.FullscreenAppsV2.prototype.onMaximized_ = function() {
@@ -111,6 +112,7 @@ remoting.FullscreenAppsV2.prototype.onMaximized_ = function() {
};
remoting.FullscreenAppsV2.prototype.onRestored_ = function() {
+ document.body.classList.remove('fullscreen');
if (this.hookingWindowEvents_) {
this.activate(false);
}
diff --git a/remoting/webapp/html/template_main.html b/remoting/webapp/html/template_main.html
index e690e3f..78e738e 100644
--- a/remoting/webapp/html/template_main.html
+++ b/remoting/webapp/html/template_main.html
@@ -5,7 +5,7 @@ Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
-<html class="scrollable full-height">
+<html class="full-height">
<head>
<meta charset="utf-8">
<link rel="icon" type="image/png" href="chromoting16.webp">
@@ -14,13 +14,16 @@ found in the LICENSE file.
<link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="menu_button.css">
<link rel="stylesheet" href="toolbar.css">
+ <link rel="stylesheet" href="window_frame.css">
<meta-include type="javascript"/>
<title i18n-content="PRODUCT_NAME"></title>
</head>
- <body class="full-height">
+ <body class="full-height inner-border-for-apps-v2">
+
+ <meta-include src="webapp/html/window_frame.html"/>
<!-- loading-mode is initially visible, but becomes hidden as soon as an
AppMode is selected by remoting.init. All other divs are initially
@@ -34,62 +37,65 @@ found in the LICENSE file.
<iframe id="wcs-sandbox" src="wcs_sandbox.html" hidden></iframe>
- <div class="inset" data-ui-mode="home" hidden>
+ <div id="scroller">
+ <div class="inset" data-ui-mode="home" hidden>
- <meta-include src="webapp/html/ui_header.html"/>
- <meta-include src="webapp/html/butterbar.html"/>
- <meta-include src="webapp/html/ui_it2me.html"/>
- <meta-include src="webapp/html/ui_me2me.html"/>
+ <meta-include src="webapp/html/ui_header.html"/>
+ <meta-include src="webapp/html/butterbar.html"/>
+ <meta-include src="webapp/html/ui_it2me.html"/>
+ <meta-include src="webapp/html/ui_me2me.html"/>
- </div>
+ </div> <!-- inset -->
- <meta-include src="webapp/html/dialog_auth.html"/>
+ <meta-include src="webapp/html/dialog_auth.html"/>
- <div class="dialog-screen"
- data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-setup home.token-refresh-failed home.manage-pairings"
- hidden></div>
+ <div class="dialog-screen"
+ data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-setup home.token-refresh-failed home.manage-pairings"
+ hidden></div>
- <div class="dialog-container"
- data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-install home.host-setup home.token-refresh-failed home.manage-pairings"
- hidden>
+ <div class="dialog-container"
+ data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-install home.host-setup home.token-refresh-failed home.manage-pairings"
+ hidden>
- <meta-include src="webapp/html/dialog_token_refresh_failed.html"/>
- <meta-include src="webapp/html/dialog_host_setup.html"/>
- <meta-include src="webapp/html/dialog_host_install.html"/>
- <meta-include src="webapp/html/dialog_host.html"/>
+ <meta-include src="webapp/html/dialog_token_refresh_failed.html"/>
+ <meta-include src="webapp/html/dialog_host_setup.html"/>
+ <meta-include src="webapp/html/dialog_host_install.html"/>
+ <meta-include src="webapp/html/dialog_host.html"/>
- <div id="client-dialog"
- class="kd-modaldialog"
- data-ui-mode="home.client">
+ <div id="client-dialog"
+ class="kd-modaldialog"
+ data-ui-mode="home.client">
- <meta-include src="webapp/html/dialog_client_unconnected.html"/>
- <meta-include src="webapp/html/dialog_client_connecting.html"/>
- <meta-include src="webapp/html/dialog_client_host_needs_upgrade.html"/>
- <meta-include src="webapp/html/dialog_client_pin_prompt.html"/>
- <meta-include src="webapp/html/dialog_client_third_party_auth.html"/>
- <meta-include src="webapp/html/dialog_client_connect_failed.html"/>
- <meta-include src="webapp/html/dialog_client_session_finished.html"/>
+ <meta-include src="webapp/html/dialog_client_unconnected.html"/>
+ <meta-include src="webapp/html/dialog_client_connecting.html"/>
+ <meta-include src="webapp/html/dialog_client_host_needs_upgrade.html"/>
+ <meta-include src="webapp/html/dialog_client_pin_prompt.html"/>
+ <meta-include src="webapp/html/dialog_client_third_party_auth.html"/>
+ <meta-include src="webapp/html/dialog_client_connect_failed.html"/>
+ <meta-include src="webapp/html/dialog_client_session_finished.html"/>
- </div>
+ </div>
- <meta-include src="webapp/html/dialog_connection_history.html"/>
- <meta-include src="webapp/html/dialog_confirm_host_delete.html"/>
- <meta-include src="webapp/html/dialog_manage_pairings.html"/>
+ <meta-include src="webapp/html/dialog_connection_history.html"/>
+ <meta-include src="webapp/html/dialog_confirm_host_delete.html"/>
+ <meta-include src="webapp/html/dialog_manage_pairings.html"/>
- </div> <!-- dialog-container -->
+ </div> <!-- dialog-container -->
- <div id="session-mode"
- data-ui-mode="in-session home.client"
- class="full-height"
- hidden>
+ <div id="session-mode"
+ data-ui-mode="in-session home.client"
+ class="full-height"
+ hidden>
- <meta-include src="webapp/html/toolbar.html"/>
- <meta-include src="webapp/html/client_plugin.html"/>
+ <meta-include src="webapp/html/toolbar.html"/>
+ <meta-include src="webapp/html/client_plugin.html"/>
- </div>
+ </div> <!-- session-mode -->
+
+ <div id="statistics" dir="ltr" class="selectable" hidden>
+ </div>
- <div id="statistics" dir="ltr" class="selectable" hidden>
- </div>
+ </div> <!-- scroller ->
</body>
</html>
diff --git a/remoting/webapp/html/toolbar.html b/remoting/webapp/html/toolbar.html
index eeead15..74fecbd 100644
--- a/remoting/webapp/html/toolbar.html
+++ b/remoting/webapp/html/toolbar.html
@@ -23,7 +23,8 @@ found in the LICENSE file.
<button id="toolbar-disconnect"
type="button"
- i18n-content="DISCONNECT_MYSELF_BUTTON">
+ i18n-content="DISCONNECT_MYSELF_BUTTON"
+ class="apps-v1-only">
</button>
<span class="menu-button" id="send-keys-menu">
@@ -47,9 +48,12 @@ found in the LICENSE file.
<ul>
<li id="screen-resize-to-client"
i18n-content="RESIZE_TO_CLIENT"></li>
- <li id="screen-shrink-to-fit" i18n-content="SHRINK_TO_FIT"></li>
- <li class="menu-separator"></li>
- <li id="toggle-full-screen" i18n-content="FULL_SCREEN"></li>
+ <li id="screen-shrink-to-fit"
+ i18n-content="SHRINK_TO_FIT"></li>
+ <li class="menu-separator apps-v1-only"></li>
+ <li id="toggle-full-screen"
+ i18n-content="FULL_SCREEN"
+ class="apps-v1-only"></li>
</ul>
</span>
diff --git a/remoting/webapp/html/window_frame.html b/remoting/webapp/html/window_frame.html
new file mode 100644
index 0000000..3b24d48
--- /dev/null
+++ b/remoting/webapp/html/window_frame.html
@@ -0,0 +1,29 @@
+<!--
+Copyright 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<div id="title-bar" class="title-bar apps-v2-only">
+ <span class="window-title">&nbsp;</span>
+ <span class="window-controls-hover-target">
+ <div class="window-controls">
+ <span i18n-title="DISCONNECT_MYSELF_BUTTON"
+ class="window-control window-disconnect">
+ <img src="icon_disconnect.webp">
+ </span>
+ <span i18n-title="MINIMIZE_WINDOW"
+ class="window-control window-minimize">
+ <img src="icon_minimize.webp">
+ </span>
+ <span i18n-title="MAXIMIZE_WINDOW"
+ class="window-control window-maximize-restore">
+ <img src="icon_maximize_restore.webp">
+ </span>
+ <span i18n-title="CLOSE_WINDOW"
+ class="window-control window-close">
+ <img src="icon_close.webp">
+ </span>
+ </div>
+ <div class="window-controls-stub">&nbsp;</div>
+ </span>
+</div>
diff --git a/remoting/webapp/js_proto/chrome_proto.js b/remoting/webapp/js_proto/chrome_proto.js
index 273910e..62ddc12 100644
--- a/remoting/webapp/js_proto/chrome_proto.js
+++ b/remoting/webapp/js_proto/chrome_proto.js
@@ -287,6 +287,7 @@ var AppWindow = function() {
AppWindow.prototype.close = function() {};
AppWindow.prototype.drawAttention = function() {};
+AppWindow.prototype.maximize = function() {};
AppWindow.prototype.minimize = function() {};
AppWindow.prototype.restore = function() {};
AppWindow.prototype.fullscreen = function() {};
diff --git a/remoting/webapp/main.css b/remoting/webapp/main.css
index ec616a8..ea741ed 100644
--- a/remoting/webapp/main.css
+++ b/remoting/webapp/main.css
@@ -20,6 +20,7 @@ tfoot, thead, tr, th, td, button {
.inset {
padding: 20px 20px 0 20px;
+ position: relative;
}
body {
@@ -30,6 +31,7 @@ body {
direction: __MSG_@@bidi_dir__;
}
+
/*
* The "app-v2" class is added to the <html> node by remoting.init if it's
* running as a V2 app.
@@ -621,7 +623,7 @@ button {
}
.dialog-screen {
- position: fixed;
+ position: absolute;
top: 0;
left: 0;
width: 100%;
diff --git a/remoting/webapp/remoting.js b/remoting/webapp/remoting.js
index 893edfd..adc26a7 100644
--- a/remoting/webapp/remoting.js
+++ b/remoting/webapp/remoting.js
@@ -69,6 +69,8 @@ remoting.init = function() {
if (remoting.isAppsV2) {
remoting.identity = new remoting.Identity(consentRequired_);
remoting.fullscreen = new remoting.FullscreenAppsV2();
+ remoting.windowFrame = new remoting.WindowFrame(
+ document.getElementById('title-bar'));
} else {
remoting.oauth2 = new remoting.OAuth2();
if (!remoting.oauth2.isAuthenticated()) {
diff --git a/remoting/webapp/toolbar.css b/remoting/webapp/toolbar.css
index 3726a29..a039227 100644
--- a/remoting/webapp/toolbar.css
+++ b/remoting/webapp/toolbar.css
@@ -4,7 +4,7 @@
*/
.toolbar-container {
- position: fixed;
+ position: absolute;
top: -48px;
width: 640px;
-webkit-transition: top 0.15s ease;
diff --git a/remoting/webapp/window_frame.css b/remoting/webapp/window_frame.css
new file mode 100644
index 0000000..a079ec1
--- /dev/null
+++ b/remoting/webapp/window_frame.css
@@ -0,0 +1,174 @@
+/* Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+html.apps-v2,
+html.apps-v2 body {
+ height: 100%;
+ width: 100%;
+}
+
+html.apps-v2 body:not(.fullscreen) {
+ border: 1px solid gray; /* This is the window border. */
+}
+
+html.apps-v2 .title-bar {
+ border-bottom: 1px solid gray;
+ z-index: 100;
+}
+
+.window-title,
+.window-controls-hover-target {
+ height: 32px;
+ line-height: 32px;
+ font-size: 16px;
+ background-color: #c4c4c4;
+}
+
+.title-bar .window-title {
+ padding-__MSG_@@bidi_start_edge__: 12px;
+ width: 100%;
+ display: inline-block;
+ -webkit-app-region: drag;
+}
+
+.window-controls-hover-target {
+ -webkit-app-region: no-drag;
+ position: fixed;
+ top: 1px;
+ __MSG_@@bidi_end_edge__: 1px;
+}
+
+.window-controls-hover-target {
+ display: table;
+}
+
+.window-controls-hover-target > div:first-child {
+ display: table-row;
+}
+
+.window-control {
+ height: 32px;
+ width: 32px;
+ text-align: center;
+ display: inline-block;
+ border-__MSG_@@bidi_start_edge__: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+.window-control:hover {
+ background-color: #d5d5d5;
+}
+
+.window-control:active {
+ background-color: #a6a6a6;
+}
+
+.window-control > img {
+ margin-bottom: -2px;
+}
+
+.window-controls-stub {
+ display: none;
+ -webkit-column-span: all;
+ line-height: 3px;
+ background: url("drag.webp");
+ border-top: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#scroller {
+ height: 100%;
+ width: 100%;
+ overflow: auto;
+ position: relative;
+}
+
+html.apps-v2 #scroller {
+ height: calc(100% - 32px); /** Allow space for the title-bar */
+}
+
+/* Add an etched border to the window controls, title bar and stub */
+.title-bar,
+.window-control,
+.window-controls-stub {
+ position: relative;
+}
+
+.title-bar:after,
+.window-control:after,
+.window-controls-stub:after {
+ content: "";
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ border-left: 1px solid rgba(255, 255, 255, 0.2);
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
+ pointer-events: none;
+}
+
+
+/* When connected to a host, the Disconnect button is displayed. */
+body:not(.connected) .window-disconnect {
+ display: none;
+}
+
+
+/*
+ * When in full-screen mode, significant changes are made:
+ * - The scroll-bars are removed.
+ * - The window controls have a border (so the left-border of the first button
+ * is not needed).
+ * - The title-bar (and its bottom border) are not displayed.
+ * - The stub is visible.
+ * - The window controls gain transition effects for position and opacity and
+ * auto-hide behind the top edge of the screen.
+ * - A border is added to the window controls to ensure they stand out against
+ * any desktop.
+ * - The window border is removed.
+ */
+
+html.apps-v2 body.fullscreen #scroller {
+ height: 100%;
+ overflow: hidden;
+}
+
+body.fullscreen .window-controls-hover-target {
+ border: 1px solid #a6a6a6;
+}
+
+body.fullscreen .window-control:first-child {
+ border-__MSG_@@bidi_start_edge__: none;
+}
+
+body.fullscreen .window-title {
+ display: none;
+}
+
+body.fullscreen .title-bar {
+ border-bottom: none;
+}
+
+body.fullscreen .window-controls-stub {
+ display: table-cell;
+}
+
+body.fullscreen .window-controls-hover-target {
+ transition-property: opacity, box-shadow, top;
+ transition-duration: 0.3s;
+ opacity: 0.7;
+ top: -33px;
+ __MSG_@@bidi_end_edge__: 8px;
+}
+
+body.fullscreen .window-controls-hover-target:hover,
+body.fullscreen .window-controls-hover-target.opened {
+ top: -4px;
+ opacity: 1.0;
+ box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.5);
+}
+
+.fullscreen .window-controls-hover-target.opened .window-controls-stub {
+ background-color: #a6a6a6;
+}
diff --git a/remoting/webapp/window_frame.js b/remoting/webapp/window_frame.js
new file mode 100644
index 0000000..b4f3d74
--- /dev/null
+++ b/remoting/webapp/window_frame.js
@@ -0,0 +1,175 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * Apps v2 custom title bar implementation
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * @param {HTMLElement} titleBar The root node of the title-bar DOM hierarchy.
+ * @constructor
+ */
+remoting.WindowFrame = function(titleBar) {
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.isConnected_ = false;
+
+ /**
+ * @type {HTMLElement}
+ * @private
+ */
+ this.titleBar_ = titleBar;
+
+ /**
+ * @type {HTMLElement}
+ * @private
+ */
+ this.hoverTarget_ = /** @type {HTMLElement} */
+ (titleBar.querySelector('.window-controls-hover-target'));
+ base.debug.assert(this.hoverTarget_ != null);
+
+ /**
+ * @type {HTMLElement}
+ * @private
+ */
+ this.maximizeRestoreControl_ = /** @type {HTMLElement} */
+ (titleBar.querySelector('.window-maximize-restore'));
+ base.debug.assert(this.maximizeRestoreControl_ != null);
+
+ /**
+ * @type {Array.<{cls:string, fn: function()}>}
+ */
+ var handlers = [
+ { cls: 'window-disconnect', fn: this.disconnectSession_.bind(this) },
+ { cls: 'window-maximize-restore',
+ fn: this.maximizeOrRestoreWindow_.bind(this) },
+ { cls: 'window-minimize', fn: this.minimizeWindow_.bind(this) },
+ { cls: 'window-close', fn: window.close.bind(window) },
+ { cls: 'window-controls-stub', fn: this.toggleWindowControls_.bind(this) }
+ ];
+ for (var i = 0; i < handlers.length; ++i) {
+ var element = titleBar.querySelector('.' + handlers[i].cls);
+ base.debug.assert(element != null);
+ element.addEventListener('click', handlers[i].fn, false);
+ }
+
+ // Ensure that tool-tips are always correct.
+ this.updateMaximizeOrRestoreIconTitle_();
+ chrome.app.window.current().onMaximized.addListener(
+ this.updateMaximizeOrRestoreIconTitle_.bind(this));
+ chrome.app.window.current().onRestored.addListener(
+ this.updateMaximizeOrRestoreIconTitle_.bind(this));
+ chrome.app.window.current().onFullscreened.addListener(
+ this.updateMaximizeOrRestoreIconTitle_.bind(this));
+};
+
+/**
+ * @param {boolean} isConnected True if there is a connection active.
+ */
+remoting.WindowFrame.prototype.setConnected = function(isConnected) {
+ this.isConnected_ = isConnected;
+ if (this.isConnected_) {
+ document.body.classList.add('connected');
+ } else {
+ document.body.classList.remove('connected');
+ }
+ this.updateMaximizeOrRestoreIconTitle_();
+};
+
+/**
+ * @return {{width: number, height: number}} The size of the window, ignoring
+ * the title-bar and window borders, if visible.
+ */
+remoting.WindowFrame.prototype.getClientArea = function() {
+ if (chrome.app.window.current().isFullscreen()) {
+ return { 'height': window.innerHeight, 'width': window.innerWidth };
+ } else {
+ var kBorderWidth = 1;
+ var titleHeight = this.titleBar_.clientHeight;
+ return {
+ 'height': window.innerHeight - titleHeight - 2 * kBorderWidth,
+ 'width': window.innerWidth - 2 * kBorderWidth
+ };
+ }
+};
+
+/**
+ * @private
+ */
+remoting.WindowFrame.prototype.disconnectSession_ = function() {
+ // When the user disconnects, exit full-screen mode. This should not be
+ // necessary, as we do the same thing in client_session.js when the plugin
+ // is removed. However, there seems to be a bug in chrome.AppWindow.restore
+ // that causes it to get stuck in full-screen mode without this.
+ if (chrome.app.window.current().isFullscreen()) {
+ chrome.app.window.current().restore();
+ chrome.app.window.current().restore();
+ }
+ remoting.disconnect();
+};
+
+/**
+ * @private
+ */
+remoting.WindowFrame.prototype.maximizeOrRestoreWindow_ = function() {
+ /** @type {boolean} */
+ var restore =
+ chrome.app.window.current().isFullscreen() ||
+ chrome.app.window.current().isMaximized();
+ if (restore) {
+ // Restore twice: once to exit full-screen and once to exit maximized.
+ // If the app is not full-screen, or went full-screen without first
+ // being maximized, then the second restore has no effect.
+ chrome.app.window.current().restore();
+ chrome.app.window.current().restore();
+ } else {
+ chrome.app.window.current().maximize();
+ }
+};
+
+/**
+ * @private
+ */
+remoting.WindowFrame.prototype.minimizeWindow_ = function() {
+ chrome.app.window.current().minimize();
+};
+
+/**
+ * @private
+ */
+remoting.WindowFrame.prototype.toggleWindowControls_ = function() {
+ this.hoverTarget_.classList.toggle('opened');
+};
+
+/**
+ * Update the tool-top for the maximize/full-screen/restore icon to reflect
+ * its current behaviour.
+ *
+ * @private
+ */
+remoting.WindowFrame.prototype.updateMaximizeOrRestoreIconTitle_ = function() {
+ /** @type {string} */
+ var tag = '';
+ if (chrome.app.window.current().isFullscreen()) {
+ tag = /*i18n-content*/'EXIT_FULL_SCREEN';
+ } else if (chrome.app.window.current().isMaximized()) {
+ tag = /*i18n-content*/'RESTORE_WINDOW';
+ } else if (this.isConnected_) {
+ tag = /*i18n-content*/'FULL_SCREEN';
+ } else {
+ tag = /*i18n-content*/'MAXIMIZE_WINDOW';
+ }
+ this.maximizeRestoreControl_.title = l10n.getTranslationOrError(tag);
+};
+
+/** @type {remoting.WindowFrame} */
+remoting.windowFrame = null; \ No newline at end of file