2015-04-13 12:09:26 -04:00
|
|
|
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
|
|
|
// or more contributor license agreements. See the NOTICE file
|
|
|
|
|
// distributed with this work for additional information
|
|
|
|
|
// regarding copyright ownership. The SFC licenses this file
|
|
|
|
|
// to you under the Apache License, Version 2.0 (the "License");
|
2013-01-11 22:18:32 +01:00
|
|
|
// 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"
|
2017-02-14 09:48:56 -08:00
|
|
|
|
|
|
|
|
#include "logging.h"
|
|
|
|
|
|
2014-09-11 12:42:08 -04:00
|
|
|
#include "BrowserFactory.h"
|
2014-09-05 02:00:00 +00:00
|
|
|
#include "CommandExecutor.h"
|
2013-01-11 22:18:32 +01:00
|
|
|
#include "IECommandExecutor.h"
|
2014-09-05 02:00:00 +00:00
|
|
|
#include "messages.h"
|
2017-02-14 09:48:56 -08:00
|
|
|
#include "StringUtilities.h"
|
2019-01-18 15:54:41 -08:00
|
|
|
#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
|
2014-09-05 02:00:00 +00:00
|
|
|
|
|
|
|
|
typedef unsigned (__stdcall *ThreadProcedure)(void*);
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
// 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";
|
2013-04-10 00:02:15 +04:00
|
|
|
} else if (mutex_wait_status == WAIT_FAILED) {
|
|
|
|
|
LOGERR(WARN) << "Mutex acquire waiting is failed";
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
2013-04-10 00:02:15 +04:00
|
|
|
LOGERR(WARN) << "Could not create session initialization mutex. Multiple "
|
|
|
|
|
<< "instances will behave unpredictably. ";
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
2013-04-10 00:02:15 +04:00
|
|
|
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;
|
|
|
|
|
|
2019-01-18 15:54:41 -08:00
|
|
|
HANDLE event_handle = ::CreateEvent(NULL, TRUE, FALSE, WEBDRIVER_START_EVENT_NAME);
|
2013-04-10 00:02:15 +04:00
|
|
|
if (event_handle == NULL) {
|
2019-01-18 15:54:41 -08:00
|
|
|
LOGERR(DEBUG) << "Unable to create event " << WEBDRIVER_START_EVENT_NAME;
|
2013-04-10 00:02:15 +04:00
|
|
|
}
|
2014-09-05 02:00:00 +00:00
|
|
|
|
|
|
|
|
ThreadProcedure thread_proc = &IECommandExecutor::ThreadProc;
|
2013-01-11 22:18:32 +01:00
|
|
|
HANDLE thread_handle = reinterpret_cast<HANDLE>(_beginthreadex(NULL,
|
|
|
|
|
0,
|
2014-09-05 02:00:00 +00:00
|
|
|
thread_proc,
|
2013-04-10 00:02:15 +04:00
|
|
|
reinterpret_cast<void*>(&thread_context),
|
2013-01-11 22:18:32 +01:00
|
|
|
0,
|
|
|
|
|
&thread_id));
|
|
|
|
|
if (event_handle != NULL) {
|
2013-04-10 00:02:15 +04:00
|
|
|
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 << "'.";
|
|
|
|
|
}
|
2013-01-11 22:18:32 +01:00
|
|
|
::CloseHandle(event_handle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (thread_handle != NULL) {
|
|
|
|
|
::CloseHandle(thread_handle);
|
2013-04-10 00:02:15 +04:00
|
|
|
} else {
|
|
|
|
|
LOG(DEBUG) << "Unable to create thread for command executor";
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
2013-02-25 11:32:30 -05:00
|
|
|
std::string session_id = "";
|
2013-04-10 00:02:15 +04:00
|
|
|
if (thread_context.hwnd != NULL) {
|
|
|
|
|
LOG(TRACE) << "Created thread for command executor returns HWND: '" << thread_context.hwnd << "'";
|
2013-07-11 17:46:24 -04:00
|
|
|
std::vector<wchar_t> window_text_buffer(37);
|
2013-04-10 00:02:15 +04:00
|
|
|
::GetWindowText(thread_context.hwnd, &window_text_buffer[0], 37);
|
2013-03-21 13:58:47 -04:00
|
|
|
session_id = StringUtilities::ToString(&window_text_buffer[0]);
|
2013-04-10 00:02:15 +04:00
|
|
|
LOG(TRACE) << "Session id is retrived from command executor window: '" << session_id << "'";
|
|
|
|
|
} else {
|
|
|
|
|
LOG(DEBUG) << "Created thread does not return HWND of created session";
|
2013-02-25 11:32:30 -05:00
|
|
|
}
|
2013-04-10 00:02:15 +04:00
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
if (mutex != NULL) {
|
|
|
|
|
LOG(DEBUG) << "Releasing session initialization mutex";
|
|
|
|
|
::ReleaseMutex(mutex);
|
|
|
|
|
::CloseHandle(mutex);
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-10 00:02:15 +04:00
|
|
|
this->executor_window_handle_ = thread_context.hwnd;
|
2013-01-11 22:18:32 +01:00
|
|
|
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.
|
2015-12-16 09:40:37 -08:00
|
|
|
::SendMessage(this->executor_window_handle_, WD_QUIT, NULL, NULL);
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
// Don't terminate the thread until the browsers have all been deallocated.
|
2013-04-23 18:33:16 -04:00
|
|
|
// Note: Loop count of 6, because the timeout is 5 seconds, giving us a nice,
|
|
|
|
|
// round 30 seconds.
|
|
|
|
|
int retry_count = 6;
|
2013-04-22 15:38:47 -04:00
|
|
|
bool has_quit = this->WaitForCommandExecutorExit(EXECUTOR_EXIT_WAIT_TIMEOUT);
|
2013-04-24 11:44:07 -04:00
|
|
|
while (!has_quit && retry_count > 0) {
|
2013-04-22 15:38:47 -04:00
|
|
|
// ASSUMPTION! If all browsers haven't been deallocated by the timeout
|
|
|
|
|
// specified, they're blocked from quitting by something. We'll assume
|
|
|
|
|
// that something is an alert blocking close, and ask the executor to
|
|
|
|
|
// attempt another close after closing the offending alert.
|
|
|
|
|
// N.B., this could probably be made more robust by modifying
|
|
|
|
|
// IECommandExecutor::OnGetQuitStatus(), but that would require some
|
|
|
|
|
// fairly complex synchronization code, to make sure a browser isn't
|
|
|
|
|
// deallocated while the "close the alert and close the browser again"
|
|
|
|
|
// code is still running, since the deallocation happens in response
|
|
|
|
|
// to the DWebBrowserEvents2::OnQuit event.
|
|
|
|
|
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);
|
2013-04-23 18:33:16 -04:00
|
|
|
retry_count--;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
2013-04-22 15:38:47 -04:00
|
|
|
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.";
|
|
|
|
|
}
|
2013-01-11 22:18:32 +01:00
|
|
|
DWORD process_id;
|
|
|
|
|
DWORD thread_id = ::GetWindowThreadProcessId(this->executor_window_handle_,
|
|
|
|
|
&process_id);
|
|
|
|
|
HANDLE thread_handle = ::OpenThread(SYNCHRONIZE, FALSE, thread_id);
|
2013-05-23 16:52:54 -04:00
|
|
|
LOG(DEBUG) << "Posting thread shutdown message";
|
|
|
|
|
::PostThreadMessage(thread_id, WD_SHUTDOWN, NULL, NULL);
|
2013-01-11 22:18:32 +01:00
|
|
|
if (thread_handle != NULL) {
|
2013-05-23 16:52:54 -04:00
|
|
|
LOG(DEBUG) << "Starting wait for thread completion";
|
|
|
|
|
DWORD wait_result = ::WaitForSingleObject(&thread_handle, 30000);
|
2013-01-11 22:18:32 +01:00
|
|
|
if (wait_result != WAIT_OBJECT_0) {
|
|
|
|
|
LOG(DEBUG) << "Waiting for thread to end returned " << wait_result;
|
2013-05-23 16:52:54 -04:00
|
|
|
} else {
|
|
|
|
|
LOG(DEBUG) << "Wait for thread handle complete";
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
::CloseHandle(thread_handle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-22 15:38:47 -04:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
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
|
2018-12-31 11:04:06 -08:00
|
|
|
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()));
|
|
|
|
|
}
|
2013-01-11 22:18:32 +01:00
|
|
|
::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));
|
2013-01-17 09:13:30 -05:00
|
|
|
LOG(TRACE) << "Beginning wait for response length to be not zero";
|
2013-01-11 22:18:32 +01:00
|
|
|
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));
|
|
|
|
|
}
|
2013-01-17 09:13:30 -05:00
|
|
|
LOG(TRACE) << "Found non-zero response length";
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
// 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
|