#include "IESession.h"
#include "logging.h"
#include "BrowserFactory.h"
#include "CommandExecutor.h"
#include "IECommandExecutor.h"
#include "messages.h"
#include "StringUtilities.h"
#include "WebDriverConstants.h"
#define MUTEX_NAME L"WD_INITIALIZATION_MUTEX"
#define MUTEX_WAIT_TIMEOUT 30000
#define THREAD_WAIT_TIMEOUT 30000
#define EXECUTOR_EXIT_WAIT_TIMEOUT 5000
#define EXECUTOR_EXIT_WAIT_INTERVAL 100
typedef unsigned (__stdcall *ThreadProcedure)(void*);
namespace webdriver {
IESession::IESession() {
}
IESession::~IESession(void) {
}
void IESession::Initialize(void* init_params) {
LOG(TRACE) << "Entering IESession::Initialize";
HANDLE mutex = ::CreateMutex(NULL, FALSE, MUTEX_NAME);
if (mutex != NULL) {
DWORD mutex_wait_status = ::WaitForSingleObject(mutex, MUTEX_WAIT_TIMEOUT);
if (mutex_wait_status == WAIT_ABANDONED) {
LOG(WARN) << "Acquired mutex, but received wait abandoned status. This "
<< "could mean the process previously owning the mutex was "
<< "unexpectedly terminated.";
} else if (mutex_wait_status == WAIT_TIMEOUT) {
LOG(WARN) << "Could not acquire mutex within the timeout. Multiple "
<< "instances may hang or behave unpredictably";
} else if (mutex_wait_status == WAIT_OBJECT_0) {
LOG(DEBUG) << "Mutex acquired for session initalization";
} else if (mutex_wait_status == WAIT_FAILED) {
LOGERR(WARN) << "Mutex acquire waiting is failed";
}
} else {
LOGERR(WARN) << "Could not create session initialization mutex. Multiple "
<< "instances will behave unpredictably. ";
}
SessionParameters* params = reinterpret_cast<SessionParameters*>(init_params);
int port = params->port;
IECommandExecutorThreadContext thread_context;
thread_context.port = port;
thread_context.hwnd = NULL;
unsigned int thread_id = 0;
HANDLE event_handle = ::CreateEvent(NULL, TRUE, FALSE, WEBDRIVER_START_EVENT_NAME);
if (event_handle == NULL) {
LOGERR(DEBUG) << "Unable to create event " << WEBDRIVER_START_EVENT_NAME;
}
ThreadProcedure thread_proc = &IECommandExecutor::ThreadProc;
HANDLE thread_handle = reinterpret_cast<HANDLE>(_beginthreadex(NULL,
0,
thread_proc,
reinterpret_cast<void*>(&thread_context),
0,
&thread_id));
if (event_handle != NULL) {
DWORD thread_wait_status = ::WaitForSingleObject(event_handle, THREAD_WAIT_TIMEOUT);
if (thread_wait_status != WAIT_OBJECT_0) {
LOGERR(WARN) << "Unable to wait until created thread notification: '" << thread_wait_status << "'.";
}
::CloseHandle(event_handle);
}
if (thread_handle != NULL) {
::CloseHandle(thread_handle);
} else {
LOG(DEBUG) << "Unable to create thread for command executor";
}
std::string session_id = "";
if (thread_context.hwnd != NULL) {
LOG(TRACE) << "Created thread for command executor returns HWND: '" << thread_context.hwnd << "'";
std::vector<wchar_t> window_text_buffer(37);
::GetWindowText(thread_context.hwnd, &window_text_buffer[0], 37);
session_id = StringUtilities::ToString(&window_text_buffer[0]);
LOG(TRACE) << "Session id is retrived from command executor window: '" << session_id << "'";
} else {
LOG(DEBUG) << "Created thread does not return HWND of created session";
}
if (mutex != NULL) {
LOG(DEBUG) << "Releasing session initialization mutex";
::ReleaseMutex(mutex);
::CloseHandle(mutex);
}
this->executor_window_handle_ = thread_context.hwnd;
this->set_session_id(session_id);
}
void IESession::ShutDown(void) {
LOG(TRACE) << "Entering IESession::ShutDown";
::SendMessage(this->executor_window_handle_, WD_QUIT, NULL, NULL);
int retry_count = 6;
bool has_quit = this->WaitForCommandExecutorExit(EXECUTOR_EXIT_WAIT_TIMEOUT);
while (!has_quit && retry_count > 0) {
LOG(DEBUG) << "Not all browsers have been deallocated!";
::PostMessage(this->executor_window_handle_,
WD_HANDLE_UNEXPECTED_ALERTS,
NULL,
NULL);
has_quit = this->WaitForCommandExecutorExit(EXECUTOR_EXIT_WAIT_TIMEOUT);
retry_count--;
}
if (has_quit) {
LOG(DEBUG) << "Executor shutdown successful!";
} else {
LOG(ERROR) << "Still running browsers after handling alerts! This is likely to lead to a crash.";
}
DWORD process_id;
DWORD thread_id = ::GetWindowThreadProcessId(this->executor_window_handle_,
&process_id);
HANDLE thread_handle = ::OpenThread(SYNCHRONIZE, FALSE, thread_id);
LOG(DEBUG) << "Posting thread shutdown message";
::PostThreadMessage(thread_id, WD_SHUTDOWN, NULL, NULL);
if (thread_handle != NULL) {
LOG(DEBUG) << "Starting wait for thread completion";
DWORD wait_result = ::WaitForSingleObject(&thread_handle, 30000);
if (wait_result != WAIT_OBJECT_0) {
LOG(DEBUG) << "Waiting for thread to end returned " << wait_result;
} else {
LOG(DEBUG) << "Wait for thread handle complete";
}
::CloseHandle(thread_handle);
}
}
bool IESession::WaitForCommandExecutorExit(int timeout_in_milliseconds) {
LOG(TRACE) << "Entering IESession::WaitForCommandExecutorExit";
int is_quitting = static_cast<int>(::SendMessage(this->executor_window_handle_,
WD_GET_QUIT_STATUS,
NULL,
NULL));
int retry_count = timeout_in_milliseconds / EXECUTOR_EXIT_WAIT_INTERVAL;
while (is_quitting > 0 && --retry_count > 0) {
::Sleep(EXECUTOR_EXIT_WAIT_INTERVAL);
is_quitting = static_cast<int>(::SendMessage(this->executor_window_handle_,
WD_GET_QUIT_STATUS,
NULL,
NULL));
}
return is_quitting == 0;
}
bool IESession::ExecuteCommand(const std::string& serialized_command,
std::string* serialized_response) {
LOG(TRACE) << "Entering IESession::ExecuteCommand";
LRESULT set_command_result = ::SendMessage(this->executor_window_handle_,
WD_SET_COMMAND,
NULL,
reinterpret_cast<LPARAM>(serialized_command.c_str()));
while (set_command_result == 0) {
::Sleep(500);
set_command_result = ::SendMessage(this->executor_window_handle_,
WD_SET_COMMAND,
NULL,
reinterpret_cast<LPARAM>(serialized_command.c_str()));
}
::PostMessage(this->executor_window_handle_,
WD_EXEC_COMMAND,
NULL,
NULL);
int response_length = static_cast<int>(::SendMessage(this->executor_window_handle_,
WD_GET_RESPONSE_LENGTH,
NULL,
NULL));
LOG(TRACE) << "Beginning wait for response length to be not zero";
while (response_length == 0) {
::Sleep(10);
response_length = static_cast<int>(::SendMessage(this->executor_window_handle_,
WD_GET_RESPONSE_LENGTH,
NULL,
NULL));
}
LOG(TRACE) << "Found non-zero response length";
std::vector<char> response_buffer(response_length + 1);
::SendMessage(this->executor_window_handle_,
WD_GET_RESPONSE,
NULL,
reinterpret_cast<LPARAM>(&response_buffer[0]));
*serialized_response = &response_buffer[0];
bool session_is_valid = ::SendMessage(this->executor_window_handle_,
WD_IS_SESSION_VALID,
NULL,
NULL) != 0;
return session_is_valid;
}
}