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. 7function ConnectionHandler() { 8 // Event listener registration map socket->event->callback 9 this.eventListener_ = {}; 10} 11 12ConnectionHandler.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}; 244