1 // Copyright (c) 2012 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.
5 // Automation connection handler is responsible for reading requests from the
6 // stream, finding and executing appropriate extension API method.
7 function ConnectionHandler() {
8 // Event listener registration map socket->event->callback
9 this.eventListener_ = {};
12 ConnectionHandler.prototype = {
13 // Stream delegate callback.
14 onStreamError: function(stream) {
15 this.unregisterListeners_(stream);
18 // Stream delegate callback.
19 onStreamTerminated: function(stream) {
20 this.unregisterListeners_(stream);
23 // Pairs event |listenerMethod| with a given |stream|.
24 registerListener_: function(stream, eventName, eventObject,
26 if (!this.eventListener_[stream.socketId_])
27 this.eventListener_[stream.socketId_] = {};
29 if (!this.eventListener_[stream.socketId_][eventName]) {
30 this.eventListener_[stream.socketId_][eventName] = {
32 'method': listenerMethod };
36 // Removes event listeners.
37 unregisterListeners_: function(stream) {
38 if (!this.eventListener_[stream.socketId_])
41 for (var eventName in this.eventListener_[stream.socketId_]) {
42 var listenerDefinition = this.eventListener_[stream.socketId_][eventName];
43 var removeFunction = listenerDefinition.event['removeListener'];
45 removeFunction.call(listenerDefinition.event,
46 listenerDefinition.method);
49 delete this.eventListener_[stream.socketId_];
52 // Finds appropriate method/event to invoke/register.
53 findExecutionTarget_: function(functionName) {
54 var funcSegments = functionName.split('.');
55 if (funcSegments.size < 2)
58 if (funcSegments[0] != 'chrome')
62 var prevSegName = null;
63 var prevSegment = null;
64 var segmentObject = null;
66 for (var i = 0; i < funcSegments.length; i++) {
71 eventName += prevSegName;
74 segName = funcSegments[i];
75 prevSegName = segName;
77 // TODO(zelidrag): Get rid of this eval.
78 segmentObject = eval(segName);
82 prevSegment = segmentObject;
83 if (segmentObject[segName])
84 segmentObject = segmentObject[segName];
88 if (segmentObject == window)
91 var isEventMethod = segName == 'addListener';
92 return {'method': segmentObject,
93 'eventName': (isEventMethod ? eventName : null),
94 'event': (isEventMethod ? prevSegment : null)};
97 // TODO(zelidrag): Figure out how to automatically detect or generate list of
99 isSyncFunction_: function(funcName) {
100 if (funcName == 'chrome.omnibox.setDefaultSuggestion')
106 // Parses |command|, finds appropriate JS method runs it with |argsJson|.
107 // If the method is an event registration, it will register an event listener
108 // method and start sending data from its callback.
109 processCommand_: function(stream, command, argsJson) {
110 var target = this.findExecutionTarget_(command);
111 if (!target || !target.method) {
112 return {'result': false,
113 'objectName': command};
116 var args = JSON.parse(decodeURIComponent(argsJson));
120 console.log(command + '(' + decodeURIComponent(argsJson) + ')',
122 // Check if we need to register an event listener.
124 // Register listener method.
125 var listener = function() {
126 stream.write(JSON.stringify({ 'type': 'eventCallback',
127 'eventName': target.eventName,
128 'arguments' : arguments}));
130 // Add event handler method to arguments.
132 args.push(null); // for |filters|.
133 target.method.apply(target.event, args);
134 this.registerListener_(stream, target.eventName,
135 target.event, listener);
136 stream.write(JSON.stringify({'type': 'eventRegistration',
137 'eventName': command}));
138 return {'result': true,
142 // Run extension method directly.
143 if (this.isSyncFunction_(command)) {
145 console.log(command + '(' + unescape(argsJson) + ')');
146 var result = target.method.apply(undefined, args);
147 stream.write(JSON.stringify({'type': 'methodResult',
148 'methodName': command,
150 'result' : result}));
151 } else { // Async method.
152 // Add callback method to arguments.
153 args.push(function() {
154 stream.write(JSON.stringify({'type': 'methodCallback',
155 'methodName': command,
157 'arguments' : arguments}));
159 target.method.apply(undefined, args);
161 return {'result': true,
165 arrayBufferToString_: function(buffer) {
167 var uArrayVal = new Uint8Array(buffer);
168 for(var s = 0; s < uArrayVal.length; s++) {
169 str += String.fromCharCode(uArrayVal[s]);
174 // Callback for stream read requests.
175 onStreamRead_: function(stream, readInfo) {
176 console.log("READ", readInfo);
177 // Parse the request.
178 var data = this.arrayBufferToString_(readInfo.data);
179 var spacePos = data.indexOf(" ");
181 if (spacePos == -1) {
182 spacePos = data.indexOf("\r\n");
184 throw {'code': 400, 'description': 'Bad Request'};
187 var verb = data.substring(0, spacePos);
191 throw {'code': 200, 'description': 'OK'};
199 throw {'code': 400, 'description': 'Bad Request: ' + verb};
203 var command = data.substring(verb.length + 1);
204 var endLine = command.indexOf('\r\n');
206 command = command.substring(0, endLine);
208 var objectNames = command;
210 var funcNameEnd = command.indexOf("?");
211 if (funcNameEnd >= 0) {
212 objectNames = command.substring(0, funcNameEnd);
213 argsJson = command.substring(funcNameEnd + 1);
215 var functions = objectNames.split(',');
216 for (var i = 0; i < functions.length; i++) {
217 var objectName = functions[i];
219 this.processCommand_(stream, objectName, argsJson);
220 if (!commandStatus.result) {
222 'description': 'Not Found: ' + commandStatus.objectName};
224 // If we have run all requested commands, read the socket again.
225 if (i == (functions.length - 1)) {
226 setTimeout(function() {
227 this.readRequest_(stream);
232 console.warn('Error', err);
233 stream.writeError(err.code, err.description);
237 // Reads next request from the |stream|.
238 readRequest_: function(stream) {
239 console.log("Reading socket " + stream.socketId_);
241 stream.read(this.onStreamRead_.bind(this));