Index: ios/web/net/request_tracker_impl.mm |
diff --git a/ios/web/net/request_tracker_impl.mm b/ios/web/net/request_tracker_impl.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f2747715862ffef1527304023cb1000d5f26e366 |
--- /dev/null |
+++ b/ios/web/net/request_tracker_impl.mm |
@@ -0,0 +1,1308 @@ |
+// Copyright 2014 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 "ios/web/net/request_tracker_impl.h" |
+ |
+#include <pthread.h> |
+ |
+#include "base/containers/hash_tables.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#include "base/mac/bind_objc_block.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/strings/string_util.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/synchronization/lock.h" |
+#import "ios/net/clients/crn_forwarding_network_client.h" |
+#import "ios/net/clients/crn_forwarding_network_client_factory.h" |
+#import "ios/web/crw_network_activity_indicator_manager.h" |
+#import "ios/web/history_state_util.h" |
+#import "ios/web/net/crw_request_tracker_delegate.h" |
+#include "ios/web/public/browser_state.h" |
+#include "ios/web/public/cert_store.h" |
+#include "ios/web/public/certificate_policy_cache.h" |
+#include "ios/web/public/ssl_status.h" |
+#include "ios/web/public/url_util.h" |
+#include "ios/web/public/web_thread.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "net/base/net_errors.h" |
+#include "net/http/http_response_headers.h" |
+#include "net/url_request/url_request.h" |
+ |
+namespace { |
+ |
+struct EqualNSStrings { |
+ bool operator()(const base::scoped_nsobject<NSString>& s1, |
+ const base::scoped_nsobject<NSString>& s2) const { |
+ // Use a ternary due to the BOOL vs bool type difference. |
+ return [s1 isEqualToString:s2] ? true : false; |
+ } |
+}; |
+ |
+struct HashNSString { |
+ size_t operator()(const base::scoped_nsobject<NSString>& s) const { |
+ return [s hash]; |
+ } |
+}; |
+ |
+// A map of all RequestTrackerImpls for tabs that are: |
+// * Currently open |
+// * Recently closed waiting for all their network operations to finish. |
+// The code accesses this variable from two threads: the consumer is expected to |
+// always access it from the main thread, the provider is accessing it from the |
+// WebThread, a thread created by the UIWebView/CFURL. For this reason access to |
+// this variable must always gated by |g_trackers_lock|. |
+typedef base::hash_map<base::scoped_nsobject<NSString>, |
+ web::RequestTrackerImpl*, |
+ HashNSString, EqualNSStrings> TrackerMap; |
+ |
+TrackerMap* g_trackers = NULL; |
+base::Lock* g_trackers_lock = NULL; |
+pthread_once_t g_once_control = PTHREAD_ONCE_INIT; |
+ |
+// Flag, lock, and function to implement BlockUntilTrackersShutdown(). |
+// |g_waiting_on_io_thread| is guarded by |g_waiting_on_io_thread_lock|; |
+// it is set to true when the shutdown wait starts, then a call to |
+// StopIOThreadWaiting is posted to the IO thread (enqueued after any pending |
+// request terminations) while the posting method loops over a check on the |
+// |g_waiting_on_io_thread|. |
+static bool g_waiting_on_io_thread = false; |
+base::Lock* g_waiting_on_io_thread_lock = NULL; |
+void StopIOThreadWaiting() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ base::AutoLock scoped_lock(*g_waiting_on_io_thread_lock); |
+ g_waiting_on_io_thread = false; |
+} |
+ |
+// Initialize global state. Calls to this should be conditional on |
+// |g_once_control| (that is, this should only be called once, across all |
+// threads). |
+void InitializeGlobals() { |
+ g_trackers = new TrackerMap; |
+ g_trackers_lock = new base::Lock; |
+ g_waiting_on_io_thread_lock = new base::Lock; |
+} |
+ |
+// Each request tracker get a unique increasing number, used anywhere an |
+// identifier is needed for tracker (e.g. storing certs). |
+int g_next_request_tracker_id = 0; |
+ |
+// IsIntranetHost logic and its associated kDot constant are lifted directly |
+// from content/browser/ssl/ssl_policy.cc. Unfortunately that particular file |
+// has way too many dependencies on content to be used on iOS. |
+static const char kDot = '.'; |
+ |
+static bool IsIntranetHost(const std::string& host) { |
+ const size_t dot = host.find(kDot); |
+ return dot == std::string::npos || dot == host.length() - 1; |
+} |
+ |
+// Add |tracker| to |g_trackers| under |key|. |
+static void RegisterTracker(web::RequestTrackerImpl* tracker, NSString* key) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ pthread_once(&g_once_control, &InitializeGlobals); |
+ { |
+ base::scoped_nsobject<NSString> scoped_key([key copy]); |
+ base::AutoLock scoped_lock(*g_trackers_lock); |
+ DCHECK(!g_trackers->count(scoped_key)); |
+ (*g_trackers)[scoped_key] = tracker; |
+ } |
+} |
+ |
+// Empty callback. |
+void DoNothing(bool flag) {} |
+ |
+} // namespace |
+ |
+// The structure used to gather the information about the resources loaded. |
+struct TrackerCounts { |
+ public: |
+ TrackerCounts(const GURL& tracked_url, const net::URLRequest* tracked_request) |
+ : url(tracked_url), |
+ first_party_for_cookies_origin( |
+ tracked_request->first_party_for_cookies().GetOrigin()), |
+ request(tracked_request), |
+ ssl_info(net::SSLInfo()), |
+ ssl_judgment(web::CertPolicy::ALLOWED), |
+ allowed_by_user(false), |
+ expected_length(0), |
+ processed(0), |
+ done(false) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ is_subrequest = tracked_request->first_party_for_cookies().is_valid() && |
+ tracked_request->url() != tracked_request->first_party_for_cookies(); |
+ }; |
+ |
+ // The resource url. |
+ const GURL url; |
+ // The origin of the url of the top level document of the resource. This is |
+ // used to ignore request coming from an old document when detecting mixed |
+ // content. |
+ const GURL first_party_for_cookies_origin; |
+ // The request associated with this struct. As a void* to prevent access from |
+ // the wrong thread. |
+ const void* request; |
+ // SSLInfo for the request. |
+ net::SSLInfo ssl_info; |
+ // Is the SSL request blocked waiting for user choice. |
+ web::CertPolicy::Judgment ssl_judgment; |
+ // True if |ssl_judgment| is ALLOWED as the result of a user choice. |
+ bool allowed_by_user; |
+ // block to call to cancel or authorize a blocked request. |
+ net::RequestTracker::SSLCallback ssl_callback; |
+ // If known, the expected length of the resource in bytes. |
+ uint64_t expected_length; |
+ // Number of bytes loaded so far. |
+ uint64_t processed; |
+ // Set to true is the resource is fully loaded. |
+ bool done; |
+ // Set to true if the request has a main request set. |
+ bool is_subrequest; |
+ |
+ NSString* Description() { |
+ NSString* spec = base::SysUTF8ToNSString(url.spec()); |
+ NSString* status = nil; |
+ if (done) { |
+ status = [NSString stringWithFormat:@"\t-- Done -- (%04qu) bytes", |
+ processed]; |
+ } else if (!expected_length) { |
+ status = [NSString stringWithFormat:@"\t>> Loading (%04qu) bytes", |
+ processed]; |
+ } else { |
+ status = [NSString stringWithFormat:@"\t>> Loading (%04qu/%04qu)", |
+ processed, expected_length]; |
+ } |
+ |
+ NSString* ssl = @""; |
+ if (ssl_info.is_valid()) { |
+ NSString* subject = base::SysUTF8ToNSString( |
+ ssl_info.cert.get()->subject().GetDisplayName()); |
+ NSString* issuer = base::SysUTF8ToNSString( |
+ ssl_info.cert.get()->issuer().GetDisplayName()); |
+ |
+ ssl = [NSString stringWithFormat: |
+ @"\n\t\tcert for '%@' issued by '%@'", subject, issuer]; |
+ |
+ if (!net::IsCertStatusMinorError(ssl_info.cert_status)) { |
+ ssl = [NSString stringWithFormat:@"%@ (status: %0xd)", |
+ ssl, ssl_info.cert_status]; |
+ } |
+ } |
+ return [NSString stringWithFormat:@"%@\n\t\t%@%@", status, spec, ssl]; |
+ } |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TrackerCounts); |
+}; |
+ |
+// A SSL carrier is used to transport SSL information to the UI via its |
+// encapsulation in a block. Once the object is constructed all public methods |
+// can be called from any thread safely. This object is designed so it is |
+// instantiated on the IO thread but may be accessed from the UI thread. |
+@interface CRWSSLCarrier : NSObject { |
+ @private |
+ scoped_refptr<web::RequestTrackerImpl> tracker_; |
+ net::SSLInfo sslInfo_; |
+ GURL url_; |
+ web::SSLStatus status_; |
+} |
+ |
+// Designated initializer. |
+- (id)initWithTracker:(web::RequestTrackerImpl*)tracker |
+ counts:(const TrackerCounts*)counts; |
+// URL of the request. |
+- (const GURL&)url; |
+// Returns a SSLStatus representing the state of the page. This assumes the |
+// target carrier is the main page request. |
+- (const web::SSLStatus&)sslStatus; |
+// Returns a SSLInfo with a reference to the certificate and SSL information. |
+- (const net::SSLInfo&)sslInfo; |
+// Callback method to allow or deny the request from going through. |
+- (void)errorCallback:(BOOL)flag; |
+// Internal method used to build the SSLStatus object. Called from the |
+// initializer to make sure it is invoked on the network thread. |
+- (void)buildSSLStatus; |
+@end |
+ |
+@implementation CRWSSLCarrier |
+ |
+- (id)initWithTracker:(web::RequestTrackerImpl*)tracker |
+ counts:(const TrackerCounts*)counts { |
+ self = [super init]; |
+ if (self) { |
+ tracker_ = tracker; |
+ url_ = counts->url; |
+ sslInfo_ = counts->ssl_info; |
+ [self buildSSLStatus]; |
+ } |
+ return self; |
+} |
+ |
+- (const GURL&)url { |
+ return url_; |
+} |
+ |
+- (const net::SSLInfo&)sslInfo { |
+ return sslInfo_; |
+} |
+ |
+- (const web::SSLStatus&)sslStatus { |
+ return status_; |
+} |
+ |
+- (void)errorCallback:(BOOL)flag { |
+ base::scoped_nsobject<CRWSSLCarrier> scoped([self retain]); |
+ web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, |
+ base::Bind(&web::RequestTrackerImpl::ErrorCallback, |
+ tracker_, scoped, flag)); |
+} |
+ |
+- (void)buildSSLStatus { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ if (!sslInfo_.is_valid()) |
+ return; |
+ |
+ status_.cert_id = web::CertStore::GetInstance()->StoreCert( |
+ sslInfo_.cert.get(), tracker_->identifier()); |
+ |
+ status_.cert_status = sslInfo_.cert_status; |
+ if (status_.cert_status & net::CERT_STATUS_COMMON_NAME_INVALID) { |
+ // CAs issue certificates for intranet hosts to everyone. Therefore, we |
+ // mark intranet hosts as being non-unique. |
+ if (IsIntranetHost(url_.host())) { |
+ status_.cert_status |= net::CERT_STATUS_NON_UNIQUE_NAME; |
+ } |
+ } |
+ |
+ status_.security_bits = sslInfo_.security_bits; |
+ status_.connection_status = sslInfo_.connection_status; |
+ |
+ if (tracker_->has_mixed_content()) { |
+ // TODO(noyau): In iOS there is no notion of resource type. The insecure |
+ // content could be an image (DISPLAYED_INSECURE_CONTENT) or a script |
+ // (RAN_INSECURE_CONTENT). The status of the page is different for both, but |
+ // there is not enough information from UIWebView to differentiate the two |
+ // cases. |
+ status_.content_status = web::SSLStatus::DISPLAYED_INSECURE_CONTENT; |
+ } else { |
+ status_.content_status = web::SSLStatus::NORMAL_CONTENT; |
+ } |
+ |
+ if (!url_.SchemeIsSecure()) { |
+ // Should not happen as the sslInfo is valid. |
+ NOTREACHED(); |
+ status_.security_style = web::SECURITY_STYLE_UNAUTHENTICATED; |
+ } else if (net::IsCertStatusError(status_.cert_status) && |
+ !net::IsCertStatusMinorError(status_.cert_status)) { |
+ // Minor errors don't lower the security style to |
+ // SECURITY_STYLE_AUTHENTICATION_BROKEN. |
+ status_.security_style = web::SECURITY_STYLE_AUTHENTICATION_BROKEN; |
+ } else { |
+ // This page is secure. |
+ status_.security_style = web::SECURITY_STYLE_AUTHENTICATED; |
+ } |
+} |
+ |
+- (NSString*)description { |
+ NSString* sslInfo = @""; |
+ if (sslInfo_.is_valid()) { |
+ switch (status_.security_style) { |
+ case web::SECURITY_STYLE_UNKNOWN: |
+ case web::SECURITY_STYLE_UNAUTHENTICATED: |
+ sslInfo = @"Unexpected SSL state "; |
+ break; |
+ case web::SECURITY_STYLE_AUTHENTICATION_BROKEN: |
+ sslInfo = @"Not secure "; |
+ break; |
+ case web::SECURITY_STYLE_AUTHENTICATED: |
+ if (status_.content_status == |
+ web::SSLStatus::DISPLAYED_INSECURE_CONTENT) |
+ sslInfo = @"Mixed "; |
+ else |
+ sslInfo = @"Secure "; |
+ break; |
+ } |
+ } |
+ |
+ NSURL* url = net::NSURLWithGURL(url_); |
+ |
+ return [NSString stringWithFormat:@"<%@%@>", sslInfo, url]; |
+} |
+ |
+@end |
+ |
+namespace web { |
+ |
+#pragma mark Consumer API |
+ |
+// static |
+scoped_refptr<RequestTrackerImpl> |
+RequestTrackerImpl::CreateTrackerForRequestGroupID( |
+ NSString* request_group_id, |
+ BrowserState* browser_state, |
+ net::URLRequestContextGetter* context_getter, |
+ id<CRWRequestTrackerDelegate> delegate) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ DCHECK(request_group_id); |
+ |
+ scoped_refptr<RequestTrackerImpl> tracker = |
+ new RequestTrackerImpl(request_group_id, context_getter, delegate); |
+ |
+ scoped_refptr<CertificatePolicyCache> policy_cache = |
+ BrowserState::GetCertificatePolicyCache(browser_state); |
+ DCHECK(policy_cache); |
+ |
+ // Take care of the IO-thread init. |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::InitOnIOThread, tracker, policy_cache)); |
+ RegisterTracker(tracker.get(), request_group_id); |
+ return tracker; |
+} |
+ |
+void RequestTrackerImpl::StartPageLoad(const GURL& url, id user_info) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ base::scoped_nsobject<id> scoped_user_info([user_info retain]); |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::TrimToURL, this, url, scoped_user_info)); |
+} |
+ |
+void RequestTrackerImpl::FinishPageLoad(const GURL& url, bool load_success) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::StopPageLoad, this, url, load_success)); |
+} |
+ |
+void RequestTrackerImpl::HistoryStateChange(const GURL& url) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::HistoryStateChangeToURL, this, url)); |
+} |
+ |
+// Close is called when an owning object (a Tab or something that acts like |
+// it) is done with the RequestTrackerImpl. There may still be queued calls on |
+// the UI thread that will make use of the fields being cleaned-up here; they |
+// must ensure they they operate without crashing with the cleaned-up values. |
+void RequestTrackerImpl::Close() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ // Mark the tracker as closing on the IO thread. Note that because the local |
+ // scoped_refptr here retains |this|, we a are guaranteed that destruiction |
+ // won't begin until the block completes, and thus |is_closing_| will always |
+ // be set before destruction begins. |
+ scoped_refptr<RequestTrackerImpl> tracker = this; |
+ web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, |
+ base::BindBlock(^{ |
+ tracker->is_closing_ = true; |
+ tracker->CancelRequests(); |
+ })); |
+ |
+ // Disable the delegate. |
+ delegate_ = nil; |
+ // The user_info is no longer needed. |
+ user_info_.reset(); |
+ // Get rid of the stored certificates |
+ web::CertStore::GetInstance()->RemoveCertsForGroup(identifier_); |
+} |
+ |
+// static |
+void RequestTrackerImpl::RunAfterRequestsCancel(const base::Closure& callback) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ // Post a no-op to the IO thread, and after that has executed, run |callback|. |
+ // This ensures that |callback| runs after anything elese queued on the IO |
+ // thread, in particular CancelRequest() calls made from closing trackers. |
+ web::WebThread::PostTaskAndReply(web::WebThread::IO, FROM_HERE, |
+ base::Bind(&base::DoNothing), callback); |
+} |
+ |
+// static |
+void RequestTrackerImpl::BlockUntilTrackersShutdown() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ { |
+ base::AutoLock scoped_lock(*g_waiting_on_io_thread_lock); |
+ g_waiting_on_io_thread = true; |
+ } |
+ web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, |
+ base::Bind(&StopIOThreadWaiting)); |
+ |
+ // Poll endlessly until the wait flag is unset on the IO thread by |
+ // StopIOThreadWaiting(). |
+ // (Consider instead having a hard time cap, like 100ms or so, after which |
+ // we stop blocking. In that case this method would return a boolean |
+ // indicating if the wait completed or not). |
+ while (1) { |
+ base::AutoLock scoped_lock(*g_waiting_on_io_thread_lock); |
+ if (!g_waiting_on_io_thread) |
+ return; |
+ // Ensure that other threads have a chance to run even on a single-core |
+ // devices. |
+ pthread_yield_np(); |
+ } |
+} |
+ |
+#pragma mark Provider API |
+ |
+// static |
+RequestTrackerImpl* RequestTrackerImpl::GetTrackerForRequestGroupID( |
+ NSString* request_group_id) { |
+ DCHECK(request_group_id); |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ RequestTrackerImpl* tracker = nullptr; |
+ TrackerMap::iterator map_it; |
+ pthread_once(&g_once_control, &InitializeGlobals); |
+ { |
+ base::AutoLock scoped_lock(*g_trackers_lock); |
+ map_it = g_trackers->find( |
+ base::scoped_nsobject<NSString>([request_group_id copy])); |
+ if (map_it != g_trackers->end()) |
+ tracker = map_it->second; |
+ } |
+ return tracker; |
+} |
+ |
+net::URLRequestContext* RequestTrackerImpl::GetRequestContext() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ return request_context_getter_->GetURLRequestContext(); |
+} |
+ |
+void RequestTrackerImpl::StartRequest(net::URLRequest* request) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ DCHECK(!counts_by_request_.count(request)); |
+ DCHECK_EQ(is_for_static_file_requests_, request->url().SchemeIsFile()); |
+ |
+ bool addedRequest = live_requests_.insert(request).second; |
+ if (!is_for_static_file_requests_ && addedRequest) { |
+ NSString* networkActivityKey = GetNetworkActivityKey(); |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::BindBlock(^{ |
+ [[CRWNetworkActivityIndicatorManager sharedInstance] |
+ startNetworkTaskForGroup:networkActivityKey]; |
+ })); |
+ } |
+ |
+ if (new_estimate_round_) { |
+ // Starting a new estimate round. Ignore the previous requests for the |
+ // calculation. |
+ counts_by_request_.clear(); |
+ estimate_start_index_ = counts_.size(); |
+ new_estimate_round_ = false; |
+ } |
+ const GURL& url = request->original_url(); |
+ TrackerCounts* counts = new TrackerCounts( |
+ GURLByRemovingRefFromGURL(url), request); |
+ counts_.push_back(counts); |
+ counts_by_request_[request] = counts; |
+ if (page_url_.SchemeIsSecure() && !url.SchemeIsSecure()) |
+ has_mixed_content_ = true; |
+ Notify(); |
+} |
+ |
+void RequestTrackerImpl::CaptureHeaders(net::URLRequest* request) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ if (is_closing_) |
+ return; |
+ |
+ if (!request->response_headers()) |
+ return; |
+ |
+ scoped_refptr<net::HttpResponseHeaders> headers(request->response_headers()); |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::NotifyResponseHeaders, this, headers, |
+ request->url())); |
+} |
+ |
+void RequestTrackerImpl::CaptureExpectedLength(const net::URLRequest* request, |
+ uint64_t length) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ if (counts_by_request_.count(request)) { |
+ TrackerCounts* counts = counts_by_request_[request]; |
+ DCHECK(!counts->done); |
+ if (length < counts->processed) { |
+ // Something is wrong with the estimate. Ignore it. |
+ counts->expected_length = 0; |
+ } else { |
+ counts->expected_length = length; |
+ } |
+ Notify(); |
+ } |
+} |
+ |
+void RequestTrackerImpl::CaptureReceivedBytes(const net::URLRequest* request, |
+ uint64_t byte_count) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ if (counts_by_request_.count(request)) { |
+ TrackerCounts* counts = counts_by_request_[request]; |
+ DCHECK(!counts->done); |
+ const net::SSLInfo& ssl_info = request->ssl_info(); |
+ if (ssl_info.is_valid()) |
+ counts->ssl_info = ssl_info; |
+ counts->processed += byte_count; |
+ if (counts->expected_length > 0 && |
+ counts->expected_length < counts->processed) { |
+ // Something is wrong with the estimate, it is too low. Ignore it. |
+ counts->expected_length = 0; |
+ } |
+ Notify(); |
+ } |
+} |
+ |
+void RequestTrackerImpl::StopRequest(net::URLRequest* request) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ |
+ int removedRequests = live_requests_.erase(request); |
+ if (!is_for_static_file_requests_ && removedRequests > 0) { |
+ NSString* networkActivityKey = GetNetworkActivityKey(); |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::BindBlock(^{ |
+ [[CRWNetworkActivityIndicatorManager sharedInstance] |
+ stopNetworkTaskForGroup:networkActivityKey]; |
+ })); |
+ } |
+ |
+ if (counts_by_request_.count(request)) { |
+ StopRedirectedRequest(request); |
+ Notify(); |
+ } |
+} |
+ |
+void RequestTrackerImpl::StopRedirectedRequest(net::URLRequest* request) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ |
+ int removedRequests = live_requests_.erase(request); |
+ if (!is_for_static_file_requests_ && removedRequests > 0) { |
+ NSString* networkActivityKey = GetNetworkActivityKey(); |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::BindBlock(^{ |
+ [[CRWNetworkActivityIndicatorManager sharedInstance] |
+ stopNetworkTaskForGroup:networkActivityKey]; |
+ })); |
+ } |
+ |
+ if (counts_by_request_.count(request)) { |
+ TrackerCounts* counts = counts_by_request_[request]; |
+ DCHECK(!counts->done); |
+ const net::SSLInfo& ssl_info = request->ssl_info(); |
+ if (ssl_info.is_valid()) |
+ counts->ssl_info = ssl_info; |
+ counts->done = true; |
+ counts_by_request_.erase(request); |
+ } |
+} |
+ |
+void RequestTrackerImpl::CaptureCertificatePolicyCache( |
+ const net::URLRequest* request, |
+ const RequestTracker::SSLCallback& should_continue) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ std::string host = request->url().host(); |
+ CertPolicy::Judgment judgment = policy_cache_->QueryPolicy( |
+ request->ssl_info().cert.get(), host, request->ssl_info().cert_status); |
+ if (judgment == CertPolicy::UNKNOWN) { |
+ // The request comes from the cache, and has been loaded even though the |
+ // policy is UNKNOWN. Display the interstitial page now. |
+ OnSSLCertificateError(request, request->ssl_info(), true, should_continue); |
+ return; |
+ } |
+ |
+ // Notify the delegate that a judgment has been used. |
+ DCHECK(judgment == CertPolicy::ALLOWED); |
+ if (counts_by_request_.count(request)) { |
+ const net::SSLInfo& ssl_info = request->ssl_info(); |
+ TrackerCounts* counts = counts_by_request_[request]; |
+ counts->allowed_by_user = true; |
+ if (ssl_info.is_valid()) |
+ counts->ssl_info = ssl_info; |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::NotifyCertificateUsed, this, |
+ ssl_info.cert, host, ssl_info.cert_status)); |
+ } |
+ should_continue.Run(true); |
+} |
+ |
+void RequestTrackerImpl::OnSSLCertificateError( |
+ const net::URLRequest* request, |
+ const net::SSLInfo& ssl_info, |
+ bool recoverable, |
+ const RequestTracker::SSLCallback& should_continue) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ DCHECK(ssl_info.is_valid()); |
+ |
+ if (counts_by_request_.count(request)) { |
+ TrackerCounts* counts = counts_by_request_[request]; |
+ |
+ DCHECK(!counts->done); |
+ // Store the ssl error. |
+ counts->ssl_info = ssl_info; |
+ counts->ssl_callback = should_continue; |
+ counts->ssl_judgment = |
+ recoverable ? CertPolicy::UNKNOWN : CertPolicy::DENIED; |
+ ReevaluateCallbacksForAllCounts(); |
+ } |
+} |
+ |
+void RequestTrackerImpl::ErrorCallback(CRWSSLCarrier* carrier, bool allow) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ DCHECK(policy_cache_); |
+ |
+ if (allow) { |
+ policy_cache_->AllowCertForHost([carrier sslInfo].cert.get(), |
+ [carrier url].host(), |
+ [carrier sslInfo].cert_status); |
+ ReevaluateCallbacksForAllCounts(); |
+ } |
+ current_ssl_error_ = NULL; |
+} |
+ |
+#pragma mark Client utility methods. |
+ |
+// TODO(marq): Convert all internal task-posting to use these. |
+void RequestTrackerImpl::PostUITaskIfOpen(const base::Closure& task) { |
+ PostTask(task, web::WebThread::UI); |
+} |
+ |
+// static |
+void RequestTrackerImpl::PostUITaskIfOpen( |
+ const base::WeakPtr<RequestTracker> tracker, |
+ const base::Closure& task) { |
+ if (!tracker) |
+ return; |
+ RequestTrackerImpl* tracker_impl = |
+ static_cast<RequestTrackerImpl*>(tracker.get()); |
+ tracker_impl->PostUITaskIfOpen(task); |
+} |
+ |
+void RequestTrackerImpl::PostIOTask(const base::Closure& task) { |
+ PostTask(task, web::WebThread::IO); |
+} |
+ |
+void RequestTrackerImpl::ScheduleIOTask(const base::Closure& task) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, task); |
+} |
+ |
+void RequestTrackerImpl::SetCacheModeFromUIThread( |
+ RequestTracker::CacheMode mode) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, |
+ base::Bind(&RequestTracker::SetCacheMode, this, mode)); |
+} |
+ |
+#pragma mark Private Object Lifecycle API |
+ |
+RequestTrackerImpl::RequestTrackerImpl( |
+ NSString* request_group_id, |
+ net::URLRequestContextGetter* context_getter, |
+ id<CRWRequestTrackerDelegate> delegate) |
+ : delegate_(delegate), |
+ previous_estimate_(0.0f), // Not active by default. |
+ estimate_start_index_(0), |
+ notification_depth_(0), |
+ current_ssl_error_(NULL), |
+ has_mixed_content_(false), |
+ is_loading_(false), |
+ new_estimate_round_(true), |
+ is_for_static_file_requests_([delegate isForStaticFileRequests]), |
+ request_context_getter_(context_getter), |
+ identifier_(++g_next_request_tracker_id), |
+ request_group_id_([request_group_id copy]), |
+ is_closing_(false) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+} |
+ |
+void RequestTrackerImpl::InitOnIOThread( |
+ const scoped_refptr<CertificatePolicyCache>& policy_cache) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ Init(); |
+ DCHECK(policy_cache); |
+ policy_cache_ = policy_cache; |
+} |
+ |
+RequestTrackerImpl::~RequestTrackerImpl() { |
+} |
+ |
+void RequestTrackerImplTraits::Destruct(const RequestTrackerImpl* t) { |
+ // RefCountedThreadSafe assumes we can do all the destruct tasks with a |
+ // const pointer, but we actually can't. |
+ RequestTrackerImpl* inconstant_t = const_cast<RequestTrackerImpl*>(t); |
+ if (web::WebThread::CurrentlyOn(web::WebThread::IO)) { |
+ inconstant_t->Destruct(); |
+ } else { |
+ // Use BindBlock rather than Bind to avoid creating another scoped_refpter |
+ // to |this|. |inconstant_t| isn't retained by the block, but since this |
+ // method is the mechanism by which all RequestTrackerImpl instances are |
+ // destroyed, the object inconstant_t points to won't be deleted while |
+ // the block is executing (and Destruct() itself will do the deleting). |
+ web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, |
+ base::BindBlock(^{ |
+ inconstant_t->Destruct(); |
+ })); |
+ } |
+} |
+ |
+void RequestTrackerImpl::Destruct() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ DCHECK(is_closing_); |
+ |
+ pthread_once(&g_once_control, &InitializeGlobals); |
+ { |
+ base::AutoLock scoped_lock(*g_trackers_lock); |
+ g_trackers->erase(request_group_id_); |
+ } |
+ InvalidateWeakPtrs(); |
+ // Delete on the UI thread. |
+ web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, base::BindBlock(^{ |
+ delete this; |
+ })); |
+} |
+ |
+#pragma mark Other private methods |
+// TODO(marq): Reorder method implementations to match header and add grouping |
+// marks/comments. |
+ |
+void RequestTrackerImpl::Notify() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ if (is_closing_) |
+ return; |
+ // Notify() is called asynchronously, it runs later on the same |
+ // thread. This is used to collate notifications together, avoiding |
+ // blanketing the UI with a stream of information. |
+ notification_depth_ += 1; |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::StackNotification, this)); |
+} |
+ |
+void RequestTrackerImpl::StackNotification() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ if (is_closing_) |
+ return; |
+ |
+ // There is no point in sending the notification if there is another one |
+ // already queued. This queue is processing very lightweight changes and |
+ // should be exhausted very easily. |
+ --notification_depth_; |
+ if (notification_depth_) |
+ return; |
+ |
+ SSLNotify(); |
+ if (is_loading_) { |
+ float estimate = EstimatedProgress(); |
+ if (estimate != -1.0f) { |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::NotifyUpdatedProgress, this, |
+ estimate)); |
+ } |
+ } |
+} |
+ |
+void RequestTrackerImpl::SSLNotify() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ if (is_closing_) |
+ return; |
+ |
+ if (!counts_.size()) |
+ return; // Nothing yet to notify. |
+ |
+ if (!page_url_.SchemeIsSecure()) |
+ return; |
+ |
+ const GURL page_origin = page_url_.GetOrigin(); |
+ ScopedVector<TrackerCounts>::iterator it; |
+ for (it = counts_.begin(); it != counts_.end(); ++it) { |
+ if (!(*it)->ssl_info.is_valid()) |
+ continue; // No SSL info at this point in time on this tracker. |
+ |
+ GURL request_origin = (*it)->url.GetOrigin(); |
+ if (request_origin != page_origin) |
+ continue; // Not interesting in the context of the page. |
+ |
+ base::scoped_nsobject<CRWSSLCarrier> carrier( |
+ [[CRWSSLCarrier alloc] initWithTracker:this counts:*it]); |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::NotifyUpdatedSSLStatus, this, carrier)); |
+ break; |
+ } |
+} |
+ |
+void RequestTrackerImpl::NotifyResponseHeaders( |
+ net::HttpResponseHeaders* headers, |
+ const GURL& request_url) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ [delegate_ handleResponseHeaders:headers requestUrl:request_url]; |
+} |
+ |
+void RequestTrackerImpl::NotifyCertificateUsed( |
+ net::X509Certificate* certificate, |
+ const std::string& host, |
+ net::CertStatus status) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ [delegate_ certificateUsed:certificate forHost:host status:status]; |
+} |
+ |
+void RequestTrackerImpl::NotifyUpdatedProgress(float estimate) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ [delegate_ updatedProgress:estimate]; |
+} |
+ |
+void RequestTrackerImpl::NotifyClearCertificates() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ [delegate_ clearCertificates]; |
+} |
+ |
+void RequestTrackerImpl::NotifyUpdatedSSLStatus( |
+ base::scoped_nsobject<CRWSSLCarrier> carrier) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ [delegate_ updatedSSLStatus:[carrier sslStatus] |
+ forPageUrl:[carrier url] |
+ userInfo:user_info_]; |
+} |
+ |
+void RequestTrackerImpl::NotifyPresentSSLError( |
+ base::scoped_nsobject<CRWSSLCarrier> carrier, |
+ bool recoverable) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
+ [delegate_ presentSSLError:[carrier sslInfo] |
+ forSSLStatus:[carrier sslStatus] |
+ onUrl:[carrier url] |
+ recoverable:recoverable |
+ callback:^(BOOL flag) { |
+ [carrier errorCallback:flag && recoverable]; |
+ }]; |
+} |
+ |
+void RequestTrackerImpl::EvaluateSSLCallbackForCounts(TrackerCounts* counts) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ DCHECK(policy_cache_); |
+ |
+ // Ignore non-SSL requests. |
+ if (!counts->ssl_info.is_valid()) |
+ return; |
+ |
+ CertPolicy::Judgment judgment = |
+ policy_cache_->QueryPolicy(counts->ssl_info.cert.get(), |
+ counts->url.host(), |
+ counts->ssl_info.cert_status); |
+ |
+ if (judgment != CertPolicy::ALLOWED) { |
+ // Apply some fine tuning. |
+ // TODO(droger): This logic is duplicated from SSLPolicy. Sharing the code |
+ // would be better. |
+ switch (net::MapCertStatusToNetError(counts->ssl_info.cert_status)) { |
+ case net::ERR_CERT_NO_REVOCATION_MECHANISM: |
+ // Ignore this error. |
+ judgment = CertPolicy::ALLOWED; |
+ break; |
+ case net::ERR_CERT_UNABLE_TO_CHECK_REVOCATION: |
+ // We ignore this error but it will show a warning status in the |
+ // location bar. |
+ judgment = CertPolicy::ALLOWED; |
+ break; |
+ case net::ERR_CERT_CONTAINS_ERRORS: |
+ case net::ERR_CERT_REVOKED: |
+ case net::ERR_CERT_INVALID: |
+ case net::ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY: |
+ case net::ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN: |
+ judgment = CertPolicy::DENIED; |
+ break; |
+ case net::ERR_CERT_WEAK_SIGNATURE_ALGORITHM: |
+ case net::ERR_CERT_COMMON_NAME_INVALID: |
+ case net::ERR_CERT_DATE_INVALID: |
+ case net::ERR_CERT_AUTHORITY_INVALID: |
+ // Nothing. If DENIED it will stay denied. If UNKNOWN it will be |
+ // shown to the user for decision. |
+ break; |
+ default: |
+ NOTREACHED(); |
+ judgment = CertPolicy::DENIED; |
+ break; |
+ } |
+ } |
+ |
+ counts->ssl_judgment = judgment; |
+ |
+ switch (judgment) { |
+ case CertPolicy::UNKNOWN: |
+ case CertPolicy::DENIED: |
+ // Simply cancel the request. |
+ CancelRequestForCounts(counts); |
+ break; |
+ case CertPolicy::ALLOWED: |
+ counts->ssl_callback.Run(YES); |
+ counts->ssl_callback = base::Bind(&DoNothing); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ // For now simply cancel the request. |
+ CancelRequestForCounts(counts); |
+ break; |
+ } |
+} |
+ |
+void RequestTrackerImpl::ReevaluateCallbacksForAllCounts() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ if (is_closing_) |
+ return; |
+ |
+ ScopedVector<TrackerCounts>::iterator it; |
+ for (it = counts_.begin(); it != counts_.end(); ++it) { |
+ // Check if the value hasn't changed via a user action. |
+ if ((*it)->ssl_judgment == CertPolicy::UNKNOWN) |
+ EvaluateSSLCallbackForCounts(*it); |
+ |
+ CertPolicy::Judgment judgment = (*it)->ssl_judgment; |
+ if (judgment == CertPolicy::ALLOWED) |
+ continue; |
+ |
+ // SSL errors on subrequests are simply ignored. The call to |
+ // EvaluateSSLCallbackForCounts() cancelled the request and nothing will |
+ // restart it. |
+ if ((*it)->is_subrequest) |
+ continue; |
+ |
+ if (!current_ssl_error_) { |
+ // For the UNKNOWN and DENIED state the information should be pushed to |
+ // the delegate. But only one at a time. |
+ current_ssl_error_ = (*it); |
+ base::scoped_nsobject<CRWSSLCarrier> carrier([[CRWSSLCarrier alloc] |
+ initWithTracker:this counts:current_ssl_error_]); |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::NotifyPresentSSLError, this, carrier, |
+ judgment == CertPolicy::UNKNOWN)); |
+ } |
+ } |
+} |
+ |
+void RequestTrackerImpl::CancelRequestForCounts(TrackerCounts* counts) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ // Cancel the request. |
+ counts->done = true; |
+ counts_by_request_.erase(counts->request); |
+ counts->ssl_callback.Run(NO); |
+ counts->ssl_callback = base::Bind(&DoNothing); |
+ Notify(); |
+} |
+ |
+PageCounts RequestTrackerImpl::pageCounts() { |
+ DCHECK_GE(counts_.size(), estimate_start_index_); |
+ |
+ PageCounts page_counts; |
+ |
+ ScopedVector<TrackerCounts>::iterator it; |
+ for (it = counts_.begin() + estimate_start_index_; |
+ it != counts_.end(); ++it) { |
+ if ((*it)->done) { |
+ uint64_t size = (*it)->processed; |
+ page_counts.finished += 1; |
+ page_counts.finished_bytes += size; |
+ if (page_counts.largest_byte_size_known < size) { |
+ page_counts.largest_byte_size_known = size; |
+ } |
+ } else { |
+ page_counts.unfinished += 1; |
+ if ((*it)->expected_length) { |
+ uint64_t size = (*it)->expected_length; |
+ page_counts.unfinished_estimate_bytes_done += (*it)->processed; |
+ page_counts.unfinished_estimated_bytes_left += size; |
+ if (page_counts.largest_byte_size_known < size) { |
+ page_counts.largest_byte_size_known = size; |
+ } |
+ } else { |
+ page_counts.unfinished_no_estimate += 1; |
+ page_counts.unfinished_no_estimate_bytes_done += (*it)->processed; |
+ } |
+ } |
+ } |
+ |
+ return page_counts; |
+} |
+ |
+float RequestTrackerImpl::EstimatedProgress() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ |
+ const PageCounts page_counts = pageCounts(); |
+ |
+ // Nothing in progress and the last time was the same. |
+ if (!page_counts.unfinished && previous_estimate_ == 0.0f) |
+ return -1.0f; |
+ |
+ // First request. |
+ if (previous_estimate_ == 0.0f) { |
+ // start low. |
+ previous_estimate_ = 0.1f; |
+ return previous_estimate_; // Return the just started status. |
+ } |
+ |
+ // The very simple case where everything is probably done and dusted. |
+ if (!page_counts.unfinished) { |
+ // Add 60%, and return. Another task is going to finish this. |
+ float bump = (1.0f - previous_estimate_) * 0.6f; |
+ previous_estimate_ += bump; |
+ return previous_estimate_; |
+ } |
+ |
+ // Calculate some ratios. |
+ // First the ratio of the finished vs the unfinished counts of resources |
+ // loaded. |
+ float unfinishedRatio = |
+ static_cast<float>(page_counts.finished) / |
+ static_cast<float>(page_counts.unfinished + page_counts.finished); |
+ |
+ // The ratio of bytes left vs bytes already downloaded for the resources where |
+ // no estimates of final size are known. For this ratio it is assumed the size |
+ // of a resource not downloaded yet is the maximum size of all the resources |
+ // seen so far. |
+ float noEstimateRatio = (!page_counts.unfinished_no_estimate_bytes_done) ? |
+ 0.0f : |
+ static_cast<float>(page_counts.unfinished_no_estimate * |
+ page_counts.largest_byte_size_known) / |
+ static_cast<float>(page_counts.finished_bytes + |
+ page_counts.unfinished_no_estimate_bytes_done); |
+ |
+ // The ratio of bytes left vs bytes already downloaded for the resources with |
+ // available estimated size. |
+ float estimateRatio = (!page_counts.unfinished_estimated_bytes_left) ? |
+ noEstimateRatio : |
+ static_cast<float>(page_counts.unfinished_estimate_bytes_done) / |
+ static_cast<float>(page_counts.unfinished_estimate_bytes_done + |
+ page_counts.unfinished_estimated_bytes_left); |
+ |
+ // Reassemble all of this. |
+ float total = |
+ 0.1f + // Minimum value. |
+ unfinishedRatio * 0.6f + |
+ estimateRatio * 0.3f; |
+ |
+ if (previous_estimate_ >= total) |
+ return -1.0f; |
+ |
+ // 10% of what's left. |
+ float maxBump = (1.0f - previous_estimate_) / 10.0f; |
+ // total is greater than previous estimate, need to bump the estimate up. |
+ if ((previous_estimate_ + maxBump) > total) { |
+ // Less than a 10% bump, bump to the new value. |
+ previous_estimate_ = total; |
+ } else { |
+ // Just bump by 10% toward the total. |
+ previous_estimate_ += maxBump; |
+ } |
+ |
+ return previous_estimate_; |
+} |
+ |
+void RequestTrackerImpl::RecomputeMixedContent( |
+ const TrackerCounts* split_position) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ // Check if the mixed content before trimming was correct. |
+ if (page_url_.SchemeIsSecure() && has_mixed_content_) { |
+ bool old_url_has_mixed_content = false; |
+ const GURL origin = page_url_.GetOrigin(); |
+ ScopedVector<TrackerCounts>::iterator it = counts_.begin(); |
+ while (it != counts_.end() && *it != split_position) { |
+ if (!(*it)->url.SchemeIsSecure() && |
+ origin == (*it)->first_party_for_cookies_origin) { |
+ old_url_has_mixed_content = true; |
+ break; |
+ } |
+ ++it; |
+ } |
+ if (!old_url_has_mixed_content) { |
+ // We marked the previous page with incorrect data about its mixed |
+ // content. Turns out that the elements that triggered that condition |
+ // where in fact in a subsequent page. Duh. |
+ // Resend a notification for the |page_url_| informing the upper layer |
+ // that the mixed content was a red herring. |
+ has_mixed_content_ = false; |
+ SSLNotify(); |
+ } |
+ } |
+} |
+ |
+void RequestTrackerImpl::RecomputeCertificatePolicy( |
+ const TrackerCounts* splitPosition) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ // Clear the judgments for the old URL. |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::NotifyClearCertificates, this)); |
+ // Report judgements for the new URL. |
+ ScopedVector<TrackerCounts>::const_reverse_iterator it; |
+ for (it = counts_.rbegin(); it != counts_.rend(); ++it) { |
+ TrackerCounts* counts = *it; |
+ if (counts->allowed_by_user) { |
+ std::string host = counts->url.host(); |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::Bind(&RequestTrackerImpl::NotifyCertificateUsed, this, |
+ counts->ssl_info.cert, host, |
+ counts->ssl_info.cert_status)); |
+ } |
+ if (counts == splitPosition) |
+ break; |
+ } |
+} |
+ |
+void RequestTrackerImpl::HistoryStateChangeToURL(const GURL& full_url) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ GURL url = GURLByRemovingRefFromGURL(full_url); |
+ |
+ if (is_loading_ && |
+ web::history_state_util::IsHistoryStateChangeValid(url, page_url_)) { |
+ page_url_ = url; |
+ } |
+} |
+ |
+void RequestTrackerImpl::TrimToURL(const GURL& full_url, id user_info) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ |
+ GURL url = GURLByRemovingRefFromGURL(full_url); |
+ |
+ // Locate the request with this url, if present. |
+ bool new_url_has_mixed_content = false; |
+ bool url_scheme_is_secure = url.SchemeIsSecure(); |
+ ScopedVector<TrackerCounts>::const_reverse_iterator rit = counts_.rbegin(); |
+ while (rit != counts_.rend() && (*rit)->url != url) { |
+ if (url_scheme_is_secure && !(*rit)->url.SchemeIsSecure() && |
+ (*rit)->first_party_for_cookies_origin == url.GetOrigin()) { |
+ new_url_has_mixed_content = true; |
+ } |
+ ++rit; |
+ } |
+ |
+ // |split_position| will be set to the count for the passed url if it exists. |
+ TrackerCounts* split_position = NULL; |
+ if (rit != counts_.rend()) { |
+ split_position = (*rit); |
+ } else { |
+ // The URL was not found, everything will be trimmed. The mixed content |
+ // calculation is invalid. |
+ new_url_has_mixed_content = false; |
+ |
+ // In the case of a page loaded via a HTML5 manifest there is no page |
+ // boundary to be found. However the latest count is a request for a |
+ // manifest. This tries to detect this peculiar case. |
+ // This is important as if this request for the manifest is on the same |
+ // domain as the page itself this will allow retrieval of the SSL |
+ // information. |
+ if (url_scheme_is_secure && counts_.size()) { |
+ TrackerCounts* back = counts_.back(); |
+ const GURL& back_url = back->url; |
+ if (back_url.SchemeIsSecure() && |
+ back_url.GetOrigin() == url.GetOrigin() && |
+ !back->is_subrequest) { |
+ split_position = back; |
+ } |
+ } |
+ } |
+ RecomputeMixedContent(split_position); |
+ RecomputeCertificatePolicy(split_position); |
+ |
+ // Trim up to that element. |
+ ScopedVector<TrackerCounts>::iterator it = counts_.begin(); |
+ while (it != counts_.end() && *it != split_position) { |
+ if (!(*it)->done) { |
+ // This is for an unfinished request on a previous page. We do not care |
+ // about those anymore. Cancel the request. |
+ if ((*it)->ssl_judgment == CertPolicy::UNKNOWN) |
+ CancelRequestForCounts(*it); |
+ counts_by_request_.erase((*it)->request); |
+ } |
+ it = counts_.erase(it); |
+ } |
+ |
+ has_mixed_content_ = new_url_has_mixed_content; |
+ page_url_ = url; |
+ user_info_.reset([user_info retain]); |
+ estimate_start_index_ = 0; |
+ is_loading_ = true; |
+ previous_estimate_ = 0.0f; |
+ new_estimate_round_ = true; |
+ ReevaluateCallbacksForAllCounts(); |
+ Notify(); |
+} |
+ |
+void RequestTrackerImpl::StopPageLoad(const GURL& url, bool load_success) { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ DCHECK(page_url_ == GURLByRemovingRefFromGURL(url)); |
+ is_loading_ = false; |
+} |
+ |
+#pragma mark Internal utilities for task posting |
+ |
+void RequestTrackerImpl::PostTask(const base::Closure& task, |
+ web::WebThread::ID thread) { |
+ // Absolute sanity test: |thread| is one of {UI, IO} |
+ DCHECK(thread == web::WebThread::UI || thread == web::WebThread::IO); |
+ // Check that we're on the counterpart thread to the one we're posting to. |
+ DCHECK_CURRENTLY_ON_WEB_THREAD( |
+ thread == web::WebThread::IO ? web::WebThread::UI : web::WebThread::IO); |
+ // Don't post if the tracker is closing and we're on the IO thread. |
+ // (there should be no way to call anything from the UI thread if |
+ // the tracker is closing). |
+ if (is_closing_ && web::WebThread::CurrentlyOn(web::WebThread::IO)) |
+ return; |
+ web::WebThread::PostTask(thread, FROM_HERE, task); |
+} |
+ |
+#pragma mark Other internal methods. |
+ |
+NSString* RequestTrackerImpl::UnsafeDescription() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ |
+ NSMutableArray* urls = [NSMutableArray array]; |
+ ScopedVector<TrackerCounts>::iterator it; |
+ for (it = counts_.begin(); it != counts_.end(); ++it) |
+ [urls addObject:(*it)->Description()]; |
+ |
+ return [NSString stringWithFormat:@"RequestGroupID %@\n%@\n%@", |
+ request_group_id_.get(), |
+ net::NSURLWithGURL(page_url_), |
+ [urls componentsJoinedByString:@"\n"]]; |
+} |
+ |
+NSString* RequestTrackerImpl::GetNetworkActivityKey() { |
+ return [NSString |
+ stringWithFormat:@"RequestTrackerImpl.NetworkActivityIndicatorKey.%@", |
+ request_group_id_.get()]; |
+ } |
+ |
+void RequestTrackerImpl::CancelRequests() { |
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
+ std::set<net::URLRequest*>::iterator it; |
+ // TODO(droger): When canceling the request, we should in theory make sure |
+ // that the NSURLProtocol client method |didFailWithError| is called, |
+ // otherwise the iOS system may wait indefinitely for the request to complete. |
+ // However, as we currently only cancel the requests when closing a tab, the |
+ // requests are all canceled by the system shortly after and nothing bad |
+ // happens. |
+ for (it = live_requests_.begin(); it != live_requests_.end(); ++it) |
+ (*it)->Cancel(); |
+ |
+ int removedRequests = live_requests_.size(); |
+ live_requests_.clear(); |
+ if (!is_for_static_file_requests_ && removedRequests > 0) { |
+ NSString* networkActivityKey = GetNetworkActivityKey(); |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::BindBlock(^{ |
+ [[CRWNetworkActivityIndicatorManager sharedInstance] |
+ clearNetworkTasksForGroup:networkActivityKey]; |
+ })); |
+ } |
+} |
+ |
+void RequestTrackerImpl::SetCertificatePolicyCacheForTest( |
+ web::CertificatePolicyCache* cache) { |
+ policy_cache_ = cache; |
+} |
+ |
+} // namespace web |