| 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/string_split.h" | 12 #include "base/string_split.h" |
| 13 #include "base/stringprintf.h" | 13 #include "base/stringprintf.h" |
| 14 #include "base/strings/string_number_conversions.h" | 14 #include "base/strings/string_number_conversions.h" |
| 15 #include "base/threading/platform_thread.h" | 15 #include "base/threading/platform_thread.h" |
| 16 #include "base/time.h" | 16 #include "base/time.h" |
| 17 #include "base/values.h" | 17 #include "base/values.h" |
| 18 #include "chrome/test/chromedriver/devtools_client_impl.h" | 18 #include "chrome/test/chromedriver/devtools_client_impl.h" |
| 19 #include "chrome/test/chromedriver/javascript_dialog_manager.h" | 19 #include "chrome/test/chromedriver/javascript_dialog_manager.h" |
| 20 #include "chrome/test/chromedriver/net/net_util.h" | 20 #include "chrome/test/chromedriver/net/net_util.h" |
| 21 #include "chrome/test/chromedriver/net/sync_websocket_impl.h" | 21 #include "chrome/test/chromedriver/net/sync_websocket_impl.h" |
| 22 #include "chrome/test/chromedriver/net/url_request_context_getter.h" | 22 #include "chrome/test/chromedriver/net/url_request_context_getter.h" |
| 23 #include "chrome/test/chromedriver/status.h" | 23 #include "chrome/test/chromedriver/status.h" |
| 24 #include "chrome/test/chromedriver/version.h" | 24 #include "chrome/test/chromedriver/version.h" |
| 25 #include "chrome/test/chromedriver/web_view_impl.h" | 25 #include "chrome/test/chromedriver/web_view_impl.h" |
| 26 #include "googleurl/src/gurl.h" | 26 #include "googleurl/src/gurl.h" |
| 27 | 27 |
| 28 namespace { | 28 namespace { |
| 29 | 29 |
| 30 typedef std::list<internal::WebViewInfo> WebViewInfoList; |
| 31 |
| 30 Status FetchVersionInfo(URLRequestContextGetter* context_getter, | 32 Status FetchVersionInfo(URLRequestContextGetter* context_getter, |
| 31 int port, | 33 int port, |
| 32 std::string* version) { | 34 std::string* version) { |
| 33 std::string url = base::StringPrintf( | 35 std::string url = base::StringPrintf( |
| 34 "http://127.0.0.1:%d/json/version", port); | 36 "http://127.0.0.1:%d/json/version", port); |
| 35 std::string data; | 37 std::string data; |
| 36 if (!FetchUrl(GURL(url), context_getter, &data)) | 38 if (!FetchUrl(GURL(url), context_getter, &data)) |
| 37 return Status(kChromeNotReachable); | 39 return Status(kChromeNotReachable); |
| 38 return internal::ParseVersionInfo(data, version); | 40 return internal::ParseVersionInfo(data, version); |
| 39 } | 41 } |
| 40 | 42 |
| 41 Status FetchPagesInfo(URLRequestContextGetter* context_getter, | 43 Status FetchWebViewsInfo(URLRequestContextGetter* context_getter, |
| 42 int port, | 44 int port, |
| 43 std::list<std::string>* page_ids) { | 45 WebViewInfoList* info_list) { |
| 44 std::string url = base::StringPrintf("http://127.0.0.1:%d/json", port); | 46 std::string url = base::StringPrintf("http://127.0.0.1:%d/json", port); |
| 45 std::string data; | 47 std::string data; |
| 46 if (!FetchUrl(GURL(url), context_getter, &data)) | 48 if (!FetchUrl(GURL(url), context_getter, &data)) |
| 47 return Status(kChromeNotReachable); | 49 return Status(kChromeNotReachable); |
| 48 return internal::ParsePagesInfo(data, page_ids); | 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; |
| 49 } | 63 } |
| 50 | 64 |
| 51 Status CloseWebView(URLRequestContextGetter* context_getter, | 65 Status CloseWebView(URLRequestContextGetter* context_getter, |
| 52 int port, | 66 int port, |
| 53 const std::string& web_view_id) { | 67 const std::string& web_view_id) { |
| 54 std::list<std::string> ids; | 68 WebViewInfoList info_list; |
| 55 Status status = FetchPagesInfo(context_getter, port, &ids); | 69 Status status = FetchWebViewsInfo(context_getter, port, &info_list); |
| 56 if (status.IsError()) | 70 if (status.IsError()) |
| 57 return status; | 71 return status; |
| 58 if (std::find(ids.begin(), ids.end(), web_view_id) == ids.end()) | 72 if (!GetWebViewFromList(web_view_id, info_list)) |
| 59 return Status(kOk); | 73 return Status(kOk); |
| 60 | 74 |
| 61 bool is_last_web_view = ids.size() == 1; | 75 bool is_last_web_view = info_list.size() == 1u; |
| 62 | 76 |
| 63 std::string url = base::StringPrintf( | 77 std::string url = base::StringPrintf( |
| 64 "http://127.0.0.1:%d/json/close/%s", port, web_view_id.c_str()); | 78 "http://127.0.0.1:%d/json/close/%s", port, web_view_id.c_str()); |
| 65 std::string data; | 79 std::string data; |
| 66 if (!FetchUrl(GURL(url), context_getter, &data)) | 80 if (!FetchUrl(GURL(url), context_getter, &data)) |
| 67 return is_last_web_view ? Status(kOk) : Status(kChromeNotReachable); | 81 return is_last_web_view ? Status(kOk) : Status(kChromeNotReachable); |
| 68 if (data != "Target is closing") | 82 if (data != "Target is closing") |
| 69 return Status(kOk); | 83 return Status(kOk); |
| 70 | 84 |
| 71 // Wait for the target window to be completely closed. | 85 // Wait for the target window to be completely closed. |
| 72 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); | 86 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); |
| 73 while (base::Time::Now() < deadline) { | 87 while (base::Time::Now() < deadline) { |
| 74 ids.clear(); | 88 info_list.clear(); |
| 75 status = FetchPagesInfo(context_getter, port, &ids); | 89 status = FetchWebViewsInfo(context_getter, port, &info_list); |
| 76 if (is_last_web_view && status.code() == kChromeNotReachable) | 90 if (is_last_web_view && status.code() == kChromeNotReachable) |
| 77 return Status(kOk); // Closing the last web view leads chrome to quit. | 91 return Status(kOk); // Closing the last web view leads chrome to quit. |
| 78 if (status.IsError()) | 92 if (status.IsError()) |
| 79 return status; | 93 return status; |
| 80 if (std::find(ids.begin(), ids.end(), web_view_id) == ids.end()) | 94 if (!GetWebViewFromList(web_view_id, info_list)) |
| 81 return Status(kOk); | 95 return Status(kOk); |
| 82 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); | 96 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); |
| 83 } | 97 } |
| 84 | 98 |
| 85 return Status(kUnknownError, "failed to close window in 20 seconds"); | 99 return Status(kUnknownError, "failed to close window in 20 seconds"); |
| 86 } | 100 } |
| 87 | 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 |
| 88 } // namespace | 197 } // namespace |
| 89 | 198 |
| 90 ChromeImpl::ChromeImpl(URLRequestContextGetter* context_getter, | 199 ChromeImpl::ChromeImpl(URLRequestContextGetter* context_getter, |
| 91 int port, | 200 int port, |
| 92 const SyncWebSocketFactory& socket_factory) | 201 const SyncWebSocketFactory& socket_factory) |
| 93 : context_getter_(context_getter), | 202 : context_getter_(context_getter), |
| 94 port_(port), | 203 port_(port), |
| 95 socket_factory_(socket_factory), | 204 socket_factory_(socket_factory), |
| 96 version_("unknown version"), | 205 version_("unknown version"), |
| 97 build_no_(0) {} | 206 build_no_(0) {} |
| 98 | 207 |
| 99 ChromeImpl::~ChromeImpl() { | 208 ChromeImpl::~ChromeImpl() { |
| 100 web_view_map_.clear(); | 209 web_view_map_.clear(); |
| 101 } | 210 } |
| 102 | 211 |
| 103 std::string ChromeImpl::GetVersion() { | 212 std::string ChromeImpl::GetVersion() { |
| 104 return version_; | 213 return version_; |
| 105 } | 214 } |
| 106 | 215 |
| 107 Status ChromeImpl::GetWebViews(std::list<WebView*>* web_views) { | 216 Status ChromeImpl::GetWebViews(std::list<WebView*>* web_views) { |
| 108 std::list<std::string> ids; | 217 WebViewInfoList info_list; |
| 109 Status status = FetchPagesInfo(context_getter_, port_, &ids); | 218 Status status = FetchWebViewsInfo( |
| 219 context_getter_, port_, &info_list); |
| 110 if (status.IsError()) | 220 if (status.IsError()) |
| 111 return status; | 221 return status; |
| 112 | 222 |
| 113 std::list<WebView*> internal_web_views; | 223 std::list<WebView*> internal_web_views; |
| 114 for (std::list<std::string>::const_iterator it = ids.begin(); | 224 for (WebViewInfoList::const_iterator it = info_list.begin(); |
| 115 it != ids.end(); ++it) { | 225 it != info_list.end(); ++it) { |
| 116 WebViewMap::const_iterator found = web_view_map_.find(*it); | 226 WebViewMap::const_iterator found = web_view_map_.find(it->id); |
| 117 if (found != web_view_map_.end()) { | 227 if (found != web_view_map_.end()) { |
| 118 internal_web_views.push_back(found->second.get()); | 228 internal_web_views.push_back(found->second.get()); |
| 119 continue; | 229 continue; |
| 120 } | 230 } |
| 121 | 231 |
| 122 std::string ws_url = base::StringPrintf( | 232 std::string ws_url = base::StringPrintf( |
| 123 "ws://127.0.0.1:%d/devtools/page/%s", port_, it->c_str()); | 233 "ws://127.0.0.1:%d/devtools/page/%s", port_, it->id.c_str()); |
| 124 web_view_map_[*it] = make_linked_ptr(new WebViewImpl( | 234 DevToolsClientImpl::FrontendCloserFunc frontend_closer_func = base::Bind( |
| 125 *it, new DevToolsClientImpl(socket_factory_, ws_url), | 235 &CloseDevToolsFrontend, this, socket_factory_, |
| 126 this, base::Bind(&CloseWebView, context_getter_, port_, *it))); | 236 context_getter_, port_, it->id); |
| 127 internal_web_views.push_back(web_view_map_[*it].get()); | 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()); |
| 128 } | 243 } |
| 129 | 244 |
| 130 web_views->swap(internal_web_views); | 245 web_views->swap(internal_web_views); |
| 131 return Status(kOk); | 246 return Status(kOk); |
| 132 } | 247 } |
| 133 | 248 |
| 134 Status ChromeImpl::IsJavaScriptDialogOpen(bool* is_open) { | 249 Status ChromeImpl::IsJavaScriptDialogOpen(bool* is_open) { |
| 135 JavaScriptDialogManager* manager; | 250 JavaScriptDialogManager* manager; |
| 136 Status status = GetDialogManagerForOpenDialog(&manager); | 251 Status status = GetDialogManagerForOpenDialog(&manager); |
| 137 if (status.IsError()) | 252 if (status.IsError()) |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 177 break; | 292 break; |
| 178 if (status.code() != kChromeNotReachable) | 293 if (status.code() != kChromeNotReachable) |
| 179 return status; | 294 return status; |
| 180 } | 295 } |
| 181 if (status.IsError()) | 296 if (status.IsError()) |
| 182 return status; | 297 return status; |
| 183 status = ParseAndCheckVersion(version); | 298 status = ParseAndCheckVersion(version); |
| 184 if (status.IsError()) | 299 if (status.IsError()) |
| 185 return status; | 300 return status; |
| 186 | 301 |
| 187 std::list<std::string> page_ids; | 302 WebViewInfoList info_list; |
| 188 while (base::Time::Now() < deadline) { | 303 while (base::Time::Now() < deadline) { |
| 189 FetchPagesInfo(context_getter_, port_, &page_ids); | 304 FetchWebViewsInfo(context_getter_, port_, &info_list); |
| 190 if (page_ids.empty()) | 305 if (info_list.empty()) |
| 191 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); | 306 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
| 192 else | 307 else |
| 193 break; | 308 return Status(kOk); |
| 194 } | 309 } |
| 195 if (page_ids.empty()) | 310 return Status(kUnknownError, "unable to discover open pages"); |
| 196 return Status(kUnknownError, "unable to discover open pages"); | |
| 197 | |
| 198 return Status(kOk); | |
| 199 } | 311 } |
| 200 | 312 |
| 201 int ChromeImpl::GetPort() const { | 313 int ChromeImpl::GetPort() const { |
| 202 return port_; | 314 return port_; |
| 203 } | 315 } |
| 204 | 316 |
| 205 Status ChromeImpl::GetDialogManagerForOpenDialog( | 317 Status ChromeImpl::GetDialogManagerForOpenDialog( |
| 206 JavaScriptDialogManager** manager) { | 318 JavaScriptDialogManager** manager) { |
| 207 std::list<WebView*> web_views; | 319 std::list<WebView*> web_views; |
| 208 Status status = GetWebViews(&web_views); | 320 Status status = GetWebViews(&web_views); |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 245 return Status(kUnknownError, "Chrome version must be >= " + | 357 return Status(kUnknownError, "Chrome version must be >= " + |
| 246 GetMinimumSupportedChromeVersion()); | 358 GetMinimumSupportedChromeVersion()); |
| 247 } | 359 } |
| 248 version_ = stripped_version; | 360 version_ = stripped_version; |
| 249 build_no_ = build_no; | 361 build_no_ = build_no; |
| 250 return Status(kOk); | 362 return Status(kOk); |
| 251 } | 363 } |
| 252 | 364 |
| 253 namespace internal { | 365 namespace internal { |
| 254 | 366 |
| 367 WebViewInfo::WebViewInfo(const std::string& id, |
| 368 const std::string& debugger_url, |
| 369 const std::string& url, |
| 370 Type type) |
| 371 : id(id), debugger_url(debugger_url), url(url), type(type) {} |
| 372 |
| 373 bool WebViewInfo::IsFrontend() const { |
| 374 return url.find("chrome-devtools://") == 0u; |
| 375 } |
| 376 |
| 255 Status ParsePagesInfo(const std::string& data, | 377 Status ParsePagesInfo(const std::string& data, |
| 256 std::list<std::string>* page_ids) { | 378 std::list<WebViewInfo>* info_list) { |
| 257 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); | 379 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); |
| 258 if (!value.get()) | 380 if (!value.get()) |
| 259 return Status(kUnknownError, "DevTools returned invalid JSON"); | 381 return Status(kUnknownError, "DevTools returned invalid JSON"); |
| 260 base::ListValue* list; | 382 base::ListValue* list; |
| 261 if (!value->GetAsList(&list)) | 383 if (!value->GetAsList(&list)) |
| 262 return Status(kUnknownError, "DevTools did not return list"); | 384 return Status(kUnknownError, "DevTools did not return list"); |
| 263 | 385 |
| 264 std::list<std::string> ids; | 386 std::list<WebViewInfo> info_list_tmp; |
| 265 for (size_t i = 0; i < list->GetSize(); ++i) { | 387 for (size_t i = 0; i < list->GetSize(); ++i) { |
| 266 base::DictionaryValue* info; | 388 base::DictionaryValue* info; |
| 267 if (!list->GetDictionary(i, &info)) | 389 if (!list->GetDictionary(i, &info)) |
| 268 return Status(kUnknownError, "DevTools contains non-dictionary item"); | 390 return Status(kUnknownError, "DevTools contains non-dictionary item"); |
| 391 std::string id; |
| 392 if (!info->GetString("id", &id)) |
| 393 return Status(kUnknownError, "DevTools did not include id"); |
| 269 std::string type; | 394 std::string type; |
| 270 if (!info->GetString("type", &type)) | 395 if (!info->GetString("type", &type)) |
| 271 return Status(kUnknownError, "DevTools did not include type"); | 396 return Status(kUnknownError, "DevTools did not include type"); |
| 272 if (type != "page") | 397 std::string url; |
| 273 continue; | 398 if (!info->GetString("url", &url)) |
| 274 std::string id; | 399 return Status(kUnknownError, "DevTools did not include url"); |
| 275 if (!info->GetString("id", &id)) | 400 std::string debugger_url; |
| 276 return Status(kUnknownError, "DevTools did not include id"); | 401 info->GetString("webSocketDebuggerUrl", &debugger_url); |
| 277 ids.push_back(id); | 402 if (type == "page") |
| 403 info_list_tmp.push_back( |
| 404 WebViewInfo(id, debugger_url, url, internal::WebViewInfo::kPage)); |
| 405 else if (type == "other") |
| 406 info_list_tmp.push_back( |
| 407 WebViewInfo(id, debugger_url, url, internal::WebViewInfo::kOther)); |
| 408 else |
| 409 return Status(kUnknownError, "DevTools returned unknown type:" + type); |
| 278 } | 410 } |
| 279 page_ids->swap(ids); | 411 info_list->swap(info_list_tmp); |
| 280 return Status(kOk); | 412 return Status(kOk); |
| 281 } | 413 } |
| 282 | 414 |
| 283 Status ParseVersionInfo(const std::string& data, | 415 Status ParseVersionInfo(const std::string& data, |
| 284 std::string* version) { | 416 std::string* version) { |
| 285 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); | 417 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); |
| 286 if (!value.get()) | 418 if (!value.get()) |
| 287 return Status(kUnknownError, "version info not in JSON"); | 419 return Status(kUnknownError, "version info not in JSON"); |
| 288 base::DictionaryValue* dict; | 420 base::DictionaryValue* dict; |
| 289 if (!value->GetAsDictionary(&dict)) | 421 if (!value->GetAsDictionary(&dict)) |
| 290 return Status(kUnknownError, "version info not a dictionary"); | 422 return Status(kUnknownError, "version info not a dictionary"); |
| 291 if (!dict->GetString("Browser", version)) { | 423 if (!dict->GetString("Browser", version)) { |
| 292 return Status( | 424 return Status( |
| 293 kUnknownError, "Chrome version must be >= 26", | 425 kUnknownError, "Chrome version must be >= 26", |
| 294 Status(kUnknownError, "version info doesn't include string 'Browser'")); | 426 Status(kUnknownError, "version info doesn't include string 'Browser'")); |
| 295 } | 427 } |
| 296 return Status(kOk); | 428 return Status(kOk); |
| 297 } | 429 } |
| 298 | 430 |
| 299 } // namespace internal | 431 } // namespace internal |
| OLD | NEW |