Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / test / chromedriver / chrome / devtools_client_impl.cc
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/chrome/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/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"
19
20 namespace {
21
22 const char kInspectorContextError[] =
23     "Execution context with given id not found.";
24
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);
34   }
35   return Status(kUnknownError, "unhandled inspector error: " + error_json);
36 }
37
38 class ScopedIncrementer {
39  public:
40   explicit ScopedIncrementer(int* count) : count_(count) {
41     (*count_)++;
42   }
43   ~ScopedIncrementer() {
44     (*count_)--;
45   }
46
47  private:
48   int* count_;
49 };
50
51 Status ConditionIsMet(bool* is_condition_met) {
52   *is_condition_met = true;
53   return Status(kOk);
54 }
55
56 Status FakeCloseFrontends() {
57   return Status(kOk);
58 }
59
60 }  // namespace
61
62 namespace internal {
63
64 InspectorEvent::InspectorEvent() {}
65
66 InspectorEvent::~InspectorEvent() {}
67
68 InspectorCommandResponse::InspectorCommandResponse() {}
69
70 InspectorCommandResponse::~InspectorCommandResponse() {}
71
72 }  // namespace internal
73
74 const char DevToolsClientImpl::kBrowserwideDevToolsClientId[] = "browser";
75
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) {}
89
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) {}
104
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) {}
120
121 DevToolsClientImpl::~DevToolsClientImpl() {}
122
123 void DevToolsClientImpl::SetParserFuncForTesting(
124     const ParserFunc& parser_func) {
125   parser_func_ = parser_func;
126 }
127
128 const std::string& DevToolsClientImpl::GetId() {
129   return id_;
130 }
131
132 bool DevToolsClientImpl::WasCrashed() {
133   return crashed_;
134 }
135
136 Status DevToolsClientImpl::ConnectIfNecessary() {
137   if (stack_count_)
138     return Status(kUnknownError, "cannot connect when nested");
139
140   if (socket_->IsConnected())
141     return Status(kOk);
142
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");
150   }
151
152   unnotified_connect_listeners_ = listeners_;
153   unnotified_event_listeners_.clear();
154   response_info_map_.clear();
155
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();
160 }
161
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);
167 }
168
169 Status DevToolsClientImpl::SendCommandAndGetResult(
170     const std::string& method,
171     const base::DictionaryValue& params,
172     scoped_ptr<base::DictionaryValue>* result) {
173   scoped_ptr<base::DictionaryValue> intermediate_result;
174   Status status = SendCommandInternal(method, params, &intermediate_result);
175   if (status.IsError())
176     return status;
177   if (!intermediate_result)
178     return Status(kUnknownError, "inspector response missing result");
179   result->reset(intermediate_result.release());
180   return Status(kOk);
181 }
182
183 void DevToolsClientImpl::AddListener(DevToolsEventListener* listener) {
184   CHECK(listener);
185   listeners_.push_back(listener);
186 }
187
188 Status DevToolsClientImpl::HandleReceivedEvents() {
189   return HandleEventsUntil(base::Bind(&ConditionIsMet), base::TimeDelta());
190 }
191
192 Status DevToolsClientImpl::HandleEventsUntil(
193     const ConditionalFunc& conditional_func, const base::TimeDelta& timeout) {
194   if (!socket_->IsConnected())
195     return Status(kDisconnected, "not connected to DevTools");
196
197   base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
198   base::TimeDelta next_message_timeout = timeout;
199   while (true) {
200     if (!socket_->HasNextMessage()) {
201       bool is_condition_met = false;
202       Status status = conditional_func.Run(&is_condition_met);
203       if (status.IsError())
204         return status;
205       if (is_condition_met)
206         return Status(kOk);
207     }
208
209     Status status = ProcessNextMessage(-1, next_message_timeout);
210     if (status.IsError())
211       return status;
212     next_message_timeout = deadline - base::TimeTicks::Now();
213   }
214 }
215
216 DevToolsClientImpl::ResponseInfo::ResponseInfo(const std::string& method)
217     : state(kWaiting), method(method) {}
218
219 DevToolsClientImpl::ResponseInfo::~ResponseInfo() {}
220
221 Status DevToolsClientImpl::SendCommandInternal(
222     const std::string& method,
223     const base::DictionaryValue& params,
224     scoped_ptr<base::DictionaryValue>* result) {
225   if (!socket_->IsConnected())
226     return Status(kDisconnected, "not connected to DevTools");
227
228   int command_id = next_id_++;
229   base::DictionaryValue command;
230   command.SetInteger("id", command_id);
231   command.SetString("method", method);
232   command.Set("params", params.DeepCopy());
233   std::string message = SerializeValue(&command);
234   if (IsVLogOn(1)) {
235     VLOG(1) << "DEVTOOLS COMMAND " << method << " (id=" << command_id << ") "
236             << FormatValueForDisplay(params);
237   }
238   if (!socket_->Send(message))
239     return Status(kDisconnected, "unable to send message to renderer");
240
241   linked_ptr<ResponseInfo> response_info =
242       make_linked_ptr(new ResponseInfo(method));
243   response_info_map_[command_id] = response_info;
244   while (response_info->state == kWaiting) {
245     Status status = ProcessNextMessage(
246         command_id, base::TimeDelta::FromMinutes(10));
247     if (status.IsError()) {
248       if (response_info->state == kReceived)
249         response_info_map_.erase(command_id);
250       return status;
251     }
252   }
253   if (response_info->state == kBlocked) {
254     response_info->state = kIgnored;
255     return Status(kUnexpectedAlertOpen);
256   }
257   CHECK_EQ(response_info->state, kReceived);
258   internal::InspectorCommandResponse& response = response_info->response;
259   if (!response.result)
260     return ParseInspectorError(response.error);
261   *result = response.result.Pass();
262   return Status(kOk);
263 }
264
265 Status DevToolsClientImpl::ProcessNextMessage(
266     int expected_id,
267     const base::TimeDelta& timeout) {
268   ScopedIncrementer increment_stack_count(&stack_count_);
269
270   Status status = EnsureListenersNotifiedOfConnect();
271   if (status.IsError())
272     return status;
273   status = EnsureListenersNotifiedOfEvent();
274   if (status.IsError())
275     return status;
276   status = EnsureListenersNotifiedOfCommandResponse();
277   if (status.IsError())
278     return status;
279
280   // The command response may have already been received (in which case it will
281   // have been deleted from |response_info_map_|) or blocked while notifying
282   // listeners.
283   if (expected_id != -1) {
284     ResponseInfoMap::iterator iter = response_info_map_.find(expected_id);
285     if (iter == response_info_map_.end() || iter->second->state != kWaiting)
286       return Status(kOk);
287   }
288
289   if (crashed_)
290     return Status(kTabCrashed);
291
292   std::string message;
293   switch (socket_->ReceiveNextMessage(&message, timeout)) {
294     case SyncWebSocket::kOk:
295       break;
296     case SyncWebSocket::kDisconnected: {
297       std::string err = "Unable to receive message from renderer";
298       LOG(ERROR) << err;
299       return Status(kDisconnected, err);
300     }
301     case SyncWebSocket::kTimeout: {
302       std::string err =
303           "Timed out receiving message from renderer: " +
304           base::StringPrintf("%.3lf", timeout.InSecondsF());
305       LOG(ERROR) << err;
306       return Status(kTimeout, err);
307     }
308     default:
309       NOTREACHED();
310       break;
311   }
312
313   internal::InspectorMessageType type;
314   internal::InspectorEvent event;
315   internal::InspectorCommandResponse response;
316   if (!parser_func_.Run(message, expected_id, &type, &event, &response)) {
317     LOG(ERROR) << "Bad inspector message: " << message;
318     return Status(kUnknownError, "bad inspector message: " + message);
319   }
320
321   if (type == internal::kEventMessageType)
322     return ProcessEvent(event);
323   CHECK_EQ(type, internal::kCommandResponseMessageType);
324   return ProcessCommandResponse(response);
325 }
326
327 Status DevToolsClientImpl::ProcessEvent(const internal::InspectorEvent& event) {
328   if (IsVLogOn(1)) {
329     VLOG(1) << "DEVTOOLS EVENT " << event.method << " "
330             << FormatValueForDisplay(*event.params);
331   }
332   unnotified_event_listeners_ = listeners_;
333   unnotified_event_ = &event;
334   Status status = EnsureListenersNotifiedOfEvent();
335   unnotified_event_ = NULL;
336   if (status.IsError())
337     return status;
338   if (event.method == "Inspector.detached")
339     return Status(kDisconnected, "received Inspector.detached event");
340   if (event.method == "Inspector.targetCrashed") {
341     crashed_ = true;
342     return Status(kTabCrashed);
343   }
344   if (event.method == "Page.javascriptDialogOpening") {
345     // A command may have opened the dialog, which will block the response.
346     // To find out which one (if any), do a round trip with a simple command
347     // to the renderer and afterwards see if any of the commands still haven't
348     // received a response.
349     // This relies on the fact that DevTools commands are processed
350     // sequentially. This may break if any of the commands are asynchronous.
351     // If for some reason the round trip command fails, mark all the waiting
352     // commands as blocked and return the error. This is better than risking
353     // a hang.
354     int max_id = next_id_;
355     base::DictionaryValue enable_params;
356     enable_params.SetString("purpose", "detect if alert blocked any cmds");
357     Status enable_status = SendCommand("Inspector.enable", enable_params);
358     for (ResponseInfoMap::const_iterator iter = response_info_map_.begin();
359          iter != response_info_map_.end(); ++iter) {
360       if (iter->first > max_id)
361         continue;
362       if (iter->second->state == kWaiting)
363         iter->second->state = kBlocked;
364     }
365     if (enable_status.IsError())
366       return status;
367   }
368   return Status(kOk);
369 }
370
371 Status DevToolsClientImpl::ProcessCommandResponse(
372     const internal::InspectorCommandResponse& response) {
373   ResponseInfoMap::iterator iter = response_info_map_.find(response.id);
374   if (IsVLogOn(1)) {
375     std::string method, result;
376     if (iter != response_info_map_.end())
377       method = iter->second->method;
378     if (response.result)
379       result = FormatValueForDisplay(*response.result);
380     else
381       result = response.error;
382     VLOG(1) << "DEVTOOLS RESPONSE " << method << " (id=" << response.id
383             << ") " << result;
384   }
385
386   if (iter == response_info_map_.end())
387     return Status(kUnknownError, "unexpected command response");
388
389   linked_ptr<ResponseInfo> response_info = response_info_map_[response.id];
390   response_info_map_.erase(response.id);
391
392   if (response_info->state != kIgnored) {
393     response_info->state = kReceived;
394     response_info->response.id = response.id;
395     response_info->response.error = response.error;
396     if (response.result)
397       response_info->response.result.reset(response.result->DeepCopy());
398   }
399
400   if (response.result) {
401     unnotified_cmd_response_listeners_ = listeners_;
402     unnotified_cmd_response_info_ = response_info;
403     Status status = EnsureListenersNotifiedOfCommandResponse();
404     unnotified_cmd_response_info_.reset();
405     if (status.IsError())
406       return status;
407   }
408   return Status(kOk);
409 }
410
411 Status DevToolsClientImpl::EnsureListenersNotifiedOfConnect() {
412   while (unnotified_connect_listeners_.size()) {
413     DevToolsEventListener* listener = unnotified_connect_listeners_.front();
414     unnotified_connect_listeners_.pop_front();
415     Status status = listener->OnConnected(this);
416     if (status.IsError())
417       return status;
418   }
419   return Status(kOk);
420 }
421
422 Status DevToolsClientImpl::EnsureListenersNotifiedOfEvent() {
423   while (unnotified_event_listeners_.size()) {
424     DevToolsEventListener* listener = unnotified_event_listeners_.front();
425     unnotified_event_listeners_.pop_front();
426     Status status = listener->OnEvent(
427         this, unnotified_event_->method, *unnotified_event_->params);
428     if (status.IsError())
429       return status;
430   }
431   return Status(kOk);
432 }
433
434 Status DevToolsClientImpl::EnsureListenersNotifiedOfCommandResponse() {
435   while (unnotified_cmd_response_listeners_.size()) {
436     DevToolsEventListener* listener =
437         unnotified_cmd_response_listeners_.front();
438     unnotified_cmd_response_listeners_.pop_front();
439     Status status =
440         listener->OnCommandSuccess(this, unnotified_cmd_response_info_->method);
441     if (status.IsError())
442       return status;
443   }
444   return Status(kOk);
445 }
446
447 namespace internal {
448
449 bool ParseInspectorMessage(
450     const std::string& message,
451     int expected_id,
452     InspectorMessageType* type,
453     InspectorEvent* event,
454     InspectorCommandResponse* command_response) {
455   scoped_ptr<base::Value> message_value(base::JSONReader::Read(message));
456   base::DictionaryValue* message_dict;
457   if (!message_value || !message_value->GetAsDictionary(&message_dict))
458     return false;
459
460   int id;
461   if (!message_dict->HasKey("id")) {
462     std::string method;
463     if (!message_dict->GetString("method", &method))
464       return false;
465     base::DictionaryValue* params = NULL;
466     message_dict->GetDictionary("params", &params);
467
468     *type = kEventMessageType;
469     event->method = method;
470     if (params)
471       event->params.reset(params->DeepCopy());
472     else
473       event->params.reset(new base::DictionaryValue());
474     return true;
475   } else if (message_dict->GetInteger("id", &id)) {
476     base::DictionaryValue* unscoped_error = NULL;
477     base::DictionaryValue* unscoped_result = NULL;
478     *type = kCommandResponseMessageType;
479     command_response->id = id;
480     // As per Chromium issue 392577, DevTools does not necessarily return a
481     // "result" dictionary for every valid response. In particular,
482     // Tracing.start and Tracing.end command responses do not contain one.
483     // So, if neither "error" nor "result" keys are present, just provide
484     // a blank result dictionary.
485     if (message_dict->GetDictionary("result", &unscoped_result))
486       command_response->result.reset(unscoped_result->DeepCopy());
487     else if (message_dict->GetDictionary("error", &unscoped_error))
488       base::JSONWriter::Write(unscoped_error, &command_response->error);
489     else
490       command_response->result.reset(new base::DictionaryValue());
491     return true;
492   }
493   return false;
494 }
495
496 }  // namespace internal