diff options
Diffstat (limited to 'ceee/ie/plugin/bho/mediumtest_browser_event.cc')
-rw-r--r-- | ceee/ie/plugin/bho/mediumtest_browser_event.cc | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/ceee/ie/plugin/bho/mediumtest_browser_event.cc b/ceee/ie/plugin/bho/mediumtest_browser_event.cc new file mode 100644 index 0000000..3288e9d --- /dev/null +++ b/ceee/ie/plugin/bho/mediumtest_browser_event.cc @@ -0,0 +1,495 @@ +// 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. +// +// A test that hosts and excercises the webbrowser control to test +// its event firing behavior. +#include <atlcrack.h> +#include <atlsync.h> +#include <atlwin.h> +#include <set> + +#include "base/logging.h" +#include "base/file_path.h" +#include "base/path_service.h" +#include "base/base_paths_win.h" +#include "ceee/ie/testing/mediumtest_ie_common.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "ceee/common/com_utils.h" +#include "ceee/common/initializing_coclass.h" +#include "ceee/testing/utils/mock_com.h" +#include "ceee/testing/utils/test_utils.h" +#include "ceee/testing/utils/instance_count_mixin.h" + + +namespace { + +using testing::InstanceCountMixin; +using testing::InstanceCountMixinBase; + +using testing::BrowserEventSinkBase; +using testing::GetTestUrl; +using testing::GetTempPath; +using testing::ShellBrowserTestImpl; + +// {AFF1D082-6B03-4b29-9521-E52240F6333B} +const GUID IID_Dummy = + { 0xaff1d082, 0x6b03, 0x4b29, + { 0x95, 0x21, 0xe5, 0x22, 0x40, 0xf6, 0x33, 0x3b } }; + +class TestBrowserEventSink; + +class TestFrameEventHandler + : public CComObjectRootEx<CComSingleThreadModel>, + public InitializingCoClass<TestFrameEventHandler>, + public InstanceCountMixin<TestFrameEventHandler>, + public IPropertyNotifySink, + public IAdviseSink { + public: + BEGIN_COM_MAP(TestFrameEventHandler) + COM_INTERFACE_ENTRY(IPropertyNotifySink) + COM_INTERFACE_ENTRY(IAdviseSink) + END_COM_MAP() + + DECLARE_PROTECT_FINAL_CONSTRUCT(); + + TestFrameEventHandler() : event_sink_(NULL), + document_property_notify_sink_cookie_(-1), + advise_sink_cookie_(-1) { + } + + virtual ~TestFrameEventHandler() { + ATLTRACE("~TestFrameEventHandler[%ws]\n", url_ ? url_ : L""); + } + + void DetachFromSink(); + + STDMETHOD(OnChanged)(DISPID changed_property); + STDMETHOD(OnRequestEdit)(DISPID changed_property) { + ATLTRACE("OnRequestEdit(%d)\n", changed_property); + return S_OK; + } + + // IAdviseSink + STDMETHOD_(void, OnDataChange)(FORMATETC *pFormatetc, STGMEDIUM *pStgmed) { + ATLTRACE("%s\n", __FUNCTION__); + } + STDMETHOD_(void, OnViewChange)(DWORD dwAspect, LONG lindex) { + ATLTRACE("%s\n", __FUNCTION__); + } + STDMETHOD_(void, OnRename)(IMoniker *pmk) { + ATLTRACE("%s\n", __FUNCTION__); + } + STDMETHOD_(void, OnSave)() { + ATLTRACE("%s\n", __FUNCTION__); + } + STDMETHOD_(void, OnClose)(); + + virtual void GetDescription(std::string* description) const { + description->clear(); + description->append("TestFrameEventHandler"); + // TODO(siggi@chromium.org): append URL + } + + HRESULT Initialize(TestBrowserEventSink* event_sink, + IWebBrowser2* browser, + BSTR url); + void FinalRelease(); + + void set_url(const wchar_t* url) { url_ = url; } + const wchar_t* url() const { return url_ ? url_ : L""; } + + template <class Interface> + HRESULT GetBrowser(Interface** browser) { + return browser_.QueryInterface(browser); + } + + private: + CComBSTR url_; + CComDispatchDriver document_; + CComPtr<IWebBrowser2> browser_; + TestBrowserEventSink* event_sink_; + + DWORD advise_sink_cookie_; + DWORD document_property_notify_sink_cookie_; +}; + +class TestBrowserEventSink + : public BrowserEventSinkBase, + public InitializingCoClass<TestBrowserEventSink> { + public: + // Disambiguate. + using InitializingCoClass<TestBrowserEventSink>::CreateInitialized; + + HRESULT Initialize(TestBrowserEventSink** self, IWebBrowser2* browser) { + *self = this; + return BrowserEventSinkBase::Initialize(browser); + } + + // We have seen cases where we get destroyed while the ATL::CAxHostWindow + // may still hold references to frame handlers. + void FinalRelease() { + FrameHandlerMap::iterator it(frame_handlers_.begin()); + FrameHandlerMap::iterator end(frame_handlers_.end()); + + // Since they will detach from us as we clear them, we must not do it + // while we loop. + std::vector<TestFrameEventHandler*> to_be_detached; + for (; it != end; ++it) { + to_be_detached.push_back(it->second); + } + for (size_t index = 0; index < to_be_detached.size(); ++index) { + to_be_detached[index]->DetachFromSink(); + } + ASSERT_EQ(0, frame_handlers_.size()); + } + + virtual void GetDescription(std::string* description) const { + description->clear(); + description->append("TestBrowserEventSink"); + } + + void AttachHandler(IWebBrowser2* browser, TestFrameEventHandler* handler) { + ASSERT_TRUE(browser != NULL && handler != NULL); + CComPtr<IUnknown> browser_unk; + ASSERT_HRESULT_SUCCEEDED(browser->QueryInterface(&browser_unk)); + + // We shouldn't already have one. + ASSERT_TRUE(NULL == FindHandlerForBrowser(browser)); + + frame_handlers_.insert(std::make_pair(browser_unk, handler)); + } + + void DetachHandler(IWebBrowser2* browser, TestFrameEventHandler* handler) { + ASSERT_TRUE(browser != NULL && handler != NULL); + + // It should already be registered. + ASSERT_TRUE(NULL != FindHandlerForBrowser(browser)); + + CComPtr<IUnknown> browser_unk; + ASSERT_HRESULT_SUCCEEDED(browser->QueryInterface(&browser_unk)); + ASSERT_EQ(1, frame_handlers_.erase(browser_unk)); + } + + TestFrameEventHandler* FindHandlerForBrowser(IDispatch* browser) { + CComPtr<IUnknown> browser_unk; + EXPECT_HRESULT_SUCCEEDED(browser->QueryInterface(&browser_unk)); + + FrameHandlerMap::iterator it(frame_handlers_.find(browser_unk)); + if (it == frame_handlers_.end()) + return NULL; + + return it->second; + } + + TestFrameEventHandler* FindHandlerForUrl(const std::wstring& url) { + FrameHandlerMap::iterator it(frame_handlers_.begin()); + FrameHandlerMap::iterator end(frame_handlers_.end()); + + for (; it != end; ++it) { + CComQIPtr<IWebBrowser2> browser(it->first); + EXPECT_TRUE(browser != NULL); + CComBSTR location_url; + EXPECT_HRESULT_SUCCEEDED(browser->get_LocationURL(&location_url)); + + if (0 == ::UrlCompare(url.c_str(), location_url, TRUE)) + return it->second; + } + + return NULL; + } + + // Override. + STDMETHOD_(void, OnNavigateComplete)(IDispatch* browser_disp, + VARIANT* url_var) { + CComBSTR url; + if (V_VT(url_var) == VT_BSTR) + url = V_BSTR(url_var); + + TestFrameEventHandler* frame_handler = FindHandlerForBrowser(browser_disp); + if (!frame_handler) { + CComQIPtr<IWebBrowser2> browser(browser_disp); + ASSERT_TRUE(browser != NULL); + + CComPtr<IUnknown> frame_handler_keeper; + ASSERT_HRESULT_SUCCEEDED( + TestFrameEventHandler::CreateInitialized(this, + browser, + url, + &frame_handler_keeper)); + } else { + ATLTRACE("FrameHandler[%ws] -> %ws\n", frame_handler->url(), url); + frame_handler->set_url(url); + } + } + + private: + typedef std::map<IUnknown*, TestFrameEventHandler*> FrameHandlerMap; + // Keeps a map from a frame or top-level browser's identifying + // IUnknown to the frame event handler instance attached. + FrameHandlerMap frame_handlers_; +}; + + +STDMETHODIMP TestFrameEventHandler::OnChanged(DISPID changed_property) { + ATLTRACE("OnChanged(%d)\n", changed_property); + + if (changed_property == DISPID_READYSTATE) { + CComVariant ready_state; + CComDispatchDriver document(document_); + EXPECT_TRUE(document != NULL); + EXPECT_HRESULT_SUCCEEDED(document.GetProperty(DISPID_READYSTATE, + &ready_state)); + EXPECT_EQ(V_VT(&ready_state), VT_I4); + ATLTRACE("READYSTATE Frame[%ws]: %d\n", url_, ready_state.lVal); + + TestBrowserEventSink::add_state(static_cast<READYSTATE>(ready_state.lVal)); + } + + return S_OK; +} + +HRESULT TestFrameEventHandler::Initialize(TestBrowserEventSink* event_sink, + IWebBrowser2* browser, + BSTR url) { + EXPECT_HRESULT_SUCCEEDED(browser->get_Document(&document_)); + + CComQIPtr<IHTMLDocument2> html_document2(document_); + if (html_document2 != NULL) { + event_sink_ = event_sink; + browser_ = browser; + url_ = url; + EXPECT_HRESULT_SUCCEEDED(AtlAdvise(document_, + GetUnknown(), + IID_IPropertyNotifySink, + &document_property_notify_sink_cookie_)); + + ATLTRACE("TestFrameEventHandler::Initialize[%ws]\n", url_ ? url_ : L""); + + CComQIPtr<IOleObject> document_ole_object(document_); + EXPECT_TRUE(document_ole_object != NULL); + EXPECT_HRESULT_SUCCEEDED( + document_ole_object->Advise(this, &advise_sink_cookie_)); + + event_sink_->AttachHandler(browser_, this); + } else { + // This happens when we're navigated to e.g. a PDF doc or a folder. + } + + return S_OK; +} + + +void TestFrameEventHandler::DetachFromSink() { + ASSERT_TRUE(event_sink_ != NULL); + event_sink_->DetachHandler(browser_, this); + event_sink_ = NULL; +} + +void TestFrameEventHandler::FinalRelease() { + if (event_sink_ && browser_) + event_sink_->DetachHandler(browser_, this); + browser_.Release(); + document_.Release(); +} + +STDMETHODIMP_(void) TestFrameEventHandler::OnClose() { + EXPECT_HRESULT_SUCCEEDED(AtlUnadvise(document_, + IID_IPropertyNotifySink, + document_property_notify_sink_cookie_)); + + CComQIPtr<IOleObject> document_ole_object(document_); + EXPECT_TRUE(document_ole_object != NULL); + EXPECT_HRESULT_SUCCEEDED( + document_ole_object->Unadvise(advise_sink_cookie_)); +} + +class BrowserEventTest: public ShellBrowserTestImpl<TestBrowserEventSink> { +}; + +const wchar_t* kSimplePage = L"simple_page.html"; + +TEST_F(BrowserEventTest, RefreshTopLevelBrowserRetainsFrameHandler) { + EXPECT_TRUE(NavigateBrowser(GetTestUrl(kSimplePage))); + + // We should have only one frame at this point. + EXPECT_EQ(1, TestFrameEventHandler::instance_count()); + + // Refreshing the top-level browser retains it. + EXPECT_HRESULT_SUCCEEDED(browser_->Refresh()); + EXPECT_TRUE(WaitForReadystateLoading()); + EXPECT_TRUE(WaitForReadystateComplete()); + + // Still there after refresh. + EXPECT_EQ(1, TestFrameEventHandler::instance_count()); +} + +const wchar_t* kTwoFramesPage = L"two_frames.html"; +const wchar_t* kFrameOne = L"frame_one.html"; +const wchar_t* kFrameTwo = L"frame_two.html"; + +TEST_F(BrowserEventTest, NavigateToFrames) { + EXPECT_TRUE(NavigateBrowser(GetTestUrl(kTwoFramesPage))); + + // We should have three frame handlers at this point. + EXPECT_EQ(3, TestFrameEventHandler::instance_count()); + + // We should have a handler for each of these. + TestFrameEventHandler* two_frames = + event_sink()->FindHandlerForUrl(GetTestUrl(kTwoFramesPage)); + TestFrameEventHandler* frame_one = + event_sink()->FindHandlerForUrl(GetTestUrl(kFrameOne)); + TestFrameEventHandler* frame_two = + event_sink()->FindHandlerForUrl(GetTestUrl(kFrameTwo)); + ASSERT_TRUE(two_frames != NULL); + ASSERT_TRUE(frame_one != NULL); + ASSERT_TRUE(frame_two != NULL); + + // Noteworthy fact: the top level browser implements an + // IPropertyNotifySink connection point, but the sub-browsers + // for the frames do not. + { + CComQIPtr<IConnectionPointContainer> cpc; + ASSERT_HRESULT_SUCCEEDED(two_frames->GetBrowser(&cpc)); + ASSERT_TRUE(cpc != NULL); + CComPtr<IConnectionPoint> cp; + EXPECT_HRESULT_SUCCEEDED( + cpc->FindConnectionPoint(IID_IPropertyNotifySink, &cp)); + } + + { + CComQIPtr<IConnectionPointContainer> cpc; + ASSERT_HRESULT_SUCCEEDED(frame_one->GetBrowser(&cpc)); + ASSERT_TRUE(cpc != NULL); + CComPtr<IConnectionPoint> cp; + EXPECT_HRESULT_FAILED( + cpc->FindConnectionPoint(IID_IPropertyNotifySink, &cp)); + } + + { + CComQIPtr<IConnectionPointContainer> cpc; + ASSERT_HRESULT_SUCCEEDED(frame_two->GetBrowser(&cpc)); + ASSERT_TRUE(cpc != NULL); + CComPtr<IConnectionPoint> cp; + EXPECT_HRESULT_FAILED( + cpc->FindConnectionPoint(IID_IPropertyNotifySink, &cp)); + } + + // Test sub-frame document IPropertyNotifySink. + { + CComPtr<IWebBrowser2> browser; + ASSERT_HRESULT_SUCCEEDED(frame_two->GetBrowser(&browser)); + + CComPtr<IDispatch> document_disp; + ASSERT_HRESULT_SUCCEEDED(browser->get_Document(&document_disp)); + + CComPtr<IConnectionPointContainer> cpc; + ASSERT_HRESULT_SUCCEEDED(document_disp->QueryInterface(&cpc)); + CComPtr<IConnectionPoint> cp; + ASSERT_HRESULT_SUCCEEDED( + cpc->FindConnectionPoint(IID_IPropertyNotifySink, &cp)); + } +} + +TEST_F(BrowserEventTest, ReNavigateToSamePageRetainsEventHandler) { + const std::wstring url(GetTestUrl(kSimplePage)); + EXPECT_TRUE(NavigateBrowser(url)); + + // We should have a frame handler attached now. + EXPECT_EQ(1, TestFrameEventHandler::instance_count()); + + // Retrieve it and make sure it doesn't die. + TestFrameEventHandler* handler_before = + event_sink()->FindHandlerForUrl(url); + + ASSERT_TRUE(handler_before != NULL); + CComPtr<IUnknown> handler_before_keeper(handler_before->GetUnknown()); + + // Re-navigate the browser to the same page. + EXPECT_TRUE(NavigateBrowser(GetTestUrl(kSimplePage))); + // Note: on re-navigation we don't see the top-level + // browser's readystate drop, I guess that only happens + // on transitions between content types. E.g. when a + // navigation requires the shell browser to instantiate + // a new type of document, such as going from a + // HTML doc to a PDF doc or the like. + EXPECT_FALSE(WaitForReadystateLoading()); + EXPECT_TRUE(WaitForReadystateComplete()); + + // We should only have the one frame handler in existence now. + EXPECT_EQ(1, TestFrameEventHandler::instance_count()); + + // Retrieve the new one. + TestFrameEventHandler* handler_after = + event_sink()->FindHandlerForUrl(GetTestUrl(kSimplePage)); + + ASSERT_EQ(handler_before, handler_after); + + // Release the old one, it should still stay around + handler_before_keeper.Release(); + EXPECT_EQ(1, TestFrameEventHandler::instance_count()); +} + +TEST_F(BrowserEventTest, NavigateToDifferentPageRetainsEventHandler) { + const std::wstring first_url(GetTestUrl(kSimplePage)); + EXPECT_TRUE(NavigateBrowser(first_url)); + + // We should have a frame handler attached now. + EXPECT_EQ(1, TestFrameEventHandler::instance_count()); + + // Retrieve it and make sure it doesn't die. + TestFrameEventHandler* handler_before = + event_sink()->FindHandlerForUrl(first_url); + + ASSERT_TRUE(handler_before != NULL); + CComPtr<IUnknown> handler_before_keeper(handler_before->GetUnknown()); + + // Navigate the browser to another page. + const std::wstring second_url(GetTestUrl(kTwoFramesPage)); + EXPECT_TRUE(NavigateBrowser(second_url)); + EXPECT_FALSE(WaitForReadystateLoading()); + EXPECT_TRUE(WaitForReadystateComplete()); + + // We should have the three frame handlers in existence now. + EXPECT_EQ(3, TestFrameEventHandler::instance_count()); + + // Retrieve the new one for the top-level browser. + TestFrameEventHandler* handler_after = + event_sink()->FindHandlerForUrl(second_url); + + ASSERT_EQ(handler_before, handler_after); + + // Release the old one, it should still stay around + handler_before_keeper.Release(); + EXPECT_EQ(3, TestFrameEventHandler::instance_count()); +} + +TEST_F(BrowserEventTest, RefreshFrameBrowserRetainsHandler) { + EXPECT_TRUE(NavigateBrowser(GetTestUrl(kTwoFramesPage))); + + // We should have three frame handlers at this point. + EXPECT_EQ(3, TestFrameEventHandler::instance_count()); + + // Get one of the frames. + TestFrameEventHandler* frame_two = + event_sink()->FindHandlerForUrl(GetTestUrl(kFrameTwo)); + ASSERT_TRUE(frame_two != NULL); + + // Now refresh a sub-browser instance, let it settle, + // observe its frame event handler is still around and + // has signalled a readystate transition. + CComPtr<IWebBrowser2> browser; + ASSERT_HRESULT_SUCCEEDED(frame_two->GetBrowser(&browser)); + ASSERT_HRESULT_SUCCEEDED(browser->Refresh()); + + EXPECT_TRUE(WaitForReadystateLoading()); + EXPECT_TRUE(WaitForReadystateComplete()); + + // We should have all three frame handlers at this point. + EXPECT_EQ(3, TestFrameEventHandler::instance_count()); + frame_two = event_sink()->FindHandlerForUrl(GetTestUrl(kFrameTwo)); + EXPECT_TRUE(frame_two != NULL); +} + +} // namespace |