Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(953)

Side by Side Diff: chrome/test/chromedriver/chrome_impl.cc

Issue 12321057: [chromedriver] Implement reconnection to DevTools. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix some nits and add one unit test. Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698