Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / test / chromedriver / chrome / web_view_impl.cc
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/chrome/web_view_impl.h"
6
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "base/json/json_writer.h"
10 #include "base/logging.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/threading/platform_thread.h"
14 #include "base/time/time.h"
15 #include "base/values.h"
16 #include "chrome/test/chromedriver/chrome/debugger_tracker.h"
17 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
18 #include "chrome/test/chromedriver/chrome/dom_tracker.h"
19 #include "chrome/test/chromedriver/chrome/frame_tracker.h"
20 #include "chrome/test/chromedriver/chrome/geolocation_override_manager.h"
21 #include "chrome/test/chromedriver/chrome/heap_snapshot_taker.h"
22 #include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h"
23 #include "chrome/test/chromedriver/chrome/js.h"
24 #include "chrome/test/chromedriver/chrome/navigation_tracker.h"
25 #include "chrome/test/chromedriver/chrome/status.h"
26 #include "chrome/test/chromedriver/chrome/ui_events.h"
27
28 namespace {
29
30 Status GetContextIdForFrame(FrameTracker* tracker,
31                             const std::string& frame,
32                             int* context_id) {
33   if (frame.empty()) {
34     *context_id = 0;
35     return Status(kOk);
36   }
37   Status status = tracker->GetContextIdForFrame(frame, context_id);
38   if (status.IsError())
39     return status;
40   return Status(kOk);
41 }
42
43 const char* GetAsString(MouseEventType type) {
44   switch (type) {
45     case kPressedMouseEventType:
46       return "mousePressed";
47     case kReleasedMouseEventType:
48       return "mouseReleased";
49     case kMovedMouseEventType:
50       return "mouseMoved";
51     default:
52       return "";
53   }
54 }
55
56 const char* GetAsString(TouchEventType type) {
57   switch (type) {
58     case kTouchStart:
59       return "touchStart";
60     case kTouchEnd:
61       return "touchEnd";
62     case kTouchMove:
63       return "touchMove";
64     default:
65       return "";
66   }
67 }
68
69 const char* GetPointStateString(TouchEventType type) {
70   switch (type) {
71     case kTouchStart:
72       return "touchPressed";
73     case kTouchEnd:
74       return "touchReleased";
75     case kTouchMove:
76       return "touchMoved";
77     default:
78       return "";
79   }
80 }
81
82 const char* GetAsString(MouseButton button) {
83   switch (button) {
84     case kLeftMouseButton:
85       return "left";
86     case kMiddleMouseButton:
87       return "middle";
88     case kRightMouseButton:
89       return "right";
90     case kNoneMouseButton:
91       return "none";
92     default:
93       return "";
94   }
95 }
96
97 const char* GetAsString(KeyEventType type) {
98   switch (type) {
99     case kKeyDownEventType:
100       return "keyDown";
101     case kKeyUpEventType:
102       return "keyUp";
103     case kRawKeyDownEventType:
104       return "rawKeyDown";
105     case kCharEventType:
106       return "char";
107     default:
108       return "";
109   }
110 }
111
112 }  // namespace
113
114 WebViewImpl::WebViewImpl(const std::string& id,
115                          int build_no,
116                          scoped_ptr<DevToolsClient> client)
117     : id_(id),
118       build_no_(build_no),
119       dom_tracker_(new DomTracker(client.get())),
120       frame_tracker_(new FrameTracker(client.get())),
121       navigation_tracker_(new NavigationTracker(client.get())),
122       dialog_manager_(new JavaScriptDialogManager(client.get())),
123       geolocation_override_manager_(
124           new GeolocationOverrideManager(client.get())),
125       heap_snapshot_taker_(new HeapSnapshotTaker(client.get())),
126       debugger_(new DebuggerTracker(client.get())),
127       client_(client.release()) {}
128
129 WebViewImpl::~WebViewImpl() {}
130
131 std::string WebViewImpl::GetId() {
132   return id_;
133 }
134
135 bool WebViewImpl::WasCrashed() {
136   return client_->WasCrashed();
137 }
138
139 Status WebViewImpl::ConnectIfNecessary() {
140   return client_->ConnectIfNecessary();
141 }
142
143 Status WebViewImpl::HandleReceivedEvents() {
144   return client_->HandleReceivedEvents();
145 }
146
147 Status WebViewImpl::Load(const std::string& url) {
148   // Javascript URLs will cause a hang while waiting for the page to stop
149   // loading, so just disallow.
150   if (StartsWithASCII(url, "javascript:", false))
151     return Status(kUnknownError, "unsupported protocol");
152   base::DictionaryValue params;
153   params.SetString("url", url);
154   return client_->SendCommand("Page.navigate", params);
155 }
156
157 Status WebViewImpl::Reload() {
158   base::DictionaryValue params;
159   params.SetBoolean("ignoreCache", false);
160   return client_->SendCommand("Page.reload", params);
161 }
162
163 Status WebViewImpl::EvaluateScript(const std::string& frame,
164                                    const std::string& expression,
165                                    scoped_ptr<base::Value>* result) {
166   int context_id;
167   Status status = GetContextIdForFrame(frame_tracker_.get(), frame,
168                                        &context_id);
169   if (status.IsError())
170     return status;
171   return internal::EvaluateScriptAndGetValue(
172       client_.get(), context_id, expression, result);
173 }
174
175 Status WebViewImpl::CallFunction(const std::string& frame,
176                                  const std::string& function,
177                                  const base::ListValue& args,
178                                  scoped_ptr<base::Value>* result) {
179   std::string json;
180   base::JSONWriter::Write(&args, &json);
181   // TODO(zachconrad): Second null should be array of shadow host ids.
182   std::string expression = base::StringPrintf(
183       "(%s).apply(null, [null, %s, %s])",
184       kCallFunctionScript,
185       function.c_str(),
186       json.c_str());
187   scoped_ptr<base::Value> temp_result;
188   Status status = EvaluateScript(frame, expression, &temp_result);
189   if (status.IsError())
190     return status;
191
192   return internal::ParseCallFunctionResult(*temp_result, result);
193 }
194
195 Status WebViewImpl::CallAsyncFunction(const std::string& frame,
196                                       const std::string& function,
197                                       const base::ListValue& args,
198                                       const base::TimeDelta& timeout,
199                                       scoped_ptr<base::Value>* result) {
200   return CallAsyncFunctionInternal(
201       frame, function, args, false, timeout, result);
202 }
203
204 Status WebViewImpl::CallUserAsyncFunction(const std::string& frame,
205                                           const std::string& function,
206                                           const base::ListValue& args,
207                                           const base::TimeDelta& timeout,
208                                           scoped_ptr<base::Value>* result) {
209   return CallAsyncFunctionInternal(
210       frame, function, args, true, timeout, result);
211 }
212
213 Status WebViewImpl::GetFrameByFunction(const std::string& frame,
214                                        const std::string& function,
215                                        const base::ListValue& args,
216                                        std::string* out_frame) {
217   int context_id;
218   Status status = GetContextIdForFrame(frame_tracker_.get(), frame,
219                                        &context_id);
220   if (status.IsError())
221     return status;
222   bool found_node;
223   int node_id;
224   status = internal::GetNodeIdFromFunction(
225       client_.get(), context_id, function, args, &found_node, &node_id);
226   if (status.IsError())
227     return status;
228   if (!found_node)
229     return Status(kNoSuchFrame);
230   return dom_tracker_->GetFrameIdForNode(node_id, out_frame);
231 }
232
233 Status WebViewImpl::DispatchMouseEvents(const std::list<MouseEvent>& events,
234                                         const std::string& frame) {
235   for (std::list<MouseEvent>::const_iterator it = events.begin();
236        it != events.end(); ++it) {
237     base::DictionaryValue params;
238     params.SetString("type", GetAsString(it->type));
239     params.SetInteger("x", it->x);
240     params.SetInteger("y", it->y);
241     params.SetInteger("modifiers", it->modifiers);
242     params.SetString("button", GetAsString(it->button));
243     params.SetInteger("clickCount", it->click_count);
244     Status status = client_->SendCommand("Input.dispatchMouseEvent", params);
245     if (status.IsError())
246       return status;
247     if (build_no_ < 1569 && it->button == kRightMouseButton &&
248         it->type == kReleasedMouseEventType) {
249       base::ListValue args;
250       args.AppendInteger(it->x);
251       args.AppendInteger(it->y);
252       args.AppendInteger(it->modifiers);
253       scoped_ptr<base::Value> result;
254       status = CallFunction(
255           frame, kDispatchContextMenuEventScript, args, &result);
256       if (status.IsError())
257         return status;
258     }
259   }
260   return Status(kOk);
261 }
262
263 Status WebViewImpl::DispatchTouchEvent(const TouchEvent& event) {
264   base::DictionaryValue params;
265   params.SetString("type", GetAsString(event.type));
266   scoped_ptr<base::ListValue> point_list(new base::ListValue);
267   scoped_ptr<base::DictionaryValue> point(new base::DictionaryValue);
268   point->SetString("state", GetPointStateString(event.type));
269   point->SetInteger("x", event.x);
270   point->SetInteger("y", event.y);
271   point_list->Set(0, point.release());
272   params.Set("touchPoints", point_list.release());
273   return client_->SendCommand("Input.dispatchTouchEvent", params);
274 }
275
276 Status WebViewImpl::DispatchTouchEvents(const std::list<TouchEvent>& events) {
277   for (std::list<TouchEvent>::const_iterator it = events.begin();
278        it != events.end(); ++it) {
279     Status status = DispatchTouchEvent(*it);
280     if (status.IsError())
281       return status;
282   }
283   return Status(kOk);
284 }
285
286 Status WebViewImpl::DispatchKeyEvents(const std::list<KeyEvent>& events) {
287   for (std::list<KeyEvent>::const_iterator it = events.begin();
288        it != events.end(); ++it) {
289     base::DictionaryValue params;
290     params.SetString("type", GetAsString(it->type));
291     if (it->modifiers & kNumLockKeyModifierMask) {
292       params.SetBoolean("isKeypad", true);
293       params.SetInteger("modifiers",
294                         it->modifiers & ~kNumLockKeyModifierMask);
295     } else {
296       params.SetInteger("modifiers", it->modifiers);
297     }
298     params.SetString("text", it->modified_text);
299     params.SetString("unmodifiedText", it->unmodified_text);
300     params.SetInteger("nativeVirtualKeyCode", it->key_code);
301     params.SetInteger("windowsVirtualKeyCode", it->key_code);
302     Status status = client_->SendCommand("Input.dispatchKeyEvent", params);
303     if (status.IsError())
304       return status;
305   }
306   return Status(kOk);
307 }
308
309 Status WebViewImpl::GetCookies(scoped_ptr<base::ListValue>* cookies) {
310   base::DictionaryValue params;
311   scoped_ptr<base::DictionaryValue> result;
312   Status status = client_->SendCommandAndGetResult(
313       "Page.getCookies", params, &result);
314   if (status.IsError())
315     return status;
316   base::ListValue* cookies_tmp;
317   if (!result->GetList("cookies", &cookies_tmp))
318     return Status(kUnknownError, "DevTools didn't return cookies");
319   cookies->reset(cookies_tmp->DeepCopy());
320   return Status(kOk);
321 }
322
323 Status WebViewImpl::DeleteCookie(const std::string& name,
324                                  const std::string& url) {
325   base::DictionaryValue params;
326   params.SetString("cookieName", name);
327   params.SetString("url", url);
328   return client_->SendCommand("Page.deleteCookie", params);
329 }
330
331 Status WebViewImpl::WaitForPendingNavigations(const std::string& frame_id,
332                                               const base::TimeDelta& timeout,
333                                               bool stop_load_on_timeout) {
334   VLOG(0) << "Waiting for pending navigations...";
335   Status status = client_->HandleEventsUntil(
336       base::Bind(&WebViewImpl::IsNotPendingNavigation,
337                  base::Unretained(this),
338                  frame_id),
339       timeout);
340   if (status.code() == kTimeout && stop_load_on_timeout) {
341     VLOG(0) << "Timed out. Stopping navigation...";
342     scoped_ptr<base::Value> unused_value;
343     EvaluateScript(std::string(), "window.stop();", &unused_value);
344     Status new_status = client_->HandleEventsUntil(
345         base::Bind(&WebViewImpl::IsNotPendingNavigation, base::Unretained(this),
346                    frame_id),
347         base::TimeDelta::FromSeconds(10));
348     if (new_status.IsError())
349       status = new_status;
350   }
351   VLOG(0) << "Done waiting for pending navigations";
352   return status;
353 }
354
355 Status WebViewImpl::IsPendingNavigation(const std::string& frame_id,
356                                         bool* is_pending) {
357   return navigation_tracker_->IsPendingNavigation(frame_id, is_pending);
358 }
359
360 JavaScriptDialogManager* WebViewImpl::GetJavaScriptDialogManager() {
361   return dialog_manager_.get();
362 }
363
364 Status WebViewImpl::OverrideGeolocation(const Geoposition& geoposition) {
365   return geolocation_override_manager_->OverrideGeolocation(geoposition);
366 }
367
368 Status WebViewImpl::CaptureScreenshot(std::string* screenshot) {
369   base::DictionaryValue params;
370   scoped_ptr<base::DictionaryValue> result;
371   Status status = client_->SendCommandAndGetResult(
372       "Page.captureScreenshot", params, &result);
373   if (status.IsError())
374     return status;
375   if (!result->GetString("data", screenshot))
376     return Status(kUnknownError, "expected string 'data' in response");
377   return Status(kOk);
378 }
379
380 Status WebViewImpl::SetFileInputFiles(
381     const std::string& frame,
382     const base::DictionaryValue& element,
383     const std::vector<base::FilePath>& files) {
384   base::ListValue file_list;
385   for (size_t i = 0; i < files.size(); ++i) {
386     if (!files[i].IsAbsolute()) {
387       return Status(kUnknownError,
388                     "path is not absolute: " + files[i].AsUTF8Unsafe());
389     }
390     if (files[i].ReferencesParent()) {
391       return Status(kUnknownError,
392                     "path is not canonical: " + files[i].AsUTF8Unsafe());
393     }
394     file_list.AppendString(files[i].value());
395   }
396
397   int context_id;
398   Status status = GetContextIdForFrame(frame_tracker_.get(), frame,
399                                        &context_id);
400   if (status.IsError())
401     return status;
402   base::ListValue args;
403   args.Append(element.DeepCopy());
404   bool found_node;
405   int node_id;
406   status = internal::GetNodeIdFromFunction(
407       client_.get(), context_id, "function(element) { return element; }",
408       args, &found_node, &node_id);
409   if (status.IsError())
410     return status;
411   if (!found_node)
412     return Status(kUnknownError, "no node ID for file input");
413   base::DictionaryValue params;
414   params.SetInteger("nodeId", node_id);
415   params.Set("files", file_list.DeepCopy());
416   return client_->SendCommand("DOM.setFileInputFiles", params);
417 }
418
419 Status WebViewImpl::TakeHeapSnapshot(scoped_ptr<base::Value>* snapshot) {
420   return heap_snapshot_taker_->TakeSnapshot(snapshot);
421 }
422
423 Status WebViewImpl::CallAsyncFunctionInternal(const std::string& frame,
424                                               const std::string& function,
425                                               const base::ListValue& args,
426                                               bool is_user_supplied,
427                                               const base::TimeDelta& timeout,
428                                               scoped_ptr<base::Value>* result) {
429   base::ListValue async_args;
430   async_args.AppendString("return (" + function + ").apply(null, arguments);");
431   async_args.Append(args.DeepCopy());
432   async_args.AppendBoolean(is_user_supplied);
433   async_args.AppendInteger(timeout.InMilliseconds());
434   scoped_ptr<base::Value> tmp;
435   Status status = CallFunction(
436       frame, kExecuteAsyncScriptScript, async_args, &tmp);
437   if (status.IsError())
438     return status;
439
440   const char* kDocUnloadError = "document unloaded while waiting for result";
441   std::string kQueryResult = base::StringPrintf(
442       "function() {"
443       "  var info = document.$chrome_asyncScriptInfo;"
444       "  if (!info)"
445       "    return {status: %d, value: '%s'};"
446       "  var result = info.result;"
447       "  if (!result)"
448       "    return {status: 0};"
449       "  delete info.result;"
450       "  return result;"
451       "}",
452       kJavaScriptError,
453       kDocUnloadError);
454
455   while (true) {
456     base::ListValue no_args;
457     scoped_ptr<base::Value> query_value;
458     Status status = CallFunction(frame, kQueryResult, no_args, &query_value);
459     if (status.IsError()) {
460       if (status.code() == kNoSuchFrame)
461         return Status(kJavaScriptError, kDocUnloadError);
462       return status;
463     }
464
465     base::DictionaryValue* result_info = NULL;
466     if (!query_value->GetAsDictionary(&result_info))
467       return Status(kUnknownError, "async result info is not a dictionary");
468     int status_code;
469     if (!result_info->GetInteger("status", &status_code))
470       return Status(kUnknownError, "async result info has no int 'status'");
471     if (status_code != kOk) {
472       std::string message;
473       result_info->GetString("value", &message);
474       return Status(static_cast<StatusCode>(status_code), message);
475     }
476
477     base::Value* value = NULL;
478     if (result_info->Get("value", &value)) {
479       result->reset(value->DeepCopy());
480       return Status(kOk);
481     }
482
483     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
484   }
485 }
486
487 Status WebViewImpl::IsNotPendingNavigation(const std::string& frame_id,
488                                            bool* is_not_pending) {
489   bool is_pending;
490   Status status =
491       navigation_tracker_->IsPendingNavigation(frame_id, &is_pending);
492   if (status.IsError())
493     return status;
494   // An alert may block the pending navigation.
495   if (is_pending && dialog_manager_->IsDialogOpen())
496     return Status(kUnexpectedAlertOpen);
497
498   *is_not_pending = !is_pending;
499   return Status(kOk);
500 }
501
502 namespace internal {
503
504 Status EvaluateScript(DevToolsClient* client,
505                       int context_id,
506                       const std::string& expression,
507                       EvaluateScriptReturnType return_type,
508                       scoped_ptr<base::DictionaryValue>* result) {
509   base::DictionaryValue params;
510   params.SetString("expression", expression);
511   if (context_id)
512     params.SetInteger("contextId", context_id);
513   params.SetBoolean("returnByValue", return_type == ReturnByValue);
514   scoped_ptr<base::DictionaryValue> cmd_result;
515   Status status = client->SendCommandAndGetResult(
516       "Runtime.evaluate", params, &cmd_result);
517   if (status.IsError())
518     return status;
519
520   bool was_thrown;
521   if (!cmd_result->GetBoolean("wasThrown", &was_thrown))
522     return Status(kUnknownError, "Runtime.evaluate missing 'wasThrown'");
523   if (was_thrown) {
524     std::string description = "unknown";
525     cmd_result->GetString("result.description", &description);
526     return Status(kUnknownError,
527                   "Runtime.evaluate threw exception: " + description);
528   }
529
530   base::DictionaryValue* unscoped_result;
531   if (!cmd_result->GetDictionary("result", &unscoped_result))
532     return Status(kUnknownError, "evaluate missing dictionary 'result'");
533   result->reset(unscoped_result->DeepCopy());
534   return Status(kOk);
535 }
536
537 Status EvaluateScriptAndGetObject(DevToolsClient* client,
538                                   int context_id,
539                                   const std::string& expression,
540                                   bool* got_object,
541                                   std::string* object_id) {
542   scoped_ptr<base::DictionaryValue> result;
543   Status status = EvaluateScript(client, context_id, expression, ReturnByObject,
544                                  &result);
545   if (status.IsError())
546     return status;
547   if (!result->HasKey("objectId")) {
548     *got_object = false;
549     return Status(kOk);
550   }
551   if (!result->GetString("objectId", object_id))
552     return Status(kUnknownError, "evaluate has invalid 'objectId'");
553   *got_object = true;
554   return Status(kOk);
555 }
556
557 Status EvaluateScriptAndGetValue(DevToolsClient* client,
558                                  int context_id,
559                                  const std::string& expression,
560                                  scoped_ptr<base::Value>* result) {
561   scoped_ptr<base::DictionaryValue> temp_result;
562   Status status = EvaluateScript(client, context_id, expression, ReturnByValue,
563                                  &temp_result);
564   if (status.IsError())
565     return status;
566
567   std::string type;
568   if (!temp_result->GetString("type", &type))
569     return Status(kUnknownError, "Runtime.evaluate missing string 'type'");
570
571   if (type == "undefined") {
572     result->reset(base::Value::CreateNullValue());
573   } else {
574     base::Value* value;
575     if (!temp_result->Get("value", &value))
576       return Status(kUnknownError, "Runtime.evaluate missing 'value'");
577     result->reset(value->DeepCopy());
578   }
579   return Status(kOk);
580 }
581
582 Status ParseCallFunctionResult(const base::Value& temp_result,
583                                scoped_ptr<base::Value>* result) {
584   const base::DictionaryValue* dict;
585   if (!temp_result.GetAsDictionary(&dict))
586     return Status(kUnknownError, "call function result must be a dictionary");
587   int status_code;
588   if (!dict->GetInteger("status", &status_code)) {
589     return Status(kUnknownError,
590                   "call function result missing int 'status'");
591   }
592   if (status_code != kOk) {
593     std::string message;
594     dict->GetString("value", &message);
595     return Status(static_cast<StatusCode>(status_code), message);
596   }
597   const base::Value* unscoped_value;
598   if (!dict->Get("value", &unscoped_value)) {
599     return Status(kUnknownError,
600                   "call function result missing 'value'");
601   }
602   result->reset(unscoped_value->DeepCopy());
603   return Status(kOk);
604 }
605
606 Status GetNodeIdFromFunction(DevToolsClient* client,
607                              int context_id,
608                              const std::string& function,
609                              const base::ListValue& args,
610                              bool* found_node,
611                              int* node_id) {
612   std::string json;
613   base::JSONWriter::Write(&args, &json);
614   // TODO(zachconrad): Second null should be array of shadow host ids.
615   std::string expression = base::StringPrintf(
616       "(%s).apply(null, [null, %s, %s, true])",
617       kCallFunctionScript,
618       function.c_str(),
619       json.c_str());
620
621   bool got_object;
622   std::string element_id;
623   Status status = internal::EvaluateScriptAndGetObject(
624       client, context_id, expression, &got_object, &element_id);
625   if (status.IsError())
626     return status;
627   if (!got_object) {
628     *found_node = false;
629     return Status(kOk);
630   }
631
632   scoped_ptr<base::DictionaryValue> cmd_result;
633   {
634     base::DictionaryValue params;
635     params.SetString("objectId", element_id);
636     status = client->SendCommandAndGetResult(
637         "DOM.requestNode", params, &cmd_result);
638   }
639   {
640     // Release the remote object before doing anything else.
641     base::DictionaryValue params;
642     params.SetString("objectId", element_id);
643     Status release_status =
644         client->SendCommand("Runtime.releaseObject", params);
645     if (release_status.IsError()) {
646       LOG(ERROR) << "Failed to release remote object: "
647                  << release_status.message();
648     }
649   }
650   if (status.IsError())
651     return status;
652
653   if (!cmd_result->GetInteger("nodeId", node_id))
654     return Status(kUnknownError, "DOM.requestNode missing int 'nodeId'");
655   *found_node = true;
656   return Status(kOk);
657 }
658
659 }  // namespace internal