diff options
author | stuartmorgan <stuartmorgan@chromium.org> | 2015-03-09 12:19:15 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-09 19:20:11 +0000 |
commit | fc8c88d3db12845eb0b6c31f754f8664ba6466fe (patch) | |
tree | bdd66ee1c928032cd196c194f8f7d8730aab0572 | |
parent | 32af42a8103c97258a0eca1ec7255e7a849b82aa (diff) | |
download | chromium_src-fc8c88d3db12845eb0b6c31f754f8664ba6466fe.zip chromium_src-fc8c88d3db12845eb0b6c31f754f8664ba6466fe.tar.gz chromium_src-fc8c88d3db12845eb0b6c31f754f8664ba6466fe.tar.bz2 |
Upstream various ios/web utilities and helpers
Upstreams miscellaneous helper classes and utility files that don't
have other dependencies, and can thus be built and tested as-is.
BUG=464810
Review URL: https://codereview.chromium.org/988383002
Cr-Commit-Position: refs/heads/master@{#319706}
27 files changed, 1385 insertions, 38 deletions
diff --git a/ios/web/crw_network_activity_indicator_manager.h b/ios/web/crw_network_activity_indicator_manager.h new file mode 100644 index 0000000..5af8f14 --- /dev/null +++ b/ios/web/crw_network_activity_indicator_manager.h @@ -0,0 +1,67 @@ +// 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_CRW_NETWORK_ACTIVITY_INDICATOR_MANAGER_H_ +#define IOS_WEB_CRW_NETWORK_ACTIVITY_INDICATOR_MANAGER_H_ + +#import <Foundation/Foundation.h> + +// This class controls access to the network activity indicator across the +// app. It provides a simple interface for clients to indicate they are +// starting a network task and they would like the indicator shown, and to +// indicate they have finished a network task. +// +// Clients are required to pass an NSString* to each method to identify +// themselves. Separating clients into groups prevents a client from "stopping" +// requests from other clients on accident, and makes those bugs easier to +// track down. Specifically, the manager will immediately fail if the number +// of tasks stopped for a group ever exceeds the number of tasks started for +// that group. Clients are responsible for namespacing their group strings +// properly. All methods must be called on the UI thread. +@interface CRWNetworkActivityIndicatorManager : NSObject + +// Returns the singleton CRWNetworkActivityIndicatorManager. ++ (CRWNetworkActivityIndicatorManager*)sharedInstance; + +// Begins a single network task. The network activity indicator is guaranteed +// to be shown after this finishes (if it isn't already). |group| must be +// non-nil. +- (void)startNetworkTaskForGroup:(NSString*)group; + +// Stops a single network task. The network activity indicator may or may not +// stop being shown once this finishes, depending on whether there are other +// unstopped tasks or not. |group| must be non-nil, and have at least one +// unstopped task. +- (void)stopNetworkTaskForGroup:(NSString*)group; + +// A convenience method for starting multiple network tasks at once. |group| +// must be non-nil. |numTasks| must be greater than 0. +- (void)startNetworkTasks:(NSUInteger)numTasks forGroup:(NSString*)group; + +// A convenience method for stopping multiple network tasks at once. |group| +// must be non-nil. |numTasks| must be greater than 0, and |numTasks| must be +// less than or equal to the number of unstopped tasks in |group|. +- (void)stopNetworkTasks:(NSUInteger)numTasks forGroup:(NSString*)group; + +// A convenience method for stopping all network tasks for a group. |group| +// must be non-nil. Can be called on any group at any time, regardless of +// whether the group has any unstopped network tasks or not. Returns the number +// of tasks stopped by this call. +- (NSUInteger)clearNetworkTasksForGroup:(NSString*)group; + +// Returns the number of unstopped network tasks for |group|. |group| must be +// non-nil. Can be called on any group at any time, regardless of whether the +// group has any unstopped network tasks or not. +- (NSUInteger)numNetworkTasksForGroup:(NSString*)group; + +// Returns the total number of unstopped network tasks, across all groups. This +// method was added for testing only. Clients should never depend on this, and +// should instead only be concerned with the number of unstopped network tasks +// for the groups they control, which can be queried using +// |-numNetworkTasksForGroup:|. +- (NSUInteger)numTotalNetworkTasks; + +@end + +#endif // IOS_WEB_CRW_NETWORK_ACTIVITY_INDICATOR_MANAGER_H_ diff --git a/ios/web/crw_network_activity_indicator_manager.mm b/ios/web/crw_network_activity_indicator_manager.mm new file mode 100644 index 0000000..ec8e0e3 --- /dev/null +++ b/ios/web/crw_network_activity_indicator_manager.mm @@ -0,0 +1,115 @@ +// 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/crw_network_activity_indicator_manager.h" + +#import <UIKit/UIKit.h> + +#include "base/logging.h" +#include "base/mac/scoped_nsobject.h" +#include "base/threading/thread_checker.h" + +@interface CRWNetworkActivityIndicatorManager () { + base::scoped_nsobject<NSMutableDictionary> _groupCounts; + NSUInteger _totalCount; + base::ThreadChecker _threadChecker; +} + +@end + +@implementation CRWNetworkActivityIndicatorManager + ++ (CRWNetworkActivityIndicatorManager*)sharedInstance { + static CRWNetworkActivityIndicatorManager* instance = + [[CRWNetworkActivityIndicatorManager alloc] init]; + return instance; +} + +- (id)init { + self = [super init]; + if (self) { + _groupCounts.reset([[NSMutableDictionary alloc] init]); + _totalCount = 0; + } + return self; +} + +- (void)startNetworkTaskForGroup:(NSString*)group { + [self startNetworkTasks:1 forGroup:group]; +} + +- (void)stopNetworkTaskForGroup:(NSString*)group { + [self stopNetworkTasks:1 forGroup:group]; +} + +- (void)startNetworkTasks:(NSUInteger)numTasks forGroup:(NSString*)group { + DCHECK(_threadChecker.CalledOnValidThread()); + DCHECK(group); + DCHECK_GT(numTasks, 0U); + NSUInteger count = 0; + NSNumber* number = [_groupCounts objectForKey:group]; + if (number) { + count = [number unsignedIntegerValue]; + DCHECK_GT(count, 0U); + } + count += numTasks; + [_groupCounts setObject:[NSNumber numberWithUnsignedInteger:count] + forKey:group]; + _totalCount += numTasks; + if (_totalCount == numTasks) { + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; + } +} + +- (void)stopNetworkTasks:(NSUInteger)numTasks forGroup:(NSString*)group { + DCHECK(_threadChecker.CalledOnValidThread()); + DCHECK(group); + DCHECK_GT(numTasks, 0U); + NSNumber* number = [_groupCounts objectForKey:group]; + DCHECK(number); + NSUInteger count = [number unsignedIntegerValue]; + DCHECK(count >= numTasks); + count -= numTasks; + if (count == 0) { + [_groupCounts removeObjectForKey:group]; + } else { + [_groupCounts setObject:[NSNumber numberWithUnsignedInteger:count] + forKey:group]; + } + _totalCount -= numTasks; + if (_totalCount == 0) { + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + } +} + +- (NSUInteger)clearNetworkTasksForGroup:(NSString*)group { + DCHECK(_threadChecker.CalledOnValidThread()); + DCHECK(group); + NSNumber* number = [_groupCounts objectForKey:group]; + if (!number) { + return 0; + } + NSUInteger count = [number unsignedIntegerValue]; + DCHECK_GT(count, 0U); + [self stopNetworkTasks:count forGroup:group]; + return count; +} + +- (NSUInteger)numNetworkTasksForGroup:(NSString*)group { + DCHECK(_threadChecker.CalledOnValidThread()); + DCHECK(group); + NSNumber* number = [_groupCounts objectForKey:group]; + if (!number) { + return 0; + } + return [number unsignedIntegerValue]; +} + +- (NSUInteger)numTotalNetworkTasks { + DCHECK(_threadChecker.CalledOnValidThread()); + return _totalCount; +} + + +@end diff --git a/ios/web/crw_network_activity_indicator_manager_unittest.mm b/ios/web/crw_network_activity_indicator_manager_unittest.mm new file mode 100644 index 0000000..081bd60 --- /dev/null +++ b/ios/web/crw_network_activity_indicator_manager_unittest.mm @@ -0,0 +1,154 @@ +// 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/crw_network_activity_indicator_manager.h" + +#import <UIKit/UIKit.h> + +#include "base/mac/scoped_nsobject.h" +#include "testing/gtest_mac.h" +#include "testing/platform_test.h" + +namespace { + +NSString* const kNetworkActivityKeyOne = + @"CRWNetworkActivityIndicatorManagerTest.NetworkActivityIndicatorKeyOne"; +NSString* const kNetworkActivityKeyTwo = + @"CRWNetworkActivityIndicatorManagerTest.NetworkActivityIndicatorKeyTwo"; + +class CRWNetworkActivityIndicatorManagerTest : public PlatformTest { + public: + CRWNetworkActivityIndicatorManagerTest() + : manager_([[CRWNetworkActivityIndicatorManager alloc] init]) {} + + protected: + void ExpectNetworkActivity(NSUInteger groupOneCount, + NSUInteger groupTwoCount) { + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyOne], + groupOneCount); + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyTwo], + groupTwoCount); + EXPECT_EQ([manager_ numTotalNetworkTasks], groupOneCount + groupTwoCount); + if (groupOneCount + groupTwoCount > 0U) { + EXPECT_TRUE( + [[UIApplication sharedApplication] + isNetworkActivityIndicatorVisible]); + } else { + EXPECT_FALSE( + [[UIApplication sharedApplication] + isNetworkActivityIndicatorVisible]); + } + } + base::scoped_nsobject<CRWNetworkActivityIndicatorManager> manager_; +}; + +TEST_F(CRWNetworkActivityIndicatorManagerTest, TestNumNetworkTasksForGroup) { + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyOne], 0U); + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyTwo], 0U); + [manager_ startNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyOne], 2U); + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyTwo], 0U); + [manager_ startNetworkTasks:3U forGroup:kNetworkActivityKeyTwo]; + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyOne], 2U); + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyTwo], 3U); + [manager_ stopNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyOne], 0U); + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyTwo], 3U); + [manager_ stopNetworkTasks:3U forGroup:kNetworkActivityKeyTwo]; + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyOne], 0U); + EXPECT_EQ([manager_ numNetworkTasksForGroup:kNetworkActivityKeyTwo], 0U); +} + +TEST_F(CRWNetworkActivityIndicatorManagerTest, TestNumTotalNetworkTasks) { + EXPECT_EQ([manager_ numTotalNetworkTasks], 0U); + [manager_ startNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + EXPECT_EQ([manager_ numTotalNetworkTasks], 2U); + [manager_ startNetworkTasks:3U forGroup:kNetworkActivityKeyTwo]; + EXPECT_EQ([manager_ numTotalNetworkTasks], 5U); + [manager_ stopNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + EXPECT_EQ([manager_ numTotalNetworkTasks], 3U); + [manager_ stopNetworkTasks:3U forGroup:kNetworkActivityKeyTwo]; + EXPECT_EQ([manager_ numTotalNetworkTasks], 0U); +} + +TEST_F(CRWNetworkActivityIndicatorManagerTest, + StartStopNetworkTaskForOneGroup) { + ExpectNetworkActivity(0U, 0U); + [manager_ startNetworkTaskForGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(1U, 0U); + [manager_ stopNetworkTaskForGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(0U, 0U); +} + +TEST_F(CRWNetworkActivityIndicatorManagerTest, + StartStopNetworkTaskForTwoGroups) { + ExpectNetworkActivity(0U, 0U); + [manager_ startNetworkTaskForGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(1U, 0U); + [manager_ startNetworkTaskForGroup:kNetworkActivityKeyTwo]; + ExpectNetworkActivity(1U, 1U); + [manager_ stopNetworkTaskForGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(0U, 1U); + [manager_ stopNetworkTaskForGroup:kNetworkActivityKeyTwo]; + ExpectNetworkActivity(0U, 0U); +} + +TEST_F(CRWNetworkActivityIndicatorManagerTest, + StartStopNetworkTasksForOneGroup) { + ExpectNetworkActivity(0U, 0U); + [manager_ startNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(2U, 0U); + [manager_ stopNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(0U, 0U); +} + +TEST_F(CRWNetworkActivityIndicatorManagerTest, + StartStopNetworkTasksForTwoGroups) { + ExpectNetworkActivity(0U, 0U); + [manager_ startNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(2U, 0U); + [manager_ startNetworkTasks:3U forGroup:kNetworkActivityKeyTwo]; + ExpectNetworkActivity(2U, 3U); + [manager_ stopNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(0U, 3U); + [manager_ stopNetworkTasks:3U forGroup:kNetworkActivityKeyTwo]; + ExpectNetworkActivity(0U, 0U); +} + +TEST_F(CRWNetworkActivityIndicatorManagerTest, ClearNetworkTasksForGroup) { + ExpectNetworkActivity(0U, 0U); + [manager_ startNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(2U, 0U); + [manager_ clearNetworkTasksForGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(0U, 0U); +} + +TEST_F(CRWNetworkActivityIndicatorManagerTest, + ClearNetworkTasksForUnusedGroup) { + ExpectNetworkActivity(0U, 0U); + [manager_ clearNetworkTasksForGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(0U, 0U); +} + +TEST_F(CRWNetworkActivityIndicatorManagerTest, StartStopNetworkTasksInChunks) { + ExpectNetworkActivity(0U, 0U); + [manager_ startNetworkTasks:2U forGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(2U, 0U); + [manager_ startNetworkTasks:3U forGroup:kNetworkActivityKeyTwo]; + ExpectNetworkActivity(2U, 3U); + [manager_ startNetworkTasks:7U forGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(9U, 3U); + [manager_ startNetworkTaskForGroup:kNetworkActivityKeyTwo]; + ExpectNetworkActivity(9U, 4U); + [manager_ stopNetworkTasks:4U forGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(5U, 4U); + [manager_ stopNetworkTasks:2U forGroup:kNetworkActivityKeyTwo]; + ExpectNetworkActivity(5U, 2U); + [manager_ stopNetworkTasks:5U forGroup:kNetworkActivityKeyOne]; + ExpectNetworkActivity(0U, 2U); + [manager_ clearNetworkTasksForGroup:kNetworkActivityKeyTwo]; + ExpectNetworkActivity(0U, 0U); +} + +} // namespace diff --git a/ios/web/history_state_util.h b/ios/web/history_state_util.h new file mode 100644 index 0000000..6c48928 --- /dev/null +++ b/ios/web/history_state_util.h @@ -0,0 +1,33 @@ +// 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_HISTORY_STATE_UTIL_H_ +#define IOS_WEB_HISTORY_STATE_UTIL_H_ + +#import <Foundation/Foundation.h> + +#include <string> + +class GURL; + +namespace web { +namespace history_state_util { + +// Checks if toUrl is a valid argument to history.pushState() or +// history.replaceState() given the current URL. +bool IsHistoryStateChangeValid(const GURL& currentUrl, + const GURL& toUrl); + +// Generates the appropriate full URL for a history.pushState() or +// history.replaceState() transition from currentURL to destination, resolved +// against baseURL. |destination| may be a relative URL. Will return an invalid +// URL if the resolved destination, or the transition, is not valid. +GURL GetHistoryStateChangeUrl(const GURL& currentUrl, + const GURL& baseUrl, + const std::string& destination); + +} // namespace history_state_util +} // namespace web + +#endif // IOS_WEB_HISTORY_STATE_UTIL_H_ diff --git a/ios/web/history_state_util.mm b/ios/web/history_state_util.mm new file mode 100644 index 0000000..bdb11bce --- /dev/null +++ b/ios/web/history_state_util.mm @@ -0,0 +1,37 @@ +// 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/history_state_util.h" + +#include "base/logging.h" +#include "url/gurl.h" + +namespace web { +namespace history_state_util { + +bool IsHistoryStateChangeValid(const GURL& currentUrl, + const GURL& toUrl) { + // These two checks are very important to the security of the page. We cannot + // allow the page to change the state to an invalid URL. + CHECK(currentUrl.is_valid()); + CHECK(toUrl.is_valid()); + + return toUrl.GetOrigin() == currentUrl.GetOrigin(); +} + +GURL GetHistoryStateChangeUrl(const GURL& currentUrl, + const GURL& baseUrl, + const std::string& destination) { + if (!baseUrl.is_valid()) + return GURL(); + GURL toUrl = baseUrl.Resolve(destination); + + if (!toUrl.is_valid() || !IsHistoryStateChangeValid(currentUrl, toUrl)) + return GURL(); + + return toUrl; +} + +} // namespace history_state_util +} // namespace web diff --git a/ios/web/history_state_util_unittest.mm b/ios/web/history_state_util_unittest.mm new file mode 100644 index 0000000..f7fe916 --- /dev/null +++ b/ios/web/history_state_util_unittest.mm @@ -0,0 +1,108 @@ +// 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 <Foundation/Foundation.h> + +#import "ios/web/history_state_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gtest_mac.h" +#include "url/gurl.h" + +namespace web { +namespace { +struct TestEntry { + std::string fromUrl; + std::string toUrl; + std::string expectedUrl; +}; + +class HistoryStateUtilTest : public ::testing::Test { + protected: + static const struct TestEntry tests_[]; +}; + +const struct TestEntry HistoryStateUtilTest::tests_[] = { + // Valid absolute changes. + { "http://foo.com", "http://foo.com/bar", "http://foo.com/bar" }, + { "https://foo.com", "https://foo.com/bar", "https://foo.com/bar" }, + { "http://foo.com/", "http://foo.com#bar", "http://foo.com#bar" }, + { "http://foo.com:80", "http://foo.com:80/b", "http://foo.com:80/b"}, + { "http://foo.com:888", "http://foo.com:888/b", "http://foo.com:888/b"}, + // Valid relative changes. + { "http://foo.com", "#bar", "http://foo.com#bar" }, + { "http://foo.com/", "#bar", "http://foo.com/#bar" }, + { "https://foo.com/", "bar", "https://foo.com/bar" }, + { "http://foo.com/foo/1", "/bar", "http://foo.com/bar" }, + { "http://foo.com/foo/1", "bar", "http://foo.com/foo/bar" }, + { "http://foo.com/", "bar.com", "http://foo.com/bar.com" }, + { "http://foo.com", "bar.com", "http://foo.com/bar.com" }, + { "http://foo.com:888", "bar.com", "http://foo.com:888/bar.com" }, + // Invalid scheme changes. + { "http://foo.com", "https://foo.com#bar", "" }, + { "https://foo.com", "http://foo.com#bar", "" }, + // Invalid domain changes. + { "http://foo.com/bar", "http://bar.com", "" }, + { "http://foo.com/bar", "http://www.foo.com/bar2", "" }, + // Valid port change. + { "http://foo.com", "http://foo.com:80/bar", "http://foo.com/bar" }, + { "http://foo.com:80", "http://foo.com/bar", "http://foo.com/bar" }, + // Invalid port change. + { "http://foo.com", "http://foo.com:42/bar", "" }, + { "http://foo.com:42", "http://foo.com/bar", "" }, + // Invalid URL. + { "http://foo.com", "http://fo o.c om/ba r", "" }, + { "http://foo.com:80", "bar", "http://foo.com:80/bar" } +}; + +TEST_F(HistoryStateUtilTest, TestIsHistoryStateChangeValid) { + for (size_t i = 0; i < arraysize(tests_); ++i) { + GURL fromUrl(tests_[i].fromUrl); + GURL toUrl = history_state_util::GetHistoryStateChangeUrl(fromUrl, fromUrl, + tests_[i].toUrl); + bool expected_result = tests_[i].expectedUrl.size() > 0; + bool actual_result = toUrl.is_valid(); + if (actual_result) { + actual_result = history_state_util::IsHistoryStateChangeValid(fromUrl, + toUrl); + } + EXPECT_EQ(expected_result, actual_result) << tests_[i].fromUrl << " " + << tests_[i].toUrl; + } +} + +TEST_F(HistoryStateUtilTest, TestGetHistoryStateChangeUrl) { + for (size_t i = 0; i < arraysize(tests_); ++i) { + GURL fromUrl(tests_[i].fromUrl); + GURL expectedResult(tests_[i].expectedUrl); + GURL actualResult = history_state_util::GetHistoryStateChangeUrl( + fromUrl, fromUrl, tests_[i].toUrl); + EXPECT_EQ(expectedResult, actualResult); + } +} + +// Ensures that the baseUrl is used to resolve the destination, not currentUrl. +TEST_F(HistoryStateUtilTest, TestGetHistoryStateChangeUrlWithBase) { + GURL fromUrl("http://foo.com/relative/path"); + GURL baseUrl("http://foo.com"); + std::string destination = "bar"; + + GURL result = history_state_util::GetHistoryStateChangeUrl(fromUrl, baseUrl, + destination); + EXPECT_TRUE(result.is_valid()); + EXPECT_EQ(GURL("http://foo.com/bar"), result); +} + +// Ensures that an invalid baseUrl gracefully returns an invalid destination. +TEST_F(HistoryStateUtilTest, TestGetHistoryStateChangeUrlWithInvalidBase) { + GURL fromUrl("http://foo.com"); + GURL baseUrl("http://not a url"); + std::string destination = "baz"; + + GURL result = history_state_util::GetHistoryStateChangeUrl(fromUrl, baseUrl, + destination); + EXPECT_FALSE(result.is_valid()); +} + +} // anonymous namespace +} // namespace web diff --git a/ios/web/ios_web.gyp b/ios/web/ios_web.gyp index 37545c9..5e208e9 100644 --- a/ios/web/ios_web.gyp +++ b/ios/web/ios_web.gyp @@ -14,6 +14,7 @@ '../..', ], 'dependencies': [ + 'ios_web_core', '../../base/base.gyp:base', '../../content/content.gyp:content_browser', '../../net/net.gyp:net', @@ -25,8 +26,14 @@ 'load_committed_details.cc', 'navigation/navigation_item_impl.h', 'navigation/navigation_item_impl.mm', + 'navigation/nscoder_util.h', + 'navigation/nscoder_util.mm', + 'navigation/time_smoother.cc', + 'navigation/time_smoother.h', 'net/cert_policy.cc', 'net/certificate_policy_cache.cc', + 'net/request_group_util.h', + 'net/request_group_util.mm', 'public/block_types.h', 'public/browser_state.h', 'public/browser_url_rewriter.h', @@ -74,6 +81,11 @@ 'url_scheme_util.mm', 'url_util.cc', 'user_metrics.cc', + 'weak_nsobject_counter.h', + 'weak_nsobject_counter.mm', + 'web_state/blocked_popup_info.h', + 'web_state/blocked_popup_info.mm', + 'web_state/crw_recurring_task_delegate.h', 'web_state/js/crw_js_base_manager.mm', 'web_state/js/crw_js_common_manager.h', 'web_state/js/crw_js_common_manager.mm', @@ -85,6 +97,8 @@ 'web_state/js/crw_js_message_manager.mm', 'web_state/web_state_observer.cc', 'web_state/web_state_observer_bridge.mm', + 'web_state/wk_web_view_ssl_error_util.h', + 'web_state/wk_web_view_ssl_error_util.mm', 'web_thread.cc', 'web_thread_impl.cc', 'web_thread_impl.h', @@ -92,6 +106,23 @@ 'web_view_util.mm', ], }, + # Target shared by ios_web and CrNet. + { + 'target_name': 'ios_web_core', + 'type': 'static_library', + 'dependencies': [ + '../../base/base.gyp:base', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'crw_network_activity_indicator_manager.h', + 'crw_network_activity_indicator_manager.mm', + 'history_state_util.h', + 'history_state_util.mm', + ], + }, { 'target_name': 'js_resources', 'type': 'none', diff --git a/ios/web/ios_web_unittests.gyp b/ios/web/ios_web_unittests.gyp index 1bf966f..d0a04bd 100644 --- a/ios/web/ios_web_unittests.gyp +++ b/ios/web/ios_web_unittests.gyp @@ -20,12 +20,17 @@ ], 'sources': [ 'browser_state_unittest.cc', + 'crw_network_activity_indicator_manager_unittest.mm', + 'history_state_util_unittest.mm', 'navigation/navigation_item_impl_unittest.mm', + 'navigation/nscoder_util_unittest.mm', 'net/cert_policy_unittest.cc', + 'net/request_group_util_unittest.mm', 'public/referrer_util_unittest.cc', 'string_util_unittest.cc', 'url_scheme_util_unittest.mm', 'url_util_unittest.cc', + 'weak_nsobject_counter_unittest.mm', ], }, ], diff --git a/ios/web/navigation/nscoder_util.h b/ios/web/navigation/nscoder_util.h new file mode 100644 index 0000000..5a92f19f --- /dev/null +++ b/ios/web/navigation/nscoder_util.h @@ -0,0 +1,24 @@ +// Copyright (c) 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_NAVIGATION_NSCODER_UTIL_H_ +#define IOS_WEB_NAVIGATION_NSCODER_UTIL_H_ + +#import <Foundation/Foundation.h> + +#include <string> + +namespace web { +namespace nscoder_util { + +// Archives a std::string in an Objective-C key archiver. +void EncodeString(NSCoder* coder, NSString* key, const std::string& string); + +// Decode a std::string from an Objective-C key unarchiver. +std::string DecodeString(NSCoder* decoder, NSString* key); + +} // namespace nscoder_util +} // namespace web + +#endif // IOS_WEB_NAVIGATION_NSCODER_UTIL_H_ diff --git a/ios/web/navigation/nscoder_util.mm b/ios/web/navigation/nscoder_util.mm new file mode 100644 index 0000000..551dccd --- /dev/null +++ b/ios/web/navigation/nscoder_util.mm @@ -0,0 +1,25 @@ +// Copyright (c) 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 <string> + +#include "ios/web/navigation/nscoder_util.h" + +namespace web { +namespace nscoder_util { + +void EncodeString(NSCoder* coder, NSString* key, const std::string& string) { + [coder encodeBytes:reinterpret_cast<const uint8_t*>(string.data()) + length:string.size() + forKey:key]; +} + +std::string DecodeString(NSCoder* decoder, NSString* key) { + NSUInteger length; + const uint8_t* bytes = [decoder decodeBytesForKey:key returnedLength:&length]; + return std::string(reinterpret_cast<const char*>(bytes), length); +} + +} // namespace nscoder_util +} // namespace web diff --git a/ios/web/navigation/nscoder_util_unittest.mm b/ios/web/navigation/nscoder_util_unittest.mm new file mode 100644 index 0000000..8d3b667 --- /dev/null +++ b/ios/web/navigation/nscoder_util_unittest.mm @@ -0,0 +1,59 @@ +// 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 <Foundation/Foundation.h> + +#include "base/basictypes.h" +#include "base/mac/scoped_nsobject.h" +#include "ios/web/navigation/nscoder_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace web { +namespace { + +typedef PlatformTest NSCoderStdStringTest; + +const char* testStrings[] = { + "Arf", + "", + "This is working™", + "古池や蛙飛込む水の音\nふるいけやかわずとびこむみずのおと", + "ἀγεωμέτρητος μηδεὶς εἰσίτω", + "Bang!\t\n" +}; + +TEST_F(NSCoderStdStringTest, encodeDecode) { + for (size_t i = 0; i < arraysize(testStrings); ++i) { + NSMutableData* data = [NSMutableData data]; + + base::scoped_nsobject<NSKeyedArchiver> archiver( + [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]); + nscoder_util::EncodeString(archiver, @"test", testStrings[i]); + [archiver finishEncoding]; + + base::scoped_nsobject<NSKeyedUnarchiver> unarchiver( + [[NSKeyedUnarchiver alloc] initForReadingWithData:data]); + const std::string decoded = nscoder_util::DecodeString(unarchiver, @"test"); + + EXPECT_EQ(decoded, testStrings[i]); + } +} + +TEST_F(NSCoderStdStringTest, decodeEmpty) { + NSMutableData* data = [NSMutableData data]; + + base::scoped_nsobject<NSKeyedArchiver> archiver( + [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]); + [archiver finishEncoding]; + + base::scoped_nsobject<NSKeyedUnarchiver> unarchiver( + [[NSKeyedUnarchiver alloc] initForReadingWithData:data]); + const std::string decoded = nscoder_util::DecodeString(unarchiver, @"test"); + + EXPECT_EQ(decoded, ""); +} + +} // namespace +} // namespace web diff --git a/ios/web/navigation/time_smoother.cc b/ios/web/navigation/time_smoother.cc new file mode 100644 index 0000000..b8aaec6 --- /dev/null +++ b/ios/web/navigation/time_smoother.cc @@ -0,0 +1,25 @@ +// Copyright 2013 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/navigation/time_smoother.h" + +namespace web { + +// Duplicated from content/browser/web_contents/navigation_controller_impl.cc. +base::Time TimeSmoother::GetSmoothedTime(base::Time t) { + // If |t| is between the water marks, we're in a run of duplicates + // or just getting out of it, so increase the high-water mark to get + // a time that probably hasn't been used before and return it. + if (low_water_mark_ <= t && t <= high_water_mark_) { + high_water_mark_ += base::TimeDelta::FromMicroseconds(1); + return high_water_mark_; + } + + // Otherwise, we're clear of the last duplicate run, so reset the + // water marks. + low_water_mark_ = high_water_mark_ = t; + return t; +} + +} // namespace web diff --git a/ios/web/navigation/time_smoother.h b/ios/web/navigation/time_smoother.h new file mode 100644 index 0000000..59eb2a27 --- /dev/null +++ b/ios/web/navigation/time_smoother.h @@ -0,0 +1,32 @@ +// Copyright 2013 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_NAVIGATION_TIME_SMOOTHER_H_ +#define IOS_WEB_NAVIGATION_TIME_SMOOTHER_H_ + +#include "base/time/time.h" + +namespace web { + +// Helper class to smooth out runs of duplicate timestamps while still +// allowing time to jump backwards. +// +// Duplicated from NavigationControllerImpl (until we have a better +// idea how to handle NavigationController implementation overlap +// in general). +class TimeSmoother { + public: + // Returns |t| with possibly some time added on. + base::Time GetSmoothedTime(base::Time t); + + private: + // |low_water_mark_| is the first time in a sequence of adjusted + // times and |high_water_mark_| is the last. + base::Time low_water_mark_; + base::Time high_water_mark_; +}; + +} // namespace web + +#endif // IOS_WEB_NAVIGATION_TIME_SMOOTHER_H_ diff --git a/ios/web/net/request_group_util.h b/ios/web/net/request_group_util.h new file mode 100644 index 0000000..525f2d0 --- /dev/null +++ b/ios/web/net/request_group_util.h @@ -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. + +#ifndef IOS_WEB_NET_REQUEST_GROUP_UTIL_H_ +#define IOS_WEB_NET_REQUEST_GROUP_UTIL_H_ + +@class NSString; +@class NSURL; +@class NSURLRequest; + +// Request group IDs are internally used by the web layer to associate network +// requests to RequestTracker instances, and in turn, to WebState instances. +// The ID can be added to the user-agent string by UIWebView in most cases and +// then extracted by the network layer. +// However, when using non-standard schemes, UIWebView does not add the +// "User-Agent" HTTP header to the requests. The workaround for this case is to +// add the ID directly in the URL of the main request (which is the only request +// accessible from the UIWebView delegate). + +namespace web { + +// Generates a request-group ID. +NSString* GenerateNewRequestGroupID(); + +// Extracts the requestGroupID embedded in a User-Agent string or nil if a +// requestGroupID cannot be located. +NSString* ExtractRequestGroupIDFromUserAgent(NSString* user_agent); + +// Returns a new user agent, which is the result of the encoding of +// |request_group_id| in |base_user_agent|. The request group ID can be later +// extracted with ExtractRequestGroupIDFromUserAgent(). +NSString* AddRequestGroupIDToUserAgent(NSString* base_user_agent, + NSString* request_group_id); + +// Extracts the requestGroupID embedded in a NSURL or nil if a requestGroupID +// cannot be located. +NSString* ExtractRequestGroupIDFromURL(NSURL* url); + +// Returns a new user agent, which is the result of the encoding of +// |request_group_id| in |base_url|. The request group ID can be later extracted +// with ExtractRequestGroupIDFromURL(). +NSURL* AddRequestGroupIDToURL(NSURL* base_url, NSString* request_group_id); + +// Extracts the request group ID from |request| by retrieving it from the +// user-agent if possible, and from the parent URL otherwise. +// The ID can only be retrived from the parent URL if its scheme is +// |application_scheme|. +// The reason why the |application_scheme| case is different is because +// UIWebView does not provide a "User-Agent" HTTP header for these requests. +// The ID is then encoded in the URL of the main request, and thus sub-requests +// have to look in their parent URL for the ID. +NSString* ExtractRequestGroupIDFromRequest(NSURLRequest* request, + NSString* application_scheme); + +} // namespace web + +#endif // IOS_WEB_NET_REQUEST_GROUP_UTIL_H_ diff --git a/ios/web/net/request_group_util.mm b/ios/web/net/request_group_util.mm new file mode 100644 index 0000000..9a41cef --- /dev/null +++ b/ios/web/net/request_group_util.mm @@ -0,0 +1,113 @@ +// 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_group_util.h" + +#include <Foundation/Foundation.h> + +#include "base/base64.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/strings/sys_string_conversions.h" +#import "net/base/mac/url_conversions.h" +#include "url/gurl.h" + +namespace { +// Minimum length for a request group ID. A shorter string is considered as an +// invalid ID. +const int kMinimumIDLength = 5; +} + +namespace web { + +// Generates a request-group ID used to correlate web requests with the embedder +// that triggered it. It is important that the return value should not be unique +// for different users. See crbug/355613 for context. +NSString* GenerateNewRequestGroupID() { + const unsigned int kGroupSize = 1000; + static unsigned long count = 0; + static unsigned int offset = base::RandInt(0, kGroupSize - 1); + + unsigned long current = count++; + if (current < kGroupSize) + current = (current + offset) % kGroupSize; + + // The returned string must have a minimum of kMinimumIDLength characters, and + // no spaces. + // TODO(blundell): Develop a long-term solution to this problem. + // crbug.com/329243 + return [NSString stringWithFormat:@"%06lu", current]; +} + +NSString* ExtractRequestGroupIDFromUserAgent(NSString* user_agent) { + if (![user_agent length]) + return nil; + + // The request_group_id is wrapped by parenthesis in the last space-delimited + // fragment. + NSString* fragment = + [[user_agent componentsSeparatedByString:@" "] lastObject]; + NSString* request_group_id = + [fragment hasPrefix:@"("] && [fragment hasSuffix:@")"] + ? [fragment substringWithRange:NSMakeRange(1, [fragment length] - 2)] + : nil; + // GTLService constructs user agents that end with "(gzip)". To avoid these + // getting treated as having the request_group_id "gzip", short-circuit out if + // the request_group_id is not long enough to be a valid request_group_id (all + // valid request_group_id are at least kMinimumIDLength characters long). + // TODO(blundell): Develop a long-term solution to this problem. + // crbug.com/329243 + if ([request_group_id length] < kMinimumIDLength) + return nil; + return request_group_id; +} + +NSString* AddRequestGroupIDToUserAgent(NSString* base_user_agent, + NSString* request_group_id) { + // TODO(blundell): Develop a long-term solution to this problem. + // crbug.com/329243 + DCHECK([request_group_id length] >= kMinimumIDLength); + return + [NSString stringWithFormat:@"%@ (%@)", base_user_agent, request_group_id]; +} + +NSString* ExtractRequestGroupIDFromURL(NSURL* url) { + GURL gurl = net::GURLWithNSURL(url); + if (!gurl.has_username()) + return nil; + + std::string request_group_id_as_string; + if (base::Base64Decode(gurl.username(), &request_group_id_as_string)) + return base::SysUTF8ToNSString(request_group_id_as_string); + + return nil; +} + +NSURL* AddRequestGroupIDToURL(NSURL* base_url, NSString* request_group_id) { + GURL url = net::GURLWithNSURL(base_url); + std::string base64RequestGroupID; + base::Base64Encode(base::SysNSStringToUTF8(request_group_id), + &base64RequestGroupID); + GURL::Replacements replacements; + replacements.SetUsernameStr(base64RequestGroupID); + url = url.ReplaceComponents(replacements); + return net::NSURLWithGURL(url); +} + +NSString* ExtractRequestGroupIDFromRequest(NSURLRequest* request, + NSString* application_scheme) { + NSString* user_agent = + [[request allHTTPHeaderFields] objectForKey:@"User-Agent"]; + NSString* request_group_id = ExtractRequestGroupIDFromUserAgent(user_agent); + if (request_group_id) + return request_group_id; + if (application_scheme && + [[request.mainDocumentURL scheme] + caseInsensitiveCompare:application_scheme] == NSOrderedSame) { + return ExtractRequestGroupIDFromURL(request.mainDocumentURL); + } + return nil; +} + +} // namespace web diff --git a/ios/web/net/request_group_util_unittest.mm b/ios/web/net/request_group_util_unittest.mm new file mode 100644 index 0000000..b21e30e --- /dev/null +++ b/ios/web/net/request_group_util_unittest.mm @@ -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. + +#include "ios/web/net/request_group_util.h" + +#include <Foundation/Foundation.h> + +#include "base/mac/scoped_nsobject.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Checks that all newly generated groupID are unique and that there are no +// duplicates. +TEST(RequestGroupUtilTest, RequestGroupID) { + base::scoped_nsobject<NSMutableSet> set([[NSMutableSet alloc] init]); + const size_t kGenerated = 2000; + for (size_t i = 0; i < kGenerated; ++i) + [set addObject:web::GenerateNewRequestGroupID()]; + EXPECT_EQ(kGenerated, [set count]); +} + +// Tests that the ExtractRequestGroupIDFromUserAgent function behaves as +// intended. +TEST(RequestGroupUtilTest, ExtractRequestGroupIDFromUserAgent) { + EXPECT_FALSE(web::ExtractRequestGroupIDFromUserAgent(nil)); + EXPECT_FALSE(web::ExtractRequestGroupIDFromUserAgent( + @"Lynx/2.8.1pre.9 libwww-FM/2.14")); + EXPECT_FALSE(web::ExtractRequestGroupIDFromUserAgent(@" ")); + EXPECT_TRUE([web::ExtractRequestGroupIDFromUserAgent(@"Mozilla/3.04 (WinNT)") + isEqualToString:@"WinNT"]); +} diff --git a/ios/web/weak_nsobject_counter.h b/ios/web/weak_nsobject_counter.h new file mode 100644 index 0000000..f5fb62f --- /dev/null +++ b/ios/web/weak_nsobject_counter.h @@ -0,0 +1,35 @@ +// 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_WEAK_NSOBJECT_COUNTER_H_ +#define IOS_WEB_WEAK_NSOBJECT_COUNTER_H_ + +#import <Foundation/Foundation.h> + +#include "base/memory/linked_ptr.h" +#include "base/threading/non_thread_safe.h" + +namespace web { + +// WeakNSObjectCounter is a class to maintain a counter of all the objects +// that are added using |Insert| that are alive. +// This class is not thread safe and objects must be destroyed and created on +// the same thread. +// TODO(stuartmorgan): Remove this once ARC is supported. +class WeakNSObjectCounter : public base::NonThreadSafe { + public: + WeakNSObjectCounter(); + ~WeakNSObjectCounter(); + // Inserts an object. |object| cannot be nil. + void Insert(id object); + // Returns the count of all active objects that have been inserted. + NSUInteger Size() const; + private: + // The object that maintains the number of active items. + linked_ptr<NSUInteger> counter_; +}; + +} // namespace web + +#endif // IOS_WEB_WEAK_NSOBJECT_COUNTER_H_ diff --git a/ios/web/weak_nsobject_counter.mm b/ios/web/weak_nsobject_counter.mm new file mode 100644 index 0000000..bd14b7b --- /dev/null +++ b/ios/web/weak_nsobject_counter.mm @@ -0,0 +1,83 @@ +// 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/weak_nsobject_counter.h" + +#import <objc/runtime.h> + +#include "base/logging.h" +#import "base/mac/scoped_nsobject.h" + +namespace { +// The key needed for objc_setAssociatedObject. Any value will do, because the +// address is the key. +const char kObserverAssociatedObjectKey = 'h'; +} + +// Used for observing the objects tracked in the WeakNSObjectCounter. This +// object will be dealloced when the tracked object is dealloced and will +// notify the shared counter. +@interface CRBWeakNSObjectDeallocationObserver : NSObject +// Designated initializer. |object| cannot be nil. It registers self as an +// associated object to |object|. +- (instancetype)initWithSharedCounter:(const linked_ptr<NSUInteger>&)counter + objectToBeObserved:(id)object; +@end + +@implementation CRBWeakNSObjectDeallocationObserver { + linked_ptr<NSUInteger> _counter; +} + +- (instancetype)init { + NOTREACHED(); + return nil; +} + +- (instancetype)initWithSharedCounter:(const linked_ptr<NSUInteger>&)counter + objectToBeObserved:(id)object { + self = [super init]; + if (self) { + DCHECK(counter.get()); + DCHECK(object); + _counter = counter; + objc_setAssociatedObject(object, &kObserverAssociatedObjectKey, self, + OBJC_ASSOCIATION_RETAIN); + (*_counter)++; + } + return self; +} + +- (void)dealloc { + DCHECK(_counter.get()); + (*_counter)--; + _counter.reset(); + [super dealloc]; +} + +@end + +namespace web { + +WeakNSObjectCounter::WeakNSObjectCounter() : counter_(new NSUInteger(0)) { +} + +WeakNSObjectCounter::~WeakNSObjectCounter() { + DCHECK(CalledOnValidThread()); +} + +void WeakNSObjectCounter::Insert(id object) { + DCHECK(CalledOnValidThread()); + DCHECK(object); + // Create an associated object and register it with |object|. + base::scoped_nsobject<CRBWeakNSObjectDeallocationObserver> observingObject( + [[CRBWeakNSObjectDeallocationObserver alloc] + initWithSharedCounter:counter_ objectToBeObserved:object]); +} + +NSUInteger WeakNSObjectCounter::Size() const { + DCHECK(CalledOnValidThread()); + return *counter_; +} + +} // namespace web diff --git a/ios/web/weak_nsobject_counter_unittest.mm b/ios/web/weak_nsobject_counter_unittest.mm new file mode 100644 index 0000000..b981d2d --- /dev/null +++ b/ios/web/weak_nsobject_counter_unittest.mm @@ -0,0 +1,85 @@ +// 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/weak_nsobject_counter.h" + +#import <Foundation/Foundation.h> + +#import "base/ios/weak_nsobject.h" +#import "base/mac/scoped_nsobject.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Tests that a WeakNSObjectCounter correctly maintains a weak reference and +// calculates the size correctly when many objects are added. +TEST(WeakNSObjectCounter, ManyObjects) { + web::WeakNSObjectCounter object_counter; + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + base::scoped_nsobject<NSObject> p2([[NSObject alloc] init]); + base::scoped_nsobject<NSObject> p3([[NSObject alloc] init]); + object_counter.Insert(p1); + EXPECT_EQ(1U, object_counter.Size()); + object_counter.Insert(p2); + EXPECT_EQ(2U, object_counter.Size()); + object_counter.Insert(p3); + EXPECT_EQ(3U, object_counter.Size()); + // Make sure p3 is correctly removed from the object counter. + p3.reset(); + EXPECT_FALSE(p3); + EXPECT_EQ(2U, object_counter.Size()); + // Make sure p2 is correctly removed from the object counter. + p2.reset(); + EXPECT_EQ(1U, object_counter.Size()); + // Make sure p1 is correctly removed from the object counter. + p1.reset(); + EXPECT_FALSE(p1); + EXPECT_EQ(0U, object_counter.Size()); +} + +// Tests that WeakNSObjectCounter correctly works when the object outlives the +// WeakNSObjectCounter that is observing it. +TEST(WeakNSObjectCounter, ObjectOutlivesWeakNSObjectCounter) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + { + web::WeakNSObjectCounter object_counter; + object_counter.Insert(p1); + EXPECT_EQ(1U, object_counter.Size()); + } + p1.reset(); + EXPECT_FALSE(p1); +} + +// Tests that WeakNSObjectCounter does not add the same object twice. +TEST(WeakNSObjectCounter, SameObject) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + base::WeakNSObject<NSObject> p2(p1); + // Make sure they are the same object. + ASSERT_EQ(p1, p2); + web::WeakNSObjectCounter object_counter; + object_counter.Insert(p1); + EXPECT_EQ(1U, object_counter.Size()); + object_counter.Insert(p2); + EXPECT_EQ(1U, object_counter.Size()); +} + +// Tests that WeakNSObjectCounter correctly maintains a reference to the object +// and reduces its size only when the last reference to the object goes away. +TEST(WeakNSObjectCounter, LastReference) { + web::WeakNSObjectCounter object_counter; + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + object_counter.Insert(p1); + EXPECT_EQ(1U, object_counter.Size()); + base::scoped_nsobject<NSObject> p2(p1); + base::scoped_nsobject<NSObject> p3(p1); + p1.reset(); + EXPECT_EQ(1U, object_counter.Size()); + p2.reset(); + EXPECT_EQ(1U, object_counter.Size()); + // Last reference goes away. + p3.reset(); + EXPECT_FALSE(object_counter.Size()); +} + +} // namespace diff --git a/ios/web/web_state/blocked_popup_info.h b/ios/web/web_state/blocked_popup_info.h new file mode 100644 index 0000000..9373ad36 --- /dev/null +++ b/ios/web/web_state/blocked_popup_info.h @@ -0,0 +1,47 @@ +// 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_WEB_STATE_BLOCKED_POPUP_INFO_H_ +#define IOS_WEB_WEB_STATE_BLOCKED_POPUP_INFO_H_ + +#import <Foundation/Foundation.h> + +#include "base/ios/block_types.h" +#include "base/mac/scoped_nsobject.h" +#include "ios/web/public/referrer.h" +#include "url/gurl.h" + +namespace web { + +// Contain all information related to a blocked popup. +// TODO(eugenebut): rename to BlockedPopup as it's not an info object anymore. +class BlockedPopupInfo { + public: + BlockedPopupInfo(const GURL& url, + const Referrer& referrer, + NSString* window_name, + ProceduralBlock show_popup_handler); + ~BlockedPopupInfo(); + + // Returns the URL of the popup that was blocked. + const GURL& url() const { return url_; } + // Returns the Referrer of the URL that was blocked. + const Referrer& referrer() const { return referrer_; } + // Returns the window name of the popup that was blocked. + NSString* window_name() const { return window_name_; } + // Allows the popup by opening the blocked popup window. + void ShowPopup() const; + + BlockedPopupInfo(const BlockedPopupInfo& blocked_popup_info); + void operator=(const BlockedPopupInfo& blocked_popup_info); + private: + GURL url_; + Referrer referrer_; + base::scoped_nsobject<NSString> window_name_; + ProceduralBlock show_popup_handler_; +}; + +} // namespace web + +#endif // IOS_WEB_WEB_STATE_BLOCKED_POPUP_INFO_H_ diff --git a/ios/web/web_state/blocked_popup_info.mm b/ios/web/web_state/blocked_popup_info.mm new file mode 100644 index 0000000..721d18e --- /dev/null +++ b/ios/web/web_state/blocked_popup_info.mm @@ -0,0 +1,39 @@ +// 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/web_state/blocked_popup_info.h" + +namespace web { + +BlockedPopupInfo::BlockedPopupInfo(const GURL& url, + const Referrer& referrer, + NSString* window_name, + ProceduralBlock show_popup_handler) + : url_(url), + referrer_(referrer), + window_name_([window_name copy]), + show_popup_handler_([show_popup_handler copy]){ +} + +BlockedPopupInfo::BlockedPopupInfo(const BlockedPopupInfo& blocked_popup_info) + : url_(blocked_popup_info.url_), + referrer_(blocked_popup_info.referrer_), + window_name_([blocked_popup_info.window_name_ copy]), + show_popup_handler_([blocked_popup_info.show_popup_handler_ copy]) { +} + +BlockedPopupInfo::~BlockedPopupInfo() {} + +void BlockedPopupInfo::ShowPopup() const { + show_popup_handler_(); +} + +void BlockedPopupInfo::operator=(const BlockedPopupInfo& blocked_popup_info) { + url_ = blocked_popup_info.url_; + referrer_ = blocked_popup_info.referrer_; + window_name_.reset([blocked_popup_info.window_name_ copy]); + show_popup_handler_ = [blocked_popup_info.show_popup_handler_ copy]; +} + +} // namespace web diff --git a/ios/web/web_state/crw_recurring_task_delegate.h b/ios/web/web_state/crw_recurring_task_delegate.h new file mode 100644 index 0000000..12d6fd8f --- /dev/null +++ b/ios/web/web_state/crw_recurring_task_delegate.h @@ -0,0 +1,16 @@ +// 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_CRW_RECURRING_TASK_DELEGATE_H_ +#define IOS_WEB_WEB_STATE_CRW_RECURRING_TASK_DELEGATE_H_ + +// A delegate object to run some recurring task. +@protocol CRWRecurringTaskDelegate + +// Runs a recurring task. +- (void)runRecurringTask; + +@end + +#endif // IOS_WEB_WEB_STATE_CRW_RECURRING_TASK_DELEGATE_H_ diff --git a/ios/web/web_state/js/crw_js_message_dynamic_manager.mm b/ios/web/web_state/js/crw_js_message_dynamic_manager.mm index 50059f0..9067a19 100644 --- a/ios/web/web_state/js/crw_js_message_dynamic_manager.mm +++ b/ios/web/web_state/js/crw_js_message_dynamic_manager.mm @@ -4,15 +4,20 @@ #import "ios/web/web_state/js/crw_js_message_dynamic_manager.h" +#include "base/logging.h" #import "ios/web/web_state/js/crw_js_common_manager.h" -#include "ios/web/web_view_util.h" @implementation CRWJSMessageDynamicManager - (NSString*)scriptPath { - if (web::IsWKWebViewEnabled()) - return @"message_dynamic_wk"; - return @"message_dynamic_ui"; + switch ([[self receiver] webViewType]) { + case web::UI_WEB_VIEW_TYPE: + return @"message_dynamic_ui"; + case web::WK_WEB_VIEW_TYPE: + return @"message_dynamic_wk"; + } + NOTREACHED(); + return nil; } - (NSString*)presenceBeacon { diff --git a/ios/web/web_state/wk_web_view_ssl_error_util.h b/ios/web/web_state/wk_web_view_ssl_error_util.h new file mode 100644 index 0000000..a9610e5 --- /dev/null +++ b/ios/web/web_state/wk_web_view_ssl_error_util.h @@ -0,0 +1,29 @@ +// 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_WEB_STATE_WK_WEB_VIEW_SSL_ERROR_UTIL_H_ +#define IOS_WEB_WEB_STATE_WK_WEB_VIEW_SSL_ERROR_UTIL_H_ + +#import <Foundation/Foundation.h> + +namespace net { +class SSLInfo; +} + +namespace web { + +// NSErrorPeerCertificateChainKey from NSError's userInfo dict. +extern NSString* const kNSErrorPeerCertificateChainKey; + +// Returns YES if geven error is a SSL error. +BOOL IsWKWebViewSSLError(NSError* error); + +// Fills SSLInfo object with information extracted from |error|. Callers are +// responsible to ensure that given |error| is an SSL error by calling +// |web::IsSSLError| function. +void GetSSLInfoFromWKWebViewSSLError(NSError* error, net::SSLInfo* ssl_info); + +} // namespace web + +#endif // IOS_WEB_WEB_STATE_WK_WEB_VIEW_SSL_ERROR_UTIL_H_ diff --git a/ios/web/web_state/wk_web_view_ssl_error_util.mm b/ios/web/web_state/wk_web_view_ssl_error_util.mm new file mode 100644 index 0000000..18b38f8 --- /dev/null +++ b/ios/web/web_state/wk_web_view_ssl_error_util.mm @@ -0,0 +1,96 @@ +// 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/wk_web_view_ssl_error_util.h" + +#include "base/strings/sys_string_conversions.h" +#include "net/cert/x509_certificate.h" +#include "net/ssl/ssl_info.h" + +namespace web { + +// This key was determined by inspecting userInfo dict of an SSL error. +NSString* const kNSErrorPeerCertificateChainKey = + @"NSErrorPeerCertificateChainKey"; + +} + +namespace { + +// Creates certificate from subject string. +net::X509Certificate* CreateCertFromSubject(NSString* subject) { + std::string issuer = ""; + base::Time start_date; + base::Time expiration_date; + return new net::X509Certificate(base::SysNSStringToUTF8(subject), + issuer, + start_date, + expiration_date); +} + +// Creates certificate from array of SecCertificateRef objects. +net::X509Certificate* CreateCertFromChain(NSArray* certs) { + if (certs.count == 0) + return nullptr; + net::X509Certificate::OSCertHandles intermediates; + for (NSUInteger i = 1; i < certs.count; i++) { + intermediates.push_back(reinterpret_cast<SecCertificateRef>(certs[i])); + } + return net::X509Certificate::CreateFromHandle( + reinterpret_cast<SecCertificateRef>(certs[0]), intermediates); +} + +// Creates certificate using information extracted from NSError. +net::X509Certificate* CreateCertFromSSLError(NSError* error) { + net::X509Certificate* cert = CreateCertFromChain( + error.userInfo[web::kNSErrorPeerCertificateChainKey]); + if (cert) + return cert; + return CreateCertFromSubject( + error.userInfo[NSURLErrorFailingURLStringErrorKey]); +} + +// Maps NSError code to net::CertStatus. +net::CertStatus GetCertStatusFromNSErrorCode(NSInteger code) { + switch (code) { + // Regardless of real certificate problem the system always returns + // NSURLErrorServerCertificateUntrusted. The mapping is done in case this + // bug is fixed (rdar://18517043). + case NSURLErrorServerCertificateUntrusted: + case NSURLErrorSecureConnectionFailed: + case NSURLErrorServerCertificateHasUnknownRoot: + case NSURLErrorClientCertificateRejected: + case NSURLErrorClientCertificateRequired: + return net::CERT_STATUS_INVALID; + case NSURLErrorServerCertificateHasBadDate: + case NSURLErrorServerCertificateNotYetValid: + return net::CERT_STATUS_DATE_INVALID; + } + NOTREACHED(); + return 0; +} + +} // namespace + + +namespace web { + +BOOL IsWKWebViewSSLError(NSError* error) { + // SSL errors range is (-2000..-1200], represented by kCFURLError constants: + // (kCFURLErrorCannotLoadFromNetwork..kCFURLErrorSecureConnectionFailed]. + // It's reasonable to expect that all SSL errors will have the error code + // less or equal to NSURLErrorSecureConnectionFailed but greater than + // NSURLErrorCannotLoadFromNetwork. + return [error.domain isEqualToString:NSURLErrorDomain] && + (error.code <= NSURLErrorSecureConnectionFailed && + NSURLErrorCannotLoadFromNetwork < error.code); +} + +void GetSSLInfoFromWKWebViewSSLError(NSError* error, net::SSLInfo* ssl_info) { + DCHECK(IsWKWebViewSSLError(error)); + ssl_info->cert_status = GetCertStatusFromNSErrorCode(error.code); + ssl_info->cert = CreateCertFromSSLError(error); +} + +} // namespace web diff --git a/ios/web/web_view_util.h b/ios/web/web_view_util.h index 5b58ad8..a625c57 100644 --- a/ios/web/web_view_util.h +++ b/ios/web/web_view_util.h @@ -7,19 +7,8 @@ namespace web { -// Returns true if WKWebView is being used instead of UIWebView. -// TODO(stuartmorgan): Eliminate this global flag in favor of a per-web-view -// flag. -bool IsWKWebViewEnabled(); - -// If |flag| is true, causes IsWKWebViewEnabled to return false, even if -// WKWebView is enabled using the compile time flag. Should only be called from -// ScopedUIWebViewEnforcer for use in unit tests that need to test UIWebView -// while WKWebView is enabled. -void SetForceUIWebView(bool flag); - -// Returns true if use of UIWebView is to be enforced. -bool GetForceUIWebView(); +// Returns true if WKWebView is supported on current OS/platform/arch. +bool IsWKWebViewSupported(); } // web diff --git a/ios/web/web_view_util.mm b/ios/web/web_view_util.mm index d983306..6807d50 100644 --- a/ios/web/web_view_util.mm +++ b/ios/web/web_view_util.mm @@ -4,33 +4,39 @@ #include "ios/web/web_view_util.h" -#include "base/ios/ios_util.h" - -namespace { +#include <Foundation/Foundation.h> +#include <sys/sysctl.h> -// If true, UIWebView is always used even if WKWebView is available. -bool g_force_ui_web_view = false; - -} // namespace +#include "base/ios/ios_util.h" namespace web { -bool IsWKWebViewEnabled() { -#if defined(ENABLE_WKWEBVIEW) - // Eventually this may be a dynamic flag, but for now it's purely a - // compile-time option. - return !g_force_ui_web_view && base::ios::IsRunningOnIOS8OrLater(); +bool IsWKWebViewSupported() { + // WKWebView is available starting from iOS8. + if (!base::ios::IsRunningOnIOS8OrLater()) + return false; + +// WKWebView does not work with 32-bit binaries running on 64-bit hardware. +// (rdar://18268087) +#if !defined(__LP64__) + NSString* simulator_model_id = + [[NSProcessInfo processInfo] environment][@"SIMULATOR_MODEL_IDENTIFIER"]; + // For simulator it's not possible to query hw.cpu64bit_capable value as the + // function will return information for mac hardware rather than simulator. + if (simulator_model_id) { + // A set of known 32-bit simulators that are also iOS 8 compatible. + // (taken from iOS 8.1 SDK simulators list). + NSSet* known_32_bit_devices = [NSSet + setWithArray:@[ @"iPad2,1", @"iPad3,1", @"iPhone4,1", @"iPhone5,1" ]]; + return [known_32_bit_devices containsObject:simulator_model_id]; + } + uint32_t cpu64bit_capable = 0; + size_t sizes = sizeof(cpu64bit_capable); + sysctlbyname("hw.cpu64bit_capable", &cpu64bit_capable, &sizes, NULL, 0); + return !cpu64bit_capable; #else - return false; + return true; #endif } -void SetForceUIWebView(bool flag) { - g_force_ui_web_view = flag; -} - -bool GetForceUIWebView() { - return g_force_ui_web_view; -} - } // namespace web |