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