- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / test / ext_auto / auto_provider / connection_handler.js
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.
4
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_ = {};
10 }
11
12 ConnectionHandler.prototype = {
13   // Stream delegate callback.
14   onStreamError: function(stream) {
15     this.unregisterListeners_(stream);
16   },
17
18   // Stream delegate callback.
19   onStreamTerminated: function(stream) {
20     this.unregisterListeners_(stream);
21   },
22
23   // Pairs event |listenerMethod| with a given |stream|.
24   registerListener_: function(stream, eventName, eventObject,
25                               listenerMethod) {
26     if (!this.eventListener_[stream.socketId_])
27       this.eventListener_[stream.socketId_] = {};
28
29     if (!this.eventListener_[stream.socketId_][eventName]) {
30       this.eventListener_[stream.socketId_][eventName] = {
31           'event': eventObject,
32           'method': listenerMethod };
33     }
34   },
35
36   // Removes event listeners.
37   unregisterListeners_: function(stream) {
38     if (!this.eventListener_[stream.socketId_])
39     return;
40
41     for (var eventName in this.eventListener_[stream.socketId_]) {
42       var listenerDefinition = this.eventListener_[stream.socketId_][eventName];
43       var removeFunction = listenerDefinition.event['removeListener'];
44       if (removeFunction) {
45         removeFunction.call(listenerDefinition.event,
46                             listenerDefinition.method);
47       }
48     }
49     delete this.eventListener_[stream.socketId_];
50   },
51
52   // Finds appropriate method/event to invoke/register.
53   findExecutionTarget_: function(functionName) {
54     var funcSegments = functionName.split('.');
55     if (funcSegments.size < 2)
56       return null;
57
58     if (funcSegments[0] != 'chrome')
59       return null;
60
61     var eventName = "";
62     var prevSegName = null;
63     var prevSegment = null;
64     var segmentObject = null;
65     var segName = null;
66     for (var i = 0; i < funcSegments.length; i++) {
67       if (prevSegName) {
68         if (eventName.length)
69           eventName += '.';
70
71         eventName += prevSegName;
72       }
73
74       segName = funcSegments[i];
75       prevSegName = segName;
76       if (!segmentObject) {
77         // TODO(zelidrag): Get rid of this eval.
78         segmentObject = eval(segName);
79         continue;
80       }
81
82       prevSegment = segmentObject;
83       if (segmentObject[segName])
84         segmentObject = segmentObject[segName];
85       else
86         segmentObject = null;
87     }
88     if (segmentObject == window)
89       return null;
90
91     var isEventMethod = segName == 'addListener';
92     return {'method': segmentObject,
93             'eventName': (isEventMethod ? eventName : null),
94             'event': (isEventMethod ? prevSegment : null)};
95   },
96
97   // TODO(zelidrag): Figure out how to automatically detect or generate list of
98   // sync API methods.
99   isSyncFunction_: function(funcName) {
100     if (funcName == 'chrome.omnibox.setDefaultSuggestion')
101       return true;
102
103     return false;
104   },
105
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};
114     }
115
116     var args = JSON.parse(decodeURIComponent(argsJson));
117     if (!args)
118       args = [];
119
120     console.log(command + '(' + decodeURIComponent(argsJson) + ')',
121                 stream.socketId_);
122     // Check if we need to register an event listener.
123     if (target.event) {
124       // Register listener method.
125       var listener = function() {
126         stream.write(JSON.stringify({ 'type': 'eventCallback',
127                                       'eventName': target.eventName,
128                                       'arguments' : arguments}));
129       }.bind(this);
130       // Add event handler method to arguments.
131       args.push(listener);
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,
139               'wasEvent': true};
140     }
141
142     // Run extension method directly.
143     if (this.isSyncFunction_(command)) {
144       // Run sync method.
145       console.log(command + '(' + unescape(argsJson) + ')');
146       var result = target.method.apply(undefined, args);
147       stream.write(JSON.stringify({'type': 'methodResult',
148                                    'methodName': command,
149                                    'isCallback': false,
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,
156                                      'isCallback': true,
157                                      'arguments' : arguments}));
158       }.bind(this));
159       target.method.apply(undefined, args);
160     }
161     return {'result': true,
162             'wasEvent': false};
163   },
164
165   arrayBufferToString_: function(buffer) {
166     var str = '';
167     var uArrayVal = new Uint8Array(buffer);
168     for(var s = 0; s < uArrayVal.length; s++) {
169       str += String.fromCharCode(uArrayVal[s]);
170     }
171     return str;
172   },
173
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(" ");
180     try {
181       if (spacePos == -1) {
182         spacePos = data.indexOf("\r\n");
183         if (spacePos == -1)
184           throw {'code': 400, 'description': 'Bad Request'};
185       }
186
187       var verb = data.substring(0, spacePos);
188       var isEvent = false;
189       switch (verb) {
190         case 'TERMINATE':
191           throw {'code': 200, 'description': 'OK'};
192           break;
193         case 'RUN':
194           break;
195         case 'LISTEN':
196           this.isEvent = true;
197           break;
198         default:
199           throw {'code': 400, 'description': 'Bad Request: ' + verb};
200           return;
201       }
202
203       var command = data.substring(verb.length + 1);
204       var endLine = command.indexOf('\r\n');
205       if (endLine)
206         command = command.substring(0, endLine);
207
208       var objectNames = command;
209       var argsJson = null;
210       var funcNameEnd =  command.indexOf("?");
211       if (funcNameEnd >= 0) {
212         objectNames = command.substring(0, funcNameEnd);
213         argsJson = command.substring(funcNameEnd + 1);
214       }
215       var functions = objectNames.split(',');
216       for (var i = 0; i < functions.length; i++) {
217         var objectName = functions[i];
218         var commandStatus =
219             this.processCommand_(stream, objectName, argsJson);
220         if (!commandStatus.result) {
221           throw {'code': 404,
222                  'description': 'Not Found: ' + commandStatus.objectName};
223         }
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);
228           }.bind(this), 0);
229         }
230       }
231     } catch(err) {
232       console.warn('Error', err);
233       stream.writeError(err.code, err.description);
234     }
235   },
236
237   // Reads next request from the |stream|.
238   readRequest_: function(stream) {
239     console.log("Reading socket " + stream.socketId_);
240     //  Read in the data
241     stream.read(this.onStreamRead_.bind(this));
242   }
243 };