/* 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.onLocalJid_ = null; /** @type {?function(remoting.Error):void} */ this.onError_ = null; /** @type {?function(string):void} */ this.onIq_ = null; /** @type {Object.} */ this.pendingXhrs_ = {}; window.addEventListener('message', this.onMessage_.bind(this), false); }; /** * @param {?function(string):void} onLocalJid Callback invoked with the client * JID when the WCS code has loaded. Note that this may be called more than * once (potentially with a different JID) if the WCS node is reloaded for * any reason. * @return {void} Nothing. */ remoting.WcsSandboxContainer.prototype.setOnLocalJid = function(onLocalJid) { this.onLocalJid_ = onLocalJid; }; /** * @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 'onLocalJid': /** @type {string} */ var clientJid = event.data['clientJid']; if (clientJid === undefined) { console.error('onReady: missing client JID'); break; } if (this.onLocalJid_) { this.onLocalJid_(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;