NaClAM.js revision f2477e01787aa58f445919b809d89e252beef54f
1// Copyright (c) 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
5function NaClAMMessage() {
6  this.header = {};
7  this.frames = new Array();
8}
9
10NaClAMMessage.prototype.reset = function() {
11  this.header = {};
12  this.frames = new Array();
13}
14
15function NaClAM(embedId) {
16  this.embedId = embedId;
17  this.requestId = 0;
18  this.message = new NaClAMMessage();
19  this.state = 0;
20  this.framesLeft = 0;
21  this.listeners_ = Object.create(null);
22  this.handleMesssage_ = this.handleMesssage_.bind(this);
23}
24
25NaClAM.prototype.enable = function() {
26  window.addEventListener('message', this.handleMesssage_, true);
27}
28
29NaClAM.prototype.disable = function() {
30  window.removeEventListener('message', this.handleMesssage_, true);
31}
32
33NaClAM.prototype.log_ = function(msg) {
34  console.log(msg);
35}
36
37NaClAM.prototype.handleMesssage_ = function(event) {
38  var STATE_WAITING_FOR_HEADER = 0;
39  var STATE_COLLECTING_FRAMES = 1;
40  if (this.state == STATE_WAITING_FOR_HEADER) {
41    var header;
42    try {
43      header = JSON.parse(String(event.data));
44    } catch (e) {
45      console.log(e);
46      console.log(event.data);
47      return;
48    }
49    // Special case our log print command
50    if (header['cmd'] == 'NaClAMPrint') {
51      this.log_(header['print'])
52        return;
53    }
54    if (typeof(header['request']) != "number") {
55      console.log('Header message requestId is not a number.');
56      return;
57    }
58    if (typeof(header['frames']) != "number") {
59      console.log('Header message frames is not a number.');
60      return;
61    }
62    this.framesLeft = header['frames'];
63    this.state = STATE_COLLECTING_FRAMES;
64    this.message.header = header;
65  } else if (this.state == STATE_COLLECTING_FRAMES) {
66    this.framesLeft--;
67    this.message.frames.push(event.data);
68  }
69  if (this.state == STATE_COLLECTING_FRAMES && this.framesLeft == 0) {
70    this.dispatchEvent(this.message);
71    this.message.reset();
72    this.state = STATE_WAITING_FOR_HEADER;
73  }
74}
75
76NaClAM.prototype.messageHeaderIsValid_ = function(header) {
77  if (header['cmd'] == undefined) {
78    console.log('NaClAM: Message header does not contain cmd.');
79    return false;
80  }
81  if (typeof(header['cmd']) != "string") {
82    console.log('NaClAm: Message cmd is not a string.');
83    return false;
84  }
85  if (header['frames'] == undefined) {
86    console.log('NaClAM: Message header does not contain frames.');
87    return false;
88  }
89  if (typeof(header['frames']) != "number") {
90    console.log('NaClAm: Message frames is not a number.');
91    return false;
92  }
93  if (header['request'] == undefined) {
94    console.log('NaClAM: Message header does not contain request.');
95    return false;
96  }
97  if (typeof(header['request']) != "number") {
98    console.log('NaClAm: Message request is not a number.');
99    return false;
100  }
101  return true;
102}
103
104NaClAM.prototype.framesIsValid_ = function(frames) {
105  var i;
106  if (!frames) {
107    // No frames.
108    return true;
109  }
110  if (Array.isArray(frames) == false) {
111    console.log('NaClAM: Frames must be an array.');
112    return false;
113  }
114  for (i = 0; i < frames.length; i++) {
115    var e = frames[i];
116    if (typeof(e) == "string") {
117      continue;
118    }
119    if ((e instanceof ArrayBuffer) == false) {
120      console.log('NaClAM: Frame is not a string or ArrayBuffer');
121      return false;
122    }
123  }
124  return true;
125}
126
127NaClAM.prototype.framesLength_ = function(frames) {
128  if (!frames) {
129    // No frames.
130    return 0;
131  }
132  return frames.length;
133}
134
135NaClAM.prototype.sendMessage = function(cmdName, arguments, frames) {
136  if (this.framesIsValid_(frames) == false) {
137    console.log('NaClAM: Not sending message because frames is invalid.');
138    return undefined;
139  }
140  var numFrames = this.framesLength_(frames);
141  this.requestId++;
142  var msgHeader = {
143    cmd: cmdName,
144    frames: numFrames,
145    request: this.requestId,
146    args: arguments
147  };
148  if (this.messageHeaderIsValid_(msgHeader) == false) {
149    console.log('NaClAM: Not sending message because header is invalid.');
150    return undefined;
151  }
152  var AM = document.getElementById(this.embedId);
153  if (!AM) {
154    console.log('NaClAM: Not sending message because Acceleration Module is not there.');
155    return undefined;
156  }
157  AM.postMessage(JSON.stringify(msgHeader));
158  var i;
159  for (i = 0; i < numFrames; i++) {
160    AM.postMessage(frames[i]);
161  }
162  return this.requestId;
163}
164
165/**
166 * Adds an event listener to this Acceleration Module.
167 * @param {string} type The name of the command.
168 * @param handler The handler for the cmomand. This is called whenever the command is received.
169 */
170NaClAM.prototype.addEventListener = function(type, handler) {
171  if (!this.listeners_) {
172    this.listeners_ = Object.create(null);
173  }
174  if (!(type in this.listeners_)) {
175    this.listeners_[type] = [handler];
176  } else {
177    var handlers = this.listeners_[type];
178    if (handlers.indexOf(handler) < 0) {
179      handlers.push(handler);
180    }
181  }
182}
183
184/**
185 * Removes an event listener from this Acceleration Module.
186 * @param {string} type The name of the command.
187 * @param handler The handler for the cmomand. This is called whenever the command is received.
188 */
189NaClAM.prototype.removeEventListener = function(type, handler) {
190  if (!this.listeners_) {
191    // No listeners
192    return;
193  }
194  if (type in this.listeners_) {
195    var handlers = this.listeners_[type];
196    var index = handlers.indexOf(handler);
197    if (index >= 0) {
198      if (handlers.length == 1) {
199        // Listeners list would be empty, delete it
200        delete this.listeners_[type];
201      } else {
202        // Remove the handler
203        handlers.splice(index, 1);
204      }
205    }
206  }
207}
208
209/**
210 *
211 */
212NaClAM.prototype.dispatchEvent = function(event) {
213  if (!this.listeners_) {
214    return true;
215  }
216  var type = event.header.cmd;
217  if (type in this.listeners_) {
218    // Make a copy to walk over
219    var handlers = this.listeners_[type].concat();
220    for (var i = 0, handler; handler = handlers[i]; i++) {
221      handler.call(this, event);
222    }
223  }
224}
225