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 sandbox side of the application/sandbox WCS interface, used by the
9 * sandbox to exchange messages with the application.
10 */
11
12'use strict';
13
14/** @suppress {duplicate} */
15var remoting = remoting || {};
16
17/** @constructor */
18remoting.WcsSandboxContent = function() {
19  /**
20   * @type {Window}
21   * @private
22   */
23  this.parentWindow_ = null;
24  /**
25   * @type {number}
26   * @private
27   */
28  this.nextXhrId_ = 0;
29  /**
30   * @type {Object.<number, XMLHttpRequest>}
31   * @private
32   */
33  this.pendingXhrs_ = {};
34
35  window.addEventListener('message', this.onMessage_.bind(this), false);
36};
37
38/**
39 * Event handler to process messages from the application.
40 *
41 * @param {Event} event
42 */
43remoting.WcsSandboxContent.prototype.onMessage_ = function(event) {
44  this.parentWindow_ = event.source;
45
46  switch (event.data['command']) {
47
48    case 'proxyXhrs':
49      // Since the WCS driver code constructs XHRs directly, the only
50      // mechanism for proxying them is to replace the XMLHttpRequest
51      // constructor.
52      XMLHttpRequest = remoting.XMLHttpRequestProxy;
53      break;
54
55    case 'sendIq':
56      /** @type {string} */
57      var stanza = event.data['stanza'];
58      if (stanza === undefined) {
59        console.error('sendIq: missing IQ stanza.');
60        break;
61      }
62      if (remoting.wcs) {
63        remoting.wcs.sendIq(stanza);
64      } else {
65        console.error('Dropping IQ stanza:', stanza);
66      }
67      break;
68
69    case 'setAccessToken':
70      /** @type {string} */
71      var token = event.data['token'];
72      if (token === undefined) {
73        console.error('setAccessToken: missing access token.');
74        break;
75      }
76      // The WCS driver JS requires that remoting.wcsLoader be a global
77      // variable, so it can't be a member of this class.
78      // TODO(jamiewalch): remoting.wcs doesn't need to be global and should
79      // be made a member (http://crbug.com/172348).
80      if (remoting.wcs) {
81        remoting.wcs.updateAccessToken(token);
82      } else if (!remoting.wcsLoader) {
83        remoting.wcsLoader = new remoting.WcsLoader();
84        remoting.wcsLoader.start(token,
85                                 this.onLocalJid_.bind(this),
86                                 this.onError_.bind(this));
87      }
88      break;
89
90    case 'xhrStateChange':
91      /** @type {number} */
92      var id = event.data['id'];
93      if (id === undefined) {
94        console.error('xhrStateChange: missing id.');
95        break;
96      }
97      var pendingXhr = this.pendingXhrs_[id];
98      if (!pendingXhr) {
99        console.error('xhrStateChange: unrecognized id:', id);
100        break;
101      }
102      /** @type {XMLHttpRequest} */
103      var xhr = event.data['xhr'];
104      if (xhr === undefined) {
105        console.error('xhrStateChange: missing xhr');
106        break;
107      }
108      for (var member in xhr) {
109        pendingXhr[member] = xhr[member];
110      }
111      if (xhr.readyState == 4) {
112        delete this.pendingXhrs_[id];
113      }
114      if (pendingXhr.onreadystatechange) {
115        pendingXhr.onreadystatechange();
116      }
117      break;
118
119    default:
120      console.error('Unexpected message:', event.data['command'], event.data);
121  }
122};
123
124/**
125 * Callback method to indicate that the WCS driver has loaded and provide the
126 * full JID of the client.
127 *
128 * @param {string} localJid The full JID of the WCS client.
129 * @private
130 */
131remoting.WcsSandboxContent.prototype.onLocalJid_ = function(localJid) {
132  remoting.wcs.setOnIq(this.onIq_.bind(this));
133  var message = {
134    'command': 'onLocalJid',
135    'localJid': localJid
136  };
137  this.parentWindow_.postMessage(message, '*');
138};
139
140/**
141 * Callback method to indicate that something went wrong loading the WCS driver.
142 *
143 * @param {remoting.Error} error Details of the error.
144 * @private
145 */
146remoting.WcsSandboxContent.prototype.onError_ = function(error) {
147  var message = {
148    'command': 'onError',
149    'error': error
150  };
151  this.parentWindow_.postMessage(message, '*');
152};
153
154/**
155 * Forward an XHR to the container process to send. This is analogous to XHR's
156 * send method.
157 *
158 * @param {remoting.XMLHttpRequestProxy} xhr The XHR to send.
159 * @return {number} The unique ID allocated to the XHR. Used to abort it.
160 */
161remoting.WcsSandboxContent.prototype.sendXhr = function(xhr) {
162  var id = this.nextXhrId_++;
163  this.pendingXhrs_[id] = xhr;
164  var message = {
165    'command': 'sendXhr',
166    'id': id,
167    'parameters': xhr.sandbox_ipc
168  };
169  this.parentWindow_.postMessage(message, '*');
170  delete xhr.sandbox_ipc;
171  return id;
172};
173
174/**
175 * Abort a forwarded XHR. This is analogous to XHR's abort method.
176 *
177 * @param {number} id The unique ID of the XHR to abort, as returned by sendXhr.
178 */
179remoting.WcsSandboxContent.prototype.abortXhr = function(id) {
180  if (!this.pendingXhrs_[id]) {
181    // The XHR is removed when it reaches the "ready" state. Calling abort
182    // subsequently is unusual, but legal, so just silently ignore the request
183    // in this case.
184    return;
185  }
186  var message = {
187    'command': 'abortXhr',
188    'id': id
189  };
190  this.parentWindow_.postMessage(message, '*');
191};
192
193/**
194 * Callback to indicate than an IQ stanza has been received from the WCS
195 * driver, and should be forwarded to the main process.
196 *
197 * @param {string} stanza
198 * @private
199 */
200remoting.WcsSandboxContent.prototype.onIq_ = function(stanza) {
201  remoting.wcs.setOnIq(this.onIq_.bind(this));
202  var message = {
203    'command': 'onIq',
204    'stanza': stanza
205  };
206  this.parentWindow_.postMessage(message, '*');
207};
208
209/**
210 * Entry point for the WCS sandbox process.
211 */
212function onSandboxInit() {
213  // The WCS code registers for a couple of events that aren't supported in
214  // Apps V2, so ignore those for now.
215  var oldAEL = window.addEventListener;
216  window.addEventListener = function(type, listener, useCapture) {
217    if (type == 'beforeunload' || type == 'unload') {
218      return;
219    }
220    oldAEL(type, listener, useCapture);
221  };
222
223  remoting.settings = new remoting.Settings();
224  remoting.sandboxContent = new remoting.WcsSandboxContent();
225}
226
227window.addEventListener('load', onSandboxInit, false);
228
229/** @type {remoting.WcsSandboxContent} */
230remoting.sandboxContent = null;
231