| // 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 "HtmlDialog.h" |
| |
| #include "errorcodes.h" |
| #include "logging.h" |
| |
| #include "BrowserFactory.h" |
| #include "StringUtilities.h" |
| #include "WebDriverConstants.h" |
| |
| #define HIDDEN_PARENT_WINDOW_CLASS "Internet Explorer_Hidden" |
| |
| namespace webdriver { |
| |
| HtmlDialog::HtmlDialog(IHTMLWindow2* window, HWND hwnd, HWND session_handle) : DocumentHost(hwnd, session_handle) { |
| LOG(TRACE) << "Entering HtmlDialog::HtmlDialog"; |
| this->is_navigating_ = false; |
| this->window_ = window; |
| this->AttachEvents(); |
| } |
| |
| HtmlDialog::~HtmlDialog(void) { |
| } |
| |
| void HtmlDialog::AttachEvents() { |
| CComPtr<IUnknown> unknown; |
| this->window_->QueryInterface<IUnknown>(&unknown); |
| HRESULT hr = this->DispEventAdvise(unknown); |
| } |
| |
| void HtmlDialog::DetachEvents() { |
| LOG(TRACE) << "Entering HtmlDialog::DetachEvents"; |
| CComPtr<IUnknown> unknown; |
| this->window_->QueryInterface<IUnknown>(&unknown); |
| HRESULT hr = this->DispEventUnadvise(unknown); |
| } |
| |
| void __stdcall HtmlDialog::OnBeforeUnload(IHTMLEventObj *pEvtObj) { |
| LOG(TRACE) << "Entering HtmlDialog::OnBeforeUnload"; |
| this->is_navigating_ = true; |
| } |
| |
| void __stdcall HtmlDialog::OnLoad(IHTMLEventObj *pEvtObj) { |
| LOG(TRACE) << "Entering HtmlDialog::OnLoad"; |
| this->is_navigating_ = false; |
| } |
| |
| void HtmlDialog::GetDocument(IHTMLDocument2** doc) { |
| this->GetDocument(false, doc); |
| } |
| |
| void HtmlDialog::GetDocument(const bool force_top_level_document, |
| IHTMLDocument2** doc) { |
| LOG(TRACE) << "Entering HtmlDialog::GetDocument"; |
| HRESULT hr = S_OK; |
| if (this->focused_frame_window() == NULL || force_top_level_document) { |
| hr = this->window_->get_document(doc); |
| } else { |
| hr = this->focused_frame_window()->get_document(doc); |
| } |
| |
| if (FAILED(hr)) { |
| LOGHR(DEBUG, hr) << "Unable to get document"; |
| } |
| } |
| |
| void HtmlDialog::Close() { |
| LOG(TRACE) << "Entering HtmlDialog::Close"; |
| if (!this->is_closing()) { |
| this->is_navigating_ = false; |
| this->set_is_closing(true); |
| // Closing the browser, so having focus on a frame doesn't |
| // make any sense. |
| this->SetFocusedFrameByElement(NULL); |
| this->DetachEvents(); |
| this->window_->close(); |
| |
| // Must manually release the CComPtr<IHTMLWindow> so that the |
| // destructor will not try to release a no-longer-valid object. |
| this->window_.Release(); |
| this->window_ = NULL; |
| |
| this->PostQuitMessage(); |
| } |
| } |
| |
| bool HtmlDialog::IsValidWindow() { |
| LOG(TRACE) << "Entering HtmlDialog::IsValidWindow"; |
| // If the window handle is no longer valid, the window is closing, |
| // and we must post the quit message. |
| if (!::IsWindow(this->GetTopLevelWindowHandle())) { |
| this->is_navigating_ = false; |
| this->DetachEvents(); |
| this->PostQuitMessage(); |
| return false; |
| } |
| return true; |
| } |
| |
| bool HtmlDialog::SetFullScreen(bool is_full_screen) { |
| return false; |
| } |
| |
| bool HtmlDialog::IsFullScreen() { |
| return false; |
| } |
| |
| bool HtmlDialog::IsBusy() { |
| LOG(TRACE) << "Entering HtmlDialog::IsBusy"; |
| return false; |
| } |
| |
| bool HtmlDialog::Wait(const std::string& page_load_strategy) { |
| LOG(TRACE) << "Entering HtmlDialog::Wait"; |
| // If the window is no longer valid, the window is closing, |
| // and the wait is completed. |
| if (!this->is_closing() && !this->IsValidWindow()) { |
| return true; |
| } |
| |
| // Check to see if a new dialog has opened up on top of this one. |
| // If so, the wait is completed, no matter whether the OnUnload |
| // event has fired signaling navigation started, nor whether the |
| // OnLoad event has fired signaling navigation complete. Set the |
| // flag so that the Wait method is no longer called. |
| HWND child_dialog_handle = this->GetActiveDialogWindowHandle(); |
| if (child_dialog_handle != NULL) { |
| this->is_navigating_ = false; |
| this->set_wait_required(false); |
| return true; |
| } |
| |
| // Otherwise, we wait a short amount and see if navigation is complete |
| // (signaled by the OnLoad event firing). |
| ::Sleep(250); |
| return !this->is_navigating_; |
| } |
| |
| HWND HtmlDialog::GetContentWindowHandle() { |
| LOG(TRACE) << "Entering HtmlDialog::GetContentWindowHandle"; |
| return this->window_handle(); |
| } |
| |
| HWND HtmlDialog::GetBrowserWindowHandle() { |
| LOG(TRACE) << "Entering HtmlDialog::GetBrowserWindowHandle"; |
| return this->window_handle(); |
| } |
| |
| std::string HtmlDialog::GetWindowName() { |
| LOG(TRACE) << "Entering HtmlDialog::GetWindowName"; |
| return ""; |
| } |
| |
| std::string HtmlDialog::GetBrowserUrl() { |
| LOG(TRACE) << "Entering HtmlDialog::GetBrowserUrl"; |
| return ""; |
| } |
| |
| std::string HtmlDialog::GetTitle() { |
| LOG(TRACE) << "Entering HtmlDialog::GetTitle"; |
| CComPtr<IHTMLDocument2> doc; |
| this->GetDocument(&doc); |
| CComBSTR title; |
| HRESULT hr = doc->get_title(&title); |
| if (FAILED(hr)) { |
| LOGHR(WARN, hr) << "Unable to get document title"; |
| return ""; |
| } |
| |
| std::wstring converted_title = title; |
| std::string title_string = StringUtilities::ToString(converted_title); |
| return title_string; |
| } |
| |
| HWND HtmlDialog::GetTopLevelWindowHandle(void) { |
| LOG(TRACE) << "Entering HtmlDialog::GetTopLevelWindowHandle"; |
| HWND parent_handle = ::GetParent(this->window_handle()); |
| |
| // "Internet Explorer_Hidden\0" == 25 |
| std::vector<char> parent_class_buffer(25); |
| if (::GetClassNameA(parent_handle, &parent_class_buffer[0], 25)) { |
| if (strcmp(HIDDEN_PARENT_WINDOW_CLASS, &parent_class_buffer[0]) == 0) { |
| // Some versions of Internet Explorer re-parent a closing showModalDialog |
| // window to a hidden parent window. If that is what we see happening |
| // here, that will be equivalent to the parent window no longer being |
| // valid, and we can return an invalid handle, indicating the window is |
| // "closed." |
| return NULL; |
| } |
| } |
| return parent_handle; |
| } |
| |
| HWND HtmlDialog::GetActiveDialogWindowHandle() { |
| LOG(TRACE) << "Entering HtmlDialog::GetActiveDialogWindowHandle"; |
| DialogWindowInfo info; |
| info.hwndOwner = this->GetTopLevelWindowHandle(); |
| info.hwndDialog = NULL; |
| if (info.hwndOwner != NULL) { |
| ::EnumWindows(&HtmlDialog::FindChildDialogWindow, reinterpret_cast<LPARAM>(&info)); |
| } |
| if (info.hwndDialog != NULL) { |
| std::vector<char> window_class_name(34); |
| if (::GetClassNameA(info.hwndDialog, &window_class_name[0], 34)) { |
| if (strcmp(HTML_DIALOG_WINDOW_CLASS, &window_class_name[0]) == 0) { |
| HWND content_window_handle = this->FindContentWindowHandle(info.hwndDialog); |
| if (content_window_handle != NULL) { |
| // Must have a sleep here to give IE a chance to draw the window. |
| ::Sleep(250); |
| ::PostMessage(this->executor_handle(), |
| WD_NEW_HTML_DIALOG, |
| NULL, |
| reinterpret_cast<LPARAM>(content_window_handle)); |
| } |
| } |
| } |
| } |
| return info.hwndDialog; |
| } |
| |
| long HtmlDialog::GetWidth() { |
| LOG(TRACE) << "Entering HtmlDialog::GetWidth"; |
| // TODO: calculate width |
| return 0L; |
| } |
| |
| long HtmlDialog::GetHeight() { |
| LOG(TRACE) << "Entering HtmlDialog::GetHeight"; |
| // TODO: calculate height |
| return 0L; |
| } |
| |
| void HtmlDialog::SetWidth(long width) { |
| LOG(TRACE) << "Entering HtmlDialog::SetWidth"; |
| } |
| |
| void HtmlDialog::SetHeight(long height) { |
| LOG(TRACE) << "Entering HtmlDialog::SetHeight"; |
| } |
| |
| int HtmlDialog::NavigateToUrl(const std::string& url, |
| std::string* error_message) { |
| LOG(TRACE) << "Entering HtmlDialog::NavigateToUrl"; |
| // Cannot force navigation on windows opened with showModalDialog(); |
| return ENOTIMPLEMENTED; |
| } |
| |
| int HtmlDialog::NavigateBack() { |
| LOG(TRACE) << "Entering HtmlDialog::NavigateBack"; |
| // Cannot force navigation on windows opened with showModalDialog(); |
| return ENOTIMPLEMENTED; |
| } |
| |
| int HtmlDialog::NavigateForward() { |
| LOG(TRACE) << "Entering HtmlDialog::NavigateForward"; |
| // Cannot force navigation on windows opened with showModalDialog(); |
| return ENOTIMPLEMENTED; |
| } |
| |
| int HtmlDialog::Refresh() { |
| LOG(TRACE) << "Entering HtmlDialog::Refresh"; |
| // Cannot force navigation on windows opened with showModalDialog(); |
| return ENOTIMPLEMENTED; |
| } |
| |
| BOOL CALLBACK HtmlDialog::FindChildDialogWindow(HWND hwnd, LPARAM arg) { |
| DialogWindowInfo* window_info = reinterpret_cast<DialogWindowInfo*>(arg); |
| if (::GetWindow(hwnd, GW_OWNER) != window_info->hwndOwner) { |
| return TRUE; |
| } |
| std::vector<char> window_class_name(34); |
| if (::GetClassNameA(hwnd, &window_class_name[0], 34) == 0) { |
| // No match found. Skip |
| return TRUE; |
| } |
| if (strcmp(ALERT_WINDOW_CLASS, &window_class_name[0]) != 0 && |
| strcmp(HTML_DIALOG_WINDOW_CLASS, &window_class_name[0]) != 0) { |
| return TRUE; |
| } else { |
| // If the window style has the WS_DISABLED bit set or the |
| // WS_VISIBLE bit unset, it can't be handled via the UI, |
| // and must not be a visible dialog. |
| if ((::GetWindowLong(hwnd, GWL_STYLE) & WS_DISABLED) != 0 || |
| (::GetWindowLong(hwnd, GWL_STYLE) & WS_VISIBLE) == 0) { |
| return TRUE; |
| } |
| } |
| window_info->hwndDialog = hwnd; |
| return FALSE; |
| } |
| |
| } // namespace webdriver |