- add sources.
[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 }  // namespace
57
58 namespace internal {
59
60 InspectorEvent::InspectorEvent() {}
61
62 InspectorEvent::~InspectorEvent() {}
63
64 InspectorCommandResponse::InspectorCommandResponse() {}
65
66 InspectorCommandResponse::~InspectorCommandResponse() {}
67
68 }  // namespace internal
69
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()),
76       url_(url),
77       crashed_(false),
78       id_(id),
79       frontend_closer_func_(frontend_closer_func),
80       parser_func_(base::Bind(&internal::ParseInspectorMessage)),
81       unnotified_event_(NULL),
82       next_id_(1),
83       stack_count_(0) {}
84
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()),
92       url_(url),
93       crashed_(false),
94       id_(id),
95       frontend_closer_func_(frontend_closer_func),
96       parser_func_(parser_func),
97       unnotified_event_(NULL),
98       next_id_(1),
99       stack_count_(0) {}
100
101 DevToolsClientImpl::~DevToolsClientImpl() {}
102
103 void DevToolsClientImpl::SetParserFuncForTesting(
104     const ParserFunc& parser_func) {
105   parser_func_ = parser_func;
106 }
107
108 const std::string& DevToolsClientImpl::GetId() {
109   return id_;
110 }
111
112 bool DevToolsClientImpl::WasCrashed() {
113   return crashed_;
114 }
115
116 Status DevToolsClientImpl::ConnectIfNecessary() {
117   if (stack_count_)
118     return Status(kUnknownError, "cannot connect when nested");
119
120   if (socket_->IsConnected())
121     return Status(kOk);
122
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())
127       return status;
128     if (!socket_->Connect(url_))
129       return Status(kDisconnected, "unable to connect to renderer");
130   }
131
132   unnotified_connect_listeners_ = listeners_;
133   unnotified_event_listeners_.clear();
134   response_info_map_.clear();
135
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();
140 }
141
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);
147 }
148
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())
156     return status;
157   if (!intermediate_result)
158     return Status(kUnknownError, "inspector response missing result");
159   result->reset(intermediate_result.release());
160   return Status(kOk);
161 }
162
163 void DevToolsClientImpl::AddListener(DevToolsEventListener* listener) {
164   CHECK(listener);
165   listeners_.push_back(listener);
166 }
167
168 Status DevToolsClientImpl::HandleReceivedEvents() {
169   return HandleEventsUntil(base::Bind(&ConditionIsMet), base::TimeDelta());
170 }
171
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");
176
177   base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
178   base::TimeDelta next_message_timeout = timeout;
179   while (true) {
180     if (!socket_->HasNextMessage()) {
181       bool is_condition_met = false;
182       Status status = conditional_func.Run(&is_condition_met);
183       if (status.IsError())
184         return status;
185       if (is_condition_met)
186         return Status(kOk);
187     }
188
189     Status status = ProcessNextMessage(-1, next_message_timeout);
190     if (status.IsError())
191       return status;
192     next_message_timeout = deadline - base::TimeTicks::Now();
193   }
194 }
195
196 DevToolsClientImpl::ResponseInfo::ResponseInfo(const std::string& method)
197     : state(kWaiting), method(method) {}
198
199 DevToolsClientImpl::ResponseInfo::~ResponseInfo() {}
200
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");
207
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);
214   if (IsVLogOn(1)) {
215     VLOG(1) << "DEVTOOLS COMMAND " << method << " (id=" << command_id << ") "
216             << FormatValueForDisplay(params);
217   }
218   if (!socket_->Send(message))
219     return Status(kDisconnected, "unable to send message to renderer");
220
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);
230       return status;
231     }
232   }
233   if (response_info->state == kBlocked) {
234     response_info->state = kIgnored;
235     return Status(kUnexpectedAlertOpen);
236   }
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();
242   return Status(kOk);
243 }
244
245 Status DevToolsClientImpl::ProcessNextMessage(
246     int expected_id,
247     const base::TimeDelta& timeout) {
248   ScopedIncrementer increment_stack_count(&stack_count_);
249
250   Status status = EnsureListenersNotifiedOfConnect();
251   if (status.IsError())
252     return status;
253   status = EnsureListenersNotifiedOfEvent();
254   if (status.IsError())
255     return status;
256   status = EnsureListenersNotifiedOfCommandResponse();
257   if (status.IsError())
258     return status;
259
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)
263     return Status(kOk);
264
265   if (crashed_)
266     return Status(kTabCrashed);
267
268   std::string message;
269   switch (socket_->ReceiveNextMessage(&message, timeout)) {
270     case SyncWebSocket::kOk:
271       break;
272     case SyncWebSocket::kDisconnected: {
273       std::string err = "Unable to receive message from renderer";
274       LOG(ERROR) << err;
275       return Status(kDisconnected, err);
276     }
277     case SyncWebSocket::kTimeout: {
278       std::string err =
279           "Timed out receiving message from renderer: " +
280           base::StringPrintf("%.3lf", timeout.InSecondsF());
281       LOG(ERROR) << err;
282       return Status(kTimeout, err);
283     }
284     default:
285       NOTREACHED();
286       break;
287   }
288
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);
295   }
296
297   if (type == internal::kEventMessageType)
298     return ProcessEvent(event);
299   CHECK_EQ(type, internal::kCommandResponseMessageType);
300   return ProcessCommandResponse(response);
301 }
302
303 Status DevToolsClientImpl::ProcessEvent(const internal::InspectorEvent& event) {
304   if (IsVLogOn(1)) {
305     VLOG(1) << "DEVTOOLS EVENT " << event.method << " "
306             << FormatValueForDisplay(*event.params);
307   }
308   unnotified_event_listeners_ = listeners_;
309   unnotified_event_ = &event;
310   Status status = EnsureListenersNotifiedOfEvent();
311   unnotified_event_ = NULL;
312   if (status.IsError())
313     return status;
314   if (event.method == "Inspector.detached")
315     return Status(kDisconnected, "received Inspector.detached event");
316   if (event.method == "Inspector.targetCrashed") {
317     crashed_ = true;
318     return Status(kTabCrashed);
319   }
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
329     // a hang.
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)
337         continue;
338       if (iter->second->state == kWaiting)
339         iter->second->state = kBlocked;
340     }
341     if (enable_status.IsError())
342       return status;
343   }
344   return Status(kOk);
345 }
346
347 Status DevToolsClientImpl::ProcessCommandResponse(
348     const internal::InspectorCommandResponse& response) {
349   ResponseInfoMap::iterator iter = response_info_map_.find(response.id);
350   if (IsVLogOn(1)) {
351     std::string method, result;
352     if (iter != response_info_map_.end())
353       method = iter->second->method;
354     if (response.result)
355       result = FormatValueForDisplay(*response.result);
356     else
357       result = response.error;
358     VLOG(1) << "DEVTOOLS RESPONSE " << method << " (id=" << response.id
359             << ") " << result;
360   }
361
362   if (iter == response_info_map_.end())
363     return Status(kUnknownError, "unexpected command response");
364
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");
368
369   if (response_info->state == kIgnored) {
370     response_info_map_.erase(response.id);
371   } else {
372     response_info->state = kReceived;
373     response_info->response.id = response.id;
374     response_info->response.error = response.error;
375     if (response.result)
376       response_info->response.result.reset(response.result->DeepCopy());
377   }
378
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())
385       return status;
386   }
387   return Status(kOk);
388 }
389
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())
396       return status;
397   }
398   return Status(kOk);
399 }
400
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())
408       return status;
409   }
410   return Status(kOk);
411 }
412
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();
418     Status status =
419         listener->OnCommandSuccess(this, unnotified_cmd_response_info_->method);
420     if (status.IsError())
421       return status;
422   }
423   return Status(kOk);
424 }
425
426 namespace internal {
427
428 bool ParseInspectorMessage(
429     const std::string& message,
430     int expected_id,
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))
437     return false;
438
439   int id;
440   if (!message_dict->HasKey("id")) {
441     std::string method;
442     if (!message_dict->GetString("method", &method))
443       return false;
444     base::DictionaryValue* params = NULL;
445     message_dict->GetDictionary("params", &params);
446
447     *type = kEventMessageType;
448     event->method = method;
449     if (params)
450       event->params.reset(params->DeepCopy());
451     else
452       event->params.reset(new base::DictionaryValue());
453     return true;
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))
459       return false;
460
461     *type = kCommandResponseMessageType;
462     command_response->id = id;
463     if (unscoped_result)
464       command_response->result.reset(unscoped_result->DeepCopy());
465     else
466       base::JSONWriter::Write(unscoped_error, &command_response->error);
467     return true;
468   }
469   return false;
470 }
471
472 }  // namespace internal