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 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
8 #include "base/json/json_reader.h"
9 #include "base/json/json_writer.h"
10 #include "base/logging.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/values.h"
13 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
14 #include "chrome/test/chromedriver/chrome/log.h"
15 #include "chrome/test/chromedriver/chrome/status.h"
16 #include "chrome/test/chromedriver/chrome/util.h"
17 #include "chrome/test/chromedriver/net/sync_websocket.h"
18 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
22 const char* kInspectorContextError =
23 "Execution context with given id not found.";
25 Status ParseInspectorError(const std::string& error_json) {
26 scoped_ptr<base::Value> error(base::JSONReader::Read(error_json));
27 base::DictionaryValue* error_dict;
28 if (!error || !error->GetAsDictionary(&error_dict))
29 return Status(kUnknownError, "inspector error with no error message");
30 std::string error_message;
31 if (error_dict->GetString("message", &error_message) &&
32 error_message == kInspectorContextError) {
33 return Status(kNoSuchExecutionContext);
35 return Status(kUnknownError, "unhandled inspector error: " + error_json);
38 class ScopedIncrementer {
40 explicit ScopedIncrementer(int* count) : count_(count) {
43 ~ScopedIncrementer() {
51 Status ConditionIsMet(bool* is_condition_met) {
52 *is_condition_met = true;
60 InspectorEvent::InspectorEvent() {}
62 InspectorEvent::~InspectorEvent() {}
64 InspectorCommandResponse::InspectorCommandResponse() {}
66 InspectorCommandResponse::~InspectorCommandResponse() {}
68 } // namespace internal
70 DevToolsClientImpl::DevToolsClientImpl(
71 const SyncWebSocketFactory& factory,
72 const std::string& url,
73 const std::string& id,
74 const FrontendCloserFunc& frontend_closer_func)
75 : socket_(factory.Run().Pass()),
79 frontend_closer_func_(frontend_closer_func),
80 parser_func_(base::Bind(&internal::ParseInspectorMessage)),
81 unnotified_event_(NULL),
85 DevToolsClientImpl::DevToolsClientImpl(
86 const SyncWebSocketFactory& factory,
87 const std::string& url,
88 const std::string& id,
89 const FrontendCloserFunc& frontend_closer_func,
90 const ParserFunc& parser_func)
91 : socket_(factory.Run().Pass()),
95 frontend_closer_func_(frontend_closer_func),
96 parser_func_(parser_func),
97 unnotified_event_(NULL),
101 DevToolsClientImpl::~DevToolsClientImpl() {}
103 void DevToolsClientImpl::SetParserFuncForTesting(
104 const ParserFunc& parser_func) {
105 parser_func_ = parser_func;
108 const std::string& DevToolsClientImpl::GetId() {
112 bool DevToolsClientImpl::WasCrashed() {
116 Status DevToolsClientImpl::ConnectIfNecessary() {
118 return Status(kUnknownError, "cannot connect when nested");
120 if (socket_->IsConnected())
123 if (!socket_->Connect(url_)) {
124 // Try to close devtools frontend and then reconnect.
125 Status status = frontend_closer_func_.Run();
126 if (status.IsError())
128 if (!socket_->Connect(url_))
129 return Status(kDisconnected, "unable to connect to renderer");
132 unnotified_connect_listeners_ = listeners_;
133 unnotified_event_listeners_.clear();
134 response_info_map_.clear();
136 // Notify all listeners of the new connection. Do this now so that any errors
137 // that occur are reported now instead of later during some unrelated call.
138 // Also gives listeners a chance to send commands before other clients.
139 return EnsureListenersNotifiedOfConnect();
142 Status DevToolsClientImpl::SendCommand(
143 const std::string& method,
144 const base::DictionaryValue& params) {
145 scoped_ptr<base::DictionaryValue> result;
146 return SendCommandInternal(method, params, &result);
149 Status DevToolsClientImpl::SendCommandAndGetResult(
150 const std::string& method,
151 const base::DictionaryValue& params,
152 scoped_ptr<base::DictionaryValue>* result) {
153 scoped_ptr<base::DictionaryValue> intermediate_result;
154 Status status = SendCommandInternal(method, params, &intermediate_result);
155 if (status.IsError())
157 if (!intermediate_result)
158 return Status(kUnknownError, "inspector response missing result");
159 result->reset(intermediate_result.release());
163 void DevToolsClientImpl::AddListener(DevToolsEventListener* listener) {
165 listeners_.push_back(listener);
168 Status DevToolsClientImpl::HandleReceivedEvents() {
169 return HandleEventsUntil(base::Bind(&ConditionIsMet), base::TimeDelta());
172 Status DevToolsClientImpl::HandleEventsUntil(
173 const ConditionalFunc& conditional_func, const base::TimeDelta& timeout) {
174 if (!socket_->IsConnected())
175 return Status(kDisconnected, "not connected to DevTools");
177 base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
178 base::TimeDelta next_message_timeout = timeout;
180 if (!socket_->HasNextMessage()) {
181 bool is_condition_met = false;
182 Status status = conditional_func.Run(&is_condition_met);
183 if (status.IsError())
185 if (is_condition_met)
189 Status status = ProcessNextMessage(-1, next_message_timeout);
190 if (status.IsError())
192 next_message_timeout = deadline - base::TimeTicks::Now();
196 DevToolsClientImpl::ResponseInfo::ResponseInfo(const std::string& method)
197 : state(kWaiting), method(method) {}
199 DevToolsClientImpl::ResponseInfo::~ResponseInfo() {}
201 Status DevToolsClientImpl::SendCommandInternal(
202 const std::string& method,
203 const base::DictionaryValue& params,
204 scoped_ptr<base::DictionaryValue>* result) {
205 if (!socket_->IsConnected())
206 return Status(kDisconnected, "not connected to DevTools");
208 int command_id = next_id_++;
209 base::DictionaryValue command;
210 command.SetInteger("id", command_id);
211 command.SetString("method", method);
212 command.Set("params", params.DeepCopy());
213 std::string message = SerializeValue(&command);
215 VLOG(1) << "DEVTOOLS COMMAND " << method << " (id=" << command_id << ") "
216 << FormatValueForDisplay(params);
218 if (!socket_->Send(message))
219 return Status(kDisconnected, "unable to send message to renderer");
221 linked_ptr<ResponseInfo> response_info =
222 make_linked_ptr(new ResponseInfo(method));
223 response_info_map_[command_id] = response_info;
224 while (response_info->state == kWaiting) {
225 Status status = ProcessNextMessage(
226 command_id, base::TimeDelta::FromMinutes(10));
227 if (status.IsError()) {
228 if (response_info->state == kReceived)
229 response_info_map_.erase(command_id);
233 if (response_info->state == kBlocked) {
234 response_info->state = kIgnored;
235 return Status(kUnexpectedAlertOpen);
237 CHECK_EQ(response_info->state, kReceived);
238 internal::InspectorCommandResponse& response = response_info->response;
239 if (!response.result)
240 return ParseInspectorError(response.error);
241 *result = response.result.Pass();
245 Status DevToolsClientImpl::ProcessNextMessage(
247 const base::TimeDelta& timeout) {
248 ScopedIncrementer increment_stack_count(&stack_count_);
250 Status status = EnsureListenersNotifiedOfConnect();
251 if (status.IsError())
253 status = EnsureListenersNotifiedOfEvent();
254 if (status.IsError())
256 status = EnsureListenersNotifiedOfCommandResponse();
257 if (status.IsError())
260 // The command response may have already been received or blocked while
261 // notifying listeners.
262 if (expected_id != -1 && response_info_map_[expected_id]->state != kWaiting)
266 return Status(kTabCrashed);
269 switch (socket_->ReceiveNextMessage(&message, timeout)) {
270 case SyncWebSocket::kOk:
272 case SyncWebSocket::kDisconnected: {
273 std::string err = "Unable to receive message from renderer";
275 return Status(kDisconnected, err);
277 case SyncWebSocket::kTimeout: {
279 "Timed out receiving message from renderer: " +
280 base::StringPrintf("%.3lf", timeout.InSecondsF());
282 return Status(kTimeout, err);
289 internal::InspectorMessageType type;
290 internal::InspectorEvent event;
291 internal::InspectorCommandResponse response;
292 if (!parser_func_.Run(message, expected_id, &type, &event, &response)) {
293 LOG(ERROR) << "Bad inspector message: " << message;
294 return Status(kUnknownError, "bad inspector message: " + message);
297 if (type == internal::kEventMessageType)
298 return ProcessEvent(event);
299 CHECK_EQ(type, internal::kCommandResponseMessageType);
300 return ProcessCommandResponse(response);
303 Status DevToolsClientImpl::ProcessEvent(const internal::InspectorEvent& event) {
305 VLOG(1) << "DEVTOOLS EVENT " << event.method << " "
306 << FormatValueForDisplay(*event.params);
308 unnotified_event_listeners_ = listeners_;
309 unnotified_event_ = &event;
310 Status status = EnsureListenersNotifiedOfEvent();
311 unnotified_event_ = NULL;
312 if (status.IsError())
314 if (event.method == "Inspector.detached")
315 return Status(kDisconnected, "received Inspector.detached event");
316 if (event.method == "Inspector.targetCrashed") {
318 return Status(kTabCrashed);
320 if (event.method == "Page.javascriptDialogOpening") {
321 // A command may have opened the dialog, which will block the response.
322 // To find out which one (if any), do a round trip with a simple command
323 // to the renderer and afterwards see if any of the commands still haven't
324 // received a response.
325 // This relies on the fact that DevTools commands are processed
326 // sequentially. This may break if any of the commands are asynchronous.
327 // If for some reason the round trip command fails, mark all the waiting
328 // commands as blocked and return the error. This is better than risking
330 int max_id = next_id_;
331 base::DictionaryValue enable_params;
332 enable_params.SetString("purpose", "detect if alert blocked any cmds");
333 Status enable_status = SendCommand("Inspector.enable", enable_params);
334 for (ResponseInfoMap::const_iterator iter = response_info_map_.begin();
335 iter != response_info_map_.end(); ++iter) {
336 if (iter->first > max_id)
338 if (iter->second->state == kWaiting)
339 iter->second->state = kBlocked;
341 if (enable_status.IsError())
347 Status DevToolsClientImpl::ProcessCommandResponse(
348 const internal::InspectorCommandResponse& response) {
349 ResponseInfoMap::iterator iter = response_info_map_.find(response.id);
351 std::string method, result;
352 if (iter != response_info_map_.end())
353 method = iter->second->method;
355 result = FormatValueForDisplay(*response.result);
357 result = response.error;
358 VLOG(1) << "DEVTOOLS RESPONSE " << method << " (id=" << response.id
362 if (iter == response_info_map_.end())
363 return Status(kUnknownError, "unexpected command response");
365 linked_ptr<ResponseInfo> response_info = response_info_map_[response.id];
366 if (response_info->state == kReceived)
367 return Status(kUnknownError, "received multiple command responses");
369 if (response_info->state == kIgnored) {
370 response_info_map_.erase(response.id);
372 response_info->state = kReceived;
373 response_info->response.id = response.id;
374 response_info->response.error = response.error;
376 response_info->response.result.reset(response.result->DeepCopy());
379 if (response.result) {
380 unnotified_cmd_response_listeners_ = listeners_;
381 unnotified_cmd_response_info_ = response_info;
382 Status status = EnsureListenersNotifiedOfCommandResponse();
383 unnotified_cmd_response_info_.reset();
384 if (status.IsError())
390 Status DevToolsClientImpl::EnsureListenersNotifiedOfConnect() {
391 while (unnotified_connect_listeners_.size()) {
392 DevToolsEventListener* listener = unnotified_connect_listeners_.front();
393 unnotified_connect_listeners_.pop_front();
394 Status status = listener->OnConnected(this);
395 if (status.IsError())
401 Status DevToolsClientImpl::EnsureListenersNotifiedOfEvent() {
402 while (unnotified_event_listeners_.size()) {
403 DevToolsEventListener* listener = unnotified_event_listeners_.front();
404 unnotified_event_listeners_.pop_front();
405 Status status = listener->OnEvent(
406 this, unnotified_event_->method, *unnotified_event_->params);
407 if (status.IsError())
413 Status DevToolsClientImpl::EnsureListenersNotifiedOfCommandResponse() {
414 while (unnotified_cmd_response_listeners_.size()) {
415 DevToolsEventListener* listener =
416 unnotified_cmd_response_listeners_.front();
417 unnotified_cmd_response_listeners_.pop_front();
419 listener->OnCommandSuccess(this, unnotified_cmd_response_info_->method);
420 if (status.IsError())
428 bool ParseInspectorMessage(
429 const std::string& message,
431 InspectorMessageType* type,
432 InspectorEvent* event,
433 InspectorCommandResponse* command_response) {
434 scoped_ptr<base::Value> message_value(base::JSONReader::Read(message));
435 base::DictionaryValue* message_dict;
436 if (!message_value || !message_value->GetAsDictionary(&message_dict))
440 if (!message_dict->HasKey("id")) {
442 if (!message_dict->GetString("method", &method))
444 base::DictionaryValue* params = NULL;
445 message_dict->GetDictionary("params", ¶ms);
447 *type = kEventMessageType;
448 event->method = method;
450 event->params.reset(params->DeepCopy());
452 event->params.reset(new base::DictionaryValue());
454 } else if (message_dict->GetInteger("id", &id)) {
455 base::DictionaryValue* unscoped_error = NULL;
456 base::DictionaryValue* unscoped_result = NULL;
457 if (!message_dict->GetDictionary("error", &unscoped_error) &&
458 !message_dict->GetDictionary("result", &unscoped_result))
461 *type = kCommandResponseMessageType;
462 command_response->id = id;
464 command_response->result.reset(unscoped_result->DeepCopy());
466 base::JSONWriter::Write(unscoped_error, &command_response->error);
472 } // namespace internal