Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/test/chromedriver/chrome_impl.h" | 5 #include "chrome/test/chromedriver/chrome_impl.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <list> | 8 #include <list> |
| 9 | 9 |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| 11 #include "base/json/json_reader.h" | 11 #include "base/json/json_reader.h" |
| 12 #include "base/stringprintf.h" | 12 #include "base/stringprintf.h" |
| 13 #include "base/threading/platform_thread.h" | 13 #include "base/threading/platform_thread.h" |
| 14 #include "base/time.h" | 14 #include "base/time.h" |
| 15 #include "base/values.h" | 15 #include "base/values.h" |
| 16 #include "chrome/test/chromedriver/devtools_client_impl.h" | 16 #include "chrome/test/chromedriver/devtools_client_impl.h" |
| 17 #include "chrome/test/chromedriver/net/net_util.h" | 17 #include "chrome/test/chromedriver/net/net_util.h" |
| 18 #include "chrome/test/chromedriver/net/sync_websocket_impl.h" | 18 #include "chrome/test/chromedriver/net/sync_websocket_impl.h" |
| 19 #include "chrome/test/chromedriver/net/url_request_context_getter.h" | 19 #include "chrome/test/chromedriver/net/url_request_context_getter.h" |
| 20 #include "chrome/test/chromedriver/status.h" | 20 #include "chrome/test/chromedriver/status.h" |
| 21 #include "chrome/test/chromedriver/web_view_impl.h" | 21 #include "chrome/test/chromedriver/web_view_impl.h" |
| 22 #include "googleurl/src/gurl.h" | 22 #include "googleurl/src/gurl.h" |
| 23 | 23 |
| 24 namespace { | 24 namespace { |
| 25 | 25 |
| 26 typedef base::Callback<Status(base::ListValue*)> ParserFunc; | |
| 26 Status FetchPagesInfo(URLRequestContextGetter* context_getter, | 27 Status FetchPagesInfo(URLRequestContextGetter* context_getter, |
| 27 int port, | 28 int port, |
| 28 std::list<std::string>* page_ids) { | 29 const ParserFunc& parser) { |
|
kkania
2013/02/22 01:35:24
i think this might be simpler to understand if ins
chrisgao (Use stgao instead)
2013/02/27 19:29:44
Done.
| |
| 29 std::string url = base::StringPrintf("http://127.0.0.1:%d/json", port); | 30 std::string url = base::StringPrintf("http://127.0.0.1:%d/json", port); |
| 30 std::string data; | 31 std::string data; |
| 31 if (!FetchUrl(GURL(url), context_getter, &data)) | 32 if (!FetchUrl(GURL(url), context_getter, &data)) |
| 32 return Status(kChromeNotReachable); | 33 return Status(kChromeNotReachable); |
| 33 return internal::ParsePagesInfo(data, page_ids); | 34 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); |
| 35 if (!value.get()) | |
| 36 return Status(kUnknownError, "DevTools returned invalid JSON"); | |
| 37 base::ListValue* list; | |
| 38 if (!value->GetAsList(&list)) | |
| 39 return Status(kUnknownError, "DevTools did not return list"); | |
| 40 | |
| 41 return parser.Run(list); | |
| 34 } | 42 } |
| 35 | 43 |
| 36 Status CloseWebView(URLRequestContextGetter* context_getter, | 44 Status CloseWebView(URLRequestContextGetter* context_getter, |
| 37 int port, | 45 int port, |
| 38 const std::string& web_view_id) { | 46 const std::string& web_view_id) { |
| 39 std::list<std::string> ids; | 47 std::list<std::string> ids; |
| 40 Status status = FetchPagesInfo(context_getter, port, &ids); | 48 Status status = FetchPagesInfo( |
| 49 context_getter, port, base::Bind(internal::ParsePagesInfo, &ids)); | |
| 41 if (status.IsError()) | 50 if (status.IsError()) |
| 42 return status; | 51 return status; |
| 43 if (std::find(ids.begin(), ids.end(), web_view_id) == ids.end()) | 52 if (std::find(ids.begin(), ids.end(), web_view_id) == ids.end()) |
| 44 return Status(kOk); | 53 return Status(kOk); |
| 45 | 54 |
| 46 bool is_last_web_view = ids.size() == 1; | 55 bool is_last_web_view = ids.size() == 1; |
| 47 | 56 |
| 48 std::string url = base::StringPrintf( | 57 std::string url = base::StringPrintf( |
| 49 "http://127.0.0.1:%d/json/close/%s", port, web_view_id.c_str()); | 58 "http://127.0.0.1:%d/json/close/%s", port, web_view_id.c_str()); |
| 50 std::string data; | 59 std::string data; |
| 51 if (!FetchUrl(GURL(url), context_getter, &data)) | 60 if (!FetchUrl(GURL(url), context_getter, &data)) |
| 52 return is_last_web_view ? Status(kOk) : Status(kChromeNotReachable); | 61 return is_last_web_view ? Status(kOk) : Status(kChromeNotReachable); |
| 53 if (data != "Target is closing") | 62 if (data != "Target is closing") |
| 54 return Status(kOk); | 63 return Status(kOk); |
| 55 | 64 |
| 56 // Wait for the target window to be completely closed. | 65 // Wait for the target window to be completely closed. |
| 57 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); | 66 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); |
| 58 while (base::Time::Now() < deadline) { | 67 while (base::Time::Now() < deadline) { |
| 59 ids.clear(); | 68 ids.clear(); |
| 60 status = FetchPagesInfo(context_getter, port, &ids); | 69 status = FetchPagesInfo( |
| 70 context_getter, port, base::Bind(internal::ParsePagesInfo, &ids)); | |
| 61 if (is_last_web_view && status.code() == kChromeNotReachable) | 71 if (is_last_web_view && status.code() == kChromeNotReachable) |
| 62 return Status(kOk); // Closing the last web view leads chrome to quit. | 72 return Status(kOk); // Closing the last web view leads chrome to quit. |
| 63 if (status.IsError()) | 73 if (status.IsError()) |
| 64 return status; | 74 return status; |
| 65 if (std::find(ids.begin(), ids.end(), web_view_id) == ids.end()) | 75 if (std::find(ids.begin(), ids.end(), web_view_id) == ids.end()) |
| 66 return Status(kOk); | 76 return Status(kOk); |
| 67 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); | 77 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); |
| 68 } | 78 } |
| 69 | 79 |
| 70 return Status(kUnknownError, "failed to close window in 20 seconds"); | 80 return Status(kUnknownError, "failed to close window in 20 seconds"); |
| 71 } | 81 } |
| 72 | 82 |
| 83 Status FakeCloseWebView() { | |
| 84 return Status(kUnknownError, | |
| 85 "built-in DevTools frontend should be closed by Javascript"); | |
| 86 } | |
| 87 | |
| 88 Status FakeCloseDevToolsFrontend() { | |
| 89 return Status(kUnknownError, | |
| 90 "built-in DevTools frontend should not try to close others"); | |
| 91 } | |
| 92 | |
| 93 Status CloseDevToolsFrontend(ChromeImpl* chrome, | |
| 94 const SyncWebSocketFactory& socket_factory, | |
| 95 URLRequestContextGetter* context_getter, | |
| 96 int port, | |
| 97 const std::string& page_id) { | |
| 98 std::list<std::string> page_ids; // DevTools opened in a separate page. | |
| 99 std::list<std::string> frontend_ids; // In-page DevTools frontend. | |
|
kkania
2013/02/22 01:35:24
the naming here is a bit confusing. how about call
chrisgao (Use stgao instead)
2013/02/27 19:29:44
Good suggestion.
| |
| 100 | |
| 101 Status status = FetchPagesInfo( | |
| 102 context_getter, port, | |
| 103 base::Bind(internal::ParseDevToolsInfo, &page_ids, &frontend_ids)); | |
| 104 if (status.IsError()) | |
| 105 return status; | |
| 106 | |
| 107 if (!page_ids.empty()) { | |
| 108 // Close DevTools page in the same way as a normal web view by json/close. | |
| 109 std::list<WebView*> web_views; | |
| 110 status = chrome->GetWebViews(&web_views); | |
| 111 if (status.IsError()) | |
| 112 return status; | |
| 113 for (std::list<WebView*>::const_iterator it = web_views.begin(); | |
| 114 it != web_views.end(); ++it) { | |
| 115 if (std::find(page_ids.begin(), page_ids.end(), (*it)->GetId()) | |
| 116 != page_ids.end()) { | |
| 117 status = (*it)->Close(); | |
| 118 if (status.IsError()) | |
| 119 return status; | |
| 120 } | |
| 121 } | |
| 122 } | |
| 123 | |
| 124 // Close in-page DevTools frontend by Javascript. | |
| 125 for (std::list<std::string>::const_iterator it = frontend_ids.begin(); | |
| 126 it != frontend_ids.end(); ++it) { | |
| 127 std::string ws_url = base::StringPrintf( | |
| 128 "ws://127.0.0.1:%d/devtools/page/%s", port, it->c_str()); | |
| 129 scoped_ptr<WebViewImpl> web_view(new WebViewImpl( | |
| 130 *it, | |
| 131 new DevToolsClientImpl(socket_factory, ws_url, | |
| 132 base::Bind(&FakeCloseDevToolsFrontend)), | |
| 133 chrome, base::Bind(&FakeCloseWebView))); | |
| 134 | |
| 135 scoped_ptr<base::Value> result; | |
| 136 status = web_view->EvaluateScript( | |
| 137 "", "document.getElementById('close-button-right').click();", &result); | |
| 138 // Ignore Disconnected error, because the DevTools frontend is closed. | |
|
kkania
2013/02/22 01:35:24
disconnected, D->d
chrisgao (Use stgao instead)
2013/02/27 19:29:44
Done.
| |
| 139 if (status.IsError() && status.code() != kDisconnected) | |
| 140 return status; | |
| 141 } | |
| 142 | |
| 143 // Wait until DevTools UI disconnects from the given web view. | |
| 144 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); | |
| 145 bool has_debugger_url = false; | |
| 146 base::Callback<Status(base::ListValue*)> checker = | |
| 147 base::Bind(&internal::CheckPageDebuggerUrl, page_id, &has_debugger_url); | |
| 148 while (base::Time::Now() < deadline) { | |
| 149 status = FetchPagesInfo(context_getter, port, checker); | |
| 150 if (status.IsError()) | |
| 151 return status; | |
| 152 if (has_debugger_url) | |
| 153 return Status(kOk); | |
| 154 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); | |
| 155 } | |
| 156 | |
| 157 return Status(kUnknownError, "failed to close DevTools frontend"); | |
| 158 } | |
| 159 | |
| 73 } // namespace | 160 } // namespace |
| 74 | 161 |
| 75 ChromeImpl::ChromeImpl(URLRequestContextGetter* context_getter, | 162 ChromeImpl::ChromeImpl(URLRequestContextGetter* context_getter, |
| 76 int port, | 163 int port, |
| 77 const SyncWebSocketFactory& socket_factory) | 164 const SyncWebSocketFactory& socket_factory) |
| 78 : context_getter_(context_getter), | 165 : context_getter_(context_getter), |
| 79 port_(port), | 166 port_(port), |
| 80 socket_factory_(socket_factory) {} | 167 socket_factory_(socket_factory) {} |
| 81 | 168 |
| 82 ChromeImpl::~ChromeImpl() { | 169 ChromeImpl::~ChromeImpl() { |
| 83 web_view_map_.clear(); | 170 web_view_map_.clear(); |
| 84 } | 171 } |
| 85 | 172 |
| 86 Status ChromeImpl::GetWebViews(std::list<WebView*>* web_views) { | 173 Status ChromeImpl::GetWebViews(std::list<WebView*>* web_views) { |
| 87 std::list<std::string> ids; | 174 std::list<std::string> ids; |
| 88 Status status = FetchPagesInfo(context_getter_, port_, &ids); | 175 Status status = FetchPagesInfo( |
| 176 context_getter_, port_, base::Bind(internal::ParsePagesInfo, &ids)); | |
| 89 if (status.IsError()) | 177 if (status.IsError()) |
| 90 return status; | 178 return status; |
| 91 | 179 |
| 92 std::list<WebView*> internal_web_views; | 180 std::list<WebView*> internal_web_views; |
| 93 for (std::list<std::string>::const_iterator it = ids.begin(); | 181 for (std::list<std::string>::const_iterator it = ids.begin(); |
| 94 it != ids.end(); ++it) { | 182 it != ids.end(); ++it) { |
| 95 WebViewMap::const_iterator found = web_view_map_.find(*it); | 183 WebViewMap::const_iterator found = web_view_map_.find(*it); |
| 96 if (found != web_view_map_.end()) { | 184 if (found != web_view_map_.end()) { |
| 97 internal_web_views.push_back(found->second.get()); | 185 internal_web_views.push_back(found->second.get()); |
| 98 continue; | 186 continue; |
| 99 } | 187 } |
| 100 | 188 |
| 101 std::string ws_url = base::StringPrintf( | 189 std::string ws_url = base::StringPrintf( |
| 102 "ws://127.0.0.1:%d/devtools/page/%s", port_, it->c_str()); | 190 "ws://127.0.0.1:%d/devtools/page/%s", port_, it->c_str()); |
| 191 DevToolsClientImpl::FrontendCloserFunc frontend_closer_func = base::Bind( | |
| 192 &CloseDevToolsFrontend, this, socket_factory_, | |
| 193 context_getter_, port_, *it); | |
| 103 web_view_map_[*it] = make_linked_ptr(new WebViewImpl( | 194 web_view_map_[*it] = make_linked_ptr(new WebViewImpl( |
| 104 *it, new DevToolsClientImpl(socket_factory_, ws_url), | 195 *it, |
| 105 this, base::Bind(&CloseWebView, context_getter_, port_, *it))); | 196 new DevToolsClientImpl(socket_factory_, ws_url, frontend_closer_func), |
| 197 this, | |
| 198 base::Bind(&CloseWebView, context_getter_, port_, *it))); | |
| 106 internal_web_views.push_back(web_view_map_[*it].get()); | 199 internal_web_views.push_back(web_view_map_[*it].get()); |
| 107 } | 200 } |
| 108 | 201 |
| 109 web_views->swap(internal_web_views); | 202 web_views->swap(internal_web_views); |
| 110 return Status(kOk); | 203 return Status(kOk); |
| 111 } | 204 } |
| 112 | 205 |
| 113 void ChromeImpl::OnWebViewClose(WebView* web_view) { | 206 void ChromeImpl::OnWebViewClose(WebView* web_view) { |
| 114 web_view_map_.erase(web_view->GetId()); | 207 web_view_map_.erase(web_view->GetId()); |
| 115 } | 208 } |
| 116 | 209 |
| 117 Status ChromeImpl::Init() { | 210 Status ChromeImpl::Init() { |
| 118 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); | 211 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); |
| 119 std::list<std::string> page_ids; | 212 std::list<std::string> page_ids; |
| 120 while (base::Time::Now() < deadline) { | 213 while (base::Time::Now() < deadline) { |
| 121 FetchPagesInfo(context_getter_, port_, &page_ids); | 214 FetchPagesInfo(context_getter_, port_, |
| 215 base::Bind(internal::ParsePagesInfo, &page_ids)); | |
| 122 if (page_ids.empty()) | 216 if (page_ids.empty()) |
| 123 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); | 217 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
| 124 else | 218 else |
| 125 break; | 219 break; |
| 126 } | 220 } |
| 127 if (page_ids.empty()) | 221 if (page_ids.empty()) |
| 128 return Status(kUnknownError, "unable to discover open pages"); | 222 return Status(kUnknownError, "unable to discover open pages"); |
| 129 | 223 |
| 130 return Status(kOk); | 224 return Status(kOk); |
| 131 } | 225 } |
| 132 | 226 |
| 133 int ChromeImpl::GetPort() const { | 227 int ChromeImpl::GetPort() const { |
| 134 return port_; | 228 return port_; |
| 135 } | 229 } |
| 136 | 230 |
| 137 namespace internal { | 231 namespace internal { |
| 138 | 232 |
| 139 Status ParsePagesInfo(const std::string& data, | 233 Status ParsePagesInfo(std::list<std::string>* page_ids, |
| 140 std::list<std::string>* page_ids) { | 234 base::ListValue* page_info_list) { |
| 141 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); | |
| 142 if (!value.get()) | |
| 143 return Status(kUnknownError, "DevTools returned invalid JSON"); | |
| 144 base::ListValue* list; | |
| 145 if (!value->GetAsList(&list)) | |
| 146 return Status(kUnknownError, "DevTools did not return list"); | |
| 147 | |
| 148 std::list<std::string> ids; | 235 std::list<std::string> ids; |
| 149 for (size_t i = 0; i < list->GetSize(); ++i) { | 236 for (size_t i = 0; i < page_info_list->GetSize(); ++i) { |
| 150 base::DictionaryValue* info; | 237 base::DictionaryValue* info; |
| 151 if (!list->GetDictionary(i, &info)) | 238 if (!page_info_list->GetDictionary(i, &info)) |
| 152 return Status(kUnknownError, "DevTools contains non-dictionary item"); | 239 return Status(kUnknownError, "DevTools contains non-dictionary item"); |
| 153 std::string type; | 240 std::string type; |
| 154 if (!info->GetString("type", &type)) | 241 if (!info->GetString("type", &type)) |
| 155 return Status(kUnknownError, "DevTools did not include type"); | 242 return Status(kUnknownError, "DevTools did not include type"); |
| 156 if (type != "page") | 243 if (type != "page") |
| 157 continue; | 244 continue; |
| 158 std::string id; | 245 std::string id; |
| 159 if (!info->GetString("id", &id)) | 246 if (!info->GetString("id", &id)) |
| 160 return Status(kUnknownError, "DevTools did not include id"); | 247 return Status(kUnknownError, "DevTools did not include id"); |
| 161 ids.push_back(id); | 248 ids.push_back(id); |
| 162 } | 249 } |
| 163 page_ids->swap(ids); | 250 page_ids->swap(ids); |
| 164 return Status(kOk); | 251 return Status(kOk); |
| 165 } | 252 } |
| 166 | 253 |
| 254 Status ParseDevToolsInfo(std::list<std::string>* page_devtools_ids, | |
| 255 std::list<std::string>* frontend_devtools_ids, | |
| 256 base::ListValue* page_info_list) { | |
| 257 std::list<std::string> page_devtools_ids_tmp; | |
| 258 std::list<std::string> frontend_devtools_ids_tmp; | |
| 259 for (size_t i = 0; i < page_info_list->GetSize(); ++i) { | |
| 260 base::DictionaryValue* info; | |
| 261 if (!page_info_list->GetDictionary(i, &info)) | |
| 262 return Status(kUnknownError, "DevTools contains non-dictionary item"); | |
| 263 std::string url; | |
| 264 if (!info->GetString("url", &url)) | |
| 265 return Status(kUnknownError, "DevTools did not include url"); | |
| 266 if (url.find("chrome-devtools://") != 0u) | |
| 267 continue; | |
| 268 std::string id; | |
| 269 if (!info->GetString("id", &id)) | |
| 270 return Status(kUnknownError, "DevTools did not include id"); | |
| 271 std::string type; | |
| 272 if (!info->GetString("type", &type)) | |
| 273 return Status(kUnknownError, "DevTools did not include type"); | |
| 274 if (type == "page") | |
| 275 page_devtools_ids_tmp.push_back(id); | |
| 276 else if (type == "other") | |
| 277 frontend_devtools_ids_tmp.push_back(id); | |
| 278 else | |
| 279 return Status(kUnknownError, "unknown type of devtools:" + type); | |
| 280 } | |
| 281 | |
| 282 page_devtools_ids->swap(page_devtools_ids_tmp); | |
| 283 frontend_devtools_ids->swap(frontend_devtools_ids_tmp); | |
| 284 return Status(kOk); | |
| 285 } | |
| 286 | |
| 287 Status CheckPageDebuggerUrl(const std::string& page_id, | |
| 288 bool* has_debugger_url, | |
| 289 base::ListValue* page_info_list) { | |
| 290 std::list<std::string> ids; | |
| 291 for (size_t i = 0; i < page_info_list->GetSize(); ++i) { | |
| 292 base::DictionaryValue* info; | |
| 293 if (!page_info_list->GetDictionary(i, &info)) | |
| 294 return Status(kUnknownError, "DevTools contains non-dictionary item"); | |
| 295 std::string id; | |
| 296 if (!info->GetString("id", &id)) | |
| 297 return Status(kUnknownError, "DevTools did not include id"); | |
| 298 if (id == page_id) { | |
| 299 std::string debugger_url; | |
| 300 *has_debugger_url = info->GetString("webSocketDebuggerUrl", &debugger_url) | |
| 301 && !debugger_url.empty(); | |
| 302 return Status(kOk); | |
| 303 } | |
| 304 } | |
| 305 return Status(kUnknownError, "page closed while closing devtools frontend"); | |
| 306 } | |
| 307 | |
| 167 } // namespace internal | 308 } // namespace internal |
| OLD | NEW |