| // 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 <algorithm> |
| #include <ctime> |
| #include <vector> |
| #include <mutex> |
| #include <unordered_set> |
| |
| #include <iepmapi.h> |
| |
| #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<CREATESTRUCT*>(lParam); |
| IECommandExecutorThreadContext* context = reinterpret_cast<IECommandExecutorThreadContext*>(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<wchar_t*>(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<LPCSTR>(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<LPSTR>(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<LPCSTR>(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<DWORD>(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<HWND>* current_window_handles = |
| reinterpret_cast<std::vector<HWND>*>(lParam); |
| std::unordered_set<HWND> 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<HWND> diff; |
| int loop_count = 0; |
| bool is_processing_wm = false; |
| ::Sleep(1000); |
| while (diff.size() == 0 && clock() < end) { |
| std::vector<HWND> edge_window_handles; |
| ::EnumWindows(&BrowserFactory::FindEdgeBrowserHandles, |
| reinterpret_cast<LPARAM>(&edge_window_handles)); |
| |
| std::vector<HWND> new_ie_window_handles; |
| for (auto& edge_window_handle : edge_window_handles) { |
| std::vector<HWND> child_window_handles; |
| ::EnumChildWindows(edge_window_handle, |
| &BrowserFactory::FindIEBrowserHandles, |
| reinterpret_cast<LPARAM>(&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<NewWindowInfo*>(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<LPCSTR>(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<LPARAM>(message_payload)); |
| return 0; |
| } |
| } else { |
| LOG(WARN) << "Unable to find browser to quit with ID " << browser_id; |
| } |
| Json::Value handles(Json::arrayValue); |
| std::vector<std::string> handle_list; |
| this->GetManagedBrowserHandles(&handle_list); |
| std::vector<std::string>::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<HWND>(wParam)); |
| } |
| return 0; |
| } |
| |
| LRESULT IECommandExecutor::OnBrowserQuit(UINT uMsg, |
| WPARAM wParam, |
| LPARAM lParam, |
| BOOL& bHandled) { |
| LOG(TRACE) << "Entering IECommandExecutor::OnBrowserQuit"; |
| |
| LPCSTR str = reinterpret_cast<LPCSTR>(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<int>(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<BrowserReattachInfo*>(lParam); |
| DWORD current_process_id = info->current_process_id; |
| std::string browser_id = info->browser_id; |
| std::vector<DWORD> 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<DWORD> 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<HWND>(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<IHTMLDocument2> document; |
| bool found_document = this->factory_->GetDocumentFromWindowHandle(dialog_handle, &document); |
| while (found_document && retry_count < MAX_HTML_DIALOG_RETRIES) { |
| CComPtr<IHTMLWindow2> 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<HANDLE>(_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<int>(::SendMessage(browser->script_executor_handle(), |
| WD_ASYNC_SCRIPT_GET_RESULT, |
| NULL, |
| reinterpret_cast<LPARAM>(&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<ElementInfo*>(lParam); |
| std::string element_id = info->element_id; |
| LPSTREAM element_stream = info->element_stream; |
| BrowserHandle browser_handle; |
| this->GetCurrentBrowser(&browser_handle); |
| CComPtr<IHTMLElement> element; |
| ::CoGetInterfaceAndReleaseStream(element_stream, |
| IID_IHTMLElement, |
| reinterpret_cast<void**>(&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<LPARAM>(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<ElementInfo*>(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<WaitThreadContext*>(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<WPARAM>(deferred_response.size()), |
| reinterpret_cast<LPARAM>(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<HWND>(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<DelayPostMessageThreadContext*>(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<IECommandExecutorThreadContext*>(lpParameter); |
| HWND window_handle = thread_context->hwnd; |
| |
| // it is better to use IECommandExecutorSessionContext instead |
| // but use ThreadContext for code minimization |
| IECommandExecutorThreadContext* session_context = new IECommandExecutorThreadContext(); |
| session_context->port = thread_context->port; |
| |
| 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<LPVOID*>(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<std::mutex> 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<LPARAM>(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<int>(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<int>(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<HANDLE>(_beginthreadex(NULL, |
| 0, |
| &IECommandExecutor::WaitThreadProc, |
| reinterpret_cast<void*>(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<HANDLE>(_beginthreadex(NULL, |
| 0, |
| &IECommandExecutor::DelayPostMessageThreadProc, |
| reinterpret_cast<void*>(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<char> 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<DWORD>& 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<LPARAM>(repost_info)); |
| } |
| |
| void IECommandExecutor::GetNewBrowserProcessIds(std::vector<DWORD>* known_process_ids, |
| std::vector<DWORD>* new_process_ids) { |
| LOG(TRACE) << "Entering IECommandExecutor::GetNewBrowserProcessIds"; |
| std::vector<DWORD> 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<DWORD> 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<DWORD>::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<std::string>* 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<IWebBrowser2> 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<HWND> edge_handles; |
| ::EnumWindows(&BrowserFactory::FindEdgeBrowserHandles, |
| reinterpret_cast<LPARAM>(&edge_handles)); |
| for (auto& edge_handle : edge_handles) { |
| std::vector<HWND> ie_mode_handles; |
| ::EnumChildWindows(edge_handle, |
| &BrowserFactory::FindIEBrowserHandles, |
| reinterpret_cast<LPARAM>(&ie_mode_handles)); |
| if (ie_mode_handles.size() > 0) { |
| top_level_handle = edge_handle; |
| break; |
| } |
| } |
| } |
| } |
| |
| std::vector<HWND> original_handles; |
| ::EnumChildWindows(top_level_handle, |
| &BrowserFactory::FindIEBrowserHandles, |
| reinterpret_cast<LPARAM>(&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<HWND> new_handles; |
| ::EnumChildWindows(top_level_handle, |
| &BrowserFactory::FindIEBrowserHandles, |
| reinterpret_cast<LPARAM>(&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<LPARAM>(&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<LPARAM>(&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<HWND> diff(new_handles.size()); |
| std::vector<HWND>::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<HWND>::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 |