Skip to content

Commit

Permalink
Fixing IE driver crash when clicking on link that opens a new window.
Browse files Browse the repository at this point in the history
When IWebBrowser2::Quit() is called, the wrapper process doesn't exit
right away. When that happens, CoCreateInstance can fail while the
abandoned iexplore.exe instance is still valid. The "right" way to do
this would be to call ::EnumProcesses before calling CoCreateInstance,
finding all of the iexplore.exe processes, waiting for one to exit,
and then proceed. However, there is no way to tell if a process ID
belongs to an Internet Explorer instance, particularly when a 32-bit
process tries to enumerate 64-bit processes on 64-bit Windows. So, we
take the brute force way out, just retrying the call to
CoCreateInstance until it succeeds (the old iexplore.exe process has
exited), or we get a different error code. We also set a 45-second
timeout, with 45 seconds being chosen because it's below the default
60 second HTTP request timeout of most language bindings.

Fixes issue SeleniumHQ#5848. Fixes issue SeleniumHQ#7021.
  • Loading branch information
jimevans committed May 8, 2014
1 parent 9608d57 commit 0da4a96
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 231 deletions.
23 changes: 19 additions & 4 deletions cpp/iedriver/Browser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,29 @@ void __stdcall Browser::NewWindow3(IDispatch** ppDisp,
LOG(TRACE) << "Entering Browser::NewWindow3";
// Handle the NewWindow3 event to allow us to immediately hook
// the events of the new browser window opened by the user action.
// The three ways we can respond to this event are documented at
// http://msdn.microsoft.com/en-us/library/aa768337%28v=vs.85%29.aspx
// We potentially use two of those response methods.
// This will not allow us to handle windows created by the JavaScript
// showModalDialog function().
IWebBrowser2* browser;
LPSTREAM message_payload;
::SendMessage(this->executor_handle(),
WD_BROWSER_NEW_WINDOW,
NULL,
reinterpret_cast<LPARAM>(&message_payload));
LRESULT create_result = ::SendMessage(this->executor_handle(),
WD_BROWSER_NEW_WINDOW,
NULL,
reinterpret_cast<LPARAM>(&message_payload));
if (create_result != 0) {
// The new, blank IWebBrowser2 object was not created,
// so we can't really do anything appropriate here.
// Note this is "response method 2" of the aforementioned
// documentation.
LOG(WARN) << "A valid IWebBrowser2 object could not be created.";
*pbCancel = VARIANT_TRUE;
return;
}

// We received a valid IWebBrowser2 pointer, so deserialize it onto this
// thread, and pass the result back to the caller.
HRESULT hr = ::CoGetInterfaceAndReleaseStream(message_payload,
IID_IWebBrowser2,
reinterpret_cast<void**>(&browser));
Expand Down
94 changes: 43 additions & 51 deletions cpp/iedriver/BrowserFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -585,32 +585,59 @@ int BrowserFactory::GetZoomLevel(IHTMLDocument2* document, IHTMLWindow2* window)
IWebBrowser2* BrowserFactory::CreateBrowser() {
LOG(TRACE) << "Entering BrowserFactory::CreateBrowser";

// TODO: Error and exception handling and return value checking.
IWebBrowser2* browser;
if (this->windows_major_version_ >= 6) {
// Only Windows Vista and above have mandatory integrity levels.
this->SetThreadIntegrityLevel();
}

IWebBrowser2* browser = NULL;
DWORD context = CLSCTX_LOCAL_SERVER;
if (this->ie_major_version_ == 7 && this->windows_major_version_ >= 6) {
// ONLY for IE 7 on Windows Vista. XP and below do not have Protected Mode;
// Windows 7 shipped with IE8.
context = context | CLSCTX_ENABLE_CLOAKING;
}

::CoCreateInstance(CLSID_InternetExplorer,
NULL,
context,
IID_IWebBrowser2,
reinterpret_cast<void**>(&browser));
HRESULT hr = ::CoCreateInstance(CLSID_InternetExplorer,
NULL,
context,
IID_IWebBrowser2,
reinterpret_cast<void**>(&browser));
// When IWebBrowser2::Quit() is called, the wrapper process doesn't
// exit right away. When that happens, CoCreateInstance can fail while
// the abandoned iexplore.exe instance is still valid. The "right" way
// to do this would be to call ::EnumProcesses before calling
// CoCreateInstance, finding all of the iexplore.exe processes, waiting
// for one to exit, and then proceed. However, there is no way to tell
// if a process ID belongs to an Internet Explorer instance, particularly
// when a 32-bit process tries to enumerate 64-bit processes on 64-bit
// Windows. So, we'll take the brute force way out, just retrying the call
// to CoCreateInstance until it succeeds (the old iexplore.exe process has
// exited), or we get a different error code. We'll also set a 45-second
// timeout, with 45 seconds being chosen because it's below the default
// 60 second HTTP request timeout of most language bindings.
if (FAILED(hr) && HRESULT_CODE(hr) == ERROR_SHUTDOWN_IS_SCHEDULED) {
LOG(DEBUG) << "CoCreateInstance for IWebBrowser2 failed due to a "
<< "browser process that has not yet fully exited. Retrying "
<< "until the browser process exits and a new instance can "
<< "be successfully created.";
}
clock_t timeout = clock() + (45 * CLOCKS_PER_SEC);
while (FAILED(hr) &&
HRESULT_CODE(hr) == ERROR_SHUTDOWN_IS_SCHEDULED &&
clock() < timeout) {
::Sleep(500);
hr = ::CoCreateInstance(CLSID_InternetExplorer,
NULL,
context,
IID_IWebBrowser2,
reinterpret_cast<void**>(&browser));
}
if (FAILED(hr) && HRESULT_CODE(hr) != ERROR_SHUTDOWN_IS_SCHEDULED) {
// If we hit this branch, the CoCreateInstance failed due to an unexpected
// error, either before we looped, or at some point during the loop. In
// in either case, there's not much else we can do except log the failure.
LOGHR(WARN, hr) << "CoCreateInstance for IWebBrowser2 failed.";
}

if (browser != NULL) {
browser->put_Visible(VARIANT_TRUE);
}
if (this->windows_major_version_ >= 6) {
// Only Windows Vista and above have mandatory integrity levels.
this->ResetThreadIntegrityLevel();
}

return browser;
}
Expand Down Expand Up @@ -666,41 +693,6 @@ bool BrowserFactory::CreateLowIntegrityLevelToken(HANDLE* process_token_handle,
return result == TRUE;
}

void BrowserFactory::SetThreadIntegrityLevel() {
LOG(TRACE) << "Entering BrowserFactory::SetThreadIntegrityLevel";

HANDLE process_token = NULL;
HANDLE thread_token = NULL;
PSID sid = NULL;
bool token_created = this->CreateLowIntegrityLevelToken(&process_token,
&thread_token,
&sid);
if (token_created) {
HANDLE thread_handle = ::GetCurrentThread();
BOOL result = ::SetThreadToken(&thread_handle, thread_token);
if (!result) {
// If we encounter an error, not bloody much we can do about it.
// Just log it and continue.
LOG(WARN) << "SetThreadToken returned FALSE";
}
result = ::ImpersonateLoggedOnUser(thread_token);
if (!result) {
// If we encounter an error, not bloody much we can do about it.
// Just log it and continue.
LOG(WARN) << "ImpersonateLoggedOnUser returned FALSE";
}

::CloseHandle(thread_token);
::CloseHandle(process_token);
::LocalFree(sid);
}
}

void BrowserFactory::ResetThreadIntegrityLevel() {
LOG(TRACE) << "Entering BrowserFactory::ResetThreadIntegrityLevel";
::RevertToSelf();
}

void BrowserFactory::InvokeClearCacheUtility(bool use_low_integrity_level) {
HRESULT hr = S_OK;
std::vector<wchar_t> system_path_buffer(MAX_PATH);
Expand Down
2 changes: 0 additions & 2 deletions cpp/iedriver/BrowserFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,6 @@ class BrowserFactory {
UINT html_getobject_msg_;
HINSTANCE oleacc_instance_handle_;

void SetThreadIntegrityLevel(void);
void ResetThreadIntegrityLevel(void);
bool CreateLowIntegrityLevelToken(HANDLE* process_token_handle,
HANDLE* mic_token_handle,
PSID* sid);
Expand Down
Loading

0 comments on commit 0da4a96

Please sign in to comment.