summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorkelvinp <kelvinp@chromium.org>2014-08-25 11:26:19 -0700
committerCommit bot <commit-bot@chromium.org>2014-08-25 18:33:52 +0000
commit8df98f3682b0c3e6ceba6e51dd4ed74396c15ef0 (patch)
tree3e07b49cd18544a1ee49e30860490b4e8824cb81 /remoting
parentfa309edf9033cf74f2aa971cb4284d6849b7bc1a (diff)
downloadchromium_src-8df98f3682b0c3e6ceba6e51dd4ed74396c15ef0.zip
chromium_src-8df98f3682b0c3e6ceba6e51dd4ed74396c15ef0.tar.gz
chromium_src-8df98f3682b0c3e6ceba6e51dd4ed74396c15ef0.tar.bz2
Show MessageWindow from the background page
In Hangout Remote Desktop, we need to show message windows from the background page for authentication. This CL introduces the MessageWindow class, which supports showing of both confirm dialogs and message boxes. BUG=405628 Review URL: https://codereview.chromium.org/493813002 Cr-Commit-Position: refs/heads/master@{#291719}
Diffstat (limited to 'remoting')
-rw-r--r--remoting/remoting_webapp_files.gypi6
-rw-r--r--remoting/webapp/background/message_window.js163
-rw-r--r--remoting/webapp/background/message_window_helper.js271
-rw-r--r--remoting/webapp/background/message_window_manager.js111
-rw-r--r--remoting/webapp/html/message_window.html27
-rw-r--r--remoting/webapp/js_proto/chrome_proto.js3
-rw-r--r--remoting/webapp/message_window.css13
7 files changed, 594 insertions, 0 deletions
diff --git a/remoting/remoting_webapp_files.gypi b/remoting/remoting_webapp_files.gypi
index 64bc593..95a6411 100644
--- a/remoting/remoting_webapp_files.gypi
+++ b/remoting/remoting_webapp_files.gypi
@@ -197,6 +197,8 @@
'webapp/background/it2me_helpee_channel.js',
'webapp/background/it2me_helper_channel.js',
'webapp/background/it2me_service.js',
+ 'webapp/background/message_window_helper.js',
+ 'webapp/background/message_window_manager.js',
],
# The JavaScript files required by wcs_sandbox.html.
@@ -211,6 +213,8 @@
# JS files for main.html.
'<@(remoting_webapp_main_html_js_files)',
'<@(remoting_webapp_background_js_files)',
+ # JS files for message_window.html
+ 'webapp/background/message_window.js',
# JS files for wcs_sandbox.html.
# Use r_w_js_wcs_sandbox_files instead of r_w_wcs_sandbox_html_js_files
# so that we don't double include error.js and plugin_settings.js.
@@ -240,8 +244,10 @@
'resources/reload.webp',
'resources/tick.webp',
'webapp/connection_stats.css',
+ 'webapp/html/message_window.html',
'webapp/main.css',
'webapp/menu_button.css',
+ 'webapp/message_window.css',
'webapp/open_sans.css',
'webapp/open_sans.woff',
'webapp/scale-to-fit.webp',
diff --git a/remoting/webapp/background/message_window.js b/remoting/webapp/background/message_window.js
new file mode 100644
index 0000000..fee4c9b0
--- /dev/null
+++ b/remoting/webapp/background/message_window.js
@@ -0,0 +1,163 @@
+// 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.
+
+'use strict';
+
+/**
+ * @constructor
+ */
+function MessageWindowImpl() {
+ /**
+ * Used to prevent multiple responses due to the closeWindow handler.
+ *
+ * @type {boolean}
+ * @private
+ */
+ this.sentReply_ = false;
+
+ window.addEventListener('message', this.onMessage_.bind(this), false);
+};
+
+/**
+ * @param {Window} parentWindow The id of the window that showed the message.
+ * @param {string} messageId The identifier of the message, as supplied by the
+ * parent.
+ * @param {number} result 0 if window was closed without pressing a button;
+ * otherwise the index of the button pressed (e.g., 1 = primary).
+ * @private
+ */
+MessageWindowImpl.prototype.sendReply_ = function(
+ parentWindow, messageId, result) {
+ // Only forward the first reply that we receive.
+ if (!this.sentReply_) {
+ var message = {
+ command: 'messageWindowResult',
+ id: messageId,
+ result: result
+ };
+ parentWindow.postMessage(message, '*');
+ this.sentReply_ = true;
+ } else {
+ // Make sure that the reply we're ignoring is from the window close.
+ base.debug.assert(result == 0);
+ }
+};
+
+/**
+ * Size the window to its content vertically.
+ * @private
+ */
+MessageWindowImpl.prototype.updateSize_ = function() {
+ var borderY = window.outerHeight - window.innerHeight;
+ window.resizeTo(window.outerWidth, document.body.clientHeight + borderY);
+};
+
+/**
+ * Initializes the button with the label and the click handler.
+ * Hides the button if the label is null or undefined.
+ *
+ * @param{HTMLElement} button
+ * @param{?string} label
+ * @param{Function} clickHandler
+ * @private
+ */
+MessageWindowImpl.prototype.initButton_ =
+ function(button, label, clickHandler) {
+ if (label) {
+ button.innerText = label;
+ button.addEventListener('click', clickHandler, false);
+ }
+ button.hidden = !Boolean(label);
+};
+
+/**
+ * Event-handler callback, invoked when the parent window supplies the
+ * message content.
+ *
+ * @param{Event} event
+ * @private
+ */
+MessageWindowImpl.prototype.onMessage_ = function(event) {
+ switch (event.data['command']) {
+ case 'show':
+ // Validate the message.
+ var messageId = /** @type {number} */ (event.data['id']);
+ var title = /** @type {string} */ (event.data['title']);
+ var message = /** @type {string} */ (event.data['message']);
+ var infobox = /** @type {string} */ (event.data['infobox']);
+ var buttonLabel = /** @type {string} */ (event.data['buttonLabel']);
+ /** @type {string} */
+ var cancelButtonLabel = (event.data['cancelButtonLabel']);
+ var showSpinner = /** @type {boolean} */ (event.data['showSpinner']);
+ if (typeof(messageId) != 'number' ||
+ typeof(title) != 'string' ||
+ typeof(message) != 'string' ||
+ typeof(infobox) != 'string' ||
+ typeof(buttonLabel) != 'string' ||
+ typeof(showSpinner) != 'boolean') {
+ console.log('Bad show message:', event.data);
+ break;
+ }
+
+ // Set the dialog text.
+ var button = document.getElementById('button-primary');
+ var cancelButton = document.getElementById('button-secondary');
+ var messageDiv = document.getElementById('message');
+ var infoboxDiv = document.getElementById('infobox');
+ document.getElementById('title').innerText = title;
+ document.querySelector('title').innerText = title;
+ messageDiv.innerText = message;
+ if (showSpinner) {
+ messageDiv.classList.add('waiting');
+ messageDiv.classList.add('prominent');
+ }
+ if (infobox != '') {
+ infoboxDiv.innerText = infobox;
+ } else {
+ infoboxDiv.hidden = true;
+ }
+
+ this.initButton_(
+ button,
+ buttonLabel,
+ this.sendReply_.bind(this, event.source, messageId, 1));
+
+ this.initButton_(
+ cancelButton,
+ cancelButtonLabel,
+ this.sendReply_.bind(this, event.source, messageId, 0));
+
+ var buttonToFocus = (cancelButtonLabel) ? cancelButton : button;
+ buttonToFocus.focus();
+
+ // Add a close handler in case the window is closed without clicking one
+ // of the buttons. This will send a 0 as the result.
+ // Note that when a button is pressed, this will result in sendReply_
+ // being called multiple times (once for the button, once for close).
+ chrome.app.window.current().onClosed.addListener(
+ this.sendReply_.bind(this, event.source, messageId, 0));
+
+ this.updateSize_();
+ chrome.app.window.current().show();
+ break;
+
+ case 'update_message':
+ var message = /** @type {string} */ (event.data['message']);
+ if (typeof(message) != 'string') {
+ console.log('Bad update_message message:', event.data);
+ break;
+ }
+
+ var messageDiv = document.getElementById('message');
+ messageDiv.innerText = message;
+
+ this.updateSize_();
+ break;
+
+ default:
+ console.error('Unexpected message:', event.data);
+ }
+};
+
+var messageWindow = new MessageWindowImpl();
diff --git a/remoting/webapp/background/message_window_helper.js b/remoting/webapp/background/message_window_helper.js
new file mode 100644
index 0000000..cbc379e
--- /dev/null
+++ b/remoting/webapp/background/message_window_helper.js
@@ -0,0 +1,271 @@
+// 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.
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * Create a new message window.
+ *
+ * @param {Object} options Message window create options
+ * @constructor
+ */
+remoting.MessageWindow = function(options) {
+ var title = /** @type {string} */ (options.title);
+ var message = /** @type {string} */ (options.message);
+ var okButtonLabel = /** @type {string} */ (options.buttonLabel);
+ var cancelButtonLabel = /** @type {string} */ (options.cancelButtonLabel);
+ var onResult = /** @type {function(number):void} */(options.onResult);
+ /** @type {number} */
+ var duration = 0;
+ if (/** @type {number?} */(options.duration)) {
+ duration = /** @type {number} */(options.duration);
+ }
+ /** @type {string} */
+ var infobox = '';
+ if (/** @type {string?} */(options.infobox)) {
+ infobox = /** @type {string} */(options.infobox);
+ }
+ var onTimeout = /** @type {?function():void} */ (options.onTimeout);
+
+ /** @type {number} */
+ this.id_ = remoting.MessageWindowManager.addMessageWindow(this);
+
+ /** @type {?function(number):void} */
+ this.onResult_ = onResult;
+
+ /** @type {Window} */
+ this.window_ = null;
+
+ /** @type {number} */
+ this.timer_ = 0;
+
+ /** @type {Array.<function():void>} */
+ this.pendingWindowOperations_ = [];
+
+ /**
+ * Callback to call when the timeout expires.
+ * @type {?function():void}
+ */
+ this.onTimeout_ = onTimeout;
+
+ var message_struct = {
+ command: 'show',
+ id: this.id_,
+ title: title,
+ message: message,
+ infobox: infobox,
+ buttonLabel: okButtonLabel,
+ cancelButtonLabel: cancelButtonLabel,
+ showSpinner: (duration != 0)
+ };
+
+ var windowAttributes = {
+ bounds: {
+ width: 400,
+ height: 100
+ },
+ resizable: false
+ };
+
+ /** @type {remoting.MessageWindow} */
+ var that = this;
+
+ /** @param {AppWindow} appWindow */
+ var onCreate = function(appWindow) {
+ that.setWindow_(/** @type {Window} */(appWindow.contentWindow));
+ var onLoad = function() {
+ appWindow.contentWindow.postMessage(message_struct, '*');
+ };
+ appWindow.contentWindow.addEventListener('load', onLoad, false);
+ };
+
+ chrome.app.window.create('message_window.html', windowAttributes, onCreate);
+
+ if (duration != 0) {
+ this.timer_ = window.setTimeout(this.onTimeoutHandler_.bind(this),
+ duration);
+ }
+};
+
+/**
+ * Called when the timer runs out. This in turn calls the window's
+ * timeout handler (if any).
+ */
+remoting.MessageWindow.prototype.onTimeoutHandler_ = function() {
+ this.close();
+ if (this.onTimeout_) {
+ this.onTimeout_();
+ }
+};
+
+/**
+ * Update the message being shown in the window. This should only be called
+ * after the window has been shown.
+ *
+ * @param {string} message The message.
+ */
+remoting.MessageWindow.prototype.updateMessage = function(message) {
+ if (!this.window_) {
+ this.pendingWindowOperations_.push(this.updateMessage.bind(this, message));
+ return;
+ }
+
+ var message_struct = {
+ command: 'update_message',
+ message: message
+ };
+ this.window_.postMessage(message_struct, '*');
+};
+
+/**
+ * Close the message box and unregister it with the window manager.
+ */
+remoting.MessageWindow.prototype.close = function() {
+ if (!this.window_) {
+ this.pendingWindowOperations_.push(this.close.bind(this));
+ return;
+ }
+
+ if (this.timer_) {
+ window.clearTimeout(this.timer_);
+ }
+ this.timer_ = 0;
+
+ // Unregister the window with the window manager.
+ // After this call, events sent to this window will no longer trigger the
+ // onResult callback.
+ remoting.MessageWindowManager.deleteMessageWindow(this.id_);
+ this.window_.close();
+ this.window_ = null;
+};
+
+/**
+ * Dispatch a message box result to the registered callback.
+ *
+ * @param {number} result The dialog result.
+ */
+remoting.MessageWindow.prototype.handleResult = function(result) {
+ if (this.onResult_) {
+ this.onResult_(result);
+ }
+}
+
+/**
+ * Set the window handle and run any pending operations that require it.
+ *
+ * @param {Window} window
+ * @private
+ */
+remoting.MessageWindow.prototype.setWindow_ = function(window) {
+ base.debug.assert(this.window_ == null);
+ this.window_ = window;
+ for (var i = 0; i < this.pendingWindowOperations_.length; ++i) {
+ var pendingOperation = this.pendingWindowOperations_[i];
+ pendingOperation();
+ }
+ this.pendingWindowOperations_ = [];
+};
+
+/**
+ * Static method to create and show a confirm message box.
+ *
+ * @param {string} title The title of the message box.
+ * @param {string} message The message.
+ * @param {string} okButtonLabel The text for the primary button.
+ * @param {string} cancelButtonLabel The text for the secondary button.
+ * @param {function(number):void} onResult The callback to invoke when the
+ * user closes the message window.
+ * @return {remoting.MessageWindow}
+ */
+remoting.MessageWindow.showConfirmWindow = function(
+ title, message, okButtonLabel, cancelButtonLabel, onResult) {
+ var options = {
+ title: title,
+ message: message,
+ buttonLabel: okButtonLabel,
+ cancelButtonLabel: cancelButtonLabel,
+ onResult: onResult
+ };
+ return new remoting.MessageWindow(options);
+};
+
+/**
+ * Static method to create and show a simple message box.
+ *
+ * @param {string} title The title of the message box.
+ * @param {string} message The message.
+ * @param {string} buttonLabel The text for the primary button.
+ * @param {function(number):void} onResult The callback to invoke when the
+ * user closes the message window.
+ * @return {remoting.MessageWindow}
+ */
+remoting.MessageWindow.showMessageWindow = function(
+ title, message, buttonLabel, onResult) {
+ var options = {
+ title: title,
+ message: message,
+ buttonLabel: buttonLabel,
+ onResult: onResult
+ };
+ return new remoting.MessageWindow(options);
+};
+
+/**
+ * Static method to create and show an error message box with an "OK" button.
+ * The app will close when the user dismisses the message window.
+ *
+ * @param {string} title The title of the message box.
+ * @param {string} message The message.
+ * @return {remoting.MessageWindow}
+ */
+remoting.MessageWindow.showErrorMessage = function(title, message) {
+ var options = {
+ title: title,
+ message: message,
+ buttonLabel: chrome.i18n.getMessage(/**i18n-content*/'OK'),
+ onResult: remoting.MessageWindow.quitApp
+ };
+ return new remoting.MessageWindow(options);
+};
+
+/**
+ * Static method to create and show a timed message box.
+ *
+ * @param {string} title The title of the message box.
+ * @param {string} message The message.
+ * @param {string} infobox Additional information to be displayed in an infobox,
+ * or the empty string if there is no additional information.
+ * @param {string} buttonLabel The text for the primary button.
+ * @param {function(number):void} onResult The callback to invoke when the
+ * user closes the message window.
+ * @param {number} duration Time for wait before calling onTime
+ * @param {?function():void} onTimeout Callback function.
+ * @return {remoting.MessageWindow}
+ */
+remoting.MessageWindow.showTimedMessageWindow = function(
+ title, message, infobox, buttonLabel, onResult, duration, onTimeout) {
+ var options = {
+ title: title,
+ message: message,
+ infobox: infobox,
+ buttonLabel: buttonLabel,
+ onResult: onResult,
+ duration: duration,
+ onTimeout: onTimeout
+ };
+ return new remoting.MessageWindow(options);
+};
+
+/**
+ * Cancel the current connection and close all app windows.
+ *
+ * @param {number} result The dialog result.
+ */
+remoting.MessageWindow.quitApp = function(result) {
+ remoting.MessageWindowManager.closeAllMessageWindows();
+ window.close();
+};
diff --git a/remoting/webapp/background/message_window_manager.js b/remoting/webapp/background/message_window_manager.js
new file mode 100644
index 0000000..39962c7
--- /dev/null
+++ b/remoting/webapp/background/message_window_manager.js
@@ -0,0 +1,111 @@
+// 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.
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * Namespace for window manager functions.
+ * @type {Object}
+ */
+remoting.MessageWindowManager = {};
+
+/**
+ * Mapping from window id to corresponding MessageWindow.
+ *
+ * @type {Object.<number, remoting.MessageWindow>}
+ * @private
+ */
+remoting.MessageWindowManager.messageWindows_ = {};
+
+/**
+ * The next window id to auto-assign.
+ * @type {number}
+ * @private
+ */
+remoting.MessageWindowManager.nextId_ = 1;
+
+/**
+ * @param {remoting.MessageWindow} window The window to associate
+ * with the window id.
+ * @return {number} The window id.
+ */
+remoting.MessageWindowManager.addMessageWindow = function(window) {
+ var id = ++remoting.MessageWindowManager.nextId_;
+ remoting.MessageWindowManager.messageWindows_[id] = window;
+ return id;
+};
+
+/**
+ * @param {number} id The window id.
+ * @return {remoting.MessageWindow}
+ */
+remoting.MessageWindowManager.getMessageWindow = function(id) {
+ return remoting.MessageWindowManager.messageWindows_[id];
+};
+
+/**
+ * @param {number} id The window id to delete.
+ */
+remoting.MessageWindowManager.deleteMessageWindow = function(id) {
+ delete remoting.MessageWindowManager.messageWindows_[id];
+};
+
+/**
+ * Close all of the registered MessageWindows
+ */
+remoting.MessageWindowManager.closeAllMessageWindows = function() {
+ /** @type {Array.<remoting.MessageWindow>} */
+ var windows = [];
+ // Make a list of the windows to close.
+ // We don't delete the window directly in this loop because close() can
+ // call deleteMessageWindow which will update messageWindows_.
+ for (var win_id in remoting.MessageWindowManager.messageWindows_) {
+ /** @type {remoting.MessageWindow} */
+ var win = remoting.MessageWindowManager.getMessageWindow(
+ /** @type {number} */(win_id));
+ base.debug.assert(win != null);
+ windows.push(win);
+ }
+ for (var i = 0; i < windows.length; i++) {
+ /** @type {remoting.MessageWindow} */(windows[i]).close();
+ }
+};
+
+/**
+ * Dispatch a message box result to the appropriate callback.
+ *
+ * @param {Event} event
+ * @private
+ */
+remoting.MessageWindowManager.onMessage_ = function(event) {
+ if (typeof(event.data) != 'object') {
+ return;
+ }
+
+ if (event.data['command'] == 'messageWindowResult') {
+ var id = /** @type {number} */ (event.data['id']);
+ var result = /** @type {number} */ (event.data['result']);
+
+ if (typeof(id) != 'number' || typeof(result) != 'number') {
+ console.log('Poorly formatted id or result');
+ return;
+ }
+
+ var messageWindow = remoting.MessageWindowManager.getMessageWindow(id);
+ if (!messageWindow) {
+ console.log('Ignoring unknown message window id:', id);
+ return;
+ }
+
+ messageWindow.handleResult(result);
+ messageWindow.close();
+ }
+};
+
+
+window.addEventListener('message', remoting.MessageWindowManager.onMessage_,
+ false);
diff --git a/remoting/webapp/html/message_window.html b/remoting/webapp/html/message_window.html
new file mode 100644
index 0000000..2e7074b
--- /dev/null
+++ b/remoting/webapp/html/message_window.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<!--
+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>
+ <head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="open_sans.css">
+ <link rel="stylesheet" href="main.css">
+ <link rel="stylesheet" href="message_window.css">
+ <script src="base.js"></script>
+ <script src="message_window.js"></script>
+ <title></title>
+ </head>
+ <body>
+ <h2 id="title"></h2>
+ <p id="infobox" class="information-box"></p>
+ <p id="message"></p>
+ <div class="button-row">
+ <button id="button-primary"></button>
+ <button id="button-secondary"></button>
+ </div>
+ </body>
+</html>
diff --git a/remoting/webapp/js_proto/chrome_proto.js b/remoting/webapp/js_proto/chrome_proto.js
index c7d983f..6187714 100644
--- a/remoting/webapp/js_proto/chrome_proto.js
+++ b/remoting/webapp/js_proto/chrome_proto.js
@@ -346,6 +346,8 @@ var AppWindow = function() {
/** @type {Window} */
this.contentWindow = null;
/** @type {chrome.Event} */
+ this.onClosed = null;
+ /** @type {chrome.Event} */
this.onRestored = null;
/** @type {chrome.Event} */
this.onMaximized = null;
@@ -360,6 +362,7 @@ AppWindow.prototype.drawAttention = function() {};
AppWindow.prototype.maximize = function() {};
AppWindow.prototype.minimize = function() {};
AppWindow.prototype.restore = function() {};
+AppWindow.prototype.show = function() {};
AppWindow.prototype.fullscreen = function() {};
/** @return {boolean} */
AppWindow.prototype.isFullscreen = function() {};
diff --git a/remoting/webapp/message_window.css b/remoting/webapp/message_window.css
new file mode 100644
index 0000000..c5a51ee
--- /dev/null
+++ b/remoting/webapp/message_window.css
@@ -0,0 +1,13 @@
+/* Copyright (c) 2013 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.
+ */
+
+body {
+ padding: 20px;
+}
+
+#infobox {
+ margin-top: 10px;
+ margin-bottom: 10px;
+} \ No newline at end of file