// 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. | |
#include "IESession.h" | |
#include "IECommandExecutor.h" | |
#include "logging.h" | |
#include "interactions.h" | |
namespace webdriver { | |
IESession::IESession() { | |
} | |
IESession::~IESession(void) { | |
} | |
void IESession::Initialize(void* init_params) { | |
LOG(TRACE) << "Entering IESession::Initialize"; | |
unsigned int thread_id = 0; | |
HWND executor_window_handle = NULL; | |
HANDLE mutex = ::CreateMutex(NULL, FALSE, MUTEX_NAME); | |
if (mutex != NULL) { | |
// Wait for up to the timeout (currently 30 seconds) for other sessions | |
// to completely initialize. | |
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 { | |
LOG(WARN) << "Could not create session initialization mutex. Multiple " | |
<< "instances will behave unpredictably."; | |
} | |
HANDLE event_handle = ::CreateEvent(NULL, TRUE, FALSE, EVENT_NAME); | |
HANDLE thread_handle = reinterpret_cast<HANDLE>(_beginthreadex(NULL, | |
0, | |
&IECommandExecutor::ThreadProc, | |
reinterpret_cast<void*>(&executor_window_handle), | |
0, | |
&thread_id)); | |
if (event_handle != NULL) { | |
::WaitForSingleObject(event_handle, INFINITE); | |
::CloseHandle(event_handle); | |
} | |
if (thread_handle != NULL) { | |
::CloseHandle(thread_handle); | |
} | |
int* int_param = reinterpret_cast<int*>(init_params); | |
int port = *int_param; | |
::SendMessage(executor_window_handle, | |
WD_INIT, | |
static_cast<WPARAM>(port), | |
NULL); | |
vector<TCHAR> window_text_buffer(37); | |
::GetWindowText(executor_window_handle, &window_text_buffer[0], 37); | |
std::string session_id = CW2A(&window_text_buffer[0], CP_UTF8); | |
if (mutex != NULL) { | |
LOG(DEBUG) << "Releasing session initialization mutex"; | |
::ReleaseMutex(mutex); | |
::CloseHandle(mutex); | |
} | |
this->executor_window_handle_ = executor_window_handle; | |
this->set_session_id(session_id); | |
} | |
void IESession::ShutDown(void) { | |
LOG(TRACE) << "Entering IESession::ShutDown"; | |
// Kill the background thread first - otherwise the IE process crashes. | |
stopPersistentEventFiring(); | |
// Don't terminate the thread until the browsers have all been deallocated. | |
int is_quitting = static_cast<int>(::SendMessage(this->executor_window_handle_, | |
WD_GET_QUIT_STATUS, | |
NULL, | |
NULL)); | |
int retry_count = 50; | |
while (is_quitting > 0 && --retry_count > 0) { | |
::Sleep(100); | |
is_quitting = static_cast<int>(::SendMessage(this->executor_window_handle_, | |
WD_GET_QUIT_STATUS, | |
NULL, | |
NULL)); | |
} | |
DWORD process_id; | |
DWORD thread_id = ::GetWindowThreadProcessId(this->executor_window_handle_, | |
&process_id); | |
HANDLE thread_handle = ::OpenThread(SYNCHRONIZE, FALSE, thread_id); | |
::SendMessage(this->executor_window_handle_, WM_CLOSE, NULL, NULL); | |
if (thread_handle != NULL) { | |
DWORD wait_result = ::WaitForSingleObject(thread_handle, 30000); | |
if (wait_result != WAIT_OBJECT_0) { | |
LOG(DEBUG) << "Waiting for thread to end returned " << wait_result; | |
} | |
::CloseHandle(thread_handle); | |
} | |
} | |
bool IESession::ExecuteCommand(const std::string& serialized_command, | |
std::string* serialized_response) { | |
LOG(TRACE) << "Entering IESession::ExecuteCommand"; | |
// Sending a command consists of five actions: | |
// 1. Setting the command to be executed | |
// 2. Executing the command | |
// 3. Waiting for the response to be populated | |
// 4. Retrieving the response | |
// 5. Retrieving whether the command sent caused the session to be ready for shutdown | |
::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)); | |
while (response_length == 0) { | |
// Sleep a short time to prevent thread starvation on single-core machines. | |
::Sleep(10); | |
response_length = static_cast<int>(::SendMessage(this->executor_window_handle_, | |
WD_GET_RESPONSE_LENGTH, | |
NULL, | |
NULL)); | |
} | |
// Must add one to the length to handle the terminating character. | |
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; | |
} | |
} // namespace webdriver |