[Chromoting] Update size to use inner/outer bounds
[chromium-blink-merge.git] / chrome / test / chromedriver / element_util.cc
blobb5a7b99388e97efadd23fb4255ce29ebdb51065e
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.
5 #include "chrome/test/chromedriver/element_util.h"
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"
22 namespace {
24 const char kElementKey[] = "ELEMENT";
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;
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;
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;
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;
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);
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");
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);
121 return Status(kOk);
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");
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;
155 *location = tmp_location;
156 return Status(kOk);
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");
178 return Status(kOk);
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);
208 } // namespace
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;
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;
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");
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));
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;
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");
267 if (result->GetSize() > 0U) {
268 value->reset(temp.release());
269 return Status(kOk);
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);
282 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(interval_ms));
285 return Status(kUnknownError);
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);
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);
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);
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;
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");
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);
396 WebRect rect;
397 status = GetElementRegion(session, web_view, element_id, &rect);
398 if (status.IsError())
399 return status;
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();
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);
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);
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");
447 return Status(kOk);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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");
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);
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;
657 *location = region_offset;
658 return Status(kOk);