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, ®ion_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, ®ion_offset);
636 if (status.IsError())
637 return status;
638 }
639 *location = region_offset;
640 return Status(kOk);
641 }
642