| OLD | NEW |
| (Empty) |
| 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 #include "chrome/test/chromedriver/devtools_client_impl.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/json/json_reader.h" | |
| 9 #include "base/json/json_writer.h" | |
| 10 #include "base/logging.h" | |
| 11 #include "base/values.h" | |
| 12 #include "chrome/test/chromedriver/devtools_event_listener.h" | |
| 13 #include "chrome/test/chromedriver/net/sync_websocket.h" | |
| 14 #include "chrome/test/chromedriver/net/url_request_context_getter.h" | |
| 15 #include "chrome/test/chromedriver/status.h" | |
| 16 | |
| 17 namespace { | |
| 18 | |
| 19 const char* kInspectorContextError = | |
| 20 "Execution context with given id not found."; | |
| 21 | |
| 22 Status ParseInspectorError(const std::string& error_json) { | |
| 23 scoped_ptr<base::Value> error(base::JSONReader::Read(error_json)); | |
| 24 base::DictionaryValue* error_dict; | |
| 25 if (!error || !error->GetAsDictionary(&error_dict)) | |
| 26 return Status(kUnknownError, "inspector error with no error message"); | |
| 27 std::string error_message; | |
| 28 if (error_dict->GetString("message", &error_message) && | |
| 29 error_message == kInspectorContextError) { | |
| 30 return Status(kNoSuchFrame); | |
| 31 } | |
| 32 return Status(kUnknownError, "unhandled inspector error: " + error_json); | |
| 33 } | |
| 34 | |
| 35 } // namespace | |
| 36 | |
| 37 namespace internal { | |
| 38 | |
| 39 InspectorEvent::InspectorEvent() {} | |
| 40 | |
| 41 InspectorEvent::~InspectorEvent() {} | |
| 42 | |
| 43 InspectorCommandResponse::InspectorCommandResponse() {} | |
| 44 | |
| 45 InspectorCommandResponse::~InspectorCommandResponse() {} | |
| 46 | |
| 47 } // namespace internal | |
| 48 | |
| 49 DevToolsClientImpl::DevToolsClientImpl( | |
| 50 const SyncWebSocketFactory& factory, | |
| 51 const std::string& url, | |
| 52 const FrontendCloserFunc& frontend_closer_func) | |
| 53 : socket_(factory.Run().Pass()), | |
| 54 url_(url), | |
| 55 frontend_closer_func_(frontend_closer_func), | |
| 56 parser_func_(base::Bind(&internal::ParseInspectorMessage)), | |
| 57 next_id_(1) {} | |
| 58 | |
| 59 DevToolsClientImpl::DevToolsClientImpl( | |
| 60 const SyncWebSocketFactory& factory, | |
| 61 const std::string& url, | |
| 62 const FrontendCloserFunc& frontend_closer_func, | |
| 63 const ParserFunc& parser_func) | |
| 64 : socket_(factory.Run().Pass()), | |
| 65 url_(url), | |
| 66 frontend_closer_func_(frontend_closer_func), | |
| 67 parser_func_(parser_func), | |
| 68 next_id_(1) {} | |
| 69 | |
| 70 DevToolsClientImpl::~DevToolsClientImpl() { | |
| 71 for (ResponseMap::iterator iter = cmd_response_map_.begin(); | |
| 72 iter != cmd_response_map_.end(); ++iter) { | |
| 73 LOG(WARNING) << "Finished with no response for command " << iter->first; | |
| 74 delete iter->second; | |
| 75 } | |
| 76 } | |
| 77 | |
| 78 void DevToolsClientImpl::SetParserFuncForTesting( | |
| 79 const ParserFunc& parser_func) { | |
| 80 parser_func_ = parser_func; | |
| 81 } | |
| 82 | |
| 83 Status DevToolsClientImpl::ConnectIfNecessary() { | |
| 84 if (!socket_->IsConnected()) { | |
| 85 if (!socket_->Connect(url_)) { | |
| 86 // Try to close devtools frontend and then reconnect. | |
| 87 Status status = frontend_closer_func_.Run(); | |
| 88 if (status.IsError()) | |
| 89 return status; | |
| 90 if (!socket_->Connect(url_)) | |
| 91 return Status(kDisconnected, "unable to connect to renderer"); | |
| 92 } | |
| 93 | |
| 94 // OnConnected notification will be sent out in method ReceiveNextMessage. | |
| 95 listeners_for_on_connected_ = listeners_; | |
| 96 } | |
| 97 return Status(kOk); | |
| 98 } | |
| 99 | |
| 100 Status DevToolsClientImpl::SendCommand( | |
| 101 const std::string& method, | |
| 102 const base::DictionaryValue& params) { | |
| 103 scoped_ptr<base::DictionaryValue> result; | |
| 104 return SendCommandInternal(method, params, &result); | |
| 105 } | |
| 106 | |
| 107 Status DevToolsClientImpl::SendCommandAndGetResult( | |
| 108 const std::string& method, | |
| 109 const base::DictionaryValue& params, | |
| 110 scoped_ptr<base::DictionaryValue>* result) { | |
| 111 scoped_ptr<base::DictionaryValue> intermediate_result; | |
| 112 Status status = SendCommandInternal(method, params, &intermediate_result); | |
| 113 if (status.IsError()) | |
| 114 return status; | |
| 115 if (!intermediate_result) | |
| 116 return Status(kUnknownError, "inspector response missing result"); | |
| 117 result->reset(intermediate_result.release()); | |
| 118 return Status(kOk); | |
| 119 } | |
| 120 | |
| 121 void DevToolsClientImpl::AddListener(DevToolsEventListener* listener) { | |
| 122 DCHECK(listener); | |
| 123 listeners_.push_back(listener); | |
| 124 } | |
| 125 | |
| 126 Status DevToolsClientImpl::HandleEventsUntil( | |
| 127 const ConditionalFunc& conditional_func) { | |
| 128 if (!socket_->IsConnected()) | |
| 129 return Status(kDisconnected, "not connected to DevTools"); | |
| 130 | |
| 131 Status status = EnsureAllListenersNotifiedOfConnection(); | |
| 132 if (status.IsError()) | |
| 133 return status; | |
| 134 | |
| 135 internal::InspectorMessageType type; | |
| 136 internal::InspectorEvent event; | |
| 137 internal::InspectorCommandResponse response; | |
| 138 | |
| 139 while (true) { | |
| 140 if (!socket_->HasNextMessage()) { | |
| 141 bool is_condition_met; | |
| 142 Status status = conditional_func.Run(&is_condition_met); | |
| 143 if (status.IsError()) | |
| 144 return status; | |
| 145 if (is_condition_met) | |
| 146 return Status(kOk); | |
| 147 } | |
| 148 | |
| 149 status = ReceiveNextMessage(-1, &type, &event, &response); | |
| 150 if (status.IsError()) | |
| 151 return status; | |
| 152 } | |
| 153 return Status(kOk); | |
| 154 } | |
| 155 | |
| 156 Status DevToolsClientImpl::SendCommandInternal( | |
| 157 const std::string& method, | |
| 158 const base::DictionaryValue& params, | |
| 159 scoped_ptr<base::DictionaryValue>* result) { | |
| 160 if (!socket_->IsConnected()) | |
| 161 return Status(kDisconnected, "not connected to DevTools"); | |
| 162 | |
| 163 int command_id = next_id_++; | |
| 164 base::DictionaryValue command; | |
| 165 command.SetInteger("id", command_id); | |
| 166 command.SetString("method", method); | |
| 167 command.Set("params", params.DeepCopy()); | |
| 168 std::string message; | |
| 169 base::JSONWriter::Write(&command, &message); | |
| 170 if (!socket_->Send(message)) | |
| 171 return Status(kDisconnected, "unable to send message to renderer"); | |
| 172 return ReceiveCommandResponse(command_id, result); | |
| 173 } | |
| 174 | |
| 175 Status DevToolsClientImpl::ReceiveCommandResponse( | |
| 176 int command_id, | |
| 177 scoped_ptr<base::DictionaryValue>* result) { | |
| 178 internal::InspectorMessageType type; | |
| 179 internal::InspectorEvent event; | |
| 180 internal::InspectorCommandResponse response; | |
| 181 cmd_response_map_[command_id] = NULL; | |
| 182 while (!HasReceivedCommandResponse(command_id)) { | |
| 183 Status status = ReceiveNextMessage(command_id, &type, &event, &response); | |
| 184 if (status.IsError()) | |
| 185 return status; | |
| 186 } | |
| 187 result->reset(cmd_response_map_[command_id]); | |
| 188 cmd_response_map_.erase(command_id); | |
| 189 return Status(kOk); | |
| 190 } | |
| 191 | |
| 192 Status DevToolsClientImpl::ReceiveNextMessage( | |
| 193 int expected_id, | |
| 194 internal::InspectorMessageType* type, | |
| 195 internal::InspectorEvent* event, | |
| 196 internal::InspectorCommandResponse* response) { | |
| 197 Status status = EnsureAllListenersNotifiedOfConnection(); | |
| 198 if (status.IsError()) | |
| 199 return status; | |
| 200 | |
| 201 // The message might be received already when processing other commands sent | |
| 202 // from DevToolsEventListener::OnConnected. | |
| 203 if (HasReceivedCommandResponse(expected_id)) | |
| 204 return Status(kOk); | |
| 205 | |
| 206 std::string message; | |
| 207 if (!socket_->ReceiveNextMessage(&message)) | |
| 208 return Status(kDisconnected, "unable to receive message from renderer"); | |
| 209 if (!parser_func_.Run(message, expected_id, type, event, response)) | |
| 210 return Status(kUnknownError, "bad inspector message: " + message); | |
| 211 if (*type == internal::kEventMessageType) | |
| 212 return NotifyEventListeners(event->method, *event->params); | |
| 213 if (*type == internal::kCommandResponseMessageType) { | |
| 214 if (cmd_response_map_.count(response->id) == 0) { | |
| 215 return Status(kUnknownError, "unexpected command message"); | |
| 216 } else if (response->result) { | |
| 217 cmd_response_map_[response->id] = response->result.release(); | |
| 218 } else { | |
| 219 cmd_response_map_.erase(response->id); | |
| 220 return ParseInspectorError(response->error); | |
| 221 } | |
| 222 } | |
| 223 return Status(kOk); | |
| 224 } | |
| 225 | |
| 226 bool DevToolsClientImpl::HasReceivedCommandResponse(int cmd_id) { | |
| 227 return cmd_response_map_.find(cmd_id) != cmd_response_map_.end() | |
| 228 && cmd_response_map_[cmd_id] != NULL; | |
| 229 } | |
| 230 | |
| 231 Status DevToolsClientImpl::NotifyEventListeners( | |
| 232 const std::string& method, | |
| 233 const base::DictionaryValue& params) { | |
| 234 for (std::list<DevToolsEventListener*>::iterator iter = listeners_.begin(); | |
| 235 iter != listeners_.end(); ++iter) { | |
| 236 (*iter)->OnEvent(method, params); | |
| 237 } | |
| 238 if (method == "Inspector.detached") | |
| 239 return Status(kDisconnected, "received Inspector.detached event"); | |
| 240 return Status(kOk); | |
| 241 } | |
| 242 | |
| 243 Status DevToolsClientImpl::EnsureAllListenersNotifiedOfConnection() { | |
| 244 while (!listeners_for_on_connected_.empty()) { | |
| 245 DevToolsEventListener* listener = listeners_for_on_connected_.front(); | |
| 246 listeners_for_on_connected_.pop_front(); | |
| 247 Status status = listener->OnConnected(); | |
| 248 if (status.IsError()) | |
| 249 return status; | |
| 250 } | |
| 251 return Status(kOk); | |
| 252 } | |
| 253 | |
| 254 namespace internal { | |
| 255 | |
| 256 bool ParseInspectorMessage( | |
| 257 const std::string& message, | |
| 258 int expected_id, | |
| 259 InspectorMessageType* type, | |
| 260 InspectorEvent* event, | |
| 261 InspectorCommandResponse* command_response) { | |
| 262 scoped_ptr<base::Value> message_value(base::JSONReader::Read(message)); | |
| 263 base::DictionaryValue* message_dict; | |
| 264 if (!message_value || !message_value->GetAsDictionary(&message_dict)) | |
| 265 return false; | |
| 266 | |
| 267 int id; | |
| 268 if (!message_dict->HasKey("id")) { | |
| 269 std::string method; | |
| 270 if (!message_dict->GetString("method", &method)) | |
| 271 return false; | |
| 272 base::DictionaryValue* params = NULL; | |
| 273 message_dict->GetDictionary("params", ¶ms); | |
| 274 | |
| 275 *type = kEventMessageType; | |
| 276 event->method = method; | |
| 277 if (params) | |
| 278 event->params.reset(params->DeepCopy()); | |
| 279 else | |
| 280 event->params.reset(new base::DictionaryValue()); | |
| 281 return true; | |
| 282 } else if (message_dict->GetInteger("id", &id)) { | |
| 283 base::DictionaryValue* unscoped_error = NULL; | |
| 284 base::DictionaryValue* unscoped_result = NULL; | |
| 285 if (!message_dict->GetDictionary("error", &unscoped_error) && | |
| 286 !message_dict->GetDictionary("result", &unscoped_result)) | |
| 287 return false; | |
| 288 | |
| 289 *type = kCommandResponseMessageType; | |
| 290 command_response->id = id; | |
| 291 if (unscoped_result) | |
| 292 command_response->result.reset(unscoped_result->DeepCopy()); | |
| 293 else | |
| 294 base::JSONWriter::Write(unscoped_error, &command_response->error); | |
| 295 return true; | |
| 296 } | |
| 297 return false; | |
| 298 } | |
| 299 | |
| 300 } // namespace internal | |
| OLD | NEW |