• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/element_util.h"
6 
7 #include "base/strings/string_number_conversions.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/threading/platform_thread.h"
11 #include "base/time/time.h"
12 #include "base/values.h"
13 #include "chrome/test/chromedriver/basic_types.h"
14 #include "chrome/test/chromedriver/chrome/browser_info.h"
15 #include "chrome/test/chromedriver/chrome/chrome.h"
16 #include "chrome/test/chromedriver/chrome/js.h"
17 #include "chrome/test/chromedriver/chrome/status.h"
18 #include "chrome/test/chromedriver/chrome/web_view.h"
19 #include "chrome/test/chromedriver/session.h"
20 #include "third_party/webdriver/atoms.h"
21 
22 namespace {
23 
24 const char kElementKey[] = "ELEMENT";
25 
ParseFromValue(base::Value * value,WebPoint * point)26 bool ParseFromValue(base::Value* value, WebPoint* point) {
27   base::DictionaryValue* dict_value;
28   if (!value->GetAsDictionary(&dict_value))
29     return false;
30   double x = 0;
31   double y = 0;
32   if (!dict_value->GetDouble("x", &x) ||
33       !dict_value->GetDouble("y", &y))
34     return false;
35   point->x = static_cast<int>(x);
36   point->y = static_cast<int>(y);
37   return true;
38 }
39 
ParseFromValue(base::Value * value,WebSize * size)40 bool ParseFromValue(base::Value* value, WebSize* size) {
41   base::DictionaryValue* dict_value;
42   if (!value->GetAsDictionary(&dict_value))
43     return false;
44   double width = 0;
45   double height = 0;
46   if (!dict_value->GetDouble("width", &width) ||
47       !dict_value->GetDouble("height", &height))
48     return false;
49   size->width = static_cast<int>(width);
50   size->height = static_cast<int>(height);
51   return true;
52 }
53 
ParseFromValue(base::Value * value,WebRect * rect)54 bool ParseFromValue(base::Value* value, WebRect* rect) {
55   base::DictionaryValue* dict_value;
56   if (!value->GetAsDictionary(&dict_value))
57     return false;
58   double x = 0;
59   double y = 0;
60   double width = 0;
61   double height = 0;
62   if (!dict_value->GetDouble("left", &x) ||
63       !dict_value->GetDouble("top", &y) ||
64       !dict_value->GetDouble("width", &width) ||
65       !dict_value->GetDouble("height", &height))
66     return false;
67   rect->origin.x = static_cast<int>(x);
68   rect->origin.y = static_cast<int>(y);
69   rect->size.width = static_cast<int>(width);
70   rect->size.height = static_cast<int>(height);
71   return true;
72 }
73 
CreateValueFrom(const WebRect & rect)74 base::Value* CreateValueFrom(const WebRect& rect) {
75   base::DictionaryValue* dict = new base::DictionaryValue();
76   dict->SetInteger("left", rect.X());
77   dict->SetInteger("top", rect.Y());
78   dict->SetInteger("width", rect.Width());
79   dict->SetInteger("height", rect.Height());
80   return dict;
81 }
82 
CallAtomsJs(const std::string & frame,WebView * web_view,const char * const * atom_function,const base::ListValue & args,scoped_ptr<base::Value> * result)83 Status CallAtomsJs(
84     const std::string& frame,
85     WebView* web_view,
86     const char* const* atom_function,
87     const base::ListValue& args,
88     scoped_ptr<base::Value>* result) {
89   return web_view->CallFunction(
90       frame, webdriver::atoms::asString(atom_function), args, result);
91 }
92 
VerifyElementClickable(const std::string & frame,WebView * web_view,const std::string & element_id,const WebPoint & location)93 Status VerifyElementClickable(
94     const std::string& frame,
95     WebView* web_view,
96     const std::string& element_id,
97     const WebPoint& location) {
98   base::ListValue args;
99   args.Append(CreateElement(element_id));
100   args.Append(CreateValueFrom(location));
101   scoped_ptr<base::Value> result;
102   Status status = CallAtomsJs(
103       frame, web_view, webdriver::atoms::IS_ELEMENT_CLICKABLE,
104       args, &result);
105   if (status.IsError())
106     return status;
107   base::DictionaryValue* dict;
108   bool is_clickable = false;
109   if (!result->GetAsDictionary(&dict) ||
110       !dict->GetBoolean("clickable", &is_clickable)) {
111     return Status(kUnknownError,
112                   "failed to parse value of IS_ELEMENT_CLICKABLE");
113   }
114 
115   if (!is_clickable) {
116     std::string message;
117     if (!dict->GetString("message", &message))
118       message = "element is not clickable";
119     return Status(kUnknownError, message);
120   }
121   return Status(kOk);
122 }
123 
ScrollElementRegionIntoViewHelper(const std::string & frame,WebView * web_view,const std::string & element_id,const WebRect & region,bool center,const std::string & clickable_element_id,WebPoint * location)124 Status ScrollElementRegionIntoViewHelper(
125     const std::string& frame,
126     WebView* web_view,
127     const std::string& element_id,
128     const WebRect& region,
129     bool center,
130     const std::string& clickable_element_id,
131     WebPoint* location) {
132   WebPoint tmp_location = *location;
133   base::ListValue args;
134   args.Append(CreateElement(element_id));
135   args.AppendBoolean(center);
136   args.Append(CreateValueFrom(region));
137   scoped_ptr<base::Value> result;
138   Status status = web_view->CallFunction(
139       frame, webdriver::atoms::asString(webdriver::atoms::GET_LOCATION_IN_VIEW),
140       args, &result);
141   if (status.IsError())
142     return status;
143   if (!ParseFromValue(result.get(), &tmp_location)) {
144     return Status(kUnknownError,
145                   "failed to parse value of GET_LOCATION_IN_VIEW");
146   }
147   if (!clickable_element_id.empty()) {
148     WebPoint middle = tmp_location;
149     middle.Offset(region.Width() / 2, region.Height() / 2);
150     status = VerifyElementClickable(
151         frame, web_view, clickable_element_id, middle);
152     if (status.IsError())
153       return status;
154   }
155   *location = tmp_location;
156   return Status(kOk);
157 }
158 
GetElementEffectiveStyle(const std::string & frame,WebView * web_view,const std::string & element_id,const std::string & property,std::string * value)159 Status GetElementEffectiveStyle(
160     const std::string& frame,
161     WebView* web_view,
162     const std::string& element_id,
163     const std::string& property,
164     std::string* value) {
165   base::ListValue args;
166   args.Append(CreateElement(element_id));
167   args.AppendString(property);
168   scoped_ptr<base::Value> result;
169   Status status = web_view->CallFunction(
170       frame, webdriver::atoms::asString(webdriver::atoms::GET_EFFECTIVE_STYLE),
171       args, &result);
172   if (status.IsError())
173     return status;
174   if (!result->GetAsString(value)) {
175     return Status(kUnknownError,
176                   "failed to parse value of GET_EFFECTIVE_STYLE");
177   }
178   return Status(kOk);
179 }
180 
GetElementBorder(const std::string & frame,WebView * web_view,const std::string & element_id,int * border_left,int * border_top)181 Status GetElementBorder(
182     const std::string& frame,
183     WebView* web_view,
184     const std::string& element_id,
185     int* border_left,
186     int* border_top) {
187   std::string border_left_str;
188   Status status = GetElementEffectiveStyle(
189       frame, web_view, element_id, "border-left-width", &border_left_str);
190   if (status.IsError())
191     return status;
192   std::string border_top_str;
193   status = GetElementEffectiveStyle(
194       frame, web_view, element_id, "border-top-width", &border_top_str);
195   if (status.IsError())
196     return status;
197   int border_left_tmp = -1;
198   int border_top_tmp = -1;
199   base::StringToInt(border_left_str, &border_left_tmp);
200   base::StringToInt(border_top_str, &border_top_tmp);
201   if (border_left_tmp == -1 || border_top_tmp == -1)
202     return Status(kUnknownError, "failed to get border width of element");
203   *border_left = border_left_tmp;
204   *border_top = border_top_tmp;
205   return Status(kOk);
206 }
207 
208 }  // namespace
209 
CreateElement(const std::string & element_id)210 base::DictionaryValue* CreateElement(const std::string& element_id) {
211   base::DictionaryValue* element = new base::DictionaryValue();
212   element->SetString(kElementKey, element_id);
213   return element;
214 }
215 
CreateValueFrom(const WebPoint & point)216 base::Value* CreateValueFrom(const WebPoint& point) {
217   base::DictionaryValue* dict = new base::DictionaryValue();
218   dict->SetInteger("x", point.x);
219   dict->SetInteger("y", point.y);
220   return dict;
221 }
222 
FindElement(int interval_ms,bool only_one,const std::string * root_element_id,Session * session,WebView * web_view,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)223 Status FindElement(
224     int interval_ms,
225     bool only_one,
226     const std::string* root_element_id,
227     Session* session,
228     WebView* web_view,
229     const base::DictionaryValue& params,
230     scoped_ptr<base::Value>* value) {
231   std::string strategy;
232   if (!params.GetString("using", &strategy))
233     return Status(kUnknownError, "'using' must be a string");
234   std::string target;
235   if (!params.GetString("value", &target))
236     return Status(kUnknownError, "'value' must be a string");
237 
238   std::string script;
239   if (only_one)
240     script = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENT);
241   else
242     script = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENTS);
243   scoped_ptr<base::DictionaryValue> locator(new base::DictionaryValue());
244   locator->SetString(strategy, target);
245   base::ListValue arguments;
246   arguments.Append(locator.release());
247   if (root_element_id)
248     arguments.Append(CreateElement(*root_element_id));
249 
250   base::TimeTicks start_time = base::TimeTicks::Now();
251   while (true) {
252     scoped_ptr<base::Value> temp;
253     Status status = web_view->CallFunction(
254         session->GetCurrentFrameId(), script, arguments, &temp);
255     if (status.IsError())
256       return status;
257 
258     if (!temp->IsType(base::Value::TYPE_NULL)) {
259       if (only_one) {
260         value->reset(temp.release());
261         return Status(kOk);
262       } else {
263         base::ListValue* result;
264         if (!temp->GetAsList(&result))
265           return Status(kUnknownError, "script returns unexpected result");
266 
267         if (result->GetSize() > 0U) {
268           value->reset(temp.release());
269           return Status(kOk);
270         }
271       }
272     }
273 
274     if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
275       if (only_one) {
276         return Status(kNoSuchElement);
277       } else {
278         value->reset(new base::ListValue());
279         return Status(kOk);
280       }
281     }
282     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(interval_ms));
283   }
284 
285   return Status(kUnknownError);
286 }
287 
GetActiveElement(Session * session,WebView * web_view,scoped_ptr<base::Value> * value)288 Status GetActiveElement(
289     Session* session,
290     WebView* web_view,
291     scoped_ptr<base::Value>* value) {
292   base::ListValue args;
293   return web_view->CallFunction(
294       session->GetCurrentFrameId(),
295       "function() { return document.activeElement || document.body }",
296       args,
297       value);
298 }
299 
IsElementFocused(Session * session,WebView * web_view,const std::string & element_id,bool * is_focused)300 Status IsElementFocused(
301     Session* session,
302     WebView* web_view,
303     const std::string& element_id,
304     bool* is_focused) {
305   scoped_ptr<base::Value> result;
306   Status status = GetActiveElement(session, web_view, &result);
307   if (status.IsError())
308     return status;
309   scoped_ptr<base::Value> element_dict(CreateElement(element_id));
310   *is_focused = result->Equals(element_dict.get());
311   return Status(kOk);
312 }
313 
GetElementAttribute(Session * session,WebView * web_view,const std::string & element_id,const std::string & attribute_name,scoped_ptr<base::Value> * value)314 Status GetElementAttribute(
315     Session* session,
316     WebView* web_view,
317     const std::string& element_id,
318     const std::string& attribute_name,
319     scoped_ptr<base::Value>* value) {
320   base::ListValue args;
321   args.Append(CreateElement(element_id));
322   args.AppendString(attribute_name);
323   return CallAtomsJs(
324       session->GetCurrentFrameId(), web_view, webdriver::atoms::GET_ATTRIBUTE,
325       args, value);
326 }
327 
IsElementAttributeEqualToIgnoreCase(Session * session,WebView * web_view,const std::string & element_id,const std::string & attribute_name,const std::string & attribute_value,bool * is_equal)328 Status IsElementAttributeEqualToIgnoreCase(
329     Session* session,
330     WebView* web_view,
331     const std::string& element_id,
332     const std::string& attribute_name,
333     const std::string& attribute_value,
334     bool* is_equal) {
335   scoped_ptr<base::Value> result;
336   Status status = GetElementAttribute(
337       session, web_view, element_id, attribute_name, &result);
338   if (status.IsError())
339     return status;
340   std::string actual_value;
341   if (result->GetAsString(&actual_value))
342     *is_equal = LowerCaseEqualsASCII(actual_value, attribute_value.c_str());
343   else
344     *is_equal = false;
345   return status;
346 }
347 
GetElementClickableLocation(Session * session,WebView * web_view,const std::string & element_id,WebPoint * location)348 Status GetElementClickableLocation(
349     Session* session,
350     WebView* web_view,
351     const std::string& element_id,
352     WebPoint* location) {
353   std::string tag_name;
354   Status status = GetElementTagName(session, web_view, element_id, &tag_name);
355   if (status.IsError())
356     return status;
357   std::string target_element_id = element_id;
358   if (tag_name == "area") {
359     // Scroll the image into view instead of the area.
360     const char* kGetImageElementForArea =
361         "function (element) {"
362         "  var map = element.parentElement;"
363         "  if (map.tagName.toLowerCase() != 'map')"
364         "    throw new Error('the area is not within a map');"
365         "  var mapName = map.getAttribute('name');"
366         "  if (mapName == null)"
367         "    throw new Error ('area\\'s parent map must have a name');"
368         "  mapName = '#' + mapName.toLowerCase();"
369         "  var images = document.getElementsByTagName('img');"
370         "  for (var i = 0; i < images.length; i++) {"
371         "    if (images[i].useMap.toLowerCase() == mapName)"
372         "      return images[i];"
373         "  }"
374         "  throw new Error('no img is found for the area');"
375         "}";
376     base::ListValue args;
377     args.Append(CreateElement(element_id));
378     scoped_ptr<base::Value> result;
379     status = web_view->CallFunction(
380         session->GetCurrentFrameId(), kGetImageElementForArea, args, &result);
381     if (status.IsError())
382       return status;
383     const base::DictionaryValue* element_dict;
384     if (!result->GetAsDictionary(&element_dict) ||
385         !element_dict->GetString(kElementKey, &target_element_id))
386       return Status(kUnknownError, "no element reference returned by script");
387   }
388   bool is_displayed = false;
389   status = IsElementDisplayed(
390       session, web_view, target_element_id, true, &is_displayed);
391   if (status.IsError())
392     return status;
393   if (!is_displayed)
394     return Status(kElementNotVisible);
395 
396   WebRect rect;
397   status = GetElementRegion(session, web_view, element_id, &rect);
398   if (status.IsError())
399     return status;
400 
401   std::string tmp_element_id = element_id;
402   int build_no = session->chrome->GetBrowserInfo()->build_no;
403   if (tag_name == "area" && build_no < 1799 && build_no >= 1666) {
404     // This is to skip clickable verification for <area>.
405     // The problem is caused by document.ElementFromPoint(crbug.com/338601).
406     // It was introduced by blink r159012, which rolled into chromium r227489.
407     // And it was fixed in blink r165426, which rolled into chromium r245994.
408     // TODO(stgao): Revert after 33 is not supported.
409     tmp_element_id = std::string();
410   }
411 
412   status = ScrollElementRegionIntoView(
413       session, web_view, target_element_id, rect,
414       true /* center */, tmp_element_id, location);
415   if (status.IsError())
416     return status;
417   location->Offset(rect.Width() / 2, rect.Height() / 2);
418   return Status(kOk);
419 }
420 
GetElementEffectiveStyle(Session * session,WebView * web_view,const std::string & element_id,const std::string & property_name,std::string * property_value)421 Status GetElementEffectiveStyle(
422     Session* session,
423     WebView* web_view,
424     const std::string& element_id,
425     const std::string& property_name,
426     std::string* property_value) {
427   return GetElementEffectiveStyle(session->GetCurrentFrameId(), web_view,
428                                   element_id, property_name, property_value);
429 }
430 
GetElementRegion(Session * session,WebView * web_view,const std::string & element_id,WebRect * rect)431 Status GetElementRegion(
432     Session* session,
433     WebView* web_view,
434     const std::string& element_id,
435     WebRect* rect) {
436   base::ListValue args;
437   args.Append(CreateElement(element_id));
438   scoped_ptr<base::Value> result;
439   Status status = web_view->CallFunction(
440       session->GetCurrentFrameId(), kGetElementRegionScript, args, &result);
441   if (status.IsError())
442     return status;
443   if (!ParseFromValue(result.get(), rect)) {
444     return Status(kUnknownError,
445                   "failed to parse value of getElementRegion");
446   }
447   return Status(kOk);
448 }
449 
GetElementTagName(Session * session,WebView * web_view,const std::string & element_id,std::string * name)450 Status GetElementTagName(
451     Session* session,
452     WebView* web_view,
453     const std::string& element_id,
454     std::string* name) {
455   base::ListValue args;
456   args.Append(CreateElement(element_id));
457   scoped_ptr<base::Value> result;
458   Status status = web_view->CallFunction(
459       session->GetCurrentFrameId(),
460       "function(elem) { return elem.tagName.toLowerCase(); }",
461       args, &result);
462   if (status.IsError())
463     return status;
464   if (!result->GetAsString(name))
465     return Status(kUnknownError, "failed to get element tag name");
466   return Status(kOk);
467 }
468 
GetElementSize(Session * session,WebView * web_view,const std::string & element_id,WebSize * size)469 Status GetElementSize(
470     Session* session,
471     WebView* web_view,
472     const std::string& element_id,
473     WebSize* size) {
474   base::ListValue args;
475   args.Append(CreateElement(element_id));
476   scoped_ptr<base::Value> result;
477   Status status = CallAtomsJs(
478       session->GetCurrentFrameId(), web_view, webdriver::atoms::GET_SIZE,
479       args, &result);
480   if (status.IsError())
481     return status;
482   if (!ParseFromValue(result.get(), size))
483     return Status(kUnknownError, "failed to parse value of GET_SIZE");
484   return Status(kOk);
485 }
486 
IsElementDisplayed(Session * session,WebView * web_view,const std::string & element_id,bool ignore_opacity,bool * is_displayed)487 Status IsElementDisplayed(
488     Session* session,
489     WebView* web_view,
490     const std::string& element_id,
491     bool ignore_opacity,
492     bool* is_displayed) {
493   base::ListValue args;
494   args.Append(CreateElement(element_id));
495   args.AppendBoolean(ignore_opacity);
496   scoped_ptr<base::Value> result;
497   Status status = CallAtomsJs(
498       session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_DISPLAYED,
499       args, &result);
500   if (status.IsError())
501     return status;
502   if (!result->GetAsBoolean(is_displayed))
503     return Status(kUnknownError, "IS_DISPLAYED should return a boolean value");
504   return Status(kOk);
505 }
506 
IsElementEnabled(Session * session,WebView * web_view,const std::string & element_id,bool * is_enabled)507 Status IsElementEnabled(
508     Session* session,
509     WebView* web_view,
510     const std::string& element_id,
511     bool* is_enabled) {
512   base::ListValue args;
513   args.Append(CreateElement(element_id));
514   scoped_ptr<base::Value> result;
515   Status status = CallAtomsJs(
516       session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_ENABLED,
517       args, &result);
518   if (status.IsError())
519     return status;
520   if (!result->GetAsBoolean(is_enabled))
521     return Status(kUnknownError, "IS_ENABLED should return a boolean value");
522   return Status(kOk);
523 }
524 
IsOptionElementSelected(Session * session,WebView * web_view,const std::string & element_id,bool * is_selected)525 Status IsOptionElementSelected(
526     Session* session,
527     WebView* web_view,
528     const std::string& element_id,
529     bool* is_selected) {
530   base::ListValue args;
531   args.Append(CreateElement(element_id));
532   scoped_ptr<base::Value> result;
533   Status status = CallAtomsJs(
534       session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_SELECTED,
535       args, &result);
536   if (status.IsError())
537     return status;
538   if (!result->GetAsBoolean(is_selected))
539     return Status(kUnknownError, "IS_SELECTED should return a boolean value");
540   return Status(kOk);
541 }
542 
IsOptionElementTogglable(Session * session,WebView * web_view,const std::string & element_id,bool * is_togglable)543 Status IsOptionElementTogglable(
544     Session* session,
545     WebView* web_view,
546     const std::string& element_id,
547     bool* is_togglable) {
548   base::ListValue args;
549   args.Append(CreateElement(element_id));
550   scoped_ptr<base::Value> result;
551   Status status = web_view->CallFunction(
552       session->GetCurrentFrameId(), kIsOptionElementToggleableScript,
553       args, &result);
554   if (status.IsError())
555     return status;
556   if (!result->GetAsBoolean(is_togglable))
557     return Status(kUnknownError, "failed check if option togglable or not");
558   return Status(kOk);
559 }
560 
SetOptionElementSelected(Session * session,WebView * web_view,const std::string & element_id,bool selected)561 Status SetOptionElementSelected(
562     Session* session,
563     WebView* web_view,
564     const std::string& element_id,
565     bool selected) {
566   // TODO(171034): need to fix throwing error if an alert is triggered.
567   base::ListValue args;
568   args.Append(CreateElement(element_id));
569   args.AppendBoolean(selected);
570   scoped_ptr<base::Value> result;
571   return CallAtomsJs(
572       session->GetCurrentFrameId(), web_view, webdriver::atoms::CLICK,
573       args, &result);
574 }
575 
ToggleOptionElement(Session * session,WebView * web_view,const std::string & element_id)576 Status ToggleOptionElement(
577     Session* session,
578     WebView* web_view,
579     const std::string& element_id) {
580   bool is_selected;
581   Status status = IsOptionElementSelected(
582       session, web_view, element_id, &is_selected);
583   if (status.IsError())
584     return status;
585   return SetOptionElementSelected(session, web_view, element_id, !is_selected);
586 }
587 
ScrollElementIntoView(Session * session,WebView * web_view,const std::string & id,WebPoint * location)588 Status ScrollElementIntoView(
589     Session* session,
590     WebView* web_view,
591     const std::string& id,
592     WebPoint* location) {
593   WebSize size;
594   Status status = GetElementSize(session, web_view, id, &size);
595   if (status.IsError())
596     return status;
597   return ScrollElementRegionIntoView(
598       session, web_view, id, WebRect(WebPoint(0, 0), size),
599       false /* center */, std::string(), location);
600 }
601 
ScrollElementRegionIntoView(Session * session,WebView * web_view,const std::string & element_id,const WebRect & region,bool center,const std::string & clickable_element_id,WebPoint * location)602 Status ScrollElementRegionIntoView(
603     Session* session,
604     WebView* web_view,
605     const std::string& element_id,
606     const WebRect& region,
607     bool center,
608     const std::string& clickable_element_id,
609     WebPoint* location) {
610   WebPoint region_offset = region.origin;
611   WebSize region_size = region.size;
612   Status status = ScrollElementRegionIntoViewHelper(
613       session->GetCurrentFrameId(), web_view, element_id, region,
614       center, clickable_element_id, &region_offset);
615   if (status.IsError())
616     return status;
617   const char* kFindSubFrameScript =
618       "function(xpath) {"
619       "  return document.evaluate(xpath, document, null,"
620       "      XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
621       "}";
622   for (std::list<FrameInfo>::reverse_iterator rit = session->frames.rbegin();
623        rit != session->frames.rend(); ++rit) {
624     base::ListValue args;
625     args.AppendString(
626         base::StringPrintf("//*[@cd_frame_id_ = '%s']",
627                            rit->chromedriver_frame_id.c_str()));
628     scoped_ptr<base::Value> result;
629     status = web_view->CallFunction(
630         rit->parent_frame_id, kFindSubFrameScript, args, &result);
631     if (status.IsError())
632       return status;
633     const base::DictionaryValue* element_dict;
634     if (!result->GetAsDictionary(&element_dict))
635       return Status(kUnknownError, "no element reference returned by script");
636     std::string frame_element_id;
637     if (!element_dict->GetString(kElementKey, &frame_element_id))
638       return Status(kUnknownError, "failed to locate a sub frame");
639 
640     // Modify |region_offset| by the frame's border.
641     int border_left = -1;
642     int border_top = -1;
643     status = GetElementBorder(
644         rit->parent_frame_id, web_view, frame_element_id,
645         &border_left, &border_top);
646     if (status.IsError())
647       return status;
648     region_offset.Offset(border_left, border_top);
649 
650     status = ScrollElementRegionIntoViewHelper(
651         rit->parent_frame_id, web_view, frame_element_id,
652         WebRect(region_offset, region_size),
653         center, frame_element_id, &region_offset);
654     if (status.IsError())
655       return status;
656   }
657   *location = region_offset;
658   return Status(kOk);
659 }
660