blob: 4b4c679c3885696c6d6f7d88c43790fc7f56d3a0 [file] [log] [blame]
// Copyright 2011 Software Freedom Conservancy
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef WEBDRIVER_IE_SENDKEYSCOMMANDHANDLER_H_
#define WEBDRIVER_IE_SENDKEYSCOMMANDHANDLER_H_
#include <ctime>
#include "../Browser.h"
#include "../IECommandHandler.h"
#include "../IECommandExecutor.h"
#include "interactions.h"
#include "logging.h"
const LPCTSTR fileDialogNames[] = {
_T("#32770"),
_T("ComboBoxEx32"),
_T("ComboBox"),
_T("Edit"),
NULL
};
namespace webdriver {
class SendKeysCommandHandler : public IECommandHandler {
public:
struct FileNameData {
HWND main;
HWND hwnd;
DWORD ieProcId;
const wchar_t* text;
};
SendKeysCommandHandler(void) {
}
virtual ~SendKeysCommandHandler(void) {
}
protected:
void ExecuteInternal(const IECommandExecutor& executor,
const LocatorMap& locator_parameters,
const ParametersMap& command_parameters,
Response* response)
{
LocatorMap::const_iterator id_parameter_iterator = locator_parameters.find("id");
ParametersMap::const_iterator value_parameter_iterator = command_parameters.find("value");
if (id_parameter_iterator == locator_parameters.end()) {
response->SetErrorResponse(400, "Missing parameter in URL: id");
return;
} else if (value_parameter_iterator == command_parameters.end()) {
response->SetErrorResponse(400, "Missing parameter: value");
return;
} else {
std::string element_id = id_parameter_iterator->second;
Json::Value key_array = value_parameter_iterator->second;
BrowserHandle browser_wrapper;
int status_code = executor.GetCurrentBrowser(&browser_wrapper);
if (status_code != SUCCESS) {
response->SetErrorResponse(status_code, "Unable to get browser");
return;
}
HWND window_handle = browser_wrapper->GetWindowHandle();
ElementHandle element_wrapper;
status_code = this->GetElement(executor, element_id, &element_wrapper);
if (status_code == SUCCESS) {
bool displayed;
status_code = element_wrapper->IsDisplayed(&displayed);
if (status_code != SUCCESS || !displayed) {
response->SetErrorResponse(EELEMENTNOTDISPLAYED,
"Element is not displayed");
return;
}
if (!element_wrapper->IsEnabled()) {
response->SetErrorResponse(EELEMENTNOTENABLED,
"Element is not enabled");
return;
}
CComQIPtr<IHTMLElement> element(element_wrapper->element());
LocationInfo location = {};
element_wrapper->GetLocationOnceScrolledIntoView(executor.input_manager()->scroll_behavior(), &location);
CComQIPtr<IHTMLInputFileElement> file(element);
CComQIPtr<IHTMLInputElement> input(element);
CComBSTR element_type;
if (input) {
input->get_type(&element_type);
element_type.ToLower();
}
bool is_file_element = (file != NULL) ||
(input != NULL && element_type == L"file");
if (is_file_element) {
std::wstring keys = L"";
for (unsigned int i = 0; i < key_array.size(); ++i ) {
std::string key(key_array[i].asString());
keys.append(CA2W(key.c_str(), CP_UTF8));
}
DWORD ie_process_id;
::GetWindowThreadProcessId(window_handle, &ie_process_id);
HWND top_level_window_handle = browser_wrapper->GetTopLevelWindowHandle();
FileNameData key_data;
key_data.main = top_level_window_handle;
key_data.hwnd = window_handle;
key_data.text = keys.c_str();
key_data.ieProcId = ie_process_id;
unsigned int thread_id;
HANDLE thread_handle = reinterpret_cast<HANDLE>(_beginthreadex(NULL,
0,
&SendKeysCommandHandler::SetFileValue,
reinterpret_cast<void*>(&key_data),
0,
&thread_id));
element->click();
// We're now blocked until the dialog closes.
::CloseHandle(thread_handle);
return;
}
this->VerifyPageHasFocus(browser_wrapper->GetTopLevelWindowHandle(), browser_wrapper->window_handle());
this->WaitUntilElementFocused(element);
std::string null_character = CW2A(L"\uE000", CP_UTF8);
Json::Value value = this->RecreateJsonParameterObject(command_parameters);
value["value"].append(null_character);
value["action"] = "keys";
Json::UInt index = 0;
Json::Value actions(Json::arrayValue);
actions[index] = value;
status_code = executor.input_manager()->PerformInputSequence(browser_wrapper, actions);
//status_code = executor.input_manager()->SendKeystrokes(browser_wrapper, key_array, true);
response->SetSuccessResponse(Json::Value::null);
return;
} else {
response->SetErrorResponse(status_code, "Element is no longer valid");
return;
}
}
}
private:
static unsigned int WINAPI SetFileValue(void *file_data) {
FileNameData* data = reinterpret_cast<FileNameData*>(file_data);
::Sleep(100);
HWND ie_main_window_handle = data->main;
HWND dialog_window_handle = ::GetLastActivePopup(ie_main_window_handle);
int max_wait = 10;
while ((dialog_window_handle == ie_main_window_handle) && --max_wait) {
::Sleep(100);
dialog_window_handle = ::GetLastActivePopup(ie_main_window_handle);
}
if (!dialog_window_handle ||
(dialog_window_handle == ie_main_window_handle)) {
LOG(WARN) << "No dialog directly owned by the top-level window";
// No dialog directly owned by the top-level window.
// Look for a dialog belonging to the same process as
// the IE server window. This isn't perfect, but it's
// all we have for now.
max_wait = 50;
while ((dialog_window_handle == ie_main_window_handle) && --max_wait) {
::Sleep(100);
ProcessWindowInfo process_win_info;
process_win_info.dwProcessId = data->ieProcId;
::EnumWindows(&BrowserFactory::FindDialogWindowForProcess,
reinterpret_cast<LPARAM>(&process_win_info));
if (process_win_info.hwndBrowser != NULL) {
dialog_window_handle = process_win_info.hwndBrowser;
}
}
}
if (!dialog_window_handle ||
(dialog_window_handle == ie_main_window_handle)) {
LOG(WARN) << "No dialog found";
return false;
}
return SendKeysToFileUploadAlert(dialog_window_handle, data->text);
}
static bool SendKeysToFileUploadAlert(HWND dialog_window_handle,
const wchar_t* value) {
HWND edit_field_window_handle = NULL;
int maxWait = 10;
while (!edit_field_window_handle && --maxWait) {
wait(200);
edit_field_window_handle = dialog_window_handle;
for (int i = 1; fileDialogNames[i]; ++i) {
edit_field_window_handle = getChildWindow(edit_field_window_handle,
fileDialogNames[i]);
}
}
if (edit_field_window_handle) {
// Attempt to set the value, looping until we succeed.
const wchar_t* filename = value;
size_t expected = wcslen(filename);
size_t curr = 0;
while (expected != curr) {
::SendMessage(edit_field_window_handle,
WM_SETTEXT,
0,
reinterpret_cast<LPARAM>(filename));
wait(1000);
curr = ::SendMessage(edit_field_window_handle, WM_GETTEXTLENGTH, 0, 0);
}
bool triedToDismiss = false;
for (int i = 0; i < 10000; i++) {
HWND open_window_handle = ::GetDlgItem(dialog_window_handle, IDOK);
if (open_window_handle) {
LRESULT total = 0;
total += ::SendMessage(open_window_handle, WM_LBUTTONDOWN, 0, 0);
total += ::SendMessage(open_window_handle, WM_LBUTTONUP, 0, 0);
if (total == 0) {
triedToDismiss = true;
// Sometimes IE10 doesn't dismiss this dialog after the messages
// are received, even though the messages were processed
// successfully. If not, try again, just in case.
if (!IsWindow(dialog_window_handle)) {
return true;
}
}
wait(200);
} else if (triedToDismiss) {
// Probably just a slow close
return true;
}
}
LOG(ERROR) << "Unable to set value of file input dialog";
return false;
}
LOG(WARN) << "No edit found";
return false;
}
void VerifyPageHasFocus(HWND top_level_window_handle, HWND browser_pane_window_handle) {
DWORD proc;
DWORD thread_id = ::GetWindowThreadProcessId(top_level_window_handle, &proc);
GUITHREADINFO info;
info.cbSize = sizeof(GUITHREADINFO);
::GetGUIThreadInfo(thread_id, &info);
if (info.hwndFocus != browser_pane_window_handle) {
// The focus is on a UI element other than the HTML viewer pane (like
// the address bar, for instance). This has implications for certain
// keystrokes, like backspace. We need to set the focus to the HTML
// viewer pane.
// N.B. The SetFocus() API should *NOT* cause the IE browser window to
// magically appear in the foreground. If that is not true, we will need
// to find some other solution.
LOG(DEBUG) << "Focus is on a UI element other than the HTML viewer pane.";
DWORD current_thread_id = ::GetCurrentThreadId();
::AttachThreadInput(current_thread_id, thread_id, TRUE);
::SetFocus(browser_pane_window_handle);
::AttachThreadInput(current_thread_id, thread_id, FALSE);
}
}
bool WaitUntilElementFocused(IHTMLElement *element) {
// Check we have focused the element.
bool has_focus = false;
CComPtr<IDispatch> dispatch;
element->get_document(&dispatch);
CComQIPtr<IHTMLDocument2> document(dispatch);
// If the element we want is already the focused element, we're done.
CComPtr<IHTMLElement> active_element;
if (document->get_activeElement(&active_element) == S_OK) {
if (active_element.IsEqualObject(element)) {
return true;
}
}
CComQIPtr<IHTMLElement2> element2(element);
element2->focus();
clock_t max_wait = clock() + 1000;
for (int i = clock(); i < max_wait; i = clock()) {
wait(1);
CComPtr<IHTMLElement> active_wait_element;
if (document->get_activeElement(&active_wait_element) == S_OK) {
CComQIPtr<IHTMLElement2> active_wait_element2(active_wait_element);
if (element2.IsEqualObject(active_wait_element2)) {
this->SetInsertionPoint(element);
has_focus = true;
break;
}
}
}
if (!has_focus) {
LOG(WARN) << "We don't have focus on element.";
}
return has_focus;
}
bool SetInsertionPoint(IHTMLElement* element) {
CComPtr<IHTMLTxtRange> range;
CComQIPtr<IHTMLInputTextElement> input_element(element);
if (input_element) {
input_element->createTextRange(&range);
} else {
CComQIPtr<IHTMLTextAreaElement> text_area_element(element);
if (text_area_element) {
text_area_element->createTextRange(&range);
}
}
if (range) {
range->collapse(VARIANT_FALSE);
range->select();
return true;
}
return false;
}
};
} // namespace webdriver
#endif // WEBDRIVER_IE_SENDKEYSCOMMANDHANDLER_H_