Roll src/third_party/WebKit eac3800:0237a66 (svn 202606:202607)
[chromium-blink-merge.git] / chrome / test / chromedriver / chrome / devtools_client_impl.cc
blobe33b54cec90b451d75a8f9afdebe395f4cbed2f4
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"
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/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"
20 namespace {
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 {
39 public:
40 explicit ScopedIncrementer(int* count) : count_(count) {
41 (*count_)++;
43 ~ScopedIncrementer() {
44 (*count_)--;
47 private:
48 int* count_;
51 Status ConditionIsMet(bool* is_condition_met) {
52 *is_condition_met = true;
53 return Status(kOk);
56 Status FakeCloseFrontends() {
57 return Status(kOk);
60 } // namespace
62 namespace internal {
64 InspectorEvent::InspectorEvent() {}
66 InspectorEvent::~InspectorEvent() {}
68 InspectorCommandResponse::InspectorCommandResponse() {}
70 InspectorCommandResponse::~InspectorCommandResponse() {}
72 } // namespace internal
74 const char DevToolsClientImpl::kBrowserwideDevToolsClientId[] = "browser";
76 DevToolsClientImpl::DevToolsClientImpl(
77 const SyncWebSocketFactory& factory,
78 const std::string& url,
79 const std::string& id)
80 : socket_(factory.Run().Pass()),
81 url_(url),
82 crashed_(false),
83 id_(id),
84 frontend_closer_func_(base::Bind(&FakeCloseFrontends)),
85 parser_func_(base::Bind(&internal::ParseInspectorMessage)),
86 unnotified_event_(NULL),
87 next_id_(1),
88 stack_count_(0) {}
90 DevToolsClientImpl::DevToolsClientImpl(
91 const SyncWebSocketFactory& factory,
92 const std::string& url,
93 const std::string& id,
94 const FrontendCloserFunc& frontend_closer_func)
95 : socket_(factory.Run().Pass()),
96 url_(url),
97 crashed_(false),
98 id_(id),
99 frontend_closer_func_(frontend_closer_func),
100 parser_func_(base::Bind(&internal::ParseInspectorMessage)),
101 unnotified_event_(NULL),
102 next_id_(1),
103 stack_count_(0) {}
105 DevToolsClientImpl::DevToolsClientImpl(
106 const SyncWebSocketFactory& factory,
107 const std::string& url,
108 const std::string& id,
109 const FrontendCloserFunc& frontend_closer_func,
110 const ParserFunc& parser_func)
111 : socket_(factory.Run().Pass()),
112 url_(url),
113 crashed_(false),
114 id_(id),
115 frontend_closer_func_(frontend_closer_func),
116 parser_func_(parser_func),
117 unnotified_event_(NULL),
118 next_id_(1),
119 stack_count_(0) {}
121 DevToolsClientImpl::~DevToolsClientImpl() {}
123 void DevToolsClientImpl::SetParserFuncForTesting(
124 const ParserFunc& parser_func) {
125 parser_func_ = parser_func;
128 const std::string& DevToolsClientImpl::GetId() {
129 return id_;
132 bool DevToolsClientImpl::WasCrashed() {
133 return crashed_;
136 Status DevToolsClientImpl::ConnectIfNecessary() {
137 if (stack_count_)
138 return Status(kUnknownError, "cannot connect when nested");
140 if (socket_->IsConnected())
141 return Status(kOk);
143 if (!socket_->Connect(url_)) {
144 // Try to close devtools frontend and then reconnect.
145 Status status = frontend_closer_func_.Run();
146 if (status.IsError())
147 return status;
148 if (!socket_->Connect(url_))
149 return Status(kDisconnected, "unable to connect to renderer");
152 unnotified_connect_listeners_ = listeners_;
153 unnotified_event_listeners_.clear();
154 response_info_map_.clear();
156 // Notify all listeners of the new connection. Do this now so that any errors
157 // that occur are reported now instead of later during some unrelated call.
158 // Also gives listeners a chance to send commands before other clients.
159 return EnsureListenersNotifiedOfConnect();
162 Status DevToolsClientImpl::SendCommand(
163 const std::string& method,
164 const base::DictionaryValue& params) {
165 scoped_ptr<base::DictionaryValue> result;
166 return SendCommandInternal(method, params, &result, true);
169 Status DevToolsClientImpl::SendAsyncCommand(
170 const std::string& method,
171 const base::DictionaryValue& params) {
172 scoped_ptr<base::DictionaryValue> result;
173 return SendCommandInternal(method, params, &result, false);
176 Status DevToolsClientImpl::SendCommandAndGetResult(
177 const std::string& method,
178 const base::DictionaryValue& params,
179 scoped_ptr<base::DictionaryValue>* result) {
180 scoped_ptr<base::DictionaryValue> intermediate_result;
181 Status status = SendCommandInternal(
182 method, params, &intermediate_result, true);
183 if (status.IsError())
184 return status;
185 if (!intermediate_result)
186 return Status(kUnknownError, "inspector response missing result");
187 result->reset(intermediate_result.release());
188 return Status(kOk);
191 void DevToolsClientImpl::AddListener(DevToolsEventListener* listener) {
192 CHECK(listener);
193 listeners_.push_back(listener);
196 Status DevToolsClientImpl::HandleReceivedEvents() {
197 return HandleEventsUntil(base::Bind(&ConditionIsMet), base::TimeDelta());
200 Status DevToolsClientImpl::HandleEventsUntil(
201 const ConditionalFunc& conditional_func, const base::TimeDelta& timeout) {
202 if (!socket_->IsConnected())
203 return Status(kDisconnected, "not connected to DevTools");
205 base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
206 base::TimeDelta next_message_timeout = timeout;
207 while (true) {
208 if (!socket_->HasNextMessage()) {
209 bool is_condition_met = false;
210 Status status = conditional_func.Run(&is_condition_met);
211 if (status.IsError())
212 return status;
213 if (is_condition_met)
214 return Status(kOk);
217 Status status = ProcessNextMessage(-1, next_message_timeout);
218 if (status.IsError())
219 return status;
220 next_message_timeout = deadline - base::TimeTicks::Now();
224 DevToolsClientImpl::ResponseInfo::ResponseInfo(const std::string& method)
225 : state(kWaiting), method(method) {}
227 DevToolsClientImpl::ResponseInfo::~ResponseInfo() {}
229 Status DevToolsClientImpl::SendCommandInternal(
230 const std::string& method,
231 const base::DictionaryValue& params,
232 scoped_ptr<base::DictionaryValue>* result,
233 bool wait_for_response) {
234 if (!socket_->IsConnected())
235 return Status(kDisconnected, "not connected to DevTools");
237 int command_id = next_id_++;
238 base::DictionaryValue command;
239 command.SetInteger("id", command_id);
240 command.SetString("method", method);
241 command.Set("params", params.DeepCopy());
242 std::string message = SerializeValue(&command);
243 if (IsVLogOn(1)) {
244 VLOG(1) << "DEVTOOLS COMMAND " << method << " (id=" << command_id << ") "
245 << FormatValueForDisplay(params);
247 if (!socket_->Send(message))
248 return Status(kDisconnected, "unable to send message to renderer");
250 if (wait_for_response) {
251 linked_ptr<ResponseInfo> response_info =
252 make_linked_ptr(new ResponseInfo(method));
253 response_info_map_[command_id] = response_info;
254 while (response_info->state == kWaiting) {
255 Status status = ProcessNextMessage(
256 command_id, base::TimeDelta::FromMinutes(10));
257 if (status.IsError()) {
258 if (response_info->state == kReceived)
259 response_info_map_.erase(command_id);
260 return status;
263 if (response_info->state == kBlocked) {
264 response_info->state = kIgnored;
265 return Status(kUnexpectedAlertOpen);
267 CHECK_EQ(response_info->state, kReceived);
268 internal::InspectorCommandResponse& response = response_info->response;
269 if (!response.result)
270 return ParseInspectorError(response.error);
271 *result = response.result.Pass();
273 return Status(kOk);
276 Status DevToolsClientImpl::ProcessNextMessage(
277 int expected_id,
278 const base::TimeDelta& timeout) {
279 ScopedIncrementer increment_stack_count(&stack_count_);
281 Status status = EnsureListenersNotifiedOfConnect();
282 if (status.IsError())
283 return status;
284 status = EnsureListenersNotifiedOfEvent();
285 if (status.IsError())
286 return status;
287 status = EnsureListenersNotifiedOfCommandResponse();
288 if (status.IsError())
289 return status;
291 // The command response may have already been received (in which case it will
292 // have been deleted from |response_info_map_|) or blocked while notifying
293 // listeners.
294 if (expected_id != -1) {
295 ResponseInfoMap::iterator iter = response_info_map_.find(expected_id);
296 if (iter == response_info_map_.end() || iter->second->state != kWaiting)
297 return Status(kOk);
300 if (crashed_)
301 return Status(kTabCrashed);
303 std::string message;
304 switch (socket_->ReceiveNextMessage(&message, timeout)) {
305 case SyncWebSocket::kOk:
306 break;
307 case SyncWebSocket::kDisconnected: {
308 std::string err = "Unable to receive message from renderer";
309 LOG(ERROR) << err;
310 return Status(kDisconnected, err);
312 case SyncWebSocket::kTimeout: {
313 std::string err =
314 "Timed out receiving message from renderer: " +
315 base::StringPrintf("%.3lf", timeout.InSecondsF());
316 LOG(ERROR) << err;
317 return Status(kTimeout, err);
319 default:
320 NOTREACHED();
321 break;
324 internal::InspectorMessageType type;
325 internal::InspectorEvent event;
326 internal::InspectorCommandResponse response;
327 if (!parser_func_.Run(message, expected_id, &type, &event, &response)) {
328 LOG(ERROR) << "Bad inspector message: " << message;
329 return Status(kUnknownError, "bad inspector message: " + message);
332 if (type == internal::kEventMessageType)
333 return ProcessEvent(event);
334 CHECK_EQ(type, internal::kCommandResponseMessageType);
335 return ProcessCommandResponse(response);
338 Status DevToolsClientImpl::ProcessEvent(const internal::InspectorEvent& event) {
339 if (IsVLogOn(1)) {
340 VLOG(1) << "DEVTOOLS EVENT " << event.method << " "
341 << FormatValueForDisplay(*event.params);
343 unnotified_event_listeners_ = listeners_;
344 unnotified_event_ = &event;
345 Status status = EnsureListenersNotifiedOfEvent();
346 unnotified_event_ = NULL;
347 if (status.IsError())
348 return status;
349 if (event.method == "Inspector.detached")
350 return Status(kDisconnected, "received Inspector.detached event");
351 if (event.method == "Inspector.targetCrashed") {
352 crashed_ = true;
353 return Status(kTabCrashed);
355 if (event.method == "Page.javascriptDialogOpening") {
356 // A command may have opened the dialog, which will block the response.
357 // To find out which one (if any), do a round trip with a simple command
358 // to the renderer and afterwards see if any of the commands still haven't
359 // received a response.
360 // This relies on the fact that DevTools commands are processed
361 // sequentially. This may break if any of the commands are asynchronous.
362 // If for some reason the round trip command fails, mark all the waiting
363 // commands as blocked and return the error. This is better than risking
364 // a hang.
365 int max_id = next_id_;
366 base::DictionaryValue enable_params;
367 enable_params.SetString("purpose", "detect if alert blocked any cmds");
368 Status enable_status = SendCommand("Inspector.enable", enable_params);
369 for (ResponseInfoMap::const_iterator iter = response_info_map_.begin();
370 iter != response_info_map_.end(); ++iter) {
371 if (iter->first > max_id)
372 continue;
373 if (iter->second->state == kWaiting)
374 iter->second->state = kBlocked;
376 if (enable_status.IsError())
377 return status;
379 return Status(kOk);
382 Status DevToolsClientImpl::ProcessCommandResponse(
383 const internal::InspectorCommandResponse& response) {
384 ResponseInfoMap::iterator iter = response_info_map_.find(response.id);
385 if (IsVLogOn(1)) {
386 std::string method, result;
387 if (iter != response_info_map_.end())
388 method = iter->second->method;
389 if (response.result)
390 result = FormatValueForDisplay(*response.result);
391 else
392 result = response.error;
393 VLOG(1) << "DEVTOOLS RESPONSE " << method << " (id=" << response.id
394 << ") " << result;
397 if (iter == response_info_map_.end())
398 return Status(kUnknownError, "unexpected command response");
400 linked_ptr<ResponseInfo> response_info = response_info_map_[response.id];
401 response_info_map_.erase(response.id);
403 if (response_info->state != kIgnored) {
404 response_info->state = kReceived;
405 response_info->response.id = response.id;
406 response_info->response.error = response.error;
407 if (response.result)
408 response_info->response.result.reset(response.result->DeepCopy());
411 if (response.result) {
412 unnotified_cmd_response_listeners_ = listeners_;
413 unnotified_cmd_response_info_ = response_info;
414 Status status = EnsureListenersNotifiedOfCommandResponse();
415 unnotified_cmd_response_info_.reset();
416 if (status.IsError())
417 return status;
419 return Status(kOk);
422 Status DevToolsClientImpl::EnsureListenersNotifiedOfConnect() {
423 while (unnotified_connect_listeners_.size()) {
424 DevToolsEventListener* listener = unnotified_connect_listeners_.front();
425 unnotified_connect_listeners_.pop_front();
426 Status status = listener->OnConnected(this);
427 if (status.IsError())
428 return status;
430 return Status(kOk);
433 Status DevToolsClientImpl::EnsureListenersNotifiedOfEvent() {
434 while (unnotified_event_listeners_.size()) {
435 DevToolsEventListener* listener = unnotified_event_listeners_.front();
436 unnotified_event_listeners_.pop_front();
437 Status status = listener->OnEvent(
438 this, unnotified_event_->method, *unnotified_event_->params);
439 if (status.IsError())
440 return status;
442 return Status(kOk);
445 Status DevToolsClientImpl::EnsureListenersNotifiedOfCommandResponse() {
446 while (unnotified_cmd_response_listeners_.size()) {
447 DevToolsEventListener* listener =
448 unnotified_cmd_response_listeners_.front();
449 unnotified_cmd_response_listeners_.pop_front();
450 Status status = listener->OnCommandSuccess(
451 this,
452 unnotified_cmd_response_info_->method,
453 *unnotified_cmd_response_info_->response.result.get());
454 if (status.IsError())
455 return status;
457 return Status(kOk);
460 namespace internal {
462 bool ParseInspectorMessage(
463 const std::string& message,
464 int expected_id,
465 InspectorMessageType* type,
466 InspectorEvent* event,
467 InspectorCommandResponse* command_response) {
468 scoped_ptr<base::Value> message_value = base::JSONReader::Read(message);
469 base::DictionaryValue* message_dict;
470 if (!message_value || !message_value->GetAsDictionary(&message_dict))
471 return false;
473 int id;
474 if (!message_dict->HasKey("id")) {
475 std::string method;
476 if (!message_dict->GetString("method", &method))
477 return false;
478 base::DictionaryValue* params = NULL;
479 message_dict->GetDictionary("params", &params);
481 *type = kEventMessageType;
482 event->method = method;
483 if (params)
484 event->params.reset(params->DeepCopy());
485 else
486 event->params.reset(new base::DictionaryValue());
487 return true;
488 } else if (message_dict->GetInteger("id", &id)) {
489 base::DictionaryValue* unscoped_error = NULL;
490 base::DictionaryValue* unscoped_result = NULL;
491 *type = kCommandResponseMessageType;
492 command_response->id = id;
493 // As per Chromium issue 392577, DevTools does not necessarily return a
494 // "result" dictionary for every valid response. In particular,
495 // Tracing.start and Tracing.end command responses do not contain one.
496 // So, if neither "error" nor "result" keys are present, just provide
497 // a blank result dictionary.
498 if (message_dict->GetDictionary("result", &unscoped_result))
499 command_response->result.reset(unscoped_result->DeepCopy());
500 else if (message_dict->GetDictionary("error", &unscoped_error))
501 base::JSONWriter::Write(*unscoped_error, &command_response->error);
502 else
503 command_response->result.reset(new base::DictionaryValue());
504 return true;
506 return false;
509 } // namespace internal