diff options
author | weitaosu <weitaosu@chromium.org> | 2015-06-23 15:55:56 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-06-23 22:56:25 +0000 |
commit | 574f3040aa6aace3de564b88ac0d83aa7281fb93 (patch) | |
tree | 322825ac6434f55bd434bb510a17df09af4feee4 | |
parent | f4505949d31e66bdef441c23aa9357cc42e6636e (diff) | |
download | chromium_src-574f3040aa6aace3de564b88ac0d83aa7281fb93.zip chromium_src-574f3040aa6aace3de564b88ac0d83aa7281fb93.tar.gz chromium_src-574f3040aa6aace3de564b88ac0d83aa7281fb93.tar.bz2 |
Add a WindowMessageDispatcher class to listen to and dispatch all messages sent via window.postMessage. Now all messages sent will contain a 'source' field so that the dispatcher can dispatch to the registered handler.
I also turned MessageWindowManager into an object.
BUG=
Review URL: https://codereview.chromium.org/1195953003
Cr-Commit-Position: refs/heads/master@{#335787}
-rw-r--r-- | remoting/remoting_webapp_files.gypi | 2 | ||||
-rw-r--r-- | remoting/webapp/app_remoting/js/app_remoting.js | 7 | ||||
-rw-r--r-- | remoting/webapp/base/js/application.js | 13 | ||||
-rw-r--r-- | remoting/webapp/base/js/message_window.js | 1 | ||||
-rw-r--r-- | remoting/webapp/base/js/message_window_helper.js | 6 | ||||
-rw-r--r-- | remoting/webapp/base/js/message_window_manager.js | 76 | ||||
-rw-r--r-- | remoting/webapp/base/js/wcs_sandbox_container.js | 18 | ||||
-rw-r--r-- | remoting/webapp/base/js/window_message_dispatcher.js | 106 | ||||
-rw-r--r-- | remoting/webapp/base/js/window_message_dispatcher_unittest.js | 124 | ||||
-rw-r--r-- | remoting/webapp/crd/js/wcs_sandbox_content.js | 5 | ||||
-rw-r--r-- | remoting/webapp/files.gni | 2 |
11 files changed, 318 insertions, 42 deletions
diff --git a/remoting/remoting_webapp_files.gypi b/remoting/remoting_webapp_files.gypi index 60aa74b..ef8ce07 100644 --- a/remoting/remoting_webapp_files.gypi +++ b/remoting/remoting_webapp_files.gypi @@ -99,6 +99,7 @@ 'webapp/base/js/typecheck_unittest.js', 'webapp/base/js/viewport_unittest.js', 'webapp/base/js/window_shape_unittest.js', + 'webapp/base/js/window_message_dispatcher_unittest.js', 'webapp/base/js/xhr_event_writer_unittest.js', 'webapp/base/js/xhr_unittest.js', 'webapp/base/js/xmpp_connection_unittest.js', @@ -186,6 +187,7 @@ 'webapp/base/js/plugin_settings.js', 'webapp/base/js/suspend_detector.js', 'webapp/base/js/typecheck.js', + 'webapp/base/js/window_message_dispatcher.js', 'webapp/base/js/xhr_event_writer.js', 'webapp/base/js/xhr.js', ], diff --git a/remoting/webapp/app_remoting/js/app_remoting.js b/remoting/webapp/app_remoting/js/app_remoting.js index 87b9134..90257ea 100644 --- a/remoting/webapp/app_remoting/js/app_remoting.js +++ b/remoting/webapp/app_remoting/js/app_remoting.js @@ -61,7 +61,10 @@ remoting.AppRemoting.prototype.signInFailed_ = function(error) { /** * @override {remoting.ApplicationInterface} */ -remoting.AppRemoting.prototype.initApplication_ = function() {}; +remoting.AppRemoting.prototype.initApplication_ = function() { + remoting.messageWindowManager = new remoting.MessageWindowManager( + this.windowMessageDispatcher_); +}; /** * @param {string} token An OAuth access token. @@ -84,6 +87,6 @@ remoting.AppRemoting.prototype.startApplication_ = function(token) { * @override {remoting.ApplicationInterface} */ remoting.AppRemoting.prototype.exitApplication_ = function() { - this.activity_.dispose(); + base.dispose(this.activity_); this.closeMainWindow_(); }; diff --git a/remoting/webapp/base/js/application.js b/remoting/webapp/base/js/application.js index 3910405..4346ffe 100644 --- a/remoting/webapp/base/js/application.js +++ b/remoting/webapp/base/js/application.js @@ -21,10 +21,19 @@ remoting.testEvents; /** * @constructor + * @implements {base.Disposable} */ remoting.Application = function() { // Create global factories. remoting.ClientPlugin.factory = new remoting.DefaultClientPluginFactory(); + + /** @private {base.WindowMessageDispatcher} */ + this.windowMessageDispatcher_ = new base.WindowMessageDispatcher(); +}; + +remoting.Application.prototype.dispose = function() { + base.dispose(this.windowMessageDispatcher_); + this.windowMessageDispatcher_ = null; }; /* Public method to exit the application. */ @@ -76,9 +85,11 @@ remoting.Application.prototype.initGlobalObjects_ = function() { console.log(this.getExtensionInfo()); l10n.localize(); + var sandbox = /** @type {HTMLIFrameElement} */ (document.getElementById('wcs-sandbox')); - remoting.wcsSandbox = new remoting.WcsSandboxContainer(sandbox.contentWindow); + remoting.wcsSandbox = new remoting.WcsSandboxContainer( + sandbox.contentWindow, this.windowMessageDispatcher_); remoting.initModalDialogs(); remoting.testEvents = new base.EventSourceImpl(); diff --git a/remoting/webapp/base/js/message_window.js b/remoting/webapp/base/js/message_window.js index f2c3634..a39fef4 100644 --- a/remoting/webapp/base/js/message_window.js +++ b/remoting/webapp/base/js/message_window.js @@ -31,6 +31,7 @@ MessageWindowImpl.prototype.sendReply_ = function( // Only forward the first reply that we receive. if (!this.sentReply_) { var message = { + source: 'message-window', command: 'messageWindowResult', id: messageId, result: result diff --git a/remoting/webapp/base/js/message_window_helper.js b/remoting/webapp/base/js/message_window_helper.js index d608a03..26ac550 100644 --- a/remoting/webapp/base/js/message_window_helper.js +++ b/remoting/webapp/base/js/message_window_helper.js @@ -66,7 +66,7 @@ remoting.MessageWindow = function(options) { var onTimeout = options.onTimeout; /** @type {number} */ - this.id_ = remoting.MessageWindowManager.addMessageWindow(this); + this.id_ = remoting.messageWindowManager.addMessageWindow(this); /** @type {?function(number):void} */ this.onResult_ = onResult; @@ -176,7 +176,7 @@ remoting.MessageWindow.prototype.close = function() { // 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_); + remoting.messageWindowManager.deleteMessageWindow(this.id_); this.window_.close(); this.window_ = null; }; @@ -276,6 +276,6 @@ remoting.MessageWindow.showErrorMessage = function(title, message) { * @param {number} result The dialog result. */ remoting.MessageWindow.quitApp = function(result) { - remoting.MessageWindowManager.closeAllMessageWindows(); + remoting.messageWindowManager.closeAllMessageWindows(); window.close(); }; diff --git a/remoting/webapp/base/js/message_window_manager.js b/remoting/webapp/base/js/message_window_manager.js index 596336f..27aca80 100644 --- a/remoting/webapp/base/js/message_window_manager.js +++ b/remoting/webapp/base/js/message_window_manager.js @@ -8,32 +8,43 @@ var remoting = remoting || {}; /** - * Namespace for window manager functions. - * @type {Object} + * This class manages all the message windows (remoting.MessageWindow). + * @param {base.WindowMessageDispatcher} windowMessageDispatcher + * @constructor + * @implements {base.Disposable} */ -remoting.MessageWindowManager = {}; - -/** - * Mapping from window id to corresponding MessageWindow. - * - * @private {Object<number, remoting.MessageWindow>} - */ -remoting.MessageWindowManager.messageWindows_ = {}; +remoting.MessageWindowManager = function(windowMessageDispatcher) { + /** + * @type {Object<number, remoting.MessageWindow>} + * @private + */ + this.messageWindows_ = null; + + /** + * The next window id to auto-assign. + * @private {number} + */ + this.nextId_ = 1; + + /** @private {base.WindowMessageDispatcher} */ + this.windowMessageDispatcher_ = windowMessageDispatcher; + + this.windowMessageDispatcher_.registerMessageHandler( + 'message-window', this.onMessage_.bind(this)); +}; -/** - * The next window id to auto-assign. - * @private {number} - */ -remoting.MessageWindowManager.nextId_ = 1; +remoting.MessageWindowManager.prototype.dispose = function() { + this.windowMessageDispatcher_.unregisterMessageHandler('message-window'); +}; /** * @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; +remoting.MessageWindowManager.prototype.addMessageWindow = function(window) { + var id = ++this.nextId_; + this.messageWindows_[id] = window; return id; }; @@ -41,30 +52,29 @@ remoting.MessageWindowManager.addMessageWindow = function(window) { * @param {number} id The window id. * @return {remoting.MessageWindow} */ -remoting.MessageWindowManager.getMessageWindow = function(id) { - return remoting.MessageWindowManager.messageWindows_[id]; +remoting.MessageWindowManager.prototype.getMessageWindow = function(id) { + return this.messageWindows_[id]; }; /** * @param {number} id The window id to delete. */ -remoting.MessageWindowManager.deleteMessageWindow = function(id) { - delete remoting.MessageWindowManager.messageWindows_[id]; +remoting.MessageWindowManager.prototype.deleteMessageWindow = function(id) { + delete this.messageWindows_[id]; }; /** * Close all of the registered MessageWindows */ -remoting.MessageWindowManager.closeAllMessageWindows = function() { +remoting.MessageWindowManager.prototype.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_) { + for (var win_id in this.messageWindows_) { /** @type {remoting.MessageWindow} */ - var win = remoting.MessageWindowManager.getMessageWindow( - parseInt(win_id, 10)); + var win = this.getMessageWindow(parseInt(win_id, 10)); base.debug.assert(win != null); windows.push(win); } @@ -79,10 +89,9 @@ remoting.MessageWindowManager.closeAllMessageWindows = function() { * @param {Event} event * @private */ -remoting.MessageWindowManager.onMessage_ = function(event) { - if (typeof(event.data) != 'object') { - return; - } +remoting.MessageWindowManager.prototype.onMessage_ = function(event) { + base.debug.assert(typeof event.data === 'object' && + event.data['source'] == 'message-window'); if (event.data['command'] == 'messageWindowResult') { var id = /** @type {number} */ (event.data['id']); @@ -93,7 +102,7 @@ remoting.MessageWindowManager.onMessage_ = function(event) { return; } - var messageWindow = remoting.MessageWindowManager.getMessageWindow(id); + var messageWindow = this.getMessageWindow(id); if (!messageWindow) { console.log('Ignoring unknown message window id:', id); return; @@ -104,6 +113,5 @@ remoting.MessageWindowManager.onMessage_ = function(event) { } }; - -window.addEventListener('message', remoting.MessageWindowManager.onMessage_, - false); +/** @type {remoting.MessageWindowManager} */ +remoting.messageWindowManager = null; diff --git a/remoting/webapp/base/js/wcs_sandbox_container.js b/remoting/webapp/base/js/wcs_sandbox_container.js index dd41e0e..e33422a 100644 --- a/remoting/webapp/base/js/wcs_sandbox_container.js +++ b/remoting/webapp/base/js/wcs_sandbox_container.js @@ -17,9 +17,11 @@ var remoting = remoting || {}; /** * @param {Window} sandbox The Javascript Window object representing the * sandboxed WCS driver. + * @param {base.WindowMessageDispatcher} windowMessageDispatcher * @constructor + * @implements {base.Disposable} */ -remoting.WcsSandboxContainer = function(sandbox) { +remoting.WcsSandboxContainer = function(sandbox, windowMessageDispatcher) { /** @private */ this.sandbox_ = sandbox; /** @private {?function(string):void} */ @@ -36,7 +38,11 @@ remoting.WcsSandboxContainer = function(sandbox) { /** @private */ this.accessTokenRefreshTimerStarted_ = false; - window.addEventListener('message', this.onMessage_.bind(this), false); + /** @private {base.WindowMessageDispatcher} */ + this.windowMessageDispatcher_ = windowMessageDispatcher; + + this.windowMessageDispatcher_.registerMessageHandler( + 'wcs-sandbox', this.onMessage_.bind(this)); if (base.isAppsV2()) { var message = { @@ -46,6 +52,10 @@ remoting.WcsSandboxContainer = function(sandbox) { } }; +remoting.WcsSandboxContainer.prototype.dispose = function() { + this.windowMessageDispatcher_.unregisterMessageHandler('wcs-sandbox'); +}; + /** * @param {function(string):void} onConnected Callback to be called when WCS is * connected. May be called synchronously if WCS is already connected. @@ -127,8 +137,12 @@ remoting.WcsSandboxContainer.prototype.sendIq = function(stanza) { * Event handler to process messages from the sandbox. * * @param {Event} event + * @private */ remoting.WcsSandboxContainer.prototype.onMessage_ = function(event) { + base.debug.assert(typeof event.data === 'object' && + event.data['source'] == 'wcs-sandbox'); + switch (event.data['command']) { case 'onLocalJid': diff --git a/remoting/webapp/base/js/window_message_dispatcher.js b/remoting/webapp/base/js/window_message_dispatcher.js new file mode 100644 index 0000000..dc9695b --- /dev/null +++ b/remoting/webapp/base/js/window_message_dispatcher.js @@ -0,0 +1,106 @@ +/* Copyright 2015 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 + * This class dispatches window messages (those sent using window.postMessage) + * from different sources to the registered handlers. + * This should be the only 'message' event listener in this app. Anyone who + * wants to listen to window messages should register with this class. + */ + +'use strict'; + +/** @suppress {duplicate} */ +var base = base || {}; + +(function() { +/** + * @constructor + * @implements {base.Disposable} + */ +base.WindowMessageDispatcher = function() { + /** @private {Object<string, function(Event):void>} */ + this.handlers_ = {}; + + /** @private */ + this.eventHook_ = new base.DomEventHook( + window, 'message', this.onMessage_.bind(this), false); +}; + +base.WindowMessageDispatcher.prototype.dispose = function() { + this.unregisterAllHandlers(); + + base.dispose(this.eventHook_); + this.eventHook_ = null; +}; + +/** + * @param {string} source Message source to register handler for. + * @param {function(Event):void} handler Handler for the messages from |source|. + * @return {void} + */ +base.WindowMessageDispatcher.prototype.registerMessageHandler = + function(source, handler) { + base.debug.assert(!!source); + base.debug.assert(!!handler); + + if (source in this.handlers_) { + console.error('Cannot register more than one handler for source: ', source); + } else { + this.handlers_[source] = handler; + } +}; + +/** + * @param {string} source Message source to unregister handler for. + * @return {void} + */ +base.WindowMessageDispatcher.prototype.unregisterMessageHandler = + function(source) { + base.debug.assert(!!source); + + if (source in this.handlers_) { + delete this.handlers_[source]; + } else { + console.error('Message handler doesn\'t exist for source: ', source); + } +}; + +/** + * @return {void} + */ +base.WindowMessageDispatcher.prototype.unregisterAllHandlers = function() { + this.handlers_ = {}; +}; + +/** + * Event handler to process window messages. + * + * @param {Event} event + */ +base.WindowMessageDispatcher.prototype.onMessage_ = function(event) { + var data = event.data; + if (typeof data === 'object') { + /** @type {string} */ + var source = data['source']; + if (source === undefined) { + console.error('Missing source field in incoming message: ', data); + return; + } + + console.log('object message received from: ', source); + var handler = this.handlers_[source]; + if (handler) { + handler(event); + } else { + console.error('No handler registered for messages from: ', source); + } + } else { + console.error('Unknown window message data type: ', data); + } +}; + +})(); diff --git a/remoting/webapp/base/js/window_message_dispatcher_unittest.js b/remoting/webapp/base/js/window_message_dispatcher_unittest.js new file mode 100644 index 0000000..c9d9399 --- /dev/null +++ b/remoting/webapp/base/js/window_message_dispatcher_unittest.js @@ -0,0 +1,124 @@ +// Copyright 2015 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 + */ + +(function() { + +'use strict'; + +/** @type {base.WindowMessageDispatcher} */ +var windowMessageDispatcher = null; + +QUnit.module('WindowMessageDispatcher', { + beforeEach: function() { + windowMessageDispatcher = new base.WindowMessageDispatcher(); + }, + afterEach: function() { + base.dispose(windowMessageDispatcher); + windowMessageDispatcher = null; + } +}); + +// Window messages are asynchronous. The message we post may still be in the +// pipeline by the time we return from the main test routine. So we return a +// promise to wait for the delivery of the message. +QUnit.test('handler should be invoked after registration', function(assert) { + var deferred = new base.Deferred(); + + /** @param {Event} event */ + var handler = function(event) { + assert.equal(event.data['source'], 'testSource'); + assert.equal(event.data['command'], 'testCommand'); + + // Resolve the promise to signal the completion of the test. + deferred.resolve(); + }; + + windowMessageDispatcher.registerMessageHandler('testSource', handler); + + var message = {'source': 'testSource', 'command': 'testCommand'}; + window.postMessage(message, '*'); + + return deferred.promise(); +}); + +// In this test we test message dispatching to 'testSource1' before and after +// the registration and unregistration of the message handler. The messages +// for 'testSource2' are used as a synchronization mechanism. +// Here is the workflow of this test: +// 1. Register message handlers for 'testSource1' and 'testSource2'. +// 2. Post a message to testSource1. +// 3. When the message is delivered, unregister message handler for +// 'testSource1' and send a message to 'testSource2'. +// 4. When the message to 'testSource2' is delivered. Send a message +// each to 'testSource1' and 'testSource2'. +// 5. When the second message to 'testSource2' is delivered. Make sure +// that the second message to 'testSource1' has not been delivered. +QUnit.test('handler should not be invoked after unregistration', + function(assert) { + var deferred = new base.Deferred(); + var callCount1 = 0; + var callCount2 = 0; + + /** @param {Event} event */ + var handler1 = function(event) { + if (callCount1 > 0) { + deferred.reject('handler1 should not be called more than once.'); + } + + assert.equal(event.data['source'], 'testSource1'); + assert.equal(event.data['command'], 'testCommand1'); + + ++callCount1; + + // 3. When the message is delivered, unregister message handler for + // 'testSource1' and send a message to 'testSource2'. + windowMessageDispatcher.unregisterMessageHandler('testSource1'); + window.postMessage( + {'source': 'testSource2', 'command': 'testCommand2'}, '*'); + }; + + /** @param {Event} event */ + var handler2 = function(event) { + assert.equal(event.data['source'], 'testSource2'); + assert.equal(event.data['command'], 'testCommand2'); + + ++callCount2; + + switch (callCount2) { + case 1: + // 4. When the message to 'testSource2' is delivered. Send a message + // each to 'testSource1' and 'testSource2'. + assert.equal(callCount1, 1); + window.postMessage( + {'source': 'testSource1', 'command': 'testCommand1'}, '*'); + window.postMessage( + {'source': 'testSource2', 'command': 'testCommand2'}, '*'); + break; + case 2: + // 5. When the second message to 'testSource2' is delivered. Make sure + // that the second message to 'testSource1' has not been delivered. + assert.equal(callCount1, 1); + deferred.resolve(); + break; + default: + deferred.reject('handler1 was never called.'); + }; + }; + + // 1. Register message handlers for 'testSource1' and 'testSource2'. + windowMessageDispatcher.registerMessageHandler('testSource1', handler1); + windowMessageDispatcher.registerMessageHandler('testSource2', handler2); + + // 2. Post a message to testSource1. + window.postMessage( + {'source': 'testSource1', 'command': 'testCommand1'}, '*'); + + return deferred.promise(); +}); + +})(); diff --git a/remoting/webapp/crd/js/wcs_sandbox_content.js b/remoting/webapp/crd/js/wcs_sandbox_content.js index dd85d1d..0b48c6b 100644 --- a/remoting/webapp/crd/js/wcs_sandbox_content.js +++ b/remoting/webapp/crd/js/wcs_sandbox_content.js @@ -122,6 +122,7 @@ remoting.WcsSandboxContent.prototype.onMessage_ = function(event) { remoting.WcsSandboxContent.prototype.onLocalJid_ = function(localJid) { remoting.wcs.setOnIq(this.onIq_.bind(this)); var message = { + 'source': 'wcs-sandbox', 'command': 'onLocalJid', 'localJid': localJid }; @@ -136,6 +137,7 @@ remoting.WcsSandboxContent.prototype.onLocalJid_ = function(localJid) { */ remoting.WcsSandboxContent.prototype.onError_ = function(error) { var message = { + 'source': 'wcs-sandbox', 'command': 'onError', 'error': error }; @@ -153,6 +155,7 @@ remoting.WcsSandboxContent.prototype.sendXhr = function(xhr) { var id = this.nextXhrId_++; this.pendingXhrs_[id] = xhr; var message = { + 'source': 'wcs-sandbox', 'command': 'sendXhr', 'id': id, 'parameters': xhr.sandboxIpc @@ -175,6 +178,7 @@ remoting.WcsSandboxContent.prototype.abortXhr = function(id) { return; } var message = { + 'source': 'wcs-sandbox', 'command': 'abortXhr', 'id': id }; @@ -191,6 +195,7 @@ remoting.WcsSandboxContent.prototype.abortXhr = function(id) { remoting.WcsSandboxContent.prototype.onIq_ = function(stanza) { remoting.wcs.setOnIq(this.onIq_.bind(this)); var message = { + 'source': 'wcs-sandbox', 'command': 'onIq', 'stanza': stanza }; diff --git a/remoting/webapp/files.gni b/remoting/webapp/files.gni index c3b983c..f59e265 100644 --- a/remoting/webapp/files.gni +++ b/remoting/webapp/files.gni @@ -93,6 +93,7 @@ remoting_webapp_unittests_js_files = [ "base/js/typecheck_unittest.js", "base/js/viewport_unittest.js", "base/js/window_shape_unittest.js", + "base/js/window_message_dispatcher_unittest.js", "base/js/xhr_event_writer_unittest.js", "base/js/xhr_unittest.js", "base/js/xmpp_connection_unittest.js", @@ -179,6 +180,7 @@ remoting_webapp_shared_js_core_files = [ "base/js/plugin_settings.js", "base/js/suspend_detector.js", "base/js/typecheck.js", + "base/js/window_message_dispatcher.js", "base/js/xhr_event_writer.js", "base/js/xhr.js", ] |