// 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 "Browser.h" #include #include #include "errorcodes.h" #include "logging.h" #include "Alert.h" #include "BrowserFactory.h" #include "CustomTypes.h" #include "messages.h" #include "HookProcessor.h" #include "Script.h" #include "StringUtilities.h" #include "WebDriverConstants.h" #include "WindowUtilities.h" namespace webdriver { Browser::Browser(IWebBrowser2* browser, HWND hwnd, HWND session_handle, bool is_edge_chromium) : DocumentHost(hwnd, session_handle) { LOG(TRACE) << "Entering Browser::Browser"; this->is_explicit_close_requested_ = false; this->is_navigation_started_ = false; this->browser_ = browser; this->AttachEvents(); this->set_is_edge_chromium(is_edge_chromium); } Browser::~Browser(void) { this->DetachEvents(); } void __stdcall Browser::BeforeNavigate2(IDispatch* pObject, VARIANT* pvarUrl, VARIANT* pvarFlags, VARIANT* pvarTargetFrame, VARIANT* pvarData, VARIANT* pvarHeaders, VARIANT_BOOL* pbCancel) { LOG(TRACE) << "Entering Browser::BeforeNavigate2"; std::wstring url(pvarUrl->bstrVal); LOG(DEBUG) << "BeforeNavigate2: Url: " << LOGWSTRING(url) << ", TargetFrame: " << pvarTargetFrame->bstrVal; } void __stdcall Browser::OnQuit() { LOG(TRACE) << "Entering Browser::OnQuit"; if (!this->is_explicit_close_requested_) { if (this->is_awaiting_new_process()) { LOG(WARN) << "A new browser process was requested. This means a Protected " << "Mode boundary has been crossed, and that future commands to " << "the current browser instance will fail. The driver will " << "attempt to reconnect to the newly created browser object, " << "but there is no guarantee it will work."; DWORD process_id; HWND window_handle = this->GetBrowserWindowHandle(); ::GetWindowThreadProcessId(window_handle, &process_id); BrowserReattachInfo* info = new BrowserReattachInfo; info->browser_id = this->browser_id(); info->current_process_id = process_id; info->known_process_ids = this->known_process_ids_; this->DetachEvents(); this->browser_ = NULL; ::PostMessage(this->executor_handle(), WD_BROWSER_REATTACH, NULL, reinterpret_cast(info)); return; } else { LOG(WARN) << "This instance of Internet Explorer (" << this->browser_id() << ") is exiting without an explicit request to close it. " << "Unless you clicked a link that specifically attempts to " << "close the page, that likely means a Protected Mode " << "boundary has been crossed (either entering or exiting " << "Protected Mode). It is highly likely that any subsequent " << "commands to this driver instance will fail. THIS IS NOT A " << "BUG IN THE IE DRIVER! Fix your code and/or browser " << "configuration so that a Protected Mode boundary is not " << "crossed."; } } this->PostQuitMessage(); } void __stdcall Browser::NewProcess(DWORD lCauseFlag, IDispatch* pWB2, VARIANT_BOOL* pbCancel) { LOG(TRACE) << "Entering Browser::NewProcess"; this->InitiateBrowserReattach(); } void __stdcall Browser::NewWindow3(IDispatch** ppDisp, VARIANT_BOOL* pbCancel, DWORD dwFlags, BSTR bstrUrlContext, BSTR bstrUrl) { LOG(TRACE) << "Entering Browser::NewWindow3"; ::PostMessage(this->executor_handle(), WD_BEFORE_NEW_WINDOW, NULL, NULL); std::vector* ie_window_handles = nullptr; WPARAM param_flag = 0; if (this->is_edge_chromium()) { param_flag = 1000; //HWND top_level_handle = this->GetTopLevelWindowHandle(); // 1) find all Edge browser window handles std::vectoredge_window_handles; ::EnumWindows(&BrowserFactory::FindEdgeBrowserHandles, reinterpret_cast(&edge_window_handles)); // 2) find all IE browser window handlers as child window when Edge runs in IEMode ie_window_handles = new std::vector; for (HWND& edge_window_handle : edge_window_handles) { std::vector child_window_handles; ::EnumChildWindows(edge_window_handle, &BrowserFactory::FindIEBrowserHandles, reinterpret_cast(&child_window_handles)); for (HWND& child_window_handle : child_window_handles) { ie_window_handles->push_back(child_window_handle); } } } else { // Handle the NewWindow3 event to allow us to immediately hook // the events of the new browser window opened by the user action. // The three ways we can respond to this event are documented at // http://msdn.microsoft.com/en-us/library/aa768337%28v=vs.85%29.aspx // We potentially use two of those response methods. // This will not allow us to handle windows created by the JavaScript // showModalDialog function(). std::wstring url = bstrUrl; IWebBrowser2* browser; NewWindowInfo info; info.target_url = StringUtilities::ToString(url); LRESULT create_result = ::SendMessage(this->executor_handle(), WD_BROWSER_NEW_WINDOW, NULL, reinterpret_cast(&info)); if (create_result != 0) { // The new, blank IWebBrowser2 object was not created, // so we can't really do anything appropriate here. // Note this is "response method 2" of the aforementioned // documentation. LOG(WARN) << "A valid IWebBrowser2 object could not be created."; *pbCancel = VARIANT_TRUE; ::PostMessage(this->executor_handle(), WD_AFTER_NEW_WINDOW, NULL, NULL); return; } // We received a valid IWebBrowser2 pointer, so deserialize it onto this // thread, and pass the result back to the caller. HRESULT hr = ::CoGetInterfaceAndReleaseStream(info.browser_stream, IID_IWebBrowser2, reinterpret_cast(&browser)); if (FAILED(hr)) { LOGHR(WARN, hr) << "Failed to marshal IWebBrowser2 interface from stream."; } *ppDisp = browser; } // 3) pass all IE window handles to WD_AFTER_NEW_WINDOW ::PostMessage(this->executor_handle(), WD_AFTER_NEW_WINDOW, param_flag, reinterpret_cast(ie_window_handles)); } void __stdcall Browser::DocumentComplete(IDispatch* pDisp, VARIANT* URL) { LOG(TRACE) << "Entering Browser::DocumentComplete"; // Flag the browser as navigation having started. this->is_navigation_started_ = true; // DocumentComplete fires last for the top-level frame. If it fires // for the top-level frame and the focused_frame_window_ member variable // is not NULL, we assume we have navigated from within a frameset to a // link that has a target of "_top", which replaces the frameset with the // target page. On a top-level navigation, we are supposed to reset the // focused frame to the top-level, so we do that here. // NOTE: This is a possible source of unreliability if the above // assumptions turn out to be wrong and/or the event firing doesn't work // the way we expect it to. CComPtr dispatch(this->browser_); if (dispatch.IsEqualObject(pDisp)) { if (this->focused_frame_window() != NULL) { LOG(DEBUG) << "DocumentComplete happened from within a frameset"; this->SetFocusedFrameByElement(NULL); } } } void Browser::InitiateBrowserReattach() { LOG(TRACE) << "Entering Browser::InitiateBrowserReattach"; LOG(DEBUG) << "Requesting browser reattach"; this->known_process_ids_.clear(); WindowUtilities::GetProcessesByName(L"iexplore.exe", &this->known_process_ids_); this->set_is_awaiting_new_process(true); ::SendMessage(this->executor_handle(), WD_BEFORE_BROWSER_REATTACH, NULL, NULL); } void Browser::ReattachBrowser(IWebBrowser2* browser) { LOG(TRACE) << "Entering Browser::ReattachBrowser"; this->browser_ = browser; this->AttachEvents(); this->set_is_awaiting_new_process(false); LOG(DEBUG) << "Reattach complete"; } void Browser::GetDocument(IHTMLDocument2** doc) { this->GetDocument(false, doc); } void Browser::GetDocument(const bool force_top_level_document, IHTMLDocument2** doc) { LOG(TRACE) << "Entering Browser::GetDocument"; CComPtr window; if (this->focused_frame_window() == NULL || force_top_level_document) { LOG(INFO) << "No child frame focus. Focus is on top-level frame"; CComPtr dispatch; HRESULT hr = this->browser_->get_Document(&dispatch); if (FAILED(hr)) { LOGHR(WARN, hr) << "Unable to get document, IWebBrowser2::get_Document call failed"; return; } CComPtr dispatch_doc; hr = dispatch->QueryInterface(&dispatch_doc); if (FAILED(hr)) { LOGHR(WARN, hr) << "Have document but cannot cast, IDispatch::QueryInterface call failed"; return; } dispatch_doc->get_parentWindow(&window); } else { window = this->focused_frame_window(); } if (window) { bool result = this->GetDocumentFromWindow(window, doc); if (!result) { LOG(WARN) << "Cannot get document"; } } else { LOG(WARN) << "No window is found"; } } std::string Browser::GetTitle() { LOG(TRACE) << "Entering Browser::GetTitle"; CComPtr dispatch; HRESULT hr = this->browser_->get_Document(&dispatch); if (FAILED(hr)) { LOGHR(WARN, hr) << "Unable to get document, IWebBrowser2::get_Document call failed"; return ""; } CComPtr doc; hr = dispatch->QueryInterface(&doc); if (FAILED(hr)) { LOGHR(WARN, hr) << "Have document but cannot cast, IDispatch::QueryInterface call failed"; return ""; } CComBSTR title; hr = doc->get_title(&title); if (FAILED(hr)) { LOGHR(WARN, hr) << "Unable to get document title, call to IHTMLDocument2::get_title failed"; return ""; } std::wstring converted_title = title; std::string title_string = StringUtilities::ToString(converted_title); return title_string; } std::string Browser::GetBrowserUrl() { LOG(TRACE) << "Entering Browser::GetBrowserUrl"; CComBSTR url; HRESULT hr = this->browser_->get_LocationURL(&url); if (FAILED(hr)) { LOGHR(WARN, hr) << "Unable to get current URL, call to IWebBrowser2::get_LocationURL failed"; return ""; } std::wstring converted_url = url; std::string current_url = StringUtilities::ToString(converted_url); return current_url; } HWND Browser::GetContentWindowHandle() { LOG(TRACE) << "Entering Browser::GetContentWindowHandle"; HWND current_content_window_handle = this->window_handle(); // If this window is closing, the only reason to care about // a valid window handle is to check for alerts whose parent // is this window handle, so return the stored window handle. if (!this->is_closing()) { // If, for some reason, the window handle is no longer valid, set the // member variable to NULL so that we can reacquire the valid window // handle. Note that this can happen when browsing from one type of // content to another, like from HTML to a transformed XML page that // renders content. If the member variable is NULL upon entering this // method, that is okay, as it typically means only that this object // is newly constructed, and has not yet had its handle set. bool window_handle_is_valid = ::IsWindow(current_content_window_handle); if (!window_handle_is_valid) { LOG(INFO) << "Flushing window handle as it is no longer valid"; this->set_window_handle(NULL); } if (this->window_handle() == NULL) { LOG(INFO) << "Restore window handle from tab"; // GetBrowserWindowHandle gets the TabWindowClass window in IE 7 and 8, // and the top-level window frame in IE 6. The window we need is the // InternetExplorer_Server window. HWND tab_window_handle = this->GetBrowserWindowHandle(); if (tab_window_handle == NULL) { LOG(WARN) << "No tab window found"; } HWND content_window_handle = this->FindContentWindowHandle(tab_window_handle); this->set_window_handle(content_window_handle); } } return this->window_handle(); } std::string Browser::GetWindowName() { LOG(TRACE) << "Entering Browser::GetWindowName"; CComPtr dispatch; HRESULT hr = this->browser_->get_Document(&dispatch); if (FAILED(hr)) { LOGHR(WARN, hr) << "Unable to get document, IWebBrowser2::get_Document call failed"; return ""; } CComPtr doc; dispatch->QueryInterface(&doc); if (!doc) { LOGHR(WARN, hr) << "Have document but cannot cast, IDispatch::QueryInterface call failed"; return ""; } CComPtr window; hr = doc->get_parentWindow(&window); if (FAILED(hr)) { LOGHR(WARN, hr) << "Unable to get parent window, call to IHTMLDocument2::get_parentWindow failed"; return ""; } std::string name = ""; CComBSTR window_name; hr = window->get_name(&window_name); if (window_name) { std::wstring converted_window_name = window_name; name = StringUtilities::ToString(converted_window_name); } else { LOG(WARN) << "Unable to get window name, IHTMLWindow2::get_name failed or returned a NULL value"; } return name; } long Browser::GetWidth() { LOG(TRACE) << "Entering Browser::GetWidth"; long width = 0; this->browser_->get_Width(&width); return width; } long Browser::GetHeight() { LOG(TRACE) << "Entering Browser::GetHeight"; long height = 0; this->browser_->get_Height(&height); return height; } void Browser::SetWidth(long width) { LOG(TRACE) << "Entering Browser::SetWidth"; this->browser_->put_Width(width); } void Browser::SetHeight(long height) { LOG(TRACE) << "Entering Browser::SetHeight"; this->browser_->put_Height(height); } void Browser::AttachEvents() { LOG(TRACE) << "Entering Browser::AttachEvents"; CComPtr unknown; this->browser_->QueryInterface(&unknown); HRESULT hr = this->DispEventAdvise(unknown); } void Browser::DetachEvents() { LOG(TRACE) << "Entering Browser::DetachEvents"; CComPtr unknown; this->browser_->QueryInterface(&unknown); HRESULT hr = this->DispEventUnadvise(unknown); } void Browser::Close() { LOG(TRACE) << "Entering Browser::Close"; if (this->is_edge_chromium()) { // For Edge in IE Mode, cache the top-level hosting Chromium window // handle so they can be properly closed on quit. HWND top_level_window_handle = this->GetTopLevelWindowHandle(); ::SendMessage(this->executor_handle(), WD_ADD_CHROMIUM_WINDOW_HANDLE, reinterpret_cast(top_level_window_handle), NULL); } this->is_explicit_close_requested_ = true; this->set_is_closing(true); // Closing the browser, so having focus on a frame doesn't // make any sense. this->SetFocusedFrameByElement(NULL); HRESULT hr = S_OK; hr = this->browser_->Stop(); hr = this->browser_->Quit(); if (FAILED(hr)) { LOGHR(WARN, hr) << "Call to IWebBrowser2::Quit failed"; } } int Browser::NavigateToUrl(const std::string& url, std::string* error_message) { LOG(TRACE) << "Entring Browser::NavigateToUrl"; std::wstring wide_url = StringUtilities::ToWString(url); CComVariant url_variant(wide_url.c_str()); CComVariant dummy; HRESULT hr = this->browser_->Navigate2(&url_variant, &dummy, &dummy, &dummy, &dummy); if (FAILED(hr)) { LOGHR(WARN, hr) << "Call to IWebBrowser2::Navigate2 failed"; _com_error error(hr); std::wstring formatted_message = StringUtilities::Format( L"Received error: 0x%08x ['%s']", hr, error.ErrorMessage()); *error_message = StringUtilities::ToString(formatted_message); return EUNHANDLEDERROR; } this->set_wait_required(true); return WD_SUCCESS; } int Browser::NavigateBack() { LOG(TRACE) << "Entering Browser::NavigateBack"; LPSTREAM stream; HRESULT hr = ::CoMarshalInterThreadInterfaceInStream(IID_IWebBrowser2, this->browser_, &stream); unsigned int thread_id = 0; HANDLE thread_handle = reinterpret_cast(_beginthreadex(NULL, 0, &Browser::GoBackThreadProc, (void *)stream, 0, &thread_id)); if (thread_handle != NULL) { ::CloseHandle(thread_handle); } this->set_wait_required(true); return WD_SUCCESS; } unsigned int WINAPI Browser::GoBackThreadProc(LPVOID param) { HRESULT hr = ::CoInitialize(NULL); IWebBrowser2* browser; LPSTREAM message_payload = reinterpret_cast(param); hr = ::CoGetInterfaceAndReleaseStream(message_payload, IID_IWebBrowser2, reinterpret_cast(&browser)); if (browser != NULL) { hr = browser->GoBack(); } return 0; } int Browser::NavigateForward() { LOG(TRACE) << "Entering Browser::NavigateForward"; LPSTREAM stream; HRESULT hr = ::CoMarshalInterThreadInterfaceInStream(IID_IWebBrowser2, this->browser_, &stream); unsigned int thread_id = 0; HANDLE thread_handle = reinterpret_cast(_beginthreadex(NULL, 0, &Browser::GoForwardThreadProc, (void *)stream, 0, &thread_id)); if (thread_handle != NULL) { ::CloseHandle(thread_handle); } this->set_wait_required(true); return WD_SUCCESS; } unsigned int WINAPI Browser::GoForwardThreadProc(LPVOID param) { HRESULT hr = ::CoInitialize(NULL); IWebBrowser2* browser; LPSTREAM message_payload = reinterpret_cast(param); hr = ::CoGetInterfaceAndReleaseStream(message_payload, IID_IWebBrowser2, reinterpret_cast(&browser)); if (browser != NULL) { hr = browser->GoForward(); } return 0; } int Browser::Refresh() { LOG(TRACE) << "Entering Browser::Refresh"; HRESULT hr = this->browser_->Refresh(); if (FAILED(hr)) { LOGHR(WARN, hr) << "Call to IWebBrowser2::Refresh failed"; } this->set_wait_required(true); return WD_SUCCESS; } HWND Browser::GetTopLevelWindowHandle() { LOG(TRACE) << "Entering Browser::GetTopLevelWindowHandle"; HWND top_level_window_handle = NULL; HRESULT hr = this->browser_->get_HWND(reinterpret_cast(&top_level_window_handle)); if (FAILED(hr)) { LOGHR(WARN, hr) << "Getting HWND property of IWebBrowser2 object failed"; } return top_level_window_handle; } bool Browser::IsValidWindow() { LOG(TRACE) << "Entering Browser::IsValidWindow"; // This is a no-op for this class. Full browser windows can properly notify // of their window's validity by using the proper events. return true; } bool Browser::IsBusy() { VARIANT_BOOL is_busy(VARIANT_FALSE); HRESULT hr = this->browser_->get_Busy(&is_busy); return SUCCEEDED(hr) && is_busy == VARIANT_TRUE; } bool Browser::Wait(const std::string& page_load_strategy) { LOG(TRACE) << "Entering Browser::Wait"; if (page_load_strategy == NONE_PAGE_LOAD_STRATEGY) { LOG(DEBUG) << "Page load strategy is 'none'. Aborting wait."; this->set_wait_required(false); return true; } if (this->is_awaiting_new_process()) { return false; } bool is_navigating = true; LOG(DEBUG) << "Navigate Events Completed."; this->is_navigation_started_ = false; HWND dialog = this->GetActiveDialogWindowHandle(); if (dialog != NULL) { LOG(DEBUG) << "Found alert. Aborting wait."; this->set_wait_required(false); return true; } // Navigate events completed. Waiting for browser.Busy != false... is_navigating = this->is_navigation_started_; if (is_navigating || (page_load_strategy == NORMAL_PAGE_LOAD_STRATEGY && this->IsBusy())) { if (is_navigating) { LOG(DEBUG) << "DocumentComplete event fired, indicating a new navigation."; } else { LOG(DEBUG) << "Browser busy property is true."; } return false; } READYSTATE expected_ready_state = READYSTATE_COMPLETE; if (page_load_strategy == EAGER_PAGE_LOAD_STRATEGY) { expected_ready_state = READYSTATE_INTERACTIVE; } // Waiting for browser.ReadyState >= expected ready state is_navigating = this->is_navigation_started_; READYSTATE ready_state; HRESULT hr = this->browser_->get_ReadyState(&ready_state); if (is_navigating || FAILED(hr) || ready_state < expected_ready_state) { if (is_navigating) { LOG(DEBUG) << "DocumentComplete event fired, indicating a new navigation."; } else if (FAILED(hr)) { LOGHR(DEBUG, hr) << "IWebBrowser2::get_ReadyState failed."; } else { LOG(DEBUG) << "Browser ReadyState is not at least '" << expected_ready_state << "'; it was " << ready_state; } return false; } // Waiting for document property != null... is_navigating = this->is_navigation_started_; CComPtr document_dispatch; hr = this->browser_->get_Document(&document_dispatch); if (is_navigating || FAILED(hr) || !document_dispatch) { if (is_navigating) { LOG(DEBUG) << "DocumentComplete event fired, indicating a new navigation."; } else if (FAILED(hr)) { LOGHR(DEBUG, hr) << "IWebBrowser2::get_Document failed."; } else { LOG(DEBUG) << "Get Document failed; IWebBrowser2::get_Document did not return a valid IDispatch object."; } return false; } // Waiting for document to complete... CComPtr doc; hr = document_dispatch->QueryInterface(&doc); if (SUCCEEDED(hr)) { LOG(DEBUG) << "Waiting for document to complete..."; is_navigating = this->IsDocumentNavigating(page_load_strategy, doc); } if (!is_navigating) { LOG(DEBUG) << "Not in navigating state"; this->set_wait_required(false); } return !is_navigating; } bool Browser::IsDocumentNavigating(const std::string& page_load_strategy, IHTMLDocument2* doc) { LOG(TRACE) << "Entering Browser::IsDocumentNavigating"; bool is_navigating = true; // Starting WaitForDocumentComplete() is_navigating = this->is_navigation_started_; CComBSTR ready_state_bstr; HRESULT hr = doc->get_readyState(&ready_state_bstr); if (FAILED(hr) || is_navigating) { if (FAILED(hr)) { LOGHR(DEBUG, hr) << "IHTMLDocument2::get_readyState failed."; } else if (is_navigating) { LOG(DEBUG) << "DocumentComplete event fired, indicating a new navigation."; } return true; } else { std::wstring ready_state = ready_state_bstr; if ((ready_state == L"complete") || (page_load_strategy == EAGER_PAGE_LOAD_STRATEGY && ready_state == L"interactive")) { is_navigating = false; } else { if (page_load_strategy == EAGER_PAGE_LOAD_STRATEGY) { LOG(DEBUG) << "document.readyState is not 'complete' or 'interactive'; it was " << LOGWSTRING(ready_state); } else { LOG(DEBUG) << "document.readyState is not 'complete'; it was " << LOGWSTRING(ready_state); } return true; } } // document.readyState == complete is_navigating = this->is_navigation_started_; CComPtr frames; hr = doc->get_frames(&frames); if (is_navigating || FAILED(hr)) { LOG(DEBUG) << "Could not get frames, navigation has started or call to IHTMLDocument2::get_frames failed"; return true; } if (frames != NULL) { long frame_count = 0; hr = frames->get_length(&frame_count); CComVariant index; index.vt = VT_I4; for (long i = 0; i < frame_count; ++i) { // Waiting on each frame index.lVal = i; CComVariant result; hr = frames->item(&index, &result); if (FAILED(hr)) { LOGHR(DEBUG, hr) << "Could not get frame item for index " << i << ", call to IHTMLFramesCollection2::item failed, frame/frameset is broken"; continue; } CComPtr window; result.pdispVal->QueryInterface(&window); if (!window) { LOG(DEBUG) << "Could not get window for frame item with index " << i << ", cast to IHTMLWindow2 failed, frame is not an HTML frame"; continue; } CComPtr frame_document; bool is_valid_frame_document = this->GetDocumentFromWindow(window, &frame_document); is_navigating = this->is_navigation_started_; if (is_navigating) { break; } // Recursively call to wait for the frame document to complete if (is_valid_frame_document) { is_navigating = this->IsDocumentNavigating(page_load_strategy, frame_document); if (is_navigating) { break; } } } } else { LOG(DEBUG) << "IHTMLDocument2.get_frames() returned empty collection"; } return is_navigating; } bool Browser::GetDocumentFromWindow(IHTMLWindow2* window, IHTMLDocument2** doc) { LOG(TRACE) << "Entering Browser::GetDocumentFromWindow"; HRESULT hr = window->get_document(doc); if (SUCCEEDED(hr)) { return true; } if (hr == E_ACCESSDENIED) { // Cross-domain documents may throw Access Denied. If so, // get the document through the IWebBrowser2 interface. CComPtr service_provider; hr = window->QueryInterface(&service_provider); if (FAILED(hr)) { LOGHR(WARN, hr) << "Unable to get browser, call to IHTMLWindow2::QueryService failed for IServiceProvider"; return false; } CComPtr window_browser; hr = service_provider->QueryService(IID_IWebBrowserApp, &window_browser); if (FAILED(hr)) { LOGHR(WARN, hr) << "Unable to get browser, call to IServiceProvider::QueryService failed for IID_IWebBrowserApp"; return false; } CComPtr document_dispatch; hr = window_browser->get_Document(&document_dispatch); if (FAILED(hr) || hr == S_FALSE) { LOGHR(WARN, hr) << "Unable to get document, call to IWebBrowser2::get_Document failed"; return false; } hr = document_dispatch->QueryInterface(doc); if (FAILED(hr)) { LOGHR(WARN, hr) << "Unable to query document, call to IDispatch::QueryInterface failed."; return false; } return true; } else { LOGHR(WARN, hr) << "Unable to get main document, IHTMLWindow2::get_document returned other than E_ACCESSDENIED"; } return false; } HWND Browser::GetBrowserWindowHandle() { LOG(TRACE) << "Entering Browser::GetBrowserWindowHandle"; HWND hwnd = NULL; CComPtr service_provider; HRESULT hr = this->browser_->QueryInterface(IID_IServiceProvider, reinterpret_cast(&service_provider)); HWND hwnd_tmp = NULL; this->browser_->get_HWND(reinterpret_cast(&hwnd_tmp)); if (SUCCEEDED(hr)) { CComPtr window; hr = service_provider->QueryService(SID_SShellBrowser, IID_IOleWindow, reinterpret_cast(&window)); if (SUCCEEDED(hr)) { // This gets the TabWindowClass window in IE 7 and 8, // and the top-level window frame in IE 6. window->GetWindow(&hwnd); } else { LOGHR(WARN, hr) << "Unable to get window, call to IOleWindow::QueryService for SID_SShellBrowser failed"; } } else { LOGHR(WARN, hr) << "Unable to get service, call to IWebBrowser2::QueryInterface for IID_IServiceProvider failed"; } return hwnd; } bool Browser::SetFullScreen(bool is_full_screen) { VARIANT_BOOL full_screen_value = VARIANT_TRUE; std::wstring full_screen_script = L"window.fullScreen = true;"; if (!is_full_screen) { full_screen_value = VARIANT_FALSE; full_screen_script = L"delete window.fullScreen;"; } this->browser_->put_FullScreen(full_screen_value); // IE does not support the W3C Fullscreen API (and likely never will). // The prefixed version cannot be triggered via JavaScript outside of // a user interaction, so we're going to cheat here and manually set // the fullScreen property of the window object to the appropriate // value. This may interfere with polyfills in use, and if that's // the case, we'll revisit this hack. CComPtr doc; this->GetDocument(true, &doc); std::wstring script = ANONYMOUS_FUNCTION_START; script += full_screen_script; script += ANONYMOUS_FUNCTION_END; Script script_wrapper(doc, script, 0); script_wrapper.Execute(); return true; } bool Browser::IsFullScreen() { VARIANT_BOOL is_full_screen = VARIANT_FALSE; this->browser_->get_FullScreen(&is_full_screen); return is_full_screen == VARIANT_TRUE; } HWND Browser::GetActiveDialogWindowHandle() { LOG(TRACE) << "Entering Browser::GetActiveDialogWindowHandle"; HWND active_dialog_handle = NULL; HWND content_window_handle = this->GetContentWindowHandle(); if (content_window_handle == NULL) { return active_dialog_handle; } DWORD process_id = 0; ::GetWindowThreadProcessId(content_window_handle, &process_id); if (process_id == 0) { return active_dialog_handle; } ProcessWindowInfo process_win_info; process_win_info.dwProcessId = process_id; process_win_info.hwndBrowser = NULL; ::EnumWindows(&BrowserFactory::FindDialogWindowForProcess, reinterpret_cast(&process_win_info)); if (process_win_info.hwndBrowser != NULL) { active_dialog_handle = process_win_info.hwndBrowser; this->CheckDialogType(active_dialog_handle); } return active_dialog_handle; } void Browser::CheckDialogType(HWND dialog_window_handle) { LOG(TRACE) << "Entering Browser::CheckDialogType"; std::vector window_class_name(34); if (GetClassNameA(dialog_window_handle, &window_class_name[0], 34)) { if (strcmp(HTML_DIALOG_WINDOW_CLASS, &window_class_name[0]) == 0) { HWND content_window_handle = this->FindContentWindowHandle(dialog_window_handle); ::PostMessage(this->executor_handle(), WD_NEW_HTML_DIALOG, NULL, reinterpret_cast(content_window_handle)); } } } } // namespace webdriver