// 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"); // 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 "IECommandExecutor.h" #include #include #include #include #include #include #include "command_types.h" #include "errorcodes.h" #include "logging.h" #include "response.h" #include "Alert.h" #include "Browser.h" #include "BrowserFactory.h" #include "CommandExecutor.h" #include "CommandHandlerRepository.h" #include "CookieManager.h" #include "Element.h" #include "ElementFinder.h" #include "ElementRepository.h" #include "IECommandHandler.h" #include "InputManager.h" #include "HtmlDialog.h" #include "ProxyManager.h" #include "StringUtilities.h" #include "Script.h" #include "WebDriverConstants.h" #include "WindowUtilities.h" #define MAX_HTML_DIALOG_RETRIES 5 #define WAIT_TIME_IN_MILLISECONDS 50 #define DEFAULT_SCRIPT_TIMEOUT_IN_MILLISECONDS 30000 #define DEFAULT_PAGE_LOAD_TIMEOUT_IN_MILLISECONDS 300000 #define DEFAULT_FILE_UPLOAD_DIALOG_TIMEOUT_IN_MILLISECONDS 3000 #define DEFAULT_BROWSER_REATTACH_TIMEOUT_IN_MILLISECONDS 10000 namespace webdriver { struct WaitThreadContext { HWND window_handle; bool is_deferred_command; LPSTR deferred_response; }; struct DelayPostMessageThreadContext { HWND window_handle; DWORD delay; UINT msg; }; LRESULT IECommandExecutor::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnCreate"; CREATESTRUCT* create = reinterpret_cast(lParam); IECommandExecutorThreadContext* context = reinterpret_cast(create->lpCreateParams); this->port_ = context->port; // NOTE: COM should be initialized on this thread, so we // could use CoCreateGuid() and StringFromGUID2() instead. UUID guid; RPC_WSTR guid_string = NULL; RPC_STATUS status = ::UuidCreate(&guid); status = ::UuidToString(&guid, &guid_string); // RPC_WSTR is currently typedef'd in RpcDce.h (pulled in by rpc.h) // as unsigned short*. It needs to be typedef'd as wchar_t* wchar_t* cast_guid_string = reinterpret_cast(guid_string); this->SetWindowText(cast_guid_string); std::string session_id = StringUtilities::ToString(cast_guid_string); this->session_id_ = session_id; this->is_valid_ = true; ::RpcStringFree(&guid_string); this->PopulateElementFinderMethods(); this->current_browser_id_ = ""; this->serialized_response_ = ""; this->unexpected_alert_behavior_ = ""; this->implicit_wait_timeout_ = 0; this->async_script_timeout_ = DEFAULT_SCRIPT_TIMEOUT_IN_MILLISECONDS; this->page_load_timeout_ = DEFAULT_PAGE_LOAD_TIMEOUT_IN_MILLISECONDS; this->reattach_browser_timeout_ = DEFAULT_BROWSER_REATTACH_TIMEOUT_IN_MILLISECONDS; this->is_waiting_ = false; this->is_quitting_ = false; this->is_awaiting_new_window_ = false; this->use_strict_file_interactability_ = false; this->page_load_strategy_ = "normal"; this->file_upload_dialog_timeout_ = DEFAULT_FILE_UPLOAD_DIALOG_TIMEOUT_IN_MILLISECONDS; this->managed_elements_ = new ElementRepository(); this->input_manager_ = new InputManager(); this->proxy_manager_ = new ProxyManager(); this->factory_ = new BrowserFactory(); this->element_finder_ = new ElementFinder(); this->command_handlers_ = new CommandHandlerRepository(); this->is_edge_chromium_ = false; this->edge_temp_dir_ = L""; return 0; } LRESULT IECommandExecutor::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(DEBUG) << "Entering IECommandExecutor::OnDestroy"; LOG(DEBUG) << "Clearing managed element cache"; this->managed_elements_->Clear(); delete this->managed_elements_; LOG(DEBUG) << "Closing command handler repository"; delete this->command_handlers_; LOG(DEBUG) << "Closing element finder"; delete this->element_finder_; LOG(DEBUG) << "Closing input manager"; delete this->input_manager_; LOG(DEBUG) << "Closing proxy manager"; delete this->proxy_manager_; LOG(DEBUG) << "Closing browser factory"; delete this->factory_; LOG(DEBUG) << "Posting quit message"; ::PostQuitMessage(0); LOG(DEBUG) << "Leaving IECommandExecutor::OnDestroy"; return 0; } LRESULT IECommandExecutor::OnSetCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnSetCommand"; LRESULT set_command_result = 0; LPCSTR json_command = reinterpret_cast(lParam); Command requested_command; requested_command.Deserialize(json_command); this->set_command_mutex_.lock(); if (this->current_command_.command_type() == CommandType::NoCommand || requested_command.command_type() == CommandType::Quit) { this->current_command_.Deserialize(json_command); set_command_result = 1; } this->set_command_mutex_.unlock(); return set_command_result; } LRESULT IECommandExecutor::OnExecCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnExecCommand"; this->DispatchCommand(); return 0; } LRESULT IECommandExecutor::OnGetResponseLength(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // Not logging trace entering IECommandExecutor::OnGetResponseLength, // because it is polled repeatedly for a non-zero return value. size_t response_length = 0; if (!this->is_waiting_) { response_length = this->serialized_response_.size(); } return response_length; } LRESULT IECommandExecutor::OnGetResponse(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnGetResponse"; LPSTR str = reinterpret_cast(lParam); strcpy_s(str, this->serialized_response_.size() + 1, this->serialized_response_.c_str()); // Reset the serialized response for the next command. this->serialized_response_ = ""; this->current_command_.Reset(); return 0; } LRESULT IECommandExecutor::OnWait(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnWait"; LPCSTR str = reinterpret_cast(lParam); std::string deferred_response(str); delete[] str; LOG(DEBUG) << "Starting wait cycle."; if (this->is_awaiting_new_window_) { LOG(DEBUG) << "Awaiting new window. Aborting current wait cycle and " << "scheduling another."; this->CreateWaitThread(deferred_response); return 0; } bool is_single_wait = (wParam == 0); BrowserHandle browser; int status_code = this->GetCurrentBrowser(&browser); if (status_code == WD_SUCCESS) { if (!browser->is_closing()) { if (this->page_load_timeout_ >= 0 && this->wait_timeout_ < clock()) { LOG(DEBUG) << "Page load timeout reached. Ending wait cycle."; Response timeout_response; timeout_response.SetErrorResponse(ERROR_WEBDRIVER_TIMEOUT, "Timed out waiting for page to load."); browser->set_wait_required(false); this->serialized_response_ = timeout_response.Serialize(); this->is_waiting_ = false; return 0; } else { LOG(DEBUG) << "Beginning wait."; this->is_waiting_ = !(browser->Wait(this->page_load_strategy_)); if (is_single_wait) { LOG(DEBUG) << "Single requested wait with no deferred " << "response complete. Ending wait cycle."; this->is_waiting_ = false; return 0; } else { if (this->is_waiting_) { LOG(DEBUG) << "Wait not complete. Scheduling another wait cycle."; this->CreateWaitThread(deferred_response); return 0; } } } } } LOG(DEBUG) << "Wait complete. Setting serialized response to deferred value " << deferred_response; this->serialized_response_ = deferred_response; this->is_waiting_ = false; return 0; } LRESULT IECommandExecutor::OnBeforeNewWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnBeforeNewWindow"; LOG(DEBUG) << "Setting await new window flag"; this->is_awaiting_new_window_ = true; return 0; } LRESULT IECommandExecutor::OnAfterNewWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnAfterNewWindow"; if (wParam > 0) { LOG(DEBUG) << "Creating thread and reposting message."; this->CreateDelayPostMessageThread(static_cast(wParam), this->m_hWnd, WD_AFTER_NEW_WINDOW); if (lParam > 0) { // a new window is created from Edge in IEMode BrowserHandle browser_wrapper; this->GetCurrentBrowser(&browser_wrapper); HWND top_level_handle = browser_wrapper->GetTopLevelWindowHandle(); std::vector* current_window_handles = reinterpret_cast*>(lParam); std::unordered_set current_window_set( current_window_handles->begin(), current_window_handles->end()); delete current_window_handles; // sleep 0.5s then get current window handles clock_t end = clock() + (DEFAULT_BROWSER_REATTACH_TIMEOUT_IN_MILLISECONDS / 1000 * CLOCKS_PER_SEC); std::vector diff; int loop_count = 0; bool is_processing_wm = false; ::Sleep(1000); while (diff.size() == 0 && clock() < end) { std::vector edge_window_handles; ::EnumWindows(&BrowserFactory::FindEdgeBrowserHandles, reinterpret_cast(&edge_window_handles)); std::vector new_ie_window_handles; for (auto& edge_window_handle : edge_window_handles) { std::vector child_window_handles; ::EnumChildWindows(edge_window_handle, &BrowserFactory::FindIEBrowserHandles, reinterpret_cast(&child_window_handles)); for (auto& child_window_handle : child_window_handles) { new_ie_window_handles.push_back(child_window_handle); } } for (auto& window_handle : new_ie_window_handles) { if (current_window_set.find(window_handle) != current_window_set.end()) { continue; } diff.push_back(window_handle); } if (diff.size() == 0) { MSG msg; if (loop_count >= 1 && !is_processing_wm &&::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); is_processing_wm = true; LOG(TRACE) << "process WM_USER"; ::Sleep(500); } ::Sleep(500); } loop_count++; } if (diff.size() == 0) { LOG(WARN) << "No new window handle found after attempt to open"; } else { for (int i = diff.size() - 1; i >= 0; i--) { HWND new_window_window = diff[i]; DWORD process_id = 0; ::GetWindowThreadProcessId(new_window_window, &process_id); if (process_id) { clock_t end = clock() + (DEFAULT_BROWSER_REATTACH_TIMEOUT_IN_MILLISECONDS / 1000 * CLOCKS_PER_SEC); bool is_ready = this->factory_->IsBrowserProcessInitialized(process_id); while (!is_ready && clock() < end) { ::Sleep(100); is_ready = this->factory_->IsBrowserProcessInitialized(process_id); } ProcessWindowInfo info; info.dwProcessId = process_id; info.hwndBrowser = new_window_window; info.pBrowser = NULL; std::string error_message = ""; bool attach_flag = this->factory_->AttachToBrowser(&info, &error_message); if (attach_flag) { BrowserHandle new_window_wrapper(new Browser(info.pBrowser, NULL, this->m_hWnd, this->is_edge_chromium_)); // Force a wait cycle to make sure the browser is finished initializing. new_window_wrapper->Wait(NORMAL_PAGE_LOAD_STRATEGY); this->AddManagedBrowser(new_window_wrapper); } } else { LOG(TRACE) << "invalid window " << new_window_window; } } } } } else { LOG(DEBUG) << "Clearing await new window flag"; this->is_awaiting_new_window_ = false; } return 0; } LRESULT IECommandExecutor::OnBrowserNewWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnBrowserNewWindow"; NewWindowInfo* info = reinterpret_cast(lParam); std::string target_url = info->target_url; std::string new_browser_id = this->OpenNewBrowsingContext(WINDOW_WINDOW_TYPE, target_url); BrowserHandle new_window_wrapper; this->GetManagedBrowser(new_browser_id, &new_window_wrapper); if (new_window_wrapper->IsCrossZoneUrl(target_url)) { new_window_wrapper->InitiateBrowserReattach(); } LOG(DEBUG) << "Attempting to marshal interface pointer to requesting thread."; IWebBrowser2* browser = new_window_wrapper->browser(); HRESULT hr = ::CoMarshalInterThreadInterfaceInStream(IID_IWebBrowser2, browser, &(info->browser_stream)); if (FAILED(hr)) { LOGHR(WARN, hr) << "Marshalling of interface pointer b/w threads is failed."; } return 0; } LRESULT IECommandExecutor::OnBrowserCloseWait(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnBrowserCloseWait"; LPCSTR str = reinterpret_cast(lParam); std::string browser_id(str); delete[] str; BrowserMap::iterator found_iterator = this->managed_browsers_.find(browser_id); if (found_iterator != this->managed_browsers_.end()) { HWND alert_handle; bool is_alert_active = this->IsAlertActive(found_iterator->second, &alert_handle); if (is_alert_active) { // If there's an alert window active, the browser's Quit event does // not fire until any alerts are handled. Note that OnBeforeUnload // alerts must be handled here; the driver contains the ability to // handle other standard alerts on the next received command. We rely // on the browser's Quit command to remove the driver from the list of // managed browsers. Alert dialog(found_iterator->second, alert_handle); if (!dialog.is_standard_alert()) { dialog.Accept(); is_alert_active = false; } } if (!is_alert_active) { ::Sleep(100); // If no alert is present, repost the message to the message pump, so // that we can wait until the browser is fully closed to return the // proper still-open list of window handles. LPSTR message_payload = new CHAR[browser_id.size() + 1]; strcpy_s(message_payload, browser_id.size() + 1, browser_id.c_str()); ::PostMessage(this->m_hWnd, WD_BROWSER_CLOSE_WAIT, NULL, reinterpret_cast(message_payload)); return 0; } } else { LOG(WARN) << "Unable to find browser to quit with ID " << browser_id; } Json::Value handles(Json::arrayValue); std::vector handle_list; this->GetManagedBrowserHandles(&handle_list); std::vector::const_iterator handle_iterator = handle_list.begin(); for (; handle_iterator != handle_list.end(); ++handle_iterator) { handles.append(*handle_iterator); } Response close_window_response; close_window_response.SetSuccessResponse(handles); this->serialized_response_ = close_window_response.Serialize(); this->is_waiting_ = false; return 0; } LRESULT IECommandExecutor::OnSessionQuitWait(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnAllBrowserCloseWait"; if (this->managed_browsers_.size() > 0) { LOG(TRACE) << "Still have " << this->managed_browsers_.size() << " browsers"; BrowserMap::const_iterator it = managed_browsers_.begin(); for (; it != managed_browsers_.end(); ++it) { LOG(TRACE) << "Still awaiting close of browser with ID " << it->first; HWND alert_handle; bool is_alert_active = this->IsAlertActive(it->second, &alert_handle); if (is_alert_active) { // If there's an alert window active, the browser's Quit event does // not fire until any alerts are handled. Note that OnBeforeUnload // alerts must be handled here; the driver contains the ability to // handle other standard alerts on the next received command. We rely // on the browser's Quit command to remove the driver from the list of // managed browsers. Alert dialog(it->second, alert_handle); if (!dialog.is_standard_alert()) { dialog.Accept(); is_alert_active = false; } } } ::Sleep(WAIT_TIME_IN_MILLISECONDS); ::PostMessage(this->m_hWnd, WD_SESSION_QUIT_WAIT, NULL, NULL); } else { for (auto& chromium_window_handle : this->chromium_window_handles_) { ::PostMessage(chromium_window_handle, WM_CLOSE, NULL, NULL); } this->is_waiting_ = false; Response quit_response; quit_response.SetSuccessResponse(Json::Value::null); this->serialized_response_ = quit_response.Serialize(); } return 0; } LRESULT IECommandExecutor::OnAddChromiumWindowHandle(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { if (wParam != NULL) { this->chromium_window_handles_.emplace(reinterpret_cast(wParam)); } return 0; } LRESULT IECommandExecutor::OnBrowserQuit(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnBrowserQuit"; LPCSTR str = reinterpret_cast(lParam); std::string browser_id(str); delete[] str; LOG(TRACE) << "Removing browser with ID " << browser_id; BrowserMap::iterator found_iterator = this->managed_browsers_.find(browser_id); if (found_iterator != this->managed_browsers_.end()) { this->managed_browsers_.erase(browser_id); if (this->managed_browsers_.size() == 0) { this->current_browser_id_ = ""; } LOG(TRACE) << "Successfully removed browser with ID " << browser_id; } else { LOG(WARN) << "Unable to find browser to quit with ID " << browser_id; } return 0; } LRESULT IECommandExecutor::OnBeforeBrowserReattach(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnBeforeBrowserReattach"; if (this->factory_->ignore_protected_mode_settings()) { this->reattach_wait_timeout_ = clock() + (static_cast(this->reattach_browser_timeout_) / 1000 * CLOCKS_PER_SEC); } return 0; } LRESULT IECommandExecutor::OnBrowserReattach(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnBrowserReattach"; BrowserReattachInfo* info = reinterpret_cast(lParam); DWORD current_process_id = info->current_process_id; std::string browser_id = info->browser_id; std::vector known_process_ids = info->known_process_ids; delete info; if (!this->factory_->ignore_protected_mode_settings()) { return 0; } if (this->reattach_wait_timeout_ < clock()) { LOG(WARN) << "Reattach attempt has timed out"; return 0; } LOG(DEBUG) << "Starting browser reattach process"; std::vector new_process_ids; this->GetNewBrowserProcessIds(&known_process_ids, &new_process_ids); if (new_process_ids.size() == 0) { LOG(DEBUG) << "No new process found, rescheduling reattach"; // If no new process IDs were found yet, repost the message this->PostBrowserReattachMessage(current_process_id, browser_id, known_process_ids); } if (new_process_ids.size() > 1) { LOG(WARN) << "Found more than one new iexplore.exe process. It is " << "impossible to know which is the proper one. Choosing one " << "at random."; } DWORD new_process_id = new_process_ids[0]; if (!this->factory_->IsBrowserProcessInitialized(new_process_id)) { // If the browser for the new process ID is not yet ready, // repost the message LOG(DEBUG) << "Browser process " << new_process_id << " not initialized, rescheduling reattach"; this->PostBrowserReattachMessage(current_process_id, browser_id, known_process_ids); return 0; } std::string error_message = ""; ProcessWindowInfo process_window_info; process_window_info.dwProcessId = new_process_id; process_window_info.hwndBrowser = NULL; process_window_info.pBrowser = NULL; bool attached = this->factory_->AttachToBrowser(&process_window_info, &error_message); BrowserMap::iterator found_iterator = this->managed_browsers_.find(browser_id); if (found_iterator != this->managed_browsers_.end()) { this->proxy_manager_->SetProxySettings(process_window_info.hwndBrowser); found_iterator->second->cookie_manager()->Initialize(process_window_info.hwndBrowser); found_iterator->second->ReattachBrowser(process_window_info.pBrowser); } else { LOG(WARN) << "The managed browser was not found to reattach to."; } return 0; } LRESULT IECommandExecutor::OnIsSessionValid(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnIsSessionValid"; return this->is_valid_ ? 1 : 0; } LRESULT IECommandExecutor::OnNewHtmlDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandles) { LOG(TRACE) << "Entering IECommandExecutor::OnNewHtmlDialog"; HWND dialog_handle = reinterpret_cast(lParam); BrowserMap::const_iterator it = this->managed_browsers_.begin(); for (; it != this->managed_browsers_.end(); ++it) { if (dialog_handle == it->second->window_handle()) { LOG(DEBUG) << "Dialog is equal to one managed browser"; return 0; } } int retry_count = 0; CComPtr document; bool found_document = this->factory_->GetDocumentFromWindowHandle(dialog_handle, &document); while (found_document && retry_count < MAX_HTML_DIALOG_RETRIES) { CComPtr window; HRESULT hr = document->get_parentWindow(&window); if (FAILED(hr)) { // Getting the parent window of the dialog's document failed. This // usually means that the document changed out from under us before we // could get the window reference. The canonical case for this is a // redirect using JavaScript. Sleep for a short time, then retry to // obtain the reference. LOGHR(DEBUG, hr) << "IHTMLDocument2::get_parentWindow failed. Retrying."; ::Sleep(100); document.Release(); found_document = this->factory_->GetDocumentFromWindowHandle(dialog_handle, &document); ++retry_count; } else { this->AddManagedBrowser(BrowserHandle(new HtmlDialog(window, dialog_handle, this->m_hWnd))); return 0; } } if (found_document) { LOG(WARN) << "Got document from dialog, but could not get window"; } else { LOG(WARN) << "Unable to get document from dialog"; } return 0; } LRESULT IECommandExecutor::OnQuit(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // Delete IEDriver temporary folder when IEDriver drvies Edge in IEMode. // Note that the this->factory_ object might have been deleted. if (this->edge_temp_dir_ != L"") { for (int i = 0; i < 100; i++) { // wait for the Edge browser completing read/write work // the delete usually completes in 1 retries ::Sleep(100); if (BrowserFactory::DeleteDirectory(edge_temp_dir_)) { // directory delete failed when some files/folders are locked LOG(TRACE) << "Failed to delete Edge temporary user data directory " << LOGWSTRING(edge_temp_dir_) << ", retrying " << i + 1 << "..."; } else { // the temporary folder has been deleted LOG(TRACE) << "Deleted Edge temporary user data directory " << LOGWSTRING(edge_temp_dir_) << "."; break; } } this->edge_temp_dir_ = L""; } return 0; } LRESULT IECommandExecutor::OnGetQuitStatus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { return this->is_quitting_ && this->managed_browsers_.size() > 0 ? 1 : 0; } LRESULT IECommandExecutor::OnScriptWait(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnScriptWait"; BrowserHandle browser; int status_code = this->GetCurrentBrowser(&browser); if (status_code == WD_SUCCESS && !browser->is_closing()) { if (this->async_script_timeout_ >= 0 && this->wait_timeout_ < clock()) { ::SendMessage(browser->script_executor_handle(), WD_ASYNC_SCRIPT_DETACH_LISTENTER, NULL, NULL); Response timeout_response; timeout_response.SetErrorResponse(ERROR_SCRIPT_TIMEOUT, "Timed out waiting for script to complete."); this->serialized_response_ = timeout_response.Serialize(); this->is_waiting_ = false; browser->set_script_executor_handle(NULL); } else { HWND alert_handle; bool is_execution_finished = ::SendMessage(browser->script_executor_handle(), WD_ASYNC_SCRIPT_IS_EXECUTION_COMPLETE, NULL, NULL) != 0; bool is_alert_active = this->IsAlertActive(browser, &alert_handle); this->is_waiting_ = !is_execution_finished && !is_alert_active; if (this->is_waiting_) { // If we are still waiting, we need to wait a bit then post a message to // ourselves to run the wait again. However, we can't wait using Sleep() // on this thread. This call happens in a message loop, and we would be // unable to process the COM events in the browser if we put this thread // to sleep. unsigned int thread_id = 0; HANDLE thread_handle = reinterpret_cast(_beginthreadex(NULL, 0, &IECommandExecutor::ScriptWaitThreadProc, (void *)this->m_hWnd, 0, &thread_id)); if (thread_handle != NULL) { ::CloseHandle(thread_handle); } else { LOGERR(DEBUG) << "Unable to create waiter thread"; } } else { Response response; Json::Value script_result; ::SendMessage(browser->script_executor_handle(), WD_ASYNC_SCRIPT_DETACH_LISTENTER, NULL, NULL); int status_code = static_cast(::SendMessage(browser->script_executor_handle(), WD_ASYNC_SCRIPT_GET_RESULT, NULL, reinterpret_cast(&script_result))); if (status_code != WD_SUCCESS) { std::string error_message = "Error executing JavaScript"; if (script_result.isString()) { error_message = script_result.asString(); } response.SetErrorResponse(status_code, error_message); } else { response.SetSuccessResponse(script_result); } ::SendMessage(browser->script_executor_handle(), WM_CLOSE, NULL, NULL); browser->set_script_executor_handle(NULL); this->serialized_response_ = response.Serialize(); } } } else { this->is_waiting_ = false; } return 0; } LRESULT IECommandExecutor::OnRefreshManagedElements(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { this->managed_elements_->ClearCache(); return 0; } LRESULT IECommandExecutor::OnHandleUnexpectedAlerts(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnHandleUnexpectedAlerts"; BrowserMap::const_iterator it = this->managed_browsers_.begin(); for (; it != this->managed_browsers_.end(); ++it) { HWND alert_handle = it->second->GetActiveDialogWindowHandle(); if (alert_handle != NULL) { std::string alert_text; this->HandleUnexpectedAlert(it->second, alert_handle, true, &alert_text); } } return 0; } LRESULT IECommandExecutor::OnTransferManagedElement(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnTransferManagedElement"; ElementInfo* info = reinterpret_cast(lParam); std::string element_id = info->element_id; LPSTREAM element_stream = info->element_stream; BrowserHandle browser_handle; this->GetCurrentBrowser(&browser_handle); CComPtr element; ::CoGetInterfaceAndReleaseStream(element_stream, IID_IHTMLElement, reinterpret_cast(&element)); delete info; ElementHandle element_handle; this->managed_elements_->AddManagedElement(browser_handle, element, &element_handle); RemappedElementInfo* return_info = new RemappedElementInfo; return_info->original_element_id = element_id; return_info->element_id = element_handle->element_id(); ::PostMessage(browser_handle->script_executor_handle(), WD_ASYNC_SCRIPT_NOTIFY_ELEMENT_TRANSFERRED, NULL, reinterpret_cast(return_info)); return WD_SUCCESS; } LRESULT IECommandExecutor::OnScheduleRemoveManagedElement(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { LOG(TRACE) << "Entering IECommandExecutor::OnScheduleRemoveManagedElement"; ElementInfo* info = reinterpret_cast(lParam); std::string element_id = info->element_id; delete info; this->RemoveManagedElement(element_id); return WD_SUCCESS; } unsigned int WINAPI IECommandExecutor::WaitThreadProc(LPVOID lpParameter) { LOG(TRACE) << "Entering IECommandExecutor::WaitThreadProc"; WaitThreadContext* thread_context = reinterpret_cast(lpParameter); HWND window_handle = thread_context->window_handle; bool is_deferred_command = thread_context->is_deferred_command; std::string deferred_response(thread_context->deferred_response); delete thread_context->deferred_response; delete thread_context; LPSTR message_payload = new CHAR[deferred_response.size() + 1]; strcpy_s(message_payload, deferred_response.size() + 1, deferred_response.c_str()); ::Sleep(WAIT_TIME_IN_MILLISECONDS); ::PostMessage(window_handle, WD_WAIT, static_cast(deferred_response.size()), reinterpret_cast(message_payload)); if (is_deferred_command) { // This wait is requested by the automatic handling of a user prompt // before a command was executed, so re-queue the command execution // for after the wait. ::PostMessage(window_handle, WD_EXEC_COMMAND, NULL, NULL); } return 0; } unsigned int WINAPI IECommandExecutor::ScriptWaitThreadProc(LPVOID lpParameter) { LOG(TRACE) << "Entering IECommandExecutor::ScriptWaitThreadProc"; HWND window_handle = reinterpret_cast(lpParameter); ::Sleep(SCRIPT_WAIT_TIME_IN_MILLISECONDS); ::PostMessage(window_handle, WD_SCRIPT_WAIT, NULL, NULL); return 0; } unsigned int WINAPI IECommandExecutor::DelayPostMessageThreadProc(LPVOID lpParameter) { LOG(TRACE) << "Entering IECommandExecutor::DelayPostMessageThreadProc"; DelayPostMessageThreadContext* context = reinterpret_cast(lpParameter); HWND window_handle = context->window_handle; DWORD sleep_time = context->delay; UINT message_to_post = context->msg; delete context; ::Sleep(sleep_time); ::PostMessage(window_handle, message_to_post, NULL, NULL); return 0; } unsigned int WINAPI IECommandExecutor::ThreadProc(LPVOID lpParameter) { LOG(TRACE) << "Entering IECommandExecutor::ThreadProc"; IECommandExecutorThreadContext* thread_context = reinterpret_cast(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; DWORD error = 0; HRESULT hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { LOGHR(DEBUG, hr) << "COM library initialization encountered an error"; } IECommandExecutor new_session; 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(session_context)); if (session_window_handle == NULL) { 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. thread_context->hwnd = session_window_handle; HANDLE event_handle = ::OpenEvent(EVENT_ALL_ACCESS, FALSE, WEBDRIVER_START_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 BOOL get_message_return_value; while ((get_message_return_value = ::GetMessage(&msg, NULL, 0, 0)) != 0) { if (get_message_return_value == -1) { LOGERR(WARN) << "Windows GetMessage() API returned error"; break; } else { if (msg.message == WD_SHUTDOWN) { LOG(DEBUG) << "Shutdown message received"; new_session.DestroyWindow(); LOG(DEBUG) << "Returned from DestroyWindow()"; break; } else { // We need to lock this mutex here to make sure only one thread is processing // win32 messages at a time. static std::mutex messageLock; std::lock_guard lock(messageLock); ::TranslateMessage(&msg); ::DispatchMessage(&msg); } } } LOG(DEBUG) << "Exited IECommandExecutor thread message loop"; ::CoUninitialize(); delete session_context; return 0; } void IECommandExecutor::DispatchCommand() { LOG(TRACE) << "Entering IECommandExecutor::DispatchCommand"; Response response; if (!this->command_handlers_->IsValidCommand(this->current_command_.command_type())) { LOG(WARN) << "Unable to find command handler for " << this->current_command_.command_type(); response.SetErrorResponse(ERROR_UNKNOWN_COMMAND, "Command not implemented"); } else if (!this->current_command_.is_valid_parameters()) { response.SetErrorResponse(ERROR_INVALID_ARGUMENT, "parameters property of command is not a valid JSON object"); } else { BrowserHandle browser; int status_code = WD_SUCCESS; std::string command_type = this->current_command_.command_type(); if (command_type != webdriver::CommandType::NewSession) { // There should never be a modal dialog or alert to check for if the command // is the "newSession" command. status_code = this->GetCurrentBrowser(&browser); if (status_code == WD_SUCCESS) { LOG(DEBUG) << "Checking for alert before executing " << command_type << " command"; HWND alert_handle = NULL; bool alert_is_active = this->IsAlertActive(browser, &alert_handle); if (alert_is_active) { if (this->IsCommandValidWithAlertPresent()) { LOG(DEBUG) << "Alert is detected, and the sent command is valid"; } else { LOG(DEBUG) << "Unexpected alert is detected, and the sent command " << "is invalid when an alert is present"; bool is_quit_command = command_type == webdriver::CommandType::Quit; std::string alert_text; bool is_notify_unexpected_alert = this->HandleUnexpectedAlert(browser, alert_handle, is_quit_command, &alert_text); if (!is_quit_command) { if (is_notify_unexpected_alert) { // To keep pace with what the specification suggests, we'll // return the text of the alert in the error response. response.SetErrorResponse(EUNEXPECTEDALERTOPEN, "Modal dialog present with text: " + alert_text); response.AddAdditionalData("text", alert_text); this->serialized_response_ = response.Serialize(); return; } else { LOG(DEBUG) << "Command other than quit was issued, and option " << "to not notify was specified. Continuing with " << "command after automatically closing alert."; // Push a wait cycle, then re-execute the current command (which // hasn't actually been executed yet). Note that an empty string // for the deferred response parameter of CreateWaitThread will // re-queue the execution of the command. this->CreateWaitThread("", true); return; } } else { LOG(DEBUG) << "Quit command was issued. Continuing with " << "command after automatically closing alert."; } } } } else { LOG(WARN) << "Unable to find current browser"; } } LOG(DEBUG) << "Executing command: " << command_type; CommandHandlerHandle command_handler = this->command_handlers_->GetCommandHandler(command_type); command_handler->Execute(*this, this->current_command_, &response); LOG(DEBUG) << "Command execution for " << command_type << " complete"; status_code = this->GetCurrentBrowser(&browser); if (status_code == WD_SUCCESS) { if (browser->is_closing() && !this->is_quitting_) { // Case 1: The browser window is closing, but not via the Quit command, // so the executor must wait for the browser window to be closed and // removed from the list of managed browser windows. LOG(DEBUG) << "Browser is closing; awaiting close."; LPSTR message_payload = new CHAR[browser->browser_id().size() + 1]; strcpy_s(message_payload, browser->browser_id().size() + 1, browser->browser_id().c_str()); this->is_waiting_ = true; ::Sleep(WAIT_TIME_IN_MILLISECONDS); ::PostMessage(this->m_hWnd, WD_BROWSER_CLOSE_WAIT, NULL, reinterpret_cast(message_payload)); return; } else if (browser->script_executor_handle() != NULL) { // Case 2: There is a pending asynchronous JavaScript execution in // progress, so the executor must wait for the script to complete // or a timeout. this->is_waiting_ = true; if (this->async_script_timeout_ >= 0) { this->wait_timeout_ = clock() + (static_cast(this->async_script_timeout_) / 1000 * CLOCKS_PER_SEC); } LOG(DEBUG) << "Awaiting completion of in-progress asynchronous JavaScript execution."; ::PostMessage(this->m_hWnd, WD_SCRIPT_WAIT, NULL, NULL); return; } else if (browser->wait_required()) { // Case 3: The command handler has explicitly asked to wait for page // load, so the executor must wait for page load or timeout. this->is_waiting_ = true; if (this->page_load_timeout_ >= 0) { this->wait_timeout_ = clock() + (static_cast(this->page_load_timeout_) / 1000 * CLOCKS_PER_SEC); } std::string deferred_response = response.Serialize(); LOG(DEBUG) << "Command handler requested wait. This will cause a minimal wait of at least 50 milliseconds."; this->CreateWaitThread(deferred_response); return; } } else { if (this->current_command_.command_type() != webdriver::CommandType::Quit) { LOG(WARN) << "Unable to get current browser"; } } if (this->is_edge_chromium_ && this->is_quitting_) { // If this is Edge in IE Mode, we need to explicitly wait for the // browsers to be completely deleted before returning for the quit // command. this->is_waiting_ = true; ::Sleep(WAIT_TIME_IN_MILLISECONDS); ::PostMessage(this->m_hWnd, WD_SESSION_QUIT_WAIT, NULL, NULL); return; } } this->serialized_response_ = response.Serialize(); LOG(DEBUG) << "Setting serialized response to " << this->serialized_response_; LOG(DEBUG) << "Is waiting flag: " << this->is_waiting_ ? "true" : "false"; } bool IECommandExecutor::IsCommandValidWithAlertPresent() { std::string command_type = this->current_command_.command_type(); if (command_type == webdriver::CommandType::GetAlertText || command_type == webdriver::CommandType::SendKeysToAlert || command_type == webdriver::CommandType::AcceptAlert || command_type == webdriver::CommandType::DismissAlert || command_type == webdriver::CommandType::SetAlertCredentials || command_type == webdriver::CommandType::GetTimeouts || command_type == webdriver::CommandType::SetTimeouts || command_type == webdriver::CommandType::Screenshot || command_type == webdriver::CommandType::ElementScreenshot || command_type == webdriver::CommandType::GetCurrentWindowHandle || command_type == webdriver::CommandType::GetWindowHandles || command_type == webdriver::CommandType::SwitchToWindow) { return true; } return false; } void IECommandExecutor::CreateWaitThread(const std::string& deferred_response) { this->CreateWaitThread(deferred_response, false); } void IECommandExecutor::CreateWaitThread(const std::string& deferred_response, const bool is_deferred_command_execution) { // If we are still waiting, we need to wait a bit then post a message to // ourselves to run the wait again. However, we can't wait using Sleep() // on this thread. This call happens in a message loop, and we would be // unable to process the COM events in the browser if we put this thread // to sleep. LOG(DEBUG) << "Creating wait thread with deferred response of `" << deferred_response << "`"; if (is_deferred_command_execution) { LOG(DEBUG) << "Command execution will be rescheduled."; } WaitThreadContext* thread_context = new WaitThreadContext; thread_context->window_handle = this->m_hWnd; thread_context->is_deferred_command = is_deferred_command_execution; thread_context->deferred_response = new CHAR[deferred_response.size() + 1]; strcpy_s(thread_context->deferred_response, deferred_response.size() + 1, deferred_response.c_str()); unsigned int thread_id = 0; HANDLE thread_handle = reinterpret_cast(_beginthreadex(NULL, 0, &IECommandExecutor::WaitThreadProc, reinterpret_cast(thread_context), 0, &thread_id)); if (thread_handle != NULL) { ::CloseHandle(thread_handle); } else { LOGERR(DEBUG) << "Unable to create waiter thread"; } } void IECommandExecutor::CreateDelayPostMessageThread(const DWORD delay_time, const HWND window_handle, const UINT message_to_post) { DelayPostMessageThreadContext* context = new DelayPostMessageThreadContext; context->delay = delay_time; context->window_handle = window_handle; context->msg = message_to_post; unsigned int thread_id = 0; HANDLE thread_handle = reinterpret_cast(_beginthreadex(NULL, 0, &IECommandExecutor::DelayPostMessageThreadProc, reinterpret_cast(context), 0, &thread_id)); if (thread_handle != NULL) { ::CloseHandle(thread_handle); } else { LOGERR(DEBUG) << "Unable to create waiter thread"; } } bool IECommandExecutor::IsAlertActive(BrowserHandle browser, HWND* alert_handle) { LOG(TRACE) << "Entering IECommandExecutor::IsAlertActive"; HWND dialog_handle = browser->GetActiveDialogWindowHandle(); if (dialog_handle != NULL) { // Found a window handle, make sure it's an actual alert, // and not a showModalDialog() window. std::vector window_class_name(34); ::GetClassNameA(dialog_handle, &window_class_name[0], 34); if (strcmp(ALERT_WINDOW_CLASS, &window_class_name[0]) == 0) { *alert_handle = dialog_handle; return true; } else if (strcmp(SECURITY_DIALOG_WINDOW_CLASS, &window_class_name[0]) == 0) { *alert_handle = dialog_handle; return true; } else { LOG(WARN) << "Found alert handle does not have a window class consistent with an alert"; } } else { LOG(DEBUG) << "No alert handle is found"; } return false; } bool IECommandExecutor::HandleUnexpectedAlert(BrowserHandle browser, HWND alert_handle, bool force_use_dismiss, std::string* alert_text) { LOG(TRACE) << "Entering IECommandExecutor::HandleUnexpectedAlert"; clock_t end = clock() + 5 * CLOCKS_PER_SEC; bool is_visible = (::IsWindowVisible(alert_handle) == TRUE); while (!is_visible && clock() < end) { ::Sleep(50); is_visible = (::IsWindowVisible(alert_handle) == TRUE); } Alert dialog(browser, alert_handle); *alert_text = dialog.GetText(); if (!dialog.is_standard_alert()) { // The dialog was non-standard. The most common case of this is // an onBeforeUnload dialog, which must be accepted to continue. dialog.Accept(); return false; } if (this->unexpected_alert_behavior_ == ACCEPT_UNEXPECTED_ALERTS || this->unexpected_alert_behavior_ == ACCEPT_AND_NOTIFY_UNEXPECTED_ALERTS) { LOG(DEBUG) << "Automatically accepting the alert"; dialog.Accept(); } else if (this->unexpected_alert_behavior_.size() == 0 || this->unexpected_alert_behavior_ == DISMISS_UNEXPECTED_ALERTS || this->unexpected_alert_behavior_ == DISMISS_AND_NOTIFY_UNEXPECTED_ALERTS || force_use_dismiss) { // If a quit command was issued, we should not ignore an unhandled // alert, even if the alert behavior is set to "ignore". LOG(DEBUG) << "Automatically dismissing the alert"; if (dialog.is_standard_alert() || dialog.is_security_alert()) { dialog.Dismiss(); } else { // The dialog was non-standard. The most common case of this is // an onBeforeUnload dialog, which must be accepted to continue. dialog.Accept(); } } bool is_notify_unexpected_alert = this->unexpected_alert_behavior_.size() == 0 || this->unexpected_alert_behavior_ == IGNORE_UNEXPECTED_ALERTS || this->unexpected_alert_behavior_ == DISMISS_AND_NOTIFY_UNEXPECTED_ALERTS || this->unexpected_alert_behavior_ == ACCEPT_AND_NOTIFY_UNEXPECTED_ALERTS; is_notify_unexpected_alert = is_notify_unexpected_alert && dialog.is_standard_alert(); return is_notify_unexpected_alert; } void IECommandExecutor::PostBrowserReattachMessage(const DWORD current_process_id, const std::string& browser_id, const std::vector& known_process_ids) { LOG(TRACE) << "Entering IECommandExecutor::PostBrowserReattachMessage"; ::Sleep(100); BrowserReattachInfo* repost_info = new BrowserReattachInfo; repost_info->current_process_id = current_process_id; repost_info->browser_id = browser_id; repost_info->known_process_ids = known_process_ids; ::PostMessage(this->m_hWnd, WD_BROWSER_REATTACH, NULL, reinterpret_cast(repost_info)); } void IECommandExecutor::GetNewBrowserProcessIds(std::vector* known_process_ids, std::vector* new_process_ids) { LOG(TRACE) << "Entering IECommandExecutor::GetNewBrowserProcessIds"; std::vector all_ie_process_ids; WindowUtilities::GetProcessesByName(L"iexplore.exe", &all_ie_process_ids); // Maximum size of the new process list is if all IE processes are unknown. std::vector temp_new_process_ids(all_ie_process_ids.size()); std::sort(known_process_ids->begin(), known_process_ids->end()); std::sort(all_ie_process_ids.begin(), all_ie_process_ids.end()); std::vector::iterator end_iterator = std::set_difference(all_ie_process_ids.begin(), all_ie_process_ids.end(), known_process_ids->begin(), known_process_ids->end(), temp_new_process_ids.begin()); temp_new_process_ids.resize(end_iterator - temp_new_process_ids.begin()); *new_process_ids = temp_new_process_ids; } int IECommandExecutor::GetCurrentBrowser(BrowserHandle* browser_wrapper) const { LOG(TRACE) << "Entering IECommandExecutor::GetCurrentBrowser"; return this->GetManagedBrowser(this->current_browser_id_, browser_wrapper); } int IECommandExecutor::GetManagedBrowser(const std::string& browser_id, BrowserHandle* browser_wrapper) const { LOG(TRACE) << "Entering IECommandExecutor::GetManagedBrowser"; if (!this->is_valid()) { LOG(TRACE) << "Current command executor is not valid"; return ENOSUCHDRIVER; } if (browser_id == "") { LOG(WARN) << "Browser ID requested was an empty string"; return ENOSUCHWINDOW; } BrowserMap::const_iterator found_iterator = this->managed_browsers_.find(browser_id); if (found_iterator == this->managed_browsers_.end()) { LOG(WARN) << "Unable to find managed browser with id " << browser_id; return ENOSUCHWINDOW; } *browser_wrapper = found_iterator->second; return WD_SUCCESS; } void IECommandExecutor::GetManagedBrowserHandles(std::vector* managed_browser_handles) const { LOG(TRACE) << "Entering IECommandExecutor::GetManagedBrowserHandles"; BrowserMap::const_iterator it = this->managed_browsers_.begin(); for (; it != this->managed_browsers_.end(); ++it) { if (it->second->IsValidWindow()) { managed_browser_handles->push_back(it->first); } // Look for browser windows created by showModalDialog(). it->second->GetActiveDialogWindowHandle(); } } void IECommandExecutor::AddManagedBrowser(BrowserHandle browser_wrapper) { LOG(TRACE) << "Entering IECommandExecutor::AddManagedBrowser"; this->managed_browsers_[browser_wrapper->browser_id()] = browser_wrapper; if (this->current_browser_id_ == "") { LOG(TRACE) << "Setting current browser id to " << browser_wrapper->browser_id(); this->current_browser_id_ = browser_wrapper->browser_id(); } } std::string IECommandExecutor::OpenNewBrowsingContext(const std::string& window_type) { return this->OpenNewBrowsingContext(window_type, "about:blank"); } std::string IECommandExecutor::OpenNewBrowsingContext(const std::string& window_type, const std::string& url) { LOG(TRACE) << "Entering IECommandExecutor::OpenNewBrowsingContext"; std::wstring target_url = StringUtilities::ToWString(url); std::string new_browser_id = ""; if (window_type == TAB_WINDOW_TYPE) { new_browser_id = this->OpenNewBrowserTab(target_url); } else { new_browser_id = this->OpenNewBrowserWindow(target_url); } BrowserHandle new_window_wrapper; this->GetManagedBrowser(new_browser_id, &new_window_wrapper); HWND new_window_handle = new_window_wrapper->GetBrowserWindowHandle(); this->proxy_manager_->SetProxySettings(new_window_handle); new_window_wrapper->cookie_manager()->Initialize(new_window_handle); return new_browser_id; } std::string IECommandExecutor::OpenNewBrowserWindow(const std::wstring& url) { LOG(TRACE) << "Entering IECommandExecutor::OpenNewBrowserWindow"; bool is_protected_mode_url = ::IEIsProtectedModeURL(url.c_str()) == S_OK; if (url.find(L"about:blank") == 0) { // Special-case URLs starting with about:blank, so that the new window // is in the same Protected Mode zone as the current window from which // it's being opened. BrowserHandle current_browser; this->GetCurrentBrowser(¤t_browser); is_protected_mode_url = current_browser->IsProtectedMode(); } CComPtr browser = this->factory_->CreateBrowser(is_protected_mode_url); if (browser == NULL) { // No browser was created, so we have to bail early. // Check the log for the HRESULT why. return ""; } LOG(DEBUG) << "New browser window was opened."; BrowserHandle new_window_wrapper(new Browser(browser, NULL, this->m_hWnd, this->is_edge_chromium_)); // It is acceptable to set the proxy settings here, as the newly-created // browser window has not yet been navigated to any page. Only after the // interface has been marshaled back across the thread boundary to the // NewWindow3 event handler will the navigation begin, which ensures that // even the initial navigation will get captured by the proxy, if one is // set. Likewise, the cookie manager needs to have its window handle // properly set to a non-NULL value so that windows messages are routed // to the correct window. // N.B. DocumentHost::GetBrowserWindowHandle returns the tab window handle // for IE 7 and above, and the top-level window for IE6. This is the window // required for setting the proxy settings. this->AddManagedBrowser(new_window_wrapper); return new_window_wrapper->browser_id(); } std::string IECommandExecutor::OpenNewBrowserTab(const std::wstring& url) { LOG(TRACE) << "Entering IECommandExecutor::OpenNewBrowserTab"; BrowserHandle browser_wrapper; this->GetCurrentBrowser(&browser_wrapper); HWND top_level_handle = browser_wrapper->GetTopLevelWindowHandle(); if (this->is_edge_chromium_) { // This is a hack to account for the case where the currently focused // WebDriver window is a tab without the visual focus. When an IE Mode // tab is sent to the background, it is reparented to a different top- // level window than the Edge window. To detect a new tab being opened, // we must find a top-level Edge window containing and active IE Mode // tab, and use that as our parent window. This is a rare case that // will only happen if the user does not switch WebDriver command focus // to the new window immediately after opening. The Selenium language // bindings do this automatically, but non-Selenium client bindings may // not do so. // ASSUMPTION: The first top-level Edge window we find containing an IE // Mode tab is the top-level window we want. std::string window_class = WindowUtilities::GetWindowClass(top_level_handle); if (window_class != "Chrome_WidgetWin_1") { std::vector edge_handles; ::EnumWindows(&BrowserFactory::FindEdgeBrowserHandles, reinterpret_cast(&edge_handles)); for (auto& edge_handle : edge_handles) { std::vector ie_mode_handles; ::EnumChildWindows(edge_handle, &BrowserFactory::FindIEBrowserHandles, reinterpret_cast(&ie_mode_handles)); if (ie_mode_handles.size() > 0) { top_level_handle = edge_handle; break; } } } } std::vector original_handles; ::EnumChildWindows(top_level_handle, &BrowserFactory::FindIEBrowserHandles, reinterpret_cast(&original_handles)); std::sort(original_handles.begin(), original_handles.end()); // IWebBrowser2::Navigate2 will open the specified URL in a new tab, // if requested. The Sleep() call after the navigate is necessary, // since the IECommandExecutor class doesn't have access to the events // to indicate the navigation is completed. CComVariant url_variant = url.c_str(); CComVariant flags = navOpenInNewTab; browser_wrapper->browser()->Navigate2(&url_variant, &flags, NULL, NULL, NULL); ::Sleep(500); HWND new_tab_window = NULL; std::vector new_handles; ::EnumChildWindows(top_level_handle, &BrowserFactory::FindIEBrowserHandles, reinterpret_cast(&new_handles)); clock_t end_time = clock() + 5 * CLOCKS_PER_SEC; if (browser_wrapper->is_edge_chromium()) { // It appears that for Chromium-based Edge in IE Mode, there will only // ever be one active child window of the top-level window with window // class "Internet Explorer_Server", which is the active tab. Inactive // tabs are re-parented until brought back to being the active tab. while ((new_handles.size() == 0 || new_handles[0] == original_handles[0]) && clock() < end_time) { if (new_handles.size() != 0) { new_handles.clear(); } ::Sleep(50); ::EnumChildWindows(top_level_handle, &BrowserFactory::FindIEBrowserHandles, reinterpret_cast(&new_handles)); } if (new_handles.size() == 0 || new_handles[0] == original_handles[0]) { LOG(WARN) << "No new window handle found after attempt to open"; return ""; } new_tab_window = new_handles[0]; } else { while (new_handles.size() <= original_handles.size() && clock() < end_time) { ::Sleep(50); ::EnumChildWindows(top_level_handle, &BrowserFactory::FindIEBrowserHandles, reinterpret_cast(&new_handles)); } std::sort(new_handles.begin(), new_handles.end()); if (new_handles.size() <= original_handles.size()) { LOG(WARN) << "No new window handle found after attempt to open"; return ""; } // We are guaranteed to have at least one HWND difference // between the two vectors if we reach this point, because // we know the vectors are different sizes. std::vector diff(new_handles.size()); std::vector::iterator it = std::set_difference(new_handles.begin(), new_handles.end(), original_handles.begin(), original_handles.end(), diff.begin()); diff.resize(it - diff.begin()); if (diff.size() > 1) { std::string handle_list = ""; std::vector::const_iterator it = diff.begin(); for (; it != diff.end(); ++it) { if (handle_list.size() > 0) { handle_list.append(", "); } handle_list.append(StringUtilities::Format("0x%08x", *it)); } LOG(DEBUG) << "Found more than one new window handles! Found " << diff.size() << "windows [" << handle_list << "]"; } new_tab_window = diff[0]; } DWORD process_id; ::GetWindowThreadProcessId(new_tab_window, &process_id); clock_t end = clock() + (DEFAULT_BROWSER_REATTACH_TIMEOUT_IN_MILLISECONDS / 1000 * CLOCKS_PER_SEC); bool is_ready = this->factory_->IsBrowserProcessInitialized(process_id); while (!is_ready && clock() < end) { ::Sleep(100); is_ready = this->factory_->IsBrowserProcessInitialized(process_id); } ProcessWindowInfo info; info.dwProcessId = process_id; info.hwndBrowser = new_tab_window; info.pBrowser = NULL; std::string error_message = ""; this->factory_->AttachToBrowser(&info, &error_message); BrowserHandle new_tab_wrapper(new Browser(info.pBrowser, NULL, this->m_hWnd, this->is_edge_chromium_)); // Force a wait cycle to make sure the browser is finished initializing. new_tab_wrapper->Wait(NORMAL_PAGE_LOAD_STRATEGY); this->AddManagedBrowser(new_tab_wrapper); return new_tab_wrapper->browser_id(); } int IECommandExecutor::CreateNewBrowser(std::string* error_message) { LOG(TRACE) << "Entering IECommandExecutor::CreateNewBrowser"; DWORD process_id = this->factory_->LaunchBrowserProcess(error_message); if (process_id == NULL) { LOG(WARN) << "Unable to launch browser, received NULL process ID"; this->is_waiting_ = false; return ENOSUCHDRIVER; } ProcessWindowInfo process_window_info; process_window_info.dwProcessId = process_id; process_window_info.hwndBrowser = NULL; process_window_info.pBrowser = NULL; bool attached = this->factory_->AttachToBrowser(&process_window_info, error_message); if (!attached) { LOG(WARN) << "Unable to attach to browser COM object"; this->is_waiting_ = false; return ENOSUCHDRIVER; } // Set persistent hover functionality in the interactions implementation. //this->input_manager_->StartPersistentEvents(); LOG(INFO) << "Persistent hovering set to: " << this->input_manager_->use_persistent_hover(); this->proxy_manager_->SetProxySettings(process_window_info.hwndBrowser); BrowserHandle wrapper(new Browser(process_window_info.pBrowser, process_window_info.hwndBrowser, this->m_hWnd, this->is_edge_chromium_)); this->AddManagedBrowser(wrapper); bool is_busy = wrapper->IsBusy(); if (is_busy) { LOG(WARN) << "Browser was launched and attached to, but is still busy."; } wrapper->SetFocusToBrowser(); this->edge_temp_dir_ = this->factory_->GetEdgeTempDir(); return WD_SUCCESS; } int IECommandExecutor::GetManagedElement(const std::string& element_id, ElementHandle* element_wrapper) const { LOG(TRACE) << "Entering IECommandExecutor::GetManagedElement"; return this->managed_elements_->GetManagedElement(element_id, element_wrapper); } bool IECommandExecutor::AddManagedElement(IHTMLElement* element, ElementHandle* element_wrapper) { LOG(TRACE) << "Entering IECommandExecutor::AddManagedElement"; BrowserHandle current_browser; this->GetCurrentBrowser(¤t_browser); return this->managed_elements_->AddManagedElement(current_browser, element, element_wrapper); } void IECommandExecutor::RemoveManagedElement(const std::string& element_id) { LOG(TRACE) << "Entering IECommandExecutor::RemoveManagedElement"; this->managed_elements_->RemoveManagedElement(element_id); } void IECommandExecutor::ListManagedElements() { LOG(TRACE) << "Entering IECommandExecutor::ListManagedElements"; this->managed_elements_->ListManagedElements(); } int IECommandExecutor::GetElementFindMethod(const std::string& mechanism, std::wstring* translation) const { LOG(TRACE) << "Entering IECommandExecutor::GetElementFindMethod"; ElementFindMethodMap::const_iterator found_iterator = this->element_find_methods_.find(mechanism); if (found_iterator == this->element_find_methods_.end()) { LOG(WARN) << "Unable to determine find method " << mechanism; return EUNHANDLEDERROR; } *translation = found_iterator->second; return WD_SUCCESS; } int IECommandExecutor::LocateElement(const ElementHandle parent_wrapper, const std::string& mechanism, const std::string& criteria, Json::Value* found_element) const { LOG(TRACE) << "Entering IECommandExecutor::LocateElement"; std::wstring mechanism_translation = L""; int status_code = this->GetElementFindMethod(mechanism, &mechanism_translation); if (status_code != WD_SUCCESS) { LOG(WARN) << "Unable to determine mechanism translation for " << mechanism; return status_code; } std::wstring wide_criteria = StringUtilities::ToWString(criteria); return this->element_finder()->FindElement(*this, parent_wrapper, mechanism_translation, wide_criteria, found_element); } int IECommandExecutor::LocateElements(const ElementHandle parent_wrapper, const std::string& mechanism, const std::string& criteria, Json::Value* found_elements) const { LOG(TRACE) << "Entering IECommandExecutor::LocateElements"; std::wstring mechanism_translation = L""; int status_code = this->GetElementFindMethod(mechanism, &mechanism_translation); if (status_code != WD_SUCCESS) { LOG(WARN) << "Unable to determine mechanism translation for " << mechanism; return status_code; } std::wstring wide_criteria = StringUtilities::ToWString(criteria); return this->element_finder()->FindElements(*this, parent_wrapper, mechanism_translation, wide_criteria, found_elements); } void IECommandExecutor::PopulateElementFinderMethods(void) { LOG(TRACE) << "Entering IECommandExecutor::PopulateElementFinderMethods"; this->element_find_methods_["id"] = L"id"; this->element_find_methods_["name"] = L"name"; this->element_find_methods_["tag name"] = L"tagName"; this->element_find_methods_["link text"] = L"linkText"; this->element_find_methods_["partial link text"] = L"partialLinkText"; this->element_find_methods_["class name"] = L"className"; this->element_find_methods_["xpath"] = L"xpath"; this->element_find_methods_["css selector"] = L"css"; } } // namespace webdriver