// 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 #include "base/file_path.h" #include "base/waitable_event.h" #include "chrome_frame/cfproxy_private.h" #include "chrome/test/automation/automation_messages.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock_mutant.h" using testing::_; using testing::DoAll; using testing::NotNull; using testing::Return; using testing::StrictMock; using testing::InvokeWithoutArgs; using testing::WithoutArgs; using testing::CreateFunctor; using testing::StrEq; using testing::Eq; // There is not much to test here since CFProxy is pretty dumb. struct MockFactory : public ChromeProxyFactory { MOCK_METHOD0(CreateProxy, ChromeProxy*()); }; struct MockChromeProxyDelegate : public ChromeProxyDelegate { MOCK_METHOD1(Connected, void(ChromeProxy* proxy)); MOCK_METHOD2(PeerLost, void(ChromeProxy*, enum DisconnectReason reason)); MOCK_METHOD0(Disconnected, void()); MOCK_METHOD0(tab_handle, int()); MOCK_METHOD4(Completed_CreateTab, void(bool success, HWND chrome_wnd, HWND tab_window, int tab_handle)); MOCK_METHOD4(Completed_ConnectToTab, void(bool success, HWND chrome_window, HWND tab_window, int tab_handle)); MOCK_METHOD2(Completed_Navigate, void(bool success, enum AutomationMsg_NavigationResponseValues res)); MOCK_METHOD3(Completed_InstallExtension, void(bool success, enum AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx)); MOCK_METHOD3(Completed_LoadExpandedExtension, void(bool success, enum AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx)); MOCK_METHOD2(Completed_GetEnabledExtensions, void(bool success, const std::vector* v)); // Network requests from Chrome. MOCK_METHOD2(Network_Start, void(int request_id, const IPC::AutomationURLRequest& request_info)); MOCK_METHOD2(Network_Read, void(int request_id, int bytes_to_read)); MOCK_METHOD2(Network_End, void(int request_id, const URLRequestStatus& s)); MOCK_METHOD1(Network_DownloadInHost, void(int request_id)); MOCK_METHOD2(GetCookies, void(const GURL& url, int cookie_id)); MOCK_METHOD2(SetCookie, void(const GURL& url, const std::string& cookie)); // Navigation progress notifications. MOCK_METHOD2(NavigationStateChanged, void(int flags, const IPC::NavigationInfo& nav_info)); MOCK_METHOD1(UpdateTargetUrl, void(const std::wstring& url)); MOCK_METHOD2(NavigationFailed, void(int error_code, const GURL& gurl)); MOCK_METHOD1(DidNavigate, void(const IPC::NavigationInfo& navigation_info)); MOCK_METHOD1(TabLoaded, void(const GURL& url)); // MOCK_METHOD3(OpenURL, void(const GURL& url_to_open, const GURL& referrer, int open_disposition)); MOCK_METHOD1(GoToHistoryOffset, void(int offset)); MOCK_METHOD3(MessageToHost, void(const std::string& message, const std::string& origin, const std::string& target)); // Misc. UI. MOCK_METHOD1(HandleAccelerator, void(const MSG& accel_message)); MOCK_METHOD3(HandleContextMenu, void(HANDLE menu_handle, int align_flags, const IPC::ContextMenuParams& params)); MOCK_METHOD1(TabbedOut, void(bool reverse)); // MOCK_METHOD0(TabClosed, void()); MOCK_METHOD1(AttachTab, void(const IPC::AttachExternalTabParams& attach_params)); }; struct MockSender : public IPC::Message::Sender { MOCK_METHOD1(Send, bool(IPC::Message* m)); }; struct MockCFProxyTraits : public CFProxyTraits { MOCK_METHOD2(DoCreateChannel, IPC::Message::Sender*(const std::string& id, IPC::Channel::Listener* l)); MOCK_METHOD1(CloseChannel, void(IPC::Message::Sender* s)); MOCK_METHOD1(LaunchApp, bool(const std::wstring& cmd_line)); // Forward the CreateChannel to DoCreateChannel, but save the ipc_thread // and the listener (i.e. proxy implementation of Channel::Listener) virtual IPC::Message::Sender* CreateChannel(const std::string& id, IPC::Channel::Listener* l) { ipc_loop = MessageLoop::current(); listener = l; return this->DoCreateChannel(id, l); } // Simulate some activity in the IPC thread. // You may find API_FIRE_XXXX macros (see below) handy instead. void FireConnect(base::TimeDelta t) { ASSERT_TRUE(ipc_loop != NULL); ipc_loop->PostDelayedTask(FROM_HERE, NewRunnableMethod(listener, &IPC::Channel::Listener::OnChannelConnected, 0), t.InMilliseconds()); } void FireError(base::TimeDelta t) { ASSERT_TRUE(ipc_loop != NULL); ipc_loop->PostDelayedTask(FROM_HERE, NewRunnableMethod(listener, &IPC::Channel::Listener::OnChannelError), t.InMilliseconds()); } void FireMessage(const IPC::Message& m, base::TimeDelta t) { ASSERT_TRUE(ipc_loop != NULL); ipc_loop->PostDelayedTask(FROM_HERE, NewRunnableMethod(listener, &IPC::Channel::Listener::OnMessageReceived, m), t.InMilliseconds()); } MockCFProxyTraits() : ipc_loop(NULL) {} MockSender sender; private: MessageLoop* ipc_loop; IPC::Channel::Listener* listener; }; // Handy macros when we want so similate something on the IPC thread. #define API_FIRE_CONNECT(api, t) InvokeWithoutArgs(CreateFunctor(&api, \ &MockCFProxyTraits::FireConnect, t)) #define API_FIRE_ERROR(api, t) InvokeWithoutArgs(CreateFunctor(&api, \ &MockCFProxyTraits::FireError, t)) #define API_FIRE_MESSAGE(api, t) InvokeWithoutArgs(CreateFunctor(&api, \ &MockCFProxyTraits::FireMessage, t)) DISABLE_RUNNABLE_METHOD_REFCOUNT(IPC::Channel::Listener); TEST(ChromeProxy, DelegateAddRemove) { StrictMock api; StrictMock delegate; StrictMock factory; // to be destroyed before other mocks CFProxy* proxy = new CFProxy(&api); EXPECT_CALL(factory, CreateProxy()).WillOnce(Return(proxy)); EXPECT_CALL(api, DoCreateChannel(_, proxy)).WillOnce(Return(&api.sender)); EXPECT_CALL(api, LaunchApp(_)).WillOnce(Return(true)); EXPECT_CALL(api, CloseChannel(&api.sender)); EXPECT_CALL(delegate, tab_handle()).WillRepeatedly(Return(0)); EXPECT_CALL(delegate, Disconnected()); ProxyParams params; params.profile = "Adam N. Epilinter"; params.timeout = base::TimeDelta::FromSeconds(4); factory.GetProxy(&delegate, params); factory.ReleaseProxy(&delegate, params.profile); } // Not very useful test. Just for illustration. :) TEST(ChromeProxy, SharedProxy) { base::WaitableEvent done1(false, false); base::WaitableEvent done2(false, false); StrictMock api; StrictMock delegate1; StrictMock delegate2; StrictMock factory; CFProxy* proxy = new CFProxy(&api); EXPECT_CALL(factory, CreateProxy()).WillOnce(Return(proxy)); EXPECT_CALL(api, DoCreateChannel(_, proxy)).WillOnce(Return(&api.sender)); EXPECT_CALL(api, LaunchApp(_)).WillOnce(DoAll( API_FIRE_CONNECT(api, base::TimeDelta::FromMilliseconds(150)), Return(true))); EXPECT_CALL(api, CloseChannel(&api.sender)); EXPECT_CALL(delegate1, tab_handle()).WillRepeatedly(Return(0)); EXPECT_CALL(delegate2, tab_handle()).WillRepeatedly(Return(0)); EXPECT_CALL(delegate1, Connected(proxy)) .WillOnce(InvokeWithoutArgs(&done1, &base::WaitableEvent::Signal)); EXPECT_CALL(delegate2, Connected(proxy)) .WillOnce(InvokeWithoutArgs(&done2, &base::WaitableEvent::Signal)); ProxyParams params; params.profile = "Adam N. Epilinter"; params.timeout = base::TimeDelta::FromSeconds(4); factory.GetProxy(&delegate1, params); params.timeout = base::TimeDelta::FromSeconds(2); factory.GetProxy(&delegate2, params); EXPECT_TRUE(done1.TimedWait(base::TimeDelta::FromSeconds(1))); EXPECT_TRUE(done2.TimedWait(base::TimeDelta::FromSeconds(1))); EXPECT_CALL(delegate2, Disconnected()); EXPECT_CALL(delegate1, Disconnected()); factory.ReleaseProxy(&delegate2, params.profile); factory.ReleaseProxy(&delegate1, params.profile); } TEST(ChromeProxy, LaunchTimeout) { base::WaitableEvent done(true, false); StrictMock api; StrictMock delegate; StrictMock factory; CFProxy* proxy = new CFProxy(&api); EXPECT_CALL(delegate, tab_handle()).WillRepeatedly(Return(0)); EXPECT_CALL(factory, CreateProxy()).WillOnce(Return(proxy)); EXPECT_CALL(api, DoCreateChannel(_, proxy)).WillOnce(Return(&api.sender)); EXPECT_CALL(api, LaunchApp(_)).WillOnce(Return(true)); EXPECT_CALL(api, CloseChannel(&api.sender)); EXPECT_CALL(delegate, PeerLost(_, ChromeProxyDelegate::CHROME_EXE_LAUNCH_TIMEOUT)) .WillOnce(InvokeWithoutArgs(&done, &base::WaitableEvent::Signal)); ProxyParams params; params.profile = "Adam N. Epilinter"; params.timeout = base::TimeDelta::FromMilliseconds(300); factory.GetProxy(&delegate, params); EXPECT_TRUE(done.TimedWait(base::TimeDelta::FromSeconds(1))); EXPECT_CALL(delegate, Disconnected()); factory.ReleaseProxy(&delegate, params.profile); } TEST(ChromeProxy, LaunchChrome) { base::WaitableEvent connected(false, false); StrictMock delegate; ChromeProxyFactory factory; ProxyParams params; params.profile = "Adam N. Epilinter"; params.timeout = base::TimeDelta::FromSeconds(10); EXPECT_CALL(delegate, tab_handle()).WillRepeatedly(Return(0)); EXPECT_CALL(delegate, Connected(NotNull())) .WillOnce(InvokeWithoutArgs(&connected, &base::WaitableEvent::Signal)); factory.GetProxy(&delegate, params); EXPECT_TRUE(connected.TimedWait(base::TimeDelta::FromSeconds(15))); EXPECT_CALL(delegate, Disconnected()); factory.ReleaseProxy(&delegate, params.profile); } // Test that a channel error results in Completed_XYZ(false, ) called if // the synchronious XYZ message has been sent. TEST(ChromeProxy, ChannelError) { base::WaitableEvent connected(false, false); StrictMock api; StrictMock delegate; StrictMock factory; CFProxy* proxy = new CFProxy(&api); ProxyParams params; params.profile = "Adam N. Epilinter"; params.timeout = base::TimeDelta::FromMilliseconds(300); testing::InSequence s; EXPECT_CALL(factory, CreateProxy()).WillOnce(Return(proxy)); EXPECT_CALL(api, DoCreateChannel(_, proxy)).WillOnce(Return(&api.sender)); EXPECT_CALL(api, LaunchApp(_)).WillOnce(DoAll( API_FIRE_CONNECT(api, base::TimeDelta::FromMilliseconds(10)), Return(true))); EXPECT_CALL(delegate, Connected(proxy)) .WillOnce(DoAll( InvokeWithoutArgs(CreateFunctor(proxy, &ChromeProxy::ConnectTab, &delegate, HWND(6), 512)), InvokeWithoutArgs(&connected, &base::WaitableEvent::Signal))); EXPECT_CALL(api.sender, Send(_)); EXPECT_CALL(delegate, Completed_ConnectToTab(false, _, _, _)); EXPECT_CALL(api, CloseChannel(&api.sender)); EXPECT_CALL(delegate, PeerLost(_, ChromeProxyDelegate::CHANNEL_ERROR)); factory.GetProxy(&delegate, params); EXPECT_TRUE(connected.TimedWait(base::TimeDelta::FromSeconds(15))); // Simulate a channel error. api.FireError(base::TimeDelta::FromMilliseconds(0)); // Expectations when the Proxy is destroyed. EXPECT_CALL(delegate, tab_handle()).WillOnce(Return(0)); EXPECT_CALL(delegate, Disconnected()); factory.ReleaseProxy(&delegate, params.profile); } /////////////////////////////////////////////////////////////////////////////// namespace { template inline IPC::Message* CreateReply(M* m, const A& a) { IPC::Message* r = IPC::SyncMessage::GenerateReply(m); if (r) { M::WriteReplyParams(r, a); } return r; } template inline IPC::Message* CreateReply(M* m, const A& a, const B& b) { IPC::Message* r = IPC::SyncMessage::GenerateReply(m); if (r) { M::WriteReplyParams(r, a, b); } return r; } template inline IPC::Message* CreateReply(M* m, const A& a, const B& b, const C& c) { IPC::Message* r = IPC::SyncMessage::GenerateReply(m); if (r) { M::WriteReplyParams(r, a, b, c); } return r; } } // namespace DISABLE_RUNNABLE_METHOD_REFCOUNT(SyncMsgSender); TEST(SyncMsgSender, Deserialize) { // Note the ipc thread is not actually needed, but we try to be close // to real-world conditions - that SyncMsgSender works from multiple threads. base::Thread ipc("ipc"); ipc.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0)); StrictMock d1; TabsMap tab2delegate; SyncMsgSender queue(&tab2delegate); // Create some sync messages and their replies. AutomationMsg_InstallExtension m1(0, FilePath(L"c:\\awesome.x"), 0); AutomationMsg_CreateExternalTab m2(0, IPC::ExternalTabSettings(), 0, 0, 0); scoped_ptr r1(CreateReply(&m1, AUTOMATION_MSG_EXTENSION_INSTALL_SUCCEEDED)); scoped_ptr r2(CreateReply(&m2, (HWND)1, (HWND)2, 6)); queue.QueueSyncMessage(&m1, &d1, NULL); queue.QueueSyncMessage(&m2, &d1, NULL); testing::InSequence s; EXPECT_CALL(d1, Completed_InstallExtension(true, AUTOMATION_MSG_EXTENSION_INSTALL_SUCCEEDED, NULL)); EXPECT_CALL(d1, Completed_CreateTab(true, (HWND)1, (HWND)2, 6)); // Execute replies in a worker thread. ipc.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(&queue, &SyncMsgSender::OnReplyReceived, r1.get())); ipc.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(&queue, &SyncMsgSender::OnReplyReceived, r2.get())); ipc.Stop(); // Expect that tab 6 has been associated with the delegate. EXPECT_EQ(&d1, tab2delegate[6]); } TEST(SyncMsgSender, OnChannelClosed) { // TODO(stoyan): implement. } MATCHER_P(EqNavigationInfo, ni, "") { return arg.navigation_type == ni.navigation_type && arg.relative_offset == ni.relative_offset && arg.navigation_index == ni.navigation_index && arg.title == ni.title && arg.url == ni.url && arg.referrer == ni.referrer && arg.security_style == ni.security_style && arg.displayed_insecure_content == ni.displayed_insecure_content && arg.ran_insecure_content == ni.ran_insecure_content; } MATCHER_P(EqMSG, msg, "") { return arg.hwnd == msg.hwnd && arg.message == msg.message && arg.wParam == msg.wParam && arg.lParam == msg.lParam && arg.time == msg.time && arg.pt.x == msg.pt.x && arg.pt.y == msg.pt.y; } MATCHER_P(EqContextMenuParam, p, "") { return arg.screen_x == p.screen_x && arg.screen_y == p.screen_y && arg.link_url == p.link_url && arg.unfiltered_link_url == p.unfiltered_link_url && arg.src_url == p.src_url && arg.page_url == p.page_url && arg.frame_url == p.frame_url; } MATCHER_P(EqURLRequest, p, "") { return arg.url == p.url && arg.method == p.method && arg.referrer == p.referrer && arg.extra_request_headers == p.extra_request_headers && // TODO(stoyan): scoped_refptr upload_data; arg.resource_type == p.resource_type; } MATCHER_P(EqAttachExternalTab, p, "") { return arg.cookie == p.cookie && arg.url == p.url && arg.dimensions == p.dimensions && arg.disposition == p.disposition && arg.user_gesture == p.user_gesture && arg.profile_name == p.profile_name; } TEST(Deserialize, DispatchTabMessage) { testing::InSequence s; StrictMock delegate; GURL url("http://destination"); GURL ref("http://referer"); // Tuple3 int flags = 2; IPC::NavigationInfo ni = {2, 3, 4, L"title", url, ref, SECURITY_STYLE_AUTHENTICATION_BROKEN, true, true}; AutomationMsg_NavigationStateChanged m1(0, 1, flags, ni); EXPECT_CALL(delegate, NavigationStateChanged(flags, EqNavigationInfo(ni))); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m1)); // Tuple2 AutomationMsg_UpdateTargetUrl m2(0, 1, L"hello"); EXPECT_CALL(delegate, UpdateTargetUrl(StrEq(L"hello"))); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m2)); // Tuple2 MSG wnd_msg = {0, WM_DESTROY, 1, 9, 0x5671, { 11, 12 }}; AutomationMsg_HandleAccelerator m3(0, 1, wnd_msg); EXPECT_CALL(delegate, HandleAccelerator(EqMSG(wnd_msg))); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m3)); // Tuple2 AutomationMsg_TabbedOut m4(0, 1, true); EXPECT_CALL(delegate, TabbedOut(true)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m4)); // Tuple4 AutomationMsg_OpenURL m5(0, 1, url, ref, 4); EXPECT_CALL(delegate, OpenURL(url, ref, 4)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m5)); // Tuple3 AutomationMsg_NavigationFailed m6(0, 1, 2, url); EXPECT_CALL(delegate, NavigationFailed(2, url)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m6)); // Tuple2 AutomationMsg_DidNavigate m7(0, 1, ni); EXPECT_CALL(delegate, DidNavigate(EqNavigationInfo(ni))); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m7)); // Tuple2 AutomationMsg_TabLoaded m8(0, 1, url); EXPECT_CALL(delegate, TabLoaded(url)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m8)); // Tuple4 std::string msg("Load oranges barrels"); std::string origin("Brothers Karamazov"); std::string target("Alexander Ivanovich"); AutomationMsg_ForwardMessageToExternalHost m9(0, 1, msg, origin, target); EXPECT_CALL(delegate, MessageToHost(msg, origin, target)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m9)); // Tuple4 IPC::ContextMenuParams ctxmenu = { 711, 512, GURL("http://link_src"), GURL("http://unfiltered_link_url"), GURL("http://src_url"), GURL("http://page_url"), GURL("http://frame_url") }; AutomationMsg_ForwardContextMenuToExternalHost m10(0, 1, HANDLE(7), 4, ctxmenu); EXPECT_CALL(delegate, HandleContextMenu(HANDLE(7), 4, EqContextMenuParam(ctxmenu))); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m10)); // Tuple3 IPC::AutomationURLRequest url_request = {"url", "post", "referer", "extra_headers", 0, 3 }; AutomationMsg_RequestStart m11(0, 1, 7, url_request); EXPECT_CALL(delegate, Network_Start(7, EqURLRequest(url_request))); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m11)); // Tuple3 AutomationMsg_RequestRead m12(0, 1, 7, 16384); EXPECT_CALL(delegate, Network_Read(7, 16384)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m12)); // Tuple3 AutomationMsg_RequestEnd m13(0, 1, 7, URLRequestStatus()); EXPECT_CALL(delegate, Network_End(7, _)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m13)); // Tuple2 AutomationMsg_DownloadRequestInHost m14(0, 1, 7); EXPECT_CALL(delegate, Network_DownloadInHost(7)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m14)); // Tuple3 AutomationMsg_SetCookieAsync m15(0, 1, url, "cake=big"); EXPECT_CALL(delegate, SetCookie(url, "cake=big")); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m15)); // Tuple2 IPC::AttachExternalTabParams ext_tab = { 0xFEDCBA0987654321i64, url, gfx::Rect(6, 9, 123, 999), 1, false, "theprofile" }; AutomationMsg_AttachExternalTab m16(0, 1, ext_tab); EXPECT_CALL(delegate, AttachTab(EqAttachExternalTab(ext_tab))); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m16)); // Tuple2 AutomationMsg_RequestGoToHistoryEntryOffset m17(0, 1, -4); EXPECT_CALL(delegate, GoToHistoryOffset(-4)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m17)); // Tuple3 AutomationMsg_GetCookiesFromHost m18(0, 1, url, 903); EXPECT_CALL(delegate, GetCookies(url, 903)); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m18)); AutomationMsg_CloseExternalTab m19(0, 1); EXPECT_CALL(delegate, TabClosed()); EXPECT_TRUE(DispatchTabMessageToDelegate(&delegate, m19)); }