summaryrefslogtreecommitdiffstats
path: root/remoting/webapp
diff options
context:
space:
mode:
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