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