diff options
Diffstat (limited to 'chrome_frame/test/ie_event_sink.cc')
-rw-r--r-- | chrome_frame/test/ie_event_sink.cc | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/chrome_frame/test/ie_event_sink.cc b/chrome_frame/test/ie_event_sink.cc new file mode 100644 index 0000000..2f7d32c --- /dev/null +++ b/chrome_frame/test/ie_event_sink.cc @@ -0,0 +1,545 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/test/ie_event_sink.h" + +#include "base/scoped_bstr_win.h" +#include "base/scoped_handle.h" +#include "base/utf_string_conversions.h" +#include "base/win_util.h" +#include "chrome_frame/test/chrome_frame_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chrome_frame_test { + +const int kDefaultWaitForIEToTerminateMs = 10 * 1000; + +_ATL_FUNC_INFO IEEventSink::kNavigateErrorInfo = { + CC_STDCALL, VT_EMPTY, 5, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_BOOL | VT_BYREF, + } +}; + +_ATL_FUNC_INFO IEEventSink::kNavigateComplete2Info = { + CC_STDCALL, VT_EMPTY, 2, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF + } +}; + +_ATL_FUNC_INFO IEEventSink::kBeforeNavigate2Info = { + CC_STDCALL, VT_EMPTY, 7, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_BOOL | VT_BYREF + } +}; + +_ATL_FUNC_INFO IEEventSink::kNewWindow2Info = { + CC_STDCALL, VT_EMPTY, 2, { + VT_DISPATCH | VT_BYREF, + VT_BOOL | VT_BYREF, + } +}; + +_ATL_FUNC_INFO IEEventSink::kNewWindow3Info = { + CC_STDCALL, VT_EMPTY, 5, { + VT_DISPATCH | VT_BYREF, + VT_BOOL | VT_BYREF, + VT_UINT, + VT_BSTR, + VT_BSTR + } +}; + +_ATL_FUNC_INFO IEEventSink::kVoidMethodInfo = { + CC_STDCALL, VT_EMPTY, 0, {NULL}}; + +_ATL_FUNC_INFO IEEventSink::kDocumentCompleteInfo = { + CC_STDCALL, VT_EMPTY, 2, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF + } +}; + +_ATL_FUNC_INFO IEEventSink::kFileDownloadInfo = { + CC_STDCALL, VT_EMPTY, 2, { + VT_BOOL, + VT_BOOL | VT_BYREF + } +}; + +IEEventSink::IEEventSink() + : ALLOW_THIS_IN_INITIALIZER_LIST( + onmessage_(this, &IEEventSink::OnMessage)), + ALLOW_THIS_IN_INITIALIZER_LIST( + onloaderror_(this, &IEEventSink::OnLoadError)), + ALLOW_THIS_IN_INITIALIZER_LIST( + onload_(this, &IEEventSink::OnLoad)), + listener_(NULL), + ie_process_id_(0), + did_receive_on_quit_(false) { +} + +IEEventSink::~IEEventSink() { + Uninitialize(); +} + +// IEEventSink member defines +void IEEventSink::Attach(IDispatch* browser_disp) { + EXPECT_TRUE(NULL != browser_disp); + if (browser_disp) { + EXPECT_HRESULT_SUCCEEDED(web_browser2_.QueryFrom(browser_disp)); + EXPECT_TRUE(S_OK == DispEventAdvise(web_browser2_, + &DIID_DWebBrowserEvents2)); + FindIEProcessId(); + } +} + +void IEEventSink::Uninitialize() { + DisconnectFromChromeFrame(); + if (web_browser2_.get()) { + if (m_dwEventCookie != 0xFEFEFEFE) { + DispEventUnadvise(web_browser2_); + CoDisconnectObject(this, 0); + } + + if (!did_receive_on_quit_) { + // Log the browser window url for debugging purposes. + ScopedBstr browser_url; + web_browser2_->get_LocationURL(browser_url.Receive()); + std::wstring browser_url_wstring; + browser_url_wstring.assign(browser_url, browser_url.Length()); + std::string browser_url_string = WideToUTF8(browser_url_wstring); + EXPECT_TRUE(did_receive_on_quit_) << "OnQuit was not received for " + << "browser with url " + << browser_url_string; + + web_browser2_->Quit(); + } + + ScopedHandle process; + process.Set(OpenProcess(SYNCHRONIZE, FALSE, ie_process_id_)); + DLOG_IF(WARNING, !process.IsValid()) + << StringPrintf("OpenProcess failed: %i", ::GetLastError()); + + web_browser2_.Release(); + + // IE may not have closed yet. Wait here for the process to finish. + // This is necessary at least on some browser/platform configurations. + if (process) { + DWORD max_wait = kDefaultWaitForIEToTerminateMs; + while (true) { + base::Time start = base::Time::Now(); + HANDLE wait_for = process; + DWORD wait = MsgWaitForMultipleObjects(1, &wait_for, FALSE, max_wait, + QS_ALLINPUT); + if (wait == WAIT_OBJECT_0 + 1) { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, TRUE) > 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } else if (wait == WAIT_OBJECT_0) { + break; + } else { + DCHECK(wait == WAIT_TIMEOUT); + DLOG(ERROR) << "Wait for IE timed out"; + break; + } + base::TimeDelta elapsed = base::Time::Now() - start; + ULARGE_INTEGER ms; + ms.QuadPart = elapsed.InMilliseconds(); + DCHECK_EQ(ms.HighPart, 0U); + if (ms.LowPart > max_wait) { + DLOG(ERROR) << "Wait for IE timed out (2)"; + break; + } else { + max_wait -= ms.LowPart; + } + } + } + } +} + +bool IEEventSink::IsCFRendering() { + DCHECK(web_browser2_); + + HWND renderer_window; + if (web_browser2_) { + ScopedComPtr<IDispatch> doc; + web_browser2_->get_Document(doc.Receive()); + if (doc) { + ScopedComPtr<IOleWindow> ole_window; + ole_window.QueryFrom(doc); + if (ole_window) { + ole_window->GetWindow(&renderer_window); + if (IsWindow(renderer_window)) { + wchar_t class_name[MAX_PATH] = {0}; + GetClassName(renderer_window, class_name, arraysize(class_name)); + return _wcsicmp(class_name, L"Internet Explorer_Server") != 0; + } + } + } + } + return false; +} + +void IEEventSink::SetFocusToRenderer() { + simulate_input::SetKeyboardFocusToWindow(GetRendererWindow()); +} + +void IEEventSink::SendKeys(const wchar_t* input_string) { + SetFocusToRenderer(); + simulate_input::SendStringW(input_string); +} + +void IEEventSink::SendMouseClick(int x, int y, + simulate_input::MouseButton button) { + simulate_input::SendMouseClick(GetRendererWindow(), x, y, button); +} + +void IEEventSink::ExpectRendererWindowHasFocus() { + HWND renderer_window = GetRendererWindow(); + EXPECT_TRUE(IsWindow(renderer_window)); + + if (IsCFRendering()) { + for (HWND first_child = renderer_window; + IsWindow(first_child); first_child = GetWindow(first_child, GW_CHILD)) { + renderer_window = first_child; + } + + wchar_t class_name[MAX_PATH] = {0}; + GetClassName(renderer_window, class_name, arraysize(class_name)); + EXPECT_EQ(0, _wcsicmp(class_name, L"Chrome_RenderWidgetHostHWND")); + } + + DWORD renderer_thread = 0; + DWORD renderer_process = 0; + renderer_thread = GetWindowThreadProcessId(renderer_window, + &renderer_process); + + ASSERT_TRUE(AttachThreadInput(GetCurrentThreadId(), renderer_thread, TRUE)); + HWND focus_window = GetFocus(); + EXPECT_TRUE(focus_window == renderer_window); + EXPECT_TRUE(AttachThreadInput(GetCurrentThreadId(), renderer_thread, FALSE)); +} + +void IEEventSink::ExpectAddressBarUrl( + const std::wstring& expected_url) { + DCHECK(web_browser2_); + if (web_browser2_) { + ScopedBstr address_bar_url; + EXPECT_EQ(S_OK, web_browser2_->get_LocationURL(address_bar_url.Receive())); + EXPECT_EQ(expected_url, std::wstring(address_bar_url)); + } +} + +void IEEventSink::Exec(const GUID* cmd_group_guid, DWORD command_id, + DWORD cmd_exec_opt, VARIANT* in_args, + VARIANT* out_args) { + ScopedComPtr<IOleCommandTarget> shell_browser_cmd_target; + DoQueryService(SID_STopLevelBrowser, web_browser2_, + shell_browser_cmd_target.Receive()); + ASSERT_TRUE(NULL != shell_browser_cmd_target); + EXPECT_HRESULT_SUCCEEDED(shell_browser_cmd_target->Exec(cmd_group_guid, + command_id, cmd_exec_opt, in_args, out_args)); +} + +HWND IEEventSink::GetRendererWindow() { + HWND renderer_window = NULL; + if (IsCFRendering()) { + DCHECK(chrome_frame_); + ScopedComPtr<IOleWindow> ole_window; + ole_window.QueryFrom(chrome_frame_); + EXPECT_TRUE(ole_window.get()); + + if (ole_window) { + HWND activex_window = NULL; + ole_window->GetWindow(&activex_window); + EXPECT_TRUE(IsWindow(activex_window)); + + // chrome tab window is the first (and the only) child of activex + HWND chrome_tab_window = GetWindow(activex_window, GW_CHILD); + EXPECT_TRUE(IsWindow(chrome_tab_window)); + renderer_window = GetWindow(chrome_tab_window, GW_CHILD); + } + } else { + DCHECK(web_browser2_); + ScopedComPtr<IDispatch> doc; + HRESULT hr = web_browser2_->get_Document(doc.Receive()); + EXPECT_HRESULT_SUCCEEDED(hr); + EXPECT_TRUE(doc); + if (doc) { + ScopedComPtr<IOleWindow> ole_window; + ole_window.QueryFrom(doc); + EXPECT_TRUE(ole_window); + if (ole_window) { + ole_window->GetWindow(&renderer_window); + } + } + } + + EXPECT_TRUE(IsWindow(renderer_window)); + return renderer_window; +} + +HRESULT IEEventSink::LaunchIEAndNavigate( + const std::wstring& navigate_url, IEEventListener* listener) { + listener_ = listener; + HRESULT hr = LaunchIEAsComServer(web_browser2_.Receive()); + EXPECT_EQ(S_OK, hr); + if (hr == S_OK) { + FindIEProcessId(); + web_browser2_->put_Visible(VARIANT_TRUE); + hr = DispEventAdvise(web_browser2_, &DIID_DWebBrowserEvents2); + EXPECT_TRUE(hr == S_OK); + hr = Navigate(navigate_url); + } + + DLOG_IF(WARNING, FAILED(hr)) << "Failed to launch IE. Error:" << hr; + return hr; +} + +HRESULT IEEventSink::Navigate(const std::wstring& navigate_url) { + VARIANT empty = ScopedVariant::kEmptyVariant; + ScopedVariant url; + url.Set(navigate_url.c_str()); + + HRESULT hr = S_OK; + hr = web_browser2_->Navigate2(url.AsInput(), &empty, &empty, &empty, &empty); + EXPECT_TRUE(hr == S_OK); + return hr; +} + +HRESULT IEEventSink::CloseWebBrowser() { + if (!web_browser2_) + return E_FAIL; + + DisconnectFromChromeFrame(); + EXPECT_HRESULT_SUCCEEDED(web_browser2_->Quit()); + return S_OK; +} + +void IEEventSink::Refresh() { + ScopedVariant refresh_level(REFRESH_NORMAL); + web_browser2_->Refresh2(refresh_level.AsInput()); +} + +void IEEventSink::ConnectToChromeFrame() { + DCHECK(web_browser2_); + if (chrome_frame_.get()) + return; + ScopedComPtr<IShellBrowser> shell_browser; + DoQueryService(SID_STopLevelBrowser, web_browser2_, + shell_browser.Receive()); + + if (shell_browser) { + ScopedComPtr<IShellView> shell_view; + shell_browser->QueryActiveShellView(shell_view.Receive()); + if (shell_view) { + shell_view->GetItemObject(SVGIO_BACKGROUND, __uuidof(IChromeFrame), + reinterpret_cast<void**>(chrome_frame_.Receive())); + } + + if (chrome_frame_) { + ScopedVariant onmessage(onmessage_.ToDispatch()); + ScopedVariant onloaderror(onloaderror_.ToDispatch()); + ScopedVariant onload(onload_.ToDispatch()); + EXPECT_HRESULT_SUCCEEDED(chrome_frame_->put_onmessage(onmessage)); + EXPECT_HRESULT_SUCCEEDED(chrome_frame_->put_onloaderror(onloaderror)); + EXPECT_HRESULT_SUCCEEDED(chrome_frame_->put_onload(onload)); + } + } +} + +void IEEventSink::DisconnectFromChromeFrame() { + if (chrome_frame_) { + // Use a local ref counted copy of the IChromeFrame interface as the + // outgoing calls could cause the interface to be deleted due to a message + // pump running in the context of the outgoing call. + ScopedComPtr<IChromeFrame> chrome_frame(chrome_frame_); + chrome_frame_.Release(); + ScopedVariant dummy(static_cast<IDispatch*>(NULL)); + chrome_frame->put_onmessage(dummy); + chrome_frame->put_onload(dummy); + chrome_frame->put_onloaderror(dummy); + } +} + +void IEEventSink::FindIEProcessId() { + HWND hwnd = NULL; + web_browser2_->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&hwnd)); + EXPECT_TRUE(::IsWindow(hwnd)); + if (::IsWindow(hwnd)) + ::GetWindowThreadProcessId(hwnd, &ie_process_id_); +} + +// Event callbacks +STDMETHODIMP_(void) IEEventSink::OnDownloadBegin() { + if (listener_) + listener_->OnDownloadBegin(); +} + +STDMETHODIMP_(void) IEEventSink::OnNewWindow2(IDispatch** disp, + VARIANT_BOOL* s) { + if (listener_) + listener_->OnNewWindow2(disp, s); +} + +STDMETHODIMP_(void) IEEventSink::OnNavigateError(IDispatch* dispatch, + VARIANT* url, VARIANT* frame_name, VARIANT* status_code, VARIANT* cancel) { + DLOG(INFO) << __FUNCTION__; + if (listener_) + listener_->OnNavigateError(dispatch, url, frame_name, status_code, cancel); +} + +STDMETHODIMP IEEventSink::OnBeforeNavigate2( + IDispatch* dispatch, VARIANT* url, VARIANT* flags, + VARIANT* target_frame_name, VARIANT* post_data, VARIANT* headers, + VARIANT_BOOL* cancel) { + DLOG(INFO) << __FUNCTION__ + << StringPrintf("%ls - 0x%08X", url->bstrVal, this); + // Reset any existing reference to chrome frame since this is a new + // navigation. + DisconnectFromChromeFrame(); + if (listener_) + listener_->OnBeforeNavigate2(dispatch, url, flags, target_frame_name, + post_data, headers, cancel); + return S_OK; +} + +STDMETHODIMP_(void) IEEventSink::OnNavigateComplete2( + IDispatch* dispatch, VARIANT* url) { + DLOG(INFO) << __FUNCTION__; + ConnectToChromeFrame(); + if (listener_) + listener_->OnNavigateComplete2(dispatch, url); +} + +STDMETHODIMP_(void) IEEventSink::OnDocumentComplete( + IDispatch* dispatch, VARIANT* url) { + DLOG(INFO) << __FUNCTION__; + EXPECT_TRUE(url); + if (!url) + return; + if (listener_) + listener_->OnDocumentComplete(dispatch, url); +} + +STDMETHODIMP_(void) IEEventSink::OnFileDownload( + VARIANT_BOOL active_doc, VARIANT_BOOL* cancel) { + DLOG(INFO) << __FUNCTION__ << StringPrintf(" 0x%08X ad=%i", this, + active_doc); + if (listener_) + listener_->OnFileDownload(active_doc, cancel); + // Always cancel file downloads in tests. + *cancel = VARIANT_TRUE; +} + +STDMETHODIMP_(void) IEEventSink::OnNewWindow3( + IDispatch** dispatch, VARIANT_BOOL* cancel, DWORD flags, BSTR url_context, + BSTR url) { + DLOG(INFO) << __FUNCTION__; + EXPECT_TRUE(dispatch); + if (!dispatch) + return; + + if (listener_) + listener_->OnNewWindow3(dispatch, cancel, flags, url_context, url); + + // Note that |dispatch| is an [in/out] argument. IE is asking listeners if + // they want to use a IWebBrowser2 of their choice for the new window. + // Since we need to listen on events on the new browser, we create one + // if needed. + if (!*dispatch) { + ScopedComPtr<IDispatch> new_browser; + HRESULT hr = new_browser.CreateInstance(CLSID_InternetExplorer, NULL, + CLSCTX_LOCAL_SERVER); + DCHECK(SUCCEEDED(hr) && new_browser); + *dispatch = new_browser.Detach(); + } + + if (*dispatch) + listener_->OnNewBrowserWindow(*dispatch, url); +} + +STDMETHODIMP_(void) IEEventSink::OnQuit() { + DLOG(INFO) << __FUNCTION__; + + did_receive_on_quit_ = true; + + DispEventUnadvise(web_browser2_); + CoDisconnectObject(this, 0); + + if (listener_) + listener_->OnQuit(); +} + +HRESULT IEEventSink::OnLoad(const VARIANT* param) { + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + ScopedVariant stack_object(*param); + if (chrome_frame_) { + if (listener_) + listener_->OnLoad(param->bstrVal); + } else { + DLOG(WARNING) << "Invalid chrome frame pointer"; + } + return S_OK; +} + +HRESULT IEEventSink::OnLoadError(const VARIANT* param) { + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + if (chrome_frame_) { + if (listener_) + listener_->OnLoadError(param->bstrVal); + } else { + DLOG(WARNING) << "Invalid chrome frame pointer"; + } + return S_OK; +} + +HRESULT IEEventSink::OnMessage(const VARIANT* param) { + DLOG(INFO) << __FUNCTION__ << " " << param; + if (!chrome_frame_.get()) { + DLOG(WARNING) << "Invalid chrome frame pointer"; + return S_OK; + } + + ScopedVariant data, origin, source; + if (param && (V_VT(param) == VT_DISPATCH)) { + wchar_t* properties[] = { L"data", L"origin", L"source" }; + const int prop_count = arraysize(properties); + DISPID ids[prop_count] = {0}; + + HRESULT hr = param->pdispVal->GetIDsOfNames(IID_NULL, properties, + prop_count, LOCALE_SYSTEM_DEFAULT, ids); + if (SUCCEEDED(hr)) { + DISPPARAMS params = { 0 }; + EXPECT_HRESULT_SUCCEEDED(param->pdispVal->Invoke(ids[0], IID_NULL, + LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, + data.Receive(), NULL, NULL)); + EXPECT_HRESULT_SUCCEEDED(param->pdispVal->Invoke(ids[1], IID_NULL, + LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, + origin.Receive(), NULL, NULL)); + EXPECT_HRESULT_SUCCEEDED(param->pdispVal->Invoke(ids[2], IID_NULL, + LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, + source.Receive(), NULL, NULL)); + } + } + + if (listener_) + listener_->OnMessage(V_BSTR(&data), V_BSTR(&origin), V_BSTR(&source)); + return S_OK; +} + +} // namespace chrome_frame_test
\ No newline at end of file |