 Chromium Code Reviews
 Chromium Code Reviews Issue 1041993004:
  content::ResourceDispatcherHostImpl changes for stale-while-revalidate  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@s-w-r-yhirano-patch
    
  
    Issue 1041993004:
  content::ResourceDispatcherHostImpl changes for stale-while-revalidate  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@s-w-r-yhirano-patch| Index: content/browser/loader/async_revalidation_manager_unittest.cc | 
| diff --git a/content/browser/loader/async_revalidation_manager_unittest.cc b/content/browser/loader/async_revalidation_manager_unittest.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..474eb314c13669c6968665b8a42d212b7a0fc516 | 
| --- /dev/null | 
| +++ b/content/browser/loader/async_revalidation_manager_unittest.cc | 
| @@ -0,0 +1,580 @@ | 
| +// Copyright 2015 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 "content/browser/loader/async_revalidation_manager.h" | 
| + | 
| +#include <deque> | 
| +#include <string> | 
| +#include <utility> | 
| + | 
| +#include "base/bind.h" | 
| +#include "base/callback.h" | 
| +#include "base/macros.h" | 
| +#include "base/memory/shared_memory_handle.h" | 
| +#include "base/pickle.h" | 
| +#include "base/run_loop.h" | 
| +#include "base/strings/string_util.h" | 
| +#include "content/browser/child_process_security_policy_impl.h" | 
| +#include "content/browser/loader/resource_dispatcher_host_impl.h" | 
| +#include "content/browser/loader/resource_message_filter.h" | 
| +#include "content/common/child_process_host_impl.h" | 
| +#include "content/common/resource_messages.h" | 
| +#include "content/public/browser/resource_context.h" | 
| +#include "content/public/common/appcache_info.h" | 
| +#include "content/public/common/process_type.h" | 
| +#include "content/public/common/resource_type.h" | 
| +#include "content/public/test/test_browser_context.h" | 
| +#include "content/public/test/test_browser_thread_bundle.h" | 
| +#include "ipc/ipc_param_traits.h" | 
| +#include "net/base/load_flags.h" | 
| +#include "net/base/network_delegate.h" | 
| +#include "net/http/http_util.h" | 
| +#include "net/url_request/url_request.h" | 
| +#include "net/url_request/url_request_job.h" | 
| +#include "net/url_request/url_request_job_factory.h" | 
| +#include "net/url_request/url_request_test_job.h" | 
| +#include "net/url_request/url_request_test_util.h" | 
| +#include "testing/gtest/include/gtest/gtest.h" | 
| +#include "ui/base/page_transition_types.h" | 
| +#include "url/gurl.h" | 
| +#include "url/url_constants.h" | 
| + | 
| +namespace content { | 
| + | 
| +namespace { | 
| + | 
| +// This class is a variation on URLRequestTestJob that | 
| +// returns ERR_IO_PENDING before every read, not just the first one. | 
| +class URLRequestTestDelayedCompletionJob : public net::URLRequestTestJob { | 
| + public: | 
| + URLRequestTestDelayedCompletionJob(net::URLRequest* request, | 
| + net::NetworkDelegate* network_delegate, | 
| + const std::string& response_headers, | 
| + const std::string& response_data) | 
| + : net::URLRequestTestJob(request, | 
| + network_delegate, | 
| + response_headers, | 
| + response_data, | 
| + false) {} | 
| + | 
| + private: | 
| + ~URLRequestTestDelayedCompletionJob() override {} | 
| + | 
| + bool NextReadAsync() override { return true; } | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(URLRequestTestDelayedCompletionJob); | 
| +}; | 
| + | 
| +// A URLRequestJob implementation which sets the |async_revalidation_required| | 
| +// flag on the HttpResponseInfo object to true if the request has the | 
| +// LOAD_SUPPORT_ASYNC_REVALIDATION flag. | 
| +class AsyncRevalidationRequiredURLRequestTestJob | 
| + : public net::URLRequestTestJob { | 
| + public: | 
| + AsyncRevalidationRequiredURLRequestTestJob( | 
| + net::URLRequest* request, | 
| + net::NetworkDelegate* network_delegate) | 
| + : URLRequestTestJob(request, | 
| + network_delegate, | 
| + net::URLRequestTestJob::test_headers(), | 
| + std::string(), | 
| + false) {} | 
| + | 
| + void GetResponseInfo(net::HttpResponseInfo* info) override { | 
| + URLRequestTestJob::GetResponseInfo(info); | 
| + if (request()->load_flags() & net::LOAD_SUPPORT_ASYNC_REVALIDATION) | 
| + info->async_revalidation_required = true; | 
| + } | 
| + | 
| + private: | 
| + ~AsyncRevalidationRequiredURLRequestTestJob() override {} | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(AsyncRevalidationRequiredURLRequestTestJob); | 
| +}; | 
| + | 
| +// A URLRequestJob implementation which serves a redirect and sets the | 
| +// |async_revalidation_required| flag on the HttpResponseInfo object to true if | 
| +// the request has the LOAD_SUPPORT_ASYNC_REVALIDATION flag. | 
| +class RedirectAndRevalidateURLRequestTestJob : public net::URLRequestTestJob { | 
| + public: | 
| + RedirectAndRevalidateURLRequestTestJob(net::URLRequest* request, | 
| + net::NetworkDelegate* network_delegate) | 
| + : URLRequestTestJob(request, | 
| + network_delegate, | 
| + CreateRedirectHeaders(), | 
| + std::string(), | 
| + false) {} | 
| + | 
| + void GetResponseInfo(net::HttpResponseInfo* info) override { | 
| + URLRequestTestJob::GetResponseInfo(info); | 
| + if (request()->load_flags() & net::LOAD_SUPPORT_ASYNC_REVALIDATION) | 
| + info->async_revalidation_required = true; | 
| + } | 
| + | 
| + private: | 
| + ~RedirectAndRevalidateURLRequestTestJob() override {} | 
| + | 
| + static std::string CreateRedirectHeaders() { | 
| + static const char kRedirectHeaders[] = | 
| + "HTTP/1.1 302 MOVED\n" | 
| + "Location: http://example.com/async-revalidate/from-redirect\n" | 
| + "\n"; | 
| + return std::string(kRedirectHeaders, arraysize(kRedirectHeaders) - 1); | 
| + } | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(RedirectAndRevalidateURLRequestTestJob); | 
| +}; | 
| + | 
| +class TestURLRequestJobFactory : public net::URLRequestJobFactory { | 
| + public: | 
| + TestURLRequestJobFactory() = default; | 
| + | 
| + // Sets the contents of the response. |headers| should have "\n" as line | 
| + // breaks and end in "\n\n". | 
| + void SetResponse(const std::string& headers, const std::string& data) { | 
| + response_headers_ = headers; | 
| + response_data_ = data; | 
| + } | 
| + | 
| + net::URLRequestJob* MaybeCreateJobWithProtocolHandler( | 
| + const std::string& scheme, | 
| + net::URLRequest* request, | 
| + net::NetworkDelegate* network_delegate) const override { | 
| + std::string path = request->url().path(); | 
| + if (base::StartsWith(path, "/async-revalidate", | 
| + base::CompareCase::SENSITIVE)) { | 
| + return new AsyncRevalidationRequiredURLRequestTestJob(request, | 
| + network_delegate); | 
| + } | 
| + if (base::StartsWith(path, "/redirect", base::CompareCase::SENSITIVE)) { | 
| + return new RedirectAndRevalidateURLRequestTestJob(request, | 
| + network_delegate); | 
| + } | 
| + return new URLRequestTestDelayedCompletionJob( | 
| + request, network_delegate, response_headers_, response_data_); | 
| + } | 
| + | 
| + net::URLRequestJob* MaybeInterceptRedirect( | 
| + net::URLRequest* request, | 
| + net::NetworkDelegate* network_delegate, | 
| + const GURL& location) const override { | 
| + return nullptr; | 
| + } | 
| + | 
| + net::URLRequestJob* MaybeInterceptResponse( | 
| + net::URLRequest* request, | 
| + net::NetworkDelegate* network_delegate) const override { | 
| + return nullptr; | 
| + } | 
| + | 
| + bool IsHandledProtocol(const std::string& scheme) const override { | 
| + // If non-standard schemes need to be tested in future it will be | 
| + // necessary to call ChildProcessSecurityPolicyImpl:: | 
| + // RegisterWebSafeScheme() for them. | 
| + return scheme == url::kHttpScheme || scheme == url::kHttpsScheme; | 
| + } | 
| + | 
| + bool IsHandledURL(const GURL& url) const override { | 
| + return IsHandledProtocol(url.scheme()); | 
| + } | 
| + | 
| + bool IsSafeRedirectTarget(const GURL& location) const override { | 
| + return false; | 
| + } | 
| + | 
| + private: | 
| + std::string response_headers_; | 
| + std::string response_data_; | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(TestURLRequestJobFactory); | 
| +}; | 
| + | 
| +// On Windows, ResourceMsg_SetDataBuffer supplies a HANDLE which is not | 
| +// automatically released. | 
| +// | 
| +// See ResourceDispatcher::ReleaseResourcesInDataMessage. | 
| +// | 
| +// TODO(ricea): Maybe share this implementation with | 
| +// resource_dispatcher_host_unittest.cc. | 
| +void ReleaseHandlesInMessage(const IPC::Message& message) { | 
| + if (message.type() == ResourceMsg_SetDataBuffer::ID) { | 
| + base::PickleIterator iter(message); | 
| + int request_id; | 
| + CHECK(iter.ReadInt(&request_id)); | 
| + base::SharedMemoryHandle shm_handle; | 
| + if (IPC::ParamTraits<base::SharedMemoryHandle>::Read(&message, &iter, | 
| + &shm_handle)) { | 
| + if (base::SharedMemory::IsHandleValid(shm_handle)) | 
| + base::SharedMemory::CloseHandle(shm_handle); | 
| + } | 
| + } | 
| +} | 
| + | 
| +// This filter just deletes any messages that are sent through it. | 
| +class BlackholeFilter : public ResourceMessageFilter { | 
| + public: | 
| + explicit BlackholeFilter(ResourceContext* resource_context) | 
| + : ResourceMessageFilter( | 
| + ChildProcessHostImpl::GenerateChildProcessUniqueId(), | 
| + PROCESS_TYPE_RENDERER, | 
| + nullptr, | 
| + nullptr, | 
| + nullptr, | 
| + nullptr, | 
| + nullptr, | 
| + base::Bind(&BlackholeFilter::GetContexts, base::Unretained(this))), | 
| + resource_context_(resource_context) { | 
| + ChildProcessSecurityPolicyImpl::GetInstance()->Add(child_id()); | 
| + } | 
| + | 
| + bool Send(IPC::Message* msg) override { | 
| + scoped_ptr<IPC::Message> take_ownership(msg); | 
| + ReleaseHandlesInMessage(*msg); | 
| + return true; | 
| + } | 
| + | 
| + private: | 
| + ~BlackholeFilter() override { | 
| + ChildProcessSecurityPolicyImpl::GetInstance()->Remove(child_id()); | 
| + } | 
| + | 
| + void GetContexts(ResourceType resource_type, | 
| + int origin_pid, | 
| + ResourceContext** resource_context, | 
| + net::URLRequestContext** request_context) { | 
| + *resource_context = resource_context_; | 
| + *request_context = resource_context_->GetRequestContext(); | 
| + } | 
| + | 
| + ResourceContext* resource_context_; | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(BlackholeFilter); | 
| +}; | 
| + | 
| +ResourceHostMsg_Request CreateResourceRequest(const char* method, | 
| + ResourceType type, | 
| + const GURL& url) { | 
| + ResourceHostMsg_Request request; | 
| + request.method = std::string(method); | 
| + request.url = url; | 
| + request.first_party_for_cookies = url; // Bypass third-party cookie blocking. | 
| + request.referrer_policy = blink::WebReferrerPolicyDefault; | 
| + request.load_flags = 0; | 
| + request.origin_pid = 0; | 
| + request.resource_type = type; | 
| + request.request_context = 0; | 
| + request.appcache_host_id = kAppCacheNoHostId; | 
| + request.download_to_file = false; | 
| + request.should_reset_appcache = false; | 
| + request.is_main_frame = true; | 
| + request.parent_is_main_frame = false; | 
| + request.parent_render_frame_id = -1; | 
| + request.transition_type = ui::PAGE_TRANSITION_LINK; | 
| + request.allow_download = true; | 
| + return request; | 
| +} | 
| + | 
| +class AsyncRevalidationManagerTest : public ::testing::Test { | 
| + protected: | 
| + AsyncRevalidationManagerTest( | 
| + scoped_ptr<net::TestNetworkDelegate> network_delegate) | 
| + : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), | 
| + network_delegate_(std::move(network_delegate)) { | 
| + browser_context_.reset(new TestBrowserContext()); | 
| + BrowserContext::EnsureResourceContextInitialized(browser_context_.get()); | 
| + base::RunLoop().RunUntilIdle(); | 
| + ResourceContext* resource_context = browser_context_->GetResourceContext(); | 
| + filter_ = new BlackholeFilter(resource_context); | 
| + net::URLRequestContext* request_context = | 
| + resource_context->GetRequestContext(); | 
| + job_factory_.reset(new TestURLRequestJobFactory); | 
| + request_context->set_job_factory(job_factory_.get()); | 
| + request_context->set_network_delegate(network_delegate_.get()); | 
| + host_.EnableStaleWhileRevalidateForTesting(); | 
| + } | 
| + | 
| + AsyncRevalidationManagerTest() | 
| + : AsyncRevalidationManagerTest( | 
| + make_scoped_ptr(new net::TestNetworkDelegate)) {} | 
| + | 
| + void TearDown() override { | 
| + host_.CancelRequestsForProcess(filter_->child_id()); | 
| + host_.Shutdown(); | 
| + host_.CancelRequestsForContext(browser_context_->GetResourceContext()); | 
| + browser_context_.reset(); | 
| + base::RunLoop().RunUntilIdle(); | 
| + } | 
| + | 
| + void SetResponse(const std::string& headers, const std::string& data) { | 
| + job_factory_->SetResponse(headers, data); | 
| + } | 
| + | 
| + // Creates a request using the current test object as the filter and | 
| + // SubResource as the resource type. | 
| + void MakeTestRequest(int render_view_id, int request_id, const GURL& url) { | 
| + ResourceHostMsg_Request request = | 
| + CreateResourceRequest("GET", RESOURCE_TYPE_SUB_RESOURCE, url); | 
| + ResourceHostMsg_RequestResource msg(render_view_id, request_id, request); | 
| + host_.OnMessageReceived(msg, filter_.get()); | 
| + base::RunLoop().RunUntilIdle(); | 
| + } | 
| + | 
| + void EnsureSchemeIsAllowed(const std::string& scheme) { | 
| + ChildProcessSecurityPolicyImpl* policy = | 
| + ChildProcessSecurityPolicyImpl::GetInstance(); | 
| + if (!policy->IsWebSafeScheme(scheme)) | 
| + policy->RegisterWebSafeScheme(scheme); | 
| + } | 
| + | 
| + content::TestBrowserThreadBundle thread_bundle_; | 
| + scoped_ptr<TestBrowserContext> browser_context_; | 
| + scoped_ptr<TestURLRequestJobFactory> job_factory_; | 
| + scoped_refptr<BlackholeFilter> filter_; | 
| + scoped_ptr<net::TestNetworkDelegate> network_delegate_; | 
| + ResourceDispatcherHostImpl host_; | 
| +}; | 
| + | 
| +TEST_F(AsyncRevalidationManagerTest, SupportsAsyncRevalidation) { | 
| + SetResponse(net::URLRequestTestJob::test_headers(), "delay complete"); | 
| + MakeTestRequest(0, 1, GURL("http://example.com/baz")); | 
| + | 
| + net::URLRequest* url_request( | 
| + host_.GetURLRequest(GlobalRequestID(filter_->child_id(), 1))); | 
| + ASSERT_TRUE(url_request); | 
| + | 
| + EXPECT_TRUE(url_request->load_flags() & net::LOAD_SUPPORT_ASYNC_REVALIDATION); | 
| +} | 
| + | 
| +TEST_F(AsyncRevalidationManagerTest, AsyncRevalidationNotSupportedForPOST) { | 
| + SetResponse(net::URLRequestTestJob::test_headers(), "delay complete"); | 
| + // Create POST request. | 
| + ResourceHostMsg_Request request = CreateResourceRequest( | 
| + "POST", RESOURCE_TYPE_SUB_RESOURCE, GURL("http://example.com/baz.php")); | 
| + ResourceHostMsg_RequestResource msg(0, 1, request); | 
| + host_.OnMessageReceived(msg, filter_.get()); | 
| + base::RunLoop().RunUntilIdle(); | 
| + | 
| + net::URLRequest* url_request( | 
| + host_.GetURLRequest(GlobalRequestID(filter_->child_id(), 1))); | 
| + ASSERT_TRUE(url_request); | 
| + | 
| + EXPECT_FALSE(url_request->load_flags() & | 
| + net::LOAD_SUPPORT_ASYNC_REVALIDATION); | 
| +} | 
| + | 
| +TEST_F(AsyncRevalidationManagerTest, | 
| + AsyncRevalidationNotSupportedAfterRedirect) { | 
| + static const char kRedirectHeaders[] = | 
| + "HTTP/1.1 302 MOVED\n" | 
| + "Location: http://example.com/var\n" | 
| + "\n"; | 
| + SetResponse(kRedirectHeaders, ""); | 
| + | 
| + MakeTestRequest(0, 1, GURL("http://example.com/baz")); | 
| + | 
| + net::URLRequest* url_request( | 
| + host_.GetURLRequest(GlobalRequestID(filter_->child_id(), 1))); | 
| + ASSERT_TRUE(url_request); | 
| + | 
| + EXPECT_FALSE(url_request->load_flags() & | 
| + net::LOAD_SUPPORT_ASYNC_REVALIDATION); | 
| +} | 
| + | 
| +// A NetworkDelegate that records the URLRequests as they are created. | 
| +class URLRequestRecordingNetworkDelegate : public net::TestNetworkDelegate { | 
| + public: | 
| + URLRequestRecordingNetworkDelegate() : requests_() {} | 
| + | 
| + net::URLRequest* NextRequest() { | 
| + EXPECT_FALSE(requests_.empty()); | 
| + if (requests_.empty()) | 
| + return nullptr; | 
| + net::URLRequest* request = requests_.front(); | 
| + EXPECT_TRUE(request); | 
| + requests_.pop_front(); | 
| + return request; | 
| + } | 
| + | 
| + bool NextRequestWasDestroyed() { | 
| + net::URLRequest* request = requests_.front(); | 
| + requests_.pop_front(); | 
| + return request == nullptr; | 
| + } | 
| + | 
| + bool IsEmpty() const { return requests_.empty(); } | 
| + | 
| + int OnBeforeURLRequest(net::URLRequest* request, | 
| + const net::CompletionCallback& callback, | 
| + GURL* new_url) override { | 
| + requests_.push_back(request); | 
| + return TestNetworkDelegate::OnBeforeURLRequest(request, callback, new_url); | 
| + } | 
| + | 
| + void OnURLRequestDestroyed(net::URLRequest* request) override { | 
| + for (auto& recorded_request : requests_) { | 
| + if (recorded_request == request) | 
| + recorded_request = nullptr; | 
| + } | 
| + net::TestNetworkDelegate::OnURLRequestDestroyed(request); | 
| + } | 
| + | 
| + private: | 
| + std::deque<net::URLRequest*> requests_; | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(URLRequestRecordingNetworkDelegate); | 
| +}; | 
| + | 
| +class AsyncRevalidationManagerRecordingTest | 
| + : public AsyncRevalidationManagerTest { | 
| + public: | 
| + AsyncRevalidationManagerRecordingTest() | 
| + : AsyncRevalidationManagerTest( | 
| + make_scoped_ptr(new URLRequestRecordingNetworkDelegate)) {} | 
| + | 
| + void TearDown() override { | 
| + EXPECT_TRUE(IsEmpty()); | 
| + AsyncRevalidationManagerTest::TearDown(); | 
| + } | 
| + | 
| + URLRequestRecordingNetworkDelegate* recording_network_delegate() const { | 
| + return static_cast<URLRequestRecordingNetworkDelegate*>( | 
| + network_delegate_.get()); | 
| + } | 
| + | 
| + bool NextRequestWasDestroyed() { | 
| + return recording_network_delegate()->NextRequestWasDestroyed(); | 
| + } | 
| + | 
| + net::URLRequest* NextRequest() { | 
| + return recording_network_delegate()->NextRequest(); | 
| + } | 
| + | 
| + bool IsEmpty() const { return recording_network_delegate()->IsEmpty(); } | 
| +}; | 
| + | 
| +// Verify that an async revalidation is actually created when needed. | 
| +TEST_F(AsyncRevalidationManagerRecordingTest, Issued) { | 
| + // Create the original request. | 
| + MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate")); | 
| + | 
| + net::URLRequest* initial_request = NextRequest(); | 
| + ASSERT_TRUE(initial_request); | 
| + EXPECT_TRUE(initial_request->load_flags() & | 
| + net::LOAD_SUPPORT_ASYNC_REVALIDATION); | 
| + | 
| + net::URLRequest* async_request = NextRequest(); | 
| + ASSERT_TRUE(async_request); | 
| +} | 
| + | 
| +// Verify the the URL of the async revalidation matches the original request. | 
| +TEST_F(AsyncRevalidationManagerRecordingTest, URLMatches) { | 
| + // Create the original request. | 
| + MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate/u")); | 
| + | 
| + // Discard the original request. | 
| + NextRequest(); | 
| + | 
| + net::URLRequest* async_request = NextRequest(); | 
| + ASSERT_TRUE(async_request); | 
| + EXPECT_EQ(GURL("http://example.com/async-revalidate/u"), | 
| + async_request->url()); | 
| +} | 
| + | 
| +TEST_F(AsyncRevalidationManagerRecordingTest, | 
| + AsyncRevalidationsDoNotSupportAsyncRevalidation) { | 
| + // Create the original request. | 
| + MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate")); | 
| + | 
| + // Discard the original request. | 
| + NextRequest(); | 
| + | 
| + // Get the async revalidation request. | 
| + net::URLRequest* async_request = NextRequest(); | 
| + ASSERT_TRUE(async_request); | 
| + EXPECT_FALSE(async_request->load_flags() & | 
| + net::LOAD_SUPPORT_ASYNC_REVALIDATION); | 
| +} | 
| + | 
| +TEST_F(AsyncRevalidationManagerRecordingTest, AsyncRevalidationsNotDuplicated) { | 
| + // Create the original request. | 
| + MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate")); | 
| + | 
| + // Discard the original request. | 
| + NextRequest(); | 
| + | 
| + // Get the async revalidation request. | 
| + net::URLRequest* async_request = NextRequest(); | 
| + EXPECT_TRUE(async_request); | 
| + | 
| + // Start a second request to the same URL. | 
| + MakeTestRequest(0, 2, GURL("http://example.com/async-revalidate")); | 
| + | 
| + // Discard the second request. | 
| + NextRequest(); | 
| + | 
| + // There should not be another async revalidation request. | 
| + EXPECT_TRUE(IsEmpty()); | 
| +} | 
| + | 
| +// Async revalidation to different URLs should not be treated as duplicates. | 
| +TEST_F(AsyncRevalidationManagerRecordingTest, | 
| + AsyncRevalidationsToSeparateURLsAreSeparate) { | 
| + // Create two requests to two URLs. | 
| + MakeTestRequest(0, 1, GURL("http://example.com/async-revalidate")); | 
| + MakeTestRequest(0, 2, GURL("http://example.com/async-revalidate2")); | 
| + | 
| + net::URLRequest* initial_request = NextRequest(); | 
| + ASSERT_TRUE(initial_request); | 
| + net::URLRequest* initial_async_revalidation = NextRequest(); | 
| + ASSERT_TRUE(initial_async_revalidation); | 
| + net::URLRequest* second_request = NextRequest(); | 
| + ASSERT_TRUE(second_request); | 
| + net::URLRequest* second_async_revalidation = NextRequest(); | 
| + ASSERT_TRUE(second_async_revalidation); | 
| + | 
| + EXPECT_EQ("http://example.com/async-revalidate", | 
| + initial_request->url().spec()); | 
| + EXPECT_EQ("http://example.com/async-revalidate", | 
| + initial_async_revalidation->url().spec()); | 
| + EXPECT_EQ("http://example.com/async-revalidate2", | 
| + second_request->url().spec()); | 
| + EXPECT_EQ("http://example.com/async-revalidate2", | 
| + second_async_revalidation->url().spec()); | 
| +} | 
| + | 
| +// A stale-while-revalidate applicable redirect response should not result in an | 
| +// async revalidation. | 
| +TEST_F(AsyncRevalidationManagerRecordingTest, InitialRedirectLegRevalidated) { | 
| + MakeTestRequest(0, 1, GURL("http://example.com/redirect")); | 
| + | 
| + net::URLRequest* initial_request = NextRequest(); | 
| + EXPECT_TRUE(initial_request); | 
| + | 
| + // There should be an async revalidation request with no body, so it has | 
| + // already completed. | 
| + ASSERT_TRUE(NextRequestWasDestroyed()); | 
| +} | 
| + | 
| +// Nothing after the first redirect leg has stale-while-revalidate applied. | 
| +// TODO(ricea): s-w-r should work with redirects. Change this test when it does. | 
| +TEST_F(AsyncRevalidationManagerRecordingTest, NoSWRAfterFirstRedirectLeg) { | 
| 
davidben
2015/12/08 21:35:48
It looks like this test tests strictly more things
 
Adam Rice
2015/12/09 06:44:43
Yes, the one above looks redundant now. I removed
 | 
| + MakeTestRequest(0, 1, GURL("http://example.com/redirect")); | 
| + | 
| + net::URLRequest* initial_request = NextRequest(); | 
| + EXPECT_TRUE(initial_request); | 
| + | 
| + EXPECT_FALSE(initial_request->load_flags() & | 
| + net::LOAD_SUPPORT_ASYNC_REVALIDATION); | 
| + | 
| + // An async revalidation happens for the redirect. It has no body, so it has | 
| + // already completed. | 
| + EXPECT_TRUE(NextRequestWasDestroyed()); | 
| + | 
| + // But no others. | 
| + EXPECT_TRUE(IsEmpty()); | 
| +} | 
| + | 
| +} // namespace | 
| + | 
| +} // namespace content |