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 |