summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ios/net/request_tracker.h2
-rw-r--r--ios/net/request_tracker.mm3
-rw-r--r--ios/web/browser_state.cc33
-rw-r--r--ios/web/ios_web.gyp28
-rw-r--r--ios/web/ios_web_unittests.gyp8
-rw-r--r--ios/web/net/cert_store_impl.cc38
-rw-r--r--ios/web/net/cert_store_impl.h40
-rw-r--r--ios/web/net/cookie_notification_bridge.h31
-rw-r--r--ios/web/net/cookie_notification_bridge.mm40
-rw-r--r--ios/web/net/crw_request_tracker_delegate.h71
-rw-r--r--ios/web/net/request_tracker_data_memoizing_store.h182
-rw-r--r--ios/web/net/request_tracker_factory_impl.h31
-rw-r--r--ios/web/net/request_tracker_factory_impl.mm49
-rw-r--r--ios/web/net/request_tracker_impl.h399
-rw-r--r--ios/web/net/request_tracker_impl.mm1308
-rw-r--r--ios/web/net/request_tracker_impl_unittest.mm498
-rw-r--r--ios/web/net/web_http_protocol_handler_delegate.h30
-rw-r--r--ios/web/net/web_http_protocol_handler_delegate.mm58
-rw-r--r--ios/web/net/web_http_protocol_handler_delegate_unittest.mm128
-rw-r--r--ios/web/public/browser_state.h5
-rw-r--r--ios/web/public/cert_store.h49
-rw-r--r--ios/web/public/test/test_web_client.h45
-rw-r--r--ios/web/public/test/test_web_client.mm41
-rw-r--r--ios/web/ui_web_view_util.h24
-rw-r--r--ios/web/ui_web_view_util.mm40
-rw-r--r--ios/web/ui_web_view_util_unittest.mm54
-rw-r--r--ios/web/web_state/ui/crw_static_file_web_view.h36
-rw-r--r--ios/web/web_state/ui/crw_static_file_web_view.mm149
-rw-r--r--ios/web/web_state/ui/crw_static_file_web_view_unittest.mm89
29 files changed, 3507 insertions, 2 deletions
diff --git a/ios/net/request_tracker.h b/ios/net/request_tracker.h
index e47b732..f21cb88 100644
--- a/ios/net/request_tracker.h
+++ b/ios/net/request_tracker.h
@@ -44,6 +44,8 @@ class RequestTracker {
class RequestTrackerFactory {
public:
+ virtual ~RequestTrackerFactory();
+
// Returns false if |request| is associated to an invalid tracker and should
// be cancelled. In this case |tracker| is set to nullptr.
// Returns true if |request| is associated with a valid tracker or if the
diff --git a/ios/net/request_tracker.mm b/ios/net/request_tracker.mm
index c984f36..6eb21c3 100644
--- a/ios/net/request_tracker.mm
+++ b/ios/net/request_tracker.mm
@@ -66,6 +66,9 @@ GlobalNetworkClientFactories*
} // namespace
+RequestTracker::RequestTrackerFactory::~RequestTrackerFactory() {
+}
+
// static
void RequestTracker::SetRequestTrackerFactory(RequestTrackerFactory* factory) {
g_request_tracker_factory = factory;
diff --git a/ios/web/browser_state.cc b/ios/web/browser_state.cc
index 5a8c032..c7ce99f 100644
--- a/ios/web/browser_state.cc
+++ b/ios/web/browser_state.cc
@@ -4,11 +4,44 @@
#include "ios/web/public/browser_state.h"
+#include "base/memory/ref_counted.h"
+#include "ios/web/public/certificate_policy_cache.h"
+#include "ios/web/public/web_thread.h"
+
namespace web {
namespace {
// Private key used for safe conversion of base::SupportsUserData to
// web::BrowserState in web::BrowserState::FromSupportsUserData.
const char kBrowserStateIdentifierKey[] = "BrowserStateIdentifierKey";
+// Data key names.
+const char kCertificatePolicyCacheKeyName[] = "cert_policy_cache";
+
+// Wraps a CertificatePolicyCache as a SupportsUserData::Data; this is necessary
+// since reference counted objects can't be user data.
+struct CertificatePolicyCacheHandle : public base::SupportsUserData::Data {
+ explicit CertificatePolicyCacheHandle(CertificatePolicyCache* cache)
+ : policy_cache(cache) {}
+
+ scoped_refptr<CertificatePolicyCache> policy_cache;
+};
+}
+
+// static
+scoped_refptr<CertificatePolicyCache> BrowserState::GetCertificatePolicyCache(
+ BrowserState* browser_state) {
+ DCHECK_CURRENTLY_ON_WEB_THREAD(WebThread::UI);
+ if (!browser_state->GetUserData(kCertificatePolicyCacheKeyName)) {
+ CertificatePolicyCacheHandle* cert_cache_service_handle =
+ new CertificatePolicyCacheHandle(new CertificatePolicyCache());
+
+ browser_state->SetUserData(kCertificatePolicyCacheKeyName,
+ cert_cache_service_handle);
+ }
+
+ CertificatePolicyCacheHandle* handle =
+ static_cast<CertificatePolicyCacheHandle*>(
+ browser_state->GetUserData(kCertificatePolicyCacheKeyName));
+ return handle->policy_cache;
}
BrowserState::BrowserState() {
diff --git a/ios/web/ios_web.gyp b/ios/web/ios_web.gyp
index 478fd7f..ab73e90 100644
--- a/ios/web/ios_web.gyp
+++ b/ios/web/ios_web.gyp
@@ -19,9 +19,12 @@
'user_agent',
'../../base/base.gyp:base',
'../../content/content.gyp:content_browser',
+ '../../ios/net/ios_net.gyp:ios_net',
+ '../../ios/third_party/blink/blink_html_tokenizer.gyp:blink_html_tokenizer',
'../../net/net.gyp:net',
'../../ui/base/ui_base.gyp:ui_base',
'../../ui/gfx/gfx.gyp:gfx',
+ '../../url/url.gyp:url_lib',
],
'sources': [
'browser_state.cc',
@@ -33,13 +36,26 @@
'navigation/time_smoother.cc',
'navigation/time_smoother.h',
'net/cert_policy.cc',
+ 'net/cert_store_impl.cc',
+ 'net/cert_store_impl.h',
'net/certificate_policy_cache.cc',
+ 'net/cookie_notification_bridge.h',
+ 'net/cookie_notification_bridge.mm',
+ 'net/crw_request_tracker_delegate.h',
'net/request_group_util.h',
'net/request_group_util.mm',
+ 'net/request_tracker_data_memoizing_store.h',
+ 'net/request_tracker_factory_impl.h',
+ 'net/request_tracker_factory_impl.mm',
+ 'net/request_tracker_impl.h',
+ 'net/request_tracker_impl.mm',
+ 'net/web_http_protocol_handler_delegate.h',
+ 'net/web_http_protocol_handler_delegate.mm',
'public/block_types.h',
'public/browser_state.h',
'public/browser_url_rewriter.h',
'public/cert_policy.h',
+ 'public/cert_store.h',
'public/certificate_policy_cache.h',
'public/favicon_status.cc',
'public/favicon_status.h',
@@ -84,6 +100,8 @@
'public/web_thread.h',
'public/web_view_type.h',
'string_util.cc',
+ 'ui_web_view_util.h',
+ 'ui_web_view_util.mm',
'url_scheme_util.mm',
'url_util.cc',
'user_metrics.cc',
@@ -102,6 +120,8 @@
'web_state/ui/crw_debug_web_view.h',
'web_state/ui/crw_debug_web_view.mm',
'web_state/ui/crw_simple_web_view_controller.h',
+ 'web_state/ui/crw_static_file_web_view.h',
+ 'web_state/ui/crw_static_file_web_view.mm',
'web_state/web_state_observer.cc',
'web_state/web_state_observer_bridge.mm',
'web_state/wk_web_view_ssl_error_util.h',
@@ -233,8 +253,12 @@
'target_name': 'test_support_ios_web',
'type': 'static_library',
'dependencies': [
- '../../content/content_shell_and_tests.gyp:test_support_content',
'ios_web',
+ '../../content/content_shell_and_tests.gyp:test_support_content',
+ '../../ios/testing/ios_testing.gyp:ocmock_support',
+ '../../testing/gmock.gyp:gmock',
+ '../../testing/gtest.gyp:gtest',
+ '../../third_party/ocmock/ocmock.gyp:ocmock',
],
'include_dirs': [
'../..',
@@ -246,6 +270,8 @@
'public/test/js_test_util.mm',
'public/test/test_browser_state.cc',
'public/test/test_browser_state.h',
+ 'public/test/test_web_client.h',
+ 'public/test/test_web_client.mm',
'public/test/test_web_state.cc',
'public/test/test_web_state.h',
'public/test/test_web_thread.h',
diff --git a/ios/web/ios_web_unittests.gyp b/ios/web/ios_web_unittests.gyp
index d0a04bd..f2e0816 100644
--- a/ios/web/ios_web_unittests.gyp
+++ b/ios/web/ios_web_unittests.gyp
@@ -4,7 +4,7 @@
{
'variables': {
'chromium_code': 1,
- },
+ },
'targets': [
{
'target_name': 'ios_web_unittests',
@@ -15,6 +15,8 @@
'../../base/base.gyp:test_support_base',
'../../testing/gmock.gyp:gmock',
'../../testing/gtest.gyp:gtest',
+ '../../third_party/ocmock/ocmock.gyp:ocmock',
+ '../testing/ios_testing.gyp:ocmock_support',
'ios_web.gyp:ios_web',
'ios_web.gyp:test_support_ios_web',
],
@@ -26,11 +28,15 @@
'navigation/nscoder_util_unittest.mm',
'net/cert_policy_unittest.cc',
'net/request_group_util_unittest.mm',
+ 'net/request_tracker_impl_unittest.mm',
+ 'net/web_http_protocol_handler_delegate_unittest.mm',
'public/referrer_util_unittest.cc',
'string_util_unittest.cc',
+ 'ui_web_view_util_unittest.mm',
'url_scheme_util_unittest.mm',
'url_util_unittest.cc',
'weak_nsobject_counter_unittest.mm',
+ 'web_state/ui/crw_static_file_web_view_unittest.mm',
],
},
],
diff --git a/ios/web/net/cert_store_impl.cc b/ios/web/net/cert_store_impl.cc
new file mode 100644
index 0000000..9cccbab
--- /dev/null
+++ b/ios/web/net/cert_store_impl.cc
@@ -0,0 +1,38 @@
+// 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 "ios/web/net/cert_store_impl.h"
+
+namespace web {
+
+// static
+CertStore* CertStore::GetInstance() {
+ return CertStoreImpl::GetInstance();
+}
+
+// static
+CertStoreImpl* CertStoreImpl::GetInstance() {
+ return Singleton<CertStoreImpl>::get();
+}
+
+CertStoreImpl::CertStoreImpl() {
+}
+
+CertStoreImpl::~CertStoreImpl() {
+}
+
+int CertStoreImpl::StoreCert(net::X509Certificate* cert, int group_id) {
+ return store_.Store(cert, group_id);
+}
+
+bool CertStoreImpl::RetrieveCert(int cert_id,
+ scoped_refptr<net::X509Certificate>* cert) {
+ return store_.Retrieve(cert_id, cert);
+}
+
+void CertStoreImpl::RemoveCertsForGroup(int group_id) {
+ store_.RemoveForRequestTracker(group_id);
+}
+
+} // namespace web
diff --git a/ios/web/net/cert_store_impl.h b/ios/web/net/cert_store_impl.h
new file mode 100644
index 0000000..8e2154c
--- /dev/null
+++ b/ios/web/net/cert_store_impl.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef IOS_WEB_NET_CERT_STORE_IMPL_H_
+#define IOS_WEB_NET_CERT_STORE_IMPL_H_
+
+#include "base/memory/singleton.h"
+#include "ios/web/net/request_tracker_data_memoizing_store.h"
+#include "ios/web/public/cert_store.h"
+#include "net/cert/x509_certificate.h"
+
+namespace web {
+
+class CertStoreImpl : public CertStore {
+ public:
+ // Returns the singleton instance of the CertStore.
+ static CertStoreImpl* GetInstance();
+
+ // CertStore implementation:
+ int StoreCert(net::X509Certificate* cert, int group_id) override;
+ bool RetrieveCert(int cert_id,
+ scoped_refptr<net::X509Certificate>* cert) override;
+ void RemoveCertsForGroup(int group_id) override;
+
+ protected:
+ CertStoreImpl();
+ ~CertStoreImpl() override;
+
+ private:
+ friend struct DefaultSingletonTraits<CertStoreImpl>;
+
+ RequestTrackerDataMemoizingStore<net::X509Certificate> store_;
+
+ DISALLOW_COPY_AND_ASSIGN(CertStoreImpl);
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NET_CERT_STORE_IMPL_H_
diff --git a/ios/web/net/cookie_notification_bridge.h b/ios/web/net/cookie_notification_bridge.h
new file mode 100644
index 0000000..36fc706
--- /dev/null
+++ b/ios/web/net/cookie_notification_bridge.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef IOS_WEB_NET_COOKIE_NOTIFICATION_BRIDGE_H_
+#define IOS_WEB_NET_COOKIE_NOTIFICATION_BRIDGE_H_
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/threading/thread_checker.h"
+
+namespace web {
+
+// CookieNotificationBridge listens to
+// NSHTTPCookieManagerCookiesChangedNotification on the main thread and re-sends
+// it to the cookie store on the IO thread.
+class CookieNotificationBridge {
+ public:
+ CookieNotificationBridge();
+ ~CookieNotificationBridge();
+
+ private:
+ void OnNotificationReceived(NSNotification* notification);
+ base::scoped_nsprotocol<id> observer_;
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(CookieNotificationBridge);
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NET_COOKIE_NOTIFICATION_BRIDGE_H_
diff --git a/ios/web/net/cookie_notification_bridge.mm b/ios/web/net/cookie_notification_bridge.mm
new file mode 100644
index 0000000..8ebb9a7
--- /dev/null
+++ b/ios/web/net/cookie_notification_bridge.mm
@@ -0,0 +1,40 @@
+// 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/cookie_notification_bridge.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "ios/net/cookies/cookie_store_ios.h"
+#include "ios/web/public/web_thread.h"
+
+namespace web {
+
+CookieNotificationBridge::CookieNotificationBridge() {
+ observer_.reset([[NSNotificationCenter defaultCenter]
+ addObserverForName:NSHTTPCookieManagerCookiesChangedNotification
+ object:[NSHTTPCookieStorage sharedHTTPCookieStorage]
+ queue:nil
+ usingBlock:^(NSNotification* notification) {
+ OnNotificationReceived(notification);
+ }]);
+}
+
+CookieNotificationBridge::~CookieNotificationBridge() {
+ [[NSNotificationCenter defaultCenter] removeObserver:observer_];
+}
+
+void CookieNotificationBridge::OnNotificationReceived(
+ NSNotification* notification) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK([[notification name]
+ isEqualToString:NSHTTPCookieManagerCookiesChangedNotification]);
+ web::WebThread::PostTask(
+ web::WebThread::IO, FROM_HERE,
+ base::Bind(&net::CookieStoreIOS::NotifySystemCookiesChanged));
+}
+
+} // namespace web
diff --git a/ios/web/net/crw_request_tracker_delegate.h b/ios/web/net/crw_request_tracker_delegate.h
new file mode 100644
index 0000000..2e8d827
--- /dev/null
+++ b/ios/web/net/crw_request_tracker_delegate.h
@@ -0,0 +1,71 @@
+// Copyright 2012 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.
+
+#ifndef IOS_WEB_NET_CRW_REQUEST_TRACKER_DELEGATE_H_
+#define IOS_WEB_NET_CRW_REQUEST_TRACKER_DELEGATE_H_
+
+#include <vector>
+
+#include "net/cert/cert_status_flags.h"
+
+class GURL;
+
+namespace net {
+class HttpResponseHeaders;
+class SSLInfo;
+class X509Certificate;
+}
+
+namespace web {
+struct SSLStatus;
+}
+
+// All the methods in this protocol must be sent on the main thread.
+@protocol CRWRequestTrackerDelegate
+
+// Returns |YES| of all the requests are static file requests and returns |NO|
+// if all the requests are network requests. Note it is not allowed for a
+// |CRWRequestTrackerDelegate| to send both static file requests and network
+// requests.
+- (BOOL)isForStaticFileRequests;
+
+// The tracker calls this method every time there is a change in the SSL status
+// of a page. The info is whatever object was passed to TrimToURL().
+- (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
+ forPageUrl:(const GURL&)url
+ userInfo:(id)userInfo;
+
+// The tracker calls this method when it receives response headers.
+- (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
+ requestUrl:(const GURL&)requestUrl;
+
+// This method is called when a network request has an issue with the SSL
+// connection to present it to the user. The user will decide if the request
+// should continue or not and the callback should be invoked to let the backend
+// know.
+// If the callback is not called the request will be cancelled on the next call
+// to TrimToURL().
+// The callback is safe to call until the requestTracker it originated from
+// is deleted.
+typedef void (^SSLErrorCallback)(BOOL);
+- (void)presentSSLError:(const net::SSLInfo&)info
+ forSSLStatus:(const web::SSLStatus&)status
+ onUrl:(const GURL&)url
+ recoverable:(BOOL)recoverable
+ callback:(SSLErrorCallback)shouldContinue;
+
+// Update the progress.
+- (void)updatedProgress:(float)progress;
+
+// This method is called when a certificate with an error is in use.
+- (void)certificateUsed:(net::X509Certificate*)certificate
+ forHost:(const std::string&)host
+ status:(net::CertStatus)status;
+
+// Called when all the active allowed certificates need to be cleared. This
+// happens during the TrimToURL(), which corresponds to a navigation.
+- (void)clearCertificates;
+@end
+
+#endif // IOS_WEB_NET_CRW_REQUEST_TRACKER_DELEGATE_H_
diff --git a/ios/web/net/request_tracker_data_memoizing_store.h b/ios/web/net/request_tracker_data_memoizing_store.h
new file mode 100644
index 0000000..7ec3e32
--- /dev/null
+++ b/ios/web/net/request_tracker_data_memoizing_store.h
@@ -0,0 +1,182 @@
+// 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.
+
+#ifndef IOS_WEB_NET_REQUEST_TRACKER_DATA_MEMOIZING_STORE_H_
+#define IOS_WEB_NET_REQUEST_TRACKER_DATA_MEMOIZING_STORE_H_
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/synchronization/lock.h"
+#include "ios/web/public/web_thread.h"
+
+namespace web {
+
+// RequestTrackerDataMemoizingStore is a thread-safe container that retains
+// reference counted objects that are associated with one or more request
+// trackers. Objects are identified by an int and only a single reference to a
+// given object is retained.
+template <typename T>
+class RequestTrackerDataMemoizingStore {
+ public:
+ RequestTrackerDataMemoizingStore() : next_item_id_(1) {}
+
+ ~RequestTrackerDataMemoizingStore() {
+ DCHECK_EQ(0U, id_to_item_.size()) << "Failed to outlive request tracker";
+ }
+
+ // Store adds |item| to this collection, associates it with the given request
+ // tracker id and returns an opaque identifier for it. If |item| is already
+ // known, the same identifier will be returned.
+ int Store(T* item, int request_tracker_id) {
+ DCHECK(item);
+ base::AutoLock auto_lock(lock_);
+
+ int item_id = 0;
+
+ // Is this item alread known?
+ typename ReverseItemMap::iterator item_iter = item_to_id_.find(item);
+ if (item_iter == item_to_id_.end()) {
+ item_id = next_item_id_++;
+ // Use 0 as an invalid item_id value. In the unlikely event that
+ // next_item_id_ wraps around, reset it to 1.
+ if (next_item_id_ == 0)
+ next_item_id_ = 1;
+ id_to_item_[item_id] = item;
+ item_to_id_[item] = item_id;
+ } else {
+ item_id = item_iter->second;
+ }
+
+ // Update the bidirection request_tracker_id and item_id mappings.
+ UpdateMap(&request_tracker_id_to_item_id_, request_tracker_id, item_id);
+ UpdateMap(&item_id_to_request_tracker_id_, item_id, request_tracker_id);
+
+ DCHECK(item_id);
+ return item_id;
+ }
+
+ // Retrieves a previously Stored() item, identified by |item_id|.
+ // If |item_id| is recognized, |item| will be updated and Retrieve() will
+ // return true.
+ bool Retrieve(int item_id, scoped_refptr<T>* item) {
+ base::AutoLock auto_lock(lock_);
+
+ typename ItemMap::iterator iter = id_to_item_.find(item_id);
+ if (iter == id_to_item_.end())
+ return false;
+ if (item)
+ *item = iter->second;
+ return true;
+ }
+
+ // Removes any items associtated only with |request_tracker_id|.
+ void RemoveForRequestTracker(int request_tracker_id) {
+ RemoveRequestTrackerItems(request_tracker_id);
+ }
+
+ private:
+ typedef std::multimap<int, int> IDMap;
+ typedef std::map<int, scoped_refptr<T>> ItemMap;
+ typedef std::map<T*, int, typename T::LessThan> ReverseItemMap;
+
+ template <typename M>
+ struct MatchSecond {
+ explicit MatchSecond(const M& t) : value(t) {}
+
+ template <typename Pair>
+ bool operator()(const Pair& p) const {
+ return (value == p.second);
+ }
+
+ M value;
+ };
+
+ // Updates |map| to contain a key_id->value_id pair, ensuring that a duplicate
+ // pair is not added if it was already present.
+ static void UpdateMap(IDMap* map, int key_id, int value_id) {
+ auto matching_key_range = map->equal_range(key_id);
+ if (std::find_if(matching_key_range.first, matching_key_range.second,
+ MatchSecond<int>(value_id)) == matching_key_range.second) {
+ map->insert(std::make_pair(key_id, value_id));
+ }
+ }
+
+ // Remove the item specified by |item_id| from id_to_item_ and item_to_id_.
+ // NOTE: the caller (RemoveRequestTrackerItems) must hold lock_.
+ void RemoveInternal(int item_id) {
+ typename ItemMap::iterator item_iter = id_to_item_.find(item_id);
+ DCHECK(item_iter != id_to_item_.end());
+
+ typename ReverseItemMap::iterator id_iter =
+ item_to_id_.find(item_iter->second.get());
+ DCHECK(id_iter != item_to_id_.end());
+ item_to_id_.erase(id_iter);
+
+ id_to_item_.erase(item_iter);
+ }
+
+ // Removes all the items associated with the specified request tracker from
+ // the store.
+ void RemoveRequestTrackerItems(int request_tracker_id) {
+ base::AutoLock auto_lock(lock_);
+
+ // Iterate through all the item ids for that request tracker.
+ auto request_tracker_ids =
+ request_tracker_id_to_item_id_.equal_range(request_tracker_id);
+ for (IDMap::iterator ids_iter = request_tracker_ids.first;
+ ids_iter != request_tracker_ids.second; ++ids_iter) {
+ int item_id = ids_iter->second;
+ // Find all the request trackers referring to this item id in
+ // item_id_to_request_tracker_id_, then locate the request tracker being
+ // removed within that range.
+ auto item_ids = item_id_to_request_tracker_id_.equal_range(item_id);
+ IDMap::iterator proc_iter =
+ std::find_if(item_ids.first, item_ids.second,
+ MatchSecond<int>(request_tracker_id));
+ DCHECK(proc_iter != item_ids.second);
+
+ // Before removing, determine if no other request trackers refer to the
+ // current item id. If |proc_iter| (the current request tracker) is the
+ // lower bound of request trackers containing the current item id and if
+ // |next_proc_iter| is the upper bound (the first request tracker that
+ // does not), then only one request tracker, the one being removed, refers
+ // to the item id.
+ IDMap::iterator next_proc_iter = proc_iter;
+ ++next_proc_iter;
+ bool last_request_tracker_for_item_id =
+ (proc_iter == item_ids.first && next_proc_iter == item_ids.second);
+ item_id_to_request_tracker_id_.erase(proc_iter);
+
+ if (last_request_tracker_for_item_id) {
+ // The current item id is not referenced by any other request trackers,
+ // so remove it from id_to_item_ and item_to_id_.
+ RemoveInternal(item_id);
+ }
+ }
+ if (request_tracker_ids.first != request_tracker_ids.second)
+ request_tracker_id_to_item_id_.erase(request_tracker_ids.first,
+ request_tracker_ids.second);
+ }
+
+ // Bidirectional mapping between items and the request trackers they are
+ // associated with.
+ IDMap request_tracker_id_to_item_id_;
+ IDMap item_id_to_request_tracker_id_;
+ // Bidirectional mappings between items and the item-level identifiers that
+ // have been assigned to them.
+ ItemMap id_to_item_;
+ ReverseItemMap item_to_id_;
+
+ // The ID to assign to the next item stored.
+ int next_item_id_;
+
+ // This lock protects: request_tracker_id_to_item_id_,
+ // item_id_to_request_tracker_id_, id_to_item_, and item_to_id_.
+ base::Lock lock_;
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NET_REQUEST_TRACKER_DATA_MEMOIZING_STORE_H_
diff --git a/ios/web/net/request_tracker_factory_impl.h b/ios/web/net/request_tracker_factory_impl.h
new file mode 100644
index 0000000..2d0bfc4
--- /dev/null
+++ b/ios/web/net/request_tracker_factory_impl.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef IOS_WEB_NET_REQUEST_TRACKER_FACTORY_IMPL_H_
+#define IOS_WEB_NET_REQUEST_TRACKER_FACTORY_IMPL_H_
+
+#include <string>
+
+#include "base/mac/scoped_nsobject.h"
+#include "ios/net/request_tracker.h"
+
+namespace web {
+
+class RequestTrackerFactoryImpl
+ : public net::RequestTracker::RequestTrackerFactory {
+ public:
+ explicit RequestTrackerFactoryImpl(const std::string& application_scheme);
+ ~RequestTrackerFactoryImpl() override;
+
+ private:
+ // RequestTracker::RequestTrackerFactory implementation
+ bool GetRequestTracker(NSURLRequest* request,
+ base::WeakPtr<net::RequestTracker>* tracker) override;
+
+ base::scoped_nsobject<NSString> application_scheme_;
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NET_REQUEST_TRACKER_FACTORY_IMPL_H_
diff --git a/ios/web/net/request_tracker_factory_impl.mm b/ios/web/net/request_tracker_factory_impl.mm
new file mode 100644
index 0000000..8973d3c
--- /dev/null
+++ b/ios/web/net/request_tracker_factory_impl.mm
@@ -0,0 +1,49 @@
+// 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_factory_impl.h"
+
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ios/web/net/request_group_util.h"
+#include "ios/web/net/request_tracker_impl.h"
+
+namespace web {
+
+RequestTrackerFactoryImpl::RequestTrackerFactoryImpl(
+ const std::string& application_scheme) {
+ if (!application_scheme.empty()) {
+ application_scheme_.reset(
+ [base::SysUTF8ToNSString(application_scheme) copy]);
+ DCHECK(application_scheme_);
+ }
+}
+
+RequestTrackerFactoryImpl::~RequestTrackerFactoryImpl() {
+}
+
+bool RequestTrackerFactoryImpl::GetRequestTracker(
+ NSURLRequest* request,
+ base::WeakPtr<net::RequestTracker>* tracker) {
+ DCHECK(tracker);
+ DCHECK(!tracker->get());
+ NSString* request_group_id =
+ web::ExtractRequestGroupIDFromRequest(request, application_scheme_);
+ if (!request_group_id) {
+ // There was no request_group_id, so the request was from something like a
+ // data: or file: URL.
+ return true;
+ }
+ RequestTrackerImpl* tracker_impl =
+ RequestTrackerImpl::GetTrackerForRequestGroupID(request_group_id);
+ if (tracker_impl)
+ *tracker = tracker_impl->GetWeakPtr();
+ // If there is a request group ID, but no associated tracker, return false.
+ // This usually happens when the tab has been closed, but can maybe also
+ // happen in other cases (see http://crbug.com/228397).
+ return tracker->get() != nullptr;
+}
+
+} // namespace web
diff --git a/ios/web/net/request_tracker_impl.h b/ios/web/net/request_tracker_impl.h
new file mode 100644
index 0000000..45aaa03
--- /dev/null
+++ b/ios/web/net/request_tracker_impl.h
@@ -0,0 +1,399 @@
+// 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.
+
+#ifndef IOS_WEB_NET_REQUEST_TRACKER_IMPL_H_
+#define IOS_WEB_NET_REQUEST_TRACKER_IMPL_H_
+
+#import <Foundation/Foundation.h>
+#include <map>
+#include <set>
+
+#include "base/callback_forward.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#import "ios/net/request_tracker.h"
+#import "ios/web/net/crw_request_tracker_delegate.h"
+#include "ios/web/public/web_thread.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
+
+@class SSLCarrier;
+@class CRWSSLCarrier;
+class SSLErrorInfo;
+struct TrackerCounts;
+
+namespace content {
+struct SSLStatus;
+}
+
+namespace net {
+class HttpResponseHeaders;
+class URLRequest;
+class URLRequestContext;
+class SSLInfo;
+class X509Certificate;
+}
+
+namespace web {
+
+class BrowserState;
+class CertificatePolicyCache;
+
+// Structure to capture the current state of a page.
+struct PageCounts {
+ public:
+ PageCounts() : finished(0),
+ finished_bytes(0),
+ unfinished(0),
+ unfinished_no_estimate(0),
+ unfinished_no_estimate_bytes_done(0),
+ unfinished_estimated_bytes_left(0),
+ unfinished_estimate_bytes_done(0),
+ largest_byte_size_known(0) {
+ };
+
+ // Count of finished requests.
+ uint64_t finished;
+ // Total bytes count dowloaded for all finished requests.
+ uint64_t finished_bytes;
+ // Count of unfinished requests.
+ uint64_t unfinished;
+ // Count of unfinished requests with unknown size.
+ uint64_t unfinished_no_estimate;
+ // Total bytes count dowloaded for unfinished requests of unknown size.
+ uint64_t unfinished_no_estimate_bytes_done;
+ // Count of unfinished requests with an estimated size.
+ uint64_t unfinished_estimated_bytes_left;
+ // Total bytes count dowloaded for unfinished requests with an estimated size.
+ uint64_t unfinished_estimate_bytes_done;
+ // Size of the request with the most bytes on the page.
+ uint64_t largest_byte_size_known;
+};
+
+// RequestTrackerImpl captures and stores all the network requests that
+// initiated from a particular tab. It only keeps the URLs and eventually, if
+// available, the expected length of the result and the length of the received
+// data so far as this is used to build a progress bar for a page.
+// Note that the Request tracker has no notion of a page, it only tracks the
+// requests by tab. In order for the tracker to know that a request is for a
+// page or a subresource it is necessary for the tab to call StartPageLoad()
+// with the URL of the page once it is known to avoid storing all the requests
+// forever.
+//
+// The consumer needs to implement the CRWRequestTrackerImplDelegate protocol
+// and needs to call StartPageLoad() and FinishPageLoad() to indicate the page
+// boundaries. StartPageLoad() will also have the side effect of clearing past
+// requests from memory. The consumer is assumed to be on the UI thread at all
+// times.
+//
+// RequestTrackerImpl objects are created and destroyed on the UI thread and
+// must be owned by some other object on the UI thread by way of a
+// scoped_refptr, as returned by the public static constructor method,
+// CreateTrackerForRequestGroupID. All consumer API methods will be called
+// through this pointer.
+
+class RequestTrackerImpl;
+
+struct RequestTrackerImplTraits {
+ static void Destruct(const RequestTrackerImpl* t);
+};
+
+class RequestTrackerImpl
+ : public base::RefCountedThreadSafe<RequestTrackerImpl,
+ RequestTrackerImplTraits>,
+ public net::RequestTracker {
+ public:
+#pragma mark Public Consumer API
+ // Consumer API methods should only be called on the UI thread.
+
+ // Create a new RequestTrackerImpl associated with a particular tab. The
+ // profile must be the one associated to the given tab. This method has to be
+ // called *once* per tab and needs to be called before triggering any network
+ // request. The caller of CreateTrackerForRequestGroupID owns the tracker, and
+ // this class also keeps a global map of all active trackers. When the owning
+ // object releases it, the class removes it from the global map.
+ static scoped_refptr<RequestTrackerImpl> CreateTrackerForRequestGroupID(
+ NSString* request_group_id,
+ BrowserState* browser_state,
+ net::URLRequestContextGetter* context_getter,
+ id<CRWRequestTrackerDelegate> delegate);
+
+ // The network layer has no way to know which network request is the primary
+ // one for a page load. The tab knows, either because it initiated the page
+ // load via the URL or received a callback informing it of the page change.
+ // Every time this happens the tab should call this method to clear the
+ // resources tracked.
+ // This will forget all the finished requests made before this URL in history.
+ // user_info is to be used by the consumer to store more additional specific
+ // info about the page, as an URL is not unique.
+ void StartPageLoad(const GURL& url, id user_info);
+
+ // In order to properly provide progress information the tracker needs to know
+ // when the page is fully loaded. |load_success| indicates if the page
+ // successfully loaded.
+ void FinishPageLoad(const GURL& url, bool load_success);
+
+ // Tells the tracker that history.pushState() or history.replaceState()
+ // changed the page URL.
+ void HistoryStateChange(const GURL& url);
+
+ // Marks the tracker as closed. An owner must call this before the tracker is
+ // deleted. Once closed, no further calls will be made to the delegate.
+ void Close();
+
+ // Call |callback| on the UI thread after any pending request cancellations
+ // have completed on the IO thread.
+ // This should be used to delete a profile for which all of the trackers
+ // that use the profile's request context are closed.
+ static void RunAfterRequestsCancel(const base::Closure& callback);
+
+ // Block until all pending IO thread activity has completed. This should only
+ // be used when Chrome is shutting down, and after all request trackers have
+ // had Close() called on them.
+ static void BlockUntilTrackersShutdown();
+
+#pragma mark Client utility methods.
+
+ // Finds the tracker given the tab ID. As calling this method involves a lock
+ // it is expected that the provider will call it only once.
+ // Returns a weak pointer, which should only be dereferenced on the IO thread.
+ // Returns NULL if no tracker exists for |request_group_id|.
+ static RequestTrackerImpl* GetTrackerForRequestGroupID(
+ NSString* request_group_id);
+
+ // Callback from the UI to allow or deny a particular certificate.
+ void ErrorCallback(CRWSSLCarrier* carrier, bool allow);
+
+ // Utility method for clients to post tasks to the IO thread from the UI
+ // thread.
+ void PostIOTask(const base::Closure& task);
+
+ // Utility method for clients to post tasks to the IO thread from the IO
+ // thread.
+ void ScheduleIOTask(const base::Closure& task);
+
+ // Utility method for clients to conditionally post tasks to the UI thread
+ // from the IO thread. The task will not be posted if the request tracker
+ // is in the process of closing (thus it "is open").
+ void PostUITaskIfOpen(const base::Closure& task);
+ // Static version of the method, where |tracker| is a RequestTrackerImpl
+ // passed as a base::WeakPtr<RequestTracker>.
+ static void PostUITaskIfOpen(const base::WeakPtr<RequestTracker> tracker,
+ const base::Closure& task);
+
+ // Sets the cache mode. Must be called from the UI thread.
+ void SetCacheModeFromUIThread(RequestTracker::CacheMode mode);
+
+#pragma mark Testing methods
+
+ void SetCertificatePolicyCacheForTest(web::CertificatePolicyCache* cache);
+
+#pragma mark Accessors used by internal classes and network clients.
+ int identifier() { return identifier_; }
+ bool has_mixed_content() { return has_mixed_content_; }
+
+ // RequestTracker implementation.
+ void StartRequest(net::URLRequest* request) override;
+ void CaptureHeaders(net::URLRequest* request) override;
+ void CaptureExpectedLength(const net::URLRequest* request,
+ uint64_t length) override;
+ void CaptureReceivedBytes(const net::URLRequest* request,
+ uint64_t byte_count) override;
+ void CaptureCertificatePolicyCache(
+ const net::URLRequest* request,
+ const SSLCallback& should_continue) override;
+ void StopRequest(net::URLRequest* request) override;
+ void StopRedirectedRequest(net::URLRequest* request) override;
+ void OnSSLCertificateError(const net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool recoverable,
+ const SSLCallback& should_continue) override;
+ net::URLRequestContext* GetRequestContext() override;
+
+ private:
+ friend class base::RefCountedThreadSafe<RequestTrackerImpl>;
+ friend struct RequestTrackerImplTraits;
+
+#pragma mark Object lifecycle API
+ // Private. RequestTrackerImpls are created through
+ // CreateTrackerForRequestGroupID().
+ RequestTrackerImpl(NSString* request_group_id,
+ net::URLRequestContextGetter* context_getter,
+ id<CRWRequestTrackerDelegate> delegate);
+
+ void InitOnIOThread(
+ const scoped_refptr<web::CertificatePolicyCache>& policy_cache);
+
+ // Private destructor because the object is reference counted. A no-op; the
+ // useful destruction work happens in Destruct().
+ ~RequestTrackerImpl() override;
+
+ // Handles pre-destruction destruction tasks. This is invoked by
+ // RequestTrackerImplTraits::Destruct whenever the reference count of a
+ // RequestTrackerImpl is zero, and this will untimately delete the
+ // RequestTrackerImpl.
+ void Destruct();
+
+#pragma mark Private Provider API
+ // Private methods that implement provider API features. All are only called
+ // on the IO thread.
+
+ // Called when something has changed (network load progress or SSL status)
+ // that the consumer should know about. Notifications are asynchronous and
+ // batched.
+ void Notify();
+
+ // If no other notifications are pending, notifies the consumer of SSL status
+ // and load progress.
+ void StackNotification();
+
+ // Notify the consumer about the SSL status of this tracker's page load.
+ void SSLNotify();
+
+ // If the counts is for a request currently waiting for the user to approve it
+ // will reevaluate the approval.
+ void EvaluateSSLCallbackForCounts(TrackerCounts* counts);
+
+ // Loop through all the requests waiting for approval and invoke
+ // |-evaluateSSLCallbackForCounts:| on all the ones with an |UNKNOWN|
+ // judgment.
+ void ReevaluateCallbacksForAllCounts();
+
+ // To cancel a rejected request due to a SSL issue.
+ void CancelRequestForCounts(TrackerCounts* counts);
+
+ // Estimate the page load progress. Returns -1 if the progress didn't change
+ // since the last time this method was invoked.
+ float EstimatedProgress();
+
+ // The URL change notification is often late, therefore the mixed content
+ // status and the certificate policies may need to be recomputed.
+ void RecomputeMixedContent(const TrackerCounts* split_position);
+ void RecomputeCertificatePolicy(const TrackerCounts* split_position);
+
+ // Remove all finished request up to the last instance of |url|. If url is not
+ // found, this will clear all the requests.
+ void TrimToURL(const GURL& url, id user_info);
+
+ // Sets page_url_ to the new URL if it's a valid history state change (i.e.
+ // the URL's have the same origin) and if the tab is currently loading.
+ void HistoryStateChangeToURL(const GURL& full_url);
+
+ // Note that the page started by a call to Trim is no longer loading.
+ // |load_success| indicates if the page successfully loaded.
+ void StopPageLoad(const GURL& url, bool load_success);
+
+ // Cancels all the requests in |live_requests_|.
+ void CancelRequests();
+
+#pragma mark Private Consumer API
+ // Private methods that call into delegate methods.
+
+ // Notify* methods are posted to the UI thread by the provider API
+ // methods.
+
+ // Has the delegate handle |headers| for |request_url|.
+ void NotifyResponseHeaders(net::HttpResponseHeaders* headers,
+ const GURL& request_url);
+
+ // Notifies the deleage of certificate use.
+ void NotifyCertificateUsed(net::X509Certificate* certificate,
+ const std::string& host,
+ net::CertStatus status);
+
+ // Notifies the deleate of a load completion estimate.
+ void NotifyUpdatedProgress(float estimate);
+
+ // Has the delegate clear SSL certificates.
+ void NotifyClearCertificates();
+
+ // Notifies the delegate of an SSL status update.
+ void NotifyUpdatedSSLStatus(base::scoped_nsobject<CRWSSLCarrier> carrier);
+
+ // Calls the delegate method to present an SSL error interstitial.
+ void NotifyPresentSSLError(base::scoped_nsobject<CRWSSLCarrier> carrier,
+ bool recoverable);
+
+#pragma mark Internal utilities for task posting
+ // Posts |task| to |thread|. Must not be called from |thread|. If |thread| is
+ // the IO thread, silently returns if |is_closing_| is true.
+ void PostTask(const base::Closure& task, web::WebThread::ID thread);
+
+ // Posts |block| to |thread|, safely passing in |caller| to |block|.
+ void PostBlock(id caller, void (^block)(id), web::WebThread::ID thread);
+
+#pragma mark Other internal methods.
+ // Returns the current state of the page.
+ PageCounts pageCounts();
+
+ // Like description, but cannot be called from any thread. It must be called
+ // only from the IO thread.
+ NSString* UnsafeDescription();
+
+ // Generates a string unique to this RequestTrackerImpl to use with the
+ // CRWNetworkActivityIndicatorManager.
+ NSString* GetNetworkActivityKey();
+
+#pragma mark Non thread-safe fields, only accessed from the main thread.
+ // The RequestTrackerImpl delegate. All changes and access to this object
+ // should be done on the main thread.
+ id<CRWRequestTrackerDelegate> delegate_; // Weak.
+
+#pragma mark Non thread-safe fields, only accessed from the IO thread.
+ // All the tracked requests for the page, indexed by net::URLRequest (Cast as
+ // a void* to avoid the temptation of accessing it from the wrong thread).
+ // This map is not exhaustive: it is only meant to estimate the loading
+ // progress, and thus requests corresponding to old navigation events are not
+ // in it.
+ std::map<const void*, TrackerCounts*> counts_by_request_;
+ // All the live requests associated with the tracker.
+ std::set<net::URLRequest*> live_requests_;
+ // A list of all the TrackerCounts, including the finished ones.
+ ScopedVector<TrackerCounts> counts_;
+ // The system shall never allow the page load estimate to go back.
+ float previous_estimate_;
+ // Index of the first request to consider for building the estimation.
+ unsigned int estimate_start_index_;
+ // How many notifications are currently queued, to avoid notifying too often.
+ int notification_depth_;
+ // The tracker containing the error currently presented to the user.
+ TrackerCounts* current_ssl_error_;
+ // Set to |YES| if the page has mixed content
+ bool has_mixed_content_;
+ // Set to true if between TrimToURL and StopPageLoad.
+ bool is_loading_;
+ // Set to true in TrimToURL if starting a new estimate round. Set to false by
+ // StartRequest once the new round is started.
+ bool new_estimate_round_;
+
+#pragma mark Other fields.
+ scoped_refptr<web::CertificatePolicyCache> policy_cache_;
+ // If |true| all the requests should be static file requests, otherwise all
+ // the requests should be network requests. This is a constant initialized
+ // in the constructor and read in IO and UI threads.
+ const bool is_for_static_file_requests_;
+
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ // Current page URL, as far as we know.
+ GURL page_url_;
+ // Userinfo attached to the page, passed back by the delegate.
+ base::scoped_nsobject<id> user_info_;
+ // A tracker identifier (a simple increasing number) used to store
+ // certificates.
+ int identifier_;
+ // The string that identifies the tab this tracker serves. Used to index
+ // g_trackers.
+ base::scoped_nsobject<NSString> request_group_id_;
+ // Flag to synchronize deletion and callback creation. Lives on the IO thread.
+ // True when this tracker has beed Close()d. If this is the case, no further
+ // references to it should be generated (for example by binding it into a
+ // callback), and the expectation is that it will soon be deleted.
+ bool is_closing_;
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NET_REQUEST_TRACKER_IMPL_H_
diff --git a/ios/web/net/request_tracker_impl.mm b/ios/web/net/request_tracker_impl.mm
new file mode 100644
index 0000000..f274771
--- /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
diff --git a/ios/web/net/request_tracker_impl_unittest.mm b/ios/web/net/request_tracker_impl_unittest.mm
new file mode 100644
index 0000000..bb87192
--- /dev/null
+++ b/ios/web/net/request_tracker_impl_unittest.mm
@@ -0,0 +1,498 @@
+// Copyright 2012 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 "base/logging.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ios/web/public/cert_policy.h"
+#include "ios/web/public/certificate_policy_cache.h"
+#include "ios/web/public/ssl_status.h"
+#include "ios/web/public/test/test_browser_state.h"
+#include "ios/web/public/test/test_web_thread.h"
+#include "net/cert/x509_certificate.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "net/url_request/url_request_job_factory_impl.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 "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#include "third_party/ocmock/gtest_support.h"
+
+@interface RequestTrackerNotificationReceiverTest
+ : NSObject<CRWRequestTrackerDelegate> {
+ @public
+ float value_;
+ float max_;
+ @private
+ base::scoped_nsobject<NSString> error_;
+ scoped_refptr<net::HttpResponseHeaders> headers_;
+}
+
+- (NSString*)error;
+- (net::HttpResponseHeaders*)headers;
+@end
+
+@implementation RequestTrackerNotificationReceiverTest
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ value_ = 0.0f;
+ max_ = 0.0f;
+ }
+ return self;
+}
+
+- (BOOL)isForStaticFileRequests {
+ return NO;
+}
+
+- (void)updatedProgress:(float)progress {
+ if (progress > 0.0f) {
+ if (progress < value_) {
+ error_.reset(
+ [[NSString stringWithFormat:
+ @"going down from %f to %f", value_, progress] retain]);
+ }
+ value_ = progress;
+ } else {
+ value_ = 0.0f;
+ }
+ if (value_ > max_) {
+ max_ = value_;
+ }
+}
+
+- (NSString*)error {
+ return error_;
+}
+
+- (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
+ requestUrl:(const GURL&)requestUrl {
+ headers_ = headers;
+}
+
+- (net::HttpResponseHeaders*)headers {
+ return headers_.get();
+}
+
+- (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
+ forPageUrl:(const GURL&)url
+ userInfo:(id)userInfo {
+ // Nothing. yet.
+}
+
+- (void)presentSSLError:(const net::SSLInfo&)info
+ forSSLStatus:(const web::SSLStatus&)status
+ onUrl:(const GURL&)url
+ recoverable:(BOOL)recoverable
+ callback:(SSLErrorCallback)shouldContinue {
+ // Nothing, yet.
+}
+
+- (void)certificateUsed:(net::X509Certificate*)certificate
+ forHost:(const std::string&)host
+ status:(net::CertStatus)status {
+ // Nothing, yet.
+}
+
+- (void)clearCertificates {
+ // Nothing, yet.
+}
+
+- (void)handlePassKitObject:(NSData*)data {
+ // Nothing yet.
+}
+
+@end
+
+namespace {
+
+// Used and incremented each time a tabId is created.
+int g_count = 0;
+
+class RequestTrackerTest : public PlatformTest {
+ public:
+ RequestTrackerTest()
+ : loop_(base::MessageLoop::TYPE_IO),
+ ui_thread_(web::WebThread::UI, &loop_),
+ io_thread_(web::WebThread::IO, &loop_){};
+
+ ~RequestTrackerTest() override {}
+
+ void SetUp() override {
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
+ request_group_id_.reset(
+ [[NSString stringWithFormat:@"test%d", g_count++] retain]);
+
+ receiver_.reset([[RequestTrackerNotificationReceiverTest alloc] init]);
+ tracker_ = web::RequestTrackerImpl::CreateTrackerForRequestGroupID(
+ request_group_id_,
+ &browser_state_,
+ browser_state_.GetRequestContext(),
+ receiver_);
+ }
+
+ void TearDown() override {
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
+ tracker_->Close();
+ }
+
+ base::MessageLoop loop_;
+ web::TestWebThread ui_thread_;
+ web::TestWebThread io_thread_;
+
+ base::scoped_nsobject<RequestTrackerNotificationReceiverTest> receiver_;
+ scoped_refptr<web::RequestTrackerImpl> tracker_;
+ base::scoped_nsobject<NSString> request_group_id_;
+ web::TestBrowserState browser_state_;
+ ScopedVector<net::URLRequestContext> contexts_;
+ ScopedVector<net::URLRequest> requests_;
+ net::URLRequestJobFactoryImpl job_factory_;
+
+ GURL GetURL(size_t i) {
+ std::stringstream ss;
+ ss << "http://www/";
+ ss << i;
+ return GURL(ss.str());
+ }
+
+ GURL GetSecureURL(size_t i) {
+ std::stringstream ss;
+ ss << "https://www/";
+ ss << i;
+ return GURL(ss.str());
+ }
+
+ net::URLRequest* GetRequest(size_t i) {
+ return GetInternalRequest(i, false);
+ }
+
+ net::URLRequest* GetSecureRequest(size_t i) {
+ return GetInternalRequest(i, true);
+ }
+
+ NSString* WaitUntilLoop(bool (^condition)(void)) {
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
+ base::Time maxDate = base::Time::Now() + base::TimeDelta::FromSeconds(10);
+ while (!condition()) {
+ if ([receiver_ error])
+ return [receiver_ error];
+ if (base::Time::Now() > maxDate)
+ return @"Time is up, too slow to go";
+ loop_.RunUntilIdle();
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
+ }
+ return nil;
+ }
+
+ NSString* CheckActive() {
+ NSString* message = WaitUntilLoop(^{
+ return (receiver_.get()->value_ > 0.0f);
+ });
+
+ if (!message && (receiver_.get()->max_ == 0.0f))
+ message = @"Max should be greater than 0.0";
+ return message;
+ }
+
+ void TrimRequest(NSString* tab_id, const GURL& url) {
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
+ receiver_.get()->value_ = 0.0f;
+ receiver_.get()->max_ = 0.0f;
+ tracker_->StartPageLoad(url, nil);
+ }
+
+ void EndPage(NSString* tab_id, const GURL& url) {
+ DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
+ tracker_->FinishPageLoad(url, false);
+ receiver_.get()->value_ = 0.0f;
+ receiver_.get()->max_ = 0.0f;
+ loop_.RunUntilIdle();
+ }
+
+ net::TestJobInterceptor* AddInterceptorToRequest(size_t i) {
+ // |interceptor| will be deleted from |job_factory_|'s destructor.
+ net::TestJobInterceptor* protocol_handler = new net::TestJobInterceptor();
+ job_factory_.SetProtocolHandler("http", protocol_handler);
+ contexts_[i]->set_job_factory(&job_factory_);
+ return protocol_handler;
+ }
+
+ private:
+ net::URLRequest* GetInternalRequest(size_t i, bool secure) {
+ GURL url;
+ if (secure)
+ url = GetSecureURL(requests_.size());
+ else
+ url = GetURL(requests_.size());
+
+ while (i >= requests_.size()) {
+ contexts_.push_back(new net::URLRequestContext());
+ requests_.push_back(contexts_[i]->CreateRequest(url,
+ net::DEFAULT_PRIORITY,
+ NULL).release());
+
+ if (secure) {
+ // Put a valid SSLInfo inside
+ net::HttpResponseInfo* response =
+ const_cast<net::HttpResponseInfo*>(&requests_[i]->response_info());
+
+ response->ssl_info.cert = new net::X509Certificate(
+ "subject", "issuer",
+ base::Time::Now() - base::TimeDelta::FromDays(2),
+ base::Time::Now() + base::TimeDelta::FromDays(2));
+ response->ssl_info.cert_status = 0; // No errors.
+ response->ssl_info.security_bits = 128;
+
+ EXPECT_TRUE(requests_[i]->ssl_info().is_valid());
+ }
+ }
+ EXPECT_TRUE(!secure == !requests_[i]->url().SchemeIsSecure());
+ return requests_[i];
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(RequestTrackerTest);
+};
+
+TEST_F(RequestTrackerTest, OnePage) {
+ // Start a request.
+ tracker_->StartRequest(GetRequest(0));
+ // Start page load.
+ TrimRequest(request_group_id_, GetURL(0));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ // Stop the request.
+ tracker_->StopRequest(GetRequest(0));
+ EndPage(request_group_id_, GetURL(0));
+}
+
+TEST_F(RequestTrackerTest, OneSecurePage) {
+ net::URLRequest* request = GetSecureRequest(0);
+ GURL url = GetSecureURL(0);
+
+ // Start a page.
+ TrimRequest(request_group_id_, url);
+
+ // Start a request.
+ tracker_->StartRequest(request);
+ tracker_->CaptureReceivedBytes(request, 42);
+ EXPECT_NSEQ(nil, CheckActive());
+
+ // Stop the request.
+ tracker_->StopRequest(request);
+
+ EndPage(request_group_id_, url);
+}
+
+TEST_F(RequestTrackerTest, OnePageAndResources) {
+ // Start a page.
+ TrimRequest(request_group_id_, GetURL(0));
+ // Start two requests.
+ tracker_->StartRequest(GetRequest(0));
+ tracker_->StartRequest(GetRequest(1));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->StopRequest(GetRequest(0));
+ tracker_->StartRequest(GetRequest(2));
+ EXPECT_NSEQ(nil, CheckActive());
+ tracker_->StopRequest(GetRequest(1));
+ tracker_->StartRequest(GetRequest(3));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->StopRequest(GetRequest(2));
+ tracker_->StartRequest(GetRequest(4));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->StopRequest(GetRequest(3));
+ tracker_->StopRequest(GetRequest(4));
+ EndPage(request_group_id_, GetURL(0));
+}
+
+TEST_F(RequestTrackerTest, OnePageOneBigImage) {
+ // Start a page.
+ TrimRequest(request_group_id_, GetURL(0));
+ tracker_->StartRequest(GetRequest(0));
+ tracker_->StopRequest(GetRequest(0));
+ tracker_->StartRequest(GetRequest(1));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ tracker_->CaptureExpectedLength(GetRequest(1), 100);
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ tracker_->CaptureReceivedBytes(GetRequest(1), 10);
+ tracker_->StopRequest(GetRequest(1));
+ EndPage(request_group_id_, GetURL(0));
+}
+
+TEST_F(RequestTrackerTest, TwoPagesPostStart) {
+ tracker_->StartRequest(GetRequest(0));
+
+ TrimRequest(request_group_id_, GetURL(0));
+ EXPECT_NSEQ(nil, CheckActive());
+ tracker_->StartRequest(GetRequest(1));
+ tracker_->StartRequest(GetRequest(2));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->StopRequest(GetRequest(0));
+ tracker_->StopRequest(GetRequest(1));
+ tracker_->StopRequest(GetRequest(2));
+ EndPage(request_group_id_, GetURL(0));
+
+ tracker_->StartRequest(GetRequest(3));
+
+ TrimRequest(request_group_id_, GetURL(3));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->StopRequest(GetRequest(3));
+ EndPage(request_group_id_, GetURL(3));
+}
+
+TEST_F(RequestTrackerTest, TwoPagesPreStart) {
+ tracker_->StartRequest(GetRequest(0));
+
+ TrimRequest(request_group_id_, GetURL(0));
+ EXPECT_NSEQ(nil, CheckActive());
+ tracker_->StartRequest(GetRequest(1));
+ tracker_->StartRequest(GetRequest(2));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->StopRequest(GetRequest(0));
+ tracker_->StopRequest(GetRequest(1));
+ tracker_->StopRequest(GetRequest(2));
+ EndPage(request_group_id_, GetURL(0));
+
+ TrimRequest(request_group_id_, GetURL(3));
+ tracker_->StartRequest(GetRequest(3));
+ tracker_->StopRequest(GetRequest(3));
+ EndPage(request_group_id_, GetURL(3));
+}
+
+TEST_F(RequestTrackerTest, TwoPagesNoWait) {
+ tracker_->StartRequest(GetRequest(0));
+
+ TrimRequest(request_group_id_, GetURL(0));
+ EXPECT_NSEQ(nil, CheckActive());
+ tracker_->StartRequest(GetRequest(1));
+ tracker_->StartRequest(GetRequest(2));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->StopRequest(GetRequest(0));
+ tracker_->StopRequest(GetRequest(1));
+ tracker_->StopRequest(GetRequest(2));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ TrimRequest(request_group_id_, GetURL(3));
+ tracker_->StartRequest(GetRequest(3));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ tracker_->StopRequest(GetRequest(3));
+ EXPECT_NSEQ(nil, CheckActive());
+
+ EndPage(request_group_id_, GetURL(3));
+}
+
+TEST_F(RequestTrackerTest, CaptureHeaders) {
+ std::string headers =
+ "HTTP/1.1 200 OK\n"
+ "content-type: multipart/mixed; boundary=inner\n"
+ "content-disposition: attachment; filename=\"name.pdf\"\n"
+ "X-Auto-Login: Hello World\n\n";
+ for (size_t i = 0; i < headers.length(); i++) {
+ if (headers.data()[i] == '\n')
+ const_cast<char*>(headers.data())[i] = '\0';
+ }
+ net::URLRequest* request = GetRequest(0);
+ const_cast<net::HttpResponseInfo&>(request->response_info()).headers =
+ new net::HttpResponseHeaders(headers);
+ // |job| will be owned by |request| and released from its destructor.
+ net::URLRequestTestJob* job = new net::URLRequestTestJob(
+ request, request->context()->network_delegate(), headers, "", false);
+ AddInterceptorToRequest(0)->set_main_intercept_job(job);
+ request->Start();
+
+ tracker_->StartRequest(request);
+ tracker_->CaptureHeaders(request);
+ tracker_->StopRequest(request);
+ loop_.RunUntilIdle();
+ EXPECT_TRUE([receiver_ headers]->HasHeaderValue("X-Auto-Login",
+ "Hello World"));
+ std::string mimeType;
+ EXPECT_TRUE([receiver_ headers]->GetMimeType(&mimeType));
+ EXPECT_EQ("multipart/mixed", mimeType);
+ EXPECT_TRUE([receiver_ headers]->HasHeaderValue(
+ "Content-Disposition", "attachment; filename=\"name.pdf\""));
+}
+
+// Do-nothing mock CertificatePolicyCache. Allows all certs for all hosts.
+class MockCertificatePolicyCache : public web::CertificatePolicyCache {
+ public:
+ MockCertificatePolicyCache() {}
+
+ void AllowCertForHost(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error) override {
+ }
+
+ web::CertPolicy::Judgment QueryPolicy(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error) override {
+ return web::CertPolicy::Judgment::ALLOWED;
+ }
+
+ void ClearCertificatePolicies() override {
+ }
+
+ private:
+ ~MockCertificatePolicyCache() override {}
+};
+
+void TwoStartsSSLCallback(bool* called, bool ok) {
+ *called = true;
+}
+
+// crbug/386180
+TEST_F(RequestTrackerTest, DISABLED_TwoStartsNoEstimate) {
+ net::X509Certificate* cert =
+ new net::X509Certificate("subject", "issuer", base::Time::Now(),
+ base::Time::Max());
+ net::SSLInfo ssl_info;
+ ssl_info.cert = cert;
+ ssl_info.cert_status = net::CERT_STATUS_AUTHORITY_INVALID;
+ scoped_refptr<MockCertificatePolicyCache> cache;
+ tracker_->SetCertificatePolicyCacheForTest(cache.get());
+ TrimRequest(request_group_id_, GetSecureURL(0));
+ tracker_->StartRequest(GetSecureRequest(0));
+ tracker_->StartRequest(GetSecureRequest(1));
+ bool request_0_called = false;
+ bool request_1_called = false;
+ tracker_->OnSSLCertificateError(GetSecureRequest(0), ssl_info, true,
+ base::Bind(&TwoStartsSSLCallback,
+ &request_0_called));
+ tracker_->OnSSLCertificateError(GetSecureRequest(1), ssl_info, true,
+ base::Bind(&TwoStartsSSLCallback,
+ &request_1_called));
+ EXPECT_TRUE(request_0_called);
+ EXPECT_TRUE(request_1_called);
+}
+
+} // namespace
diff --git a/ios/web/net/web_http_protocol_handler_delegate.h b/ios/web/net/web_http_protocol_handler_delegate.h
new file mode 100644
index 0000000..e885134
--- /dev/null
+++ b/ios/web/net/web_http_protocol_handler_delegate.h
@@ -0,0 +1,30 @@
+// 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.
+
+#ifndef IOS_WEB_NET_WEB_HTTP_PROTOCOL_HANDLER_DELEGATE_H_
+#define IOS_WEB_NET_WEB_HTTP_PROTOCOL_HANDLER_DELEGATE_H_
+
+#include "base/memory/ref_counted.h"
+#import "ios/net/crn_http_protocol_handler.h"
+
+namespace web {
+
+// Web-specific implementation of net::HTTPProtocolHandlerDelegate.
+class WebHTTPProtocolHandlerDelegate : public net::HTTPProtocolHandlerDelegate {
+ public:
+ WebHTTPProtocolHandlerDelegate(net::URLRequestContextGetter* default_getter);
+ ~WebHTTPProtocolHandlerDelegate() override;
+
+ // net::HTTPProtocolHandlerDelegate implementation:
+ bool CanHandleRequest(NSURLRequest* request) override;
+ bool IsRequestSupported(NSURLRequest* request) override;
+ net::URLRequestContextGetter* GetDefaultURLRequestContext() override;
+
+ private:
+ scoped_refptr<net::URLRequestContextGetter> default_getter_;
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NET_WEB_HTTP_PROTOCOL_HANDLER_DELEGATE_H_
diff --git a/ios/web/net/web_http_protocol_handler_delegate.mm b/ios/web/net/web_http_protocol_handler_delegate.mm
new file mode 100644
index 0000000..9405fbe
--- /dev/null
+++ b/ios/web/net/web_http_protocol_handler_delegate.mm
@@ -0,0 +1,58 @@
+// 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/web_http_protocol_handler_delegate.h"
+
+#include "ios/web/public/url_scheme_util.h"
+#include "ios/web/public/web_client.h"
+#import "ios/web/web_state/ui/crw_static_file_web_view.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
+
+namespace {
+
+bool IsAppSpecificScheme(NSURL* url) {
+ NSString* scheme = [url scheme];
+ if (![scheme length])
+ return false;
+ // Use the GURL implementation, but with a scheme-only URL to avoid
+ // unnecessary parsing in GURL construction.
+ GURL gurl([[scheme stringByAppendingString:@":"] UTF8String]);
+ return web::GetWebClient()->IsAppSpecificURL(gurl);
+}
+
+} // namespace
+
+namespace web {
+
+WebHTTPProtocolHandlerDelegate::WebHTTPProtocolHandlerDelegate(
+ net::URLRequestContextGetter* default_getter)
+ : default_getter_(default_getter) {
+ DCHECK(default_getter_);
+}
+
+WebHTTPProtocolHandlerDelegate::~WebHTTPProtocolHandlerDelegate() {
+}
+
+bool WebHTTPProtocolHandlerDelegate::CanHandleRequest(NSURLRequest* request) {
+ // Accept all the requests. If we declined a request, it would then be passed
+ // to the default iOS network stack, which would possibly load it.
+ // As we want to control what is loaded, we have to prevent the default stack
+ // from loading anything.
+ return true;
+}
+
+bool WebHTTPProtocolHandlerDelegate::IsRequestSupported(NSURLRequest* request) {
+ return web::UrlHasWebScheme([request URL]) ||
+ [CRWStaticFileWebView isStaticFileRequest:request] ||
+ (IsAppSpecificScheme([request URL]) &&
+ IsAppSpecificScheme([request mainDocumentURL]));
+}
+
+net::URLRequestContextGetter*
+WebHTTPProtocolHandlerDelegate::GetDefaultURLRequestContext() {
+ return default_getter_.get();
+}
+
+} // namespace web
diff --git a/ios/web/net/web_http_protocol_handler_delegate_unittest.mm b/ios/web/net/web_http_protocol_handler_delegate_unittest.mm
new file mode 100644
index 0000000..5045374
--- /dev/null
+++ b/ios/web/net/web_http_protocol_handler_delegate_unittest.mm
@@ -0,0 +1,128 @@
+// 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/web_http_protocol_handler_delegate.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/thread_task_runner_handle.h"
+#include "ios/web/public/web_client.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace web {
+
+namespace {
+
+// Test application specific scheme.
+const char kAppSpecificScheme[] = "appspecific";
+
+// URLs expected to be supported.
+const char* kSupportedURLs[] = {
+ "http://foo.com",
+ "https://foo.com",
+ "data:text/html;charset=utf-8,Hello",
+};
+
+// URLs expected to be unsupported.
+const char* kUnsupportedURLs[] = {
+ "foo:blank", // Unknown scheme.
+ "appspecific:blank", // No main document URL.
+};
+
+// Test web client with an application specific scheme.
+class AppSpecificURLTestWebClient : public WebClient {
+ public:
+ bool IsAppSpecificURL(const GURL& url) const override {
+ return url.SchemeIs(kAppSpecificScheme);
+ }
+};
+
+class WebHTTPProtocolHandlerDelegateTest : public testing::Test {
+ public:
+ WebHTTPProtocolHandlerDelegateTest()
+ : context_getter_(new net::TestURLRequestContextGetter(
+ base::ThreadTaskRunnerHandle::Get())),
+ delegate_(new WebHTTPProtocolHandlerDelegate(context_getter_.get())),
+ original_web_client_(GetWebClient()),
+ web_client_(new AppSpecificURLTestWebClient) {
+ SetWebClient(web_client_.get());
+ }
+
+ ~WebHTTPProtocolHandlerDelegateTest() override {
+ SetWebClient(original_web_client_);
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ scoped_refptr<net::URLRequestContextGetter> context_getter_;
+ scoped_ptr<WebHTTPProtocolHandlerDelegate> delegate_;
+ WebClient* original_web_client_; // Stash the web client, Weak.
+ scoped_ptr<AppSpecificURLTestWebClient> web_client_;
+};
+
+} // namespace
+
+TEST_F(WebHTTPProtocolHandlerDelegateTest, IsRequestSupported) {
+ base::scoped_nsobject<NSMutableURLRequest> request;
+
+ for (unsigned int i = 0; i < arraysize(kSupportedURLs); ++i) {
+ base::scoped_nsobject<NSString> url_string(
+ [[NSString alloc] initWithUTF8String:kSupportedURLs[i]]);
+ request.reset([[NSMutableURLRequest alloc]
+ initWithURL:[NSURL URLWithString:url_string]]);
+ EXPECT_TRUE(delegate_->IsRequestSupported(request))
+ << kSupportedURLs[i] << " should be supported.";
+ }
+
+ for (unsigned int i = 0; i < arraysize(kUnsupportedURLs); ++i) {
+ base::scoped_nsobject<NSString> url_string(
+ [[NSString alloc] initWithUTF8String:kUnsupportedURLs[i]]);
+ request.reset([[NSMutableURLRequest alloc]
+ initWithURL:[NSURL URLWithString:url_string]]);
+ EXPECT_FALSE(delegate_->IsRequestSupported(request))
+ << kUnsupportedURLs[i] << " should NOT be supported.";
+ }
+
+ // Application specific scheme with main document URL.
+ request.reset([[NSMutableURLRequest alloc]
+ initWithURL:[NSURL URLWithString:@"appspecific:blank"]]);
+ [request setMainDocumentURL:[NSURL URLWithString:@"http://foo"]];
+ EXPECT_FALSE(delegate_->IsRequestSupported(request));
+ [request setMainDocumentURL:[NSURL URLWithString:@"appspecific:main"]];
+ EXPECT_TRUE(delegate_->IsRequestSupported(request));
+ request.reset([[NSMutableURLRequest alloc]
+ initWithURL:[NSURL URLWithString:@"foo:blank"]]);
+ [request setMainDocumentURL:[NSURL URLWithString:@"appspecific:main"]];
+ EXPECT_FALSE(delegate_->IsRequestSupported(request));
+}
+
+TEST_F(WebHTTPProtocolHandlerDelegateTest, IsRequestSupportedMalformed) {
+ base::scoped_nsobject<NSURLRequest> request;
+
+ // Null URL.
+ request.reset([[NSMutableURLRequest alloc] init]);
+ ASSERT_FALSE([request URL]);
+ EXPECT_FALSE(delegate_->IsRequestSupported(request));
+
+ // URL with no scheme.
+ request.reset(
+ [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"foo"]]);
+ ASSERT_TRUE([request URL]);
+ ASSERT_FALSE([[request URL] scheme]);
+ EXPECT_FALSE(delegate_->IsRequestSupported(request));
+
+ // Empty scheme.
+ request.reset(
+ [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@":foo"]]);
+ ASSERT_TRUE([request URL]);
+ ASSERT_TRUE([[request URL] scheme]);
+ ASSERT_FALSE([[[request URL] scheme] length]);
+ EXPECT_FALSE(delegate_->IsRequestSupported(request));
+}
+
+} // namespace web
diff --git a/ios/web/public/browser_state.h b/ios/web/public/browser_state.h
index b4f2ade..44facdd 100644
--- a/ios/web/public/browser_state.h
+++ b/ios/web/public/browser_state.h
@@ -16,6 +16,7 @@ class URLRequestContextGetter;
}
namespace web {
+class CertificatePolicyCache;
// This class holds the context needed for a browsing session.
// It lives on the UI thread. All these methods must only be called on the UI
@@ -24,6 +25,10 @@ class BrowserState : public base::SupportsUserData {
public:
~BrowserState() override;
+ // static
+ static scoped_refptr<CertificatePolicyCache> GetCertificatePolicyCache(
+ BrowserState* browser_state);
+
// Returns whether this BrowserState is incognito. Default is false.
virtual bool IsOffTheRecord() const = 0;
diff --git a/ios/web/public/cert_store.h b/ios/web/public/cert_store.h
new file mode 100644
index 0000000..b6b0855
--- /dev/null
+++ b/ios/web/public/cert_store.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef IOS_WEB_PUBLIC_BROWSER_CERT_STORE_H_
+#define IOS_WEB_PUBLIC_BROWSER_CERT_STORE_H_
+
+#include "base/memory/ref_counted.h"
+
+namespace net {
+class X509Certificate;
+}
+
+namespace web {
+
+// The purpose of the cert store is to provide an easy way to store/retrieve
+// X509Certificate objects. When stored, an X509Certificate object is
+// associated with a group, which can be cleared when it is no longer useful.
+// It can be accessed from the UI and IO threads (it is thread-safe).
+// Note that the cert ids will overflow if more than 2^32 - 1 certs are
+// registered in one browsing session (which is highly unlikely to happen).
+class CertStore {
+ public:
+ // Returns the singleton instance of the CertStore.
+ static CertStore* GetInstance();
+
+ // Stores the specified cert and returns the id associated with it. The cert
+ // is associated to the specified RequestTracker.
+ // When all the RequestTrackers associated with a cert have been closed, the
+ // cert is removed from the store.
+ // Note: ids starts at 1.
+ virtual int StoreCert(net::X509Certificate* cert, int group_id) = 0;
+
+ // Tries to retrieve the previously stored cert associated with the specified
+ // |cert_id|. Returns whether the cert could be found, and, if |cert| is
+ // non-nullptr, copies it in.
+ virtual bool RetrieveCert(int cert_id,
+ scoped_refptr<net::X509Certificate>* cert) = 0;
+
+ // Removes all the certs associated with the specified groups from the store.
+ virtual void RemoveCertsForGroup(int group_id) = 0;
+
+ protected:
+ virtual ~CertStore() {}
+};
+
+} // namespace web
+
+#endif // IOS_WEB_PUBLIC_BROWSER_CERT_STORE_H_
diff --git a/ios/web/public/test/test_web_client.h b/ios/web/public/test/test_web_client.h
new file mode 100644
index 0000000..b6eb167
--- /dev/null
+++ b/ios/web/public/test/test_web_client.h
@@ -0,0 +1,45 @@
+// 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.
+
+#ifndef WEB_TEST_WEB_PUBLIC_TEST_TEST_WEB_CLIENT_H_
+#define WEB_TEST_WEB_PUBLIC_TEST_TEST_WEB_CLIENT_H_
+
+#import <Foundation/Foundation.h>
+
+#include "base/compiler_specific.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "ios/web/public/web_client.h"
+
+namespace web {
+
+class WebViewFactory;
+
+// A WebClient used for testing purposes.
+class TestWebClient : public web::WebClient {
+ public:
+ TestWebClient();
+ ~TestWebClient() override;
+ // WebClient implementation.
+ std::string GetUserAgent(bool is_desktop_user_agent) const override;
+ NSString* GetEarlyPageScript(web::WebViewType web_view_type) const override;
+
+ // Changes the user agent for testing purposes. Passing true
+ // for |is_desktop_user_agent| affects the result of GetUserAgent(true) call.
+ // Passing false affects the result of GetUserAgent(false).
+ void SetUserAgent(const std::string& user_agent, bool is_desktop_user_agent);
+
+ // Changes Early Page Script for testing purposes.
+ void SetEarlyPageScript(NSString* page_script,
+ web::WebViewType web_view_type);
+
+ private:
+ std::string user_agent_;
+ std::string desktop_user_agent_;
+ base::scoped_nsobject<NSMutableDictionary> early_page_scripts_;
+};
+
+} // namespace web
+
+#endif // WEB_TEST_WEB_PUBLIC_TEST_TEST_WEB_CLIENT_H_
diff --git a/ios/web/public/test/test_web_client.mm b/ios/web/public/test/test_web_client.mm
new file mode 100644
index 0000000..d4b1e5c
--- /dev/null
+++ b/ios/web/public/test/test_web_client.mm
@@ -0,0 +1,41 @@
+// 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.
+
+#import "ios/web/public/test/test_web_client.h"
+
+#include "base/strings/sys_string_conversions.h"
+
+namespace web {
+
+TestWebClient::TestWebClient()
+ : early_page_scripts_([[NSMutableDictionary alloc] init]) {
+}
+
+TestWebClient::~TestWebClient() {
+}
+
+std::string TestWebClient::GetUserAgent(bool desktop_user_agent) const {
+ return desktop_user_agent ? desktop_user_agent_ : user_agent_;
+}
+
+void TestWebClient::SetUserAgent(const std::string& user_agent,
+ bool is_desktop_user_agent) {
+ if (is_desktop_user_agent)
+ desktop_user_agent_ = user_agent;
+ else
+ user_agent_ = user_agent;
+}
+
+NSString* TestWebClient::GetEarlyPageScript(
+ web::WebViewType web_view_type) const {
+ NSString* result = [early_page_scripts_ objectForKey:@(web_view_type)];
+ return result ? result : @"";
+}
+
+void TestWebClient::SetEarlyPageScript(NSString* page_script,
+ web::WebViewType web_view_type) {
+ [early_page_scripts_ setObject:page_script forKey:@(web_view_type)];
+}
+
+} // namespace web
diff --git a/ios/web/ui_web_view_util.h b/ios/web/ui_web_view_util.h
new file mode 100644
index 0000000..46b36ab
--- /dev/null
+++ b/ios/web/ui_web_view_util.h
@@ -0,0 +1,24 @@
+// 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.
+
+#ifndef IOS_WEB_UI_WEB_VIEW_UTIL_H_
+#define IOS_WEB_UI_WEB_VIEW_UTIL_H_
+
+#include <Foundation/Foundation.h>
+
+namespace web {
+
+// Registers the user agent encoding |request_group_id| in the user defaults.
+// This is a utility method, used to workaround crbug.com/228397. Do not use
+// it for other purposes.
+void BuildAndRegisterUserAgentForUIWebView(NSString* request_group_id,
+ BOOL use_desktop_user_agent);
+
+// Registers |user_agent| as the user agent string to be used by the UIWebView
+// instances that are created from now on.
+void RegisterUserAgentForUIWebView(NSString* user_agent);
+
+} // namespace web
+
+#endif // IOS_WEB_UI_WEB_VIEW_UTIL_H_
diff --git a/ios/web/ui_web_view_util.mm b/ios/web/ui_web_view_util.mm
new file mode 100644
index 0000000..35c6347
--- /dev/null
+++ b/ios/web/ui_web_view_util.mm
@@ -0,0 +1,40 @@
+// 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.
+
+#import "ios/web/ui_web_view_util.h"
+
+#include "base/logging.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ios/web/net/request_group_util.h"
+#include "ios/web/public/web_client.h"
+
+namespace {
+// Returns true if given Request Group ID string contains only decimal digits.
+BOOL IsValidRequestGroupID(NSString* request_group_id) {
+ return [[NSCharacterSet decimalDigitCharacterSet] isSupersetOfSet:
+ [NSCharacterSet characterSetWithCharactersInString:request_group_id]];
+}
+} // namespace
+
+namespace web {
+
+void BuildAndRegisterUserAgentForUIWebView(NSString* request_group_id,
+ BOOL use_desktop_user_agent) {
+ DCHECK(!request_group_id || IsValidRequestGroupID(request_group_id));
+ DCHECK(web::GetWebClient());
+ std::string baseUserAgent = web::GetWebClient()->GetUserAgent(
+ use_desktop_user_agent);
+ web::RegisterUserAgentForUIWebView(
+ web::AddRequestGroupIDToUserAgent(base::SysUTF8ToNSString(baseUserAgent),
+ request_group_id));
+}
+
+void RegisterUserAgentForUIWebView(NSString* user_agent) {
+ [[NSUserDefaults standardUserDefaults] registerDefaults:@{
+ @"UserAgent" : user_agent
+ }];
+}
+
+} // namespace web
diff --git a/ios/web/ui_web_view_util_unittest.mm b/ios/web/ui_web_view_util_unittest.mm
new file mode 100644
index 0000000..0354579
--- /dev/null
+++ b/ios/web/ui_web_view_util_unittest.mm
@@ -0,0 +1,54 @@
+// 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.
+
+#import "ios/web/ui_web_view_util.h"
+
+#import "ios/web/public/test/test_web_client.h"
+#include "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+// Returns user agent string registered for UIWebView.
+NSString* GetUserAgent() {
+ return [[NSUserDefaults standardUserDefaults] stringForKey:@"UserAgent"];
+}
+
+class UIWebViewUtilTest : public PlatformTest {
+ protected:
+ void SetUp() override {
+ PlatformTest::SetUp();
+ test_web_client_.SetUserAgent("DesktopUA", true);
+ test_web_client_.SetUserAgent("RegularUA", false);
+ web::SetWebClient(&test_web_client_);
+ }
+ void TearDown() override {
+ web::SetWebClient(nullptr);
+ PlatformTest::TearDown();
+ }
+ private:
+ // WebClient that returns test user agent.
+ web::TestWebClient test_web_client_;
+};
+
+// Tests registration of a non-desktop user agent.
+TEST_F(UIWebViewUtilTest, BuildAndRegisterNonDesktopUserAgentForUIWebView) {
+ web::BuildAndRegisterUserAgentForUIWebView(@"1231546541321", NO);
+ EXPECT_NSEQ(@"RegularUA (1231546541321)", GetUserAgent());
+}
+
+// Tests registration of a desktop user agent.
+TEST_F(UIWebViewUtilTest, BuildAndRegisterDesktopUserAgentForUIWebView) {
+ web::BuildAndRegisterUserAgentForUIWebView(@"1231546541321", YES);
+ EXPECT_NSEQ(@"DesktopUA (1231546541321)", GetUserAgent());
+}
+
+// Tests web::RegisterUserAgentForUIWebView function that it correctly registers
+// arbitrary user agent.
+TEST_F(UIWebViewUtilTest, RegisterUserAgentForUIWebView) {
+ web::RegisterUserAgentForUIWebView(@"UA");
+ EXPECT_NSEQ(@"UA", GetUserAgent());
+}
+
+} // namespace
diff --git a/ios/web/web_state/ui/crw_static_file_web_view.h b/ios/web/web_state/ui/crw_static_file_web_view.h
new file mode 100644
index 0000000..1b7e54d
--- /dev/null
+++ b/ios/web/web_state/ui/crw_static_file_web_view.h
@@ -0,0 +1,36 @@
+// Copyright 2012 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.
+
+#ifndef IOS_WEB_WEB_STATE_UI_CRW_STATIC_FILE_WEB_VIEW_H_
+#define IOS_WEB_WEB_STATE_UI_CRW_STATIC_FILE_WEB_VIEW_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/web/net/crw_request_tracker_delegate.h"
+
+namespace web {
+class BrowserState;
+}
+
+// A subclass of UIWebView that specializes in presenting static html file
+// content. Use this type of UIWebView that needs to display static HTML
+// content from application bundle.
+// TODO(shreyasv): Remove this class, since nobody is using it.
+@interface CRWStaticFileWebView : UIWebView<CRWRequestTrackerDelegate>
+
+// Creates a new UIWebView associated with the given |browserState|.
+// |browserState| may be nullptr.
+- (instancetype)initWithFrame:(CGRect)frame
+ browserState:(web::BrowserState*)browserState;
+
+// Returns whether the request should be allowed for rendering into a
+// special UIWebView that allows static file content.
+// TODO(marq): Since this is only used to inject a FileRequestVerfier into
+// HttpProtocolHandler, instead make this a method that returns a block of
+// that type.
++ (BOOL)isStaticFileRequest:(NSURLRequest*)request;
+
+@end
+
+#endif // IOS_WEB_WEB_STATE_UI_CRW_STATIC_FILE_WEB_VIEW_H_
diff --git a/ios/web/web_state/ui/crw_static_file_web_view.mm b/ios/web/web_state/ui/crw_static_file_web_view.mm
new file mode 100644
index 0000000..e40a0af
--- /dev/null
+++ b/ios/web/web_state/ui/crw_static_file_web_view.mm
@@ -0,0 +1,149 @@
+// Copyright 2012 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.
+
+#import "ios/web/web_state/ui/crw_static_file_web_view.h"
+
+#include "base/logging.h"
+#include "ios/web/net/request_group_util.h"
+#include "ios/web/net/request_tracker_impl.h"
+#include "ios/web/public/browser_state.h"
+#import "ios/web/ui_web_view_util.h"
+#include "net/http/http_response_headers.h"
+
+namespace {
+NSString* const kStaticFileUserAgent = @"UIWebViewForStaticFileContent";
+}
+
+@interface CRWStaticFileWebView () {
+ // Saves the user agent in order to set it on future requests.
+ base::scoped_nsobject<NSString> userAgent_;
+ // Saves the identification assigned to this web view for request
+ // tracking. This identifiation is required for terminating request
+ // tracking.
+ scoped_refptr<web::RequestTrackerImpl> requestTracker_;
+}
+
+// Whether the User-Agent is the allowed user agent.
++ (BOOL)isStaticFileUserAgent:(NSString*)userAgent;
+@end
+
+@implementation CRWStaticFileWebView
+
+- (instancetype)initWithFrame:(CGRect)frame
+ browserState:(web::BrowserState*)browserState {
+ // Register the user agent before instantiating the UIWebView.
+ NSString* requestGroupID = nil;
+ NSString* userAgent = kStaticFileUserAgent;
+ if (browserState) {
+ requestGroupID = web::GenerateNewRequestGroupID();
+ userAgent = web::AddRequestGroupIDToUserAgent(userAgent, requestGroupID);
+ }
+ web::RegisterUserAgentForUIWebView(userAgent);
+ self = [super initWithFrame:frame];
+ if (self) {
+ DCHECK(!browserState || [requestGroupID length]);
+ if (browserState) {
+ userAgent_.reset([userAgent copy]);
+ // If |browserState| is not nullptr, associate this UIWebView with the
+ // given |requestGroupID| which cannot be nil or zero-length.
+ // RequestTracker keeps track of requests to this requestGroupID until
+ // this UIWebView is deallocated. UIWebViews not associated with a
+ // requestGroupID will issue requests in the global request context.
+ base::scoped_nsobject<NSString> requestGroupIDCopy([requestGroupID copy]);
+ requestTracker_ = web::RequestTrackerImpl::CreateTrackerForRequestGroupID(
+ requestGroupIDCopy,
+ browserState,
+ browserState->GetRequestContext(),
+ self);
+ }
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (requestTracker_.get())
+ requestTracker_->Close();
+ [super dealloc];
+}
+
++ (BOOL)isStaticFileRequest:(NSURLRequest*)request {
+ NSString* userAgent = [request allHTTPHeaderFields][@"User-Agent"];
+ if (userAgent) {
+ return [CRWStaticFileWebView isStaticFileUserAgent:userAgent];
+ }
+
+ // If a request originated from another file:/// page, the User-Agent
+ // will not be there. To be safe, check that the request is for image
+ // resources only.
+ // TODO(pkl): This current test to allow nil User-Agent and images to
+ // be loaded. A more air-tight implementation should inline images as
+ // "data" instead. See crbug.com/228603
+ NSString* suffix = [[request URL] pathExtension];
+ return [@[ @"png", @"jpg", @"jpeg" ] containsObject:[suffix lowercaseString]];
+}
+
++ (BOOL)isStaticFileUserAgent:(NSString*)userAgent {
+ return [userAgent hasPrefix:kStaticFileUserAgent];
+}
+
+#pragma mark -
+#pragma mark UIWebView
+
+// On iOS 6.0, UIWebView does not set the user agent for static file requests.
+// The solution consists of overriding |loadRequest:| and setting the user agent
+// before loading the request.
+- (void)loadRequest:(NSURLRequest*)request {
+ base::scoped_nsobject<NSMutableURLRequest> mutableRequest(
+ (NSMutableURLRequest*)[request mutableCopy]);
+ [mutableRequest setValue:userAgent_ forHTTPHeaderField:@"User-Agent"];
+ [super loadRequest:mutableRequest];
+}
+
+#pragma mark -
+#pragma mark CRWRequestTrackerDelegate
+
+- (BOOL)isForStaticFileRequests {
+ return YES;
+}
+
+- (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
+ requestUrl:(const GURL&)requestUrl {
+ std::string mimeType;
+ DCHECK(headers->GetMimeType(&mimeType) && mimeType == "text/html");
+ DCHECK(!headers->HasHeader("X-Auto-Login"));
+ DCHECK(!headers->HasHeader("content-disposition"));
+}
+
+// The following delegate functions are not expected to be called since
+// the page is intended to be a static HTML page.
+
+- (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
+ forPageUrl:(const GURL&)url
+ userInfo:(id)userInfo {
+ NOTREACHED();
+}
+
+- (void)presentSSLError:(const net::SSLInfo&)info
+ forSSLStatus:(const web::SSLStatus&)status
+ onUrl:(const GURL&)url
+ recoverable:(BOOL)recoverable
+ callback:(SSLErrorCallback)shouldContinue {
+ NOTREACHED();
+}
+
+- (void)updatedProgress:(float)progress {
+ NOTREACHED();
+}
+
+- (void)certificateUsed:(net::X509Certificate*)certificate
+ forHost:(const std::string&)host
+ status:(net::CertStatus)status {
+ NOTREACHED();
+}
+
+- (void)clearCertificates {
+ NOTREACHED();
+}
+
+@end
diff --git a/ios/web/web_state/ui/crw_static_file_web_view_unittest.mm b/ios/web/web_state/ui/crw_static_file_web_view_unittest.mm
new file mode 100644
index 0000000..bcf12eb
--- /dev/null
+++ b/ios/web/web_state/ui/crw_static_file_web_view_unittest.mm
@@ -0,0 +1,89 @@
+// 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.
+
+#import "ios/web/web_state/ui/crw_static_file_web_view.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/mac/scoped_nsobject.h"
+#include "ios/web/net/request_group_util.h"
+#include "ios/web/public/test/test_browser_state.h"
+#include "ios/web/public/test/test_web_thread_bundle.h"
+#import "ios/web/ui_web_view_util.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+
+@interface CRWStaticFileWebView (Testing)
++ (BOOL)isStaticFileUserAgent:(NSString*)userAgent;
+@end
+
+class CRWStaticFileWebViewTest : public PlatformTest {
+ public:
+ CRWStaticFileWebViewTest() {}
+
+ protected:
+ void SetUp() override { PlatformTest::SetUp(); }
+ void TearDown() override { PlatformTest::TearDown(); }
+
+ // Gets the user agent of |web_view|, using javascript.
+ NSString* GetWebViewUserAgent(UIWebView* web_view) {
+ NSString* const kJsUserAgent = @"navigator.userAgent";
+ return [web_view stringByEvaluatingJavaScriptFromString:kJsUserAgent];
+ };
+
+ web::TestWebThreadBundle thread_bundle_;
+ web::TestBrowserState browser_state_;
+};
+
+// Tests that requests for images are considered as static file requests,
+// regardless of the user agent.
+TEST_F(CRWStaticFileWebViewTest, TestIsStaticImageRequestTrue) {
+ // Empty dictionary so User-Agent check fails.
+ NSDictionary* dictionary = @{};
+ NSURL* url = [NSURL URLWithString:@"file:///show/this.png"];
+ id mockRequest = [OCMockObject mockForClass:[NSURLRequest class]];
+ [[[mockRequest stub] andReturn:dictionary] allHTTPHeaderFields];
+ [[[mockRequest stub] andReturn:url] URL];
+ EXPECT_TRUE([CRWStaticFileWebView isStaticFileRequest:mockRequest]);
+}
+
+// Tests that requests for files are considered as static file requests if they
+// have the static file user agent.
+TEST_F(CRWStaticFileWebViewTest, TestIsStaticFileRequestTrue) {
+ base::scoped_nsobject<UIWebView> webView(
+ [[CRWStaticFileWebView alloc] initWithFrame:CGRectZero
+ browserState:&browser_state_]);
+ EXPECT_TRUE(webView);
+ NSString* userAgent = GetWebViewUserAgent(webView);
+ NSDictionary* dictionary = @{ @"User-Agent" : userAgent };
+ NSURL* url = [NSURL URLWithString:@"file:///some/random/url.html"];
+ id mockRequest = [OCMockObject mockForClass:[NSURLRequest class]];
+ [[[mockRequest stub] andReturn:dictionary] allHTTPHeaderFields];
+ [[[mockRequest stub] andReturn:url] URL];
+ EXPECT_TRUE([CRWStaticFileWebView isStaticFileRequest:mockRequest]);
+}
+
+
+// Tests that arbitrary files cannot be retrieved by a web view for
+// static file content.
+TEST_F(CRWStaticFileWebViewTest, TestIsStaticFileRequestFalse) {
+ // Empty dictionary so User-Agent check fails.
+ NSDictionary* dictionary = @{};
+ NSURL* url = [NSURL URLWithString:@"file:///steal/this/file.html"];
+ id mockRequest = [OCMockObject mockForClass:[NSURLRequest class]];
+ [[[mockRequest stub] andReturn:dictionary] allHTTPHeaderFields];
+ [[[mockRequest stub] andReturn:url] URL];
+ EXPECT_FALSE([CRWStaticFileWebView isStaticFileRequest:mockRequest]);
+}
+
+// Tests that the user agent of a CRWStaticFileWebView includes a request group
+// ID.
+TEST_F(CRWStaticFileWebViewTest, TestExtractRequestGroupIDStaticFile) {
+ base::scoped_nsobject<UIWebView> webView(
+ [[CRWStaticFileWebView alloc] initWithFrame:CGRectZero
+ browserState:&browser_state_]);
+ EXPECT_TRUE(webView);
+ NSString* userAgentString = GetWebViewUserAgent(webView);
+ EXPECT_TRUE(web::ExtractRequestGroupIDFromUserAgent(userAgentString));
+}