blob: 4f13ca5b789873920d3d20f621836be084137f65 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/chromedriver/element_util.h"
#include <utility>
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/test/chromedriver/basic_types.h"
#include "chrome/test/chromedriver/chrome/browser_info.h"
#include "chrome/test/chromedriver/chrome/chrome.h"
#include "chrome/test/chromedriver/chrome/js.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/chrome/web_view.h"
#include "chrome/test/chromedriver/session.h"
#include "third_party/webdriver/atoms.h"
namespace {
const char kElementKey[] = "ELEMENT";
const char kElementKeyW3C[] = "element-6066-11e4-a52e-4f735466cecf";
bool ParseFromValue(base::Value* value, WebPoint* point) {
base::DictionaryValue* dict_value;
if (!value->GetAsDictionary(&dict_value))
return false;
double x = 0;
double y = 0;
if (!dict_value->GetDouble("x", &x) ||
!dict_value->GetDouble("y", &y))
return false;
point->x = static_cast<int>(x);
point->y = static_cast<int>(y);
return true;
}
bool ParseFromValue(base::Value* value, WebSize* size) {
base::DictionaryValue* dict_value;
if (!value->GetAsDictionary(&dict_value))
return false;
double width = 0;
double height = 0;
if (!dict_value->GetDouble("width", &width) ||
!dict_value->GetDouble("height", &height))
return false;
size->width = static_cast<int>(width);
size->height = static_cast<int>(height);
return true;
}
bool ParseFromValue(base::Value* value, WebRect* rect) {
base::DictionaryValue* dict_value;
if (!value->GetAsDictionary(&dict_value))
return false;
double x = 0;
double y = 0;
double width = 0;
double height = 0;
if (!dict_value->GetDouble("left", &x) ||
!dict_value->GetDouble("top", &y) ||
!dict_value->GetDouble("width", &width) ||
!dict_value->GetDouble("height", &height))
return false;
rect->origin.x = static_cast<int>(x);
rect->origin.y = static_cast<int>(y);
rect->size.width = static_cast<int>(width);
rect->size.height = static_cast<int>(height);
return true;
}
std::unique_ptr<base::DictionaryValue> CreateValueFrom(const WebRect& rect) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetInteger("left", rect.X());
dict->SetInteger("top", rect.Y());
dict->SetInteger("width", rect.Width());
dict->SetInteger("height", rect.Height());
return dict;
}
Status CallAtomsJs(const std::string& frame,
WebView* web_view,
const char* const* atom_function,
const base::ListValue& args,
std::unique_ptr<base::Value>* result) {
return web_view->CallFunction(
frame, webdriver::atoms::asString(atom_function), args, result);
}
Status VerifyElementClickable(
const std::string& frame,
WebView* web_view,
const std::string& element_id,
const WebPoint& location) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
args.Append(CreateValueFrom(location));
std::unique_ptr<base::Value> result;
status = CallAtomsJs(
frame, web_view, webdriver::atoms::IS_ELEMENT_CLICKABLE,
args, &result);
if (status.IsError())
return status;
base::DictionaryValue* dict;
bool is_clickable = false;
if (!result->GetAsDictionary(&dict) ||
!dict->GetBoolean("clickable", &is_clickable)) {
return Status(kUnknownError,
"failed to parse value of IS_ELEMENT_CLICKABLE");
}
if (!is_clickable) {
std::string message;
if (!dict->GetString("message", &message))
message = "element click intercepted";
return Status(kElementClickIntercepted, message);
}
return Status(kOk);
}
Status 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) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
WebPoint tmp_location = *location;
base::ListValue args;
args.Append(CreateElement(element_id));
args.AppendBoolean(center);
args.Append(CreateValueFrom(region));
std::unique_ptr<base::Value> result;
status = web_view->CallFunction(
frame, webdriver::atoms::asString(webdriver::atoms::GET_LOCATION_IN_VIEW),
args, &result);
if (status.IsError())
return status;
if (!ParseFromValue(result.get(), &tmp_location)) {
return Status(kUnknownError,
"failed to parse value of GET_LOCATION_IN_VIEW");
}
if (!clickable_element_id.empty()) {
WebPoint middle = tmp_location;
middle.Offset(region.Width() / 2, region.Height() / 2);
status = VerifyElementClickable(
frame, web_view, clickable_element_id, middle);
if (status.code() == kUnknownError &&
status.message().find("is not clickable") != std::string::npos) {
// Clicking at the target location isn't reaching the target element.
// One possible cause is a scroll event handler has shifted the element.
// Try again to get the updated location of the target element.
status = web_view->CallFunction(
frame,
webdriver::atoms::asString(webdriver::atoms::GET_LOCATION_IN_VIEW),
args, &result);
if (status.IsError())
return status;
if (!ParseFromValue(result.get(), &tmp_location)) {
return Status(kUnknownError,
"failed to parse value of GET_LOCATION_IN_VIEW");
}
middle = tmp_location;
middle.Offset(region.Width() / 2, region.Height() / 2);
status =
VerifyElementClickable(frame, web_view, clickable_element_id, middle);
}
if (status.IsError())
return status;
}
*location = tmp_location;
return Status(kOk);
}
Status GetElementEffectiveStyle(
const std::string& frame,
WebView* web_view,
const std::string& element_id,
const std::string& property,
std::string* value) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
args.AppendString(property);
std::unique_ptr<base::Value> result;
status = web_view->CallFunction(
frame, webdriver::atoms::asString(webdriver::atoms::GET_EFFECTIVE_STYLE),
args, &result);
if (status.IsError())
return status;
if (!result->GetAsString(value)) {
return Status(kUnknownError,
"failed to parse value of GET_EFFECTIVE_STYLE");
}
return Status(kOk);
}
Status GetElementBorder(
const std::string& frame,
WebView* web_view,
const std::string& element_id,
int* border_left,
int* border_top) {
std::string border_left_str;
Status status = GetElementEffectiveStyle(
frame, web_view, element_id, "border-left-width", &border_left_str);
if (status.IsError())
return status;
std::string border_top_str;
status = GetElementEffectiveStyle(
frame, web_view, element_id, "border-top-width", &border_top_str);
if (status.IsError())
return status;
int border_left_tmp = -1;
int border_top_tmp = -1;
base::StringToInt(border_left_str, &border_left_tmp);
base::StringToInt(border_top_str, &border_top_tmp);
if (border_left_tmp == -1 || border_top_tmp == -1)
return Status(kUnknownError, "failed to get border width of element");
std::string padding_left_str;
status = GetElementEffectiveStyle(frame, web_view, element_id, "padding-left",
&padding_left_str);
int padding_left = 0;
if (status.IsOk())
base::StringToInt(padding_left_str, &padding_left);
std::string padding_top_str;
status = GetElementEffectiveStyle(frame, web_view, element_id, "padding-top",
&padding_top_str);
int padding_top = 0;
if (status.IsOk())
base::StringToInt(padding_top_str, &padding_top);
*border_left = border_left_tmp + padding_left;
*border_top = border_top_tmp + padding_top;
return Status(kOk);
}
Status GetElementLocationInViewCenterHelper(const std::string& frame,
WebView* web_view,
const std::string& element_id,
bool center,
WebPoint* location) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
args.AppendBoolean(center);
std::unique_ptr<base::Value> result;
status =
web_view->CallFunction(frame, kGetElementLocationScript, args, &result);
if (status.IsError())
return status;
if (!ParseFromValue(result.get(), location)) {
return Status(kUnknownError,
"failed to parse value of getElementLocationInViewCenter");
}
return Status(kOk);
}
} // namespace
std::string GetElementKey() {
Session* session = GetThreadLocalSession();
if (session && session->w3c_compliant)
return kElementKeyW3C;
else
return kElementKey;
}
// example of element_id - d9cf1666-0066-4c07-bb86-03edcbab6680
// should contain only 0-9 or a-f
// format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Status CheckElement(const std::string& element_id) {
Session* session = GetThreadLocalSession();
if (session && session->w3c_compliant) {
if (element_id.length()!=36)
return Status(kNoSuchElement, "Element_id length is invalid");
for (std::string::size_type i=0; i<element_id.length(); ++i) {
if (i==8 || i == 13 || i == 18 || i == 23) {
if (element_id[i] != '-')
return Status(kNoSuchElement, "Element_id format is invalid");
} else {
if (!(element_id[i] >='0' && element_id[i] <= '9') &&
!(element_id[i] >='a' && element_id[i] <= 'f'))
return Status(kNoSuchElement,
"Element_id contains invalid letter on position: " +
std::to_string(i) );
}
}
}
return Status(kOk);
}
std::unique_ptr<base::DictionaryValue> CreateElement(
const std::string& element_id) {
std::unique_ptr<base::DictionaryValue> element(new base::DictionaryValue());
element->SetString(GetElementKey(), element_id);
return element;
}
std::unique_ptr<base::DictionaryValue> CreateValueFrom(const WebPoint& point) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetInteger("x", point.x);
dict->SetInteger("y", point.y);
return dict;
}
Status FindElement(int interval_ms,
bool only_one,
const std::string* root_element_id,
Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::string strategy;
if (!params.GetString("using", &strategy))
return Status(kInvalidArgument, "'using' must be a string");
if (session->w3c_compliant &&
strategy != "css selector" &&
strategy != "link text" &&
strategy != "partial link text" &&
strategy != "tag name" &&
strategy != "xpath")
return Status(kInvalidArgument, "invalid locator");
std::string target;
if (!params.GetString("value", &target))
return Status(kInvalidArgument, "'value' must be a string");
std::string script;
if (only_one)
script = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENT);
else
script = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENTS);
std::unique_ptr<base::DictionaryValue> locator(new base::DictionaryValue());
locator->SetString(strategy, target);
base::ListValue arguments;
arguments.Append(std::move(locator));
if (root_element_id)
arguments.Append(CreateElement(*root_element_id));
base::TimeTicks start_time = base::TimeTicks::Now();
int context_retry = 0;
while (true) {
std::unique_ptr<base::Value> temp;
Status status = web_view->CallFunction(
session->GetCurrentFrameId(), script, arguments, &temp);
// A "Cannot find context" error can occur due to transition from in-process
// iFrame to OOPIF. Retry a couple of times.
if (status.IsError() &&
(status.message().find("Cannot find context") == std::string::npos ||
++context_retry > 2)) {
return status;
}
if (temp && !temp->is_none()) {
if (only_one) {
*value = std::move(temp);
return Status(kOk);
} else {
base::ListValue* result;
if (!temp->GetAsList(&result))
return Status(kUnknownError, "script returns unexpected result");
if (result->GetSize() > 0U) {
*value = std::move(temp);
return Status(kOk);
}
}
}
if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
if (only_one) {
return Status(kNoSuchElement, "Unable to locate element: {\"method\":\""
+ strategy + "\",\"selector\":\"" + target + "\"}");
} else {
value->reset(new base::ListValue());
return Status(kOk);
}
}
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(interval_ms));
}
return Status(kUnknownError);
}
Status GetActiveElement(Session* session,
WebView* web_view,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
Status status = web_view->CallFunction(
session->GetCurrentFrameId(),
"function() { return document.activeElement || document.body }", args,
value);
if (status.IsError()) {
return status;
}
if (value->get()->is_none()) {
return Status(kNoSuchElement);
}
return status;
}
Status IsElementFocused(
Session* session,
WebView* web_view,
const std::string& element_id,
bool* is_focused) {
std::unique_ptr<base::Value> result;
Status status = GetActiveElement(session, web_view, &result);
if (status.IsError())
return status;
std::unique_ptr<base::Value> element_dict(CreateElement(element_id));
*is_focused = result->Equals(element_dict.get());
return Status(kOk);
}
Status IsDocumentTypeXml(
Session* session,
WebView* web_view,
bool* is_xml_document) {
std::unique_ptr<base::Value> contentType;
Status status = web_view->EvaluateScript(
session->GetCurrentFrameId(),
"document.contentType", &contentType);
if (status.IsError())
return status;
if (base::LowerCaseEqualsASCII(contentType->GetString(),
"text/xml"))
*is_xml_document = true;
else
*is_xml_document = false;
return Status(kOk);
}
Status GetElementAttribute(Session* session,
WebView* web_view,
const std::string& element_id,
const std::string& attribute_name,
std::unique_ptr<base::Value>* value) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
args.AppendString(attribute_name);
return CallAtomsJs(
session->GetCurrentFrameId(), web_view, webdriver::atoms::GET_ATTRIBUTE,
args, value);
}
Status IsElementAttributeEqualToIgnoreCase(
Session* session,
WebView* web_view,
const std::string& element_id,
const std::string& attribute_name,
const std::string& attribute_value,
bool* is_equal) {
std::unique_ptr<base::Value> result;
Status status = GetElementAttribute(
session, web_view, element_id, attribute_name, &result);
if (status.IsError())
return status;
std::string actual_value;
if (result->GetAsString(&actual_value)) {
*is_equal = base::LowerCaseEqualsASCII(actual_value, attribute_value);
} else {
*is_equal = false;
}
return status;
}
Status GetElementClickableLocation(
Session* session,
WebView* web_view,
const std::string& element_id,
WebPoint* location) {
std::string tag_name;
Status status = GetElementTagName(session, web_view, element_id, &tag_name);
if (status.IsError())
return status;
std::string target_element_id = element_id;
if (tag_name == "area") {
// Scroll the image into view instead of the area.
const char kGetImageElementForArea[] =
"function (element) {"
" var map = element.parentElement;"
" if (map.tagName.toLowerCase() != 'map')"
" throw new Error('the area is not within a map');"
" var mapName = map.getAttribute('name');"
" if (mapName == null)"
" throw new Error ('area\\'s parent map must have a name');"
" mapName = '#' + mapName.toLowerCase();"
" var images = document.getElementsByTagName('img');"
" for (var i = 0; i < images.length; i++) {"
" if (images[i].useMap.toLowerCase() == mapName)"
" return images[i];"
" }"
" throw new Error('no img is found for the area');"
"}";
status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
std::unique_ptr<base::Value> result;
status = web_view->CallFunction(
session->GetCurrentFrameId(), kGetImageElementForArea, args, &result);
if (status.IsError())
return status;
const base::DictionaryValue* element_dict;
if (!result->GetAsDictionary(&element_dict) ||
!element_dict->GetString(GetElementKey(), &target_element_id))
return Status(kUnknownError, "no element reference returned by script");
}
bool is_displayed = false;
base::TimeTicks start_time = base::TimeTicks::Now();
while (true) {
Status status = IsElementDisplayed(
session, web_view, target_element_id, true, &is_displayed);
if (status.IsError())
return status;
if (is_displayed)
break;
if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
return Status(kElementNotVisible);
}
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
}
WebRect rect;
status = GetElementRegion(session, web_view, element_id, &rect);
if (status.IsError())
return status;
status = ScrollElementRegionIntoView(
session, web_view, target_element_id, rect,
true /* center */, element_id, location);
if (status.IsError())
return status;
location->Offset(rect.Width() / 2, rect.Height() / 2);
return Status(kOk);
}
Status GetElementEffectiveStyle(
Session* session,
WebView* web_view,
const std::string& element_id,
const std::string& property_name,
std::string* property_value) {
return GetElementEffectiveStyle(session->GetCurrentFrameId(), web_view,
element_id, property_name, property_value);
}
Status GetElementRegion(
Session* session,
WebView* web_view,
const std::string& element_id,
WebRect* rect) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
std::unique_ptr<base::Value> result;
status = web_view->CallFunction(
session->GetCurrentFrameId(), kGetElementRegionScript, args, &result);
if (status.IsError())
return status;
if (!ParseFromValue(result.get(), rect)) {
return Status(kUnknownError,
"failed to parse value of getElementRegion");
}
return Status(kOk);
}
Status GetElementTagName(
Session* session,
WebView* web_view,
const std::string& element_id,
std::string* name) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
std::unique_ptr<base::Value> result;
status = web_view->CallFunction(
session->GetCurrentFrameId(),
"function(elem) { return elem.tagName.toLowerCase(); }",
args, &result);
if (status.IsError())
return status;
if (!result->GetAsString(name))
return Status(kUnknownError, "failed to get element tag name");
return Status(kOk);
}
Status GetElementSize(
Session* session,
WebView* web_view,
const std::string& element_id,
WebSize* size) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
std::unique_ptr<base::Value> result;
status = CallAtomsJs(
session->GetCurrentFrameId(), web_view, webdriver::atoms::GET_SIZE,
args, &result);
if (status.IsError())
return status;
if (!ParseFromValue(result.get(), size))
return Status(kUnknownError, "failed to parse value of GET_SIZE");
return Status(kOk);
}
Status IsElementDisplayed(
Session* session,
WebView* web_view,
const std::string& element_id,
bool ignore_opacity,
bool* is_displayed) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
args.AppendBoolean(ignore_opacity);
std::unique_ptr<base::Value> result;
status = CallAtomsJs(
session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_DISPLAYED,
args, &result);
if (status.IsError())
return status;
if (!result->GetAsBoolean(is_displayed))
return Status(kUnknownError, "IS_DISPLAYED should return a boolean value");
return Status(kOk);
}
Status IsElementEnabled(
Session* session,
WebView* web_view,
const std::string& element_id,
bool* is_enabled) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
std::unique_ptr<base::Value> result;
status = CallAtomsJs(
session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_ENABLED,
args, &result);
if (status.IsError())
return status;
if (!result->GetAsBoolean(is_enabled))
return Status(kUnknownError, "IS_ENABLED should return a boolean value");
return Status(kOk);
}
Status IsOptionElementSelected(
Session* session,
WebView* web_view,
const std::string& element_id,
bool* is_selected) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
std::unique_ptr<base::Value> result;
status = CallAtomsJs(
session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_SELECTED,
args, &result);
if (status.IsError())
return status;
if (!result->GetAsBoolean(is_selected))
return Status(kUnknownError, "IS_SELECTED should return a boolean value");
return Status(kOk);
}
Status IsOptionElementTogglable(
Session* session,
WebView* web_view,
const std::string& element_id,
bool* is_togglable) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
base::ListValue args;
args.Append(CreateElement(element_id));
std::unique_ptr<base::Value> result;
status = web_view->CallFunction(
session->GetCurrentFrameId(), kIsOptionElementToggleableScript,
args, &result);
if (status.IsError())
return status;
if (!result->GetAsBoolean(is_togglable))
return Status(kUnknownError, "failed check if option togglable or not");
return Status(kOk);
}
Status SetOptionElementSelected(
Session* session,
WebView* web_view,
const std::string& element_id,
bool selected) {
Status status = CheckElement(element_id);
if (status.IsError())
return status;
// TODO(171034): need to fix throwing error if an alert is triggered.
base::ListValue args;
args.Append(CreateElement(element_id));
args.AppendBoolean(selected);
std::unique_ptr<base::Value> result;
return CallAtomsJs(
session->GetCurrentFrameId(), web_view, webdriver::atoms::CLICK,
args, &result);
}
Status ToggleOptionElement(
Session* session,
WebView* web_view,
const std::string& element_id) {
bool is_selected;
Status status = IsOptionElementSelected(
session, web_view, element_id, &is_selected);
if (status.IsError())
return status;
return SetOptionElementSelected(session, web_view, element_id, !is_selected);
}
Status ScrollElementIntoView(
Session* session,
WebView* web_view,
const std::string& id,
const WebPoint* offset,
WebPoint* location) {
WebRect region;
Status status = GetElementRegion(session, web_view, id, &region);
if (status.IsError())
return status;
status = ScrollElementRegionIntoView(session, web_view, id, region,
false /* center */, std::string(), location);
if (status.IsError())
return status;
if (offset)
location->Offset(offset->x, offset->y);
else
location->Offset(region.size.width / 2, region.size.height / 2);
return Status(kOk);
}
Status ScrollElementRegionIntoView(
Session* session,
WebView* web_view,
const std::string& element_id,
const WebRect& region,
bool center,
const std::string& clickable_element_id,
WebPoint* location) {
WebPoint region_offset = region.origin;
WebSize region_size = region.size;
Status status = ScrollElementRegionIntoViewHelper(
session->GetCurrentFrameId(), web_view, element_id, region,
center, clickable_element_id, &region_offset);
if (status.IsError())
return status;
const char kFindSubFrameScript[] =
"function(xpath) {"
" return document.evaluate(xpath, document, null,"
" XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
"}";
for (auto rit = session->frames.rbegin(); rit != session->frames.rend();
++rit) {
base::ListValue args;
args.AppendString(
base::StringPrintf("//*[@cd_frame_id_ = '%s']",
rit->chromedriver_frame_id.c_str()));
std::unique_ptr<base::Value> result;
status = web_view->CallFunction(
rit->parent_frame_id, kFindSubFrameScript, args, &result);
if (status.IsError())
return status;
const base::DictionaryValue* element_dict;
if (!result->GetAsDictionary(&element_dict))
return Status(kUnknownError, "no element reference returned by script");
std::string frame_element_id;
if (!element_dict->GetString(GetElementKey(), &frame_element_id))
return Status(kUnknownError, "failed to locate a sub frame");
// Modify |region_offset| by the frame's border.
int border_left = -1;
int border_top = -1;
status = GetElementBorder(
rit->parent_frame_id, web_view, frame_element_id,
&border_left, &border_top);
if (status.IsError())
return status;
region_offset.Offset(border_left, border_top);
status = ScrollElementRegionIntoViewHelper(
rit->parent_frame_id, web_view, frame_element_id,
WebRect(region_offset, region_size),
center, frame_element_id, &region_offset);
if (status.IsError())
return status;
}
*location = region_offset;
return Status(kOk);
}
Status GetElementLocationInViewCenter(Session* session,
WebView* web_view,
const std::string& element_id,
WebPoint* location) {
WebPoint center_location;
Status status = GetElementLocationInViewCenterHelper(
session->GetCurrentFrameId(), web_view, element_id, true,
&center_location);
if (status.IsError())
return status;
const char kFindSubFrameScript[] =
"function(xpath) {"
" return document.evaluate(xpath, document, null,"
" XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
"}";
for (auto rit = session->frames.rbegin(); rit != session->frames.rend();
++rit) {
base::ListValue args;
args.AppendString(base::StringPrintf("//*[@cd_frame_id_ = '%s']",
rit->chromedriver_frame_id.c_str()));
std::unique_ptr<base::Value> result;
status = web_view->CallFunction(rit->parent_frame_id, kFindSubFrameScript,
args, &result);
if (status.IsError())
return status;
const base::DictionaryValue* element_dict;
if (!result->GetAsDictionary(&element_dict))
return Status(kUnknownError, "no element reference returned by script");
std::string frame_element_id;
if (!element_dict->GetString(GetElementKey(), &frame_element_id))
return Status(kUnknownError, "failed to locate a sub frame");
// Modify |center_location| by the frame's border.
int border_left = -1;
int border_top = -1;
status = GetElementBorder(rit->parent_frame_id, web_view, frame_element_id,
&border_left, &border_top);
if (status.IsError())
return status;
center_location.Offset(border_left, border_top);
WebPoint frame_offset;
status = GetElementLocationInViewCenterHelper(
rit->parent_frame_id, web_view, frame_element_id, false, &frame_offset);
if (status.IsError())
return status;
center_location.Offset(frame_offset.x, frame_offset.y);
}
*location = center_location;
return Status(kOk);
}