// Copyright 2012 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. | |
#include "Alert.h" | |
#include "logging.h" | |
namespace webdriver { | |
Alert::Alert(BrowserHandle browser, HWND handle) { | |
LOG(TRACE) << "Entering Alert::Alert"; | |
this->browser_ = browser; | |
this->alert_handle_ = handle; | |
HWND direct_ui_child = NULL; | |
::EnumChildWindows(this->alert_handle_, | |
&Alert::FindDirectUIChild, | |
reinterpret_cast<LPARAM>(&direct_ui_child)); | |
this->is_standard_alert_ = direct_ui_child == NULL; | |
} | |
Alert::~Alert(void) { | |
} | |
int Alert::Accept() { | |
LOG(TRACE) << "Entering Alert::Accept"; | |
DialogButtonInfo button_info = this->GetDialogButton(OK); | |
if (!button_info.button_exists) { | |
// No OK button on dialog. Look for a cancel button | |
// (JavaScript alert() dialogs have a single button, but its ID | |
// can be that of a "cancel" button.) | |
LOG(INFO) << "OK button does not exist on dialog; looking for Cancel button"; | |
button_info = this->GetDialogButton(CANCEL); | |
} | |
if (!button_info.button_exists) { | |
LOG(WARN) << "OK and Cancel button do not exist on alert"; | |
return EUNHANDLEDERROR; | |
} else { | |
LOG(DEBUG) << "Closing alert using SendMessage"; | |
int status_code = this->ClickAlertButton(button_info); | |
} | |
return SUCCESS; | |
} | |
int Alert::Dismiss() { | |
LOG(TRACE) << "Entering Alert::Dismiss"; | |
DialogButtonInfo button_info = this->GetDialogButton(CANCEL); | |
if (!button_info.button_exists) { | |
LOG(WARN) << "Cancel button does not exist on alert"; | |
return EUNHANDLEDERROR; | |
} else { | |
// TODO(JimEvans): Check return code and return an appropriate | |
// error if the alert didn't get closed properly. | |
LOG(DEBUG) << "Closing alert using SendMessage"; | |
int status_code = this->ClickAlertButton(button_info); | |
} | |
return SUCCESS; | |
} | |
int Alert::SendKeys(std::string keys) { | |
LOG(TRACE) << "Entering Alert::SendKeys"; | |
HWND text_box_handle = NULL; | |
// Alert present, find the OK button. | |
// Retry up to 10 times to find the dialog. | |
int max_wait = 10; | |
while ((text_box_handle == NULL) && --max_wait) { | |
::EnumChildWindows(this->alert_handle_, | |
&Alert::FindTextBox, | |
reinterpret_cast<LPARAM>(&text_box_handle)); | |
if (text_box_handle == NULL) { | |
::Sleep(50); | |
} | |
} | |
if (text_box_handle == NULL) { | |
LOG(WARN) << "Text box not found on alert"; | |
return EELEMENTNOTDISPLAYED; | |
} else { | |
LOG(DEBUG) << "Sending keystrokes to alert using SendMessage"; | |
std::wstring text = CA2W(keys.c_str(), CP_UTF8); | |
::SendMessage(text_box_handle, | |
WM_SETTEXT, | |
NULL, | |
reinterpret_cast<LPARAM>(text.c_str())); | |
} | |
return SUCCESS; | |
} | |
std::string Alert::GetText() { | |
LOG(TRACE) << "Entering Alert::GetText"; | |
HWND label_handle = NULL; | |
// Alert present, find the OK button. | |
// Retry up to 10 times to find the dialog. | |
int max_wait = 10; | |
while ((label_handle == NULL) && --max_wait) { | |
::EnumChildWindows(this->alert_handle_, | |
&Alert::FindTextLabel, | |
reinterpret_cast<LPARAM>(&label_handle)); | |
if (label_handle == NULL) { | |
::Sleep(50); | |
} | |
} | |
std::string alert_text_value; | |
if (label_handle == NULL) { | |
alert_text_value = ""; | |
} else { | |
int text_length = ::GetWindowTextLength(label_handle); | |
std::vector<wchar_t> text_buffer(text_length + 1); | |
::GetWindowText(label_handle, &text_buffer[0], text_length + 1); | |
std::wstring alert_text = &text_buffer[0]; | |
alert_text_value = CW2A(alert_text.c_str(), CP_UTF8); | |
} | |
return alert_text_value; | |
} | |
int Alert::ClickAlertButton(DialogButtonInfo button_info) { | |
LOG(TRACE) << "Entering Alert::ClickAlertButton"; | |
// Click on the appropriate button of the Alert | |
if (this->is_standard_alert_) { | |
::SendMessage(this->alert_handle_, | |
WM_COMMAND, | |
button_info.button_control_id, | |
NULL); | |
} else { | |
::SendMessage(button_info.button_handle, | |
BM_CLICK, | |
NULL, | |
NULL); | |
} | |
// Hack to make sure alert is really closed, and browser | |
// is ready for the next operation. This may be a flawed | |
// algorithim, since the busy property of the browser may | |
// not be the right thing to check here. | |
int retry_count = 20; | |
while (::IsWindow(this->alert_handle_) && this->browser_->IsBusy() && retry_count > 0) { | |
::Sleep(50); | |
retry_count--; | |
} | |
// TODO(JimEvans): Check for the following error conditions: | |
// 1. Alert window still present (::IsWindow(this->alert_handle_) == TRUE) | |
// 2. Browser still busy (this->browser_->IsBusy() == true) | |
// and return an appropriate non-SUCCESS error code. | |
return SUCCESS; | |
} | |
Alert::DialogButtonInfo Alert::GetDialogButton(BUTTON_TYPE button_type) { | |
LOG(TRACE) << "Entering Alert::GetDialogButton"; | |
DialogButtonFindInfo button_find_info; | |
button_find_info.button_handle = NULL; | |
button_find_info.button_control_id = this->is_standard_alert_ ? IDOK : INVALID_CONTROL_ID; | |
if (button_type == OK) { | |
button_find_info.match_proc = &Alert::IsOKButton; | |
} else { | |
button_find_info.match_proc = &Alert::IsCancelButton; | |
} | |
int max_wait = 10; | |
// Retry up to 10 times to find the dialog. | |
while ((button_find_info.button_handle == NULL) && --max_wait) { | |
::EnumChildWindows(this->alert_handle_, | |
&Alert::FindDialogButton, | |
reinterpret_cast<LPARAM>(&button_find_info)); | |
if (button_find_info.button_handle == NULL) { | |
::Sleep(50); | |
} else { | |
break; | |
} | |
} | |
// Use the simple version of the struct so that subclasses do not | |
// have to know anything about the function pointer definition. | |
DialogButtonInfo button_info; | |
button_info.button_handle = button_find_info.button_handle; | |
button_info.button_control_id = button_find_info.button_control_id; | |
button_info.button_exists = button_find_info.button_handle != NULL; | |
return button_info; | |
} | |
bool Alert::IsOKButton(HWND button_handle) { | |
int control_id = ::GetDlgCtrlID(button_handle); | |
if (control_id != 0) { | |
return control_id == IDOK || control_id == IDYES || control_id == IDRETRY; | |
} | |
vector<TCHAR> button_window_class(100); | |
::GetClassName(button_handle, &button_window_class[0], static_cast<int>(button_window_class.size())); | |
if (wcscmp(&button_window_class[0], L"Button") == 0) { | |
long window_long = ::GetWindowLong(button_handle, GWL_STYLE); | |
return (window_long & BS_DEFCOMMANDLINK) == BS_DEFCOMMANDLINK; | |
} | |
return false; | |
} | |
bool Alert::IsCancelButton(HWND button_handle) { | |
int control_id = ::GetDlgCtrlID(button_handle); | |
if (control_id != 0) { | |
return control_id == IDCANCEL || control_id == IDNO; | |
} | |
vector<TCHAR> button_window_class(100); | |
::GetClassName(button_handle, &button_window_class[0], static_cast<int>(button_window_class.size())); | |
if (wcscmp(&button_window_class[0], L"Button") == 0) { | |
long window_long = ::GetWindowLong(button_handle, GWL_STYLE); | |
// The BS_DEFCOMMANDLINK mask includes BS_COMMANDLINK, but we | |
// want only to match those without the default bits set. | |
return (window_long & BS_DEFCOMMANDLINK) == BS_COMMANDLINK; | |
} | |
return false; | |
} | |
BOOL CALLBACK Alert::FindDialogButton(HWND hwnd, LPARAM arg) { | |
Alert::DialogButtonFindInfo* button_info = reinterpret_cast<Alert::DialogButtonFindInfo*>(arg); | |
int control_id = ::GetDlgCtrlID(hwnd); | |
if (button_info->match_proc(hwnd)) { | |
button_info->button_handle = hwnd; | |
button_info->button_control_id = control_id; | |
return FALSE; | |
} | |
return TRUE; | |
} | |
BOOL CALLBACK Alert::FindTextBox(HWND hwnd, LPARAM arg) { | |
HWND *dialog_handle = reinterpret_cast<HWND*>(arg); | |
TCHAR child_window_class[100]; | |
::GetClassName(hwnd, child_window_class, 100); | |
if (wcscmp(child_window_class, L"Edit") == 0) { | |
*dialog_handle = hwnd; | |
return FALSE; | |
} | |
return TRUE; | |
} | |
BOOL CALLBACK Alert::FindTextLabel(HWND hwnd, LPARAM arg) { | |
HWND *dialog_handle = reinterpret_cast<HWND*>(arg); | |
TCHAR child_window_class[100]; | |
::GetClassName(hwnd, child_window_class, 100); | |
if (wcscmp(child_window_class, L"Static") != 0) { | |
return TRUE; | |
} | |
int control_id = ::GetDlgCtrlID(hwnd); | |
int text_length = ::GetWindowTextLength(hwnd); | |
if (text_length > 0) { | |
*dialog_handle = hwnd; | |
return FALSE; | |
} | |
return TRUE; | |
} | |
BOOL CALLBACK Alert::FindDirectUIChild(HWND hwnd, LPARAM arg){ | |
HWND *dialog_handle = reinterpret_cast<HWND*>(arg); | |
TCHAR child_window_class[100]; | |
::GetClassName(hwnd, child_window_class, 100); | |
if (wcscmp(child_window_class, L"DirectUIHWND") != 0) { | |
return TRUE; | |
} | |
*dialog_handle = hwnd; | |
return FALSE; | |
} | |
} // namespace webdriver |