summaryrefslogtreecommitdiffstats
path: root/remoting/webapp
diff options
context:
space:
mode:
authorjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-29 21:24:10 +0000
committerjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-29 21:24:10 +0000
commit38643635962d27bf9cb6cc1d7f9701a29ecbe90e (patch)
tree78b671af90079ab65e1d4a602b550a7240b14618 /remoting/webapp
parentdb471019d15ed9e8e086bf36f426326e3ebbe86d (diff)
downloadchromium_src-38643635962d27bf9cb6cc1d7f9701a29ecbe90e.zip
chromium_src-38643635962d27bf9cb6cc1d7f9701a29ecbe90e.tar.gz
chromium_src-38643635962d27bf9cb6cc1d7f9701a29ecbe90e.tar.bz2
Get Chromoting client working in Apps V2
This involves the following: 1. Removing chrome extension API calls from wcs.js and wcs_loader.js. 2. Moving those files into a separate <iframe> sandbox. 3. Adding a postMessage-based API to communicate with the rest of the code. 4. Working around miscellaneous sandbox restrictions. BUG=134213 Review URL: https://chromiumcodereview.appspot.com/12021029 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@179413 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/webapp')
-rw-r--r--remoting/webapp/all_js_load.gtestjs5
-rw-r--r--remoting/webapp/appsv2.patch17
-rw-r--r--remoting/webapp/client_screen.js78
-rw-r--r--remoting/webapp/client_session.js51
-rw-r--r--remoting/webapp/jscompiler_hacks.js3
-rw-r--r--remoting/webapp/log_to_server.js6
-rw-r--r--remoting/webapp/main.html5
-rw-r--r--remoting/webapp/remoting.js3
-rw-r--r--remoting/webapp/wcs.js31
-rw-r--r--remoting/webapp/wcs_loader.js62
-rw-r--r--remoting/webapp/wcs_sandbox.html18
-rw-r--r--remoting/webapp/wcs_sandbox_container.js244
-rw-r--r--remoting/webapp/wcs_sandbox_content.js222
-rw-r--r--remoting/webapp/xhr_proxy.js93
14 files changed, 714 insertions, 124 deletions
diff --git a/remoting/webapp/all_js_load.gtestjs b/remoting/webapp/all_js_load.gtestjs
index c752d5d..4e09e5e 100644
--- a/remoting/webapp/all_js_load.gtestjs
+++ b/remoting/webapp/all_js_load.gtestjs
@@ -46,6 +46,8 @@ AllJsLoadTest.prototype = {
// |window.addEventListener| when loaded.
'oauth2.js',
'plugin_settings.js',
+ //'xhr_proxy.js', // Disabled because it accesses |XMLHttpRequest|
+ // when loaded, which is not available to tests.
'remoting.js',
'server_log_entry.js',
'stats_accumulator.js',
@@ -57,6 +59,9 @@ AllJsLoadTest.prototype = {
//'wcs_iq_client_proto.js', // Only used by jscompiler.
'wcs.js',
'wcs_loader.js',
+ //'wcs_sandbox_content.js', // Disabled because it calls
+ // |window.addEventListener| when loaded.
+ 'wcs_sandbox_container.js',
'xhr.js',
],
};
diff --git a/remoting/webapp/appsv2.patch b/remoting/webapp/appsv2.patch
index 24747c0..758bcb8 100644
--- a/remoting/webapp/appsv2.patch
+++ b/remoting/webapp/appsv2.patch
@@ -93,7 +93,7 @@ index a52b1da..fdae490 100644
"https://accounts.google.com/*",
"https://www.googleapis.com/chromoting/*",
"https://*.talkgadget.google.com/talkgadget/*",
-@@ -31,12 +25,12 @@
+@@ -31,14 +25,17 @@
"clipboardRead",
"clipboardWrite"
],
@@ -112,6 +112,11 @@ index a52b1da..fdae490 100644
"requirements": {
"plugins": {
"npapi": false
+ }
++ },
++ "sandbox": {
++ "pages": [ "wcs_sandbox.html" ]
+ }
diff --git a/remoting/webapp/remoting.js b/remoting/webapp/remoting.js
index a8ab35b..9c6df35 100644
--- a/remoting.js
@@ -138,3 +143,13 @@ index a8ab35b..9c6df35 100644
remoting.hostSetupDialog =
new remoting.HostSetupDialog(remoting.hostController);
// Display the cached host list, then asynchronously update and re-display it.
+diff --git a/remoting/webapp/xhr_proxy.js b/remoting/webapp/xhr_proxy.js
+index 4c45780..653b481 100644
+--- a/xhr_proxy.js
++++ b/xhr_proxy.js
+@@ -90,4 +90,4 @@ remoting.XMLHttpRequestProxy.prototype.DONE = 4;
+
+ // Since the WCS driver code constructs XHRs directly, the only mechanism for
+ // proxying them is to replace the XMLHttpRequest constructor.
+-//XMLHttpRequest = remoting.XMLHttpRequestProxy;
++XMLHttpRequest = remoting.XMLHttpRequestProxy;
diff --git a/remoting/webapp/client_screen.js b/remoting/webapp/client_screen.js
index a1a01b9..97bf94e 100644
--- a/remoting/webapp/client_screen.js
+++ b/remoting/webapp/client_screen.js
@@ -62,8 +62,16 @@ remoting.currentConnectionType = null;
*/
remoting.connectIt2Me = function() {
remoting.currentConnectionType = remoting.ClientSession.Mode.IT2ME;
- remoting.WcsLoader.load(connectIt2MeWithAccessToken_,
- remoting.showErrorMessage);
+ /** @param {string} token */
+ var startWcsAndConnect = function(token) {
+ remoting.wcsSandbox.setOnReady(
+ connectIt2MeWithAccessToken_.bind(null, token));
+ remoting.wcsSandbox.setOnError(remoting.showErrorMessage);
+ remoting.wcsSandbox.setAccessToken(token);
+ startAccessTokenRefreshTimer_();
+ };
+ remoting.identity.callWithToken(startWcsAndConnect,
+ remoting.showErrorMessage);
};
/**
@@ -160,9 +168,10 @@ remoting.sendPrintScreen = function() {
* report an error.
*
* @param {string} token The OAuth2 access token.
+ * @param {string} clientJid The full JID of the WCS client.
* @return {void} Nothing.
*/
-function connectIt2MeWithAccessToken_(token) {
+function connectIt2MeWithAccessToken_(token, clientJid) {
var accessCode = document.getElementById('access-code-entry').value;
remoting.accessCode = normalizeAccessCode_(accessCode);
// At present, only 12-digit access codes are supported, of which the first
@@ -176,7 +185,7 @@ function connectIt2MeWithAccessToken_(token) {
} else {
var supportId = remoting.accessCode.substring(0, kSupportIdLen);
remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
- resolveSupportId(supportId, token);
+ resolveSupportId(clientJid, supportId, token);
}
}
@@ -311,15 +320,17 @@ function retryConnectOrReportOffline_() {
/**
* Create the client session object and initiate the connection.
*
+ * @param {string} clientJid The full JID of the WCS client.
* @return {void} Nothing.
*/
-function startSession_() {
+function startSession_(clientJid) {
console.log('Starting session...');
var accessCode = document.getElementById('access-code-entry');
accessCode.value = ''; // The code has been validated and won't work again.
remoting.clientSession =
new remoting.ClientSession(
- remoting.hostJid, remoting.hostPublicKey,
+ remoting.hostJid, clientJid,
+ remoting.hostPublicKey,
remoting.accessCode, 'spake2_plain', '',
remoting.ClientSession.Mode.IT2ME,
onClientStateChange_);
@@ -367,10 +378,11 @@ function setConnectionInterruptedButtonsText_() {
/**
* Parse the response from the server to a request to resolve a support id.
*
+ * @param {string} clientJid The full JID of the WCS client.
* @param {XMLHttpRequest} xhr The XMLHttpRequest object.
* @return {void} Nothing.
*/
-function parseServerResponse_(xhr) {
+function parseServerResponse_(clientJid, xhr) {
remoting.supportHostsXhr_ = null;
console.log('parseServerResponse: xhr =', xhr);
if (xhr.status == 200) {
@@ -381,7 +393,7 @@ function parseServerResponse_(xhr) {
remoting.hostPublicKey = host.data.publicKey;
var split = remoting.hostJid.split('/');
document.getElementById('connected-to').innerText = split[0];
- startSession_();
+ startSession_(clientJid);
return;
} else {
console.error('Invalid "support-hosts" response from server.');
@@ -415,10 +427,11 @@ function normalizeAccessCode_(accessCode) {
/**
* Initiate a request to the server to resolve a support ID.
*
+ * @param {string} clientJid The full JID of the WCS client.
* @param {string} supportId The canonicalized support ID.
* @param {string} token The OAuth access token.
*/
-function resolveSupportId(supportId, token) {
+function resolveSupportId(clientJid, supportId, token) {
var headers = {
'Authorization': 'OAuth ' + token
};
@@ -426,7 +439,7 @@ function resolveSupportId(supportId, token) {
remoting.supportHostsXhr_ = remoting.xhr.get(
'https://www.googleapis.com/chromoting/v1/support-hosts/' +
encodeURIComponent(supportId),
- parseServerResponse_,
+ parseServerResponse_.bind(null, clientJid),
'',
headers);
}
@@ -497,23 +510,32 @@ remoting.connectMe2MeWithPin = function() {
document.title = host.hostName + ' - ' +
chrome.i18n.getMessage('PRODUCT_NAME');
- remoting.WcsLoader.load(connectMe2MeWithAccessToken_,
- remoting.showErrorMessage);
+ /** @param {string} token */
+ var startWcsAndConnect = function(token) {
+ remoting.wcsSandbox.setOnReady(
+ connectMe2MeWithAccessToken_.bind(null, token));
+ remoting.wcsSandbox.setOnError(remoting.showErrorMessage);
+ remoting.wcsSandbox.setAccessToken(token);
+ startAccessTokenRefreshTimer_();
+ };
+ remoting.identity.callWithToken(startWcsAndConnect,
+ remoting.showErrorMessage);
};
/**
* Continue making the connection to a host, once WCS has initialized.
*
* @param {string} token The OAuth2 access token.
+ * @param {string} clientJid The full JID of the WCS client.
* @return {void} Nothing.
*/
-function connectMe2MeWithAccessToken_(token) {
+function connectMe2MeWithAccessToken_(token, clientJid) {
/** @type {string} */
var pin = document.getElementById('pin-entry').value;
remoting.clientSession =
new remoting.ClientSession(
- remoting.hostJid, remoting.hostPublicKey,
+ remoting.hostJid, clientJid, remoting.hostPublicKey,
pin, 'spake2_hmac,spake2_plain', remoting.hostId,
remoting.ClientSession.Mode.ME2ME, onClientStateChange_);
// Don't log errors for cached JIDs.
@@ -521,3 +543,31 @@ function connectMe2MeWithAccessToken_(token) {
remoting.clientSession.createPluginAndConnect(
document.getElementById('session-mode'));
}
+
+/** @type {number} */
+remoting.wcsAccessTokenRefreshTimer = 0;
+
+function startAccessTokenRefreshTimer_() {
+ if (remoting.wcsAccessTokenRefreshTimer != 0) {
+ return;
+ }
+
+ /** @param {string} token */
+ var updateAccessToken = function(token) {
+ remoting.wcsSandbox.setAccessToken(token);
+ };
+ /** @param {remoting.Error} error */
+ var logError = function(error) {
+ console.error('updateAccessToken: Authentication failed: ' + error);
+ };
+ var refreshAccessToken = function() {
+ remoting.identity.callWithToken(updateAccessToken, logError);
+ };
+ /**
+ * A timer that polls for an updated access token.
+ * @type {number}
+ * @private
+ */
+ remoting.wcsAccessTokenRefreshTimer = setInterval(refreshAccessToken,
+ 60 * 1000);
+}
diff --git a/remoting/webapp/client_session.js b/remoting/webapp/client_session.js
index ee6da89..0c9b14d 100644
--- a/remoting/webapp/client_session.js
+++ b/remoting/webapp/client_session.js
@@ -24,6 +24,7 @@ var remoting = remoting || {};
/**
* @param {string} hostJid The jid of the host to connect to.
+ * @param {string} clientJid The jid of the WCS client.
* @param {string} hostPublicKey The base64 encoded version of the host's
* public key.
* @param {string} sharedSecret The access code for IT2Me or the PIN
@@ -38,18 +39,19 @@ var remoting = remoting || {};
* The callback to invoke when the session changes state.
* @constructor
*/
-remoting.ClientSession = function(hostJid, hostPublicKey, sharedSecret,
+remoting.ClientSession = function(hostJid, clientJid,
+ hostPublicKey, sharedSecret,
authenticationMethods, hostId,
mode, onStateChange) {
this.state = remoting.ClientSession.State.CREATED;
this.hostJid = hostJid;
+ this.clientJid = clientJid;
this.hostPublicKey = hostPublicKey;
this.sharedSecret = sharedSecret;
this.authenticationMethods = authenticationMethods;
this.hostId = hostId;
this.mode = mode;
- this.clientJid = '';
this.sessionId = '';
/** @type {remoting.ClientPlugin} */
this.plugin = null;
@@ -385,23 +387,21 @@ remoting.ClientSession.prototype.disconnect = function() {
this.logToServer.logClientSessionStateChange(
remoting.ClientSession.State.CLOSED,
remoting.ClientSession.ConnectionError.NONE, this.mode);
- if (remoting.wcs) {
- remoting.wcs.setOnIq(function(stanza) {});
- this.sendIq_(
- '<cli:iq ' +
- 'to="' + this.hostJid + '" ' +
- 'type="set" ' +
- 'id="session-terminate" ' +
- 'xmlns:cli="jabber:client">' +
- '<jingle ' +
- 'xmlns="urn:xmpp:jingle:1" ' +
- 'action="session-terminate" ' +
- 'initiator="' + this.clientJid + '" ' +
- 'sid="' + this.sessionId + '">' +
- '<reason><success/></reason>' +
- '</jingle>' +
- '</cli:iq>');
- }
+ remoting.wcsSandbox.setOnIq(null);
+ this.sendIq_(
+ '<cli:iq ' +
+ 'to="' + this.hostJid + '" ' +
+ 'type="set" ' +
+ 'id="session-terminate" ' +
+ 'xmlns:cli="jabber:client">' +
+ '<jingle ' +
+ 'xmlns="urn:xmpp:jingle:1" ' +
+ 'action="session-terminate" ' +
+ 'initiator="' + this.clientJid + '" ' +
+ 'sid="' + this.sessionId + '">' +
+ '<reason><success/></reason>' +
+ '</jingle>' +
+ '</cli:iq>');
this.removePlugin();
};
@@ -549,12 +549,7 @@ remoting.ClientSession.prototype.sendIq_ = function(msg) {
console.log(remoting.timestamp(), remoting.formatIq.prettifySendIq(msg));
// Send the stanza.
- if (remoting.wcs) {
- remoting.wcs.sendIq(msg);
- } else {
- console.error('Tried to send IQ before WCS was ready.');
- this.setState_(remoting.ClientSession.State.FAILED);
- }
+ remoting.wcsSandbox.sendIq(msg);
};
/**
@@ -564,10 +559,6 @@ remoting.ClientSession.prototype.sendIq_ = function(msg) {
* @return {void} Nothing.
*/
remoting.ClientSession.prototype.connectPluginToWcs_ = function() {
- this.clientJid = remoting.wcs.getJid();
- if (this.clientJid == '') {
- console.error('Tried to connect without a full JID.');
- }
remoting.formatIq.setJids(this.clientJid, this.hostJid);
var plugin = this.plugin;
var forwardIq = plugin.onIncomingIq.bind(plugin);
@@ -591,7 +582,7 @@ remoting.ClientSession.prototype.connectPluginToWcs_ = function() {
remoting.formatIq.prettifyReceiveIq(stanza));
forwardIq(stanza);
}
- remoting.wcs.setOnIq(onIncomingIq);
+ remoting.wcsSandbox.setOnIq(onIncomingIq);
this.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid,
this.sharedSecret, this.authenticationMethods,
this.hostId);
diff --git a/remoting/webapp/jscompiler_hacks.js b/remoting/webapp/jscompiler_hacks.js
index c956e78..c2fc838 100644
--- a/remoting/webapp/jscompiler_hacks.js
+++ b/remoting/webapp/jscompiler_hacks.js
@@ -44,6 +44,9 @@ HTMLEmbedElement.prototype.height;
/** @type {number} */
HTMLEmbedElement.prototype.width;
+/** @type {Window} */
+HTMLIFrameElement.prototype.contentWindow;
+
/** @constructor */
var MutationRecord = function() {};
diff --git a/remoting/webapp/log_to_server.js b/remoting/webapp/log_to_server.js
index 0ad8721..ff104d3 100644
--- a/remoting/webapp/log_to_server.js
+++ b/remoting/webapp/log_to_server.js
@@ -163,10 +163,6 @@ remoting.LogToServer.prototype.log = function(entry) {
entry.toDebugLog(1);
// Store a stanza for the entry.
this.pendingEntries.push(entry.toStanza());
- // Stop if there's no connection to the server.
- if (!remoting.wcs) {
- return;
- }
// Send all pending entries to the server.
console.log('Sending ' + this.pendingEntries.length + ' log ' +
((this.pendingEntries.length == 1) ? 'entry' : 'entries') +
@@ -177,7 +173,7 @@ remoting.LogToServer.prototype.log = function(entry) {
stanza += /** @type string */ this.pendingEntries.shift();
}
stanza += '</gr:log></cli:iq>';
- remoting.wcs.sendIq(stanza);
+ remoting.wcsSandbox.sendIq(stanza);
};
/**
diff --git a/remoting/webapp/main.html b/remoting/webapp/main.html
index d1b9a75..4d8aa94 100644
--- a/remoting/webapp/main.html
+++ b/remoting/webapp/main.html
@@ -45,8 +45,7 @@ found in the LICENSE file.
<script src="toolbar.js"></script>
<script src="ui_mode.js"></script>
<script src="xhr.js"></script>
- <script src="wcs.js"></script>
- <script src="wcs_loader.js"></script>
+ <script src="wcs_sandbox_container.js"></script>
<title i18n-content="PRODUCT_NAME"></title>
</head>
@@ -61,6 +60,8 @@ found in the LICENSE file.
<div id="daemon-plugin-container"></div>
+ <iframe id="wcs-sandbox" src="wcs_sandbox.html" hidden></iframe>
+
<header data-ui-mode="home" hidden>
<div>
<img src="chromoting48.png">
diff --git a/remoting/webapp/remoting.js b/remoting/webapp/remoting.js
index aa4c072..ae2e7b0 100644
--- a/remoting/webapp/remoting.js
+++ b/remoting/webapp/remoting.js
@@ -83,6 +83,9 @@ remoting.init = function() {
}
}
);
+ var sandbox = /** @type {HTMLIFrameElement} */
+ document.getElementById('wcs-sandbox');
+ remoting.wcsSandbox = new remoting.WcsSandboxContainer(sandbox.contentWindow);
remoting.identity.getEmail(remoting.onEmail, remoting.showErrorMessage);
diff --git a/remoting/webapp/wcs.js b/remoting/webapp/wcs.js
index 6451420..3a6d1e3 100644
--- a/remoting/webapp/wcs.js
+++ b/remoting/webapp/wcs.js
@@ -20,8 +20,7 @@ remoting.wcs = null;
* @constructor
* @param {remoting.WcsIqClient} wcsIqClient The WCS client.
* @param {string} token An OAuth2 access token.
- * @param {function(boolean): void} onReady A function called when the WCS
- * client has received a full JID.
+ * @param {function(string): void} onReady Called with the WCS client's JID.
*/
remoting.Wcs = function(wcsIqClient, token, onReady) {
/**
@@ -40,7 +39,7 @@ remoting.Wcs = function(wcsIqClient, token, onReady) {
/**
* The function called when the WCS client has received a full JID.
- * @type {function(boolean): void}
+ * @type {?function(string): void}
* @private
*/
this.onReady_ = onReady;
@@ -52,23 +51,6 @@ remoting.Wcs = function(wcsIqClient, token, onReady) {
*/
this.clientFullJid_ = '';
- var updateAccessToken = this.updateAccessToken_.bind(this);
- /** @param {remoting.Error} error */
- var onError = function(error) {
- console.error('updateAccessToken: Authentication failed: ' + error);
- };
-
- /**
- * A timer that polls for an updated access token.
- * @type {number}
- * @private
- */
- this.pollForUpdatedToken_ = setInterval(
- function() {
- remoting.identity.callWithToken(updateAccessToken, onError);
- },
- 60 * 1000);
-
/**
* A function called when an IQ stanza is received.
* @param {string} stanza The IQ stanza.
@@ -88,9 +70,8 @@ remoting.Wcs = function(wcsIqClient, token, onReady) {
*
* @param {string} tokenNew A (possibly updated) access token.
* @return {void} Nothing.
- * @private
*/
-remoting.Wcs.prototype.updateAccessToken_ = function(tokenNew) {
+remoting.Wcs.prototype.updateAccessToken = function(tokenNew) {
if (tokenNew != this.token_) {
this.token_ = tokenNew;
this.wcsIqClient_.updateAccessToken(this.token_);
@@ -110,8 +91,10 @@ remoting.Wcs.prototype.onMessage_ = function(msg) {
} else if (msg[0] == 'cfj') {
this.clientFullJid_ = msg[1];
console.log('Received JID: ' + this.clientFullJid_);
- this.onReady_(true);
- this.onReady_ = function(success) {};
+ if (this.onReady_) {
+ this.onReady_(this.clientFullJid_);
+ this.onReady_ = null;
+ }
}
};
diff --git a/remoting/webapp/wcs_loader.js b/remoting/webapp/wcs_loader.js
index 3a53848..c3ec7e1 100644
--- a/remoting/webapp/wcs_loader.js
+++ b/remoting/webapp/wcs_loader.js
@@ -30,26 +30,6 @@ remoting.WcsLoader = function() {
};
/**
- * Load WCS if necessary, then invoke the callback with an access token.
- *
- * @param {function(string): void} onReady The callback function, called with
- * an OAuth2 access token when WCS has been loaded.
- * @param {function(remoting.Error):void} onError Function to invoke with an
- * error code on failure.
- * @return {void} Nothing.
- */
-remoting.WcsLoader.load = function(onReady, onError) {
- if (!remoting.wcsLoader) {
- remoting.wcsLoader = new remoting.WcsLoader();
- }
- /** @param {string} token The OAuth2 access token. */
- var start = function(token) {
- remoting.wcsLoader.start_(token, onReady, onError);
- };
- remoting.identity.callWithToken(start, onError);
-};
-
-/**
* The URL of the GTalk gadget.
* @type {string}
* @private
@@ -65,13 +45,6 @@ remoting.WcsLoader.prototype.TALK_GADGET_URL_ =
remoting.WcsLoader.prototype.SCRIPT_NODE_ID_ = 'wcs-script-node';
/**
- * The attribute name indicating that the WCS has finished loading.
- * @type {string}
- * @private
- */
-remoting.WcsLoader.prototype.SCRIPT_NODE_LOADED_FLAG_ = 'wcs-script-loaded';
-
-/**
* Starts loading the WCS IQ client.
*
* When it's loaded, construct remoting.wcs as a wrapper for it.
@@ -80,34 +53,29 @@ remoting.WcsLoader.prototype.SCRIPT_NODE_LOADED_FLAG_ = 'wcs-script-loaded';
*
* @param {string} token An OAuth2 access token.
* @param {function(string): void} onReady The callback function, called with
- * an OAuth2 access token when WCS has been loaded.
+ * a client JID when WCS has been loaded.
* @param {function(remoting.Error):void} onError Function to invoke with an
* error code on failure.
* @return {void} Nothing.
- * @private
*/
-remoting.WcsLoader.prototype.start_ = function(token, onReady, onError) {
+remoting.WcsLoader.prototype.start = function(token, onReady, onError) {
var node = document.getElementById(this.SCRIPT_NODE_ID_);
- if (!node) {
- // The first time, there will be no script node, so create one.
- node = document.createElement('script');
- node.id = this.SCRIPT_NODE_ID_;
- node.src = this.TALK_GADGET_URL_ + 'iq?access_token=' + token;
- node.type = 'text/javascript';
- document.body.insertBefore(node, document.body.firstChild);
- } else if (node.hasAttribute(this.SCRIPT_NODE_LOADED_FLAG_)) {
- // Subsequently, explicitly invoke onReady if onload has already fired.
- // TODO(jamiewalch): It's possible that the WCS client has not finished
- // initializing. Add support for multiple callbacks to the remoting.Wcs
- // class to address this.
- onReady(token);
+ if (node) {
+ console.error('Multiple calls to WcsLoader.start are not allowed.');
+ onError(remoting.Error.UNEXPECTED);
return;
}
+
+ // Create a script node to load the WCS driver.
+ node = document.createElement('script');
+ node.id = this.SCRIPT_NODE_ID_;
+ node.src = this.TALK_GADGET_URL_ + 'iq?access_token=' + token;
+ node.type = 'text/javascript';
+ document.body.insertBefore(node, document.body.firstChild);
+
/** @type {remoting.WcsLoader} */
var that = this;
var onLoad = function() {
- var typedNode = /** @type {Element} */ (node);
- typedNode.setAttribute(that.SCRIPT_NODE_LOADED_FLAG_, true);
that.constructWcs_(token, onReady);
};
var onLoadError = function(event) {
@@ -143,7 +111,5 @@ remoting.WcsLoader.prototype.start_ = function(token, onReady, onError) {
*/
remoting.WcsLoader.prototype.constructWcs_ = function(token, onReady) {
remoting.wcs = new remoting.Wcs(
- remoting.wcsLoader.wcsIqClient,
- token,
- function() { onReady(token); });
+ remoting.wcsLoader.wcsIqClient, token, onReady);
};
diff --git a/remoting/webapp/wcs_sandbox.html b/remoting/webapp/wcs_sandbox.html
new file mode 100644
index 0000000..bbc9d7f
--- /dev/null
+++ b/remoting/webapp/wcs_sandbox.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<!--
+Copyright 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.
+-->
+
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script src="xhr_proxy.js"></script>
+ <script src="wcs.js"></script>
+ <script src="wcs_loader.js"></script>
+ <script src="wcs_sandbox_content.js"></script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/remoting/webapp/wcs_sandbox_container.js b/remoting/webapp/wcs_sandbox_container.js
new file mode 100644
index 0000000..68f2fbd
--- /dev/null
+++ b/remoting/webapp/wcs_sandbox_container.js
@@ -0,0 +1,244 @@
+/* Copyright 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.
+ */
+
+/**
+ * @fileoverview
+ * The application side of the application/sandbox WCS interface, used by the
+ * application to exchange messages with the sandbox.
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * @param {Window} sandbox The Javascript Window object representing the
+ * sandboxed WCS driver.
+ * @constructor
+ */
+remoting.WcsSandboxContainer = function(sandbox) {
+ this.sandbox_ = sandbox;
+ /** @type {?function(string):void} */
+ this.onReady_ = null;
+ /** @type {?function(remoting.Error):void} */
+ this.onError_ = null;
+ /** @type {?function(string):void} */
+ this.onIq_ = null;
+ /** @type {Object.<number, XMLHttpRequest>} */
+ this.pendingXhrs_ = {};
+
+ window.addEventListener('message', this.onMessage_.bind(this), false);
+};
+
+/**
+ * @param {?function(string):void} onReady Callback invoked with the client JID
+ * when the WCS code has loaded.
+ * @return {void} Nothing.
+ */
+remoting.WcsSandboxContainer.prototype.setOnReady = function(onReady) {
+ this.onReady_ = onReady;
+};
+
+/**
+ * @param {?function(remoting.Error):void} onError Callback invoked if the WCS
+ * code cannot be loaded.
+ * @return {void} Nothing.
+ */
+remoting.WcsSandboxContainer.prototype.setOnError = function(onError) {
+ this.onError_ = onError;
+};
+
+/**
+ * @param {?function(string):void} onIq Callback invoked when an IQ stanza is
+ * received.
+ * @return {void} Nothing.
+ */
+remoting.WcsSandboxContainer.prototype.setOnIq = function(onIq) {
+ this.onIq_ = onIq;
+};
+
+/**
+ * @param {string} token The access token.
+ * @return {void}
+ */
+remoting.WcsSandboxContainer.prototype.setAccessToken = function(token) {
+ var message = {
+ 'command': 'setAccessToken',
+ 'token': token
+ };
+ this.sandbox_.postMessage(message, '*');
+};
+
+/**
+ * @param {string} stanza The IQ stanza to send.
+ * @return {void}
+ */
+remoting.WcsSandboxContainer.prototype.sendIq = function(stanza) {
+ var message = {
+ 'command': 'sendIq',
+ 'stanza': stanza
+ };
+ this.sandbox_.postMessage(message, '*');
+};
+
+/**
+ * Event handler to process messages from the sandbox.
+ *
+ * @param {Event} event
+ */
+remoting.WcsSandboxContainer.prototype.onMessage_ = function(event) {
+ switch (event.data['command']) {
+
+ case 'onReady':
+ /** @type {string} */
+ var clientJid = event.data['clientJid'];
+ if (clientJid === undefined) {
+ console.error('onReady: missing client JID');
+ break;
+ }
+ if (this.onReady_) {
+ this.onReady_(clientJid);
+ }
+ break;
+
+ case 'onError':
+ /** @type {remoting.Error} */
+ var error = event.data['error'];
+ if (error === undefined) {
+ console.error('onError: missing error code');
+ break;
+ }
+ this.onError_(error);
+ break;
+
+ case 'onIq':
+ /** @type {string} */
+ var stanza = event.data['stanza'];
+ if (stanza === undefined) {
+ console.error('onIq: missing IQ stanza');
+ break;
+ }
+ if (this.onIq_) {
+ this.onIq_(stanza);
+ }
+ break;
+
+ case 'sendXhr':
+ /** @type {number} */
+ var id = event.data['id'];
+ if (id === undefined) {
+ console.error('sendXhr: missing id');
+ break;
+ }
+ /** @type {Object} */
+ var parameters = event.data['parameters'];
+ if (parameters === undefined) {
+ console.error('sendXhr: missing parameters');
+ break;
+ }
+ /** @type {string} */
+ var method = parameters['method'];
+ if (method === undefined) {
+ console.error('sendXhr: missing method');
+ break;
+ }
+ /** @type {string} */
+ var url = parameters['url'];
+ if (url === undefined) {
+ console.error('sendXhr: missing url');
+ break;
+ }
+ /** @type {string} */
+ var data = parameters['data'];
+ if (data === undefined) {
+ console.error('sendXhr: missing data');
+ break;
+ }
+ /** @type {string|undefined}*/
+ var user = parameters['user'];
+ /** @type {string|undefined}*/
+ var password = parameters['password'];
+ var xhr = new XMLHttpRequest;
+ this.pendingXhrs_[id] = xhr;
+ xhr.open(method, url, true, user, password);
+ /** @type {Object} */
+ var headers = parameters['headers'];
+ if (headers) {
+ for (var header in headers) {
+ xhr.setRequestHeader(header, headers[header]);
+ }
+ }
+ xhr.onreadystatechange = this.onReadyStateChange_.bind(this, id);
+ xhr.send(data);
+ break;
+
+ case 'abortXhr':
+ var id = event.data['id'];
+ if (id === undefined) {
+ console.error('abortXhr: missing id');
+ break;
+ }
+ var xhr = this.pendingXhrs_[id]
+ if (!xhr) {
+ // It's possible for an abort and a reply to cross each other on the
+ // IPC channel. In that case, we silently ignore the abort.
+ break;
+ }
+ xhr.abort();
+ break;
+
+ default:
+ console.error('Unexpected message:', event.data['command'], event.data);
+ }
+};
+
+/**
+ * Return a "copy" of an XHR object suitable for postMessage. Specifically,
+ * remove all non-serializable members such as functions.
+ *
+ * @param {XMLHttpRequest} xhr The XHR to serialize.
+ * @return {Object} A serializable version of the input.
+ */
+function sanitizeXhr_(xhr) {
+ /** @type {Object} */
+ var result = {
+ readyState: xhr.readyState,
+ response: xhr.response,
+ responseText: xhr.responseText,
+ responseType: xhr.responseType,
+ responseXML: xhr.responseXML,
+ status: xhr.status,
+ statusText: xhr.statusText,
+ withCredentials: xhr.withCredentials
+ };
+ return result;
+}
+
+/**
+ * @param {number} id The unique ID of the XHR for which the state has changed.
+ * @private
+ */
+remoting.WcsSandboxContainer.prototype.onReadyStateChange_ = function(id) {
+ var xhr = this.pendingXhrs_[id];
+ if (!xhr) {
+ // XHRs are only removed when they have completed, in which case no
+ // further callbacks should be received.
+ console.error('Unexpected callback for xhr', id);
+ return;
+ }
+ var message = {
+ 'command': 'xhrStateChange',
+ 'id': id,
+ 'xhr': sanitizeXhr_(xhr)
+ };
+ this.sandbox_.postMessage(message, '*');
+ if (xhr.readyState == 4) {
+ delete this.pendingXhrs_[id];
+ }
+}
+
+/** @type {remoting.WcsSandboxContainer} */
+remoting.wcsSandbox = null; \ No newline at end of file
diff --git a/remoting/webapp/wcs_sandbox_content.js b/remoting/webapp/wcs_sandbox_content.js
new file mode 100644
index 0000000..c297900
--- /dev/null
+++ b/remoting/webapp/wcs_sandbox_content.js
@@ -0,0 +1,222 @@
+/* Copyright 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.
+ */
+
+/**
+ * @fileoverview
+ * The sandbox side of the application/sandbox WCS interface, used by the
+ * sandbox to exchange messages with the application.
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/** @constructor */
+remoting.WcsSandboxContent = function() {
+ /**
+ * @type {Window}
+ * @private
+ */
+ this.parentWindow_ = null;
+ /**
+ * @type {number}
+ * @private
+ */
+ this.nextXhrId_ = 0;
+ /**
+ * @type {Object.<number, XMLHttpRequest>}
+ * @private
+ */
+ this.pendingXhrs_ = {};
+
+ window.addEventListener('message', this.onMessage_.bind(this), false);
+};
+
+/**
+ * Event handler to process messages from the application.
+ *
+ * @param {Event} event
+ */
+remoting.WcsSandboxContent.prototype.onMessage_ = function(event) {
+ this.parentWindow_ = event.source;
+
+ switch (event.data['command']) {
+
+ case 'sendIq':
+ /** @type {string} */
+ var stanza = event.data['stanza'];
+ if (stanza === undefined) {
+ console.error('sendIq: missing IQ stanza.');
+ break;
+ }
+ if (remoting.wcs) {
+ remoting.wcs.sendIq(stanza);
+ } else {
+ console.error('Dropping IQ stanza:', stanza);
+ }
+ break;
+
+ case 'setAccessToken':
+ /** @type {string} */
+ var token = event.data['token'];
+ if (token === undefined) {
+ console.error('setAccessToken: missing access token.');
+ break;
+ }
+ // The WCS driver JS requires that remoting.wcsLoader be a global
+ // variable, so it can't be a member of this class.
+ // TODO(jamiewalch): remoting.wcs doesn't need to be global and should
+ // be made a member (http://crbug.com/172348).
+ if (remoting.wcs) {
+ remoting.wcs.updateAccessToken(token);
+ } else if (!remoting.wcsLoader) {
+ remoting.wcsLoader = new remoting.WcsLoader();
+ remoting.wcsLoader.start(token,
+ this.onReady_.bind(this),
+ this.onError_.bind(this));
+ }
+ break;
+
+ case 'xhrStateChange':
+ /** @type {number} */
+ var id = event.data['id'];
+ if (id === undefined) {
+ console.error('xhrStateChange: missing id.');
+ break;
+ }
+ var pendingXhr = this.pendingXhrs_[id];
+ if (!pendingXhr) {
+ console.error('xhrStateChange: unrecognized id:', id);
+ break;
+ }
+ /** @type {XMLHttpRequest} */
+ var xhr = event.data['xhr'];
+ if (xhr === undefined) {
+ console.error('xhrStateChange: missing xhr');
+ break;
+ }
+ for (var member in xhr) {
+ pendingXhr[member] = xhr[member];
+ }
+ if (xhr.readyState == 4) {
+ delete this.pendingXhrs_[id];
+ }
+ if (pendingXhr.onreadystatechange) {
+ pendingXhr.onreadystatechange();
+ }
+ break;
+
+ default:
+ console.error('Unexpected message:', event.data['command'], event.data);
+ }
+};
+
+/**
+ * Callback method to indicate that the WCS driver has loaded and provide the
+ * full JID of the client.
+ *
+ * @param {string} clientJid The full JID of the WCS client.
+ * @private
+ */
+remoting.WcsSandboxContent.prototype.onReady_ = function(clientJid) {
+ remoting.wcs.setOnIq(this.onIq_.bind(this));
+ var message = {
+ 'command': 'onReady',
+ 'clientJid': clientJid
+ };
+ this.parentWindow_.postMessage(message, '*');
+};
+
+/**
+ * Callback method to indicate that something went wrong loading the WCS driver.
+ *
+ * @param {remoting.Error} error Details of the error.
+ * @private
+ */
+remoting.WcsSandboxContent.prototype.onError_ = function(error) {
+ var message = {
+ 'command': 'onError',
+ 'error': error
+ };
+ this.parentWindow_.postMessage(message, '*');
+};
+
+/**
+ * Forward an XHR to the container process to send. This is analogous to XHR's
+ * send method.
+ *
+ * @param {remoting.XMLHttpRequestProxy} xhr The XHR to send.
+ * @return {number} The unique ID allocated to the XHR. Used to abort it.
+ */
+remoting.WcsSandboxContent.prototype.sendXhr = function(xhr) {
+ var id = this.nextXhrId_++;
+ this.pendingXhrs_[id] = xhr;
+ var message = {
+ 'command': 'sendXhr',
+ 'id': id,
+ 'parameters': xhr.sandbox_ipc
+ };
+ this.parentWindow_.postMessage(message, '*');
+ delete xhr.sandbox_ipc;
+ return id;
+};
+
+/**
+ * Abort a forwarded XHR. This is analogous to XHR's abort method.
+ *
+ * @param {number} id The unique ID of the XHR to abort, as returned by sendXhr.
+ */
+remoting.WcsSandboxContent.prototype.abortXhr = function(id) {
+ if (!this.pendingXhrs_[id]) {
+ // The XHR is removed when it reaches the "ready" state. Calling abort
+ // subsequently is unusual, but legal, so just silently ignore the request
+ // in this case.
+ return;
+ }
+ var message = {
+ 'command': 'abortXhr',
+ 'id': id
+ };
+ this.parentWindow_.postMessage(message, '*');
+};
+
+/**
+ * Callback to indicate than an IQ stanza has been received from the WCS
+ * driver, and should be forwarded to the main process.
+ *
+ * @param {string} stanza
+ * @private
+ */
+remoting.WcsSandboxContent.prototype.onIq_ = function(stanza) {
+ remoting.wcs.setOnIq(this.onIq_.bind(this));
+ var message = {
+ 'command': 'onIq',
+ 'stanza': stanza
+ };
+ this.parentWindow_.postMessage(message, '*');
+};
+
+/**
+ * Entry point for the WCS sandbox process.
+ */
+function onSandboxInit() {
+ // The WCS code registers for a couple of events that aren't supported in
+ // Apps V2, so ignore those for now.
+ var oldAEL = window.addEventListener;
+ window.addEventListener = function(type, listener, useCapture) {
+ if (type == 'beforeunload' || type == 'unload') {
+ return;
+ }
+ oldAEL(type, listener, useCapture);
+ };
+
+ remoting.sandboxContent = new remoting.WcsSandboxContent();
+}
+
+window.addEventListener('load', onSandboxInit, false);
+
+/** @type {remoting.WcsSandboxContent} */
+remoting.sandboxContent = null; \ No newline at end of file
diff --git a/remoting/webapp/xhr_proxy.js b/remoting/webapp/xhr_proxy.js
new file mode 100644
index 0000000..4c45780
--- /dev/null
+++ b/remoting/webapp/xhr_proxy.js
@@ -0,0 +1,93 @@
+/* Copyright 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.
+ */
+
+/**
+ * @fileoverview
+ * The sandbox isn't allowed to make XHRs, so they have to be proxied to the
+ * main process. The XMLHttpRequestProxy class is API-compatible with the
+ * XMLHttpRequest class, but forwards the requests to the main process where
+ * they can be serviced. The forwarding of XHRs and responses is handled by
+ * the WcsSandboxContent class; this class is just a thin wrapper to hook XHR
+ * creations by JS running in the sandbox.
+ *
+ * Because XMLHttpRequest is implemented natively, and because the intent is
+ * to replace its functionality entirely, prototype linking is not a suitable
+ * approach here, so much of the interface definition is duplicated from the
+ * w3c specification: http://www.w3.org/TR/XMLHttpRequest
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * @constructor
+ * @extends {XMLHttpRequest}
+ */
+remoting.XMLHttpRequestProxy = function() {
+ /**
+ * @type {{headers: Object}}
+ */
+ this.sandbox_ipc = {
+ headers: {}
+ };
+ /**
+ * @type {number}
+ * @private
+ */
+ this.xhr_id_ = -1;
+};
+
+remoting.XMLHttpRequestProxy.prototype.open = function(
+ method, url, async, user, password) {
+ if (!async) {
+ console.warn('Synchronous XHRs are not supported.');
+ }
+ this.sandbox_ipc.method = method;
+ this.sandbox_ipc.url = url.toString();
+ this.sandbox_ipc.user = user;
+ this.sandbox_ipc.password = password;
+};
+
+remoting.XMLHttpRequestProxy.prototype.send = function(data) {
+ if (remoting.sandboxContent) {
+ this.sandbox_ipc.data = data;
+ this.xhr_id_ = remoting.sandboxContent.sendXhr(this);
+ }
+};
+
+remoting.XMLHttpRequestProxy.prototype.setRequestHeader = function(
+ header, value) {
+ this.sandbox_ipc.headers[header] = value;
+};
+
+remoting.XMLHttpRequestProxy.prototype.abort = function() {
+ if (this.xhr_id_ != -1) {
+ remoting.sandboxContent.abortXhr(this.xhr_id_);
+ }
+};
+
+remoting.XMLHttpRequestProxy.prototype.getResponseHeader = function(header) {
+ console.error('Sandbox: unproxied getResponseHeader(' + header + ') called.');
+};
+
+remoting.XMLHttpRequestProxy.prototype.getAllResponseHeaders = function() {
+ console.error('Sandbox: unproxied getAllResponseHeaders called.');
+};
+
+remoting.XMLHttpRequestProxy.prototype.overrideMimeType = function() {
+ console.error('Sandbox: unproxied overrideMimeType called.');
+};
+
+remoting.XMLHttpRequestProxy.prototype.UNSENT = 0;
+remoting.XMLHttpRequestProxy.prototype.OPENED = 1;
+remoting.XMLHttpRequestProxy.prototype.HEADERS_RECEIVED = 2;
+remoting.XMLHttpRequestProxy.prototype.LOADING = 3;
+remoting.XMLHttpRequestProxy.prototype.DONE = 4;
+
+// Since the WCS driver code constructs XHRs directly, the only mechanism for
+// proxying them is to replace the XMLHttpRequest constructor.
+//XMLHttpRequest = remoting.XMLHttpRequestProxy;