| OLD | NEW |
| (Empty) |
| 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_impl.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <list> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/json/json_reader.h" | |
| 12 #include "base/stringprintf.h" | |
| 13 #include "base/strings/string_number_conversions.h" | |
| 14 #include "base/strings/string_split.h" | |
| 15 #include "base/threading/platform_thread.h" | |
| 16 #include "base/time.h" | |
| 17 #include "base/values.h" | |
| 18 #include "chrome/test/chromedriver/devtools_client_impl.h" | |
| 19 #include "chrome/test/chromedriver/javascript_dialog_manager.h" | |
| 20 #include "chrome/test/chromedriver/net/net_util.h" | |
| 21 #include "chrome/test/chromedriver/net/sync_websocket_impl.h" | |
| 22 #include "chrome/test/chromedriver/net/url_request_context_getter.h" | |
| 23 #include "chrome/test/chromedriver/status.h" | |
| 24 #include "chrome/test/chromedriver/version.h" | |
| 25 #include "chrome/test/chromedriver/web_view_impl.h" | |
| 26 #include "googleurl/src/gurl.h" | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 typedef std::list<internal::WebViewInfo> WebViewInfoList; | |
| 31 | |
| 32 Status FetchVersionInfo(URLRequestContextGetter* context_getter, | |
| 33 int port, | |
| 34 std::string* version) { | |
| 35 std::string url = base::StringPrintf( | |
| 36 "http://127.0.0.1:%d/json/version", port); | |
| 37 std::string data; | |
| 38 if (!FetchUrl(GURL(url), context_getter, &data)) | |
| 39 return Status(kChromeNotReachable); | |
| 40 return internal::ParseVersionInfo(data, version); | |
| 41 } | |
| 42 | |
| 43 Status FetchWebViewsInfo(URLRequestContextGetter* context_getter, | |
| 44 int port, | |
| 45 WebViewInfoList* info_list) { | |
| 46 std::string url = base::StringPrintf("http://127.0.0.1:%d/json", port); | |
| 47 std::string data; | |
| 48 if (!FetchUrl(GURL(url), context_getter, &data)) | |
| 49 return Status(kChromeNotReachable); | |
| 50 | |
| 51 return internal::ParsePagesInfo(data, info_list); | |
| 52 } | |
| 53 | |
| 54 const internal::WebViewInfo* GetWebViewFromList( | |
| 55 const std::string& id, | |
| 56 const WebViewInfoList& info_list) { | |
| 57 for (WebViewInfoList::const_iterator it = info_list.begin(); | |
| 58 it != info_list.end(); ++it) { | |
| 59 if (it->id == id) | |
| 60 return &(*it); | |
| 61 } | |
| 62 return NULL; | |
| 63 } | |
| 64 | |
| 65 Status CloseWebView(URLRequestContextGetter* context_getter, | |
| 66 int port, | |
| 67 const std::string& web_view_id) { | |
| 68 WebViewInfoList info_list; | |
| 69 Status status = FetchWebViewsInfo(context_getter, port, &info_list); | |
| 70 if (status.IsError()) | |
| 71 return status; | |
| 72 if (!GetWebViewFromList(web_view_id, info_list)) | |
| 73 return Status(kOk); | |
| 74 | |
| 75 bool is_last_web_view = info_list.size() == 1u; | |
| 76 | |
| 77 std::string url = base::StringPrintf( | |
| 78 "http://127.0.0.1:%d/json/close/%s", port, web_view_id.c_str()); | |
| 79 std::string data; | |
| 80 if (!FetchUrl(GURL(url), context_getter, &data)) | |
| 81 return is_last_web_view ? Status(kOk) : Status(kChromeNotReachable); | |
| 82 if (data != "Target is closing") | |
| 83 return Status(kOk); | |
| 84 | |
| 85 // Wait for the target window to be completely closed. | |
| 86 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); | |
| 87 while (base::Time::Now() < deadline) { | |
| 88 info_list.clear(); | |
| 89 status = FetchWebViewsInfo(context_getter, port, &info_list); | |
| 90 if (is_last_web_view && status.code() == kChromeNotReachable) | |
| 91 return Status(kOk); // Closing the last web view leads chrome to quit. | |
| 92 if (status.IsError()) | |
| 93 return status; | |
| 94 if (!GetWebViewFromList(web_view_id, info_list)) | |
| 95 return Status(kOk); | |
| 96 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); | |
| 97 } | |
| 98 | |
| 99 return Status(kUnknownError, "failed to close window in 20 seconds"); | |
| 100 } | |
| 101 | |
| 102 Status FakeCloseWebView() { | |
| 103 // This is for the docked DevTools frontend only. | |
| 104 return Status(kUnknownError, | |
| 105 "docked DevTools frontend should be closed by Javascript"); | |
| 106 } | |
| 107 | |
| 108 Status FakeCloseDevToolsFrontend() { | |
| 109 // This is for the docked DevTools frontend only. | |
| 110 return Status(kOk); | |
| 111 } | |
| 112 | |
| 113 Status CloseDevToolsFrontend(ChromeImpl* chrome, | |
| 114 const SyncWebSocketFactory& socket_factory, | |
| 115 URLRequestContextGetter* context_getter, | |
| 116 int port, | |
| 117 const std::string& web_view_id) { | |
| 118 WebViewInfoList info_list; | |
| 119 Status status = FetchWebViewsInfo(context_getter, port, &info_list); | |
| 120 if (status.IsError()) | |
| 121 return status; | |
| 122 | |
| 123 std::list<std::string> tab_frontend_ids; | |
| 124 std::list<std::string> docked_frontend_ids; | |
| 125 // Filter out DevTools frontend. | |
| 126 for (WebViewInfoList::const_iterator it = info_list.begin(); | |
| 127 it != info_list.end(); ++it) { | |
| 128 if (it->IsFrontend()) { | |
| 129 if (it->type == internal::WebViewInfo::kPage) | |
| 130 tab_frontend_ids.push_back(it->id); | |
| 131 else if (it->type == internal::WebViewInfo::kOther) | |
| 132 docked_frontend_ids.push_back(it->id); | |
| 133 else | |
| 134 return Status(kUnknownError, "unknown type of DevTools frontend"); | |
| 135 } | |
| 136 } | |
| 137 | |
| 138 // Close tab DevTools frontend as if closing a normal web view. | |
| 139 for (std::list<std::string>::const_iterator it = tab_frontend_ids.begin(); | |
| 140 it != tab_frontend_ids.end(); ++it) { | |
| 141 status = CloseWebView(context_getter, port, *it); | |
| 142 if (status.IsError()) | |
| 143 return status; | |
| 144 } | |
| 145 | |
| 146 // Close docked DevTools frontend by Javascript. | |
| 147 for (std::list<std::string>::const_iterator it = docked_frontend_ids.begin(); | |
| 148 it != docked_frontend_ids.end(); ++it) { | |
| 149 std::string ws_url = base::StringPrintf( | |
| 150 "ws://127.0.0.1:%d/devtools/page/%s", port, it->c_str()); | |
| 151 scoped_ptr<WebViewImpl> web_view(new WebViewImpl( | |
| 152 *it, | |
| 153 new DevToolsClientImpl(socket_factory, ws_url, | |
| 154 base::Bind(&FakeCloseDevToolsFrontend)), | |
| 155 chrome, base::Bind(&FakeCloseWebView))); | |
| 156 | |
| 157 status = web_view->ConnectIfNecessary(); | |
| 158 if (status.IsError()) | |
| 159 return status; | |
| 160 | |
| 161 scoped_ptr<base::Value> result; | |
| 162 status = web_view->EvaluateScript( | |
| 163 "", "document.getElementById('close-button-right').click();", &result); | |
| 164 // Ignore disconnected error, because the DevTools frontend is closed. | |
| 165 if (status.IsError() && status.code() != kDisconnected) | |
| 166 return status; | |
| 167 } | |
| 168 | |
| 169 // Wait until DevTools UI disconnects from the given web view. | |
| 170 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); | |
| 171 bool web_view_still_open = false; | |
| 172 while (base::Time::Now() < deadline) { | |
| 173 info_list.clear(); | |
| 174 status = FetchWebViewsInfo(context_getter, port, &info_list); | |
| 175 if (status.IsError()) | |
| 176 return status; | |
| 177 | |
| 178 web_view_still_open = false; | |
| 179 for (WebViewInfoList::const_iterator it = info_list.begin(); | |
| 180 it != info_list.end(); ++it) { | |
| 181 if (it->id == web_view_id) { | |
| 182 if (!it->debugger_url.empty()) | |
| 183 return Status(kOk); | |
| 184 web_view_still_open = true; | |
| 185 break; | |
| 186 } | |
| 187 } | |
| 188 if (!web_view_still_open) | |
| 189 return Status(kUnknownError, "window closed while closing devtools"); | |
| 190 | |
| 191 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); | |
| 192 } | |
| 193 | |
| 194 return Status(kUnknownError, "failed to close DevTools frontend"); | |
| 195 } | |
| 196 | |
| 197 } // namespace | |
| 198 | |
| 199 ChromeImpl::ChromeImpl(URLRequestContextGetter* context_getter, | |
| 200 int port, | |
| 201 const SyncWebSocketFactory& socket_factory) | |
| 202 : context_getter_(context_getter), | |
| 203 port_(port), | |
| 204 socket_factory_(socket_factory), | |
| 205 version_("unknown version"), | |
| 206 build_no_(0) {} | |
| 207 | |
| 208 ChromeImpl::~ChromeImpl() { | |
| 209 web_view_map_.clear(); | |
| 210 } | |
| 211 | |
| 212 std::string ChromeImpl::GetVersion() { | |
| 213 return version_; | |
| 214 } | |
| 215 | |
| 216 Status ChromeImpl::GetWebViews(std::list<WebView*>* web_views) { | |
| 217 WebViewInfoList info_list; | |
| 218 Status status = FetchWebViewsInfo( | |
| 219 context_getter_, port_, &info_list); | |
| 220 if (status.IsError()) | |
| 221 return status; | |
| 222 | |
| 223 std::list<WebView*> internal_web_views; | |
| 224 for (WebViewInfoList::const_iterator it = info_list.begin(); | |
| 225 it != info_list.end(); ++it) { | |
| 226 WebViewMap::const_iterator found = web_view_map_.find(it->id); | |
| 227 if (found != web_view_map_.end()) { | |
| 228 internal_web_views.push_back(found->second.get()); | |
| 229 continue; | |
| 230 } | |
| 231 | |
| 232 std::string ws_url = base::StringPrintf( | |
| 233 "ws://127.0.0.1:%d/devtools/page/%s", port_, it->id.c_str()); | |
| 234 DevToolsClientImpl::FrontendCloserFunc frontend_closer_func = base::Bind( | |
| 235 &CloseDevToolsFrontend, this, socket_factory_, | |
| 236 context_getter_, port_, it->id); | |
| 237 web_view_map_[it->id] = make_linked_ptr(new WebViewImpl( | |
| 238 it->id, | |
| 239 new DevToolsClientImpl(socket_factory_, ws_url, frontend_closer_func), | |
| 240 this, | |
| 241 base::Bind(&CloseWebView, context_getter_, port_, it->id))); | |
| 242 internal_web_views.push_back(web_view_map_[it->id].get()); | |
| 243 } | |
| 244 | |
| 245 web_views->swap(internal_web_views); | |
| 246 return Status(kOk); | |
| 247 } | |
| 248 | |
| 249 Status ChromeImpl::IsJavaScriptDialogOpen(bool* is_open) { | |
| 250 JavaScriptDialogManager* manager; | |
| 251 Status status = GetDialogManagerForOpenDialog(&manager); | |
| 252 if (status.IsError()) | |
| 253 return status; | |
| 254 *is_open = manager != NULL; | |
| 255 return Status(kOk); | |
| 256 } | |
| 257 | |
| 258 Status ChromeImpl::GetJavaScriptDialogMessage(std::string* message) { | |
| 259 JavaScriptDialogManager* manager; | |
| 260 Status status = GetDialogManagerForOpenDialog(&manager); | |
| 261 if (status.IsError()) | |
| 262 return status; | |
| 263 if (!manager) | |
| 264 return Status(kNoAlertOpen); | |
| 265 | |
| 266 return manager->GetDialogMessage(message); | |
| 267 } | |
| 268 | |
| 269 Status ChromeImpl::HandleJavaScriptDialog(bool accept, | |
| 270 const std::string& prompt_text) { | |
| 271 JavaScriptDialogManager* manager; | |
| 272 Status status = GetDialogManagerForOpenDialog(&manager); | |
| 273 if (status.IsError()) | |
| 274 return status; | |
| 275 if (!manager) | |
| 276 return Status(kNoAlertOpen); | |
| 277 | |
| 278 return manager->HandleDialog(accept, prompt_text); | |
| 279 } | |
| 280 | |
| 281 void ChromeImpl::OnWebViewClose(WebView* web_view) { | |
| 282 web_view_map_.erase(web_view->GetId()); | |
| 283 } | |
| 284 | |
| 285 Status ChromeImpl::Init() { | |
| 286 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); | |
| 287 std::string version; | |
| 288 Status status(kOk); | |
| 289 while (base::Time::Now() < deadline) { | |
| 290 status = FetchVersionInfo(context_getter_, port_, &version); | |
| 291 if (status.IsOk()) | |
| 292 break; | |
| 293 if (status.code() != kChromeNotReachable) | |
| 294 return status; | |
| 295 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); | |
| 296 } | |
| 297 if (status.IsError()) | |
| 298 return status; | |
| 299 status = ParseAndCheckVersion(version); | |
| 300 if (status.IsError()) | |
| 301 return status; | |
| 302 | |
| 303 WebViewInfoList info_list; | |
| 304 while (base::Time::Now() < deadline) { | |
| 305 FetchWebViewsInfo(context_getter_, port_, &info_list); | |
| 306 if (info_list.empty()) | |
| 307 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); | |
| 308 else | |
| 309 return Status(kOk); | |
| 310 } | |
| 311 return Status(kUnknownError, "unable to discover open pages"); | |
| 312 } | |
| 313 | |
| 314 int ChromeImpl::GetPort() const { | |
| 315 return port_; | |
| 316 } | |
| 317 | |
| 318 Status ChromeImpl::GetDialogManagerForOpenDialog( | |
| 319 JavaScriptDialogManager** manager) { | |
| 320 std::list<WebView*> web_views; | |
| 321 Status status = GetWebViews(&web_views); | |
| 322 if (status.IsError()) | |
| 323 return status; | |
| 324 | |
| 325 for (std::list<WebView*>::const_iterator it = web_views.begin(); | |
| 326 it != web_views.end(); ++it) { | |
| 327 if ((*it)->GetJavaScriptDialogManager()->IsDialogOpen()) { | |
| 328 *manager = (*it)->GetJavaScriptDialogManager(); | |
| 329 return Status(kOk); | |
| 330 } | |
| 331 } | |
| 332 *manager = NULL; | |
| 333 return Status(kOk); | |
| 334 } | |
| 335 | |
| 336 Status ChromeImpl::ParseAndCheckVersion(const std::string& version) { | |
| 337 if (version.empty()) { | |
| 338 // Content Shell has an empty product version and a fake user agent. | |
| 339 // There's no way to detect the actual version, so assume it is tip of tree. | |
| 340 version_ = "content shell"; | |
| 341 build_no_ = 9999; | |
| 342 return Status(kOk); | |
| 343 } | |
| 344 std::string prefix = "Chrome/"; | |
| 345 if (version.find(prefix) != 0u) | |
| 346 return Status(kUnknownError, "unrecognized Chrome version: " + version); | |
| 347 | |
| 348 std::string stripped_version = version.substr(prefix.length()); | |
| 349 int build_no; | |
| 350 std::vector<std::string> version_parts; | |
| 351 base::SplitString(stripped_version, '.', &version_parts); | |
| 352 if (version_parts.size() != 4 || | |
| 353 !base::StringToInt(version_parts[2], &build_no)) { | |
| 354 return Status(kUnknownError, "unrecognized Chrome version: " + version); | |
| 355 } | |
| 356 | |
| 357 if (build_no < kMinimumSupportedChromeBuildNo) { | |
| 358 return Status(kUnknownError, "Chrome version must be >= " + | |
| 359 GetMinimumSupportedChromeVersion()); | |
| 360 } | |
| 361 version_ = stripped_version; | |
| 362 build_no_ = build_no; | |
| 363 return Status(kOk); | |
| 364 } | |
| 365 | |
| 366 namespace internal { | |
| 367 | |
| 368 WebViewInfo::WebViewInfo(const std::string& id, | |
| 369 const std::string& debugger_url, | |
| 370 const std::string& url, | |
| 371 Type type) | |
| 372 : id(id), debugger_url(debugger_url), url(url), type(type) {} | |
| 373 | |
| 374 bool WebViewInfo::IsFrontend() const { | |
| 375 return url.find("chrome-devtools://") == 0u; | |
| 376 } | |
| 377 | |
| 378 Status ParsePagesInfo(const std::string& data, | |
| 379 std::list<WebViewInfo>* info_list) { | |
| 380 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); | |
| 381 if (!value.get()) | |
| 382 return Status(kUnknownError, "DevTools returned invalid JSON"); | |
| 383 base::ListValue* list; | |
| 384 if (!value->GetAsList(&list)) | |
| 385 return Status(kUnknownError, "DevTools did not return list"); | |
| 386 | |
| 387 std::list<WebViewInfo> info_list_tmp; | |
| 388 for (size_t i = 0; i < list->GetSize(); ++i) { | |
| 389 base::DictionaryValue* info; | |
| 390 if (!list->GetDictionary(i, &info)) | |
| 391 return Status(kUnknownError, "DevTools contains non-dictionary item"); | |
| 392 std::string id; | |
| 393 if (!info->GetString("id", &id)) | |
| 394 return Status(kUnknownError, "DevTools did not include id"); | |
| 395 std::string type; | |
| 396 if (!info->GetString("type", &type)) | |
| 397 return Status(kUnknownError, "DevTools did not include type"); | |
| 398 std::string url; | |
| 399 if (!info->GetString("url", &url)) | |
| 400 return Status(kUnknownError, "DevTools did not include url"); | |
| 401 std::string debugger_url; | |
| 402 info->GetString("webSocketDebuggerUrl", &debugger_url); | |
| 403 if (type == "page") | |
| 404 info_list_tmp.push_back( | |
| 405 WebViewInfo(id, debugger_url, url, internal::WebViewInfo::kPage)); | |
| 406 else if (type == "other") | |
| 407 info_list_tmp.push_back( | |
| 408 WebViewInfo(id, debugger_url, url, internal::WebViewInfo::kOther)); | |
| 409 else | |
| 410 return Status(kUnknownError, "DevTools returned unknown type:" + type); | |
| 411 } | |
| 412 info_list->swap(info_list_tmp); | |
| 413 return Status(kOk); | |
| 414 } | |
| 415 | |
| 416 Status ParseVersionInfo(const std::string& data, | |
| 417 std::string* version) { | |
| 418 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); | |
| 419 if (!value.get()) | |
| 420 return Status(kUnknownError, "version info not in JSON"); | |
| 421 base::DictionaryValue* dict; | |
| 422 if (!value->GetAsDictionary(&dict)) | |
| 423 return Status(kUnknownError, "version info not a dictionary"); | |
| 424 if (!dict->GetString("Browser", version)) { | |
| 425 return Status( | |
| 426 kUnknownError, "Chrome version must be >= 26", | |
| 427 Status(kUnknownError, "version info doesn't include string 'Browser'")); | |
| 428 } | |
| 429 return Status(kOk); | |
| 430 } | |
| 431 | |
| 432 } // namespace internal | |
| OLD | NEW |