9397872852f2c52be9c78fd6fb46a4586b4074fd
[platform/framework/web/crosswalk.git] / src / chrome / test / chromedriver / chrome / devtools_http_client.cc
1 // Copyright (c) 2013 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_http_client.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/json/json_reader.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/threading/platform_thread.h"
14 #include "base/time/time.h"
15 #include "base/values.h"
16 #include "chrome/test/chromedriver/chrome/device_metrics.h"
17 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
18 #include "chrome/test/chromedriver/chrome/log.h"
19 #include "chrome/test/chromedriver/chrome/status.h"
20 #include "chrome/test/chromedriver/chrome/version.h"
21 #include "chrome/test/chromedriver/chrome/web_view_impl.h"
22 #include "chrome/test/chromedriver/net/net_util.h"
23 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
24
25 WebViewInfo::WebViewInfo(const std::string& id,
26                          const std::string& debugger_url,
27                          const std::string& url,
28                          Type type)
29     : id(id), debugger_url(debugger_url), url(url), type(type) {}
30
31 WebViewInfo::~WebViewInfo() {}
32
33 bool WebViewInfo::IsFrontend() const {
34   return url.find("chrome-devtools://") == 0u;
35 }
36
37 WebViewsInfo::WebViewsInfo() {}
38
39 WebViewsInfo::WebViewsInfo(const std::vector<WebViewInfo>& info)
40     : views_info(info) {}
41
42 WebViewsInfo::~WebViewsInfo() {}
43
44 const WebViewInfo& WebViewsInfo::Get(int index) const {
45   return views_info[index];
46 }
47
48 size_t WebViewsInfo::GetSize() const {
49   return views_info.size();
50 }
51
52 const WebViewInfo* WebViewsInfo::GetForId(const std::string& id) const {
53   for (size_t i = 0; i < views_info.size(); ++i) {
54     if (views_info[i].id == id)
55       return &views_info[i];
56   }
57   return NULL;
58 }
59
60 DevToolsHttpClient::DevToolsHttpClient(
61     const NetAddress& address,
62     scoped_refptr<URLRequestContextGetter> context_getter,
63     const SyncWebSocketFactory& socket_factory,
64     scoped_ptr<DeviceMetrics> device_metrics)
65     : context_getter_(context_getter),
66       socket_factory_(socket_factory),
67       server_url_("http://" + address.ToString()),
68       web_socket_url_prefix_(base::StringPrintf(
69           "ws://%s/devtools/page/", address.ToString().c_str())),
70       device_metrics_(device_metrics.Pass()) {}
71
72 DevToolsHttpClient::~DevToolsHttpClient() {}
73
74 Status DevToolsHttpClient::Init(const base::TimeDelta& timeout) {
75   base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
76   std::string browser_version;
77   std::string blink_version;
78
79   while (true) {
80     Status status = GetVersion(&browser_version, &blink_version);
81     if (status.IsOk())
82       break;
83     if (status.code() != kChromeNotReachable ||
84         base::TimeTicks::Now() > deadline) {
85       return status;
86     }
87     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
88   }
89
90   // |blink_version| is should look something like "537.36 (@159105)", and for
91   // this example |blink_revision| should be 159105
92   size_t before = blink_version.find('@');
93   size_t after = blink_version.find(')');
94   if (before == std::string::npos || after == std::string::npos) {
95     return Status(kUnknownError,
96                   "unrecognized Blink version: " + blink_version);
97   }
98
99   std::string blink_revision_string = blink_version.substr(before + 1,
100                                                            after - before - 1);
101   int blink_revision_int;
102   if (!base::StringToInt(blink_revision_string, &blink_revision_int)) {
103     return Status(kUnknownError,
104                   "unrecognized Blink revision: " + blink_revision_string);
105   }
106
107   browser_info_.blink_revision = blink_revision_int;
108
109   if (browser_version.empty()) {
110     browser_info_.browser_name = "content shell";
111     return Status(kOk);
112   }
113   if (browser_version.find("Version/") == 0u) {
114     browser_info_.browser_name = "webview";
115     return Status(kOk);
116   }
117   std::string prefix = "Chrome/";
118   if (browser_version.find(prefix) != 0u) {
119     return Status(kUnknownError,
120                   "unrecognized Chrome version: " + browser_version);
121   }
122
123   std::string stripped_version = browser_version.substr(prefix.length());
124   int temp_build_no;
125   std::vector<std::string> version_parts;
126   base::SplitString(stripped_version, '.', &version_parts);
127   if (version_parts.size() != 4 ||
128       !base::StringToInt(version_parts[2], &temp_build_no)) {
129     return Status(kUnknownError,
130                   "unrecognized Chrome version: " + browser_version);
131   }
132
133   browser_info_.browser_name = "chrome";
134   browser_info_.browser_version = stripped_version;
135   browser_info_.build_no = temp_build_no;
136
137   return Status(kOk);
138 }
139
140 Status DevToolsHttpClient::GetWebViewsInfo(WebViewsInfo* views_info) {
141   std::string data;
142   if (!FetchUrlAndLog(server_url_ + "/json", context_getter_.get(), &data))
143     return Status(kChromeNotReachable);
144
145   return internal::ParseWebViewsInfo(data, views_info);
146 }
147
148 scoped_ptr<DevToolsClient> DevToolsHttpClient::CreateClient(
149     const std::string& id) {
150   return scoped_ptr<DevToolsClient>(new DevToolsClientImpl(
151       socket_factory_,
152       web_socket_url_prefix_ + id,
153       id,
154       base::Bind(
155           &DevToolsHttpClient::CloseFrontends, base::Unretained(this), id)));
156 }
157
158 Status DevToolsHttpClient::CloseWebView(const std::string& id) {
159   std::string data;
160   if (!FetchUrlAndLog(
161           server_url_ + "/json/close/" + id, context_getter_.get(), &data)) {
162     return Status(kOk);  // Closing the last web view leads chrome to quit.
163   }
164
165   // Wait for the target window to be completely closed.
166   base::TimeTicks deadline =
167       base::TimeTicks::Now() + base::TimeDelta::FromSeconds(20);
168   while (base::TimeTicks::Now() < deadline) {
169     WebViewsInfo views_info;
170     Status status = GetWebViewsInfo(&views_info);
171     if (status.code() == kChromeNotReachable)
172       return Status(kOk);
173     if (status.IsError())
174       return status;
175     if (!views_info.GetForId(id))
176       return Status(kOk);
177     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
178   }
179   return Status(kUnknownError, "failed to close window in 20 seconds");
180 }
181
182 Status DevToolsHttpClient::ActivateWebView(const std::string& id) {
183   std::string data;
184   if (!FetchUrlAndLog(
185           server_url_ + "/json/activate/" + id, context_getter_.get(), &data))
186     return Status(kUnknownError, "cannot activate web view");
187   return Status(kOk);
188 }
189
190 const BrowserInfo* DevToolsHttpClient::browser_info() {
191   return &browser_info_;
192 }
193
194 const DeviceMetrics* DevToolsHttpClient::device_metrics() {
195   return device_metrics_.get();
196 }
197
198 Status DevToolsHttpClient::GetVersion(std::string* browser_version,
199                                       std::string* blink_version) {
200   std::string data;
201   if (!FetchUrlAndLog(
202           server_url_ + "/json/version", context_getter_.get(), &data))
203     return Status(kChromeNotReachable);
204
205   return internal::ParseVersionInfo(data, browser_version, blink_version);
206 }
207
208 Status DevToolsHttpClient::CloseFrontends(const std::string& for_client_id) {
209   WebViewsInfo views_info;
210   Status status = GetWebViewsInfo(&views_info);
211   if (status.IsError())
212     return status;
213
214   // Close frontends. Usually frontends are docked in the same page, although
215   // some may be in tabs (undocked, chrome://inspect, the DevTools
216   // discovery page, etc.). Tabs can be closed via the DevTools HTTP close
217   // URL, but docked frontends can only be closed, by design, by connecting
218   // to them and clicking the close button. Close the tab frontends first
219   // in case one of them is debugging a docked frontend, which would prevent
220   // the code from being able to connect to the docked one.
221   std::list<std::string> tab_frontend_ids;
222   std::list<std::string> docked_frontend_ids;
223   for (size_t i = 0; i < views_info.GetSize(); ++i) {
224     const WebViewInfo& view_info = views_info.Get(i);
225     if (view_info.IsFrontend()) {
226       if (view_info.type == WebViewInfo::kPage)
227         tab_frontend_ids.push_back(view_info.id);
228       else if (view_info.type == WebViewInfo::kOther)
229         docked_frontend_ids.push_back(view_info.id);
230       else
231         return Status(kUnknownError, "unknown type of DevTools frontend");
232     }
233   }
234
235   for (std::list<std::string>::const_iterator it = tab_frontend_ids.begin();
236        it != tab_frontend_ids.end(); ++it) {
237     status = CloseWebView(*it);
238     if (status.IsError())
239       return status;
240   }
241
242   for (std::list<std::string>::const_iterator it = docked_frontend_ids.begin();
243        it != docked_frontend_ids.end(); ++it) {
244     scoped_ptr<DevToolsClient> client(new DevToolsClientImpl(
245         socket_factory_,
246         web_socket_url_prefix_ + *it,
247         *it));
248     scoped_ptr<WebViewImpl> web_view(
249         new WebViewImpl(*it, &browser_info_, client.Pass(), NULL));
250
251     status = web_view->ConnectIfNecessary();
252     // Ignore disconnected error, because the debugger might have closed when
253     // its container page was closed above.
254     if (status.IsError() && status.code() != kDisconnected)
255       return status;
256
257     scoped_ptr<base::Value> result;
258     status = web_view->EvaluateScript(
259         std::string(),
260         "document.querySelector('*[id^=\"close-button-\"]').click();",
261         &result);
262     // Ignore disconnected error, because it may be closed already.
263     if (status.IsError() && status.code() != kDisconnected)
264       return status;
265   }
266
267   // Wait until DevTools UI disconnects from the given web view.
268   base::TimeTicks deadline =
269       base::TimeTicks::Now() + base::TimeDelta::FromSeconds(20);
270   while (base::TimeTicks::Now() < deadline) {
271     status = GetWebViewsInfo(&views_info);
272     if (status.IsError())
273       return status;
274
275     const WebViewInfo* view_info = views_info.GetForId(for_client_id);
276     if (!view_info)
277       return Status(kNoSuchWindow, "window was already closed");
278     if (view_info->debugger_url.size())
279       return Status(kOk);
280
281     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
282   }
283   return Status(kUnknownError, "failed to close UI debuggers");
284 }
285
286 bool DevToolsHttpClient::FetchUrlAndLog(const std::string& url,
287                                         URLRequestContextGetter* getter,
288                                         std::string* response) {
289   VLOG(1) << "DevTools request: " << url;
290   bool ok = FetchUrl(url, getter, response);
291   if (ok) {
292     VLOG(1) << "DevTools response: " << *response;
293   } else {
294     VLOG(1) << "DevTools request failed";
295   }
296   return ok;
297 }
298
299 namespace internal {
300
301 Status ParseWebViewsInfo(const std::string& data,
302                          WebViewsInfo* views_info) {
303   scoped_ptr<base::Value> value(base::JSONReader::Read(data));
304   if (!value.get())
305     return Status(kUnknownError, "DevTools returned invalid JSON");
306   base::ListValue* list;
307   if (!value->GetAsList(&list))
308     return Status(kUnknownError, "DevTools did not return list");
309
310   std::vector<WebViewInfo> temp_views_info;
311   for (size_t i = 0; i < list->GetSize(); ++i) {
312     base::DictionaryValue* info;
313     if (!list->GetDictionary(i, &info))
314       return Status(kUnknownError, "DevTools contains non-dictionary item");
315     std::string id;
316     if (!info->GetString("id", &id))
317       return Status(kUnknownError, "DevTools did not include id");
318     std::string type_as_string;
319     if (!info->GetString("type", &type_as_string))
320       return Status(kUnknownError, "DevTools did not include type");
321     std::string url;
322     if (!info->GetString("url", &url))
323       return Status(kUnknownError, "DevTools did not include url");
324     std::string debugger_url;
325     info->GetString("webSocketDebuggerUrl", &debugger_url);
326     WebViewInfo::Type type;
327     if (type_as_string == "app")
328       type = WebViewInfo::kApp;
329     else if (type_as_string == "background_page")
330       type = WebViewInfo::kBackgroundPage;
331     else if (type_as_string == "page")
332       type = WebViewInfo::kPage;
333     else if (type_as_string == "worker")
334       type = WebViewInfo::kWorker;
335     else if (type_as_string == "other")
336       type = WebViewInfo::kOther;
337     else
338       return Status(kUnknownError,
339                     "DevTools returned unknown type:" + type_as_string);
340     temp_views_info.push_back(WebViewInfo(id, debugger_url, url, type));
341   }
342   *views_info = WebViewsInfo(temp_views_info);
343   return Status(kOk);
344 }
345
346 Status ParseVersionInfo(const std::string& data,
347                         std::string* browser_version,
348                         std::string* blink_version) {
349   scoped_ptr<base::Value> value(base::JSONReader::Read(data));
350   if (!value.get())
351     return Status(kUnknownError, "version info not in JSON");
352   base::DictionaryValue* dict;
353   if (!value->GetAsDictionary(&dict))
354     return Status(kUnknownError, "version info not a dictionary");
355   if (!dict->GetString("Browser", browser_version)) {
356     return Status(
357         kUnknownError,
358         "Chrome version must be >= " + GetMinimumSupportedChromeVersion(),
359         Status(kUnknownError, "version info doesn't include string 'Browser'"));
360   }
361   if (!dict->GetString("WebKit-Version", blink_version)) {
362     return Status(kUnknownError,
363                   "version info doesn't include string 'WebKit-Version'");
364   }
365   return Status(kOk);
366 }
367
368 }  // namespace internal