text: // 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 "BrowserFactory.h"
  #include <ctime>
  #include <iostream>
  #include <WinInet.h>
  #include "logging.h"
  #include "psapi.h"
  #include "RegistryUtilities.h"
  
  namespace webdriver {
  
  BrowserFactory::BrowserFactory(void) {
  }
  
  BrowserFactory::~BrowserFactory(void) {
    if (this->oleacc_instance_handle_) {
      ::FreeLibrary(this->oleacc_instance_handle_);
    }
  }
  
  void BrowserFactory::Initialize(BrowserFactorySettings settings) {
    LOG(TRACE) << "Entering BrowserFactory::Initialize";
    this->ignore_protected_mode_settings_ = settings.ignore_protected_mode_settings;
    this->ignore_zoom_setting_ = settings.ignore_zoom_setting;
    this->browser_attach_timeout_ = settings.browser_attach_timeout;
    this->force_createprocess_api_ = settings.force_create_process_api;
    this->clear_cache_ = settings.clear_cache_before_launch;
    this->browser_command_line_switches_ = StringUtilities::ToWString(settings.browser_command_line_switches);
    this->initial_browser_url_ = StringUtilities::ToWString(settings.initial_browser_url);
  
    this->GetExecutableLocation();
    this->GetIEVersion();
    this->GetOSVersion();
    this->html_getobject_msg_ = ::RegisterWindowMessage(HTML_GETOBJECT_MSG);
  
    // Explicitly load MSAA so we know if it's installed
    this->oleacc_instance_handle_ = ::LoadLibrary(OLEACC_LIBRARY_NAME);
  }
  
  void BrowserFactory::ClearCache() {
    if (this->clear_cache_) {
      if (this->windows_major_version_ >= 6) {
        this->InvokeClearCacheUtility(true);
      }
      this->InvokeClearCacheUtility(false);
    }
  }
  
  DWORD BrowserFactory::LaunchBrowserProcess(std::string* error_message) {
    LOG(TRACE) << "Entering BrowserFactory::LaunchBrowserProcess";
  
    DWORD process_id = NULL;
    bool has_valid_protected_mode_settings = false;
    LOG(DEBUG) << "Ignoring Protected Mode Settings: "
               << this->ignore_protected_mode_settings_;
    if (!this->ignore_protected_mode_settings_) {
      LOG(DEBUG) << "Checking validity of Protected Mode settings.";
      has_valid_protected_mode_settings = this->ProtectedModeSettingsAreValid();
    }
    LOG(DEBUG) << "Has Valid Protected Mode Settings: "
               << has_valid_protected_mode_settings;
    if (this->ignore_protected_mode_settings_ || has_valid_protected_mode_settings) {
      // Determine which launch API to use.
      bool use_createprocess_api = false;
      if (this->force_createprocess_api_) {
        if (this->IsCreateProcessApiAvailable()) {
          use_createprocess_api = true;
        } else {
          // The only time IsCreateProcessApiAvailable will return false
          // is when the user is using IE 8 or higher, and does not have
          // the correct registry key setting to force the same process
          // for the enclosing window and tab processes.
          *error_message = CREATEPROCESS_REGISTRY_ERROR_MESSAGE;
          return NULL;
        }
      } else {
        // If we have the IELaunchURL API, expressly use it. Otherwise,
        // fall back to using CreateProcess().
        if (!this->IsIELaunchURLAvailable()) {
          use_createprocess_api = true;
        }
      }
  
      this->ClearCache();
  
      PROCESS_INFORMATION proc_info;
      ::ZeroMemory(&proc_info, sizeof(proc_info));
  
      if (!use_createprocess_api) {
        this->LaunchBrowserUsingIELaunchURL(&proc_info, error_message);
      } else {
        this->LaunchBrowserUsingCreateProcess(&proc_info, error_message);
      }
  
      process_id = proc_info.dwProcessId;
      if (process_id == NULL) {
        // If whatever API we are using failed to launch the browser, we should
        // have a NULL value in the dwProcessId member of the PROCESS_INFORMATION
        // structure. In that case, we will have already set the approprate error
        // message. On the off chance that we haven't yet set the appropriate
        // error message, that means we successfully launched the browser (i.e.,
        // the browser launch API returned a success code), but we still have a
        // NULL process ID.
        if (error_message->size() == 0) {
          std::string launch_api_name = use_createprocess_api ? "The CreateProcess API" : "The IELaunchURL API";
          *error_message = launch_api_name + NULL_PROCESS_ID_ERROR_MESSAGE;
        }
      } else {
        ::WaitForInputIdle(proc_info.hProcess, 2000);
        LOG(DEBUG) << "IE launched successfully with process ID " << process_id;
        std::vector<wchar_t> image_buffer(MAX_PATH);
        int buffer_count = ::GetProcessImageFileName(proc_info.hProcess, &image_buffer[0], MAX_PATH);
        std::wstring full_image_path = &image_buffer[0];
        size_t last_delimiter = full_image_path.find_last_of('\\');
        std::string image_name = StringUtilities::ToString(full_image_path.substr(last_delimiter + 1, buffer_count - last_delimiter));
        LOG(DEBUG) << "Process with ID " << process_id << " is executing " << image_name;
      }
  
      if (proc_info.hThread != NULL) {
        ::CloseHandle(proc_info.hThread);
      }
  
      if (proc_info.hProcess != NULL) {
        ::CloseHandle(proc_info.hProcess);
      }
  
    } else {
      *error_message = PROTECTED_MODE_SETTING_ERROR_MESSAGE;
    }
    return process_id;
  }
  
  bool BrowserFactory::IsIELaunchURLAvailable() {
    LOG(TRACE) << "Entering BrowserFactory::IsIELaunchURLAvailable";
    bool api_is_available = false;
    HMODULE library_handle = ::LoadLibrary(IEFRAME_LIBRARY_NAME);
    if (library_handle != NULL) {
      FARPROC proc_address = 0;
      proc_address = ::GetProcAddress(library_handle, IELAUNCHURL_FUNCTION_NAME);
      if (proc_address == NULL || proc_address == 0) {
        LOGERR(DEBUG) << "Unable to get address of " << IELAUNCHURL_FUNCTION_NAME 
                      << " method in " << IEFRAME_LIBRARY_NAME;
      } else {
        api_is_available = true;
      }
      ::FreeLibrary(library_handle);
    } else {
      LOGERR(DEBUG) << "Unable to load library " << IEFRAME_LIBRARY_NAME;
    }
    return api_is_available;
  }
  
  void BrowserFactory::LaunchBrowserUsingIELaunchURL(PROCESS_INFORMATION* proc_info,
                                                     std::string* error_message) {
    LOG(TRACE) << "Entering BrowserFactory::LaunchBrowserUsingIELaunchURL";
    LOG(DEBUG) << "Starting IE using the IELaunchURL API";
    HRESULT launch_result = ::IELaunchURL(this->initial_browser_url_.c_str(),
                                          proc_info,
                                          NULL);
    if (FAILED(launch_result)) {
      LOGHR(WARN, launch_result) << "Error using IELaunchURL to start IE";
      *error_message = StringUtilities::Format(IELAUNCHURL_ERROR_MESSAGE,
                                               launch_result,
                                               this->initial_browser_url().c_str());
    }
  }
  
  bool BrowserFactory::IsCreateProcessApiAvailable() {
    LOG(TRACE) << "Entering BrowserFactory::IsCreateProcessApiAvailable";
    if (this->ie_major_version_ >= 8) {
      // According to http://blogs.msdn.com/b/askie/archive/2009/03/09/opening-a-new-tab-may-launch-a-new-process-with-internet-explorer-8-0.aspx
      // If CreateProcess() is used and TabProcGrowth != 0 IE will use different tab and frame processes.
      // Such behaviour is not supported by AttachToBrowser().
      // FYI, IELaunchURL() returns correct 'frame' process (but sometimes not).
      std::wstring tab_proc_growth;
      if (RegistryUtilities::GetRegistryValue(HKEY_CURRENT_USER,
                                              IE_TABPROCGROWTH_REGISTRY_KEY,
                                              L"TabProcGrowth",
                                              &tab_proc_growth)) {
        if (tab_proc_growth != L"0") {
          // Registry value has wrong value, return false
          return false;
        }
      } else {
        // Registry key or value not found, or another error condition getting the value.
        return false;
      }
    }
    return true;
  }
  
  void BrowserFactory::LaunchBrowserUsingCreateProcess(PROCESS_INFORMATION* proc_info,
                                                       std::string* error_message) {
    LOG(TRACE) << "Entering BrowserFactory::LaunchBrowserUsingCreateProcess";
    LOG(DEBUG) << "Starting IE using the CreateProcess API";
  
    STARTUPINFO start_info;
    ::ZeroMemory(&start_info, sizeof(start_info));
    start_info.cb = sizeof(start_info);
  
    std::wstring executable_and_url = this->ie_executable_location_;
    if (this->browser_command_line_switches_.size() != 0) {
      executable_and_url.append(L" ");
      executable_and_url.append(this->browser_command_line_switches_);
    }
    executable_and_url.append(L" ");
    executable_and_url.append(this->initial_browser_url_);
  
    LOG(TRACE) << "IE starting command line is: '" << LOGWSTRING(executable_and_url) << "'.";
  
    LPWSTR command_line = new WCHAR[executable_and_url.size() + 1];
    wcscpy_s(command_line,
             executable_and_url.size() + 1,
             executable_and_url.c_str());
    command_line[executable_and_url.size()] = L'\0';
    BOOL create_process_result = ::CreateProcess(NULL,
                                                 command_line,
                                                 NULL,
                                                 NULL,
                                                 FALSE,
                                                 0,
                                                 NULL,
                                                 NULL,
                                                 &start_info,
                                                 proc_info);
    if (!create_process_result) {
      *error_message = StringUtilities::Format(CREATEPROCESS_ERROR_MESSAGE,
                                               StringUtilities::ToString(command_line));
    }
    delete[] command_line;
  }
  
  bool BrowserFactory::GetDocumentFromWindowHandle(HWND window_handle,
                                                   IHTMLDocument2** document) {
    LOG(TRACE) << "Entering BrowserFactory::GetDocumentFromWindowHandle";
  
    if (window_handle != NULL && this->oleacc_instance_handle_) {
      LRESULT result;
      ::SendMessageTimeout(window_handle,
                           this->html_getobject_msg_,
                           0L,
                           0L,
                           SMTO_ABORTIFHUNG,
                           1000,
                           reinterpret_cast<PDWORD_PTR>(&result));
  
      LPFNOBJECTFROMLRESULT object_pointer = reinterpret_cast<LPFNOBJECTFROMLRESULT>(::GetProcAddress(this->oleacc_instance_handle_, "ObjectFromLresult"));
      if (object_pointer != NULL) {
        HRESULT hr;
        hr = (*object_pointer)(result,
                               IID_IHTMLDocument2,
                               0,
                               reinterpret_cast<void**>(document));
        if (SUCCEEDED(hr)) {
          return true;
        } else {
          LOGHR(WARN, hr) << "Unable to convert document object pointer to IHTMLDocument2 object via ObjectFromLresult";
        }
      } else {
        LOG(WARN) << "Unable to get address of ObjectFromLresult method from library; GetProcAddress() for ObjectFromLresult returned NULL";
      }
    } else {
      LOG(WARN) << "Window handle is invalid or OLEACC.DLL is not loaded properly";
    }
    return false;
  }
  
  bool BrowserFactory::AttachToBrowser(ProcessWindowInfo* process_window_info,
                                       std::string* error_message) {
    LOG(TRACE) << "Entering BrowserFactory::AttachToBrowser";
    clock_t end = clock() + (this->browser_attach_timeout_ / 1000 * CLOCKS_PER_SEC);
    while (process_window_info->hwndBrowser == NULL) {
      if (this->browser_attach_timeout_ > 0 && (clock() > end)) {
        break;
      }
      ::EnumWindows(&BrowserFactory::FindBrowserWindow,
                    reinterpret_cast<LPARAM>(process_window_info));
      if (process_window_info->hwndBrowser == NULL) {
        ::Sleep(250);
      }
    }
  
    if (process_window_info->hwndBrowser == NULL) {
      *error_message = StringUtilities::Format(ATTACH_TIMEOUT_ERROR_MESSAGE,
                                               process_window_info->dwProcessId,
                                               this->browser_attach_timeout_);
      return false;
    }
  
    CComPtr<IHTMLDocument2> document;
    if (this->GetDocumentFromWindowHandle(process_window_info->hwndBrowser,
                                          &document)) {
      CComPtr<IHTMLWindow2> window;
      HRESULT hr = document->get_parentWindow(&window);
  
      // Test for zoom level = 100%
      int zoom_level = 100;
      LOG(DEBUG) << "Ignoring zoom setting: " << this->ignore_zoom_setting_;
      if (!this->ignore_zoom_setting_) {
        zoom_level = this->GetZoomLevel(document, window);
      }
      if (zoom_level != 100) {
        *error_message = StringUtilities::Format(ZOOM_SETTING_ERROR_MESSAGE,
                                                 zoom_level);
        return false;
      }
      if (SUCCEEDED(hr)) {
        // http://support.microsoft.com/kb/257717
        CComPtr<IServiceProvider> provider;
        window->QueryInterface<IServiceProvider>(&provider);
        if (provider) {
          CComPtr<IServiceProvider> child_provider;
          hr = provider->QueryService(SID_STopLevelBrowser,
                                      IID_IServiceProvider,
                                      reinterpret_cast<void**>(&child_provider));
          if (SUCCEEDED(hr)) {
            IWebBrowser2* browser;
            hr = child_provider->QueryService(SID_SWebBrowserApp,
                                              IID_IWebBrowser2,
                                              reinterpret_cast<void**>(&browser));
            if (SUCCEEDED(hr)) {
              process_window_info->pBrowser = browser;
              return true;
            } else {
              LOGHR(WARN, hr) << "IServiceProvider::QueryService for SID_SWebBrowserApp failed";
            }
          } else {
            LOGHR(WARN, hr) << "IServiceProvider::QueryService for SID_STopLevelBrowser failed";
          }
        } else {
          LOG(WARN) << "QueryInterface for IServiceProvider failed";
        }
      } else {
        LOGHR(WARN, hr) << "Call to IHTMLDocument2::get_parentWindow failed";
      }
    } else {
      *error_message = "Could not get document from window handle";
    }
    return false;
  }
  
  int BrowserFactory::GetZoomLevel(IHTMLDocument2* document, IHTMLWindow2* window) {
    LOG(TRACE) << "Entering BrowserFactory::GetZoomLevel";
    int zoom = 100;  // Chances are the zoom level hasn't been modified....
    HRESULT hr = S_OK;
    if (this->ie_major_version_ == 7) {
      CComPtr<IHTMLElement> body;
      hr = document->get_body(&body);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Call to IHTMLDocument2::get_body failed";
        return zoom;
      }
  
      long offset_width = 0;
      hr = body->get_offsetWidth(&offset_width);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Call to IHTMLElement::get_offsetWidth failed";
        return zoom;
      }
  
      CComPtr<IHTMLElement2> body2;
      hr = body.QueryInterface<IHTMLElement2>(&body2);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Attempt to QueryInterface for IHTMLElement2 failed";
        return zoom;
      }
  
      CComPtr<IHTMLRect> rect;
      hr = body2->getBoundingClientRect(&rect);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Call to IHTMLElement2::getBoundingClientRect failed";
        return zoom;
      }
  
      long left = 0, right = 0;
      hr = rect->get_left(&left);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Call to IHTMLRect::get_left failed";
        return zoom;
      }
  
      hr = rect->get_right(&right);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Call to IHTMLRect::get_right failed";
        return zoom;
      }
  
      zoom = static_cast<int>((static_cast<double>(right - left) / offset_width) * 100.0);
    } else if (this->ie_major_version_ >= 8) {
      CComPtr<IHTMLScreen> screen;
      hr = window->get_screen(&screen);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Call to IHTMLWindow2::get_screen failed";
        return zoom;
      }
  
      CComPtr<IHTMLScreen2> screen2;
      hr = screen.QueryInterface<IHTMLScreen2>(&screen2);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Attempt to QueryInterface for IHTMLScreen2 failed";
        return zoom;
      }
  
      long device_xdpi=0, logical_xdpi = 0;
      hr = screen2->get_deviceXDPI(&device_xdpi);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Call to IHTMLScreen2::get_deviceXDPI failed";
        return zoom;
      }
  
      hr = screen2->get_logicalXDPI(&logical_xdpi);
      if (FAILED(hr)) {
        LOGHR(WARN, hr) << "Call to IHTMLScreen2::get_logicalXDPI failed";
        return zoom;
      }
  
      zoom = static_cast<int>((static_cast<double>(device_xdpi) / logical_xdpi) * 100.0);
    } else {
      // IE6 case
      zoom = 100;
    }
  
    LOG(DEBUG) << "Browser zoom level is " << zoom << "%";
    return zoom;
  }
  
  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();
    }
  
    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));
    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;
  }
  
  bool BrowserFactory::CreateLowIntegrityLevelToken(HANDLE* process_token_handle,
                                                    HANDLE* mic_token_handle,
                                                    PSID* sid) {
    LOG(TRACE) << "Entering BrowserFactory::CreateLowIntegrityLevelToken";
    BOOL result = TRUE;
    TOKEN_MANDATORY_LABEL tml = {0};
  
    HANDLE process_handle = ::GetCurrentProcess();
    result = ::OpenProcessToken(process_handle,
                                MAXIMUM_ALLOWED,
                                process_token_handle);
  
    if (result) {
      result = ::DuplicateTokenEx(*process_token_handle,
                                  MAXIMUM_ALLOWED,
                                  NULL,
                                  SecurityImpersonation,
                                  TokenPrimary,
                                  mic_token_handle);
      if (!result) {
        ::CloseHandle(*process_token_handle);
      }
     }
  
    if (result) {     
      result = ::ConvertStringSidToSid(SDDL_ML_LOW, sid);
      if (result) {
        tml.Label.Attributes = SE_GROUP_INTEGRITY;
        tml.Label.Sid = *sid;
      } else {
        ::CloseHandle(*process_token_handle);
        ::CloseHandle(*mic_token_handle);
      }
    }
  
    if(result) {
      result = ::SetTokenInformation(*mic_token_handle,
                                     TokenIntegrityLevel,
                                     &tml,
                                     sizeof(tml) + ::GetLengthSid(*sid));
      if (!result) {
        ::CloseHandle(*process_token_handle);
        ::CloseHandle(*mic_token_handle);
        ::LocalFree(*sid);
      }
    }
  
    ::CloseHandle(process_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);
    std::vector<wchar_t> rundll_exe_path_buffer(MAX_PATH);
    std::vector<wchar_t> inetcpl_path_buffer(MAX_PATH);
    std::wstring args = L"";
  
    UINT system_path_size = ::GetSystemDirectory(&system_path_buffer[0], MAX_PATH);
  
    HANDLE  process_token = NULL;
    HANDLE  mic_token = NULL;
    PSID    sid = NULL;
  
    bool can_create_process = true;
    if (!use_low_integrity_level || 
        this->CreateLowIntegrityLevelToken(&process_token, &mic_token, &sid)) {
      if (0 != system_path_size &&
          system_path_size <= static_cast<int>(system_path_buffer.size())) {
        if (::PathCombine(&rundll_exe_path_buffer[0],
                          &system_path_buffer[0],
                          RUNDLL_EXE_NAME) && 
            ::PathCombine(&inetcpl_path_buffer[0],
                          &system_path_buffer[0],
                          INTERNET_CONTROL_PANEL_APPLET_NAME)) {
          // PathCombine will return NULL if the buffer would be exceeded.
          ::PathQuoteSpaces(&rundll_exe_path_buffer[0]);
          ::PathQuoteSpaces(&inetcpl_path_buffer[0]);
          args = StringUtilities::Format(CLEAR_CACHE_COMMAND_LINE_ARGS,
                                         &inetcpl_path_buffer[0],
                                         CLEAR_CACHE_OPTIONS);
        } else {
          can_create_process = false;
        }
      } else {
        can_create_process = false;
      }
  
      if (can_create_process) {
        STARTUPINFO start_info;
        ::ZeroMemory(&start_info, sizeof(start_info));
        start_info.cb = sizeof(start_info);
  
        PROCESS_INFORMATION process_info;
        BOOL is_process_created = FALSE;
        start_info.dwFlags = STARTF_USESHOWWINDOW;
        start_info.wShowWindow = SW_SHOWNORMAL;
  
        std::vector<wchar_t> args_buffer(0);
        StringUtilities::ToBuffer(args, &args_buffer);
        // Create the process to run with low or medium rights
        if (use_low_integrity_level) {
          is_process_created = CreateProcessAsUser(mic_token,
                                                   &rundll_exe_path_buffer[0],
                                                   &args_buffer[0],
                                                   NULL,
                                                   NULL,
                                                   FALSE,
                                                   0,
                                                   NULL,
                                                   NULL,
                                                   &start_info,
                                                   &process_info);
        } else {
          is_process_created = CreateProcess(&rundll_exe_path_buffer[0],
                                             &args_buffer[0],
                                             NULL,
                                             NULL,
                                             FALSE,
                                             0,
                                             NULL,
                                             NULL,
                                             &start_info,
                                             &process_info);
        }
  
        if (is_process_created) {
          // Wait for the rundll32.exe process to exit.
          ::WaitForInputIdle(process_info.hProcess, 5000);
          ::WaitForSingleObject(process_info.hProcess, 30000);
          ::CloseHandle(process_info.hProcess);
          ::CloseHandle(process_info.hThread);
        }
      }
  
      // Close the handles opened when creating the
      // low integrity level token
      if (use_low_integrity_level) {
        ::CloseHandle(process_token);
        ::CloseHandle(mic_token);
        ::LocalFree(sid);
      }
    }    
  }
  
  BOOL CALLBACK BrowserFactory::FindBrowserWindow(HWND hwnd, LPARAM arg) {
    // Could this be an IE instance?
    // 8 == "IeFrame\0"
    // 21 == "Shell DocObject View\0";
    char name[21];
    if (::GetClassNameA(hwnd, name, 21) == 0) {
      // No match found. Skip
      return TRUE;
    }
    
    if (strcmp(IE_FRAME_WINDOW_CLASS, name) != 0 && 
        strcmp(SHELL_DOCOBJECT_VIEW_WINDOW_CLASS, name) != 0) {
      return TRUE;
    }
  
    return EnumChildWindows(hwnd, FindChildWindowForProcess, arg);
  }
  
  BOOL CALLBACK BrowserFactory::FindChildWindowForProcess(HWND hwnd, LPARAM arg) {
    ProcessWindowInfo *process_window_info = reinterpret_cast<ProcessWindowInfo*>(arg);
  
    // Could this be an Internet Explorer Server window?
    // 25 == "Internet Explorer_Server\0"
    char name[25];
    if (::GetClassNameA(hwnd, name, 25) == 0) {
      // No match found. Skip
      return TRUE;
    }
    
    if (strcmp(IE_SERVER_CHILD_WINDOW_CLASS, name) != 0) {
      return TRUE;
    } else {
      DWORD process_id = NULL;
      ::GetWindowThreadProcessId(hwnd, &process_id);
      if (process_window_info->dwProcessId == process_id) {
        // Once we've found the first Internet Explorer_Server window
        // for the process we want, we can stop.
        process_window_info->hwndBrowser = hwnd;
        return FALSE;
      }
    }
  
    return TRUE;
  }
  
  BOOL CALLBACK BrowserFactory::FindDialogWindowForProcess(HWND hwnd, LPARAM arg) {
    ProcessWindowInfo* process_win_info = reinterpret_cast<ProcessWindowInfo*>(arg);
  
    // Could this be an dialog window?
    // 7 == "#32770\0"
    // 34 == "Internet Explorer_TridentDlgFrame\0"
    char name[34];
    if (::GetClassNameA(hwnd, name, 34) == 0) {
      // No match found. Skip
      return TRUE;
    }
    
    if (strcmp(ALERT_WINDOW_CLASS, name) != 0 && 
        strcmp(HTML_DIALOG_WINDOW_CLASS, name) != 0) {
      return TRUE;
    } else {
      // If the window style has the WS_DISABLED bit set or the 
      // WS_VISIBLE bit unset, it can't be handled via the UI, 
      // and must not be a visible dialog. Furthermore, if the
      // window style does not display a caption bar, it's not a
      // dialog displayed by the browser, but likely by an add-on
      // (like an antivirus toolbar). Note that checking the caption
      // window style is a hack, and may begin to fail if IE ever
      // changes the style of its alert windows.
      long window_long_style = ::GetWindowLong(hwnd, GWL_STYLE);
      if ((window_long_style & WS_DISABLED) != 0 ||
          (window_long_style & WS_VISIBLE) == 0 ||
          (window_long_style & WS_CAPTION) == 0) {
        return TRUE;
      }
      DWORD process_id = NULL;
      ::GetWindowThreadProcessId(hwnd, &process_id);
      if (process_win_info->dwProcessId == process_id) {
        // Once we've found the first dialog (#32770) window
        // for the process we want, we can stop.
        process_win_info->hwndBrowser = hwnd;
        return FALSE;
      }
    }
  
    return TRUE;
  }
  
  void BrowserFactory::GetExecutableLocation() {
    LOG(TRACE) << "Entering BrowserFactory::GetExecutableLocation";
  
    std::wstring class_id;
    if (RegistryUtilities::GetRegistryValue(HKEY_LOCAL_MACHINE,
                                            IE_CLSID_REGISTRY_KEY,
                                            L"",
                                            &class_id)) {
      std::wstring location_key = L"SOFTWARE\\Classes\\CLSID\\" + 
                                  class_id +
                                  L"\\LocalServer32";
      std::wstring executable_location;
  
      if (RegistryUtilities::GetRegistryValue(HKEY_LOCAL_MACHINE,
                                              location_key,
                                              L"",
                                              &executable_location)) {
        // If the executable location in the registry has an environment
        // variable in it, expand the environment variable to an absolute
        // path.
        DWORD expanded_location_size = ::ExpandEnvironmentStrings(executable_location.c_str(), NULL, 0);
        std::vector<wchar_t> expanded_location(expanded_location_size);
        ::ExpandEnvironmentStrings(executable_location.c_str(), &expanded_location[0], expanded_location_size);
        executable_location = &expanded_location[0];
        this->ie_executable_location_ = executable_location;
        size_t arg_start_pos = executable_location.find(L" -");
        if (arg_start_pos != std::string::npos) {
          this->ie_executable_location_ = executable_location.substr(0, arg_start_pos);
        }
        if (this->ie_executable_location_.substr(0, 1) == L"\"") {
          this->ie_executable_location_.erase(0, 1);
          this->ie_executable_location_.erase(this->ie_executable_location_.size() - 1, 1);
        }
      } else {
        LOG(WARN) << "Unable to get IE executable location from registry";
      }
    } else {
      LOG(WARN) << "Unable to get IE class id from registry";
    }
  }
  
  void BrowserFactory::GetIEVersion() {
    LOG(TRACE) << "Entering BrowserFactory::GetIEVersion";
  
    struct LANGANDCODEPAGE {
      WORD language;
      WORD code_page;
      } *lpTranslate;
  
    DWORD dummy = 0;
    DWORD length = ::GetFileVersionInfoSize(this->ie_executable_location_.c_str(),
                                            &dummy);
    if (length == 0) {
      // 64-bit Windows 8 has a bug where it does not return the executable location properly
      this->ie_major_version_ = -1;
      LOG(WARN) << "Couldn't find IE version for executable "
                 << LOGWSTRING(this->ie_executable_location_)
                 << ", falling back to "
                 << this->ie_major_version_;
      return;
    }
    std::vector<BYTE> version_buffer(length);
    ::GetFileVersionInfo(this->ie_executable_location_.c_str(),
                         0, /* ignored */
                         length,
                         &version_buffer[0]);
  
    UINT page_count;
    BOOL query_result = ::VerQueryValue(&version_buffer[0],
                                        FILE_LANGUAGE_INFO,
                                        reinterpret_cast<void**>(&lpTranslate),
                                        &page_count);
      
    wchar_t sub_block[MAX_PATH];
    _snwprintf_s(sub_block,
                 MAX_PATH,
                 MAX_PATH,
                 FILE_VERSION_INFO,
                 lpTranslate->language,
                 lpTranslate->code_page);
    LPVOID value = NULL;
    UINT size;
    query_result = ::VerQueryValue(&version_buffer[0],
                                   sub_block,
                                   &value,
                                   &size);
    std::wstring ie_version;
    ie_version.assign(static_cast<wchar_t*>(value));
    std::wstringstream version_stream(ie_version);
    version_stream >> this->ie_major_version_;
  }
  
  void BrowserFactory::GetOSVersion() {
    LOG(TRACE) << "Entering BrowserFactory::GetOSVersion";
  
    OSVERSIONINFO osVersion;
    osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    ::GetVersionEx(&osVersion);
    this->windows_major_version_ = osVersion.dwMajorVersion;
    this->windows_minor_version_ = osVersion.dwMinorVersion;
  }
  
  bool BrowserFactory::ProtectedModeSettingsAreValid() {
    LOG(TRACE) << "Entering BrowserFactory::ProtectedModeSettingsAreValid";
  
    bool settings_are_valid = true;
    LOG(DEBUG) << "Detected IE version: " << this->ie_major_version_
               << ", detected Windows version: " << this->windows_major_version_;
    // Only need to check Protected Mode settings on IE 7 or higher
    // and on Windows Vista or higher. Otherwise, Protected Mode
    // doesn't come into play, and are valid.
    // Documentation of registry settings can be found at the following
    // Microsoft KnowledgeBase article:
    // http://support.microsoft.com/kb/182569
    if (this->ie_major_version_ >= 7 && this->windows_major_version_ >= 6) {
      HKEY key_handle;
      if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_CURRENT_USER,
                                          IE_SECURITY_ZONES_REGISTRY_KEY,
                                          0,
                                          KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS,
                                          &key_handle)) {
        DWORD subkey_count = 0;
        DWORD max_subkey_name_length = 0;
        if (ERROR_SUCCESS == ::RegQueryInfoKey(key_handle,
                                               NULL,
                                               NULL,
                                               NULL,
                                               &subkey_count,
                                               &max_subkey_name_length,
                                               NULL,
                                               NULL,
                                               NULL,
                                               NULL,
                                               NULL,
                                               NULL)) {
          int protected_mode_value = -1;
          std::vector<wchar_t> subkey_name_buffer(max_subkey_name_length + 1);
          for (size_t index = 0; index < subkey_count; ++index) {
            DWORD number_of_characters_copied = max_subkey_name_length + 1;
            ::RegEnumKeyEx(key_handle,
                           static_cast<DWORD>(index),
                           &subkey_name_buffer[0],
                           &number_of_characters_copied,
                           NULL,
                           NULL,
                           NULL,
                           NULL);
            std::wstring subkey_name = &subkey_name_buffer[0];
            // Ignore the "My Computer" zone, since it's not displayed
            // in the UI.
            if (subkey_name != ZONE_MY_COMPUTER) {
              int value = this->GetZoneProtectedModeSetting(key_handle,
                                                            subkey_name);
              if (protected_mode_value == -1) {
                protected_mode_value = value;
              } else {
                if (value != protected_mode_value) {
                  settings_are_valid = false;
                  break;
                }
              }
            }
          }
        } else {
          LOG(WARN) << "RegQueryInfoKey to get count of zone setting subkeys failed";
        }
        ::RegCloseKey(key_handle);
      } else {
        std::wstring registry_key_string = IE_SECURITY_ZONES_REGISTRY_KEY;
        LOG(WARN) << "RegOpenKeyEx for zone settings registry key "
                  << LOGWSTRING(registry_key_string)
                  << " in HKEY_CURRENT_USER failed";
      }
    }
    return settings_are_valid;
  }
  
  int BrowserFactory::GetZoneProtectedModeSetting(const HKEY key_handle,
                                                  const std::wstring& zone_subkey_name) {
    LOG(TRACE) << "Entering BrowserFactory::GetZoneProtectedModeSetting";
  
    int protected_mode_value = 3;
    HKEY subkey_handle;
    if (ERROR_SUCCESS == ::RegOpenKeyEx(key_handle,
                                        zone_subkey_name.c_str(),
                                        0,
                                        KEY_QUERY_VALUE,
                                        &subkey_handle)) {
      DWORD value = 0;
      DWORD value_length = sizeof(DWORD);
      if (ERROR_SUCCESS == ::RegQueryValueEx(subkey_handle,
                                             IE_PROTECTED_MODE_SETTING_VALUE_NAME,
                                             NULL,
                                             NULL,
                                             reinterpret_cast<LPBYTE>(&value),
                                             &value_length)) {
        LOG(DEBUG) << "Found Protected Mode setting value of "
                   << value << " for zone " << LOGWSTRING(zone_subkey_name);
        protected_mode_value = value;
      } else {
        LOG(DEBUG) << "RegQueryValueEx failed for getting Protected Mode setting for a zone: "
                   << LOGWSTRING(zone_subkey_name);
      }
      ::RegCloseKey(subkey_handle);
    } else {
      // The REG_DWORD value doesn't exist, so we have to return the default
      // value, which is "on" for the Internet and Restricted Sites zones and
      // is "on" for the Local Intranet zone in IE7 only (the default was
      // changed to "off" for Local Intranet in IE8), and "off" everywhere
      // else.
      // Note that a value of 0 in the registry value indicates that Protected
      // Mode is "on" for that zone; a value of 3 indicates that Protected Mode
      // is "off" for that zone.
      if (zone_subkey_name == ZONE_INTERNET ||
          zone_subkey_name == ZONE_RESTRICTED_SITES ||
          (zone_subkey_name == ZONE_LOCAL_INTRANET && this->ie_major_version_ == 7)) {
        protected_mode_value = 0;
      }
      LOG(DEBUG) << "Protected Mode zone setting value does not exist for zone "
                 << LOGWSTRING(zone_subkey_name) << ". Using default value of "
                 << protected_mode_value;
    }
    return protected_mode_value;
  }
  
  } // namespace webdriver