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