| 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 "base/json/json_reader.h" | 7 #include "base/json/json_reader.h" |
| 8 #include "base/json/json_writer.h" | 8 #include "base/json/json_writer.h" |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/process_util.h" | 10 #include "base/process_util.h" |
| 11 #include "base/stringprintf.h" | 11 #include "base/stringprintf.h" |
| 12 #include "base/threading/platform_thread.h" | 12 #include "base/threading/platform_thread.h" |
| 13 #include "base/time.h" | 13 #include "base/time.h" |
| 14 #include "base/values.h" | 14 #include "base/values.h" |
| 15 #include "chrome/test/chromedriver/devtools_client_impl.h" | 15 #include "chrome/test/chromedriver/devtools_client_impl.h" |
| 16 #include "chrome/test/chromedriver/dom_tracker.h" |
| 16 #include "chrome/test/chromedriver/js.h" | 17 #include "chrome/test/chromedriver/js.h" |
| 17 #include "chrome/test/chromedriver/net/net_util.h" | 18 #include "chrome/test/chromedriver/net/net_util.h" |
| 18 #include "chrome/test/chromedriver/net/sync_websocket_impl.h" | 19 #include "chrome/test/chromedriver/net/sync_websocket_impl.h" |
| 19 #include "chrome/test/chromedriver/net/url_request_context_getter.h" | 20 #include "chrome/test/chromedriver/net/url_request_context_getter.h" |
| 20 #include "chrome/test/chromedriver/status.h" | 21 #include "chrome/test/chromedriver/status.h" |
| 21 #include "googleurl/src/gurl.h" | 22 #include "googleurl/src/gurl.h" |
| 22 | 23 |
| 23 namespace { | 24 namespace { |
| 24 | 25 |
| 25 Status FetchPagesInfo(URLRequestContextGetter* context_getter, | 26 Status FetchPagesInfo(URLRequestContextGetter* context_getter, |
| 26 int port, | 27 int port, |
| 27 std::list<std::string>* debugger_urls) { | 28 std::list<std::string>* debugger_urls) { |
| 28 std::string url = base::StringPrintf( | 29 std::string url = base::StringPrintf( |
| 29 "http://127.0.0.1:%d/json", port); | 30 "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, debugger_urls); | 34 return internal::ParsePagesInfo(data, debugger_urls); |
| 34 } | 35 } |
| 35 | 36 |
| 37 Status GetContextIdForFrame(DomTracker* tracker, |
| 38 const std::string& frame, |
| 39 int* context_id) { |
| 40 if (frame.empty()) { |
| 41 *context_id = 0; |
| 42 return Status(kOk); |
| 43 } |
| 44 Status status = tracker->GetContextIdForFrame(frame, context_id); |
| 45 if (status.IsError()) |
| 46 return status; |
| 47 return Status(kOk); |
| 48 } |
| 49 |
| 36 } // namespace | 50 } // namespace |
| 37 | 51 |
| 38 ChromeImpl::ChromeImpl(base::ProcessHandle process, | 52 ChromeImpl::ChromeImpl(base::ProcessHandle process, |
| 39 URLRequestContextGetter* context_getter, | 53 URLRequestContextGetter* context_getter, |
| 40 base::ScopedTempDir* user_data_dir, | 54 base::ScopedTempDir* user_data_dir, |
| 41 int port, | 55 int port, |
| 42 const SyncWebSocketFactory& socket_factory) | 56 const SyncWebSocketFactory& socket_factory) |
| 43 : process_(process), | 57 : process_(process), |
| 44 context_getter_(context_getter), | 58 context_getter_(context_getter), |
| 45 port_(port), | 59 port_(port), |
| 46 socket_factory_(socket_factory) { | 60 socket_factory_(socket_factory), |
| 61 dom_tracker_(new DomTracker()) { |
| 47 if (user_data_dir->IsValid()) { | 62 if (user_data_dir->IsValid()) { |
| 48 CHECK(user_data_dir_.Set(user_data_dir->Take())); | 63 CHECK(user_data_dir_.Set(user_data_dir->Take())); |
| 49 } | 64 } |
| 50 } | 65 } |
| 51 | 66 |
| 52 ChromeImpl::~ChromeImpl() { | 67 ChromeImpl::~ChromeImpl() { |
| 53 base::CloseProcessHandle(process_); | 68 base::CloseProcessHandle(process_); |
| 54 } | 69 } |
| 55 | 70 |
| 56 Status ChromeImpl::Init() { | 71 Status ChromeImpl::Init() { |
| 57 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); | 72 base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); |
| 58 std::list<std::string> debugger_urls; | 73 std::list<std::string> debugger_urls; |
| 59 while (base::Time::Now() < deadline) { | 74 while (base::Time::Now() < deadline) { |
| 60 FetchPagesInfo(context_getter_, port_, &debugger_urls); | 75 FetchPagesInfo(context_getter_, port_, &debugger_urls); |
| 61 if (debugger_urls.empty()) | 76 if (debugger_urls.empty()) |
| 62 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); | 77 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
| 63 else | 78 else |
| 64 break; | 79 break; |
| 65 } | 80 } |
| 66 if (debugger_urls.empty()) | 81 if (debugger_urls.empty()) |
| 67 return Status(kUnknownError, "unable to discover open pages"); | 82 return Status(kUnknownError, "unable to discover open pages"); |
| 68 client_.reset(new DevToolsClientImpl( | 83 client_.reset(new DevToolsClientImpl( |
| 69 socket_factory_, debugger_urls.front(), NULL)); | 84 socket_factory_, debugger_urls.front(), dom_tracker_.get())); |
| 70 return Status(kOk); | 85 |
| 86 // Perform necessary configuration of the DevTools client. |
| 87 // Fetch the root document node so that Inspector will push DOM node |
| 88 // information to the client. |
| 89 base::DictionaryValue params; |
| 90 Status status = client_->SendCommand("DOM.getDocument", params); |
| 91 if (status.IsError()) |
| 92 return status; |
| 93 // Enable runtime events to allow tracking execution context creation. |
| 94 return client_->SendCommand("Runtime.enable", params); |
| 71 } | 95 } |
| 72 | 96 |
| 73 Status ChromeImpl::Load(const std::string& url) { | 97 Status ChromeImpl::Load(const std::string& url) { |
| 74 base::DictionaryValue params; | 98 base::DictionaryValue params; |
| 75 params.SetString("url", url); | 99 params.SetString("url", url); |
| 76 return client_->SendCommand("Page.navigate", params); | 100 return client_->SendCommand("Page.navigate", params); |
| 77 } | 101 } |
| 78 | 102 |
| 79 Status ChromeImpl::EvaluateScript(const std::string& expression, | 103 Status ChromeImpl::EvaluateScript(const std::string& frame, |
| 104 const std::string& expression, |
| 80 scoped_ptr<base::Value>* result) { | 105 scoped_ptr<base::Value>* result) { |
| 81 return internal::EvaluateScript(client_.get(), expression, result); | 106 int context_id; |
| 107 Status status = GetContextIdForFrame(dom_tracker_.get(), frame, &context_id); |
| 108 if (status.IsError()) |
| 109 return status; |
| 110 return internal::EvaluateScriptAndGetValue( |
| 111 client_.get(), context_id, expression, result); |
| 82 } | 112 } |
| 83 | 113 |
| 84 Status ChromeImpl::CallFunction(const std::string& function, | 114 Status ChromeImpl::CallFunction(const std::string& frame, |
| 115 const std::string& function, |
| 85 const base::ListValue& args, | 116 const base::ListValue& args, |
| 86 scoped_ptr<base::Value>* result) { | 117 scoped_ptr<base::Value>* result) { |
| 87 std::string json; | 118 std::string json; |
| 88 base::JSONWriter::Write(&args, &json); | 119 base::JSONWriter::Write(&args, &json); |
| 89 std::string expression = base::StringPrintf( | 120 std::string expression = base::StringPrintf( |
| 90 "(%s).apply(null, [%s, %s])", | 121 "(%s).apply(null, [%s, %s])", |
| 91 kCallFunctionScript, | 122 kCallFunctionScript, |
| 92 function.c_str(), | 123 function.c_str(), |
| 93 json.c_str()); | 124 json.c_str()); |
| 94 return EvaluateScript(expression, result); | 125 return EvaluateScript(frame, expression, result); |
| 126 } |
| 127 |
| 128 Status ChromeImpl::GetFrameByFunction(const std::string& frame, |
| 129 const std::string& function, |
| 130 const base::ListValue& args, |
| 131 std::string* out_frame) { |
| 132 int context_id; |
| 133 Status status = GetContextIdForFrame(dom_tracker_.get(), frame, &context_id); |
| 134 if (status.IsError()) |
| 135 return status; |
| 136 int node_id; |
| 137 status = internal::GetNodeIdFromFunction( |
| 138 client_.get(), context_id, function, args, &node_id); |
| 139 if (status.IsError()) |
| 140 return status; |
| 141 return dom_tracker_->GetFrameIdForNode(node_id, out_frame); |
| 95 } | 142 } |
| 96 | 143 |
| 97 Status ChromeImpl::Quit() { | 144 Status ChromeImpl::Quit() { |
| 98 if (!base::KillProcess(process_, 0, true)) | 145 if (!base::KillProcess(process_, 0, true)) |
| 99 return Status(kUnknownError, "cannot kill Chrome"); | 146 return Status(kUnknownError, "cannot kill Chrome"); |
| 100 return Status(kOk); | 147 return Status(kOk); |
| 101 } | 148 } |
| 102 | 149 |
| 103 namespace internal { | 150 namespace internal { |
| 104 | 151 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 119 std::string debugger_url; | 166 std::string debugger_url; |
| 120 if (!info->GetString("webSocketDebuggerUrl", &debugger_url)) | 167 if (!info->GetString("webSocketDebuggerUrl", &debugger_url)) |
| 121 return Status(kUnknownError, "DevTools did not include debugger URL"); | 168 return Status(kUnknownError, "DevTools did not include debugger URL"); |
| 122 internal_urls.push_back(debugger_url); | 169 internal_urls.push_back(debugger_url); |
| 123 } | 170 } |
| 124 debugger_urls->swap(internal_urls); | 171 debugger_urls->swap(internal_urls); |
| 125 return Status(kOk); | 172 return Status(kOk); |
| 126 } | 173 } |
| 127 | 174 |
| 128 Status EvaluateScript(DevToolsClient* client, | 175 Status EvaluateScript(DevToolsClient* client, |
| 176 int context_id, |
| 129 const std::string& expression, | 177 const std::string& expression, |
| 130 scoped_ptr<base::Value>* result) { | 178 EvaluateScriptReturnType return_type, |
| 179 scoped_ptr<base::DictionaryValue>* result) { |
| 131 base::DictionaryValue params; | 180 base::DictionaryValue params; |
| 132 params.SetString("expression", expression); | 181 params.SetString("expression", expression); |
| 133 params.SetBoolean("returnByValue", true); | 182 if (context_id) |
| 183 params.SetInteger("contextId", context_id); |
| 184 params.SetBoolean("returnByValue", return_type == ReturnByValue); |
| 134 scoped_ptr<base::DictionaryValue> cmd_result; | 185 scoped_ptr<base::DictionaryValue> cmd_result; |
| 135 Status status = client->SendCommandAndGetResult( | 186 Status status = client->SendCommandAndGetResult( |
| 136 "Runtime.evaluate", params, &cmd_result); | 187 "Runtime.evaluate", params, &cmd_result); |
| 137 if (status.IsError()) | 188 if (status.IsError()) |
| 138 return status; | 189 return status; |
| 139 | 190 |
| 140 bool was_thrown; | 191 bool was_thrown; |
| 141 if (!cmd_result->GetBoolean("wasThrown", &was_thrown)) | 192 if (!cmd_result->GetBoolean("wasThrown", &was_thrown)) |
| 142 return Status(kUnknownError, "Runtime.evaluate missing 'wasThrown'"); | 193 return Status(kUnknownError, "Runtime.evaluate missing 'wasThrown'"); |
| 143 if (was_thrown) { | 194 if (was_thrown) { |
| 144 std::string description = "unknown"; | 195 std::string description = "unknown"; |
| 145 cmd_result->GetString("result.description", &description); | 196 cmd_result->GetString("result.description", &description); |
| 146 return Status(kUnknownError, | 197 return Status(kUnknownError, |
| 147 "Runtime.evaluate threw exception: " + description); | 198 "Runtime.evaluate threw exception: " + description); |
| 148 } | 199 } |
| 149 | 200 |
| 201 base::DictionaryValue* unscoped_result; |
| 202 if (!cmd_result->GetDictionary("result", &unscoped_result)) |
| 203 return Status(kUnknownError, "evaluate missing dictionary 'result'"); |
| 204 result->reset(unscoped_result->DeepCopy()); |
| 205 return Status(kOk); |
| 206 } |
| 207 |
| 208 Status EvaluateScriptAndGetObject(DevToolsClient* client, |
| 209 int context_id, |
| 210 const std::string& expression, |
| 211 std::string* object_id) { |
| 212 scoped_ptr<base::DictionaryValue> result; |
| 213 Status status = EvaluateScript(client, context_id, expression, ReturnByObject, |
| 214 &result); |
| 215 if (status.IsError()) |
| 216 return status; |
| 217 if (!result->GetString("objectId", object_id)) |
| 218 return Status(kUnknownError, "evaluate missing string 'objectId'"); |
| 219 return Status(kOk); |
| 220 } |
| 221 |
| 222 Status EvaluateScriptAndGetValue(DevToolsClient* client, |
| 223 int context_id, |
| 224 const std::string& expression, |
| 225 scoped_ptr<base::Value>* result) { |
| 226 scoped_ptr<base::DictionaryValue> temp_result; |
| 227 Status status = EvaluateScript(client, context_id, expression, ReturnByValue, |
| 228 &temp_result); |
| 229 if (status.IsError()) |
| 230 return status; |
| 231 |
| 150 std::string type; | 232 std::string type; |
| 151 if (!cmd_result->GetString("result.type", &type)) | 233 if (!temp_result->GetString("type", &type)) |
| 152 return Status(kUnknownError, "Runtime.evaluate missing result.type"); | 234 return Status(kUnknownError, "Runtime.evaluate missing string 'type'"); |
| 153 | 235 |
| 154 if (type == "undefined") { | 236 if (type == "undefined") { |
| 155 result->reset(base::Value::CreateNullValue()); | 237 result->reset(base::Value::CreateNullValue()); |
| 156 } else { | 238 } else { |
| 157 int status_code; | 239 int status_code; |
| 158 if (!cmd_result->GetInteger("result.value.status", &status_code)) { | 240 if (!temp_result->GetInteger("value.status", &status_code)) { |
| 159 return Status(kUnknownError, | 241 return Status(kUnknownError, |
| 160 "Runtime.evaluate missing result.value.status"); | 242 "Runtime.evaluate missing int 'value.status'"); |
| 161 } | 243 } |
| 162 if (status_code != kOk) | 244 if (status_code != kOk) |
| 163 return Status(static_cast<StatusCode>(status_code)); | 245 return Status(static_cast<StatusCode>(status_code)); |
| 164 base::Value* unscoped_value; | 246 base::Value* unscoped_value; |
| 165 if (!cmd_result->Get("result.value.value", &unscoped_value)) { | 247 if (!temp_result->Get("value.value", &unscoped_value)) { |
| 166 return Status(kUnknownError, | 248 return Status(kUnknownError, |
| 167 "Runtime.evaluate missing result.value.value"); | 249 "Runtime.evaluate missing 'value.value'"); |
| 168 } | 250 } |
| 169 result->reset(unscoped_value->DeepCopy()); | 251 result->reset(unscoped_value->DeepCopy()); |
| 170 } | 252 } |
| 171 return Status(kOk); | 253 return Status(kOk); |
| 172 } | 254 } |
| 173 | 255 |
| 256 Status GetNodeIdFromFunction(DevToolsClient* client, |
| 257 int context_id, |
| 258 const std::string& function, |
| 259 const base::ListValue& args, |
| 260 int* node_id) { |
| 261 std::string json; |
| 262 base::JSONWriter::Write(&args, &json); |
| 263 std::string expression = base::StringPrintf( |
| 264 "(%s).apply(null, [%s, %s, true])", |
| 265 kCallFunctionScript, |
| 266 function.c_str(), |
| 267 json.c_str()); |
| 268 |
| 269 std::string element_id; |
| 270 Status status = internal::EvaluateScriptAndGetObject( |
| 271 client, context_id, expression, &element_id); |
| 272 if (status.IsError()) |
| 273 return status; |
| 274 |
| 275 scoped_ptr<base::DictionaryValue> cmd_result; |
| 276 { |
| 277 base::DictionaryValue params; |
| 278 params.SetString("objectId", element_id); |
| 279 status = client->SendCommandAndGetResult( |
| 280 "DOM.requestNode", params, &cmd_result); |
| 281 } |
| 282 { |
| 283 // Release the remote object before doing anything else. |
| 284 base::DictionaryValue params; |
| 285 params.SetString("objectId", element_id); |
| 286 Status release_status = |
| 287 client->SendCommand("Runtime.releaseObject", params); |
| 288 if (release_status.IsError()) { |
| 289 LOG(ERROR) << "Failed to release remote object: " |
| 290 << release_status.message(); |
| 291 } |
| 292 } |
| 293 if (status.IsError()) |
| 294 return status; |
| 295 |
| 296 if (!cmd_result->GetInteger("nodeId", node_id)) |
| 297 return Status(kUnknownError, "DOM.requestNode missing int 'nodeId'"); |
| 298 return Status(kOk); |
| 299 } |
| 300 |
| 174 } // namespace internal | 301 } // namespace internal |
| OLD | NEW |