Index: content/browser/loader/resource_dispatcher_host_impl.cc |
diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc |
index 6cc7f6196f95a3832224a5332cb20df0b113cf6c..eb63c60683f30108eeab45ba851d6238672c52d8 100644 |
--- a/content/browser/loader/resource_dispatcher_host_impl.cc |
+++ b/content/browser/loader/resource_dispatcher_host_impl.cc |
@@ -8,6 +8,7 @@ |
#include <algorithm> |
#include <set> |
+#include <utility> |
#include <vector> |
#include "base/bind.h" |
@@ -19,6 +20,7 @@ |
#include "base/memory/scoped_ptr.h" |
#include "base/memory/shared_memory.h" |
#include "base/message_loop/message_loop.h" |
+#include "base/metrics/field_trial.h" |
#include "base/metrics/histogram_macros.h" |
#include "base/metrics/sparse_histogram.h" |
#include "base/profiler/scoped_tracker.h" |
@@ -91,6 +93,8 @@ |
#include "net/cookies/cookie_monster.h" |
#include "net/http/http_response_headers.h" |
#include "net/http/http_response_info.h" |
+#include "net/http/http_transaction_factory.h" |
+#include "net/http/http_util.h" |
#include "net/ssl/ssl_cert_request_info.h" |
#include "net/url_request/url_request.h" |
#include "net/url_request/url_request_context.h" |
@@ -435,6 +439,23 @@ void LogResourceRequestTimeOnUI( |
} |
} |
+bool QualifiesForAsyncRevalidation(const ResourceHostMsg_Request& request) { |
+ if (request.load_flags & |
+ (net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | |
+ net::LOAD_VALIDATE_CACHE | net::LOAD_PREFERRING_CACHE | |
+ net::LOAD_ONLY_FROM_CACHE)) { |
mmenke
2015/10/09 16:02:22
LOAD_IGNORE_LIMITS is also kinda weird. You eithe
Adam Rice
2015/10/13 22:53:17
LOAD_IGNORE_LIMITS is never set by Blink, but I ad
|
+ return false; |
+ } |
+ if (request.method != "GET") |
+ return false; |
+ // A GET request should not have a body, but don't leave it to chance. |
+ if (request.request_body.get()) |
+ return false; |
+ if (!request.url.SchemeIsHTTPOrHTTPS()) |
+ return false; |
+ return true; |
+} |
+ |
} // namespace |
// static |
@@ -448,14 +469,14 @@ ResourceDispatcherHostImpl::ResourceDispatcherHostImpl() |
is_shutdown_(false), |
num_in_flight_requests_(0), |
max_num_in_flight_requests_(base::SharedMemory::GetHandleLimit()), |
- max_num_in_flight_requests_per_process_( |
- static_cast<int>( |
- max_num_in_flight_requests_ * kMaxRequestsPerProcessRatio)), |
+ max_num_in_flight_requests_per_process_(static_cast<int>( |
+ max_num_in_flight_requests_ * kMaxRequestsPerProcessRatio)), |
max_outstanding_requests_cost_per_process_( |
kMaxOutstandingRequestsCostPerProcess), |
filter_(NULL), |
delegate_(NULL), |
- allow_cross_origin_auth_prompt_(false) { |
+ allow_cross_origin_auth_prompt_(false), |
+ async_revalidation_enabled_(false) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
DCHECK(!g_resource_dispatcher_host); |
g_resource_dispatcher_host = this; |
@@ -473,10 +494,19 @@ ResourceDispatcherHostImpl::ResourceDispatcherHostImpl() |
update_load_states_timer_.reset( |
new base::RepeatingTimer<ResourceDispatcherHostImpl>()); |
+ |
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
+ if (base::FieldTrialList::FindFullName("StaleWhileRevalidate") == "Enabled" || |
+ command_line->HasSwitch(switches::kEnableStaleWhileRevalidate)) { |
+ async_revalidation_enabled_ = true; |
+ } |
} |
ResourceDispatcherHostImpl::~ResourceDispatcherHostImpl() { |
DCHECK(outstanding_requests_stats_map_.empty()); |
+ // It's not safe to destroy this object while AsyncRevalidationDriver objects |
+ // have callbacks referencing it. |
+ CHECK(in_progress_async_revalidations_.empty()); |
DCHECK(g_resource_dispatcher_host); |
g_resource_dispatcher_host = NULL; |
} |
@@ -578,6 +608,10 @@ void ResourceDispatcherHostImpl::CancelRequestsForContext( |
loaders_to_cancel.clear(); |
+ // Cancelling async revalidations should not result in the creation of new |
+ // requests, but do it before the checks just to be on the safe side. |
+ CancelAsyncRevalidationsForResourceContext(context); |
+ |
// Validate that no more requests for this context were added. |
for (LoaderMap::const_iterator i = pending_loaders_.begin(); |
i != pending_loaders_.end(); ++i) { |
@@ -776,6 +810,14 @@ scoped_ptr<ResourceHandler> ResourceDispatcherHostImpl::MaybeInterceptAsStream( |
return handler.Pass(); |
} |
+ResourceDispatcherHostImpl::ExtraRequestParams::ExtraRequestParams() |
+ : blob_context(nullptr), |
+ child_id(0), |
+ is_sync_load(false), |
+ use_embedded_identity(false), |
+ support_async_revalidation(false), |
+ extra_headers(nullptr) {} |
+ |
ResourceDispatcherHostLoginDelegate* |
ResourceDispatcherHostImpl::CreateLoginDelegate( |
ResourceLoader* loader, |
@@ -824,6 +866,14 @@ void ResourceDispatcherHostImpl::DidReceiveRedirect(ResourceLoader* loader, |
if (!info->GetAssociatedRenderFrame(&render_process_id, &render_frame_host)) |
return; |
+ // Remove the LOAD_SUPPORT_ASYNC_REVALIDATION flag if it is present. |
+ net::URLRequest* request = loader->request(); |
+ if (request->load_flags() & net::LOAD_SUPPORT_ASYNC_REVALIDATION) { |
+ int new_load_flags = request->load_flags() & |
+ ~net::LOAD_SUPPORT_ASYNC_REVALIDATION; |
+ request->SetLoadFlags(new_load_flags); |
davidben
2015/10/08 21:57:51
This isn't sufficient. The first redirect leg alre
Adam Rice
2015/10/13 22:53:17
I think will just modify net::HttpCache::Transacti
|
+ } |
+ |
// Notify the observers on the UI thread. |
scoped_ptr<ResourceRedirectDetails> detail(new ResourceRedirectDetails( |
loader->request(), |
@@ -846,6 +896,9 @@ void ResourceDispatcherHostImpl::DidReceiveResponse(ResourceLoader* loader) { |
info->GetChildID(), info->GetRouteID()); |
} |
+ if (request->response_info().async_revalidation_required) |
+ BeginAsyncRevalidation(request); |
mmenke
2015/10/09 16:02:22
We shouldn't call this on failures, right? Partic
mmenke
2015/10/09 16:29:57
David pointed out to me that we don't reach this p
|
+ |
int render_process_id, render_frame_host; |
if (!info->GetAssociatedRenderFrame(&render_process_id, &render_frame_host)) |
return; |
@@ -1210,55 +1263,13 @@ void ResourceDispatcherHostImpl::BeginRequest( |
return; |
} |
- // Construct the request. |
- scoped_ptr<net::URLRequest> new_request = request_context->CreateRequest( |
- request_data.url, request_data.priority, NULL); |
- |
- new_request->set_method(request_data.method); |
- new_request->set_first_party_for_cookies( |
- request_data.first_party_for_cookies); |
- |
- // If the request is a MAIN_FRAME request, the first-party URL gets updated on |
- // redirects. |
- if (request_data.resource_type == RESOURCE_TYPE_MAIN_FRAME) { |
- new_request->set_first_party_url_policy( |
- net::URLRequest::UPDATE_FIRST_PARTY_URL_ON_REDIRECT); |
- } |
- |
- const Referrer referrer(request_data.referrer, request_data.referrer_policy); |
- SetReferrerForRequest(new_request.get(), referrer); |
- |
- new_request->SetExtraRequestHeaders(headers); |
- |
- storage::BlobStorageContext* blob_context = |
- GetBlobStorageContext(filter_->blob_storage_context()); |
- // Resolve elements from request_body and prepare upload data. |
- if (request_data.request_body.get()) { |
- // |blob_context| could be null when the request is from the plugins because |
- // ResourceMessageFilters created in PluginProcessHost don't have the blob |
- // context. |
- if (blob_context) { |
- // Attaches the BlobDataHandles to request_body not to free the blobs and |
- // any attached shareable files until upload completion. These data will |
- // be used in UploadDataStream and ServiceWorkerURLRequestJob. |
- AttachRequestBodyBlobDataHandles( |
- request_data.request_body.get(), |
- blob_context); |
- } |
- new_request->set_upload(UploadDataStreamBuilder::Build( |
- request_data.request_body.get(), |
- blob_context, |
- filter_->file_system_context(), |
- BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE) |
- .get())); |
- } |
- |
- bool allow_download = request_data.allow_download && |
- IsResourceTypeFrame(request_data.resource_type); |
+ ExtraRequestParams params; |
+ params.blob_context = GetBlobStorageContext(filter_->blob_storage_context()); |
+ params.child_id = child_id; |
+ params.is_sync_load = sync_result != nullptr; |
+ params.use_embedded_identity = true; |
+ params.extra_headers = &headers; |
bool do_not_prompt_for_login = request_data.do_not_prompt_for_login; |
- bool is_sync_load = sync_result != NULL; |
- int load_flags = |
- BuildLoadFlagsForRequest(request_data, child_id, is_sync_load); |
if (request_data.resource_type == RESOURCE_TYPE_PREFETCH || |
request_data.resource_type == RESOURCE_TYPE_FAVICON) { |
do_not_prompt_for_login = true; |
@@ -1276,18 +1287,19 @@ void ResourceDispatcherHostImpl::BeginRequest( |
// filter sources for <script>, <link>, <embed>, <object>, <iframe> tags |
// would be considered vulnerable in and of itself. |
do_not_prompt_for_login = true; |
- load_flags |= net::LOAD_DO_NOT_USE_EMBEDDED_IDENTITY; |
+ params.use_embedded_identity = false; |
} |
- // Sync loads should have maximum priority and should be the only |
- // requets that have the ignore limits flag set. |
- if (is_sync_load) { |
- DCHECK_EQ(request_data.priority, net::MAXIMUM_PRIORITY); |
- DCHECK_NE(load_flags & net::LOAD_IGNORE_LIMITS, 0); |
- } else { |
- DCHECK_EQ(load_flags & net::LOAD_IGNORE_LIMITS, 0); |
- } |
- new_request->SetLoadFlags(load_flags); |
+ // Check if request is eligible for async revalidation. |
+ params.support_async_revalidation = |
+ (!params.is_sync_load && async_revalidation_enabled_ && |
+ QualifiesForAsyncRevalidation(request_data)); |
mmenke
2015/10/08 21:58:26
We shouldn't do async revalidations when "predicti
Adam Rice
2015/10/13 22:53:17
I don't agree that stale-while-revalidate is a pre
mmenke
2015/10/13 23:01:53
You are guessing that it will be used in the futur
mmenke
2015/10/14 00:16:49
Worth noting it also potentially drains battery by
Adam Rice
2015/10/14 15:03:45
The feature is not speculative. We are not making
|
+ |
+ scoped_ptr<net::URLRequest> new_request = |
+ ConstructRequest(request_context, request_data, params); |
+ |
+ bool allow_download = request_data.allow_download && |
+ IsResourceTypeFrame(request_data.resource_type); |
// Make extra info and read footer (contains request ID). |
ResourceRequestInfoImpl* extra_info = new ResourceRequestInfoImpl( |
@@ -1312,7 +1324,8 @@ void ResourceDispatcherHostImpl::BeginRequest( |
request_data.referrer_policy, |
request_data.visiblity_state, |
resource_context, filter_->GetWeakPtr(), |
- !is_sync_load); |
+ !params.is_sync_load, |
+ params.support_async_revalidation ? &request_data : NULL); |
// Request takes ownership. |
extra_info->AssociateWithRequest(new_request.get()); |
@@ -1328,9 +1341,9 @@ void ResourceDispatcherHostImpl::BeginRequest( |
// Initialize the service worker handler for the request. We don't use |
// ServiceWorker for synchronous loads to avoid renderer deadlocks. |
ServiceWorkerRequestHandler::InitializeHandler( |
- new_request.get(), filter_->service_worker_context(), blob_context, |
+ new_request.get(), filter_->service_worker_context(), params.blob_context, |
child_id, request_data.service_worker_provider_id, |
- request_data.skip_service_worker || is_sync_load, |
+ request_data.skip_service_worker || params.is_sync_load, |
request_data.fetch_request_mode, request_data.fetch_credentials_mode, |
request_data.fetch_redirect_mode, request_data.resource_type, |
request_data.fetch_request_context_type, request_data.fetch_frame_type, |
@@ -1342,16 +1355,79 @@ void ResourceDispatcherHostImpl::BeginRequest( |
request_data.appcache_host_id, request_data.resource_type, |
request_data.should_reset_appcache); |
- scoped_ptr<ResourceHandler> handler( |
- CreateResourceHandler( |
- new_request.get(), |
- request_data, sync_result, route_id, process_type, child_id, |
- resource_context)); |
+ scoped_ptr<ResourceHandler> handler(CreateResourceHandler( |
+ new_request.get(), request_data, sync_result, route_id, process_type, |
+ child_id, resource_context)); |
if (handler) |
BeginRequestInternal(new_request.Pass(), handler.Pass()); |
} |
+scoped_ptr<net::URLRequest> ResourceDispatcherHostImpl::ConstructRequest( |
+ net::URLRequestContext* request_context, |
+ const ResourceHostMsg_Request& request_data, |
+ const ExtraRequestParams& extra_params) { |
+ // Construct the request. |
+ scoped_ptr<net::URLRequest> new_request = request_context->CreateRequest( |
+ request_data.url, request_data.priority, NULL); |
+ |
+ new_request->set_method(request_data.method); |
+ new_request->set_first_party_for_cookies( |
+ request_data.first_party_for_cookies); |
+ |
+ // If the request is a MAIN_FRAME request, the first-party URL gets updated on |
+ // redirects. |
+ if (request_data.resource_type == RESOURCE_TYPE_MAIN_FRAME) { |
+ new_request->set_first_party_url_policy( |
+ net::URLRequest::UPDATE_FIRST_PARTY_URL_ON_REDIRECT); |
+ } |
+ |
+ const Referrer referrer(request_data.referrer, request_data.referrer_policy); |
+ SetReferrerForRequest(new_request.get(), referrer); |
+ |
+ new_request->SetExtraRequestHeaders(*extra_params.extra_headers); |
+ |
+ // Resolve elements from request_body and prepare upload data. |
+ if (request_data.request_body.get()) { |
+ // |blob_context| could be null when the request is from the plugins because |
+ // ResourceMessageFilters created in PluginProcessHost don't have the blob |
+ // context. |
+ if (extra_params.blob_context) { |
+ // Attaches the BlobDataHandles to request_body not to free the blobs and |
+ // any attached shareable files until upload completion. These data will |
+ // be used in UploadDataStream and ServiceWorkerURLRequestJob. |
+ AttachRequestBodyBlobDataHandles(request_data.request_body.get(), |
+ extra_params.blob_context); |
+ } |
+ new_request->set_upload(UploadDataStreamBuilder::Build( |
+ request_data.request_body.get(), extra_params.blob_context, |
+ filter_->file_system_context(), |
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE) |
+ .get())); |
+ } |
+ |
+ int load_flags = BuildLoadFlagsForRequest(request_data, extra_params.child_id, |
+ extra_params.is_sync_load); |
+ |
+ if (!extra_params.use_embedded_identity) |
+ load_flags |= net::LOAD_DO_NOT_USE_EMBEDDED_IDENTITY; |
+ |
+ if (extra_params.support_async_revalidation) |
+ load_flags |= net::LOAD_SUPPORT_ASYNC_REVALIDATION; |
+ |
+ // Sync loads should have maximum priority and should be the only |
+ // requests that have the ignore limits flag set. |
+ if (extra_params.is_sync_load) { |
+ DCHECK_EQ(request_data.priority, net::MAXIMUM_PRIORITY); |
+ DCHECK_NE(load_flags & net::LOAD_IGNORE_LIMITS, 0); |
+ } else { |
+ DCHECK_EQ(load_flags & net::LOAD_IGNORE_LIMITS, 0); |
+ } |
+ new_request->SetLoadFlags(load_flags); |
+ |
+ return new_request.Pass(); |
+} |
+ |
scoped_ptr<ResourceHandler> ResourceDispatcherHostImpl::CreateResourceHandler( |
net::URLRequest* request, |
const ResourceHostMsg_Request& request_data, |
@@ -1583,7 +1659,8 @@ ResourceRequestInfoImpl* ResourceDispatcherHostImpl::CreateRequestInfo( |
blink::WebPageVisibilityStateVisible, |
context, |
base::WeakPtr<ResourceMessageFilter>(), // filter |
- true); // is_async |
+ true, // is_async |
+ NULL); // original_request |
davidben
2015/10/08 21:57:52
nullptr
Adam Rice
2015/10/13 22:53:17
Done.
|
} |
void ResourceDispatcherHostImpl::OnRenderViewHostCreated(int child_id, |
@@ -2026,7 +2103,8 @@ void ResourceDispatcherHostImpl::BeginNavigationRequest( |
blink::WebPageVisibilityStateVisible, |
resource_context, |
base::WeakPtr<ResourceMessageFilter>(), // filter |
- true); |
+ true, // is_async |
+ NULL); // original_message |
davidben
2015/10/08 21:57:51
original_request
davidben
2015/10/08 21:57:52
nullptr
Adam Rice
2015/10/13 22:53:17
Done.
Adam Rice
2015/10/13 22:53:17
Done.
|
// Request takes ownership. |
extra_info->AssociateWithRequest(new_request.get()); |
@@ -2053,8 +2131,8 @@ void ResourceDispatcherHostImpl::BeginNavigationRequest( |
handler = AddStandardHandlers(new_request.get(), resource_type, |
resource_context, |
nullptr, // appcache_service |
- -1, // child_id |
- -1, // route_id |
+ -1, // child_id |
+ -1, // route_id |
handler.Pass()); |
BeginRequestInternal(new_request.Pass(), handler.Pass()); |
@@ -2392,4 +2470,104 @@ int ResourceDispatcherHostImpl::BuildLoadFlagsForRequest( |
return load_flags; |
} |
+void ResourceDispatcherHostImpl::BeginAsyncRevalidation( |
+ net::URLRequest* for_request) { |
+ ResourceRequestInfoImpl* info = |
+ ResourceRequestInfoImpl::ForRequest(for_request); |
+ DCHECK(info); |
+ ResourceHostMsg_Request* original_request_data = info->original_request(); |
+ DCHECK(original_request_data); |
+ DCHECK(!original_request_data->request_body); |
+ |
+ ResourceHostMsg_Request new_request_data = *original_request_data; |
+ new_request_data.do_not_prompt_for_login = true; |
+ new_request_data.allow_download = false; |
+ new_request_data.priority = net::MINIMUM_PRIORITY; |
+ |
+ int child_id = info->GetChildID(); |
+ int route_id = info->GetRouteID(); |
+ |
+ ResourceContext* resource_context = NULL; |
+ net::URLRequestContext* request_context = NULL; |
+ // This |request_context| needs to be valid until |
+ // RemoveResourceContext(resource_context) is called. This is currently |
+ // correct, see |
+ // https://groups.google.com/a/chromium.org/d/msg/net-dev/lHh764ZFdr0/phJIWXokt7cJ |
davidben
2015/10/08 21:57:51
I must have missed the context when you made the o
Adam Rice
2015/10/13 22:53:17
If the child is already gone at this point, I thin
|
+ info->filter()->GetContexts(new_request_data, &resource_context, |
+ &request_context); |
+ CHECK(ContainsKey(active_resource_contexts_, resource_context)); |
+ if (is_shutdown_) |
+ return; |
mmenke
2015/10/09 16:02:22
Can this happen while shutting down? Also, why is
Adam Rice
2015/10/13 22:53:17
1. Based on other uses in this file: probably.
2.
|
+ |
+ AsyncRevalidationKey async_revalidation_key( |
+ resource_context, request_context->http_transaction_factory()->GetCache(), |
+ original_request_data->url); |
+ std::pair<AsyncRevalidationMap::iterator, bool> insert_result = |
+ in_progress_async_revalidations_.insert( |
+ AsyncRevalidationMap::value_type(async_revalidation_key, nullptr)); |
+ if (!insert_result.second) { |
+ // A matching async revalidation is already in progress for this cache; we |
+ // don't need another one. |
+ return; |
+ } |
+ |
+ net::HttpRequestHeaders headers; |
+ headers.AddHeadersFromString(new_request_data.headers); |
davidben
2015/10/08 21:57:51
If the request did not change, it really needs to
Adam Rice
2015/10/13 22:53:17
The cache will conditionalize the request if If-Mo
|
+ |
+ ExtraRequestParams params; |
+ params.child_id = child_id; |
davidben
2015/10/08 21:57:52
This request cannot be associated with this proces
Adam Rice
2015/10/13 22:53:17
This is only used by BuildLoadFlagsForRequest to d
|
+ params.is_sync_load = false; |
+ params.use_embedded_identity = true; |
+ params.support_async_revalidation = false; |
+ params.extra_headers = &headers; |
+ if (new_request_data.resource_type == RESOURCE_TYPE_IMAGE && |
+ HTTP_AUTH_RELATION_BLOCKED_CROSS == |
+ HttpAuthRelationTypeOf(new_request_data.url, |
+ new_request_data.first_party_for_cookies)) { |
davidben
2015/10/08 21:57:52
We should avoid duplicating this code. Ideally by
Adam Rice
2015/10/13 22:53:17
I would rather do the experiment first before worr
|
+ // Prevent <img> tags from using cached credentials to third-party sites |
+ // to perform actions on those sites. |
+ params.use_embedded_identity = false; |
+ } |
+ |
+ scoped_ptr<net::URLRequest> new_request = |
+ ConstructRequest(request_context, new_request_data, params); |
+ |
+ scoped_ptr<ResourceThrottle> throttle = scheduler_->ScheduleRequest( |
+ child_id, route_id, false, new_request.get()); |
davidben
2015/10/08 21:57:51
Associating the request with child_id / route_id d
Adam Rice
2015/10/13 22:53:17
The async request has to be scheduled after the ot
|
+ // This use of base::Unretained() is safe because the AsyncRevalidatonDriver |
+ // object will be destroyed before this object is. |
+ insert_result.first->second = new AsyncRevalidationDriver( |
+ new_request.Pass(), throttle.Pass(), |
+ base::Bind(&ResourceDispatcherHostImpl::OnAsyncRevalidationComplete, |
+ base::Unretained(this), async_revalidation_key)); |
mmenke
2015/10/09 16:02:22
Not creating a ResourceRequestInfo for the request
Adam Rice
2015/10/13 22:53:17
Sorry, I thought we had agreed that the request sh
mmenke
2015/10/13 23:01:53
I'm not saying we should have the object, but that
|
+ insert_result.first->second->StartRequest(); |
+} |
+ |
+void ResourceDispatcherHostImpl::OnAsyncRevalidationComplete( |
+ const AsyncRevalidationKey& key) { |
+ // TODO(ricea): Record histograms? |
+ auto it = in_progress_async_revalidations_.find(key); |
+ // This is CHECK() and not DCHECK() because deferencing an off-the-end |
+ // iterator would cause a security hole. |
+ CHECK(it != in_progress_async_revalidations_.end()); |
davidben
2015/10/08 21:57:52
I don't think we do CHECK vs DCHECK this way. Othe
Adam Rice
2015/10/13 22:53:17
My understanding is that the security people would
|
+ delete it->second, it->second = nullptr; |
davidben
2015/10/08 21:57:51
Style: Don't use the comma operator.
Adam Rice
2015/10/13 22:53:17
Done.
|
+ in_progress_async_revalidations_.erase(it); |
+} |
+ |
+void ResourceDispatcherHostImpl::CancelAsyncRevalidationsForResourceContext( |
+ ResourceContext* resource_context) { |
+ // For this algorithm to work, elements using |resource_context| must be |
+ // contiguous in the map (ie. it must form the first part of the key). |
+ AsyncRevalidationKey partial_key(resource_context); |
+ auto next_it = in_progress_async_revalidations_.lower_bound(partial_key); |
+ while (next_it != in_progress_async_revalidations_.end() && |
+ next_it->first.resource_context == resource_context) { |
+ // Erasing the element invalidates the iterator. Increment it first. |
+ auto current_it = next_it++; |
+ current_it->second->CancelRequest(); |
+ delete current_it->second, current_it->second = nullptr; |
davidben
2015/10/08 21:57:52
Style: Don't use the comma operator.
Adam Rice
2015/10/13 22:53:17
Done.
|
+ in_progress_async_revalidations_.erase(current_it); |
+ } |
+} |
+ |
} // namespace content |