// Copyright (c) 2012 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 #include #include #include #include "base/lazy_instance.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "base/win/scoped_bstr.h" #include "base/win/scoped_handle.h" #include "base/win/scoped_variant.h" #include "chrome_frame/test/chrome_frame_test_utils.h" #include "testing/gtest/include/gtest/gtest.h" using base::win::ScopedBstr; namespace { // A lookup table from DISPID to DWebBrowserEvents and/or DWebBrowserEvents2 // method name. class DispIdNameTable { public: DispIdNameTable(); ~DispIdNameTable(); // Returns the method name corresponding to |dispid| or, if none is known, // the string "DISPID |dispid|". std::string Lookup(DISPID dispid) const; private: std::map dispid_to_name_; DISALLOW_COPY_AND_ASSIGN(DispIdNameTable); }; DispIdNameTable::DispIdNameTable() { static const struct { DISPID dispid; const char* name; } kIdToName[] = { // DWebBrowserEvents { 100, "BeforeNavigate" }, { 101, "NavigateComplete" }, { 102, "StatusTextChange" }, { 108, "ProgressChange" }, { 104, "DownloadComplete" }, { 105, "CommandStateChange" }, { 106, "DownloadBegin" }, { 107, "NewWindow" }, { 113, "TitleChange" }, { 200, "FrameBeforeNavigate" }, { 201, "FrameNavigateComplete" }, { 204, "FrameNewWindow" }, { 103, "Quit" }, { 109, "WindowMove" }, { 110, "WindowResize" }, { 111, "WindowActivate" }, { 112, "PropertyChange" }, // DWebBrowserEvents2 { 250, "BeforeNavigate2" }, { 251, "NewWindow2" }, { 252, "NavigateComplete2" }, { 259, "DocumentComplete" }, { 253, "OnQuit" }, { 254, "OnVisible" }, { 255, "OnToolBar" }, { 256, "OnMenuBar" }, { 257, "OnStatusBar" }, { 258, "OnFullScreen" }, { 260, "OnTheaterMode" }, { 262, "WindowSetResizable" }, { 264, "WindowSetLeft" }, { 265, "WindowSetTop" }, { 266, "WindowSetWidth" }, { 267, "WindowSetHeight" }, { 263, "WindowClosing" }, { 268, "ClientToHostWindow" }, { 269, "SetSecureLockIcon" }, { 270, "FileDownload" }, { 271, "NavigateError" }, { 225, "PrintTemplateInstantiation" }, { 226, "PrintTemplateTeardown" }, { 227, "UpdatePageStatus" }, { 272, "PrivacyImpactedStateChange" }, { 273, "NewWindow3" }, { 282, "SetPhishingFilterStatus" }, { 283, "WindowStateChanged" }, { 284, "NewProcess" }, { 285, "ThirdPartyUrlBlocked" }, { 286, "RedirectXDomainBlocked" }, // Present in ExDispid.h but not ExDisp.idl { 114, "TitleIconChange" }, { 261, "OnAddressBar" }, { 281, "ViewUpdate" }, }; size_t index_of_duplicate = 0; DISPID duplicate_dispid = 0; for (size_t i = 0; i < arraysize(kIdToName); ++i) { if (!dispid_to_name_.insert(std::make_pair(kIdToName[i].dispid, kIdToName[i].name)).second && index_of_duplicate == 0) { index_of_duplicate = i; duplicate_dispid = kIdToName[i].dispid; } } DCHECK_EQ(static_cast(0), index_of_duplicate) << "Duplicate name for DISPID " << duplicate_dispid << " at kIdToName[" << index_of_duplicate << "]"; } DispIdNameTable::~DispIdNameTable() { } std::string DispIdNameTable::Lookup(DISPID dispid) const { std::map::const_iterator it = dispid_to_name_.find(dispid); if (it != dispid_to_name_.end()) return it->second; return std::string("DISPID ").append(base::IntToString(dispid)); } base::LazyInstance g_dispIdToName = LAZY_INSTANCE_INITIALIZER; } // namespace 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 } }; bool IEEventSink::abnormal_shutdown_ = false; 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(); } void IEEventSink::SetAbnormalShutdown(bool abnormal_shutdown) { abnormal_shutdown_ = abnormal_shutdown; } // 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_HRESULT_SUCCEEDED(Attach(web_browser2_.get())); } } HRESULT IEEventSink::Attach(IWebBrowser2* browser) { DCHECK(browser); HRESULT result; if (browser) { web_browser2_ = browser; FindIEProcessId(); result = DispEventAdvise(web_browser2_, &DIID_DWebBrowserEvents2); } return result; } void IEEventSink::Uninitialize() { if (!abnormal_shutdown_) { 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); LOG(ERROR) << "OnQuit was not received for browser with url " << browser_url_string; web_browser2_->Quit(); } base::win::ScopedHandle process; process.Set(OpenProcess(SYNCHRONIZE, FALSE, ie_process_id_)); web_browser2_.Release(); if (!process.IsValid()) { LOG_IF(WARNING, !process.IsValid()) << base::StringPrintf("OpenProcess failed: %i", ::GetLastError()); return; } // IE may not have closed yet. Wait here for the process to finish. // This is necessary at least on some browser/platform configurations. WaitForSingleObject(process, kDefaultWaitForIEToTerminateMs); } } else { LOG(ERROR) << "Terminating hung IE process"; } chrome_frame_test::KillProcesses(chrome_frame_test::kIEImageName, 0, !abnormal_shutdown_); chrome_frame_test::KillProcesses(chrome_frame_test::kIEBrokerImageName, 0, !abnormal_shutdown_); } bool IEEventSink::IsCFRendering() { DCHECK(web_browser2_); if (web_browser2_) { base::win::ScopedComPtr doc; web_browser2_->get_Document(doc.Receive()); if (doc) { // Detect if CF is rendering based on whether the document is a // ChromeActiveDocument. Detecting based on hwnd is problematic as // the CF Active Document window may not have been created yet. base::win::ScopedComPtr chrome_frame; chrome_frame.QueryFrom(doc); return chrome_frame.get(); } } return false; } void IEEventSink::PostMessageToCF(const std::wstring& message, const std::wstring& target) { EXPECT_TRUE(chrome_frame_ != NULL); if (!chrome_frame_) return; ScopedBstr message_bstr(message.c_str()); base::win::ScopedVariant target_variant(target.c_str()); EXPECT_HRESULT_SUCCEEDED( chrome_frame_->postMessage(message_bstr, target_variant)); } 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)); 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) { base::win::ScopedComPtr 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::GetBrowserWindow() { HWND browser_window = NULL; web_browser2_->get_HWND(reinterpret_cast(&browser_window)); EXPECT_TRUE(::IsWindow(browser_window)); return browser_window; } HWND IEEventSink::GetRendererWindow() { HWND renderer_window = NULL; if (IsCFRendering()) { DCHECK(chrome_frame_); base::win::ScopedComPtr 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)); wchar_t class_name[MAX_PATH] = {0}; HWND child_window = NULL; // chrome tab window is the first (and the only) child of activex for (HWND first_child = activex_window; ::IsWindow(first_child); first_child = ::GetWindow(first_child, GW_CHILD)) { child_window = first_child; GetClassName(child_window, class_name, arraysize(class_name)); if (!_wcsicmp(class_name, L"Chrome_RenderWidgetHostHWND")) { renderer_window = child_window; break; } } } } else { DCHECK(web_browser2_); base::win::ScopedComPtr doc; HRESULT hr = web_browser2_->get_Document(doc.Receive()); EXPECT_HRESULT_SUCCEEDED(hr); EXPECT_TRUE(doc); if (doc) { base::win::ScopedComPtr 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; } HWND IEEventSink::GetRendererWindowSafe() { HWND renderer_window = NULL; if (IsCFRendering()) { DCHECK(chrome_frame_); base::win::ScopedComPtr ole_window; ole_window.QueryFrom(chrome_frame_); if (ole_window) { HWND activex_window = NULL; ole_window->GetWindow(&activex_window); // chrome tab window is the first (and the only) child of activex for (HWND first_child = activex_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)); if (_wcsicmp(class_name, L"Chrome_RenderWidgetHostHWND") != 0) renderer_window = NULL; } } else { DCHECK(web_browser2_); base::win::ScopedComPtr doc; web_browser2_->get_Document(doc.Receive()); if (doc) { base::win::ScopedComPtr ole_window; ole_window.QueryFrom(doc); if (ole_window) { ole_window->GetWindow(&renderer_window); } } } if (!::IsWindow(renderer_window)) renderer_window = NULL; return renderer_window; } HRESULT IEEventSink::LaunchIEAndNavigate(const std::wstring& navigate_url, IEEventListener* listener) { listener_ = listener; HRESULT hr = LaunchIEAsComServer(web_browser2_.Receive()); if (SUCCEEDED(hr)) { web_browser2_->put_Visible(VARIANT_TRUE); hr = Attach(web_browser2_); if (SUCCEEDED(hr)) { hr = Navigate(navigate_url); if (FAILED(hr)) { LOG(ERROR) << "Failed to navigate IE to " << navigate_url << ", hr = 0x" << std::hex << hr; } } else { LOG(ERROR) << "Failed to attach to web browser event sink for " << navigate_url << ", hr = 0x" << std::hex << hr; } } else { LOG(ERROR) << "Failed to Launch IE for " << navigate_url << ", hr = 0x" << std::hex << hr; } return hr; } HRESULT IEEventSink::Navigate(const std::wstring& navigate_url) { VARIANT empty = base::win::ScopedVariant::kEmptyVariant; base::win::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() { base::win::ScopedVariant refresh_level(REFRESH_COMPLETELY); web_browser2_->Refresh2(refresh_level.AsInput()); } // private methods void IEEventSink::ConnectToChromeFrame() { DCHECK(web_browser2_); if (chrome_frame_.get()) return; base::win::ScopedComPtr shell_browser; DoQueryService(SID_STopLevelBrowser, web_browser2_, shell_browser.Receive()); if (shell_browser) { base::win::ScopedComPtr shell_view; shell_browser->QueryActiveShellView(shell_view.Receive()); if (shell_view) { shell_view->GetItemObject(SVGIO_BACKGROUND, __uuidof(IChromeFrame), reinterpret_cast(chrome_frame_.Receive())); } if (chrome_frame_) { base::win::ScopedVariant onmessage(onmessage_.ToDispatch()); base::win::ScopedVariant onloaderror(onloaderror_.ToDispatch()); base::win::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. base::win::ScopedComPtr chrome_frame(chrome_frame_); chrome_frame_.Release(); base::win::ScopedVariant dummy(static_cast(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(&hwnd)); EXPECT_TRUE(::IsWindow(hwnd)); if (::IsWindow(hwnd)) ::GetWindowThreadProcessId(hwnd, &ie_process_id_); EXPECT_NE(static_cast(0), ie_process_id_); } // Event callbacks STDMETHODIMP_(void) IEEventSink::OnDownloadBegin() { if (listener_) listener_->OnDownloadBegin(); } STDMETHODIMP_(void) IEEventSink::OnNewWindow2(IDispatch** dispatch, VARIANT_BOOL* s) { VLOG(1) << __FUNCTION__; EXPECT_TRUE(dispatch); if (!dispatch) return; if (listener_) listener_->OnNewWindow2(dispatch, s); // 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) { base::win::ScopedComPtr 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_) listener_->OnNewBrowserWindow(*dispatch, ScopedBstr()); } STDMETHODIMP_(void) IEEventSink::OnNavigateError(IDispatch* dispatch, VARIANT* url, VARIANT* frame_name, VARIANT* status_code, VARIANT* cancel) { VLOG(1) << __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) { VLOG(1) << __FUNCTION__ << " " << base::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) { VLOG(1) << __FUNCTION__; ConnectToChromeFrame(); if (listener_) listener_->OnNavigateComplete2(dispatch, url); } STDMETHODIMP_(void) IEEventSink::OnDocumentComplete( IDispatch* dispatch, VARIANT* url) { VLOG(1) << __FUNCTION__; EXPECT_TRUE(url); if (!url) return; if (listener_) listener_->OnDocumentComplete(dispatch, url); } STDMETHODIMP_(void) IEEventSink::OnFileDownload( VARIANT_BOOL active_doc, VARIANT_BOOL* cancel) { VLOG(1) << __FUNCTION__ << " " << base::StringPrintf(" 0x%08X ad=%i", this, active_doc); if (listener_) { listener_->OnFileDownload(active_doc, cancel); } else { *cancel = VARIANT_TRUE; } } STDMETHODIMP_(void) IEEventSink::OnNewWindow3( IDispatch** dispatch, VARIANT_BOOL* cancel, DWORD flags, BSTR url_context, BSTR url) { VLOG(1) << __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) { base::win::ScopedComPtr 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_) listener_->OnNewBrowserWindow(*dispatch, url); } STDMETHODIMP_(void) IEEventSink::OnQuit() { VLOG(1) << __FUNCTION__; did_receive_on_quit_ = true; DispEventUnadvise(web_browser2_); CoDisconnectObject(this, 0); if (listener_) listener_->OnQuit(); } STDMETHODIMP IEEventSink::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD flags, DISPPARAMS* params, VARIANT* result, EXCEPINFO* except_info, UINT* arg_error) { VLOG(1) << __FUNCTION__ << L" event: " << g_dispIdToName.Get().Lookup(dispid); return DispEventsImpl::Invoke(dispid, riid, lcid, flags, params, result, except_info, arg_error); } HRESULT IEEventSink::OnLoad(const VARIANT* param) { VLOG(1) << __FUNCTION__ << " " << param->bstrVal; base::win::ScopedVariant stack_object(*param); if (chrome_frame_) { if (listener_) listener_->OnLoad(param->bstrVal); } else { LOG(WARNING) << "Invalid chrome frame pointer"; } return S_OK; } HRESULT IEEventSink::OnLoadError(const VARIANT* param) { VLOG(1) << __FUNCTION__ << " " << param->bstrVal; if (chrome_frame_) { if (listener_) listener_->OnLoadError(param->bstrVal); } else { LOG(WARNING) << "Invalid chrome frame pointer"; } return S_OK; } HRESULT IEEventSink::OnMessage(const VARIANT* param) { VLOG(1) << __FUNCTION__ << " " << param; if (!chrome_frame_.get()) { LOG(WARNING) << "Invalid chrome frame pointer"; return S_OK; } base::win::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