ricea | 96e3ce4 | 2015-12-10 02:13:29 | [diff] [blame] | 1 | // Copyright 2015 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 "content/browser/loader/async_revalidation_manager.h" |
| 6 | |
| 7 | #include <deque> |
| 8 | #include <string> |
| 9 | #include <utility> |
| 10 | |
| 11 | #include "base/bind.h" |
| 12 | #include "base/callback.h" |
| 13 | #include "base/macros.h" |
| 14 | #include "base/memory/shared_memory_handle.h" |
| 15 | #include "base/pickle.h" |
| 16 | #include "base/run_loop.h" |
| 17 | #include "base/strings/string_util.h" |
| 18 | #include "content/browser/child_process_security_policy_impl.h" |
| 19 | #include "content/browser/loader/resource_dispatcher_host_impl.h" |
| 20 | #include "content/browser/loader/resource_message_filter.h" |
| 21 | #include "content/common/child_process_host_impl.h" |
| 22 | #include "content/common/resource_messages.h" |
| 23 | #include "content/public/browser/resource_context.h" |
| 24 | #include "content/public/common/appcache_info.h" |
| 25 | #include "content/public/common/process_type.h" |
| 26 | #include "content/public/common/resource_type.h" |
| 27 | #include "content/public/test/test_browser_context.h" |
| 28 | #include "content/public/test/test_browser_thread_bundle.h" |
| 29 | #include "ipc/ipc_param_traits.h" |
| 30 | #include "net/base/load_flags.h" |
| 31 | #include "net/base/network_delegate.h" |
| 32 | #include "net/http/http_util.h" |
| 33 | #include "net/url_request/url_request.h" |
| 34 | #include "net/url_request/url_request_job.h" |
| 35 | #include "net/url_request/url_request_job_factory.h" |
| 36 | #include "net/url_request/url_request_test_job.h" |
| 37 | #include "net/url_request/url_request_test_util.h" |
| 38 | #include "testing/gtest/include/gtest/gtest.h" |
| 39 | #include "ui/base/page_transition_types.h" |
| 40 | #include "url/gurl.h" |
| 41 | #include "url/url_constants.h" |
| 42 | |
| 43 | namespace content { |
| 44 | |
| 45 | namespace { |
| 46 | |
| 47 | // This class is a variation on URLRequestTestJob that |
| 48 | // returns ERR_IO_PENDING before every read, not just the first one. |
| 49 | class URLRequestTestDelayedCompletionJob : public net::URLRequestTestJob { |
| 50 | public: |
| 51 | URLRequestTestDelayedCompletionJob(net::URLRequest* request, |
| 52 | net::NetworkDelegate* network_delegate, |
| 53 | const std::string& response_headers, |
| 54 | const std::string& response_data) |
| 55 | : net::URLRequestTestJob(request, |
| 56 | network_delegate, |
| 57 | response_headers, |
| 58 | response_data, |
| 59 | false) {} |
| 60 | |
| 61 | private: |
| 62 | bool NextReadAsync() override { return true; } |
| 63 | }; |
| 64 | |
| 65 | // A URLRequestJob implementation which sets the |async_revalidation_required| |
| 66 | // flag on the HttpResponseInfo object to true if the request has the |
| 67 | // LOAD_SUPPORT_ASYNC_REVALIDATION flag. |
| 68 | class AsyncRevalidationRequiredURLRequestTestJob |
| 69 | : public net::URLRequestTestJob { |
| 70 | public: |
| 71 | AsyncRevalidationRequiredURLRequestTestJob( |
| 72 | net::URLRequest* request, |
| 73 | net::NetworkDelegate* network_delegate) |
| 74 | : URLRequestTestJob(request, |
| 75 | network_delegate, |
| 76 | net::URLRequestTestJob::test_headers(), |
| 77 | std::string(), |
| 78 | false) {} |
| 79 | |
| 80 | void GetResponseInfo(net::HttpResponseInfo* info) override { |
| 81 | URLRequestTestJob::GetResponseInfo(info); |
| 82 | if (request()->load_flags() & net::LOAD_SUPPORT_ASYNC_REVALIDATION) |
| 83 | info->async_revalidation_required = true; |
| 84 | } |
| 85 | }; |
| 86 | |
| 87 | // A URLRequestJob implementation which serves a redirect and sets the |
| 88 | // |async_revalidation_required| flag on the HttpResponseInfo object to true if |
| 89 | // the request has the LOAD_SUPPORT_ASYNC_REVALIDATION flag. |
| 90 | class RedirectAndRevalidateURLRequestTestJob : public net::URLRequestTestJob { |
| 91 | public: |
| 92 | RedirectAndRevalidateURLRequestTestJob(net::URLRequest* request, |
| 93 | net::NetworkDelegate* network_delegate) |
| 94 | : URLRequestTestJob(request, |
| 95 | network_delegate, |
| 96 | CreateRedirectHeaders(), |
| 97 | std::string(), |
| 98 | false) {} |
| 99 | |
| 100 | void GetResponseInfo(net::HttpResponseInfo* info) override { |
| 101 | URLRequestTestJob::GetResponseInfo(info); |
| 102 | if (request()->load_flags() & net::LOAD_SUPPORT_ASYNC_REVALIDATION) |
| 103 | info->async_revalidation_required = true; |
| 104 | } |
| 105 | |
| 106 | private: |
| 107 | static std::string CreateRedirectHeaders() { |
| 108 | static const char kRedirectHeaders[] = |
| 109 | "HTTP/1.1 302 MOVED\n" |
| 110 | "Location: http://example.com/async-revalidate/from-redirect\n" |
| 111 | "\n"; |
| 112 | return std::string(kRedirectHeaders, arraysize(kRedirectHeaders) - 1); |
| 113 | } |
| 114 | }; |
| 115 | |
| 116 | class TestURLRequestJobFactory : public net::URLRequestJobFactory { |
| 117 | public: |
| 118 | TestURLRequestJobFactory() = default; |
| 119 | |
| 120 | // Sets the contents of the response. |headers| should have "\n" as line |
| 121 | // breaks and end in "\n\n". |
| 122 | void SetResponse(const std::string& headers, const std::string& data) { |
| 123 | response_headers_ = headers; |
| 124 | response_data_ = data; |
| 125 | } |
| 126 | |
| 127 | net::URLRequestJob* MaybeCreateJobWithProtocolHandler( |
| 128 | const std::string& scheme, |
| 129 | net::URLRequest* request, |
| 130 | net::NetworkDelegate* network_delegate) const override { |
| 131 | std::string path = request->url().path(); |
| 132 | if (base::StartsWith(path, "/async-revalidate", |
| 133 | base::CompareCase::SENSITIVE)) { |
| 134 | return new AsyncRevalidationRequiredURLRequestTestJob(request, |
| 135 | network_delegate); |
| 136 | } |
| 137 | if (base::StartsWith(path, "/redirect", base::CompareCase::SENSITIVE)) { |
| 138 | return new RedirectAndRevalidateURLRequestTestJob(request, |
| 139 | network_delegate); |
| 140 | } |
| 141 | return new URLRequestTestDelayedCompletionJob( |
| 142 | request, network_delegate, response_headers_, response_data_); |
| 143 | } |
| 144 | |
| 145 | net::URLRequestJob* MaybeInterceptRedirect( |
| 146 | net::URLRequest* request, |
| 147 | net::NetworkDelegate* network_delegate, |
| 148 | const GURL& location) const override { |
| 149 | return nullptr; |
| 150 | } |
| 151 | |
| 152 | net::URLRequestJob* MaybeInterceptResponse( |
| 153 | net::URLRequest* request, |
| 154 | net::NetworkDelegate* network_delegate) const override { |
| 155 | return nullptr; |
| 156 | } |
| 157 | |
| 158 | bool IsHandledProtocol(const std::string& scheme) const override { |
| 159 | // If non-standard schemes need to be tested in future it will be |
| 160 | // necessary to call ChildProcessSecurityPolicyImpl:: |
| 161 | // RegisterWebSafeScheme() for them. |
| 162 | return scheme == url::kHttpScheme || scheme == url::kHttpsScheme; |
| 163 | } |
| 164 | |
| 165 | bool IsHandledURL(const GURL& url) const override { |
| 166 | return IsHandledProtocol(url.scheme()); |
| 167 | } |
| 168 | |
| 169 | bool IsSafeRedirectTarget(const GURL& location) const override { |
| 170 | return false; |
| 171 | } |
| 172 | |
| 173 | private: |
| 174 | std::string response_headers_; |
| 175 | std::string response_data_; |
| 176 | |
| 177 | DISALLOW_COPY_AND_ASSIGN(TestURLRequestJobFactory); |
| 178 | }; |
| 179 | |
| 180 | // On Windows, ResourceMsg_SetDataBuffer supplies a HANDLE which is not |
| 181 | // automatically released. |
| 182 | // |
| 183 | // See ResourceDispatcher::ReleaseResourcesInDataMessage. |
| 184 | // |
| 185 | // TODO(ricea): Maybe share this implementation with |
| 186 | // resource_dispatcher_host_unittest.cc. |
| 187 | void ReleaseHandlesInMessage(const IPC::Message& message) { |
| 188 | if (message.type() == ResourceMsg_SetDataBuffer::ID) { |
| 189 | base::PickleIterator iter(message); |
| 190 | int request_id; |
| 191 | CHECK(iter.ReadInt(&request_id)); |
| 192 | base::SharedMemoryHandle shm_handle; |
| 193 | if (IPC::ParamTraits<base::SharedMemoryHandle>::Read(&message, &iter, |
| 194 | &shm_handle)) { |
| 195 | if (base::SharedMemory::IsHandleValid(shm_handle)) |
| 196 | base::SharedMemory::CloseHandle(shm_handle); |
| 197 | } |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | // This filter just deletes any messages that are sent through it. |
| 202 | class BlackholeFilter : public ResourceMessageFilter { |
| 203 | public: |
| 204 | explicit BlackholeFilter(ResourceContext* resource_context) |
| 205 | : ResourceMessageFilter( |
| 206 | ChildProcessHostImpl::GenerateChildProcessUniqueId(), |
| 207 | PROCESS_TYPE_RENDERER, |
| 208 | nullptr, |
| 209 | nullptr, |
| 210 | nullptr, |
| 211 | nullptr, |
| 212 | nullptr, |
| 213 | base::Bind(&BlackholeFilter::GetContexts, base::Unretained(this))), |
| 214 | resource_context_(resource_context) { |
| 215 | ChildProcessSecurityPolicyImpl::GetInstance()->Add(child_id()); |
| 216 | } |
| 217 | |
| 218 | bool Send(IPC::Message* msg) override { |
| 219 | scoped_ptr<IPC::Message> take_ownership(msg); |
| 220 | ReleaseHandlesInMessage(*msg); |
| 221 | return true; |
| 222 | } |
| 223 | |
| 224 | private: |
| 225 | ~BlackholeFilter() override { |
| 226 | ChildProcessSecurityPolicyImpl::GetInstance()->Remove(child_id()); |
| 227 | } |
| 228 | |
| 229 | void GetContexts(ResourceType resource_type, |
| 230 | int origin_pid, |
| 231 | ResourceContext** resource_context, |
| 232 | net::URLRequestContext** request_context) { |
| 233 | *resource_context = resource_context_; |
| 234 | *request_context = resource_context_->GetRequestContext(); |
| 235 | } |
| 236 | |
| 237 | ResourceContext* resource_context_; |
| 238 | |
| 239 | DISALLOW_COPY_AND_ASSIGN(BlackholeFilter); |
| 240 | }; |
| 241 | |
| 242 | ResourceHostMsg_Request CreateResourceRequest(const char* method, |
| 243 | ResourceType type, |
| 244 | const GURL& url) { |
| 245 | ResourceHostMsg_Request request; |
| 246 | request.method = std::string(method); |
| 247 | request.url = url; |
| 248 | request.first_party_for_cookies = url; // Bypass third-party cookie blocking. |
| 249 | request.referrer_policy = blink::WebReferrerPolicyDefault; |
| 250 | request.load_flags = 0; |
| 251 | request.origin_pid = 0; |
| 252 | request.resource_type = type; |
| 253 | request.request_context = 0; |
| 254 | request.appcache_host_id = kAppCacheNoHostId; |
| 255 | request.download_to_file = false; |
| 256 | request.should_reset_appcache = false; |
| 257 | request.is_main_frame = true; |
| 258 | request.parent_is_main_frame = false; |
| 259 | request.parent_render_frame_id = -1; |
| 260 | request.transition_type = ui::PAGE_TRANSITION_LINK; |
| 261 | request.allow_download = true; |
| 262 | return request; |
| 263 | } |
| 264 | |
| 265 | class AsyncRevalidationManagerTest : public ::testing::Test { |
| 266 | protected: |
| 267 | AsyncRevalidationManagerTest( |
| 268 | scoped_ptr<net::TestNetworkDelegate> network_delegate) |
| 269 | : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), |
| 270 | network_delegate_(std::move(network_delegate)) { |
| 271 | browser_context_.reset(new TestBrowserContext()); |
| 272 | BrowserContext::EnsureResourceContextInitialized(browser_context_.get()); |
| 273 | base::RunLoop().RunUntilIdle(); |
| 274 | ResourceContext* resource_context = browser_context_->GetResourceContext(); |
| 275 | filter_ = new BlackholeFilter(resource_context); |
| 276 | net::URLRequestContext* request_context = |
| 277 | resource_context->GetRequestContext(); |
| 278 | job_factory_.reset(new TestURLRequestJobFactory); |
| 279 | request_context->set_job_factory(job_factory_.get()); |
| 280 | request_context->set_network_delegate(network_delegate_.get()); |
| 281 | host_.EnableStaleWhileRevalidateForTesting(); |
| 282 | } |
| 283 | |
| 284 | AsyncRevalidationManagerTest() |
| 285 | : AsyncRevalidationManagerTest( |
| 286 | make_scoped_ptr(new net::TestNetworkDelegate)) {} |
| 287 | |
| 288 | void TearDown() override { |
| 289 | host_.CancelRequestsForProcess(filter_->child_id()); |
| 290 | host_.Shutdown(); |
| 291 | host_.CancelRequestsForContext(browser_context_->GetResourceContext()); |
| 292 | browser_context_.reset(); |
| 293 | base::RunLoop().RunUntilIdle(); |
| 294 | } |
| 295 | |
| 296 | void SetResponse(const std::string& headers, const std::string& data) { |
| 297 | job_factory_->SetResponse(headers, data); |
| 298 | } |
| 299 | |
| 300 | // Creates a request using the current test object as the filter and |
| 301 | // SubResource as the resource type. |
| 302 | void MakeTestRequest(int render_view_id, int request_id, const GURL& url) { |
| 303 | ResourceHostMsg_Request request = |
| 304 | CreateResourceRequest("GET", RESOURCE_TYPE_SUB_RESOURCE, url); |
| 305 | ResourceHostMsg_RequestResource msg(render_view_id, request_id, request); |
| 306 | host_.OnMessageReceived(msg, filter_.get()); |
| 307 | base::RunLoop().RunUntilIdle(); |
| 308 | } |
| 309 | |
| 310 | void EnsureSchemeIsAllowed(const std::string& scheme) { |
| 311 | ChildProcessSecurityPolicyImpl* policy = |
| 312 | ChildProcessSecurityPolicyImpl::GetInstance(); |
| 313 | if (!policy->IsWebSafeScheme(scheme)) |
| 314 | policy->RegisterWebSafeScheme(scheme); |
| 315 | } |
| 316 | |
| 317 | content::TestBrowserThreadBundle thread_bundle_; |
| 318 | scoped_ptr<TestBrowserContext> browser_context_; |
| 319 | scoped_ptr<TestURLRequestJobFactory> job_factory_; |
| 320 | scoped_refptr<BlackholeFilter> filter_; |
| 321 | scoped_ptr<net::TestNetworkDelegate> network_delegate_; |
| 322 | ResourceDispatcherHostImpl host_; |
| 323 | }; |
| 324 | |
| 325 | TEST_F(AsyncRevalidationManagerTest, SupportsAsyncRevalidation) { |
| 326 | SetResponse(net::URLRequestTestJob::test_headers(), "delay complete"); |
| 327 | MakeTestRequest(0, 1, GURL("http://example.com/baz")); |
| 328 | |
| 329 | net::URLRequest* url_request( |
| 330 | host_.GetURLRequest(GlobalRequestID(filter_->child_id(), 1))); |
| 331 | ASSERT_TRUE(url_request); |
| 332 | |
| 333 | EXPECT_TRUE(url_request->load_flags() & net::LOAD_SUPPORT_ASYNC_REVALIDATION); |
| 334 | } |
| 335 | |
| 336 | TEST_F(AsyncRevalidationManagerTest, AsyncRevalidationNotSupportedForPOST) { |
| 337 | SetResponse(net::URLRequestTestJob::test_headers(), "delay complete"); |
| 338 | // Create POST request. |
| 339 | ResourceHostMsg_Request request = CreateResourceRequest( |
| 340 | "POST", RESOURCE_TYPE_SUB_RESOURCE, GURL("http://example.com/baz.php")); |
| 341 | ResourceHostMsg_RequestResource msg(0, 1, request); |
| 342 | host_.OnMessageReceived(msg, filter_.get()); |
| 343 | base::RunLoop().RunUntilIdle(); |
| 344 | |
| 345 | net::URLRequest* url_request( |
| 346 | host_.GetURLRequest(GlobalRequestID(filter_->child_id(), 1))); |
| 347 | ASSERT_TRUE(url_request); |
| 348 | |
| 349 | EXPECT_FALSE(url_request->load_flags() & |
| 350 | net::LOAD_SUPPORT_ASYNC_REVALIDATION); |
| 351 | } |
| 352 | |
| 353 | TEST_F(AsyncRevalidationManagerTest, |
| 354 | AsyncRevalidationNotSupportedAfterRedirect) { |
| 355 | static const char kRedirectHeaders[] = |
| 356 | "HTTP/1.1 302 MOVED\n" |
| 357 | "Location: http://example.com/var\n" |
| 358 | "\n"; |
| 359 | SetResponse(kRedirectHeaders, ""); |
| 360 | |
| 361 | MakeTestRequest(0, 1, GURL("http://example.com/baz")); |
| 362 | |
| 363 | net::URLRequest* url_request( |
| 364 | host_.GetURLRequest(GlobalRequestID(filter_->child_id(), 1))); |
| 365 | ASSERT_TRUE(url_request); |
| 366 | |
| 367 | EXPECT_FALSE(url_request->load_flags() & |
| 368 | net::LOAD_SUPPORT_ASYNC_REVALIDATION); |
| 369 | } |
| 370 | |
| 371 | // A NetworkDelegate that records the URLRequests as they are created. |
| 372 | class URLRequestRecordingNetworkDelegate : public net::TestNetworkDelegate { |
| 373 | public: |
| 374 | URLRequestRecordingNetworkDelegate() : requests_() {} |
| 375 | |
| 376 | net::URLRequest* NextRequest() { |
| 377 | EXPECT_FALSE(requests_.empty()); |
| 378 | if (requests_.empty()) |
| 379 | return nullptr; |
| 380 | net::URLRequest* request = requests_.front(); |
| 381 | EXPECT_TRUE(request); |
| 382 | requests_.pop_front(); |
| 383 | return request; |
| 384 | } |
| 385 | |
| 386 | bool NextRequestWasDestroyed() { |
| 387 | net::URLRequest* request = requests_.front(); |
| 388 | requests_.pop_front(); |
| 389 | return request == nullptr; |
| 390 | } |
| 391 | |
| 392 | bool IsEmpty() const { return requests_.empty(); } |
| 393 | |
| 394 | int OnBeforeURLRequest(net::URLRequest* request, |
| 395 | const net::CompletionCallback& callback, |
| 396 | GURL* new_url) override { |
| 397 | requests_.push_back(request); |
| 398 | return TestNetworkDelegate::OnBeforeURLRequest(request, callback, new_url); |
| 399 | } |
| 400 | |
| 401 | void OnURLRequestDestroyed(net::URLRequest* request) override { |
| 402 | for (auto& recorded_request : requests_) { |
| 403 | if (recorded_request == request) |
| 404 | recorded_request = nullptr; |
| 405 | } |
| 406 | net::TestNetworkDelegate::OnURLRequestDestroyed(request); |
| 407 | } |
| 408 | |
| 409 | private: |
| 410 | std::deque<net::URLRequest*> requests_; |
| 411 | |
| 412 | DISALLOW_COPY_AND_ASSIGN(URLRequestRecordingNetworkDelegate); |
| 413 | }; |
| 414 | |
| 415 | class AsyncRevalidationManagerRecordingTest |
| 416 | : public AsyncRevalidationManagerTest { |
| 417 | public: |
| 418 | AsyncRevalidationManagerRecordingTest() |
| 419 | : AsyncRevalidationManagerTest( |
| 420 | make_scoped_ptr(new URLRequestRecordingNetworkDelegate)) {} |
| 421 | |
| 422 | void TearDown() override { |
| 423 | EXPECT_TRUE(IsEmpty()); |
| 424 | AsyncRevalidationManagerTest::TearDown(); |
| 425 | } |
| 426 | |
| 427 | URLRequestRecordingNetworkDelegate* recording_network_delegate() const { |
| 428 | return static_cast<URLRequestRecordingNetworkDelegate*>( |
| 429 | network_delegate_.get()); |
| 430 | } |
| 431 | |
| 432 | bool NextRequestWasDestroyed() { |
| 433 | return recording_network_delegate()->NextRequestWasDestroyed(); |
| 434 | } |
| 435 | |
| 436 | net::URLRequest* NextRequest() { |
| 437 | return recording_network_delegate()->NextRequest(); |
| 438 | } |
| 439 | |
| 440 | bool IsEmpty() const { return recording_network_delegate()->IsEmpty(); } |
| 441 | }; |
| 442 | |
| 443 | // Verify that an async revalidation is actually created when needed. |
| 444 | TEST_F(AsyncRevalidationManagerRecordingTest, Issued) { |
| 445 | // Create the original request. |
| 446 | MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate")); |
| 447 | |
| 448 | net::URLRequest* initial_request = NextRequest(); |
| 449 | ASSERT_TRUE(initial_request); |
| 450 | EXPECT_TRUE(initial_request->load_flags() & |
| 451 | net::LOAD_SUPPORT_ASYNC_REVALIDATION); |
| 452 | |
| 453 | net::URLRequest* async_request = NextRequest(); |
| 454 | ASSERT_TRUE(async_request); |
| 455 | } |
| 456 | |
| 457 | // Verify the the URL of the async revalidation matches the original request. |
| 458 | TEST_F(AsyncRevalidationManagerRecordingTest, URLMatches) { |
| 459 | // Create the original request. |
| 460 | MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate/u")); |
| 461 | |
| 462 | // Discard the original request. |
| 463 | NextRequest(); |
| 464 | |
| 465 | net::URLRequest* async_request = NextRequest(); |
| 466 | ASSERT_TRUE(async_request); |
| 467 | EXPECT_EQ(GURL("http://example.com/async-revalidate/u"), |
| 468 | async_request->url()); |
| 469 | } |
| 470 | |
| 471 | TEST_F(AsyncRevalidationManagerRecordingTest, |
| 472 | AsyncRevalidationsDoNotSupportAsyncRevalidation) { |
| 473 | // Create the original request. |
| 474 | MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate")); |
| 475 | |
| 476 | // Discard the original request. |
| 477 | NextRequest(); |
| 478 | |
| 479 | // Get the async revalidation request. |
| 480 | net::URLRequest* async_request = NextRequest(); |
| 481 | ASSERT_TRUE(async_request); |
| 482 | EXPECT_FALSE(async_request->load_flags() & |
| 483 | net::LOAD_SUPPORT_ASYNC_REVALIDATION); |
| 484 | } |
| 485 | |
| 486 | TEST_F(AsyncRevalidationManagerRecordingTest, AsyncRevalidationsNotDuplicated) { |
| 487 | // Create the original request. |
| 488 | MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate")); |
| 489 | |
| 490 | // Discard the original request. |
| 491 | NextRequest(); |
| 492 | |
| 493 | // Get the async revalidation request. |
| 494 | net::URLRequest* async_request = NextRequest(); |
| 495 | EXPECT_TRUE(async_request); |
| 496 | |
| 497 | // Start a second request to the same URL. |
| 498 | MakeTestRequest(0, 2, GURL("http://example.com/async-revalidate")); |
| 499 | |
| 500 | // Discard the second request. |
| 501 | NextRequest(); |
| 502 | |
| 503 | // There should not be another async revalidation request. |
| 504 | EXPECT_TRUE(IsEmpty()); |
| 505 | } |
| 506 | |
| 507 | // Async revalidation to different URLs should not be treated as duplicates. |
| 508 | TEST_F(AsyncRevalidationManagerRecordingTest, |
| 509 | AsyncRevalidationsToSeparateURLsAreSeparate) { |
| 510 | // Create two requests to two URLs. |
| 511 | MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate")); |
| 512 | MakeTestRequest(0, 2, GURL("http://example.com/async-revalidate2")); |
| 513 | |
| 514 | net::URLRequest* initial_request = NextRequest(); |
| 515 | ASSERT_TRUE(initial_request); |
| 516 | net::URLRequest* initial_async_revalidation = NextRequest(); |
| 517 | ASSERT_TRUE(initial_async_revalidation); |
| 518 | net::URLRequest* second_request = NextRequest(); |
| 519 | ASSERT_TRUE(second_request); |
| 520 | net::URLRequest* second_async_revalidation = NextRequest(); |
| 521 | ASSERT_TRUE(second_async_revalidation); |
| 522 | |
| 523 | EXPECT_EQ("http://example.com/async-revalidate", |
| 524 | initial_request->url().spec()); |
| 525 | EXPECT_EQ("http://example.com/async-revalidate", |
| 526 | initial_async_revalidation->url().spec()); |
| 527 | EXPECT_EQ("http://example.com/async-revalidate2", |
| 528 | second_request->url().spec()); |
| 529 | EXPECT_EQ("http://example.com/async-revalidate2", |
| 530 | second_async_revalidation->url().spec()); |
| 531 | } |
| 532 | |
| 533 | // Nothing after the first redirect leg has stale-while-revalidate applied. |
| 534 | // TODO(ricea): s-w-r should work with redirects. Change this test when it does. |
| 535 | TEST_F(AsyncRevalidationManagerRecordingTest, NoSWRAfterFirstRedirectLeg) { |
| 536 | MakeTestRequest(0, 1, GURL("http://example.com/redirect")); |
| 537 | |
| 538 | net::URLRequest* initial_request = NextRequest(); |
| 539 | EXPECT_TRUE(initial_request); |
| 540 | |
| 541 | EXPECT_FALSE(initial_request->load_flags() & |
| 542 | net::LOAD_SUPPORT_ASYNC_REVALIDATION); |
| 543 | |
| 544 | // An async revalidation happens for the redirect. It has no body, so it has |
| 545 | // already completed. |
| 546 | EXPECT_TRUE(NextRequestWasDestroyed()); |
| 547 | |
| 548 | // But no others. |
| 549 | EXPECT_TRUE(IsEmpty()); |
| 550 | } |
| 551 | |
| 552 | } // namespace |
| 553 | |
| 554 | } // namespace content |