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