1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <atlbase.h> 6 #include <atlcom.h> 7 8 #include "base/bind.h" 9 #include "base/threading/thread.h" 10 #include "base/win/scoped_comptr.h" 11 #include "base/win/scoped_handle.h" 12 #include "chrome_frame/bho.h" 13 //#include "chrome_frame/urlmon_moniker.h" 14 #include "chrome_frame/test/chrome_frame_test_utils.h" 15 #include "chrome_frame/test/test_server.h" 16 #include "chrome_frame/test/urlmon_moniker_tests.h" 17 #include "gmock/gmock.h" 18 #include "gtest/gtest.h" 19 20 #define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING 21 #include "testing/gmock_mutant.h" 22 23 using testing::_; 24 using testing::CreateFunctor; 25 using testing::Eq; 26 using testing::Invoke; 27 using testing::SetArgumentPointee; 28 using testing::StrEq; 29 using testing::Return; 30 using testing::DoAll; 31 using testing::WithArgs; 32 33 34 static const base::TimeDelta kUrlmonMonikerTimeout = 35 base::TimeDelta::FromSeconds(5); 36 37 namespace { 38 const char kTestContent[] = "<html><head>" 39 "<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />" 40 "</head><body>Test HTML content</body></html>"; 41 } // end namespace 42 43 class UrlmonMonikerTest : public testing::Test { 44 protected: 45 UrlmonMonikerTest() { 46 } 47 }; 48 49 TEST_F(UrlmonMonikerTest, MonikerPatch) { 50 EXPECT_TRUE(MonikerPatch::Initialize()); 51 EXPECT_TRUE(MonikerPatch::Initialize()); // Should be ok to call twice. 52 MonikerPatch::Uninitialize(); 53 } 54 55 // Runs an HTTP server on a worker thread that has a message loop. 56 class RunTestServer : public base::Thread { 57 public: 58 RunTestServer() 59 : base::Thread("TestServer"), 60 default_response_("/", kTestContent), 61 ready_(::CreateEvent(NULL, TRUE, FALSE, NULL)) { 62 } 63 64 ~RunTestServer() { 65 Stop(); 66 } 67 68 bool Start() { 69 bool ret = StartWithOptions(Options(base::MessageLoop::TYPE_UI, 0)); 70 if (ret) { 71 message_loop()->PostTask(FROM_HERE, 72 base::Bind(&RunTestServer::StartServer, this)); 73 wait_until_ready(); 74 } 75 return ret; 76 } 77 78 static void StartServer(RunTestServer* me) { 79 me->server_.reset(new test_server::SimpleWebServer(43210)); 80 me->server_->AddResponse(&me->default_response_); 81 ::SetEvent(me->ready_); 82 } 83 84 bool wait_until_ready() { 85 return ::WaitForSingleObject(ready_, kUrlmonMonikerTimeout.InMilliseconds()) 86 == WAIT_OBJECT_0; 87 } 88 89 protected: 90 scoped_ptr<test_server::SimpleWebServer> server_; 91 test_server::SimpleResponse default_response_; 92 base::win::ScopedHandle ready_; 93 }; 94 95 // Helper class for running tests that rely on the NavigationManager. 96 class UrlmonMonikerTestManager { 97 public: 98 explicit UrlmonMonikerTestManager(const wchar_t* test_url) { 99 EXPECT_TRUE(MonikerPatch::Initialize()); 100 } 101 102 ~UrlmonMonikerTestManager() { 103 MonikerPatch::Uninitialize(); 104 } 105 106 chrome_frame_test::TimedMsgLoop& loop() { 107 return loop_; 108 } 109 110 protected: 111 chrome_frame_test::TimedMsgLoop loop_; 112 }; 113 114 ACTION_P(SetBindInfo, is_async) { 115 DWORD* flags = arg0; 116 BINDINFO* bind_info = arg1; 117 118 DCHECK(flags); 119 DCHECK(bind_info); 120 DCHECK(bind_info->cbSize >= sizeof(BINDINFO)); 121 122 *flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA; 123 if (is_async) 124 *flags |= BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE; 125 126 bind_info->dwBindVerb = BINDVERB_GET; 127 memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM)); 128 bind_info->grfBindInfoF = 0; 129 bind_info->szCustomVerb = NULL; 130 } 131 132 // Wraps the MockBindStatusCallbackImpl mock object and allows the user 133 // to specify expectations on the callback object. 134 class UrlmonMonikerTestCallback { 135 public: 136 explicit UrlmonMonikerTestCallback(UrlmonMonikerTestManager* mgr) 137 : mgr_(mgr), clip_format_(0) { 138 } 139 140 ~UrlmonMonikerTestCallback() { 141 } 142 143 typedef enum GetBindInfoExpectations { 144 EXPECT_NO_CALL, 145 REQUEST_SYNCHRONOUS, 146 REQUEST_ASYNCHRONOUS, 147 } GET_BIND_INFO_EXPECTATION; 148 149 // Sets gmock expectations for the IBindStatusCallback mock object. 150 void SetCallbackExpectations(GetBindInfoExpectations bind_info_handling, 151 HRESULT data_available_response, 152 bool quit_loop_on_stop) { 153 EXPECT_CALL(callback_, OnProgress(_, _, _, _)) 154 .WillRepeatedly(Return(S_OK)); 155 156 if (bind_info_handling == REQUEST_ASYNCHRONOUS) { 157 EXPECT_CALL(callback_, GetBindInfo(_, _)) 158 .WillOnce(DoAll(SetBindInfo(true), Return(S_OK))); 159 } else if (bind_info_handling == REQUEST_SYNCHRONOUS) { 160 EXPECT_CALL(callback_, GetBindInfo(_, _)) 161 .WillOnce(DoAll(SetBindInfo(false), Return(S_OK))); 162 } else { 163 DCHECK(bind_info_handling == EXPECT_NO_CALL); 164 } 165 166 EXPECT_CALL(callback_, OnStartBinding(_, _)) 167 .WillOnce(Return(S_OK)); 168 169 EXPECT_CALL(callback_, OnDataAvailable(_, _, _, _)) 170 .WillRepeatedly(Return(data_available_response)); 171 172 if (quit_loop_on_stop) { 173 // When expecting asynchronous 174 EXPECT_CALL(callback_, OnStopBinding(data_available_response, _)) 175 .WillOnce(DoAll(QUIT_LOOP(mgr_->loop()), Return(S_OK))); 176 } else { 177 EXPECT_CALL(callback_, OnStopBinding(data_available_response, _)) 178 .WillOnce(Return(S_OK)); 179 } 180 } 181 182 HRESULT CreateUrlMonikerAndBindToStorage(const wchar_t* url, 183 IBindCtx** bind_ctx) { 184 base::win::ScopedComPtr<IMoniker> moniker; 185 HRESULT hr = CreateURLMoniker(NULL, url, moniker.Receive()); 186 EXPECT_TRUE(moniker != NULL); 187 if (moniker) { 188 base::win::ScopedComPtr<IBindCtx> context; 189 ::CreateAsyncBindCtx(0, callback(), NULL, context.Receive()); 190 DCHECK(context); 191 base::win::ScopedComPtr<IStream> stream; 192 hr = moniker->BindToStorage(context, NULL, IID_IStream, 193 reinterpret_cast<void**>(stream.Receive())); 194 if (SUCCEEDED(hr) && bind_ctx) 195 *bind_ctx = context.Detach(); 196 } 197 return hr; 198 } 199 200 IBindStatusCallback* callback() { 201 return &callback_; 202 } 203 204 protected: 205 CComObjectStackEx<MockBindStatusCallbackImpl> callback_; 206 UrlmonMonikerTestManager* mgr_; 207 CLIPFORMAT clip_format_; 208 }; 209 210 /* 211 212 // Tests synchronously binding to a moniker and downloading the target. 213 TEST_F(UrlmonMonikerTest, BindToStorageSynchronous) { 214 const wchar_t test_url[] = L"http://localhost:43210/"; 215 UrlmonMonikerTestManager test(test_url); 216 UrlmonMonikerTestCallback callback(&test); 217 218 RunTestServer server_thread; 219 EXPECT_TRUE(server_thread.Start()); 220 221 callback.SetCallbackExpectations( 222 UrlmonMonikerTestCallback::REQUEST_SYNCHRONOUS, S_OK, false); 223 224 base::win::ScopedComPtr<IBindCtx> bind_ctx; 225 HRESULT hr = callback.CreateUrlMonikerAndBindToStorage(test_url, 226 bind_ctx.Receive()); 227 // The download should have happened synchronously, so we don't expect 228 // MK_S_ASYNCHRONOUS or any errors. 229 EXPECT_EQ(S_OK, hr); 230 231 IBindCtx* release = bind_ctx.Detach(); 232 EXPECT_EQ(0, release->Release()); 233 234 server_thread.Stop(); 235 } 236 237 // Tests asynchronously binding to a moniker and downloading the target. 238 TEST_F(UrlmonMonikerTest, BindToStorageAsynchronous) { 239 const wchar_t test_url[] = L"http://localhost:43210/"; 240 UrlmonMonikerTestManager test(test_url); 241 UrlmonMonikerTestCallback callback(&test); 242 243 test_server::SimpleWebServer server(43210); 244 test_server::SimpleResponse default_response("/", kTestContent); 245 server.AddResponse(&default_response); 246 247 callback.SetCallbackExpectations( 248 UrlmonMonikerTestCallback::REQUEST_ASYNCHRONOUS, S_OK, true); 249 250 base::win::ScopedComPtr<IBindCtx> bind_ctx; 251 HRESULT hr = callback.CreateUrlMonikerAndBindToStorage(test_url, 252 bind_ctx.Receive()); 253 EXPECT_EQ(MK_S_ASYNCHRONOUS, hr); 254 test.loop().RunFor(kUrlmonMonikerTimeout); 255 256 IBindCtx* release = bind_ctx.Detach(); 257 EXPECT_EQ(0, release->Release()); 258 } 259 260 // Responds with the Chrome mime type. 261 class ResponseWithContentType : public test_server::SimpleResponse { 262 public: 263 ResponseWithContentType(const char* request_path, 264 const std::string& contents) 265 : test_server::SimpleResponse(request_path, contents) { 266 } 267 virtual bool GetContentType(std::string* content_type) const { 268 *content_type = WideToASCII(kChromeMimeType); 269 return true; 270 } 271 }; 272 273 // Downloads a document asynchronously and then verifies that the downloaded 274 // contents were cached and the cache contents are correct. 275 // TODO(tommi): Fix and re-enable. 276 // http://code.google.com/p/chromium/issues/detail?id=39415 277 TEST_F(UrlmonMonikerTest, BindToStorageSwitchContent) { 278 const wchar_t test_url[] = L"http://localhost:43210/"; 279 UrlmonMonikerTestManager test(test_url); 280 UrlmonMonikerTestCallback callback(&test); 281 282 test_server::SimpleWebServer server(43210); 283 ResponseWithContentType default_response("/", kTestContent); 284 server.AddResponse(&default_response); 285 286 callback.SetCallbackExpectations( 287 UrlmonMonikerTestCallback::REQUEST_ASYNCHRONOUS, INET_E_TERMINATED_BIND, 288 true); 289 290 HRESULT hr = callback.CreateUrlMonikerAndBindToStorage(test_url, NULL); 291 EXPECT_EQ(MK_S_ASYNCHRONOUS, hr); 292 test.loop().RunFor(kUrlmonMonikerTimeout); 293 294 scoped_refptr<RequestData> request_data( 295 test.nav_manager().GetActiveRequestData(test_url)); 296 EXPECT_TRUE(request_data != NULL); 297 298 if (request_data) { 299 EXPECT_EQ(request_data->GetCachedContentSize(), 300 arraysize(kTestContent) - 1); 301 base::win::ScopedComPtr<IStream> stream; 302 request_data->GetResetCachedContentStream(stream.Receive()); 303 EXPECT_TRUE(stream != NULL); 304 if (stream) { 305 char buffer[0xffff]; 306 DWORD read = 0; 307 stream->Read(buffer, sizeof(buffer), &read); 308 EXPECT_EQ(read, arraysize(kTestContent) - 1); 309 EXPECT_EQ(0, memcmp(buffer, kTestContent, read)); 310 } 311 } 312 } 313 314 // Fetches content asynchronously first to cache it and then 315 // verifies that fetching the cached content the same way works as expected 316 // and happens synchronously. 317 TEST_F(UrlmonMonikerTest, BindToStorageCachedContent) { 318 const wchar_t test_url[] = L"http://localhost:43210/"; 319 UrlmonMonikerTestManager test(test_url); 320 UrlmonMonikerTestCallback callback(&test); 321 322 test_server::SimpleWebServer server(43210); 323 ResponseWithContentType default_response("/", kTestContent); 324 server.AddResponse(&default_response); 325 326 // First set of expectations. Download the contents 327 // asynchronously. This should populate the cache so that 328 // the second request should be served synchronously without 329 // going to the server. 330 callback.SetCallbackExpectations( 331 UrlmonMonikerTestCallback::REQUEST_ASYNCHRONOUS, INET_E_TERMINATED_BIND, 332 true); 333 334 HRESULT hr = callback.CreateUrlMonikerAndBindToStorage(test_url, NULL); 335 EXPECT_EQ(MK_S_ASYNCHRONOUS, hr); 336 test.loop().RunFor(kUrlmonMonikerTimeout); 337 338 scoped_refptr<RequestData> request_data( 339 test.nav_manager().GetActiveRequestData(test_url)); 340 EXPECT_TRUE(request_data != NULL); 341 342 if (request_data) { 343 // This time, just accept the content as normal. 344 UrlmonMonikerTestCallback callback2(&test); 345 callback2.SetCallbackExpectations( 346 UrlmonMonikerTestCallback::EXPECT_NO_CALL, S_OK, false); 347 hr = callback2.CreateUrlMonikerAndBindToStorage(test_url, NULL); 348 // S_OK means that the operation completed synchronously. 349 // Otherwise we'd get MK_S_ASYNCHRONOUS. 350 EXPECT_EQ(S_OK, hr); 351 } 352 } 353 354 */ 355