| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/test/chromedriver/web_view_impl.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/json/json_writer.h" | |
| 9 #include "base/logging.h" | |
| 10 #include "base/stringprintf.h" | |
| 11 #include "base/values.h" | |
| 12 #include "chrome/test/chromedriver/devtools_client_impl.h" | |
| 13 #include "chrome/test/chromedriver/dom_tracker.h" | |
| 14 #include "chrome/test/chromedriver/frame_tracker.h" | |
| 15 #include "chrome/test/chromedriver/javascript_dialog_manager.h" | |
| 16 #include "chrome/test/chromedriver/js.h" | |
| 17 #include "chrome/test/chromedriver/navigation_tracker.h" | |
| 18 #include "chrome/test/chromedriver/status.h" | |
| 19 #include "chrome/test/chromedriver/ui_events.h" | |
| 20 #include "chrome/test/chromedriver/web_view_delegate.h" | |
| 21 | |
| 22 namespace { | |
| 23 | |
| 24 Status GetContextIdForFrame(FrameTracker* tracker, | |
| 25 const std::string& frame, | |
| 26 int* context_id) { | |
| 27 if (frame.empty()) { | |
| 28 *context_id = 0; | |
| 29 return Status(kOk); | |
| 30 } | |
| 31 Status status = tracker->GetContextIdForFrame(frame, context_id); | |
| 32 if (status.IsError()) | |
| 33 return status; | |
| 34 return Status(kOk); | |
| 35 } | |
| 36 | |
| 37 const char* GetAsString(MouseEventType type) { | |
| 38 switch (type) { | |
| 39 case kPressedMouseEventType: | |
| 40 return "mousePressed"; | |
| 41 case kReleasedMouseEventType: | |
| 42 return "mouseReleased"; | |
| 43 case kMovedMouseEventType: | |
| 44 return "mouseMoved"; | |
| 45 default: | |
| 46 return ""; | |
| 47 } | |
| 48 } | |
| 49 | |
| 50 const char* GetAsString(MouseButton button) { | |
| 51 switch (button) { | |
| 52 case kLeftMouseButton: | |
| 53 return "left"; | |
| 54 case kMiddleMouseButton: | |
| 55 return "middle"; | |
| 56 case kRightMouseButton: | |
| 57 return "right"; | |
| 58 case kNoneMouseButton: | |
| 59 return "none"; | |
| 60 default: | |
| 61 return ""; | |
| 62 } | |
| 63 } | |
| 64 | |
| 65 Status IsNotPendingNavigation(NavigationTracker* tracker, | |
| 66 const std::string& frame_id, | |
| 67 bool* is_not_pending) { | |
| 68 bool is_pending; | |
| 69 Status status = tracker->IsPendingNavigation(frame_id, &is_pending); | |
| 70 if (status.IsError()) | |
| 71 return status; | |
| 72 *is_not_pending = !is_pending; | |
| 73 return Status(kOk); | |
| 74 } | |
| 75 | |
| 76 const char* GetAsString(KeyEventType type) { | |
| 77 switch (type) { | |
| 78 case kKeyDownEventType: | |
| 79 return "keyDown"; | |
| 80 case kKeyUpEventType: | |
| 81 return "keyUp"; | |
| 82 case kRawKeyDownEventType: | |
| 83 return "rawKeyDown"; | |
| 84 case kCharEventType: | |
| 85 return "char"; | |
| 86 default: | |
| 87 return ""; | |
| 88 } | |
| 89 } | |
| 90 | |
| 91 } // namespace | |
| 92 | |
| 93 WebViewImpl::WebViewImpl(const std::string& id, | |
| 94 DevToolsClient* client, | |
| 95 WebViewDelegate* delegate, | |
| 96 const CloserFunc& closer_func) | |
| 97 : id_(id), | |
| 98 dom_tracker_(new DomTracker(client)), | |
| 99 frame_tracker_(new FrameTracker(client)), | |
| 100 navigation_tracker_(new NavigationTracker(client)), | |
| 101 dialog_manager_(new JavaScriptDialogManager(client)), | |
| 102 client_(client), | |
| 103 delegate_(delegate), | |
| 104 closer_func_(closer_func) {} | |
| 105 | |
| 106 WebViewImpl::~WebViewImpl() {} | |
| 107 | |
| 108 std::string WebViewImpl::GetId() { | |
| 109 return id_; | |
| 110 } | |
| 111 | |
| 112 Status WebViewImpl::ConnectIfNecessary() { | |
| 113 return client_->ConnectIfNecessary(); | |
| 114 } | |
| 115 | |
| 116 Status WebViewImpl::Close() { | |
| 117 Status status = closer_func_.Run(); | |
| 118 if (status.IsOk()) | |
| 119 delegate_->OnWebViewClose(this); | |
| 120 return status; | |
| 121 } | |
| 122 | |
| 123 Status WebViewImpl::Load(const std::string& url) { | |
| 124 base::DictionaryValue params; | |
| 125 params.SetString("url", url); | |
| 126 return client_->SendCommand("Page.navigate", params); | |
| 127 } | |
| 128 | |
| 129 Status WebViewImpl::Reload() { | |
| 130 base::DictionaryValue params; | |
| 131 params.SetBoolean("ignoreCache", false); | |
| 132 return client_->SendCommand("Page.reload", params); | |
| 133 } | |
| 134 | |
| 135 Status WebViewImpl::EvaluateScript(const std::string& frame, | |
| 136 const std::string& expression, | |
| 137 scoped_ptr<base::Value>* result) { | |
| 138 int context_id; | |
| 139 Status status = GetContextIdForFrame(frame_tracker_.get(), frame, | |
| 140 &context_id); | |
| 141 if (status.IsError()) | |
| 142 return status; | |
| 143 return internal::EvaluateScriptAndGetValue( | |
| 144 client_.get(), context_id, expression, result); | |
| 145 } | |
| 146 | |
| 147 Status WebViewImpl::CallFunction(const std::string& frame, | |
| 148 const std::string& function, | |
| 149 const base::ListValue& args, | |
| 150 scoped_ptr<base::Value>* result) { | |
| 151 std::string json; | |
| 152 base::JSONWriter::Write(&args, &json); | |
| 153 std::string expression = base::StringPrintf( | |
| 154 "(%s).apply(null, [%s, %s])", | |
| 155 kCallFunctionScript, | |
| 156 function.c_str(), | |
| 157 json.c_str()); | |
| 158 scoped_ptr<base::Value> temp_result; | |
| 159 Status status = EvaluateScript(frame, expression, &temp_result); | |
| 160 if (status.IsError()) | |
| 161 return status; | |
| 162 | |
| 163 return internal::ParseCallFunctionResult(*temp_result, result); | |
| 164 } | |
| 165 | |
| 166 Status WebViewImpl::GetFrameByFunction(const std::string& frame, | |
| 167 const std::string& function, | |
| 168 const base::ListValue& args, | |
| 169 std::string* out_frame) { | |
| 170 int context_id; | |
| 171 Status status = GetContextIdForFrame(frame_tracker_.get(), frame, | |
| 172 &context_id); | |
| 173 if (status.IsError()) | |
| 174 return status; | |
| 175 int node_id; | |
| 176 status = internal::GetNodeIdFromFunction( | |
| 177 client_.get(), context_id, function, args, &node_id); | |
| 178 if (status.IsError()) | |
| 179 return status; | |
| 180 return dom_tracker_->GetFrameIdForNode(node_id, out_frame); | |
| 181 } | |
| 182 | |
| 183 Status WebViewImpl::DispatchMouseEvents(const std::list<MouseEvent>& events) { | |
| 184 for (std::list<MouseEvent>::const_iterator it = events.begin(); | |
| 185 it != events.end(); ++it) { | |
| 186 base::DictionaryValue params; | |
| 187 params.SetString("type", GetAsString(it->type)); | |
| 188 params.SetInteger("x", it->x); | |
| 189 params.SetInteger("y", it->y); | |
| 190 params.SetString("button", GetAsString(it->button)); | |
| 191 params.SetInteger("clickCount", it->click_count); | |
| 192 Status status = client_->SendCommand("Input.dispatchMouseEvent", params); | |
| 193 if (status.IsError()) | |
| 194 return status; | |
| 195 } | |
| 196 return Status(kOk); | |
| 197 } | |
| 198 | |
| 199 Status WebViewImpl::DispatchKeyEvents(const std::list<KeyEvent>& events) { | |
| 200 for (std::list<KeyEvent>::const_iterator it = events.begin(); | |
| 201 it != events.end(); ++it) { | |
| 202 base::DictionaryValue params; | |
| 203 params.SetString("type", GetAsString(it->type)); | |
| 204 if (it->modifiers & kNumLockKeyModifierMask) { | |
| 205 params.SetBoolean("isKeypad", true); | |
| 206 params.SetInteger("modifiers", | |
| 207 it->modifiers & ~kNumLockKeyModifierMask); | |
| 208 } else { | |
| 209 params.SetInteger("modifiers", it->modifiers); | |
| 210 } | |
| 211 params.SetString("text", it->modified_text); | |
| 212 params.SetString("unmodifiedText", it->unmodified_text); | |
| 213 params.SetInteger("nativeVirtualKeyCode", it->key_code); | |
| 214 params.SetInteger("windowsVirtualKeyCode", it->key_code); | |
| 215 Status status = client_->SendCommand("Input.dispatchKeyEvent", params); | |
| 216 if (status.IsError()) | |
| 217 return status; | |
| 218 } | |
| 219 return Status(kOk); | |
| 220 } | |
| 221 | |
| 222 Status WebViewImpl::GetCookies(scoped_ptr<base::ListValue>* cookies) { | |
| 223 base::DictionaryValue params; | |
| 224 scoped_ptr<base::DictionaryValue> result; | |
| 225 Status status = client_->SendCommandAndGetResult( | |
| 226 "Page.getCookies", params, &result); | |
| 227 if (status.IsError()) | |
| 228 return status; | |
| 229 base::ListValue* cookies_tmp; | |
| 230 if (!result->GetList("cookies", &cookies_tmp)) | |
| 231 return Status(kUnknownError, "DevTools didn't return cookies"); | |
| 232 cookies->reset(cookies_tmp->DeepCopy()); | |
| 233 return Status(kOk); | |
| 234 } | |
| 235 | |
| 236 Status WebViewImpl::DeleteCookie(const std::string& name, | |
| 237 const std::string& url) { | |
| 238 base::DictionaryValue params; | |
| 239 params.SetString("cookieName", name); | |
| 240 params.SetString("url", url); | |
| 241 return client_->SendCommand("Page.deleteCookie", params); | |
| 242 } | |
| 243 | |
| 244 Status WebViewImpl::WaitForPendingNavigations(const std::string& frame_id) { | |
| 245 std::string full_frame_id(frame_id); | |
| 246 if (full_frame_id.empty()) { | |
| 247 Status status = GetMainFrame(&full_frame_id); | |
| 248 if (status.IsError()) | |
| 249 return status; | |
| 250 } | |
| 251 return client_->HandleEventsUntil( | |
| 252 base::Bind(IsNotPendingNavigation, navigation_tracker_.get(), | |
| 253 full_frame_id)); | |
| 254 } | |
| 255 | |
| 256 Status WebViewImpl::IsPendingNavigation(const std::string& frame_id, | |
| 257 bool* is_pending) { | |
| 258 std::string full_frame_id(frame_id); | |
| 259 if (full_frame_id.empty()) { | |
| 260 Status status = GetMainFrame(&full_frame_id); | |
| 261 if (status.IsError()) | |
| 262 return status; | |
| 263 } | |
| 264 return navigation_tracker_->IsPendingNavigation(frame_id, is_pending); | |
| 265 } | |
| 266 | |
| 267 Status WebViewImpl::GetMainFrame(std::string* out_frame) { | |
| 268 base::DictionaryValue params; | |
| 269 scoped_ptr<base::DictionaryValue> result; | |
| 270 Status status = client_->SendCommandAndGetResult( | |
| 271 "Page.getResourceTree", params, &result); | |
| 272 if (status.IsError()) | |
| 273 return status; | |
| 274 if (!result->GetString("frameTree.frame.id", out_frame)) | |
| 275 return Status(kUnknownError, "missing 'frameTree.frame.id' in response"); | |
| 276 return Status(kOk); | |
| 277 } | |
| 278 | |
| 279 JavaScriptDialogManager* WebViewImpl::GetJavaScriptDialogManager() { | |
| 280 return dialog_manager_.get(); | |
| 281 } | |
| 282 | |
| 283 Status WebViewImpl::CaptureScreenshot(std::string* screenshot) { | |
| 284 base::DictionaryValue params; | |
| 285 scoped_ptr<base::DictionaryValue> result; | |
| 286 Status status = client_->SendCommandAndGetResult( | |
| 287 "Page.captureScreenshot", params, &result); | |
| 288 if (status.IsError()) | |
| 289 return status; | |
| 290 if (!result->GetString("data", screenshot)) | |
| 291 return Status(kUnknownError, "expected string 'data' in response"); | |
| 292 return Status(kOk); | |
| 293 } | |
| 294 | |
| 295 namespace internal { | |
| 296 | |
| 297 Status EvaluateScript(DevToolsClient* client, | |
| 298 int context_id, | |
| 299 const std::string& expression, | |
| 300 EvaluateScriptReturnType return_type, | |
| 301 scoped_ptr<base::DictionaryValue>* result) { | |
| 302 base::DictionaryValue params; | |
| 303 params.SetString("expression", expression); | |
| 304 if (context_id) | |
| 305 params.SetInteger("contextId", context_id); | |
| 306 params.SetBoolean("returnByValue", return_type == ReturnByValue); | |
| 307 scoped_ptr<base::DictionaryValue> cmd_result; | |
| 308 Status status = client->SendCommandAndGetResult( | |
| 309 "Runtime.evaluate", params, &cmd_result); | |
| 310 if (status.IsError()) | |
| 311 return status; | |
| 312 | |
| 313 bool was_thrown; | |
| 314 if (!cmd_result->GetBoolean("wasThrown", &was_thrown)) | |
| 315 return Status(kUnknownError, "Runtime.evaluate missing 'wasThrown'"); | |
| 316 if (was_thrown) { | |
| 317 std::string description = "unknown"; | |
| 318 cmd_result->GetString("result.description", &description); | |
| 319 return Status(kUnknownError, | |
| 320 "Runtime.evaluate threw exception: " + description); | |
| 321 } | |
| 322 | |
| 323 base::DictionaryValue* unscoped_result; | |
| 324 if (!cmd_result->GetDictionary("result", &unscoped_result)) | |
| 325 return Status(kUnknownError, "evaluate missing dictionary 'result'"); | |
| 326 result->reset(unscoped_result->DeepCopy()); | |
| 327 return Status(kOk); | |
| 328 } | |
| 329 | |
| 330 Status EvaluateScriptAndGetObject(DevToolsClient* client, | |
| 331 int context_id, | |
| 332 const std::string& expression, | |
| 333 std::string* object_id) { | |
| 334 scoped_ptr<base::DictionaryValue> result; | |
| 335 Status status = EvaluateScript(client, context_id, expression, ReturnByObject, | |
| 336 &result); | |
| 337 if (status.IsError()) | |
| 338 return status; | |
| 339 if (!result->GetString("objectId", object_id)) | |
| 340 return Status(kUnknownError, "evaluate missing string 'objectId'"); | |
| 341 return Status(kOk); | |
| 342 } | |
| 343 | |
| 344 Status EvaluateScriptAndGetValue(DevToolsClient* client, | |
| 345 int context_id, | |
| 346 const std::string& expression, | |
| 347 scoped_ptr<base::Value>* result) { | |
| 348 scoped_ptr<base::DictionaryValue> temp_result; | |
| 349 Status status = EvaluateScript(client, context_id, expression, ReturnByValue, | |
| 350 &temp_result); | |
| 351 if (status.IsError()) | |
| 352 return status; | |
| 353 | |
| 354 std::string type; | |
| 355 if (!temp_result->GetString("type", &type)) | |
| 356 return Status(kUnknownError, "Runtime.evaluate missing string 'type'"); | |
| 357 | |
| 358 if (type == "undefined") { | |
| 359 result->reset(base::Value::CreateNullValue()); | |
| 360 } else { | |
| 361 base::Value* value; | |
| 362 if (!temp_result->Get("value", &value)) | |
| 363 return Status(kUnknownError, "Runtime.evaluate missing 'value'"); | |
| 364 result->reset(value->DeepCopy()); | |
| 365 } | |
| 366 return Status(kOk); | |
| 367 } | |
| 368 | |
| 369 Status ParseCallFunctionResult(const base::Value& temp_result, | |
| 370 scoped_ptr<base::Value>* result) { | |
| 371 const base::DictionaryValue* dict; | |
| 372 if (!temp_result.GetAsDictionary(&dict)) | |
| 373 return Status(kUnknownError, "call function result must be a dictionary"); | |
| 374 int status_code; | |
| 375 if (!dict->GetInteger("status", &status_code)) { | |
| 376 return Status(kUnknownError, | |
| 377 "call function result missing int 'status'"); | |
| 378 } | |
| 379 if (status_code != kOk) { | |
| 380 std::string message; | |
| 381 dict->GetString("value", &message); | |
| 382 return Status(static_cast<StatusCode>(status_code), message); | |
| 383 } | |
| 384 const base::Value* unscoped_value; | |
| 385 if (!dict->Get("value", &unscoped_value)) { | |
| 386 return Status(kUnknownError, | |
| 387 "call function result missing 'value'"); | |
| 388 } | |
| 389 result->reset(unscoped_value->DeepCopy()); | |
| 390 return Status(kOk); | |
| 391 } | |
| 392 | |
| 393 Status GetNodeIdFromFunction(DevToolsClient* client, | |
| 394 int context_id, | |
| 395 const std::string& function, | |
| 396 const base::ListValue& args, | |
| 397 int* node_id) { | |
| 398 std::string json; | |
| 399 base::JSONWriter::Write(&args, &json); | |
| 400 std::string expression = base::StringPrintf( | |
| 401 "(%s).apply(null, [%s, %s, true])", | |
| 402 kCallFunctionScript, | |
| 403 function.c_str(), | |
| 404 json.c_str()); | |
| 405 | |
| 406 std::string element_id; | |
| 407 Status status = internal::EvaluateScriptAndGetObject( | |
| 408 client, context_id, expression, &element_id); | |
| 409 if (status.IsError()) | |
| 410 return status; | |
| 411 | |
| 412 scoped_ptr<base::DictionaryValue> cmd_result; | |
| 413 { | |
| 414 base::DictionaryValue params; | |
| 415 params.SetString("objectId", element_id); | |
| 416 status = client->SendCommandAndGetResult( | |
| 417 "DOM.requestNode", params, &cmd_result); | |
| 418 } | |
| 419 { | |
| 420 // Release the remote object before doing anything else. | |
| 421 base::DictionaryValue params; | |
| 422 params.SetString("objectId", element_id); | |
| 423 Status release_status = | |
| 424 client->SendCommand("Runtime.releaseObject", params); | |
| 425 if (release_status.IsError()) { | |
| 426 LOG(ERROR) << "Failed to release remote object: " | |
| 427 << release_status.message(); | |
| 428 } | |
| 429 } | |
| 430 if (status.IsError()) | |
| 431 return status; | |
| 432 | |
| 433 if (!cmd_result->GetInteger("nodeId", node_id)) | |
| 434 return Status(kUnknownError, "DOM.requestNode missing int 'nodeId'"); | |
| 435 return Status(kOk); | |
| 436 } | |
| 437 | |
| 438 } // namespace internal | |
| OLD | NEW |