wcs_sandbox_container.js revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1/* Copyright 2013 The Chromium Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6/**
7 * @fileoverview
8 * The application side of the application/sandbox WCS interface, used by the
9 * application to exchange messages with the sandbox.
10 */
11
12'use strict';
13
14/** @suppress {duplicate} */
15var remoting = remoting || {};
16
17/**
18 * @param {Window} sandbox The Javascript Window object representing the
19 *     sandboxed WCS driver.
20 * @constructor
21 */
22remoting.WcsSandboxContainer = function(sandbox) {
23  this.sandbox_ = sandbox;
24  /** @type {?function(string):void} */
25  this.onReady_ = null;
26  /** @type {?function(remoting.Error):void} */
27  this.onError_ = null;
28  /** @type {?function(string):void} */
29  this.onIq_ = null;
30  /** @type {Object.<number, XMLHttpRequest>} */
31  this.pendingXhrs_ = {};
32
33  window.addEventListener('message', this.onMessage_.bind(this), false);
34};
35
36/**
37 * @param {?function(string):void} onReady Callback invoked with the client JID
38 *     when the WCS code has loaded.
39 * @return {void} Nothing.
40 */
41remoting.WcsSandboxContainer.prototype.setOnReady = function(onReady) {
42  this.onReady_ = onReady;
43};
44
45/**
46 * @param {?function(remoting.Error):void} onError Callback invoked if the WCS
47 *     code cannot be loaded.
48 * @return {void} Nothing.
49 */
50remoting.WcsSandboxContainer.prototype.setOnError = function(onError) {
51  this.onError_ = onError;
52};
53
54/**
55 * @param {?function(string):void} onIq Callback invoked when an IQ stanza is
56 *     received.
57 * @return {void} Nothing.
58 */
59remoting.WcsSandboxContainer.prototype.setOnIq = function(onIq) {
60  this.onIq_ = onIq;
61};
62
63/**
64 * @param {string} token The access token.
65 * @return {void}
66 */
67remoting.WcsSandboxContainer.prototype.setAccessToken = function(token) {
68  var message = {
69    'command': 'setAccessToken',
70    'token': token
71  };
72  this.sandbox_.postMessage(message, '*');
73};
74
75/**
76 * @param {string} stanza The IQ stanza to send.
77 * @return {void}
78 */
79remoting.WcsSandboxContainer.prototype.sendIq = function(stanza) {
80  var message = {
81    'command': 'sendIq',
82    'stanza': stanza
83  };
84  this.sandbox_.postMessage(message, '*');
85};
86
87/**
88 * Event handler to process messages from the sandbox.
89 *
90 * @param {Event} event
91 */
92remoting.WcsSandboxContainer.prototype.onMessage_ = function(event) {
93  switch (event.data['command']) {
94
95    case 'onReady':
96      /** @type {string} */
97      var clientJid = event.data['clientJid'];
98      if (clientJid === undefined) {
99        console.error('onReady: missing client JID');
100        break;
101      }
102      if (this.onReady_) {
103        this.onReady_(clientJid);
104      }
105      break;
106
107    case 'onError':
108      /** @type {remoting.Error} */
109      var error = event.data['error'];
110      if (error === undefined) {
111        console.error('onError: missing error code');
112        break;
113      }
114      this.onError_(error);
115      break;
116
117    case 'onIq':
118      /** @type {string} */
119      var stanza = event.data['stanza'];
120      if (stanza === undefined) {
121        console.error('onIq: missing IQ stanza');
122        break;
123      }
124      if (this.onIq_) {
125        this.onIq_(stanza);
126      }
127      break;
128
129    case 'sendXhr':
130      /** @type {number} */
131      var id = event.data['id'];
132      if (id === undefined) {
133        console.error('sendXhr: missing id');
134        break;
135      }
136      /** @type {Object} */
137      var parameters = event.data['parameters'];
138      if (parameters === undefined) {
139        console.error('sendXhr: missing parameters');
140        break;
141      }
142      /** @type {string} */
143      var method = parameters['method'];
144      if (method === undefined) {
145        console.error('sendXhr: missing method');
146        break;
147      }
148      /** @type {string} */
149      var url = parameters['url'];
150      if (url === undefined) {
151        console.error('sendXhr: missing url');
152        break;
153      }
154      /** @type {string} */
155      var data = parameters['data'];
156      if (data === undefined) {
157        console.error('sendXhr: missing data');
158        break;
159      }
160      /** @type {string|undefined}*/
161      var user = parameters['user'];
162      /** @type {string|undefined}*/
163      var password = parameters['password'];
164      var xhr = new XMLHttpRequest;
165      this.pendingXhrs_[id] = xhr;
166      xhr.open(method, url, true, user, password);
167      /** @type {Object} */
168      var headers = parameters['headers'];
169      if (headers) {
170        for (var header in headers) {
171          xhr.setRequestHeader(header, headers[header]);
172        }
173      }
174      xhr.onreadystatechange = this.onReadyStateChange_.bind(this, id);
175      xhr.send(data);
176      break;
177
178    case 'abortXhr':
179      var id = event.data['id'];
180      if (id === undefined) {
181        console.error('abortXhr: missing id');
182        break;
183      }
184      var xhr = this.pendingXhrs_[id]
185      if (!xhr) {
186        // It's possible for an abort and a reply to cross each other on the
187        // IPC channel. In that case, we silently ignore the abort.
188        break;
189      }
190      xhr.abort();
191      break;
192
193    default:
194      console.error('Unexpected message:', event.data['command'], event.data);
195  }
196};
197
198/**
199 * Return a "copy" of an XHR object suitable for postMessage. Specifically,
200 * remove all non-serializable members such as functions.
201 *
202 * @param {XMLHttpRequest} xhr The XHR to serialize.
203 * @return {Object} A serializable version of the input.
204 */
205function sanitizeXhr_(xhr) {
206  /** @type {Object} */
207  var result = {
208    readyState: xhr.readyState,
209    response: xhr.response,
210    responseText: xhr.responseText,
211    responseType: xhr.responseType,
212    responseXML: xhr.responseXML,
213    status: xhr.status,
214    statusText: xhr.statusText,
215    withCredentials: xhr.withCredentials
216  };
217  return result;
218}
219
220/**
221 * @param {number} id The unique ID of the XHR for which the state has changed.
222 * @private
223 */
224remoting.WcsSandboxContainer.prototype.onReadyStateChange_ = function(id) {
225  var xhr = this.pendingXhrs_[id];
226  if (!xhr) {
227    // XHRs are only removed when they have completed, in which case no
228    // further callbacks should be received.
229    console.error('Unexpected callback for xhr', id);
230    return;
231  }
232  var message = {
233    'command': 'xhrStateChange',
234    'id': id,
235    'xhr': sanitizeXhr_(xhr)
236  };
237  this.sandbox_.postMessage(message, '*');
238  if (xhr.readyState == 4) {
239    delete this.pendingXhrs_[id];
240  }
241}
242
243/** @type {remoting.WcsSandboxContainer} */
244remoting.wcsSandbox = null;