summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorweitaosu <weitaosu@chromium.org>2015-06-23 15:55:56 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-23 22:56:25 +0000
commit574f3040aa6aace3de564b88ac0d83aa7281fb93 (patch)
tree322825ac6434f55bd434bb510a17df09af4feee4
parentf4505949d31e66bdef441c23aa9357cc42e6636e (diff)
downloadchromium_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.gypi2
-rw-r--r--remoting/webapp/app_remoting/js/app_remoting.js7
-rw-r--r--remoting/webapp/base/js/application.js13
-rw-r--r--remoting/webapp/base/js/message_window.js1
-rw-r--r--remoting/webapp/base/js/message_window_helper.js6
-rw-r--r--remoting/webapp/base/js/message_window_manager.js76
-rw-r--r--remoting/webapp/base/js/wcs_sandbox_container.js18
-rw-r--r--remoting/webapp/base/js/window_message_dispatcher.js106
-rw-r--r--remoting/webapp/base/js/window_message_dispatcher_unittest.js124
-rw-r--r--remoting/webapp/crd/js/wcs_sandbox_content.js5
-rw-r--r--remoting/webapp/files.gni2
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",
]