Skip to content

Commit

Permalink
Implement ability to specify IE launch API and IE CLI switches
Browse files Browse the repository at this point in the history
For some cases, it may be necessary to launch IE in Private mode for
testing in parallel on a single node. To support this, there must be a way
to force the IE driver to use the Windows CreateProcess() API to launch IE
so that the user can specify command-line switches. To preserve the
ability to locate the correct process for IE versions 8 and higher, a
registry value must be set, and the IE driver checks for the registry
value before launching IE.

Signed-off-by: Jim Evans <james.h.evans.jr@gmail.com>
  • Loading branch information
alex-savchuk authored and jimevans committed Apr 11, 2013
1 parent 517683f commit e0a467d
Show file tree
Hide file tree
Showing 19 changed files with 425 additions and 159 deletions.
276 changes: 178 additions & 98 deletions cpp/IEDriver/BrowserFactory.cpp

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions cpp/IEDriver/BrowserFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

#define IE_CLSID_REGISTRY_KEY L"SOFTWARE\\Classes\\InternetExplorer.Application\\CLSID"
#define IE_SECURITY_ZONES_REGISTRY_KEY L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Zones"
#define IE_TABPROCGROWTH_REGISTRY_KEY L"Software\\Microsoft\\Internet Explorer\\Main"

#define IE_PROTECTED_MODE_SETTING_VALUE_NAME L"2500"

Expand All @@ -48,13 +49,17 @@
#define NULL_PROCESS_ID_ERROR_MESSAGE " successfully launched Internet Explorer, but did not return a valid process ID."
#define PROTECTED_MODE_SETTING_ERROR_MESSAGE "Protected Mode settings are not the same for all zones. Enable Protected Mode must be set to the same value (enabled or disabled) for all zones."
#define ATTACH_TIMEOUT_ERROR_MESSAGE "Could not find an Internet Explorer window belonging to the process with ID %d within %d milliseconds."
#define CREATEPROCESS_REGISTRY_ERROR_MESSAGE "Unable to use CreateProcess() API. To use CreateProcess() with Internet Explorer 8 or higher, the value of registry setting in HEKY_CURRENT_USER\\Software\\Microsoft\\Internet Explorer\\Main\\TabProcGrowth must be '0'."

#define ZONE_MY_COMPUTER L"0"
#define ZONE_LOCAL_INTRANET L"1"
#define ZONE_TRUSTED_SITES L"2"
#define ZONE_INTERNET L"3"
#define ZONE_RESTRICTED_SITES L"4"

#define IELAUNCHURL_API L"ielaunchurl"
#define CREATEPROCESS_API L"createprocess"

using namespace std;

namespace webdriver {
Expand All @@ -72,6 +77,8 @@ class BrowserFactory {

DWORD LaunchBrowserProcess(const std::string& initial_url,
const bool ignore_protected_mode_settings,
const bool force_createprocess_api,
const std::string& ie_switches,
std::string* error_message);
IWebBrowser2* CreateBrowser();
bool AttachToBrowser(ProcessWindowInfo* procWinInfo,
Expand Down Expand Up @@ -107,6 +114,15 @@ class BrowserFactory {
int GetZoneProtectedModeSetting(const HKEY key_handle,
const std::wstring& zone_subkey_name);
int GetZoomLevel(IHTMLDocument2* document, IHTMLWindow2* window);
void LaunchBrowserUsingCreateProcess(const std::string& initial_url,
const std::string& command_line_switches,
PROCESS_INFORMATION* proc_info,
std::string* error_message);
void LaunchBrowserUsingIELaunchURL(const std::string& initial_url,
PROCESS_INFORMATION* proc_info,
std::string* error_message);
bool IsIELaunchURLAvailable(void);
bool IsCreateProcessApiAvailable(void);

int ie_major_version_;
int windows_major_version_;
Expand Down
4 changes: 2 additions & 2 deletions cpp/IEDriver/DocumentHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ bool DocumentHost::IsHtmlPage(IHTMLDocument2* doc) {
if (document_type_key_name == L"IE.HTTP") {
document_type_key_name = L"htmlfile";
} else {
LOG(DEBUG) << "Unable to support custom document type: " << LOGWSTRING(document_type_key_name.c_str());
LOG(DEBUG) << "Unable to support custom document type: " << LOGWSTRING(document_type_key_name);
document_type_key_name = L"";
}
} else {
Expand Down Expand Up @@ -348,7 +348,7 @@ bool DocumentHost::IsHtmlPage(IHTMLDocument2* doc) {
std::wstring type_string = type;

if (type_string == mime_type_name) {
return true;
return true;
}

// If the user set Firefox as a default browser at any point, the MIME type
Expand Down
32 changes: 16 additions & 16 deletions cpp/IEDriver/ElementFinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ int ElementFinder::FindElement(const IECommandExecutor& executor,
}

LOG(DEBUG) << L"Using FindElement atom to locate element having "
<< LOGWSTRING(mechanism.c_str()) << " = "
<< LOGWSTRING(criteria.c_str());
<< LOGWSTRING(mechanism) << " = "
<< LOGWSTRING(criteria);
std::wstring sanitized_criteria = criteria;
this->SanitizeCriteria(mechanism, &sanitized_criteria);
std::wstring criteria_object_script = L"(function() { return function(){ return { \"" +
Expand Down Expand Up @@ -83,28 +83,28 @@ int ElementFinder::FindElement(const IECommandExecutor& executor,
script_wrapper.ConvertResultToJsonValue(executor, found_element);
} else {
LOG(WARN) << "Unable to find element by mechanism "
<< LOGWSTRING(mechanism.c_str()) << " and criteria "
<< LOGWSTRING(sanitized_criteria.c_str());
<< LOGWSTRING(mechanism) << " and criteria "
<< LOGWSTRING(sanitized_criteria);
status_code = ENOSUCHELEMENT;
}
} else {
// An error in the execution of the FindElement atom for XPath is assumed
// to be a syntactically invalid XPath.
if (mechanism == L"xpath") {
LOG(WARN) << "Attempted to find element using invalid xpath: "
<< LOGWSTRING(sanitized_criteria.c_str());
<< LOGWSTRING(sanitized_criteria);
status_code = EINVALIDSELECTOR;
} else {
LOG(WARN) << "Unexpected error attempting to find element by mechanism "
<< LOGWSTRING(mechanism.c_str()) << " with criteria "
<< LOGWSTRING(sanitized_criteria.c_str());
<< LOGWSTRING(mechanism) << " with criteria "
<< LOGWSTRING(sanitized_criteria);
status_code = ENOSUCHELEMENT;
}
}
} else {
LOG(WARN) << "Unable to create criteria object for mechanism "
<< LOGWSTRING(mechanism.c_str()) << " and criteria "
<< LOGWSTRING(sanitized_criteria.c_str());
<< LOGWSTRING(mechanism) << " and criteria "
<< LOGWSTRING(sanitized_criteria);
status_code = ENOSUCHELEMENT;
}
} else {
Expand Down Expand Up @@ -136,8 +136,8 @@ int ElementFinder::FindElements(const IECommandExecutor& executor,
}

LOG(DEBUG) << L"Using FindElements atom to locate element having "
<< LOGWSTRING(mechanism.c_str()) << " = "
<< LOGWSTRING(criteria.c_str());
<< LOGWSTRING(mechanism) << " = "
<< LOGWSTRING(criteria);
std::wstring sanitized_criteria = criteria;
this->SanitizeCriteria(mechanism, &sanitized_criteria);
std::wstring criteria_object_script = L"(function() { return function(){ return { \"" + mechanism + L"\" : \"" + sanitized_criteria + L"\" }; };})();";
Expand Down Expand Up @@ -177,19 +177,19 @@ int ElementFinder::FindElements(const IECommandExecutor& executor,
// to be a syntactically invalid XPath.
if (mechanism == L"xpath") {
LOG(WARN) << "Attempted to find elements using invalid xpath: "
<< LOGWSTRING(sanitized_criteria.c_str());
<< LOGWSTRING(sanitized_criteria);
status_code = EINVALIDSELECTOR;
} else {
LOG(WARN) << "Unexpected error attempting to find element by mechanism "
<< LOGWSTRING(mechanism.c_str()) << " and criteria "
<< LOGWSTRING(sanitized_criteria.c_str());
<< LOGWSTRING(mechanism) << " and criteria "
<< LOGWSTRING(sanitized_criteria);
status_code = ENOSUCHELEMENT;
}
}
} else {
LOG(WARN) << "Unable to create criteria object for mechanism "
<< LOGWSTRING(mechanism.c_str()) << " and criteria "
<< LOGWSTRING(sanitized_criteria.c_str());
<< LOGWSTRING(mechanism) << " and criteria "
<< LOGWSTRING(sanitized_criteria);
status_code = ENOSUCHELEMENT;
}
} else {
Expand Down
64 changes: 47 additions & 17 deletions cpp/IEDriver/IECommandExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ LRESULT IECommandExecutor::OnInit(UINT uMsg,
LPARAM lParam,
BOOL& bHandled) {
LOG(TRACE) << "Entering IECommandExecutor::OnInit";

// If we wanted to be a little more clever, we could create a struct
// containing the HWND and the port number and pass them into the
// ThreadProc via lpParameter and avoid this message handler altogether.
this->port_ = (int)wParam;
return 0;
}

Expand All @@ -94,6 +89,12 @@ LRESULT IECommandExecutor::OnCreate(UINT uMsg,
LPARAM lParam,
BOOL& bHandled) {
LOG(TRACE) << "Entering IECommandExecutor::OnCreate";

CREATESTRUCT* create = reinterpret_cast<CREATESTRUCT*>(lParam);
IECommandExecutorThreadContext* context = reinterpret_cast<IECommandExecutorThreadContext*>(create->lpCreateParams);
this->port_ = context->port;
this->force_createprocess_api_ = context->force_createprocess_api;
this->ie_switches_ = context->ie_switches;

// NOTE: COM should be initialized on this thread, so we
// could use CoCreateGuid() and StringFromGUID2() instead.
Expand Down Expand Up @@ -244,6 +245,8 @@ LRESULT IECommandExecutor::OnWait(UINT uMsg,
&thread_id));
if (thread_handle != NULL) {
::CloseHandle(thread_handle);
} else {
LOGERR(DEBUG) << "Unable to create waiter thread";
}
}
}
Expand All @@ -266,6 +269,10 @@ LRESULT IECommandExecutor::OnBrowserNewWindow(UINT uMsg,
HRESULT hr = ::CoMarshalInterThreadInterfaceInStream(IID_IWebBrowser2,
browser,
stream);
if (FAILED(hr)) {
LOGHR(DEBUG, hr) << "Marshalling of interface pointer b/w threads is failed.";
}

return 0;
}

Expand Down Expand Up @@ -345,6 +352,7 @@ LRESULT IECommandExecutor::OnRefreshManagedElements(UINT uMsg,
}

unsigned int WINAPI IECommandExecutor::WaitThreadProc(LPVOID lpParameter) {
LOG(TRACE) << "Entering IECommandExecutor::WaitThreadProc";
HWND window_handle = reinterpret_cast<HWND>(lpParameter);
::Sleep(WAIT_TIME_IN_MILLISECONDS);
::PostMessage(window_handle, WD_WAIT, NULL, NULL);
Expand All @@ -353,31 +361,48 @@ unsigned int WINAPI IECommandExecutor::WaitThreadProc(LPVOID lpParameter) {


unsigned int WINAPI IECommandExecutor::ThreadProc(LPVOID lpParameter) {
// Optional TODO: Create a struct to pass in via lpParameter
// instead of just a pointer to an HWND. That way, we could
// pass the mongoose server port via a single call, rather than
// having to send an init message after the window is created.
HWND *window_handle = reinterpret_cast<HWND*>(lpParameter);
LOG(TRACE) << "Entering IECommandExecutor::ThreadProc";

IECommandExecutorThreadContext* thread_context = reinterpret_cast<IECommandExecutorThreadContext*>(lpParameter);
HWND window_handle = thread_context->hwnd;

// it is better to use IECommandExecutorSessionContext instead
// but use ThreadContext for code minimization
IECommandExecutorThreadContext* session_context = new IECommandExecutorThreadContext();
session_context->port = thread_context->port;
session_context->ie_switches = thread_context->ie_switches;
session_context->force_createprocess_api = thread_context->force_createprocess_api;

DWORD error = 0;
HRESULT hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr)) {
LOGHR(DEBUG, hr) << "COM library initialization has some problem";
}

IECommandExecutor new_session;
HWND session_window_handle = new_session.Create(HWND_MESSAGE,
CWindow::rcDefault);
HWND session_window_handle = new_session.Create(/*HWND*/ HWND_MESSAGE,
/*_U_RECT rect*/ CWindow::rcDefault,
/*LPCTSTR szWindowName*/ NULL,
/*DWORD dwStyle*/ NULL,
/*DWORD dwExStyle*/ NULL,
/*_U_MENUorID MenuOrID*/ 0U,
/*LPVOID lpCreateParam*/ reinterpret_cast<LPVOID*>(session_context));
if (session_window_handle == NULL) {
error = ::GetLastError();
LOG(WARN) << "Unable to create new session: " << error;
LOGERR(WARN) << "Unable to create new IECommandExecutor session";
}

MSG msg;
::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);

// Return the HWND back through lpParameter, and signal that the
// window is ready for messages.
*window_handle = session_window_handle;
thread_context->hwnd = session_window_handle;
HANDLE event_handle = ::OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME);
if (event_handle != NULL) {
::SetEvent(event_handle);
::CloseHandle(event_handle);
} else {
LOGERR(DEBUG) << "Unable to signal that window is ready";
}

// Run the message loop
Expand All @@ -387,6 +412,7 @@ unsigned int WINAPI IECommandExecutor::ThreadProc(LPVOID lpParameter) {
}

::CoUninitialize();
delete session_context;
return 0;
}

Expand Down Expand Up @@ -467,7 +493,7 @@ void IECommandExecutor::DispatchCommand() {
LOG(WARN) << "Unable to find current browser";
}
}
CommandHandlerHandle command_handler = found_iterator->second;
CommandHandlerHandle command_handler = found_iterator->second;
command_handler->Execute(*this, this->current_command_, &response);

status_code = this->GetCurrentBrowser(&browser);
Expand Down Expand Up @@ -499,6 +525,7 @@ int IECommandExecutor::GetManagedBrowser(const std::string& browser_id,
LOG(TRACE) << "Entering IECommandExecutor::GetManagedBrowser";

if (!this->is_valid()) {
LOG(TRACE) << "Current command executor is not valid";
return ENOSUCHDRIVER;
}

Expand Down Expand Up @@ -553,7 +580,10 @@ int IECommandExecutor::CreateNewBrowser(std::string* error_message) {
}

DWORD process_id = this->factory_.LaunchBrowserProcess(initial_url,
this->ignore_protected_mode_settings_, error_message);
this->ignore_protected_mode_settings_,
this->force_createprocess_api_,
this->ie_switches_,
error_message);
if (process_id == NULL) {
LOG(WARN) << "Unable to launch browser, received NULL process ID";
this->is_waiting_ = false;
Expand Down
12 changes: 12 additions & 0 deletions cpp/IEDriver/IECommandExecutor.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ using namespace std;

namespace webdriver {

// Structure to be used for comunication between threads
struct IECommandExecutorThreadContext
{
HWND hwnd;
int port;
bool force_createprocess_api;
std::string ie_switches;
};

// We use a CWindowImpl (creating a hidden window) here because we
// want to synchronize access to the command handler. For that we
// use SendMessage() most of the time, and SendMessage() requires
Expand Down Expand Up @@ -241,6 +250,9 @@ class IECommandExecutor : public CWindowImpl<IECommandExecutor> {

std::string session_id_;
int port_;
bool force_createprocess_api_;
std::string launch_api_;
std::string ie_switches_;
int browser_attach_timeout_;
bool ignore_protected_mode_settings_;
bool enable_native_events_;
Expand Down
17 changes: 15 additions & 2 deletions cpp/IEDriver/IEServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,40 @@

#include "IEServer.h"
#include "IESession.h"
#include "logging.h"

namespace webdriver {

IEServer::IEServer(int port,
const std::string& host,
const std::string& log_level,
const std::string& log_file,
const bool force_createprocess,
const std::string& ie_switches,
const std::string& version) : Server(port, host, log_level, log_file) {
LOG(TRACE) << "Entering IEServer::IEServer";

this->version_ = version;
this->force_createprocess_ = force_createprocess;
this->ie_switches_ = ie_switches;
}

IEServer::~IEServer(void) {
}

SessionHandle IEServer::InitializeSession() {
LOG(TRACE) << "Entering IEServer::InitializeSession";
SessionHandle session_handle(new IESession());
int port = this->port();
session_handle->Initialize(&port);
SessionParameters params;
params.port = this->port();
params.force_createprocess_api = this->force_createprocess_;
params.ie_switches = this->ie_switches_;
session_handle->Initialize(reinterpret_cast<void*>(&params));
return session_handle;
}

std::string IEServer::GetStatus() {
LOG(TRACE) << "Entering IEServer::GetStatus";
SYSTEM_INFO system_info;
::ZeroMemory(&system_info, sizeof(SYSTEM_INFO));
::GetNativeSystemInfo(&system_info);
Expand Down Expand Up @@ -80,6 +92,7 @@ std::string IEServer::GetStatus() {
}

void IEServer::ShutDown() {
LOG(TRACE) << "Entering IEServer::ShutDown";
DWORD process_id = ::GetCurrentProcessId();
vector<wchar_t> process_id_buffer(10);
_ltow_s(process_id, &process_id_buffer[0], process_id_buffer.size(), 10);
Expand Down
4 changes: 4 additions & 0 deletions cpp/IEDriver/IEServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class IEServer : public Server
const std::string& host,
const std::string& log_level,
const std::string& log_file,
const bool force_createprocess,
const std::string& ie_switches,
const std::string& version);
virtual ~IEServer(void);

Expand All @@ -39,6 +41,8 @@ class IEServer : public Server
virtual void ShutDown(void);
private:
std::string version_;
bool force_createprocess_;
std::string ie_switches_;
};

} // namespace webdriver
Expand Down
Loading

0 comments on commit e0a467d

Please sign in to comment.