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