diff options
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/remoting_webapp_files.gypi | 6 | ||||
-rw-r--r-- | remoting/webapp/background/message_window.js | 163 | ||||
-rw-r--r-- | remoting/webapp/background/message_window_helper.js | 271 | ||||
-rw-r--r-- | remoting/webapp/background/message_window_manager.js | 111 | ||||
-rw-r--r-- | remoting/webapp/html/message_window.html | 27 | ||||
-rw-r--r-- | remoting/webapp/js_proto/chrome_proto.js | 3 | ||||
-rw-r--r-- | remoting/webapp/message_window.css | 13 |
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 |