Path: blob/trunk/cpp/iedriver/CommandHandlers/ClickElementCommandHandler.cpp
2868 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the "License");5// you may not use this file except in compliance with the License.6// You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS,12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13// See the License for the specific language governing permissions and14// limitations under the License.1516#define CLICK_OPTION_EVENT_NAME L"ClickOptionEvent"1718#include "ClickElementCommandHandler.h"19#include "errorcodes.h"20#include "logging.h"21#include "../Browser.h"22#include "../Element.h"23#include "../Generated/atoms.h"24#include "../IECommandExecutor.h"25#include "../InputManager.h"26#include "../Script.h"27#include "../StringUtilities.h"28#include "../WebDriverConstants.h"2930namespace webdriver {3132ClickElementCommandHandler::ClickElementCommandHandler(void) {33}3435ClickElementCommandHandler::~ClickElementCommandHandler(void) {36}3738void ClickElementCommandHandler::ExecuteInternal(const IECommandExecutor& executor,39const ParametersMap& command_parameters,40Response* response) {41ParametersMap::const_iterator id_parameter_iterator = command_parameters.find("id");42if (id_parameter_iterator == command_parameters.end()) {43response->SetErrorResponse(ERROR_INVALID_ARGUMENT, "Missing parameter in URL: id");44return;45} else {46int status_code = WD_SUCCESS;47std::string element_id = id_parameter_iterator->second.asString();4849BrowserHandle browser_wrapper;50status_code = executor.GetCurrentBrowser(&browser_wrapper);51if (status_code != WD_SUCCESS) {52response->SetErrorResponse(status_code, "Unable to get browser");53return;54}5556ElementHandle element_wrapper;57status_code = this->GetElement(executor, element_id, &element_wrapper);58if (status_code == WD_SUCCESS) {59if (this->IsFileUploadElement(element_wrapper)) {60response->SetErrorResponse(ERROR_INVALID_ARGUMENT, "Cannot call click on an <input type='file'> element. Use sendKeys to upload files.");61return;62}63std::string navigation_url = "";64bool reattach_after_click = false;65if (this->IsPossibleNavigation(element_wrapper, &navigation_url)) {66reattach_after_click = browser_wrapper->IsCrossZoneUrl(navigation_url);67}68if (executor.input_manager()->enable_native_events()) {69if (this->IsOptionElement(element_wrapper)) {70std::string option_click_error = "";71status_code = this->ExecuteAtom(executor,72this->GetClickAtom(),73browser_wrapper,74element_wrapper,75&option_click_error);76if (status_code != WD_SUCCESS) {77response->SetErrorResponse(status_code, "Cannot click on option element. " + option_click_error);78return;79}80} else {81Json::Value move_action;82move_action["type"] = "pointerMove";83move_action["origin"] = element_wrapper->ConvertToJson();84move_action["duration"] = 0;8586Json::Value down_action;87down_action["type"] = "pointerDown";88down_action["button"] = 0;8990Json::Value up_action;91up_action["type"] = "pointerUp";92up_action["button"] = 0;9394Json::Value action_array(Json::arrayValue);95action_array.append(move_action);96action_array.append(down_action);97action_array.append(up_action);9899Json::Value parameters_value;100parameters_value["pointerType"] = "mouse";101102Json::Value value;103value["type"] = "pointer";104value["id"] = "click action mouse";105value["parameters"] = parameters_value;106value["actions"] = action_array;107108Json::Value actions(Json::arrayValue);109actions.append(value);110111int double_click_time = ::GetDoubleClickTime();112int milliseconds_since_last_click = (clock() - executor.input_manager()->last_click_time()) * CLOCKS_PER_SEC / 1000;113if (double_click_time - milliseconds_since_last_click > 0) {114::Sleep(double_click_time - milliseconds_since_last_click);115}116117// Scroll the target element into view before executing the action118// sequence.119LocationInfo location = {};120std::vector<LocationInfo> frame_locations;121status_code = element_wrapper->GetLocationOnceScrolledIntoView(executor.input_manager()->scroll_behavior(),122&location,123&frame_locations);124125bool displayed;126status_code = element_wrapper->IsDisplayed(true, &displayed);127if (status_code != WD_SUCCESS || !displayed) {128response->SetErrorResponse(EELEMENTNOTDISPLAYED,129"Element is not displayed");130return;131}132133LocationInfo click_location = {};134long obscuring_element_index = -1;135std::string obscuring_element_description = "";136bool obscured = element_wrapper->IsObscured(&click_location,137&obscuring_element_index,138&obscuring_element_description);139if (obscured) {140std::string error_msg = StringUtilities::Format("Element not clickable at point (%d,%d). Other element would receive the click: %s (elementsFromPoint index %d)",141click_location.x,142click_location.y,143obscuring_element_description.c_str(),144obscuring_element_index);145response->SetErrorResponse(ERROR_ELEMENT_CLICK_INTERCEPTED, error_msg);146return;147}148149if (reattach_after_click) {150browser_wrapper->InitiateBrowserReattach();151}152std::string error_info = "";153IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);154status_code = mutable_executor.input_manager()->PerformInputSequence(browser_wrapper,155actions,156&error_info);157browser_wrapper->set_wait_required(true);158if (status_code != WD_SUCCESS) {159if (status_code == EELEMENTCLICKPOINTNOTSCROLLED) {160// We hard-code the error code here to be "Element not visible"161// to maintain compatibility with previous behavior.162response->SetErrorResponse(ERROR_ELEMENT_NOT_INTERACTABLE, "The point at which the driver is attempting to click on the element was not scrolled into the viewport.");163} else {164response->SetErrorResponse(status_code, "Cannot click on element");165}166return;167}168}169} else {170bool displayed;171status_code = element_wrapper->IsDisplayed(true, &displayed);172if (status_code != WD_SUCCESS) {173response->SetErrorResponse(status_code, "Unable to determine element is displayed");174return;175}176177if (!displayed) {178response->SetErrorResponse(ERROR_ELEMENT_NOT_INTERACTABLE, "Element is not displayed");179return;180}181std::string synthetic_click_error = "";182status_code = this->ExecuteAtom(executor,183this->GetSyntheticClickAtom(),184browser_wrapper,185element_wrapper,186&synthetic_click_error);187if (status_code != WD_SUCCESS) {188// This is a hack. We should change this when we can get proper error189// codes back from the atoms. We'll assume the script failed because190// the element isn't visible.191response->SetErrorResponse(ERROR_ELEMENT_NOT_INTERACTABLE,192"Received a JavaScript error attempting to click on the element using synthetic events. We are assuming this is because the element isn't displayed, but it may be due to other problems with executing JavaScript.");193return;194}195browser_wrapper->set_wait_required(true);196}197} else if (status_code == ENOSUCHELEMENT) {198response->SetErrorResponse(ERROR_NO_SUCH_ELEMENT, "Invalid internal element ID requested: " + element_id);199return;200} else {201response->SetErrorResponse(status_code, "Element is no longer valid");202return;203}204205response->SetSuccessResponse(Json::Value::null);206}207}208209bool ClickElementCommandHandler::IsOptionElement(ElementHandle element_wrapper) {210CComPtr<IHTMLOptionElement> option;211HRESULT(hr) = element_wrapper->element()->QueryInterface<IHTMLOptionElement>(&option);212return SUCCEEDED(hr) && !!option;213}214215std::wstring ClickElementCommandHandler::GetSyntheticClickAtom() {216std::wstring script_source = L"(function() { return function(){" +217atoms::asString(atoms::INPUTS_BIN) +218L"; return webdriver.atoms.inputs.click(arguments[0]);" +219L"};})();";220return script_source;221}222223std::wstring ClickElementCommandHandler::GetClickAtom() {224std::wstring script_source = L"(function() { return (";225script_source += atoms::asString(atoms::CLICK);226script_source += L")})();";227return script_source;228}229230int ClickElementCommandHandler::ExecuteAtom(231const IECommandExecutor& executor,232const std::wstring& atom_script_source,233BrowserHandle browser_wrapper,234ElementHandle element_wrapper,235std::string* error_msg) {236HWND async_executor_handle;237CComPtr<IHTMLDocument2> doc;238browser_wrapper->GetDocument(&doc);239Script script_wrapper(doc, atom_script_source);240Json::Value args(Json::arrayValue);241args.append(element_wrapper->ConvertToJson());242int status_code = script_wrapper.ExecuteAsync(executor,243args,244ASYNC_SCRIPT_EXECUTION_TIMEOUT_IN_MILLISECONDS,245&async_executor_handle);246if (status_code != WD_SUCCESS) {247if (script_wrapper.ResultIsString()) {248std::wstring error = script_wrapper.result().bstrVal;249*error_msg = StringUtilities::ToString(error);250} else {251std::string error = "Executing JavaScript click function returned an";252error.append(" unexpected error, but no error could be returned from");253error.append(" Internet Explorer's JavaScript engine.");254*error_msg = error;255}256}257return status_code;258}259260bool ClickElementCommandHandler::IsFileUploadElement(ElementHandle element) {261CComPtr<IHTMLInputFileElement> file;262element->element()->QueryInterface<IHTMLInputFileElement>(&file);263CComPtr<IHTMLInputElement> input;264element->element()->QueryInterface<IHTMLInputElement>(&input);265CComBSTR element_type;266if (input) {267input->get_type(&element_type);268HRESULT hr = element_type.ToLower();269if (FAILED(hr)) {270LOGHR(WARN, hr) << "Failed converting type attribute of <input> element to lowercase using ToLower() method of BSTR";271}272}273bool is_file_element = (file != NULL) ||274(input != NULL && element_type == L"file");275return is_file_element;276}277278bool ClickElementCommandHandler::IsPossibleNavigation(ElementHandle element_wrapper,279std::string* url) {280CComPtr<IHTMLAnchorElement> anchor;281element_wrapper->element()->QueryInterface<IHTMLAnchorElement>(&anchor);282if (anchor) {283CComVariant href_value;284element_wrapper->GetAttributeValue("href", &href_value);285if (href_value.vt == VT_BSTR) {286std::wstring wide_url = href_value.bstrVal;287CComPtr<IUri> parsed_url;288::CreateUri(wide_url.c_str(), Uri_CREATE_ALLOW_RELATIVE, 0, &parsed_url);289DWORD url_scheme = 0;290parsed_url->GetScheme(&url_scheme);291if (url_scheme == URL_SCHEME_HTTPS || url_scheme == URL_SCHEME_HTTP) {292*url = StringUtilities::ToString(wide_url);293return true;294}295}296}297return false;298}299300} // namespace webdriver301302303