summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstuartmorgan <stuartmorgan@chromium.org>2015-03-24 09:58:09 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-24 16:58:50 +0000
commit271aab95144f3cb8dbf41e7a3bba829018a6dafc (patch)
treece6fba11ad0dc4d0ae0a68c506d898acd20a4b1d
parentc90b59db808926b62c1dcc42845af79fc5a82926 (diff)
downloadchromium_src-271aab95144f3cb8dbf41e7a3bba829018a6dafc.zip
chromium_src-271aab95144f3cb8dbf41e7a3bba829018a6dafc.tar.gz
chromium_src-271aab95144f3cb8dbf41e7a3bba829018a6dafc.tar.bz2
Upstream ios/web/navigation
This directly upstreams the remainder of the ios/web/navigation folder, and a few supporting pieces. BUG=464810 Review URL: https://codereview.chromium.org/1028603004 Cr-Commit-Position: refs/heads/master@{#322014}
-rw-r--r--ios/web/DEPS5
-rw-r--r--ios/web/ios_web.gyp2
-rw-r--r--ios/web/navigation/crw_session_certificate_policy_manager.h43
-rw-r--r--ios/web/navigation/crw_session_certificate_policy_manager.mm182
-rw-r--r--ios/web/navigation/crw_session_controller+private_constructors.h37
-rw-r--r--ios/web/navigation/crw_session_controller.h159
-rw-r--r--ios/web/navigation/crw_session_controller.mm874
-rw-r--r--ios/web/navigation/crw_session_controller_unittest.mm1022
-rw-r--r--ios/web/navigation/crw_session_entry.h65
-rw-r--r--ios/web/navigation/crw_session_entry.mm318
-rw-r--r--ios/web/navigation/crw_session_entry_unittest.mm400
-rw-r--r--ios/web/navigation/navigation_item_facade_delegate.h31
-rw-r--r--ios/web/navigation/navigation_item_impl.h42
-rw-r--r--ios/web/navigation/navigation_item_impl.mm55
-rw-r--r--ios/web/navigation/navigation_manager_delegate.h34
-rw-r--r--ios/web/navigation/navigation_manager_facade_delegate.h47
-rw-r--r--ios/web/navigation/navigation_manager_impl.h147
-rw-r--r--ios/web/navigation/navigation_manager_impl.mm251
-rw-r--r--ios/web/navigation/navigation_manager_impl_unittest.mm32
-rw-r--r--ios/web/navigation/web_load_params.h60
-rw-r--r--ios/web/navigation/web_load_params.mm42
-rw-r--r--ios/web/public/navigation_item.h10
-rw-r--r--ios/web/public/web_state/page_scroll_state.h75
-rw-r--r--ios/web/public/web_state/page_scroll_state.mm72
24 files changed, 4004 insertions, 1 deletions
diff --git a/ios/web/DEPS b/ios/web/DEPS
index 2f189bf..abbd333 100644
--- a/ios/web/DEPS
+++ b/ios/web/DEPS
@@ -1,8 +1,13 @@
include_rules = [
+ "+ios/public/provider/web",
+ "+ios/net",
"+ios/web",
"+net",
"+third_party/libwebp/webp",
"+ui",
+
+ # For tests.
+ "+ios/testing",
]
specific_include_rules = {
diff --git a/ios/web/ios_web.gyp b/ios/web/ios_web.gyp
index 5621e41..9f1845a 100644
--- a/ios/web/ios_web.gyp
+++ b/ios/web/ios_web.gyp
@@ -73,6 +73,8 @@
'public/web_state/js/crw_js_injection_manager.h',
'public/web_state/js/crw_js_injection_receiver.h',
'public/web_state/js/crw_js_message_manager.h',
+ 'public/web_state/page_scroll_state.h',
+ 'public/web_state/page_scroll_state.mm',
'public/web_state/url_verification_constants.h',
'public/web_state/web_state_observer.h',
'public/web_state/web_state_observer_bridge.h',
diff --git a/ios/web/navigation/crw_session_certificate_policy_manager.h b/ios/web/navigation/crw_session_certificate_policy_manager.h
new file mode 100644
index 0000000..a97e913
--- /dev/null
+++ b/ios/web/navigation/crw_session_certificate_policy_manager.h
@@ -0,0 +1,43 @@
+// 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_NAVIGATION_CRW_SESSION_CERTIFICATE_POLICY_MANAGER_H_
+#define IOS_WEB_NAVIGATION_CRW_SESSION_CERTIFICATE_POLICY_MANAGER_H_
+
+#import <Foundation/Foundation.h>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "net/cert/cert_status_flags.h"
+
+@class CRWSessionEntry;
+
+namespace net {
+class X509Certificate;
+}
+
+namespace web {
+class CertificatePolicyCache;
+}
+
+// The CRWSessionCertificatePolicyManager keeps track of the certificates that
+// have been manually allowed by the user despite the errors.
+// The CRWSessionCertificatePolicyManager lives on the main thread.
+@interface CRWSessionCertificatePolicyManager : NSObject <NSCoding, NSCopying>
+
+- (void)registerAllowedCertificate:(net::X509Certificate*)certificate
+ forHost:(const std::string&)host
+ status:(net::CertStatus)status;
+
+// Removes all the certificates associated with this session. Note that this has
+// no effect on the policy cache service.
+- (void)clearCertificates;
+
+// Copies the certificate polices for the session into |cache|.
+- (void)updateCertificatePolicyCache:
+ (const scoped_refptr<web::CertificatePolicyCache>&)cache;
+
+@end
+
+#endif // IOS_WEB_NAVIGATION_CRW_SESSION_CERTIFICATE_POLICY_MANAGER_H_
diff --git a/ios/web/navigation/crw_session_certificate_policy_manager.mm b/ios/web/navigation/crw_session_certificate_policy_manager.mm
new file mode 100644
index 0000000..3547536
--- /dev/null
+++ b/ios/web/navigation/crw_session_certificate_policy_manager.mm
@@ -0,0 +1,182 @@
+// 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/navigation/crw_session_certificate_policy_manager.h"
+
+#include <map>
+#include <set>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ios/web/public/certificate_policy_cache.h"
+#include "ios/web/public/web_thread.h"
+#include "net/cert/x509_certificate.h"
+
+// Break if we detect that CertStatus values changed, because we persist them on
+// disk and thus require them to be consistent.
+COMPILE_ASSERT(net::CERT_STATUS_ALL_ERRORS == 0xFFFF,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_COMMON_NAME_INVALID == 1 << 0,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_DATE_INVALID == 1 << 1,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_AUTHORITY_INVALID == 1 << 2,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_NO_REVOCATION_MECHANISM == 1 << 4,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION == 1 << 5,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_REVOKED == 1 << 6,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_INVALID == 1 << 7,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM == 1 << 8,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_NON_UNIQUE_NAME == 1 << 10,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_WEAK_KEY == 1 << 11,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_IS_EV == 1 << 16,
+ cert_status_value_changed);
+COMPILE_ASSERT(net::CERT_STATUS_REV_CHECKING_ENABLED == 1 << 17,
+ cert_status_value_changed);
+
+namespace {
+
+NSString* const kAllowedCertificatesKey = @"allowedCertificates";
+
+struct AllowedCertificate {
+ scoped_refptr<net::X509Certificate> certificate;
+ std::string host;
+};
+
+class LessThan {
+ public:
+ bool operator() (const AllowedCertificate& lhs,
+ const AllowedCertificate& rhs) const {
+ if (lhs.host != rhs.host)
+ return lhs.host < rhs.host;
+ return certificateCompare_(lhs.certificate, rhs.certificate);
+ }
+ private:
+ net::X509Certificate::LessThan certificateCompare_;
+};
+
+typedef std::map<AllowedCertificate, net::CertStatus, LessThan>
+ AllowedCertificates;
+
+NSData* CertificateToNSData(net::X509Certificate* certificate) {
+ std::string s;
+ bool success =
+ net::X509Certificate::GetDEREncoded(certificate->os_cert_handle(), &s);
+ DCHECK(success);
+ return [NSData dataWithBytes:s.c_str() length:s.length()];
+}
+
+net::X509Certificate* NSDataToCertificate(NSData* data) {
+ return net::X509Certificate::CreateFromBytes((const char *)[data bytes],
+ [data length]);
+}
+
+void AddToCertificatePolicyCache(
+ scoped_refptr<web::CertificatePolicyCache> policy_cache,
+ AllowedCertificates certs) {
+ DCHECK(policy_cache);
+ AllowedCertificates::iterator it;
+ for (it = certs.begin(); it != certs.end(); ++it) {
+ policy_cache->AllowCertForHost(
+ it->first.certificate.get(), it->first.host, it->second);
+ }
+}
+
+} // namespace
+
+@implementation CRWSessionCertificatePolicyManager {
+ @private
+ AllowedCertificates allowed_;
+}
+
+- (void)registerAllowedCertificate:(net::X509Certificate*)certificate
+ forHost:(const std::string&)host
+ status:(net::CertStatus)status {
+ DCHECK([NSThread isMainThread]);
+ DCHECK(certificate);
+ AllowedCertificate allowedCertificate = {certificate, host};
+ allowed_[allowedCertificate] = status;
+}
+
+- (void)clearCertificates {
+ DCHECK([NSThread isMainThread]);
+ allowed_.clear();
+}
+
+- (void)updateCertificatePolicyCache:
+ (const scoped_refptr<web::CertificatePolicyCache>&)cache {
+ DCHECK([NSThread isMainThread]);
+ DCHECK(cache);
+ // Make a copy of allowed_ and access the policy cache from the IOThread.
+ web::WebThread::PostTask(
+ web::WebThread::IO, FROM_HERE,
+ base::Bind(&AddToCertificatePolicyCache, cache, allowed_));
+}
+
+#pragma mark -
+#pragma mark NSCoding and NSCopying methods
+
+- (id)initWithCoder:(NSCoder*)aDecoder {
+ DCHECK([NSThread isMainThread]);
+ self = [super init];
+ if (self) {
+ NSMutableSet* allowed = [aDecoder
+ decodeObjectForKey:kAllowedCertificatesKey];
+ for (NSArray* fields in allowed) {
+ if ([fields count] == 2) {
+ DVLOG(2) << "Dropping cached certificate policy (old format).";
+ continue;
+ } else if ([fields count] != 3) {
+ NOTREACHED();
+ continue;
+ }
+ net::X509Certificate* c = NSDataToCertificate([fields objectAtIndex:0]);
+ std::string host = base::SysNSStringToUTF8([fields objectAtIndex:1]);
+ net::CertStatus status = (net::CertStatus)[[fields objectAtIndex:2]
+ unsignedIntegerValue];
+ [self registerAllowedCertificate:c forHost:host status:status];
+ }
+ }
+ return self;
+}
+
+- (void)encodeWithCoder:(NSCoder*)aCoder {
+ if (allowed_.size() == 0)
+ return;
+
+ // Simple serialization of the set. If a same certificate is duplicated in the
+ // set (for a different host), the serialization will be duplicated as well.
+ NSMutableSet* allowedToEncode = [NSMutableSet set];
+ AllowedCertificates::iterator it;
+ for (it = allowed_.begin(); it != allowed_.end(); ++it) {
+ NSData* c = CertificateToNSData(it->first.certificate.get());
+ NSString* h = base::SysUTF8ToNSString(it->first.host);
+ DCHECK(c);
+ DCHECK(h);
+ if (!c || !h)
+ continue;
+ const NSUInteger status = (NSUInteger)it->second;
+ NSArray* fields = [NSArray arrayWithObjects:c, h, @(status), nil];
+ [allowedToEncode addObject:fields];
+ }
+ [aCoder encodeObject:allowedToEncode forKey:kAllowedCertificatesKey];
+}
+
+- (id)copyWithZone:(NSZone*)zone {
+ DCHECK([NSThread isMainThread]);
+ CRWSessionCertificatePolicyManager* copy = [[[self class] alloc] init];
+ copy->allowed_ = allowed_;
+ return copy;
+}
+
+@end
diff --git a/ios/web/navigation/crw_session_controller+private_constructors.h b/ios/web/navigation/crw_session_controller+private_constructors.h
new file mode 100644
index 0000000..dd7a119
--- /dev/null
+++ b/ios/web/navigation/crw_session_controller+private_constructors.h
@@ -0,0 +1,37 @@
+// 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_NAVIGATION_CRW_SESSION_CONTROLLER_PRIVATE_CONSTRUCTORS_H_
+#define IOS_WEB_NAVIGATION_CRW_SESSION_CONTROLLER_PRIVATE_CONSTRUCTORS_H_
+
+#import "ios/web/navigation/crw_session_controller.h"
+
+#include "base/memory/scoped_vector.h"
+
+namespace web {
+class BrowserState;
+class NavigationItem;
+}
+
+// Temporary interface for NavigationManager and tests to create
+// CRWSessionControllers. Once CRWSessionController has no users outside of
+// web/, these methods can go back into session_controller.h. crbug.com/318974
+@interface CRWSessionController (PrivateConstructors)
+// Initializes a session controller, supplying a unique textual identifier for
+// the window, or nil. |opener| is the tab id of the parent tab. It may be
+// nil or empty if there is no parent.
+- (id)initWithWindowName:(NSString*)windowName
+ openerId:(NSString*)opener
+ openedByDOM:(BOOL)openedByDOM
+ openerNavigationIndex:(NSInteger)openerIndex
+ browserState:(web::BrowserState*)browserState;
+
+// Initializes a session controller, supplying a list of NavigationItem objects
+// and the current index in the navigation history.
+- (id)initWithNavigationItems:(ScopedVector<web::NavigationItem>)scoped_items
+ currentIndex:(NSUInteger)currentIndex
+ browserState:(web::BrowserState*)browserState;
+@end
+
+#endif // IOS_WEB_NAVIGATION_CRW_SESSION_CONTROLLER_PRIVATE_CONSTRUCTORS_H_
diff --git a/ios/web/navigation/crw_session_controller.h b/ios/web/navigation/crw_session_controller.h
new file mode 100644
index 0000000..b6eb972
--- /dev/null
+++ b/ios/web/navigation/crw_session_controller.h
@@ -0,0 +1,159 @@
+// 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_NAVIGATION_CRW_SESSION_CONTROLLER_H_
+#define IOS_WEB_NAVIGATION_CRW_SESSION_CONTROLLER_H_
+
+#import <Foundation/Foundation.h>
+#include <vector>
+
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+@class CRWSessionEntry;
+@class CRWSessionCertificatePolicyManager;
+@class XCallbackParameters;
+
+namespace web {
+class NavigationManagerImpl;
+struct Referrer;
+struct SSLStatus;
+}
+
+// A CRWSessionController is similar to a NavigationController object in desktop
+// Chrome. It maintains information needed to save/restore a tab with its
+// complete session history. There is one of these for each tab.
+// TODO(stuartmorgan): Move under NavigationManager, and consider merging into
+// NavigationManager.
+@interface CRWSessionController : NSObject<NSCoding, NSCopying>
+
+@property(nonatomic, readonly, retain) NSString* tabId;
+@property(nonatomic, readonly, assign) NSInteger currentNavigationIndex;
+@property(nonatomic, readonly, assign) NSInteger previousNavigationIndex;
+@property(nonatomic, readonly, retain) NSArray* entries;
+@property(nonatomic, copy) NSString* windowName;
+// Indicates whether the page was opened by DOM (e.g. with |window.open|
+// JavaScript call or by clicking a link with |_blank| target).
+@property(nonatomic, readonly, getter=isOpenedByDOM) BOOL openedByDOM;
+@property(nonatomic, readonly, retain)
+ CRWSessionCertificatePolicyManager* sessionCertificatePolicyManager;
+// Returns the current entry in the session list, or the pending entry if there
+// is a navigation in progress.
+@property(nonatomic, readonly) CRWSessionEntry* currentEntry;
+// Returns the entry that should be displayed to users (e.g., in the omnibox).
+@property(nonatomic, readonly) CRWSessionEntry* visibleEntry;
+// Returns the pending entry, if any.
+@property(nonatomic, readonly) CRWSessionEntry* pendingEntry;
+// Returns the transient entry, if any.
+@property(nonatomic, readonly) CRWSessionEntry* transientEntry;
+// Returns the last committed entry.
+@property(nonatomic, readonly) CRWSessionEntry* lastCommittedEntry;
+// Returns the previous entry in the session list, or nil if there isn't any.
+@property(nonatomic, readonly) CRWSessionEntry* previousEntry;
+@property(nonatomic, assign) NSTimeInterval lastVisitedTimestamp;
+@property(nonatomic, readonly, copy) NSString* openerId;
+@property(nonatomic, readonly, assign) NSInteger openerNavigationIndex;
+@property(nonatomic, retain) XCallbackParameters* xCallbackParameters;
+
+// CRWSessionController doesn't have public constructors. New
+// CRWSessionControllers are created by deserialization, or via a
+// NavigationManager.
+
+// Sets the corresponding NavigationManager.
+- (void)setNavigationManager:(web::NavigationManagerImpl*)navigationManager;
+
+// Add a new entry with the given url, referrer, and navigation type, making it
+// the current entry. If |url| is the same as the current entry's url, this
+// does nothing. |referrer| may be nil if there isn't one. The entry starts
+// out as pending, and will be lost unless |-commitPendingEntry| is called.
+- (void)addPendingEntry:(const GURL&)url
+ referrer:(const web::Referrer&)referrer
+ transition:(ui::PageTransition)type
+ rendererInitiated:(BOOL)rendererInitiated;
+
+// Updates the URL of the yet to be committed pending entry. Useful for page
+// redirects. Does nothing if there is no pending entry.
+- (void)updatePendingEntry:(const GURL&)url;
+
+// Commits the current pending entry. No changes are made to the entry during
+// this process, it is just moved from pending to committed.
+// TODO(pinkerton): Desktop Chrome broadcasts a notification here, should we?
+- (void)commitPendingEntry;
+
+// Adds a transient entry with the given information. A transient entry will be
+// discarded on any navigation.
+// TODO(stuartmorgan): Make this work more like upstream, where the entry can
+// be made from outside and then handed in, instead of having to pass a bunch
+// of construction params here.
+- (void)addTransientEntry:(const GURL&)url
+ title:(const base::string16&)title
+ sslStatus:(const web::SSLStatus*)status;
+
+// Creates a new CRWSessionEntry with the given URL and state object. A state
+// object is a serialized generic JavaScript object that contains details of the
+// UI's state for a given CRWSessionEntry/URL. The current entry's URL is the
+// new entry's referrer.
+- (void)pushNewEntryWithURL:(const GURL&)url stateObject:(NSString*)stateObject;
+// Updates the URL and state object for the current entry.
+- (void)updateCurrentEntryWithURL:(const GURL&)url
+ stateObject:(NSString*)stateObject;
+
+- (void)discardNonCommittedEntries;
+
+// Returns YES if there is a pending entry.
+- (BOOL)hasPendingEntry;
+
+// Copies history state from the given CRWSessionController and adds it to this
+// controller. If |replaceState|, replaces the state of this controller with
+// the state of |otherSession|, instead of appending.
+- (void)copyStateFromAndPrune:(CRWSessionController*)otherSession
+ replaceState:(BOOL)replaceState;
+
+// Returns YES if there are entries to go back or forward to, given the
+// current entry.
+- (BOOL)canGoBack;
+- (BOOL)canGoForward;
+// Adjusts the current entry to reflect the navigation in the corresponding
+// direction in history.
+- (void)goBack;
+- (void)goForward;
+// Calls goBack or goForward the appropriate number of times to adjust
+// currentNavigationIndex_ by delta.
+- (void)goDelta:(int)delta;
+// Sets |currentNavigationIndex_| to the index of |entry| if |entries_| contains
+// |entry|.
+- (void)goToEntry:(CRWSessionEntry*)entry;
+
+// Removes the entry at |index| after discarding any noncomitted entries.
+// |index| must not be the index of the last committed entry, or a noncomitted
+// entry.
+- (void)removeEntryAtIndex:(NSInteger)index;
+
+// Returns an array containing all of the non-redirected CRWSessionEntry objects
+// whose index in |entries_| is less than |currentNavigationIndex_|.
+- (NSArray*)backwardEntries;
+
+// Returns an array containing all of the non-redirected CRWSessionEntry objects
+// whose index in |entries_| is greater than |currentNavigationIndex_|.
+- (NSArray*)forwardEntries;
+
+// Returns the URLs in the entries that are redirected to the current entry.
+- (std::vector<GURL>)currentRedirectedUrls;
+
+// Determines if navigation between the two given entries is a push state
+// navigation. Entries can be passed in in any order.
+- (BOOL)isPushStateNavigationBetweenEntry:(CRWSessionEntry*)firstEntry
+ andEntry:(CRWSessionEntry*)secondEntry;
+
+// Find the most recent session entry that is not a redirect. Returns nil if
+// |entries_| is empty.
+- (CRWSessionEntry*)lastUserEntry;
+
+// Set |useDesktopUserAgentForNextPendingEntry_| to YES if there is no pending
+// entry, otherwise set |useDesktopUserAgent| in the pending entry.
+- (void)useDesktopUserAgentForNextPendingEntry;
+
+@end
+
+#endif // IOS_WEB_NAVIGATION_CRW_SESSION_CONTROLLER_H_
diff --git a/ios/web/navigation/crw_session_controller.mm b/ios/web/navigation/crw_session_controller.mm
new file mode 100644
index 0000000..5407ec3
--- /dev/null
+++ b/ios/web/navigation/crw_session_controller.mm
@@ -0,0 +1,874 @@
+// 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/navigation/crw_session_controller.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/mac/objc_property_releaser.h"
+#import "base/mac/scoped_nsobject.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/sys_string_conversions.h"
+#import "ios/web/history_state_util.h"
+#import "ios/web/navigation/crw_session_certificate_policy_manager.h"
+#import "ios/web/navigation/crw_session_controller+private_constructors.h"
+#import "ios/web/navigation/crw_session_entry.h"
+#include "ios/web/navigation/navigation_item_impl.h"
+#import "ios/web/navigation/navigation_manager_facade_delegate.h"
+#import "ios/web/navigation/navigation_manager_impl.h"
+#include "ios/web/navigation/time_smoother.h"
+#include "ios/web/public/browser_state.h"
+#include "ios/web/public/browser_url_rewriter.h"
+#include "ios/web/public/referrer.h"
+#include "ios/web/public/ssl_status.h"
+#include "ios/web/public/user_metrics.h"
+
+using base::UserMetricsAction;
+
+namespace {
+NSString* const kCertificatePolicyManagerKey = @"certificatePolicyManager";
+NSString* const kCurrentNavigationIndexKey = @"currentNavigationIndex";
+NSString* const kEntriesKey = @"entries";
+NSString* const kLastVisitedTimestampKey = @"lastVisitedTimestamp";
+NSString* const kOpenerIdKey = @"openerId";
+NSString* const kOpenedByDOMKey = @"openedByDOM";
+NSString* const kOpenerNavigationIndexKey = @"openerNavigationIndex";
+NSString* const kPreviousNavigationIndexKey = @"previousNavigationIndex";
+NSString* const kTabIdKey = @"tabId";
+NSString* const kWindowNameKey = @"windowName";
+NSString* const kXCallbackParametersKey = @"xCallbackParameters";
+} // anonymous namespace
+
+@interface CRWSessionController () {
+ // Weak pointer back to the owning NavigationManager. This is to facilitate
+ // the incremental merging of the two classes.
+ web::NavigationManagerImpl* _navigationManager;
+
+ NSString* _tabId; // Unique id of the tab.
+ NSString* _openerId; // Id of tab who opened this tab, empty/nil if none.
+ // Navigation index of the tab which opened this tab. Do not rely on the
+ // value of this member variable to indicate whether or not this tab has
+ // an opener, as both 0 and -1 are used as navigationIndex values.
+ NSInteger _openerNavigationIndex;
+ // Identifies the index of the current navigation in the CRWSessionEntry
+ // array.
+ NSInteger _currentNavigationIndex;
+ // Identifies the index of the previous navigation in the CRWSessionEntry
+ // array.
+ NSInteger _previousNavigationIndex;
+ // Ordered array of |CRWSessionEntry| objects, one for each site in session
+ // history. End of the list is the most recent load.
+ NSMutableArray* _entries;
+
+ // An entry we haven't gotten a response for yet. This will be discarded
+ // when we navigate again. It's used only so we know what the currently
+ // displayed tab is. It backs the property of the same name and should only
+ // be set through its setter.
+ base::scoped_nsobject<CRWSessionEntry> _pendingEntry;
+
+ // The transient entry, if any. A transient entry is discarded on any
+ // navigation, and is used for representing interstitials that need to be
+ // represented in the session. It backs the property of the same name and
+ // should only be set through its setter.
+ base::scoped_nsobject<CRWSessionEntry> _transientEntry;
+
+ // The window name associated with the session.
+ NSString* _windowName;
+
+ // Stores the certificate policies decided by the user.
+ CRWSessionCertificatePolicyManager* _sessionCertificatePolicyManager;
+
+ // The timestamp of the last time this tab is visited, represented in time
+ // interval since 1970.
+ NSTimeInterval _lastVisitedTimestamp;
+
+ // If |YES|, override |currentEntry.useDesktopUserAgent| and create the
+ // pending entry using the desktop user agent.
+ BOOL _useDesktopUserAgentForNextPendingEntry;
+
+ // The browser state associated with this CRWSessionController;
+ __weak web::BrowserState* _browserState;
+
+ // Time smoother for navigation entry timestamps; see comment in
+ // navigation_controller_impl.h
+ web::TimeSmoother _timeSmoother;
+
+ // XCallback parameters used to create (or clobber) the tab. Can be nil.
+ XCallbackParameters* _xCallbackParameters;
+
+ base::mac::ObjCPropertyReleaser _propertyReleaser_CRWSessionController;
+}
+
+// TODO(rohitrao): These properties must be redefined readwrite to work around a
+// clang bug. crbug.com/228650
+@property(nonatomic, readwrite, retain) NSString* tabId;
+@property(nonatomic, readwrite, retain) NSArray* entries;
+@property(nonatomic, readwrite, retain)
+ CRWSessionCertificatePolicyManager* sessionCertificatePolicyManager;
+
+- (NSString*)uniqueID;
+// Removes all entries after currentNavigationIndex_.
+- (void)clearForwardEntries;
+// Discards the transient entry, if any.
+- (void)discardTransientEntry;
+// Create a new autoreleased session entry.
+- (CRWSessionEntry*)sessionEntryWithURL:(const GURL&)url
+ referrer:(const web::Referrer&)referrer
+ transition:(ui::PageTransition)transition
+ useDesktopUserAgent:(BOOL)useDesktopUserAgent
+ rendererInitiated:(BOOL)rendererInitiated;
+// Return the PageTransition for the underlying navigationItem at |index| in
+// |entries_|
+- (ui::PageTransition)transitionForIndex:(NSUInteger)index;
+@end
+
+@implementation CRWSessionController
+
+@synthesize tabId = _tabId;
+@synthesize currentNavigationIndex = _currentNavigationIndex;
+@synthesize previousNavigationIndex = _previousNavigationIndex;
+@synthesize entries = _entries;
+@synthesize windowName = _windowName;
+@synthesize lastVisitedTimestamp = _lastVisitedTimestamp;
+@synthesize openerId = _openerId;
+@synthesize openedByDOM = _openedByDOM;
+@synthesize openerNavigationIndex = _openerNavigationIndex;
+@synthesize sessionCertificatePolicyManager = _sessionCertificatePolicyManager;
+@synthesize xCallbackParameters = _xCallbackParameters;
+
+- (id)initWithWindowName:(NSString*)windowName
+ openerId:(NSString*)openerId
+ openedByDOM:(BOOL)openedByDOM
+ openerNavigationIndex:(NSInteger)openerIndex
+ browserState:(web::BrowserState*)browserState {
+ self = [super init];
+ if (self) {
+ _propertyReleaser_CRWSessionController.Init(self,
+ [CRWSessionController class]);
+ self.windowName = windowName;
+ _tabId = [[self uniqueID] retain];
+ _openerId = [openerId copy];
+ _openedByDOM = openedByDOM;
+ _openerNavigationIndex = openerIndex;
+ _browserState = browserState;
+ _entries = [[NSMutableArray array] retain];
+ _lastVisitedTimestamp = [[NSDate date] timeIntervalSince1970];
+ _currentNavigationIndex = -1;
+ _previousNavigationIndex = -1;
+ _sessionCertificatePolicyManager =
+ [[CRWSessionCertificatePolicyManager alloc] init];
+ }
+ return self;
+}
+
+- (id)initWithNavigationItems:(ScopedVector<web::NavigationItem>)scoped_items
+ currentIndex:(NSUInteger)currentIndex
+ browserState:(web::BrowserState*)browserState {
+ self = [super init];
+ if (self) {
+ _propertyReleaser_CRWSessionController.Init(self,
+ [CRWSessionController class]);
+ _tabId = [[self uniqueID] retain];
+ _openerId = nil;
+ _browserState = browserState;
+
+ // Create entries array from list of navigations.
+ _entries = [[NSMutableArray alloc] initWithCapacity:scoped_items.size()];
+ std::vector<web::NavigationItem*> items;
+ scoped_items.release(&items);
+
+ for (size_t i = 0; i < items.size(); ++i) {
+ scoped_ptr<web::NavigationItem> item(items[i]);
+ base::scoped_nsobject<CRWSessionEntry> entry(
+ [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass() index:i]);
+ [_entries addObject:entry];
+ }
+ _currentNavigationIndex = currentIndex;
+ // Prior to M34, 0 was used as "no index" instead of -1; adjust for that.
+ if (![_entries count])
+ _currentNavigationIndex = -1;
+ if (_currentNavigationIndex >= static_cast<NSInteger>(items.size())) {
+ _currentNavigationIndex = static_cast<NSInteger>(items.size()) - 1;
+ }
+ _previousNavigationIndex = -1;
+ _lastVisitedTimestamp = [[NSDate date] timeIntervalSince1970];
+ _sessionCertificatePolicyManager =
+ [[CRWSessionCertificatePolicyManager alloc] init];
+ }
+ return self;
+}
+
+- (id)initWithCoder:(NSCoder*)aDecoder {
+ self = [super init];
+ if (self) {
+ _propertyReleaser_CRWSessionController.Init(self,
+ [CRWSessionController class]);
+ NSString* uuid = [aDecoder decodeObjectForKey:kTabIdKey];
+ if (!uuid)
+ uuid = [self uniqueID];
+
+ self.windowName = [aDecoder decodeObjectForKey:kWindowNameKey];
+ _tabId = [uuid retain];
+ _openerId = [[aDecoder decodeObjectForKey:kOpenerIdKey] copy];
+ _openedByDOM = [aDecoder decodeBoolForKey:kOpenedByDOMKey];
+ _openerNavigationIndex =
+ [aDecoder decodeIntForKey:kOpenerNavigationIndexKey];
+ _currentNavigationIndex =
+ [aDecoder decodeIntForKey:kCurrentNavigationIndexKey];
+ _previousNavigationIndex =
+ [aDecoder decodeIntForKey:kPreviousNavigationIndexKey];
+ _lastVisitedTimestamp =
+ [aDecoder decodeDoubleForKey:kLastVisitedTimestampKey];
+ NSMutableArray* temp =
+ [NSMutableArray arrayWithArray:
+ [aDecoder decodeObjectForKey:kEntriesKey]];
+ _entries = [temp retain];
+ // Prior to M34, 0 was used as "no index" instead of -1; adjust for that.
+ if (![_entries count])
+ _currentNavigationIndex = -1;
+ _sessionCertificatePolicyManager =
+ [[aDecoder decodeObjectForKey:kCertificatePolicyManagerKey] retain];
+ if (!_sessionCertificatePolicyManager) {
+ _sessionCertificatePolicyManager =
+ [[CRWSessionCertificatePolicyManager alloc] init];
+ }
+
+ _xCallbackParameters =
+ [[aDecoder decodeObjectForKey:kXCallbackParametersKey] retain];
+ }
+ return self;
+}
+
+- (void)encodeWithCoder:(NSCoder*)aCoder {
+ [aCoder encodeObject:_tabId forKey:kTabIdKey];
+ [aCoder encodeObject:_openerId forKey:kOpenerIdKey];
+ [aCoder encodeBool:_openedByDOM forKey:kOpenedByDOMKey];
+ [aCoder encodeInt:_openerNavigationIndex forKey:kOpenerNavigationIndexKey];
+ [aCoder encodeObject:_windowName forKey:kWindowNameKey];
+ [aCoder encodeInt:_currentNavigationIndex forKey:kCurrentNavigationIndexKey];
+ [aCoder encodeInt:_previousNavigationIndex
+ forKey:kPreviousNavigationIndexKey];
+ [aCoder encodeDouble:_lastVisitedTimestamp forKey:kLastVisitedTimestampKey];
+ [aCoder encodeObject:_entries forKey:kEntriesKey];
+ [aCoder encodeObject:_sessionCertificatePolicyManager
+ forKey:kCertificatePolicyManagerKey];
+ [aCoder encodeObject:_xCallbackParameters forKey:kXCallbackParametersKey];
+ // rendererInitiated is deliberately not preserved, as upstream.
+}
+
+- (id)copyWithZone:(NSZone*)zone {
+ CRWSessionController* copy = [[[self class] alloc] init];
+ copy->_propertyReleaser_CRWSessionController.Init(
+ copy, [CRWSessionController class]);
+ copy->_tabId = [_tabId copy];
+ copy->_openerId = [_openerId copy];
+ copy->_openedByDOM = _openedByDOM;
+ copy->_openerNavigationIndex = _openerNavigationIndex;
+ copy.windowName = self.windowName;
+ copy->_currentNavigationIndex = _currentNavigationIndex;
+ copy->_previousNavigationIndex = _previousNavigationIndex;
+ copy->_lastVisitedTimestamp = _lastVisitedTimestamp;
+ copy->_entries = [_entries copy];
+ copy->_sessionCertificatePolicyManager =
+ [_sessionCertificatePolicyManager copy];
+ copy->_xCallbackParameters = [_xCallbackParameters copy];
+ return copy;
+}
+
+- (void)setNavigationManager:(web::NavigationManagerImpl*)navigationManager {
+ _navigationManager = navigationManager;
+ if (_navigationManager) {
+ // _browserState will be nullptr if CRWSessionController has been
+ // initialized with -initWithCoder: method. Take _browserState from
+ // NavigationManagerImpl if that's the case.
+ if (!_browserState) {
+ _browserState = _navigationManager->GetBrowserState();
+ }
+ DCHECK_EQ(_browserState, _navigationManager->GetBrowserState());
+ }
+}
+
+- (NSString*)description {
+ return [NSString
+ stringWithFormat:
+ @"id: %@\nname: %@\nlast visit: %f\ncurrent index: %" PRIdNS
+ @"\nprevious index: %" PRIdNS "\n%@\npending: %@\nxCallback:\n%@\n",
+ _tabId,
+ self.windowName,
+ _lastVisitedTimestamp,
+ _currentNavigationIndex,
+ _previousNavigationIndex,
+ _entries,
+ _pendingEntry.get(),
+ _xCallbackParameters];
+}
+
+// Returns the current entry in the session list, or the pending entry if there
+// is a navigation in progress.
+- (CRWSessionEntry*)currentEntry {
+ if (_transientEntry)
+ return _transientEntry.get();
+ if (_pendingEntry)
+ return _pendingEntry.get();
+ return [self lastCommittedEntry];
+}
+
+// See NavigationController::GetVisibleEntry for the motivation for this
+// distinction.
+- (CRWSessionEntry*)visibleEntry {
+ if (_transientEntry)
+ return _transientEntry.get();
+ // Only return the pending_entry for:
+ // (a) new (non-history), browser-initiated navigations, and
+ // (b) pending unsafe navigations (while showing the interstitial)
+ // in order to prevent URL spoof attacks.
+ web::NavigationItemImpl* pendingItemImpl =
+ static_cast<web::NavigationItemImpl*>([_pendingEntry navigationItem]);
+ if (_pendingEntry &&
+ (!pendingItemImpl->is_renderer_initiated() ||
+ pendingItemImpl->IsUnsafe())) {
+ return _pendingEntry.get();
+ }
+ return [self lastCommittedEntry];
+}
+
+- (CRWSessionEntry*)pendingEntry {
+ return _pendingEntry.get();
+}
+
+- (CRWSessionEntry*)transientEntry {
+ return _transientEntry.get();
+}
+
+- (CRWSessionEntry*)lastCommittedEntry {
+ if (_currentNavigationIndex == -1)
+ return nil;
+ return [_entries objectAtIndex:_currentNavigationIndex];
+}
+
+// Returns the previous entry in the session list, or nil if there isn't any.
+- (CRWSessionEntry*)previousEntry {
+ if ((_previousNavigationIndex < 0) || (![_entries count]))
+ return nil;
+ return [_entries objectAtIndex:_previousNavigationIndex];
+}
+
+- (void)addPendingEntry:(const GURL&)url
+ referrer:(const web::Referrer&)ref
+ transition:(ui::PageTransition)trans
+ rendererInitiated:(BOOL)rendererInitiated {
+ [self discardTransientEntry];
+
+ // Don't create a new entry if it's already the same as the current entry,
+ // allowing this routine to be called multiple times in a row without issue.
+ // Note: CRWSessionController currently has the responsibility to distinguish
+ // between new navigations and history stack navigation, hence the inclusion
+ // of specific transiton type logic here, in order to make it reliable with
+ // real-world observed behavior.
+ // TODO(stuartmorgan): Fix the way changes are detected/reported elsewhere
+ // in the web layer so that this hack can be removed.
+ // Remove the workaround code from -presentSafeBrowsingWarningForResource:.
+ CRWSessionEntry* currentEntry = self.currentEntry;
+ if (currentEntry) {
+ // If the current entry is known-unsafe (and thus not visible and likely to
+ // be removed), ignore any renderer-initated updates and don't worry about
+ // sending a notification.
+ web::NavigationItem* item = [currentEntry navigationItem];
+ if (item->IsUnsafe() && rendererInitiated) {
+ return;
+ }
+ if (item->GetURL() == url &&
+ (!PageTransitionCoreTypeIs(trans, ui::PAGE_TRANSITION_FORM_SUBMIT) ||
+ PageTransitionCoreTypeIs(item->GetTransitionType(),
+ ui::PAGE_TRANSITION_FORM_SUBMIT) ||
+ item->IsUnsafe())) {
+ // Send the notification anyway, to preserve old behavior. It's unknown
+ // whether anything currently relies on this, but since both this whole
+ // hack and the content facade will both be going away, it's not worth
+ // trying to unwind.
+ if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
+ _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
+ }
+ return;
+ }
+ }
+
+ BOOL useDesktopUserAgent = _useDesktopUserAgentForNextPendingEntry ||
+ self.currentEntry.useDesktopUserAgent;
+ _useDesktopUserAgentForNextPendingEntry = NO;
+ _pendingEntry.reset([[self sessionEntryWithURL:url
+ referrer:ref
+ transition:trans
+ useDesktopUserAgent:useDesktopUserAgent
+ rendererInitiated:rendererInitiated] retain]);
+
+ if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
+ _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
+ }
+}
+
+- (void)updatePendingEntry:(const GURL&)url {
+ [self discardTransientEntry];
+
+ // If there is no pending entry, navigation is probably happening within the
+ // session history. Don't modify the entry list.
+ if (!_pendingEntry)
+ return;
+ web::NavigationItem* item = [_pendingEntry navigationItem];
+ if (url != item->GetURL()) {
+ item->SetURL(url);
+ item->SetVirtualURL(url);
+ // Since updates are caused by page redirects, they are renderer-initiated.
+ web::NavigationItemImpl* pendingItemImpl =
+ static_cast<web::NavigationItemImpl*>([_pendingEntry navigationItem]);
+ pendingItemImpl->set_is_renderer_initiated(true);
+ // Redirects (3xx response code), or client side navigation must change
+ // POST requests to GETs.
+ [_pendingEntry setPOSTData:nil];
+ [_pendingEntry resetHTTPHeaders];
+ }
+
+ // This should probably not be sent if the URLs matched, but that's what was
+ // done before, so preserve behavior in case something relies on it.
+ if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
+ _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
+ }
+}
+
+- (void)clearForwardEntries {
+ [self discardTransientEntry];
+
+ NSInteger forwardEntryStartIndex = _currentNavigationIndex + 1;
+ DCHECK(forwardEntryStartIndex >= 0);
+
+ if (forwardEntryStartIndex >= static_cast<NSInteger>([_entries count]))
+ return;
+
+ NSRange remove = NSMakeRange(forwardEntryStartIndex,
+ [_entries count] - forwardEntryStartIndex);
+ // Store removed items in temporary NSArray so they can be deallocated after
+ // their facades.
+ base::scoped_nsobject<NSArray> removedItems(
+ [[_entries subarrayWithRange:remove] retain]);
+ [_entries removeObjectsInRange:remove];
+ if (_previousNavigationIndex >= forwardEntryStartIndex)
+ _previousNavigationIndex = -1;
+ if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
+ _navigationManager->GetFacadeDelegate()->OnNavigationItemsPruned(
+ remove.length);
+ }
+}
+
+- (void)commitPendingEntry {
+ if (_pendingEntry) {
+ [self clearForwardEntries];
+ // Add the new entry at the end.
+ [_entries addObject:_pendingEntry];
+ _previousNavigationIndex = _currentNavigationIndex;
+ _currentNavigationIndex = [_entries count] - 1;
+ // Once an entry is committed it's not renderer-initiated any more. (Matches
+ // the implementation in NavigationController.)
+ web::NavigationItemImpl* pendingItemImpl =
+ static_cast<web::NavigationItemImpl*>([_pendingEntry navigationItem]);
+ pendingItemImpl->ResetForCommit();
+ _pendingEntry.reset();
+ }
+
+ CRWSessionEntry* currentEntry = self.currentEntry;
+ web::NavigationItem* item = currentEntry.navigationItem;
+ // Update the navigation timestamp now that it's actually happened.
+ if (item)
+ item->SetTimestamp(_timeSmoother.GetSmoothedTime(base::Time::Now()));
+
+ if (_navigationManager && item)
+ _navigationManager->OnNavigationItemCommitted();
+}
+
+- (void)addTransientEntry:(const GURL&)url
+ title:(const base::string16&)title
+ sslStatus:(const web::SSLStatus*)status {
+ // TODO(stuartmorgan): Don't do this; this is here only to preserve the old
+ // behavior from when transient entries were faked with pending entries, so
+ // any actual pending entry had to be committed. This shouldn't be necessary
+ // now, but things may rely on the old behavior and need to be fixed.
+ [self commitPendingEntry];
+
+ _transientEntry.reset(
+ [[self sessionEntryWithURL:url
+ referrer:web::Referrer()
+ transition:ui::PAGE_TRANSITION_CLIENT_REDIRECT
+ useDesktopUserAgent:NO
+ rendererInitiated:NO] retain]);
+
+ web::NavigationItem* navigationItem = [_transientEntry navigationItem];
+ DCHECK(navigationItem);
+ if (status)
+ navigationItem->GetSSL() = *status;
+ navigationItem->SetTitle(title);
+ navigationItem->SetTimestamp(
+ _timeSmoother.GetSmoothedTime(base::Time::Now()));
+
+ // This doesn't match upstream, but matches what we've traditionally done and
+ // will hopefully continue to be good enough for as long as we need the
+ // facade.
+ if (_navigationManager)
+ _navigationManager->OnNavigationItemChanged();
+}
+
+- (void)pushNewEntryWithURL:(const GURL&)url
+ stateObject:(NSString*)stateObject {
+ DCHECK([self currentEntry]);
+ web::NavigationItem* item = [self currentEntry].navigationItem;
+ CHECK(
+ web::history_state_util::IsHistoryStateChangeValid(item->GetURL(), url));
+ web::Referrer referrer(item->GetURL(), web::ReferrerPolicyDefault);
+ base::scoped_nsobject<CRWSessionEntry> pushedEntry(
+ [[self sessionEntryWithURL:url
+ referrer:referrer
+ transition:ui::PAGE_TRANSITION_LINK
+ useDesktopUserAgent:self.currentEntry.useDesktopUserAgent
+ rendererInitiated:NO] retain]);
+ pushedEntry.get().serializedStateObject = stateObject;
+ pushedEntry.get().createdFromPushState = YES;
+ web::SSLStatus& sslStatus = [self currentEntry].navigationItem->GetSSL();
+ pushedEntry.get().navigationItem->GetSSL() = sslStatus;
+
+ [self clearForwardEntries];
+ // Add the new entry at the end.
+ [_entries addObject:pushedEntry];
+ _previousNavigationIndex = _currentNavigationIndex;
+ _currentNavigationIndex = [_entries count] - 1;
+
+ if (_navigationManager)
+ _navigationManager->OnNavigationItemCommitted();
+}
+
+- (void)updateCurrentEntryWithURL:(const GURL&)url
+ stateObject:(NSString*)stateObject {
+ DCHECK(!_transientEntry);
+ CRWSessionEntry* currentEntry = self.currentEntry;
+ currentEntry.navigationItem->SetURL(url);
+ currentEntry.serializedStateObject = stateObject;
+ // If the change is to a committed entry, notify interested parties.
+ if (currentEntry != self.pendingEntry && _navigationManager)
+ _navigationManager->OnNavigationItemChanged();
+}
+
+- (void)discardNonCommittedEntries {
+ [self discardTransientEntry];
+ _pendingEntry.reset();
+}
+
+- (void)discardTransientEntry {
+ // Keep the entry alive temporarily. There are flows that get the current
+ // entry, do some navigation operation, and then try to use that old current
+ // entry; since navigations clear the transient entry, these flows might
+ // crash. (This should be removable once more session management is handled
+ // within this class and/or NavigationManager).
+ [[_transientEntry retain] autorelease];
+ _transientEntry.reset();
+}
+
+- (BOOL)hasPendingEntry {
+ return _pendingEntry != nil;
+}
+
+- (void)copyStateFromAndPrune:(CRWSessionController*)otherSession
+ replaceState:(BOOL)replaceState {
+ DCHECK(otherSession);
+ if (replaceState) {
+ [_entries removeAllObjects];
+ _currentNavigationIndex = -1;
+ _previousNavigationIndex = -1;
+ }
+ self.xCallbackParameters =
+ [[otherSession.xCallbackParameters copy] autorelease];
+ self.windowName = otherSession.windowName;
+ NSInteger numInitialEntries = [_entries count];
+
+ // Cycle through the entries from the other session and insert them before any
+ // entries from this session. Do not copy anything that comes after the other
+ // session's current entry unless replaceState is true.
+ NSArray* otherEntries = [otherSession entries];
+
+ // The other session may not have any entries, in which case there is nothing
+ // to copy or prune. The other session's currentNavigationEntry will be bogus
+ // in such cases, so ignore it and return early.
+ // TODO(rohitrao): Do we need to copy over any pending entries? We might not
+ // add the prerendered page into the back/forward history if we don't copy
+ // pending entries.
+ if (![otherEntries count])
+ return;
+
+ NSInteger maxCopyIndex = replaceState ? [otherEntries count] - 1 :
+ [otherSession currentNavigationIndex];
+ for (NSInteger i = 0; i <= maxCopyIndex; ++i) {
+ [_entries insertObject:[otherEntries objectAtIndex:i] atIndex:i];
+ ++_currentNavigationIndex;
+ _previousNavigationIndex = -1;
+ }
+
+ // If this CRWSessionController has no entries initially, reset
+ // |currentNavigationIndex_| to be in bounds.
+ if (!numInitialEntries) {
+ if (replaceState) {
+ _currentNavigationIndex = [otherSession currentNavigationIndex];
+ _previousNavigationIndex = [otherSession previousNavigationIndex];
+ } else {
+ _currentNavigationIndex = maxCopyIndex;
+ }
+ }
+ DCHECK_LT((NSUInteger)_currentNavigationIndex, [_entries count]);
+}
+
+- (ui::PageTransition)transitionForIndex:(NSUInteger)index {
+ return [[_entries objectAtIndex:index] navigationItem]->GetTransitionType();
+}
+
+- (BOOL)canGoBack {
+ if ([_entries count] == 0)
+ return NO;
+
+ NSInteger lastNonRedirectedIndex = _currentNavigationIndex;
+ while (lastNonRedirectedIndex >= 0 &&
+ ui::PageTransitionIsRedirect(
+ [self transitionForIndex:lastNonRedirectedIndex])) {
+ --lastNonRedirectedIndex;
+ }
+
+ return lastNonRedirectedIndex > 0;
+}
+
+- (BOOL)canGoForward {
+ // In case there are pending entries return no since when the entry will be
+ // committed the history will be cleared from that point forward.
+ if (_pendingEntry)
+ return NO;
+ // If the current index is less than the last element, there are entries to
+ // go forward to.
+ const NSInteger count = [_entries count];
+ return count && _currentNavigationIndex < (count - 1);
+}
+
+- (void)goBack {
+ if (![self canGoBack])
+ return;
+
+ [self discardTransientEntry];
+
+ web::RecordAction(UserMetricsAction("Back"));
+ _previousNavigationIndex = _currentNavigationIndex;
+ // To stop the user getting 'stuck' on redirecting pages they weren't even
+ // aware existed, it is necessary to pass over pages that would immediately
+ // result in a redirect (the entry *before* the redirected page).
+ while (_currentNavigationIndex &&
+ [self transitionForIndex:_currentNavigationIndex] &
+ ui::PAGE_TRANSITION_IS_REDIRECT_MASK) {
+ --_currentNavigationIndex;
+ }
+
+ if (_currentNavigationIndex)
+ --_currentNavigationIndex;
+}
+
+- (void)goForward {
+ [self discardTransientEntry];
+
+ web::RecordAction(UserMetricsAction("Forward"));
+ if (_currentNavigationIndex + 1 < static_cast<NSInteger>([_entries count])) {
+ _previousNavigationIndex = _currentNavigationIndex;
+ ++_currentNavigationIndex;
+ }
+ // To reduce the chance of a redirect kicking in (truncating the history
+ // stack) we skip over any pages that might do this; we detect this by
+ // looking for when the *next* page had rediection transition type (was
+ // auto redirected to).
+ while (_currentNavigationIndex + 1 <
+ (static_cast<NSInteger>([_entries count])) &&
+ ([self transitionForIndex:_currentNavigationIndex + 1] &
+ ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
+ ++_currentNavigationIndex;
+ }
+}
+
+- (void)goDelta:(int)delta {
+ if (delta < 0) {
+ while ([self canGoBack] && delta < 0) {
+ [self goBack];
+ ++delta;
+ }
+ } else {
+ while ([self canGoForward] && delta > 0) {
+ [self goForward];
+ --delta;
+ }
+ }
+}
+
+- (void)goToEntry:(CRWSessionEntry*)entry {
+ DCHECK(entry);
+
+ [self discardTransientEntry];
+
+ // Check that |entries_| still contains |entry|. |entry| could have been
+ // removed by -clearForwardEntries.
+ if ([_entries containsObject:entry])
+ _currentNavigationIndex = [_entries indexOfObject:entry];
+}
+
+- (void)removeEntryAtIndex:(NSInteger)index {
+ DCHECK(index < static_cast<NSInteger>([_entries count]));
+ DCHECK(index != _currentNavigationIndex);
+ DCHECK(index >= 0);
+
+ [self discardNonCommittedEntries];
+
+ [_entries removeObjectAtIndex:index];
+ if (_currentNavigationIndex > index)
+ _currentNavigationIndex--;
+ if (_previousNavigationIndex >= index)
+ _previousNavigationIndex--;
+}
+
+- (NSArray*)backwardEntries {
+ NSMutableArray* entries = [NSMutableArray array];
+ NSInteger lastNonRedirectedIndex = _currentNavigationIndex;
+ while (lastNonRedirectedIndex >= 0) {
+ CRWSessionEntry* entry = [_entries objectAtIndex:lastNonRedirectedIndex];
+ if (!ui::PageTransitionIsRedirect(
+ entry.navigationItem->GetTransitionType())) {
+ [entries addObject:entry];
+ }
+ --lastNonRedirectedIndex;
+ }
+ // Remove the currently displayed entry.
+ [entries removeObjectAtIndex:0];
+ return entries;
+}
+
+- (NSArray*)forwardEntries {
+ NSMutableArray* entries = [NSMutableArray array];
+ NSUInteger lastNonRedirectedIndex = _currentNavigationIndex + 1;
+ while (lastNonRedirectedIndex < [_entries count]) {
+ CRWSessionEntry* entry = [_entries objectAtIndex:lastNonRedirectedIndex];
+ if (!ui::PageTransitionIsRedirect(
+ entry.navigationItem->GetTransitionType())) {
+ [entries addObject:entry];
+ }
+ ++lastNonRedirectedIndex;
+ }
+ return entries;
+}
+
+- (std::vector<GURL>)currentRedirectedUrls {
+ std::vector<GURL> results;
+ if (_pendingEntry) {
+ web::NavigationItem* item = [_pendingEntry navigationItem];
+ results.push_back(item->GetURL());
+
+ if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
+ return results;
+ }
+
+ if (![_entries count])
+ return results;
+
+ NSInteger index = _currentNavigationIndex;
+ // Add urls in the redirected entries.
+ while (index >= 0) {
+ web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
+ if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
+ break;
+ results.push_back(item->GetURL());
+ --index;
+ }
+ // Add the last non-redirected entry.
+ if (index >= 0) {
+ web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
+ results.push_back(item->GetURL());
+ }
+ return results;
+}
+
+- (BOOL)isPushStateNavigationBetweenEntry:(CRWSessionEntry*)firstEntry
+ andEntry:(CRWSessionEntry*)secondEntry {
+ DCHECK(firstEntry);
+ DCHECK(secondEntry);
+ if (firstEntry == secondEntry)
+ return NO;
+ NSUInteger firstIndex = [_entries indexOfObject:firstEntry];
+ NSUInteger secondIndex = [_entries indexOfObject:secondEntry];
+ if (firstIndex == NSNotFound || secondIndex == NSNotFound)
+ return NO;
+ NSUInteger startIndex = firstIndex < secondIndex ? firstIndex : secondIndex;
+ NSUInteger endIndex = firstIndex < secondIndex ? secondIndex : firstIndex;
+
+ for (NSUInteger i = startIndex + 1; i <= endIndex; i++) {
+ CRWSessionEntry* entry = [_entries objectAtIndex:i];
+ // Every entry in the sequence has to be created from a pushState() call.
+ if (!entry.createdFromPushState)
+ return NO;
+ // Every entry in the sequence has to have a URL that could have been
+ // created from a pushState() call.
+ if (!web::history_state_util::IsHistoryStateChangeValid(
+ firstEntry.navigationItem->GetURL(),
+ entry.navigationItem->GetURL()))
+ return NO;
+ }
+ return YES;
+}
+
+- (CRWSessionEntry*)lastUserEntry {
+ if (![_entries count])
+ return nil;
+
+ NSInteger index = _currentNavigationIndex;
+ // This will return the first session entry if all other entries are
+ // redirects, regardless of the transition state of the first entry.
+ while (index > 0 &&
+ [self transitionForIndex:index] &
+ ui::PAGE_TRANSITION_IS_REDIRECT_MASK) {
+ --index;
+ }
+ return [_entries objectAtIndex:index];
+}
+
+- (void)useDesktopUserAgentForNextPendingEntry {
+ if (_pendingEntry)
+ [_pendingEntry setUseDesktopUserAgent:YES];
+ else
+ _useDesktopUserAgentForNextPendingEntry = YES;
+}
+
+#pragma mark -
+#pragma mark Private methods
+
+- (NSString*)uniqueID {
+ CFUUIDRef uuidRef = CFUUIDCreate(NULL);
+ CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
+ CFRelease(uuidRef);
+ NSString* uuid = [NSString stringWithString:(NSString*)uuidStringRef];
+ CFRelease(uuidStringRef);
+ return uuid;
+}
+
+- (CRWSessionEntry*)sessionEntryWithURL:(const GURL&)url
+ referrer:(const web::Referrer&)referrer
+ transition:(ui::PageTransition)transition
+ useDesktopUserAgent:(BOOL)useDesktopUserAgent
+ rendererInitiated:(BOOL)rendererInitiated {
+ GURL loaded_url(url);
+ web::BrowserURLRewriter::GetInstance()->RewriteURLIfNecessary(&loaded_url,
+ _browserState);
+ return [[[CRWSessionEntry alloc] initWithUrl:loaded_url
+ referrer:referrer
+ transition:transition
+ useDesktopUserAgent:useDesktopUserAgent
+ rendererInitiated:rendererInitiated] autorelease];
+}
+
+@end
diff --git a/ios/web/navigation/crw_session_controller_unittest.mm b/ios/web/navigation/crw_session_controller_unittest.mm
new file mode 100644
index 0000000..79a6dff
--- /dev/null
+++ b/ios/web/navigation/crw_session_controller_unittest.mm
@@ -0,0 +1,1022 @@
+// 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/logging.h"
+#import "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/sys_string_conversions.h"
+#import "ios/web/navigation/crw_session_controller+private_constructors.h"
+#import "ios/web/navigation/crw_session_controller.h"
+#include "ios/web/navigation/crw_session_entry.h"
+#include "ios/web/navigation/navigation_item_impl.h"
+#include "ios/web/public/referrer.h"
+#include "ios/web/public/test/test_browser_state.h"
+#import "net/base/mac/url_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+
+@interface CRWSessionController (Testing)
+- (const GURL&)URLForSessionAtIndex:(NSUInteger)index;
+- (const GURL&)currentURL;
+@end
+
+@implementation CRWSessionController (Testing)
+- (const GURL&)URLForSessionAtIndex:(NSUInteger)index {
+ CRWSessionEntry* entry =
+ static_cast<CRWSessionEntry*>([self.entries objectAtIndex:index]);
+ return entry.navigationItem->GetURL();
+}
+
+- (const GURL&)currentURL {
+ DCHECK([self currentEntry]);
+ return [self currentEntry].navigationItem->GetURL();
+}
+@end
+
+namespace {
+
+class CRWSessionControllerTest : public PlatformTest {
+ protected:
+ void SetUp() override {
+ session_controller_.reset(
+ [[CRWSessionController alloc] initWithWindowName:@"test window"
+ openerId:@"opener"
+ openedByDOM:NO
+ openerNavigationIndex:0
+ browserState:&browser_state_]);
+ }
+
+ web::Referrer MakeReferrer(std::string url) {
+ return web::Referrer(GURL(url), web::ReferrerPolicyDefault);
+ }
+
+ web::TestBrowserState browser_state_;
+ base::scoped_nsobject<CRWSessionController> session_controller_;
+};
+
+TEST_F(CRWSessionControllerTest, InitWithWindowName) {
+ EXPECT_NSEQ(@"test window", [session_controller_ windowName]);
+ EXPECT_NSEQ(@"opener", [session_controller_ openerId]);
+ EXPECT_FALSE([session_controller_ isOpenedByDOM]);
+ EXPECT_EQ(0U, [[session_controller_ entries] count]);
+ EXPECT_EQ(nil, [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, AddPendingEntry) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+
+ EXPECT_EQ(0U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ currentURL]);
+}
+
+TEST_F(CRWSessionControllerTest, AddPendingEntryWithCommittedEntries) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.committed.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.committed.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ currentURL]);
+}
+
+TEST_F(CRWSessionControllerTest, AddPendingEntryOverriding) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_
+ addPendingEntry:GURL("http://www.another.url.com")
+ referrer:MakeReferrer("http://www.another.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+
+ EXPECT_EQ(0U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.another.url.com/"),
+ [session_controller_ currentURL]);
+}
+
+TEST_F(CRWSessionControllerTest, AddPendingEntryAndCommit) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, AddPendingEntryOverridingAndCommit) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_
+ addPendingEntry:GURL("http://www.another.url.com")
+ referrer:MakeReferrer("http://www.another.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.another.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, AddPendingEntryAndCommitMultiple) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_
+ addPendingEntry:GURL("http://www.another.url.com")
+ referrer:MakeReferrer("http://www.another.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(2U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ GURL("http://www.another.url.com/"),
+ [session_controller_ URLForSessionAtIndex:1U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:1U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, AddPendingEntryAndDiscard) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ discardNonCommittedEntries];
+
+ EXPECT_EQ(0U, [[session_controller_ entries] count]);
+ EXPECT_EQ(nil, [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, AddPendingEntryAndDiscardAndAddAndCommit) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ discardNonCommittedEntries];
+
+ [session_controller_
+ addPendingEntry:GURL("http://www.another.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.another.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, AddPendingEntryAndCommitAndAddAndDiscard) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_
+ addPendingEntry:GURL("http://www.another.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ discardNonCommittedEntries];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest,
+ CommitPendingEntryWithoutPendingOrCommittedEntry) {
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(0U, [[session_controller_ entries] count]);
+ EXPECT_EQ(nil, [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest,
+ CommitPendingEntryWithoutPendingEntryWithCommittedEntry) {
+ // Setup committed entry
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ // Commit pending entry when there is no such one
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest,
+ DiscardPendingEntryWithoutPendingOrCommittedEntry) {
+ [session_controller_ discardNonCommittedEntries];
+
+ EXPECT_EQ(0U, [[session_controller_ entries] count]);
+ EXPECT_EQ(nil, [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest,
+ DiscardPendingEntryWithoutPendingEntryWithCommittedEntry) {
+ // Setup committed entry
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ // Discard noncommitted entries when there is no such one
+ [session_controller_ discardNonCommittedEntries];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, UpdatePendingEntryWithoutPendingEntry) {
+ [session_controller_
+ updatePendingEntry:GURL("http://www.another.url.com")];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(0U, [[session_controller_ entries] count]);
+ EXPECT_EQ(nil, [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, UpdatePendingEntryWithPendingEntry) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_
+ updatePendingEntry:GURL("http://www.another.url.com")];
+
+ EXPECT_EQ(
+ GURL("http://www.another.url.com/"),
+ [session_controller_ currentURL]);
+}
+
+TEST_F(CRWSessionControllerTest,
+ UpdatePendingEntryWithPendingEntryAlreadyCommited) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ updatePendingEntry:GURL("http://www.another.url.com")];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoBackWithoutCommitedEntry) {
+ [session_controller_ goBack];
+
+ EXPECT_EQ(0U, [[session_controller_ entries] count]);
+ EXPECT_EQ(nil, [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoBackWithSingleCommitedEntry) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_ goBack];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoBackFromTheEnd) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_ goBack];
+
+ EXPECT_EQ(2U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ GURL("http://www.url2.com/"),
+ [session_controller_ URLForSessionAtIndex:1U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoBackFromTheBeginning) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_ goBack];
+ [session_controller_ goBack];
+
+ EXPECT_EQ(2U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ GURL("http://www.url2.com/"),
+ [session_controller_ URLForSessionAtIndex:1U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoBackFromTheMiddle) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url3.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url4.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_ goBack];
+ [session_controller_ goBack];
+
+ EXPECT_EQ(4U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ GURL("http://www.url2.com/"),
+ [session_controller_ URLForSessionAtIndex:1U]);
+ EXPECT_EQ(
+ GURL("http://www.url3.com/"),
+ [session_controller_ URLForSessionAtIndex:2U]);
+ EXPECT_EQ(
+ GURL("http://www.url4.com/"),
+ [session_controller_ URLForSessionAtIndex:3U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:1U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoBackAndRemove) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_ goBack];
+ [session_controller_ removeEntryAtIndex:1];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+ EXPECT_EQ([session_controller_ currentEntry],
+ [session_controller_ previousEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoForwardWithoutCommitedEntry) {
+ [session_controller_ goForward];
+
+ EXPECT_EQ(0U, [[session_controller_ entries] count]);
+ EXPECT_EQ(nil, [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoForwardWithSingleCommitedEntry) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_ goForward];
+
+ EXPECT_EQ(1U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:0U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoForewardFromTheEnd) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_ goForward];
+
+ EXPECT_EQ(2U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ GURL("http://www.url2.com/"),
+ [session_controller_ URLForSessionAtIndex:1U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:1U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoForewardFromTheBeginning) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_ goBack];
+ [session_controller_ goForward];
+
+ EXPECT_EQ(2U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ GURL("http://www.url2.com/"),
+ [session_controller_ URLForSessionAtIndex:1U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:1U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, GoForwardFromTheMiddle) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url3.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url4.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ [session_controller_ goBack];
+ [session_controller_ goBack];
+ [session_controller_ goForward];
+
+ EXPECT_EQ(4U, [[session_controller_ entries] count]);
+ EXPECT_EQ(
+ GURL("http://www.url.com/"),
+ [session_controller_ URLForSessionAtIndex:0U]);
+ EXPECT_EQ(
+ GURL("http://www.url2.com/"),
+ [session_controller_ URLForSessionAtIndex:1U]);
+ EXPECT_EQ(
+ GURL("http://www.url3.com/"),
+ [session_controller_ URLForSessionAtIndex:2U]);
+ EXPECT_EQ(
+ GURL("http://www.url4.com/"),
+ [session_controller_ URLForSessionAtIndex:3U]);
+ EXPECT_EQ(
+ [[session_controller_ entries] objectAtIndex:2U],
+ [session_controller_ currentEntry]);
+}
+
+TEST_F(CRWSessionControllerTest, CanGoBackWithoutCommitedEntry) {
+ EXPECT_FALSE([session_controller_ canGoBack]);
+}
+
+TEST_F(CRWSessionControllerTest, CanGoBackWithSingleCommitedEntry) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_FALSE([session_controller_ canGoBack]);
+}
+
+TEST_F(CRWSessionControllerTest, CanGoBackWithMultipleCommitedEntries) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url1.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_TRUE([session_controller_ canGoBack]);
+
+ [session_controller_ goBack];
+ EXPECT_TRUE([session_controller_ canGoBack]);
+
+ [session_controller_ goBack];
+ EXPECT_FALSE([session_controller_ canGoBack]);
+
+ [session_controller_ goBack];
+ EXPECT_FALSE([session_controller_ canGoBack]);
+
+ [session_controller_ goForward];
+ EXPECT_TRUE([session_controller_ canGoBack]);
+}
+
+TEST_F(CRWSessionControllerTest, CanGoForwardWithoutCommitedEntry) {
+ EXPECT_FALSE([session_controller_ canGoBack]);
+}
+
+TEST_F(CRWSessionControllerTest, CanGoForwardWithSingleCommitedEntry) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_FALSE([session_controller_ canGoBack]);
+}
+
+TEST_F(CRWSessionControllerTest, CanGoForwardWithMultipleCommitedEntries) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url1.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_FALSE([session_controller_ canGoForward]);
+
+ [session_controller_ goBack];
+ EXPECT_TRUE([session_controller_ canGoForward]);
+
+ [session_controller_ goBack];
+ EXPECT_TRUE([session_controller_ canGoForward]);
+
+ [session_controller_ goForward];
+ EXPECT_TRUE([session_controller_ canGoForward]);
+
+ [session_controller_ goForward];
+ EXPECT_FALSE([session_controller_ canGoForward]);
+}
+
+// Helper to create a NavigationItem. Caller is responsible for freeing
+// the memory.
+web::NavigationItem* CreateNavigationItem(const std::string& url,
+ const std::string& referrer,
+ NSString* title) {
+ web::Referrer referrer_object(GURL(referrer),
+ web::ReferrerPolicyDefault);
+ web::NavigationItemImpl* navigation_item = new web::NavigationItemImpl();
+ navigation_item->SetURL(GURL(url));
+ navigation_item->SetReferrer(referrer_object);
+ navigation_item->SetTitle(base::SysNSStringToUTF16(title));
+ navigation_item->SetTransitionType(ui::PAGE_TRANSITION_TYPED);
+
+ return navigation_item;
+}
+
+TEST_F(CRWSessionControllerTest, CreateWithEmptyNavigations) {
+ ScopedVector<web::NavigationItem> items;
+ base::scoped_nsobject<CRWSessionController> controller(
+ [[CRWSessionController alloc] initWithNavigationItems:items.Pass()
+ currentIndex:0
+ browserState:&browser_state_]);
+ EXPECT_EQ(controller.get().entries.count, 0U);
+ EXPECT_EQ(controller.get().currentNavigationIndex, -1);
+ EXPECT_EQ(controller.get().previousNavigationIndex, -1);
+ EXPECT_FALSE(controller.get().currentEntry);
+}
+
+TEST_F(CRWSessionControllerTest, CreateWithNavList) {
+ ScopedVector<web::NavigationItem> items;
+ items.push_back(CreateNavigationItem("http://www.google.com",
+ "http://www.referrer.com", @"Google"));
+ items.push_back(CreateNavigationItem("http://www.yahoo.com",
+ "http://www.google.com", @"Yahoo"));
+ items.push_back(CreateNavigationItem("http://www.espn.com",
+ "http://www.nothing.com", @"ESPN"));
+ base::scoped_nsobject<CRWSessionController> controller(
+ [[CRWSessionController alloc] initWithNavigationItems:items.Pass()
+ currentIndex:1
+ browserState:&browser_state_]);
+
+ EXPECT_EQ(controller.get().entries.count, 3U);
+ EXPECT_EQ(controller.get().currentNavigationIndex, 1);
+ EXPECT_EQ(controller.get().previousNavigationIndex, -1);
+ // Sanity check the current entry, the CRWSessionEntry unit test will ensure
+ // the entire object is created properly.
+ CRWSessionEntry* current_entry = controller.get().currentEntry;
+ EXPECT_EQ(current_entry.navigationItem->GetURL(),
+ GURL("http://www.yahoo.com"));
+ EXPECT_EQ([[controller openerId] length], 0UL);
+}
+
+TEST_F(CRWSessionControllerTest, PreviousNavigationEntry) {
+ [session_controller_
+ addPendingEntry:GURL("http://www.url.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url1.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_
+ addPendingEntry:GURL("http://www.url2.com")
+ referrer:MakeReferrer("http://www.referer.com")
+ transition:ui::PAGE_TRANSITION_TYPED
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(session_controller_.get().previousNavigationIndex, 1);
+
+ [session_controller_ goBack];
+ EXPECT_EQ(session_controller_.get().previousNavigationIndex, 2);
+
+ [session_controller_ goBack];
+ EXPECT_EQ(session_controller_.get().previousNavigationIndex, 1);
+
+ [session_controller_ goForward];
+ EXPECT_EQ(session_controller_.get().previousNavigationIndex, 0);
+
+ [session_controller_ goForward];
+ EXPECT_EQ(session_controller_.get().previousNavigationIndex, 1);
+}
+
+TEST_F(CRWSessionControllerTest, PushNewEntry) {
+ ScopedVector<web::NavigationItem> items;
+ items.push_back(CreateNavigationItem("http://www.firstpage.com",
+ "http://www.starturl.com", @"First"));
+ items.push_back(CreateNavigationItem("http://www.secondpage.com",
+ "http://www.firstpage.com", @"Second"));
+ items.push_back(CreateNavigationItem("http://www.thirdpage.com",
+ "http://www.secondpage.com", @"Third"));
+ base::scoped_nsobject<CRWSessionController> controller(
+ [[CRWSessionController alloc] initWithNavigationItems:items.Pass()
+ currentIndex:0
+ browserState:&browser_state_]);
+
+ GURL pushPageGurl1("http://www.firstpage.com/#push1");
+ NSString* stateObject1 = @"{'foo': 1}";
+ [controller pushNewEntryWithURL:pushPageGurl1 stateObject:stateObject1];
+ CRWSessionEntry* pushedEntry = [controller currentEntry];
+ NSUInteger expectedCount = 2;
+ EXPECT_EQ(expectedCount, controller.get().entries.count);
+ EXPECT_EQ(pushPageGurl1, pushedEntry.navigationItem->GetURL());
+ EXPECT_TRUE(pushedEntry.createdFromPushState);
+ EXPECT_NSEQ(stateObject1, pushedEntry.serializedStateObject);
+ EXPECT_EQ(GURL("http://www.firstpage.com/"),
+ pushedEntry.navigationItem->GetReferrer().url);
+
+ // Add another new entry and check size and fields again.
+ GURL pushPageGurl2("http://www.firstpage.com/push2");
+ [controller pushNewEntryWithURL:pushPageGurl2 stateObject:nil];
+ pushedEntry = [controller currentEntry];
+ expectedCount = 3;
+ EXPECT_EQ(expectedCount, controller.get().entries.count);
+ EXPECT_EQ(pushPageGurl2, pushedEntry.navigationItem->GetURL());
+ EXPECT_TRUE(pushedEntry.createdFromPushState);
+ EXPECT_EQ(nil, pushedEntry.serializedStateObject);
+ EXPECT_EQ(pushPageGurl1, pushedEntry.navigationItem->GetReferrer().url);
+}
+
+TEST_F(CRWSessionControllerTest, IsPushStateNavigation) {
+ ScopedVector<web::NavigationItem> items;
+ items.push_back(
+ CreateNavigationItem("http://foo.com", "http://google.com", @"First"));
+ // Push state navigation.
+ items.push_back(
+ CreateNavigationItem("http://foo.com#bar", "http://foo.com", @"Second"));
+ items.push_back(CreateNavigationItem("http://google.com",
+ "http://foo.com#bar", @"Third"));
+ items.push_back(
+ CreateNavigationItem("http://foo.com", "http://google.com", @"Fourth"));
+ // Push state navigation.
+ items.push_back(
+ CreateNavigationItem("http://foo.com/bar", "http://foo.com", @"Fifth"));
+ // Push state navigation.
+ items.push_back(CreateNavigationItem("http://foo.com/bar#bar",
+ "http://foo.com/bar", @"Sixth"));
+ base::scoped_nsobject<CRWSessionController> controller(
+ [[CRWSessionController alloc] initWithNavigationItems:items.Pass()
+ currentIndex:0
+ browserState:&browser_state_]);
+ CRWSessionEntry* entry0 = [controller.get().entries objectAtIndex:0];
+ CRWSessionEntry* entry1 = [controller.get().entries objectAtIndex:1];
+ CRWSessionEntry* entry2 = [controller.get().entries objectAtIndex:2];
+ CRWSessionEntry* entry3 = [controller.get().entries objectAtIndex:3];
+ CRWSessionEntry* entry4 = [controller.get().entries objectAtIndex:4];
+ CRWSessionEntry* entry5 = [controller.get().entries objectAtIndex:5];
+ entry1.createdFromPushState = YES;
+ entry4.createdFromPushState = YES;
+ entry5.createdFromPushState = YES;
+
+ EXPECT_TRUE(
+ [controller isPushStateNavigationBetweenEntry:entry0 andEntry:entry1]);
+ EXPECT_TRUE(
+ [controller isPushStateNavigationBetweenEntry:entry5 andEntry:entry3]);
+ EXPECT_TRUE(
+ [controller isPushStateNavigationBetweenEntry:entry4 andEntry:entry3]);
+ EXPECT_FALSE(
+ [controller isPushStateNavigationBetweenEntry:entry1 andEntry:entry2]);
+ EXPECT_FALSE(
+ [controller isPushStateNavigationBetweenEntry:entry0 andEntry:entry5]);
+ EXPECT_FALSE(
+ [controller isPushStateNavigationBetweenEntry:entry2 andEntry:entry4]);
+}
+
+TEST_F(CRWSessionControllerTest, UpdateCurrentEntry) {
+ ScopedVector<web::NavigationItem> items;
+ items.push_back(CreateNavigationItem("http://www.firstpage.com",
+ "http://www.starturl.com", @"First"));
+ items.push_back(CreateNavigationItem("http://www.secondpage.com",
+ "http://www.firstpage.com", @"Second"));
+ items.push_back(CreateNavigationItem("http://www.thirdpage.com",
+ "http://www.secondpage.com", @"Third"));
+ base::scoped_nsobject<CRWSessionController> controller(
+ [[CRWSessionController alloc] initWithNavigationItems:items.Pass()
+ currentIndex:0
+ browserState:&browser_state_]);
+
+ GURL replacePageGurl1("http://www.firstpage.com/#replace1");
+ NSString* stateObject1 = @"{'foo': 1}";
+
+ // Replace current entry and check the size of history and fields of the
+ // modified entry.
+ [controller updateCurrentEntryWithURL:replacePageGurl1
+ stateObject:stateObject1];
+ CRWSessionEntry* replacedEntry = [controller currentEntry];
+ NSUInteger expectedCount = 3;
+ EXPECT_EQ(expectedCount, controller.get().entries.count);
+ EXPECT_EQ(replacePageGurl1, replacedEntry.navigationItem->GetURL());
+ EXPECT_FALSE(replacedEntry.createdFromPushState);
+ EXPECT_NSEQ(stateObject1, replacedEntry.serializedStateObject);
+ EXPECT_EQ(GURL("http://www.starturl.com/"),
+ replacedEntry.navigationItem->GetReferrer().url);
+
+ // Replace current entry and check size and fields again.
+ GURL replacePageGurl2("http://www.firstpage.com/#replace2");
+ [controller.get() updateCurrentEntryWithURL:replacePageGurl2 stateObject:nil];
+ replacedEntry = [controller currentEntry];
+ EXPECT_EQ(expectedCount, controller.get().entries.count);
+ EXPECT_EQ(replacePageGurl2, replacedEntry.navigationItem->GetURL());
+ EXPECT_FALSE(replacedEntry.createdFromPushState);
+ EXPECT_NSEQ(nil, replacedEntry.serializedStateObject);
+ EXPECT_EQ(GURL("http://www.starturl.com/"),
+ replacedEntry.navigationItem->GetReferrer().url);
+}
+
+TEST_F(CRWSessionControllerTest, TestBackwardForwardEntries) {
+ [session_controller_ addPendingEntry:GURL("http://www.example.com/0")
+ referrer:MakeReferrer("http://www.example.com/a")
+ transition:ui::PAGE_TRANSITION_LINK
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_ addPendingEntry:GURL("http://www.example.com/1")
+ referrer:MakeReferrer("http://www.example.com/b")
+ transition:ui::PAGE_TRANSITION_LINK
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_ addPendingEntry:GURL("http://www.example.com/redirect")
+ referrer:MakeReferrer("http://www.example.com/r")
+ transition:ui::PAGE_TRANSITION_IS_REDIRECT_MASK
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_ addPendingEntry:GURL("http://www.example.com/2")
+ referrer:MakeReferrer("http://www.example.com/c")
+ transition:ui::PAGE_TRANSITION_LINK
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+
+ EXPECT_EQ(3, session_controller_.get().currentNavigationIndex);
+ NSArray* backEntries = [session_controller_ backwardEntries];
+ EXPECT_EQ(2U, [backEntries count]);
+ EXPECT_EQ(0U, [[session_controller_ forwardEntries] count]);
+ EXPECT_EQ("http://www.example.com/1",
+ [[backEntries objectAtIndex:0] navigationItem]->GetURL().spec());
+
+ [session_controller_ goBack];
+ EXPECT_EQ(1U, [[session_controller_ backwardEntries] count]);
+ EXPECT_EQ(1U, [[session_controller_ forwardEntries] count]);
+
+ [session_controller_ goBack];
+ NSArray* forwardEntries = [session_controller_ forwardEntries];
+ EXPECT_EQ(0U, [[session_controller_ backwardEntries] count]);
+ EXPECT_EQ(2U, [forwardEntries count]);
+ EXPECT_EQ("http://www.example.com/2",
+ [[forwardEntries objectAtIndex:1] navigationItem]->GetURL().spec());
+}
+
+TEST_F(CRWSessionControllerTest, GoToEntry) {
+ [session_controller_ addPendingEntry:GURL("http://www.example.com/0")
+ referrer:MakeReferrer("http://www.example.com/a")
+ transition:ui::PAGE_TRANSITION_LINK
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_ addPendingEntry:GURL("http://www.example.com/1")
+ referrer:MakeReferrer("http://www.example.com/b")
+ transition:ui::PAGE_TRANSITION_LINK
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_ addPendingEntry:GURL("http://www.example.com/redirect")
+ referrer:MakeReferrer("http://www.example.com/r")
+ transition:ui::PAGE_TRANSITION_IS_REDIRECT_MASK
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ [session_controller_ addPendingEntry:GURL("http://www.example.com/2")
+ referrer:MakeReferrer("http://www.example.com/c")
+ transition:ui::PAGE_TRANSITION_LINK
+ rendererInitiated:NO];
+ [session_controller_ commitPendingEntry];
+ EXPECT_EQ(3, session_controller_.get().currentNavigationIndex);
+
+ CRWSessionEntry* entry1 = [session_controller_.get().entries objectAtIndex:1];
+ [session_controller_ goToEntry:entry1];
+ EXPECT_EQ(1, session_controller_.get().currentNavigationIndex);
+
+ // Remove an entry and attempt to go it. Ensure it outlives the removal.
+ base::scoped_nsobject<CRWSessionEntry> entry3(
+ [[session_controller_.get().entries objectAtIndex:3] retain]);
+ [session_controller_ removeEntryAtIndex:3];
+ [session_controller_ goToEntry:entry3];
+ EXPECT_EQ(1, session_controller_.get().currentNavigationIndex);
+}
+
+} // anonymous namespace
diff --git a/ios/web/navigation/crw_session_entry.h b/ios/web/navigation/crw_session_entry.h
new file mode 100644
index 0000000..00d8ec8
--- /dev/null
+++ b/ios/web/navigation/crw_session_entry.h
@@ -0,0 +1,65 @@
+// 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_NAVIGATION_CRW_SESSION_ENTRY_H_
+#define IOS_WEB_NAVIGATION_CRW_SESSION_ENTRY_H_
+
+#import <Foundation/Foundation.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+namespace web {
+class NavigationItem;
+struct Referrer;
+}
+
+// A CRWSessionEntry is similar to a NavigationEntry object in desktop Chrome.
+// It maintains the information needed to save/restore a single entry in session
+// history (i.e., one site) for a tab. A tab may have multiple of these objects
+// comprising its entire session history.
+// TODO(rohitrao): Fold CRWSessionEntry's logic into NavigationItem.
+@interface CRWSessionEntry : NSObject<NSCoding, NSCopying>
+
+@property(nonatomic, assign) NSInteger index;
+@property(nonatomic, readonly) const GURL& originalUrl;
+@property(nonatomic, assign) BOOL useDesktopUserAgent;
+@property(nonatomic, assign) BOOL usedDataReductionProxy;
+@property(nonatomic, retain) NSString* serializedStateObject;
+@property(nonatomic, assign) BOOL createdFromPushState;
+@property(nonatomic, retain) NSData* POSTData;
+@property(nonatomic, readonly) NSDictionary* httpHeaders;
+@property(nonatomic, assign) BOOL skipResubmitDataConfirmation;
+
+// Initialize the session entry with the given url.
+- (id)initWithUrl:(const GURL&)url
+ referrer:(const web::Referrer&)referrer
+ transition:(ui::PageTransition)transition
+ useDesktopUserAgent:(BOOL)useDesktopUserAgent
+ rendererInitiated:(BOOL)rendererInitiated;
+
+// Initialize the session entry with the given NavigationItem.
+- (id)initWithNavigationItem:(scoped_ptr<web::NavigationItem>)item
+ index:(int)index;
+
+// Returns a pointer to the NavigationItem associated with this CRWSessionEntry.
+// Eventually, this will replace CRWSessionEntry entirely.
+- (web::NavigationItem*)navigationItem;
+
+// Adds headers from |moreHTTPHeaders| to |httpHeaders|; existing headers with
+// the same key will be overridden.
+- (void)addHTTPHeaders:(NSDictionary*)moreHTTPHeaders;
+
+// Removes the header for the given key from |httpHeaders|.
+- (void)removeHTTPHeaderForKey:(NSString*)key;
+
+// Resets |httpHeaders| to nil.
+- (void)resetHTTPHeaders;
+
+@end
+
+#endif // IOS_WEB_NAVIGATION_CRW_SESSION_ENTRY_H_
diff --git a/ios/web/navigation/crw_session_entry.mm b/ios/web/navigation/crw_session_entry.mm
new file mode 100644
index 0000000..c786499
--- /dev/null
+++ b/ios/web/navigation/crw_session_entry.mm
@@ -0,0 +1,318 @@
+// 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/navigation/crw_session_entry.h"
+
+#include "base/mac/objc_property_releaser.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ios/web/navigation/navigation_item_impl.h"
+#include "ios/web/navigation/nscoder_util.h"
+#include "ios/web/public/navigation_item.h"
+#include "ios/web/public/web_state/page_scroll_state.h"
+#import "net/base/mac/url_conversions.h"
+
+namespace {
+// Keys used to serialize web::PageScrollState properties.
+NSString* const kScrollOffsetXKey = @"scrollX";
+NSString* const kScrollOffsetYKey = @"scrollY";
+NSString* const kMinimumZoomScaleKey = @"minZoom";
+NSString* const kMaximumZoomScaleKey = @"maxZoom";
+NSString* const kZoomScaleKey = @"zoom";
+}
+
+@interface CRWSessionEntry () {
+ // The index in the CRWSessionController.
+ //
+ // This is used when determining the selected CRWSessionEntry and only useful
+ // to the SessionServiceIOS.
+ NSInteger _index;
+
+ // The original URL of the page. In cases where a redirect occurred, |url_|
+ // will contain the final post-redirect URL, and |originalUrl_| will contain
+ // the pre-redirect URL. This field is not persisted to disk.
+ GURL _originalUrl;
+
+ // Headers passed along with the request. For POST requests, these are
+ // persisted, to be able to resubmit them. Some specialized non-POST requests
+ // may also pass custom headers.
+ base::scoped_nsobject<NSMutableDictionary> _httpHeaders;
+
+ // Data submitted with a POST request, persisted for resubmits.
+ NSData* _POSTData;
+
+ // Serialized representation of the state object that was used in conjunction
+ // with a JavaScript window.history.pushState() or
+ // window.history.replaceState() call that created or modified this
+ // CRWSessionEntry. Intended to be used for JavaScript history operations and
+ // will be nil in most cases.
+ NSString* _serializedStateObject;
+
+ // Whether or not this entry was created by calling history.pushState().
+ BOOL _createdFromPushState;
+
+ // If |YES| use a desktop user agent in HTTP requests and UIWebView.
+ BOOL _useDesktopUserAgent;
+
+ // If |YES| the page was last fetched through the data reduction proxy.
+ BOOL _usedDataReductionProxy;
+
+ // Whether or not to bypass showing the resubmit data confirmation when
+ // loading a POST request. Set to YES for browser-generated POST requests such
+ // as search-by-image requests.
+ BOOL _skipResubmitDataConfirmation;
+
+ // The NavigationItemImpl corresponding to this CRWSessionEntry.
+ // TODO(stuartmorgan): Move ownership to NavigationManagerImpl.
+ scoped_ptr<web::NavigationItemImpl> _navigationItem;
+
+ base::mac::ObjCPropertyReleaser _propertyReleaser_CRWSessionEntry;
+}
+// Redefine originalUrl to be read-write.
+@property(nonatomic, readwrite) const GURL& originalUrl;
+
+// Converts a serialized NSDictionary to a web::PageScrollState.
++ (web::PageScrollState)scrollStateFromDictionary:(NSDictionary*)dictionary;
+// Serializes a web::PageScrollState to an NSDictionary.
++ (NSDictionary*)dictionaryFromScrollState:
+ (const web::PageScrollState&)scrollState;
+// Returns a readable description of |scrollState|.
++ (NSString*)scrollStateDescription:(const web::PageScrollState&)scrollState;
+@end
+
+@implementation CRWSessionEntry
+
+@synthesize POSTData = _POSTData;
+@synthesize originalUrl = _originalUrl;
+@synthesize useDesktopUserAgent = _useDesktopUserAgent;
+@synthesize usedDataReductionProxy = _usedDataReductionProxy;
+@synthesize index = _index;
+@synthesize serializedStateObject = _serializedStateObject;
+@synthesize createdFromPushState = _createdFromPushState;
+@synthesize skipResubmitDataConfirmation = _skipResubmitDataConfirmation;
+
+// Creates a new session entry. These may be nil.
+- (instancetype)initWithUrl:(const GURL&)url
+ referrer:(const web::Referrer&)referrer
+ transition:(ui::PageTransition)transition
+ useDesktopUserAgent:(BOOL)useDesktopUserAgent
+ rendererInitiated:(BOOL)rendererInitiated {
+ self = [super init];
+ if (self) {
+ _propertyReleaser_CRWSessionEntry.Init(self, [CRWSessionEntry class]);
+ _navigationItem.reset(new web::NavigationItemImpl());
+
+ _navigationItem->SetURL(url);
+ _navigationItem->SetReferrer(referrer);
+ _navigationItem->SetTransitionType(transition);
+ _navigationItem->set_is_renderer_initiated(rendererInitiated);
+
+ self.originalUrl = url;
+ self.useDesktopUserAgent = useDesktopUserAgent;
+ }
+ return self;
+}
+
+- (instancetype)initWithNavigationItem:(scoped_ptr<web::NavigationItem>)item
+ index:(int)index {
+ self = [super init];
+ if (self) {
+ _propertyReleaser_CRWSessionEntry.Init(self, [CRWSessionEntry class]);
+ _navigationItem.reset(
+ static_cast<web::NavigationItemImpl*>(item.release()));
+
+ self.index = index;
+ self.originalUrl = _navigationItem->GetURL();
+ self.useDesktopUserAgent = NO;
+ }
+ return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder*)aDecoder {
+ self = [super init];
+ if (self) {
+ _propertyReleaser_CRWSessionEntry.Init(self, [CRWSessionEntry class]);
+ _navigationItem.reset(new web::NavigationItemImpl());
+
+ // Desktop chrome only persists virtualUrl_ and uses it to feed the url
+ // when creating a NavigationEntry.
+ GURL url;
+ if ([aDecoder containsValueForKey:@"virtualUrlString"]) {
+ url = GURL(
+ web::nscoder_util::DecodeString(aDecoder, @"virtualUrlString"));
+ } else {
+ // Backward compatibility.
+ url = net::GURLWithNSURL([aDecoder decodeObjectForKey:@"virtualUrl"]);
+ }
+ _navigationItem->SetURL(url);
+ self.originalUrl = url;
+
+ if ([aDecoder containsValueForKey:@"referrerUrlString"]) {
+ const std::string referrerString(web::nscoder_util::DecodeString(
+ aDecoder, @"referrerUrlString"));
+ web::ReferrerPolicy referrerPolicy =
+ static_cast<web::ReferrerPolicy>(
+ [aDecoder decodeIntForKey:@"referrerPolicy"]);
+ _navigationItem->SetReferrer(
+ web::Referrer(GURL(referrerString), referrerPolicy));
+ } else {
+ // Backward compatibility.
+ NSURL* referrer = [aDecoder decodeObjectForKey:@"referrer"];
+ _navigationItem->SetReferrer(web::Referrer(
+ net::GURLWithNSURL(referrer), web::ReferrerPolicyDefault));
+ }
+
+ if ([aDecoder containsValueForKey:@"timestamp"]) {
+ int64 us = [aDecoder decodeInt64ForKey:@"timestamp"];
+ _navigationItem->SetTimestamp(base::Time::FromInternalValue(us));
+ }
+
+ NSString* title = [aDecoder decodeObjectForKey:@"title"];
+ // Use a transition type of reload so that we don't incorrectly increase
+ // the typed count. This is what desktop chrome does.
+ _navigationItem->SetPageID(-1);
+ _navigationItem->SetTitle(base::SysNSStringToUTF16(title));
+ _navigationItem->SetTransitionType(ui::PAGE_TRANSITION_RELOAD);
+ _navigationItem->SetPageScrollState([[self class]
+ scrollStateFromDictionary:[aDecoder decodeObjectForKey:@"state"]]);
+ self.index = [aDecoder decodeIntForKey:@"index"];
+ self.useDesktopUserAgent =
+ [aDecoder decodeBoolForKey:@"useDesktopUserAgent"];
+ self.usedDataReductionProxy =
+ [aDecoder decodeBoolForKey:@"usedDataReductionProxy"];
+ [self addHTTPHeaders:[aDecoder decodeObjectForKey:@"httpHeaders"]];
+ self.POSTData = [aDecoder decodeObjectForKey:@"POSTData"];
+ self.skipResubmitDataConfirmation =
+ [aDecoder decodeBoolForKey:@"skipResubmitDataConfirmation"];
+ }
+ return self;
+}
+
+- (void)encodeWithCoder:(NSCoder*)aCoder {
+ // Desktop Chrome doesn't persist |url_| or |originalUrl_|, only
+ // |virtualUrl_|.
+ [aCoder encodeInt:self.index forKey:@"index"];
+ web::nscoder_util::EncodeString(aCoder, @"virtualUrlString",
+ _navigationItem->GetVirtualURL().spec());
+ web::nscoder_util::EncodeString(aCoder, @"referrerUrlString",
+ _navigationItem->GetReferrer().url.spec());
+ [aCoder encodeInt:_navigationItem->GetReferrer().policy
+ forKey:@"referrerPolicy"];
+ [aCoder encodeInt64:_navigationItem->GetTimestamp().ToInternalValue()
+ forKey:@"timestamp"];
+
+ [aCoder encodeObject:base::SysUTF16ToNSString(_navigationItem->GetTitle())
+ forKey:@"title"];
+ [aCoder encodeObject:[[self class] dictionaryFromScrollState:
+ _navigationItem->GetPageScrollState()]
+ forKey:@"state"];
+ [aCoder encodeBool:self.useDesktopUserAgent forKey:@"useDesktopUserAgent"];
+ [aCoder encodeBool:self.usedDataReductionProxy
+ forKey:@"usedDataReductionProxy"];
+ [aCoder encodeObject:self.httpHeaders forKey:@"httpHeaders"];
+ [aCoder encodeObject:self.POSTData forKey:@"POSTData"];
+ [aCoder encodeBool:self.skipResubmitDataConfirmation
+ forKey:@"skipResubmitDataConfirmation"];
+}
+
+// TODO(ios): Shall we overwrite EqualTo:?
+
+- (instancetype)copyWithZone:(NSZone*)zone {
+ CRWSessionEntry* copy = [[[self class] alloc] init];
+ copy->_propertyReleaser_CRWSessionEntry.Init(copy, [CRWSessionEntry class]);
+ copy->_navigationItem.reset(
+ new web::NavigationItemImpl(*_navigationItem.get()));
+ copy->_index = _index;
+ copy->_originalUrl = _originalUrl;
+ copy->_useDesktopUserAgent = _useDesktopUserAgent;
+ copy->_usedDataReductionProxy = _usedDataReductionProxy;
+ copy->_POSTData = [_POSTData copy];
+ copy->_httpHeaders.reset([_httpHeaders mutableCopy]);
+ copy->_skipResubmitDataConfirmation = _skipResubmitDataConfirmation;
+ return copy;
+}
+
+- (NSString*)description {
+ return [NSString
+ stringWithFormat:
+ @"url:%@ originalurl:%@ title:%@ transition:%d scrollState:%@ "
+ @"desktopUA:%d " @"proxy:%d",
+ base::SysUTF8ToNSString(_navigationItem->GetURL().spec()),
+ base::SysUTF8ToNSString(self.originalUrl.spec()),
+ base::SysUTF16ToNSString(_navigationItem->GetTitle()),
+ _navigationItem->GetTransitionType(),
+ [[self class]
+ scrollStateDescription:_navigationItem->GetPageScrollState()],
+ _useDesktopUserAgent, _usedDataReductionProxy];
+}
+
+- (web::NavigationItem*)navigationItem {
+ return _navigationItem.get();
+}
+
+- (NSDictionary*)httpHeaders {
+ return _httpHeaders ? [NSDictionary dictionaryWithDictionary:_httpHeaders]
+ : nil;
+}
+
+- (void)addHTTPHeaders:(NSDictionary*)moreHTTPHeaders {
+ if (_httpHeaders)
+ [_httpHeaders addEntriesFromDictionary:moreHTTPHeaders];
+ else
+ _httpHeaders.reset([moreHTTPHeaders mutableCopy]);
+}
+
+- (void)removeHTTPHeaderForKey:(NSString*)key {
+ [_httpHeaders removeObjectForKey:key];
+ if (![_httpHeaders count])
+ _httpHeaders.reset();
+}
+
+- (void)resetHTTPHeaders {
+ _httpHeaders.reset();
+}
+
+#pragma mark - Serialization helpers
+
++ (web::PageScrollState)scrollStateFromDictionary:(NSDictionary*)dictionary {
+ web::PageScrollState scrollState;
+ NSNumber* serializedValue = nil;
+ if ((serializedValue = dictionary[kScrollOffsetXKey]))
+ scrollState.set_scroll_offset_x([serializedValue doubleValue]);
+ if ((serializedValue = dictionary[kScrollOffsetYKey]))
+ scrollState.set_scroll_offset_y([serializedValue doubleValue]);
+ if ((serializedValue = dictionary[kMinimumZoomScaleKey]))
+ scrollState.set_minimum_zoom_scale([serializedValue doubleValue]);
+ if ((serializedValue = dictionary[kMaximumZoomScaleKey]))
+ scrollState.set_maximum_zoom_scale([serializedValue doubleValue]);
+ if ((serializedValue = dictionary[kZoomScaleKey]))
+ scrollState.set_zoom_scale([serializedValue doubleValue]);
+ return scrollState;
+}
+
++ (NSDictionary*)dictionaryFromScrollState:
+ (const web::PageScrollState&)scrollState {
+ return @{
+ kScrollOffsetXKey : @(scrollState.scroll_offset_x()),
+ kScrollOffsetYKey : @(scrollState.scroll_offset_y()),
+ kMinimumZoomScaleKey : @(scrollState.minimum_zoom_scale()),
+ kMaximumZoomScaleKey : @(scrollState.maximum_zoom_scale()),
+ kZoomScaleKey : @(scrollState.zoom_scale())
+ };
+}
+
++ (NSString*)scrollStateDescription:(const web::PageScrollState&)scrollState {
+ NSString* const kPageScrollStateDescriptionFormat =
+ @"{ scrollOffset:(%0.2f, %0.2f), zoomScaleRange:(%0.2f, %0.2f), "
+ @"zoomScale:%0.2f }";
+ return [NSString stringWithFormat:kPageScrollStateDescriptionFormat,
+ scrollState.scroll_offset_x(),
+ scrollState.scroll_offset_y(),
+ scrollState.minimum_zoom_scale(),
+ scrollState.maximum_zoom_scale(),
+ scrollState.zoom_scale()];
+}
+
+@end
diff --git a/ios/web/navigation/crw_session_entry_unittest.mm b/ios/web/navigation/crw_session_entry_unittest.mm
new file mode 100644
index 0000000..210afc6
--- /dev/null
+++ b/ios/web/navigation/crw_session_entry_unittest.mm
@@ -0,0 +1,400 @@
+// 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/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#import "ios/testing/ocmock_complex_type_helper.h"
+#import "ios/web/navigation/crw_session_entry.h"
+#include "ios/web/navigation/navigation_item_impl.h"
+#include "ios/web/public/referrer.h"
+#import "net/base/mac/url_conversions.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"
+#include "ui/base/page_transition_types.h"
+
+@interface CRWSessionEntry (ExposedForTesting)
++ (web::PageScrollState)scrollStateFromDictionary:(NSDictionary*)dictionary;
++ (NSDictionary*)dictionaryFromScrollState:
+ (const web::PageScrollState&)scrollState;
+@end
+
+static NSString* const kHTTPHeaderKey1 = @"key1";
+static NSString* const kHTTPHeaderKey2 = @"key2";
+static NSString* const kHTTPHeaderValue1 = @"value1";
+static NSString* const kHTTPHeaderValue2 = @"value2";
+
+class CRWSessionEntryTest : public PlatformTest {
+ public:
+ static void expectEqualSessionEntries(CRWSessionEntry* entry1,
+ CRWSessionEntry* entry2,
+ ui::PageTransition transition);
+
+ protected:
+ void SetUp() override {
+ GURL url("http://init.test");
+ ui::PageTransition transition =
+ ui::PAGE_TRANSITION_AUTO_BOOKMARK;
+ sessionEntry_.reset([[CRWSessionEntry alloc] initWithUrl:url
+ referrer:web::Referrer()
+ transition:transition
+ useDesktopUserAgent:NO
+ rendererInitiated:NO]);
+ [sessionEntry_ navigationItem]->SetTimestamp(base::Time::Now());
+ [sessionEntry_ addHTTPHeaders:@{ kHTTPHeaderKey1 : kHTTPHeaderValue1 }];
+ [sessionEntry_
+ setPOSTData:[@"Test data" dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+ void TearDown() override { sessionEntry_.reset(); }
+
+ protected:
+ base::scoped_nsobject<CRWSessionEntry> sessionEntry_;
+};
+
+@implementation OCMockComplexTypeHelper (CRWSessionEntryTest)
+typedef void (^encodeBytes_length_forKey_block)(
+ const uint8_t*, NSUInteger, NSString*);
+- (void)encodeBytes:(const uint8_t*)bytes
+ length:(NSUInteger)length
+ forKey:(NSString*)key {
+ static_cast<encodeBytes_length_forKey_block>([self blockForSelector:_cmd])(
+ bytes, length, key);
+}
+
+typedef const uint8_t* (^decodeBytesForKeyBlock)(NSString*, NSUInteger*);
+- (const uint8_t*)decodeBytesForKey:(NSString*)key
+ returnedLength:(NSUInteger*)lengthp {
+ return static_cast<decodeBytesForKeyBlock>([self blockForSelector:_cmd])(
+ key, lengthp);
+}
+@end
+
+void CRWSessionEntryTest::expectEqualSessionEntries(
+ CRWSessionEntry* entry1,
+ CRWSessionEntry* entry2,
+ ui::PageTransition transition) {
+ EXPECT_EQ(entry1.index, entry2.index);
+ web::NavigationItem* navItem1 = entry1.navigationItem;
+ web::NavigationItem* navItem2 = entry2.navigationItem;
+ // url is not compared because it could differ after copy or archive.
+ EXPECT_EQ(navItem1->GetVirtualURL(), navItem2->GetVirtualURL());
+ EXPECT_EQ(navItem1->GetReferrer().url, navItem2->GetReferrer().url);
+ EXPECT_EQ(navItem1->GetTimestamp(), navItem2->GetTimestamp());
+ EXPECT_EQ(navItem1->GetTitle(), navItem2->GetTitle());
+ EXPECT_EQ(navItem1->GetPageScrollState(), navItem2->GetPageScrollState());
+ EXPECT_EQ(entry1.useDesktopUserAgent, entry2.useDesktopUserAgent);
+ EXPECT_EQ(entry1.usedDataReductionProxy, entry2.usedDataReductionProxy);
+ EXPECT_EQ(navItem2->GetTransitionType(), transition);
+ EXPECT_NSEQ(entry1.httpHeaders, entry2.httpHeaders);
+ EXPECT_TRUE((!entry1.POSTData && !entry2.POSTData) ||
+ [entry1.POSTData isEqualToData:entry2.POSTData]);
+ EXPECT_EQ(entry1.skipResubmitDataConfirmation,
+ entry2.skipResubmitDataConfirmation);
+}
+
+TEST_F(CRWSessionEntryTest, Description) {
+ [sessionEntry_ navigationItem]->SetTitle(base::SysNSStringToUTF16(@"Title"));
+ EXPECT_NSEQ([sessionEntry_ description], @"url:http://init.test/ "
+ @"originalurl:http://init.test/ " @"title:Title " @"transition:2 "
+ @"scrollState:{ scrollOffset:(nan, nan), zoomScaleRange:(nan, "
+ @"nan), zoomScale:nan } " @"desktopUA:0 " @"proxy:0");
+}
+
+TEST_F(CRWSessionEntryTest, InitWithCoder) {
+ web::NavigationItem* item = [sessionEntry_ navigationItem];
+ item->SetVirtualURL(GURL("http://user.friendly"));
+ item->SetTitle(base::SysNSStringToUTF16(@"Title"));
+ int index = sessionEntry_.get().index;
+ // Old serialized entries have no timestamp.
+ item->SetTimestamp(base::Time::FromInternalValue(0));
+
+ NSURL* virtualUrl = net::NSURLWithGURL(item->GetVirtualURL());
+ NSURL* referrer = net::NSURLWithGURL(item->GetReferrer().url);
+ NSString* title = base::SysUTF16ToNSString(item->GetTitle());
+ base::scoped_nsobject<id> decoder([[OCMockComplexTypeHelper alloc]
+ initWithRepresentedObject:[OCMockObject mockForClass:[NSCoder class]]]);
+
+ decodeBytesForKeyBlock block = ^ const uint8_t* (NSString* key,
+ NSUInteger* length) {
+ *length = 0;
+ return NULL;
+ };
+
+ [[[decoder stub] andReturnValue:[NSNumber numberWithBool:NO]]
+ containsValueForKey:[OCMArg any]];
+
+ [decoder onSelector:@selector(decodeBytesForKey:returnedLength:)
+ callBlockExpectation:block];
+ [[[decoder expect] andReturnValue:OCMOCK_VALUE(index)]
+ decodeIntForKey:@"index"];
+ [[[decoder expect] andReturn:virtualUrl]
+ decodeObjectForKey:@"virtualUrl"];
+ [[[decoder expect] andReturn:referrer]
+ decodeObjectForKey:@"referrer"];
+ [[[decoder expect] andReturn:title]
+ decodeObjectForKey:@"title"];
+ const web::PageScrollState& scrollState =
+ [sessionEntry_ navigationItem]->GetPageScrollState();
+ NSDictionary* serializedScrollState =
+ [CRWSessionEntry dictionaryFromScrollState:scrollState];
+ [[[decoder expect] andReturn:serializedScrollState]
+ decodeObjectForKey:@"state"];
+ BOOL useDesktopUserAgent = sessionEntry_.get().useDesktopUserAgent;
+ [[[decoder expect] andReturnValue:OCMOCK_VALUE(useDesktopUserAgent)]
+ decodeBoolForKey:@"useDesktopUserAgent"];
+ BOOL usedDataReductionProxy = sessionEntry_.get().usedDataReductionProxy;
+ [[[decoder expect] andReturnValue:OCMOCK_VALUE(usedDataReductionProxy)]
+ decodeBoolForKey:@"usedDataReductionProxy"];
+ [[[decoder expect] andReturn:sessionEntry_.get().httpHeaders]
+ decodeObjectForKey:@"httpHeaders"];
+ [[[decoder expect] andReturn:sessionEntry_.get().POSTData]
+ decodeObjectForKey:@"POSTData"];
+ BOOL skipResubmitDataConfirmation =
+ sessionEntry_.get().skipResubmitDataConfirmation;
+ [[[decoder expect] andReturnValue:OCMOCK_VALUE(skipResubmitDataConfirmation)]
+ decodeBoolForKey:@"skipResubmitDataConfirmation"];
+
+ base::scoped_nsobject<CRWSessionEntry> newSessionEntry(
+ [[CRWSessionEntry alloc] initWithCoder:decoder]);
+ web::NavigationItem* newItem = [newSessionEntry navigationItem];
+
+ EXPECT_OCMOCK_VERIFY(decoder);
+ expectEqualSessionEntries(sessionEntry_, newSessionEntry,
+ ui::PAGE_TRANSITION_RELOAD);
+ EXPECT_NE(item->GetURL(), newItem->GetURL());
+ EXPECT_EQ(item->GetVirtualURL(), newItem->GetURL());
+}
+
+TEST_F(CRWSessionEntryTest, InitWithCoderNewStyle) {
+ web::NavigationItem* item = [sessionEntry_ navigationItem];
+ item->SetVirtualURL(GURL("http://user.friendly"));
+ item->SetTitle(base::SysNSStringToUTF16(@"Title"));
+ int index = sessionEntry_.get().index;
+ int64 timestamp = item->GetTimestamp().ToInternalValue();
+
+ std::string virtualUrl = item->GetVirtualURL().spec();
+ std::string referrerUrl = item->GetReferrer().url.spec();
+ NSString* title = base::SysUTF16ToNSString(item->GetTitle());
+ base::scoped_nsobject<id> decoder([[OCMockComplexTypeHelper alloc]
+ initWithRepresentedObject:[OCMockObject mockForClass:[NSCoder class]]]);
+
+ const std::string emptyString;
+ decodeBytesForKeyBlock block = ^ const uint8_t* (NSString* key,
+ NSUInteger* length) {
+ const std::string *value = &emptyString;
+ if ([key isEqualToString:@"virtualUrlString"])
+ value = &virtualUrl;
+ else if ([key isEqualToString:@"referrerUrlString"])
+ value = &referrerUrl;
+ else
+ EXPECT_TRUE(false);
+
+ *length = value->size();
+ return reinterpret_cast<const uint8_t*>(value->data());
+ };
+
+ [decoder onSelector:@selector(decodeBytesForKey:returnedLength:)
+ callBlockExpectation:block];
+ [[[decoder stub] andReturnValue:[NSNumber numberWithBool:YES]]
+ containsValueForKey:[OCMArg any]];
+ [[[decoder expect] andReturnValue:OCMOCK_VALUE(index)]
+ decodeIntForKey:@"index"];
+ web::ReferrerPolicy expectedPolicy = item->GetReferrer().policy;
+ [[[decoder expect]
+ andReturnValue:OCMOCK_VALUE(expectedPolicy)]
+ decodeIntForKey:@"referrerPolicy"];
+ [[[decoder expect] andReturnValue:OCMOCK_VALUE(timestamp)]
+ decodeInt64ForKey:@"timestamp"];
+ [[[decoder expect] andReturn:title]
+ decodeObjectForKey:@"title"];
+ const web::PageScrollState& scrollState =
+ [sessionEntry_ navigationItem]->GetPageScrollState();
+ NSDictionary* serializedScrollState =
+ [CRWSessionEntry dictionaryFromScrollState:scrollState];
+ [[[decoder expect] andReturn:serializedScrollState]
+ decodeObjectForKey:@"state"];
+ BOOL useDesktopUserAgent = sessionEntry_.get().useDesktopUserAgent;
+ [[[decoder expect] andReturnValue:OCMOCK_VALUE(useDesktopUserAgent)]
+ decodeBoolForKey:@"useDesktopUserAgent"];
+ BOOL usedDataReductionProxy = sessionEntry_.get().usedDataReductionProxy;
+ [[[decoder expect] andReturnValue:OCMOCK_VALUE(usedDataReductionProxy)]
+ decodeBoolForKey:@"usedDataReductionProxy"];
+ [[[decoder expect] andReturn:sessionEntry_.get().httpHeaders]
+ decodeObjectForKey:@"httpHeaders"];
+ [[[decoder expect] andReturn:sessionEntry_.get().POSTData]
+ decodeObjectForKey:@"POSTData"];
+ BOOL skipResubmitDataConfirmation =
+ sessionEntry_.get().skipResubmitDataConfirmation;
+ [[[decoder expect] andReturnValue:OCMOCK_VALUE(skipResubmitDataConfirmation)]
+ decodeBoolForKey:@"skipResubmitDataConfirmation"];
+
+ base::scoped_nsobject<CRWSessionEntry> newSessionEntry(
+ [[CRWSessionEntry alloc] initWithCoder:decoder]);
+ web::NavigationItem* newItem = [newSessionEntry navigationItem];
+
+ EXPECT_OCMOCK_VERIFY(decoder);
+ expectEqualSessionEntries(sessionEntry_, newSessionEntry,
+ ui::PAGE_TRANSITION_RELOAD);
+ EXPECT_NE(item->GetURL(), newItem->GetURL());
+ EXPECT_EQ(item->GetVirtualURL(), newItem->GetVirtualURL());
+}
+
+TEST_F(CRWSessionEntryTest, EncodeDecode) {
+ NSData *data =
+ [NSKeyedArchiver archivedDataWithRootObject:sessionEntry_];
+ id decoded = [NSKeyedUnarchiver unarchiveObjectWithData:data];
+
+ expectEqualSessionEntries(sessionEntry_, decoded,
+ ui::PAGE_TRANSITION_RELOAD);
+}
+
+TEST_F(CRWSessionEntryTest, EncodeWithCoder) {
+ web::NavigationItem* item = [sessionEntry_ navigationItem];
+ NSString* title = base::SysUTF16ToNSString(item->GetTitle());
+
+ base::scoped_nsobject<id> coder([[OCMockComplexTypeHelper alloc]
+ initWithRepresentedObject:[OCMockObject mockForClass:[NSCoder class]]]);
+
+ encodeBytes_length_forKey_block block = ^(const uint8_t* bytes,
+ NSUInteger length,
+ NSString* key) {
+ if ([key isEqualToString:@"virtualUrlString"]) {
+ ASSERT_EQ(item->GetVirtualURL().spec(),
+ std::string(reinterpret_cast<const char*>(bytes), length));
+ return;
+ } else if ([key isEqualToString:@"referrerUrlString"]) {
+ ASSERT_EQ(item->GetReferrer().url.spec(),
+ std::string(reinterpret_cast<const char*>(bytes), length));
+ return;
+ }
+ FAIL();
+ };
+ [coder onSelector:@selector(encodeBytes:length:forKey:)
+ callBlockExpectation:block];
+ [[coder expect] encodeInt:[sessionEntry_ index] forKey:@"index"];
+ [[coder expect] encodeInt:item->GetReferrer().policy
+ forKey:@"referrerPolicy"];
+ [[coder expect] encodeInt64:item->GetTimestamp().ToInternalValue()
+ forKey:@"timestamp"];
+ [[coder expect] encodeObject:title forKey:@"title"];
+ const web::PageScrollState& scrollState =
+ [sessionEntry_ navigationItem]->GetPageScrollState();
+ NSDictionary* serializedScrollState =
+ [CRWSessionEntry dictionaryFromScrollState:scrollState];
+ [[coder expect] encodeObject:serializedScrollState forKey:@"state"];
+ [[coder expect] encodeBool:[sessionEntry_ useDesktopUserAgent]
+ forKey:@"useDesktopUserAgent"];
+ [[coder expect] encodeBool:[sessionEntry_ usedDataReductionProxy]
+ forKey:@"usedDataReductionProxy"];
+ [[coder expect] encodeObject:[sessionEntry_ httpHeaders]
+ forKey:@"httpHeaders"];
+ [[coder expect] encodeObject:[sessionEntry_ POSTData] forKey:@"POSTData"];
+ [[coder expect] encodeBool:[sessionEntry_ skipResubmitDataConfirmation]
+ forKey:@"skipResubmitDataConfirmation"];
+ [sessionEntry_ encodeWithCoder:coder];
+ EXPECT_OCMOCK_VERIFY(coder);
+}
+
+TEST_F(CRWSessionEntryTest, CodingEncoding) {
+ web::NavigationItem* item = [sessionEntry_ navigationItem];
+ item->SetVirtualURL(GURL("http://user.friendly"));
+ NSData* data = [NSKeyedArchiver archivedDataWithRootObject:sessionEntry_];
+ EXPECT_TRUE(data != nil);
+ CRWSessionEntry* unarchivedSessionEntry =
+ [NSKeyedUnarchiver unarchiveObjectWithData:data];
+ ASSERT_TRUE(unarchivedSessionEntry != nil);
+ web::NavigationItem* unarchivedItem = [unarchivedSessionEntry navigationItem];
+ expectEqualSessionEntries(sessionEntry_, unarchivedSessionEntry,
+ ui::PAGE_TRANSITION_RELOAD);
+ EXPECT_EQ(unarchivedItem->GetURL(), item->GetVirtualURL());
+ EXPECT_NE(unarchivedItem->GetURL(), item->GetURL());
+}
+
+TEST_F(CRWSessionEntryTest, CopyWithZone) {
+ CRWSessionEntry* sessionEntry2 = [sessionEntry_ copy];
+ EXPECT_NE(sessionEntry_, sessionEntry2);
+ expectEqualSessionEntries(
+ sessionEntry_, sessionEntry2,
+ [sessionEntry_ navigationItem]->GetTransitionType());
+}
+
+TEST_F(CRWSessionEntryTest, EmptyVirtualUrl) {
+ EXPECT_EQ(GURL("http://init.test/"),
+ [sessionEntry_ navigationItem]->GetURL());
+}
+
+TEST_F(CRWSessionEntryTest, NonEmptyVirtualUrl) {
+ web::NavigationItem* item = [sessionEntry_ navigationItem];
+ item->SetVirtualURL(GURL("http://user.friendly"));
+ EXPECT_EQ(GURL("http://user.friendly/"), item->GetVirtualURL());
+ EXPECT_EQ(GURL("http://init.test/"), item->GetURL());
+}
+
+TEST_F(CRWSessionEntryTest, EmptyDescription) {
+ EXPECT_GT([[sessionEntry_ description] length], 0U);
+}
+
+TEST_F(CRWSessionEntryTest, CreateWithNavigationItem) {
+ int index = 5; // Just pick something non-zero.
+ GURL url("http://www.virtualurl.com");
+ web::Referrer referrer(GURL("http://www.referrer.com"),
+ web::ReferrerPolicyDefault);
+ base::string16 title = base::SysNSStringToUTF16(@"Title");
+ std::string state;
+ ui::PageTransition transition = ui::PAGE_TRANSITION_GENERATED;
+
+ scoped_ptr<web::NavigationItem> navigation_item(
+ new web::NavigationItemImpl());
+ navigation_item->SetURL(url);
+ navigation_item->SetReferrer(referrer);
+ navigation_item->SetTitle(title);
+ navigation_item->SetTransitionType(transition);
+
+ base::scoped_nsobject<CRWSessionEntry> sessionEntry(
+ [[CRWSessionEntry alloc] initWithNavigationItem:navigation_item.Pass()
+ index:index]);
+ web::NavigationItem* item = [sessionEntry navigationItem];
+ // Validate everything was set correctly.
+ EXPECT_EQ(sessionEntry.get().index, index);
+ // Desktop only persists the virtual url, all three fields are initialized
+ // by it.
+ EXPECT_EQ(item->GetURL(), url);
+ EXPECT_EQ(item->GetVirtualURL(), url);
+ EXPECT_EQ(sessionEntry.get().originalUrl, url);
+ EXPECT_EQ(item->GetReferrer().url, referrer.url);
+ EXPECT_EQ(item->GetTitle(), title);
+ EXPECT_EQ(item->GetTransitionType(), transition);
+}
+
+TEST_F(CRWSessionEntryTest, AddHTTPHeaders) {
+ EXPECT_NSEQ(@{ kHTTPHeaderKey1 : kHTTPHeaderValue1 },
+ [sessionEntry_ httpHeaders]);
+
+ [sessionEntry_ addHTTPHeaders:@{ kHTTPHeaderKey1 : kHTTPHeaderValue2 }];
+ EXPECT_NSEQ(@{ kHTTPHeaderKey1 : kHTTPHeaderValue2 },
+ [sessionEntry_ httpHeaders]);
+
+ [sessionEntry_ addHTTPHeaders:@{ kHTTPHeaderKey2 : kHTTPHeaderValue1 }];
+ NSDictionary* expected = @{ kHTTPHeaderKey1 : kHTTPHeaderValue2,
+ kHTTPHeaderKey2 : kHTTPHeaderValue1 };
+ EXPECT_NSEQ(expected, [sessionEntry_ httpHeaders]);
+}
+
+TEST_F(CRWSessionEntryTest, RemoveHTTPHeaderForKey) {
+ NSDictionary* httpHeaders = @{ kHTTPHeaderKey1 : kHTTPHeaderValue1,
+ kHTTPHeaderKey2 : kHTTPHeaderValue2 };
+ [sessionEntry_ addHTTPHeaders:httpHeaders];
+ EXPECT_NSEQ(httpHeaders, [sessionEntry_ httpHeaders]);
+
+ [sessionEntry_ removeHTTPHeaderForKey:kHTTPHeaderKey1];
+ EXPECT_NSEQ(@{ kHTTPHeaderKey2 : kHTTPHeaderValue2 },
+ [sessionEntry_ httpHeaders]);
+
+ [sessionEntry_ removeHTTPHeaderForKey:kHTTPHeaderKey2];
+ EXPECT_TRUE([sessionEntry_ httpHeaders] == nil);
+}
diff --git a/ios/web/navigation/navigation_item_facade_delegate.h b/ios/web/navigation/navigation_item_facade_delegate.h
new file mode 100644
index 0000000..8ccd6c0
--- /dev/null
+++ b/ios/web/navigation/navigation_item_facade_delegate.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_NAVIGATION_NAVIGATION_ITEM_FACADE_DELEGATE_H_
+#define IOS_WEB_NAVIGATION_NAVIGATION_ITEM_FACADE_DELEGATE_H_
+
+namespace content {
+class NavigationEntry;
+}
+
+namespace web {
+
+class NavigationItemImpl;
+
+// Interface used by NavigationItems to interact with their NavigationEntry
+// facades. This pushes references to NavigationEntry out of the web-layer.
+// Note that since communication is one-directional, this interface is primarily
+// to add hooks for creation/deletion of facades.
+class NavigationItemFacadeDelegate {
+ public:
+ NavigationItemFacadeDelegate() {}
+ virtual ~NavigationItemFacadeDelegate() {}
+
+ // Returns the facade object being driven by this delegate.
+ virtual content::NavigationEntry* GetNavigationEntryFacade() = 0;
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NAVIGATION_NAVIGATION_ITEM_FACADE_DELEGATE_H_
diff --git a/ios/web/navigation/navigation_item_impl.h b/ios/web/navigation/navigation_item_impl.h
index 26cc2bc..9abd992 100644
--- a/ios/web/navigation/navigation_item_impl.h
+++ b/ios/web/navigation/navigation_item_impl.h
@@ -8,14 +8,18 @@
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string16.h"
+#include "ios/web/navigation/navigation_item_facade_delegate.h"
#include "ios/web/public/favicon_status.h"
#include "ios/web/public/navigation_item.h"
#include "ios/web/public/referrer.h"
#include "ios/web/public/ssl_status.h"
#include "url/gurl.h"
+
namespace web {
+class NavigationItemFacadeDelegate;
+
// Implementation of NavigationItem.
class NavigationItemImpl : public web::NavigationItem {
public:
@@ -23,6 +27,17 @@ class NavigationItemImpl : public web::NavigationItem {
NavigationItemImpl();
~NavigationItemImpl() override;
+ // Since NavigationItemImpls own their facade delegates, there is no implicit
+ // copy constructor (scoped_ptrs can't be copied), so one is defined here.
+ NavigationItemImpl(const NavigationItemImpl& item);
+
+ // Accessors for the delegate used to drive the navigation entry facade.
+ // NOTE: to minimize facade synchronization code, NavigationItems take
+ // ownership of their facade delegates.
+ void SetFacadeDelegate(
+ scoped_ptr<NavigationItemFacadeDelegate> facade_delegate);
+ NavigationItemFacadeDelegate* GetFacadeDelegate() const;
+
// NavigationItem implementation:
int GetUniqueID() const override;
void SetURL(const GURL& url) override;
@@ -35,6 +50,8 @@ class NavigationItemImpl : public web::NavigationItem {
const base::string16& GetTitle() const override;
void SetPageID(int page_id) override;
int32 GetPageID() const override;
+ void SetPageScrollState(const PageScrollState& scroll_state) override;
+ const PageScrollState& GetPageScrollState() const override;
const base::string16& GetTitleForDisplay(
const std::string& languages) const override;
void SetTransitionType(ui::PageTransition transition_type) override;
@@ -45,6 +62,19 @@ class NavigationItemImpl : public web::NavigationItem {
SSLStatus& GetSSL() override;
void SetTimestamp(base::Time timestamp) override;
base::Time GetTimestamp() const override;
+ void SetUnsafe(bool is_unsafe) override;
+ bool IsUnsafe() const override;
+
+ // Once a navigation item is committed, we should no longer track
+ // non-persisted state, as documented on the members below.
+ void ResetForCommit();
+
+ // Whether this (pending) navigation is renderer-initiated. Resets to false
+ // for all types of navigations after commit.
+ void set_is_renderer_initiated(bool is_renderer_initiated) {
+ is_renderer_initiated_ = is_renderer_initiated;
+ }
+ bool is_renderer_initiated() const { return is_renderer_initiated_; }
private:
int unique_id_;
@@ -53,15 +83,27 @@ class NavigationItemImpl : public web::NavigationItem {
GURL virtual_url_;
base::string16 title_;
int32 page_id_;
+ PageScrollState page_scroll_state_;
ui::PageTransition transition_type_;
FaviconStatus favicon_;
SSLStatus ssl_;
base::Time timestamp_;
+ // Whether the item, while loading, was created for a renderer-initiated
+ // navigation. This dictates whether the URL should be displayed before the
+ // navigation commits. It is cleared in |ResetForCommit| and not persisted.
+ bool is_renderer_initiated_;
+
+ // Whether the navigation contains unsafe resources.
+ bool is_unsafe_;
+
// This is a cached version of the result of GetTitleForDisplay. When the URL,
// virtual URL, or title is set, this should be cleared to force a refresh.
mutable base::string16 cached_display_title_;
+ // Weak pointer to the facade delegate.
+ scoped_ptr<NavigationItemFacadeDelegate> facade_delegate_;
+
// Copy and assignment is explicitly allowed for this class.
};
diff --git a/ios/web/navigation/navigation_item_impl.mm b/ios/web/navigation/navigation_item_impl.mm
index 1f86d72..56edd85 100644
--- a/ios/web/navigation/navigation_item_impl.mm
+++ b/ios/web/navigation/navigation_item_impl.mm
@@ -31,12 +31,42 @@ scoped_ptr<NavigationItem> NavigationItem::Create() {
NavigationItemImpl::NavigationItemImpl()
: unique_id_(GetUniqueIDInConstructor()),
page_id_(-1),
- transition_type_(ui::PAGE_TRANSITION_LINK) {
+ transition_type_(ui::PAGE_TRANSITION_LINK),
+ is_renderer_initiated_(false),
+ is_unsafe_(false),
+ facade_delegate_(nullptr) {
}
NavigationItemImpl::~NavigationItemImpl() {
}
+NavigationItemImpl::NavigationItemImpl(const NavigationItemImpl& item)
+ : unique_id_(item.unique_id_),
+ url_(item.url_),
+ referrer_(item.referrer_),
+ virtual_url_(item.virtual_url_),
+ title_(item.title_),
+ page_id_(item.page_id_),
+ page_scroll_state_(item.page_scroll_state_),
+ transition_type_(item.transition_type_),
+ favicon_(item.favicon_),
+ ssl_(item.ssl_),
+ timestamp_(item.timestamp_),
+ is_renderer_initiated_(item.is_renderer_initiated_),
+ is_unsafe_(item.is_unsafe_),
+ cached_display_title_(item.cached_display_title_),
+ facade_delegate_(nullptr) {
+}
+
+void NavigationItemImpl::SetFacadeDelegate(
+ scoped_ptr<NavigationItemFacadeDelegate> facade_delegate) {
+ facade_delegate_ = facade_delegate.Pass();
+}
+
+NavigationItemFacadeDelegate* NavigationItemImpl::GetFacadeDelegate() const {
+ return facade_delegate_.get();
+}
+
int NavigationItemImpl::GetUniqueID() const {
return unique_id_;
}
@@ -84,6 +114,15 @@ int32 NavigationItemImpl::GetPageID() const {
return page_id_;
}
+void NavigationItemImpl::SetPageScrollState(
+ const web::PageScrollState& scroll_state) {
+ page_scroll_state_ = scroll_state;
+}
+
+const PageScrollState& NavigationItemImpl::GetPageScrollState() const {
+ return page_scroll_state_;
+}
+
const base::string16& NavigationItemImpl::GetTitleForDisplay(
const std::string& languages) const {
// Most pages have real titles. Don't even bother caching anything if this is
@@ -148,4 +187,18 @@ base::Time NavigationItemImpl::GetTimestamp() const {
return timestamp_;
}
+void NavigationItemImpl::ResetForCommit() {
+ // Any state that only matters when a navigation item is pending should be
+ // cleared here.
+ set_is_renderer_initiated(false);
+}
+
+void NavigationItemImpl::SetUnsafe(bool is_unsafe) {
+ is_unsafe_ = is_unsafe;
+}
+
+bool NavigationItemImpl::IsUnsafe() const {
+ return is_unsafe_;
+}
+
} // namespace web
diff --git a/ios/web/navigation/navigation_manager_delegate.h b/ios/web/navigation/navigation_manager_delegate.h
new file mode 100644
index 0000000..bc1e058
--- /dev/null
+++ b/ios/web/navigation/navigation_manager_delegate.h
@@ -0,0 +1,34 @@
+// 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_NAVIGATION_NAVIGATION_MANAGER_DELEGATE_H_
+#define IOS_WEB_NAVIGATION_NAVIGATION_MANAGER_DELEGATE_H_
+
+namespace web {
+
+struct LoadCommittedDetails;
+class WebState;
+
+// Delegate for NavigationManager to hand off parts of the navigation flow.
+// TODO(stuartmorgan): See if this can be eliminated by moving more
+class NavigationManagerDelegate {
+ public:
+ virtual ~NavigationManagerDelegate() {}
+
+ // Instructs the delegate to begin navigating to the pending entry.
+ // TODO(stuartmorgan): Remove this once more navigation logic moves to
+ // NavigationManagerImpl.
+ virtual void NavigateToPendingEntry() = 0;
+
+ // Informs the delegate that a navigation item has been commited.
+ virtual void OnNavigationItemCommitted(
+ const LoadCommittedDetails& load_details) = 0;
+
+ // Returns the WebState associated with this delegate.
+ virtual WebState* GetWebState() = 0;
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NAVIGATION_NAVIGATION_MANAGER_DELEGATE_H_
diff --git a/ios/web/navigation/navigation_manager_facade_delegate.h b/ios/web/navigation/navigation_manager_facade_delegate.h
new file mode 100644
index 0000000..b405f04
--- /dev/null
+++ b/ios/web/navigation/navigation_manager_facade_delegate.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_NAVIGATION_NAVIGATION_MANAGER_FACADE_DELEGATE_H_
+#define IOS_WEB_NAVIGATION_NAVIGATION_MANAGER_FACADE_DELEGATE_H_
+
+namespace content {
+class NavigationController;
+}
+
+namespace web {
+
+class NavigationItemImpl;
+class NavigationManagerImpl;
+
+// Interface used by the NavigationManager to drive the NavigationController
+// facade. This pushes the ownership of the facade out of the web-layer to
+// simplify upstreaming efforts. Once upstream features are componentized and
+// use NavigationManager, this class will no longer be necessary.
+class NavigationManagerFacadeDelegate {
+ public:
+ NavigationManagerFacadeDelegate() {}
+ virtual ~NavigationManagerFacadeDelegate() {}
+
+ // Sets the NavigationManagerImpl that backs the NavigationController facade.
+ virtual void SetNavigationManager(
+ NavigationManagerImpl* navigation_manager) = 0;
+ // Returns the facade object being driven by this delegate.
+ virtual content::NavigationController* GetNavigationControllerFacade() = 0;
+
+ // Callbacks for triggering notifications:
+
+ // Called when the NavigationManager has a pending NavigationItem.
+ virtual void OnNavigationItemPending() = 0;
+ // Called when the NavigationManager has updated a NavigationItem.
+ virtual void OnNavigationItemChanged() = 0;
+ // Called when the NavigationManager has committed a pending NavigationItem.
+ virtual void OnNavigationItemCommitted(int previous_item_index,
+ bool is_in_page) = 0;
+ // Called when the NavigationManager has pruned committed NavigationItems.
+ virtual void OnNavigationItemsPruned(size_t pruned_item_count) = 0;
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NAVIGATION_NAVIGATION_MANAGER_FACADE_DELEGATE_H_
diff --git a/ios/web/navigation/navigation_manager_impl.h b/ios/web/navigation/navigation_manager_impl.h
new file mode 100644
index 0000000..92f65d4
--- /dev/null
+++ b/ios/web/navigation/navigation_manager_impl.h
@@ -0,0 +1,147 @@
+// 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_NAVIGATION_MANAGER_IMPL_H_
+#define IOS_WEB_NAVIGATION_NAVIGATION_MANAGER_IMPL_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_vector.h"
+#include "ios/web/public/navigation_manager.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+@class CRWSessionController;
+@class CRWSessionEntry;
+
+namespace web {
+class BrowserState;
+class NavigationItem;
+struct Referrer;
+class NavigationManagerDelegate;
+class NavigationManagerFacadeDelegate;
+
+// Implementation of NavigationManager.
+// Generally mirrors upstream's NavigationController.
+class NavigationManagerImpl : public web::NavigationManager {
+ public:
+ NavigationManagerImpl(NavigationManagerDelegate* delegate,
+ BrowserState* browser_state);
+ ~NavigationManagerImpl() override;
+
+ // Sets the CRWSessionController that backs this object.
+ // Keeps a strong reference to |session_controller|.
+ // This method should only be called when deserializing |session_controller|
+ // and joining it with its NavigationManager. Other cases should call
+ // InitializeSession() or ReplaceSessionHistory().
+ // TODO(stuartmorgan): Also move deserialization of CRWSessionControllers
+ // under the control of this class, and move the bulk of CRWSessionController
+ // logic into it.
+ void SetSessionController(CRWSessionController* session_controller);
+
+ // Initializes a new session history, supplying a unique |window_name| for the
+ // window (or nil). |opener_id| is the id of opener, or nil if there is none.
+ // |opened_by_dom| is YES if the page was opened by DOM.
+ // |opener_index| is the navigation index of the opener, or -1 if there is
+ // none.
+ void InitializeSession(NSString* window_name,
+ NSString* opener_id,
+ BOOL opened_by_dom,
+ int opener_navigation_index);
+
+ // Replace the session history with a new one, where |items| is the
+ // complete set of navigation items in the new history, and |current_index|
+ // is the index of the currently active item.
+ void ReplaceSessionHistory(ScopedVector<web::NavigationItem> items,
+ int current_index);
+
+ // Sets the delegate used to drive the navigation controller facade.
+ void SetFacadeDelegate(NavigationManagerFacadeDelegate* facade_delegate);
+ NavigationManagerFacadeDelegate* GetFacadeDelegate() const;
+
+ // Helper functions for communicating with the facade layer.
+ // TODO(stuartmorgan): Make these private once the logic triggering them moves
+ // into this layer.
+ void OnNavigationItemChanged();
+ void OnNavigationItemCommitted();
+
+ // Returns the pending entry corresponding to the navigation that is
+ // currently in progress, or nullptr if there is none.
+ NavigationItem* GetPendingItem() const;
+
+ // Returns the transient item if any. This is an item which is removed and
+ // discarded if any navigation occurs. Note that the returned item is owned
+ // by the navigation manager and may be deleted at any time.
+ NavigationItem* GetTransientItem() const;
+
+ // Returns the last committed NavigationItem, which may be NULL if there
+ // are no committed entries.
+ NavigationItem* GetLastCommittedItem() const;
+
+ // Returns the committed NavigationItem at |index|.
+ NavigationItem* GetItemAtIndex(size_t index) const;
+
+ // Temporary accessors and content/ class pass-throughs.
+ // TODO(stuartmorgan): Re-evaluate this list once the refactorings have
+ // settled down.
+ CRWSessionController* GetSessionController();
+ int GetCurrentEntryIndex() const;
+ int GetLastCommittedEntryIndex() const;
+ int GetEntryCount() const;
+ bool RemoveEntryAtIndex(int index);
+ void DiscardNonCommittedEntries();
+ int GetPendingEntryIndex() const;
+ void LoadURL(const GURL& url,
+ const web::Referrer& referrer,
+ ui::PageTransition type);
+ bool CanGoBack() const;
+ bool CanGoForward() const;
+ void GoBack();
+ void GoForward();
+
+ // Convenience accessors to get the underlying NavigationItems from the
+ // SessionEntries returned from |session_controller_|'s -lastUserEntry and
+ // -previousEntry methods.
+ // TODO(marq):Evaluate the long-term utility of these methods.
+ NavigationItem* GetLastUserItem() const;
+ NavigationItem* GetPreviousItem() const;
+
+ // Temporary method. Returns a vector of NavigationItems corresponding to
+ // the SessionEntries of the uderlying CRWSessionController.
+ // TOOD(marq): Remove this method and unfork its caller,
+ // TabRestoreServiceHelper::PopulateTab
+ std::vector<NavigationItem*> GetItems();
+
+ // NavigationManager:
+ BrowserState* GetBrowserState() const override;
+ WebState* GetWebState() const override;
+ web::NavigationItem* GetVisibleItem() const override;
+
+ // Copy state from |navigation_manager|, including a copy of that object's
+ // CRWSessionController.
+ // TODO(marq): This doesn't deep-copy the SessionEntries in the
+ // CRWSessionController.
+ void CopyState(NavigationManagerImpl* navigation_manager);
+ private:
+ // The primary delegate for this manager.
+ NavigationManagerDelegate* delegate_;
+
+ // The BrowserState that is associated with this instance.
+ BrowserState* browser_state_;
+
+ // CRWSessionController that backs this instance.
+ // TODO(stuartmorgan): Fold CRWSessionController into this class.
+ base::scoped_nsobject<CRWSessionController> session_controller_;
+
+ // Weak pointer to the facade delegate.
+ NavigationManagerFacadeDelegate* facade_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(NavigationManagerImpl);
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NAVIGATION_NAVIGATION_MANAGER_IMPL_H_
diff --git a/ios/web/navigation/navigation_manager_impl.mm b/ios/web/navigation/navigation_manager_impl.mm
new file mode 100644
index 0000000..f445104
--- /dev/null
+++ b/ios/web/navigation/navigation_manager_impl.mm
@@ -0,0 +1,251 @@
+// 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/navigation_manager_impl.h"
+
+#include "base/logging.h"
+#import "ios/web/navigation/crw_session_controller+private_constructors.h"
+#import "ios/web/navigation/crw_session_controller.h"
+#import "ios/web/navigation/crw_session_entry.h"
+#include "ios/web/navigation/navigation_item_impl.h"
+#include "ios/web/navigation/navigation_manager_delegate.h"
+#import "ios/web/navigation/navigation_manager_facade_delegate.h"
+#include "ios/web/public/load_committed_details.h"
+#include "ios/web/public/navigation_item.h"
+#include "ios/web/public/web_state/web_state.h"
+#include "ui/base/page_transition_types.h"
+
+namespace {
+
+// Checks whether or not two URL are an in-page navigation (differing only
+// in the fragment).
+bool AreURLsInPageNavigation(const GURL& existing_url, const GURL& new_url) {
+ if (existing_url == new_url || !new_url.has_ref())
+ return false;
+
+ url::Replacements<char> replacements;
+ replacements.ClearRef();
+ return existing_url.ReplaceComponents(replacements) ==
+ new_url.ReplaceComponents(replacements);
+}
+
+} // anonymous namespace
+
+namespace web {
+
+NavigationManagerImpl::NavigationManagerImpl(
+ NavigationManagerDelegate* delegate,
+ BrowserState* browser_state)
+ : delegate_(delegate),
+ browser_state_(browser_state),
+ facade_delegate_(nullptr) {
+ DCHECK(browser_state_);
+}
+
+NavigationManagerImpl::~NavigationManagerImpl() {
+ // The facade layer should be deleted before this object.
+ DCHECK(!facade_delegate_);
+
+ [session_controller_ setNavigationManager:nullptr];
+}
+
+CRWSessionController* NavigationManagerImpl::GetSessionController() {
+ return session_controller_;
+}
+
+void NavigationManagerImpl::SetSessionController(
+ CRWSessionController* session_controller) {
+ session_controller_.reset([session_controller retain]);
+ [session_controller_ setNavigationManager:this];
+}
+
+void NavigationManagerImpl::InitializeSession(NSString* window_name,
+ NSString* opener_id,
+ BOOL opened_by_dom,
+ int opener_navigation_index) {
+ SetSessionController([[[CRWSessionController alloc]
+ initWithWindowName:window_name
+ openerId:opener_id
+ openedByDOM:opened_by_dom
+ openerNavigationIndex:opener_navigation_index
+ browserState:browser_state_] autorelease]);
+}
+
+void NavigationManagerImpl::ReplaceSessionHistory(
+ ScopedVector<web::NavigationItem> items,
+ int current_index) {
+ SetSessionController([[[CRWSessionController alloc]
+ initWithNavigationItems:items.Pass()
+ currentIndex:current_index
+ browserState:browser_state_] autorelease]);
+}
+
+void NavigationManagerImpl::SetFacadeDelegate(
+ NavigationManagerFacadeDelegate* facade_delegate) {
+ facade_delegate_ = facade_delegate;
+}
+
+NavigationManagerFacadeDelegate* NavigationManagerImpl::GetFacadeDelegate()
+ const {
+ return facade_delegate_;
+}
+
+
+void NavigationManagerImpl::OnNavigationItemChanged() {
+ if (facade_delegate_)
+ facade_delegate_->OnNavigationItemChanged();
+}
+
+void NavigationManagerImpl::OnNavigationItemCommitted() {
+ LoadCommittedDetails details;
+ details.item = GetLastCommittedItem();
+ DCHECK(details.item);
+ details.previous_item_index = [session_controller_ previousNavigationIndex];
+ if (details.previous_item_index >= 0) {
+ DCHECK([session_controller_ previousEntry]);
+ details.previous_url =
+ [session_controller_ previousEntry].navigationItem->GetURL();
+ details.is_in_page =
+ AreURLsInPageNavigation(details.previous_url, details.item->GetURL());
+ } else {
+ details.previous_url = GURL();
+ details.is_in_page = NO;
+ }
+
+ delegate_->OnNavigationItemCommitted(details);
+
+ if (facade_delegate_) {
+ facade_delegate_->OnNavigationItemCommitted(details.previous_item_index,
+ details.is_in_page);
+ }
+}
+
+NavigationItem* NavigationManagerImpl::GetVisibleItem() const {
+ CRWSessionEntry* entry = [session_controller_ visibleEntry];
+ return [entry navigationItem];
+}
+
+NavigationItem* NavigationManagerImpl::GetPendingItem() const {
+ return [[session_controller_ pendingEntry] navigationItem];
+}
+
+NavigationItem* NavigationManagerImpl::GetTransientItem() const {
+ return [[session_controller_ transientEntry] navigationItem];
+}
+
+NavigationItem* NavigationManagerImpl::GetLastCommittedItem() const {
+ CRWSessionEntry* entry = [session_controller_ lastCommittedEntry];
+ return [entry navigationItem];
+}
+
+NavigationItem* NavigationManagerImpl::GetItemAtIndex(size_t index) const {
+ NSArray* entries = [session_controller_ entries];
+ return index < entries.count ? [entries[index] navigationItem] : nullptr;
+}
+
+int NavigationManagerImpl::GetCurrentEntryIndex() const {
+ return [session_controller_ currentNavigationIndex];
+}
+
+int NavigationManagerImpl::GetLastCommittedEntryIndex() const {
+ if (![[session_controller_ entries] count])
+ return -1;
+ return [session_controller_ currentNavigationIndex];
+}
+
+int NavigationManagerImpl::GetEntryCount() const {
+ return [[session_controller_ entries] count];
+}
+
+bool NavigationManagerImpl::RemoveEntryAtIndex(int index) {
+ if (index == GetLastCommittedEntryIndex() ||
+ index == GetPendingEntryIndex())
+ return false;
+
+ NSUInteger idx = static_cast<NSUInteger>(index);
+ NSArray* entries = [session_controller_ entries];
+ if (idx >= entries.count)
+ return false;
+
+ [session_controller_ removeEntryAtIndex:index];
+ return true;
+}
+
+void NavigationManagerImpl::DiscardNonCommittedEntries() {
+ [session_controller_ discardNonCommittedEntries];
+}
+
+NavigationItem* NavigationManagerImpl::GetLastUserItem() const {
+ CRWSessionEntry* entry = [session_controller_ lastUserEntry];
+ return [entry navigationItem];
+}
+
+NavigationItem* NavigationManagerImpl::GetPreviousItem() const {
+ CRWSessionEntry* entry = [session_controller_ previousEntry];
+ return [entry navigationItem];
+}
+
+int NavigationManagerImpl::GetPendingEntryIndex() const {
+ if ([session_controller_ hasPendingEntry])
+ return GetCurrentEntryIndex();
+ return -1;
+}
+
+void NavigationManagerImpl::LoadURL(const GURL& url,
+ const web::Referrer& referrer,
+ ui::PageTransition type) {
+ WebState::OpenURLParams params(url, referrer, CURRENT_TAB, type, NO);
+ delegate_->GetWebState()->OpenURL(params);
+}
+
+bool NavigationManagerImpl::CanGoBack() const {
+ return [session_controller_ canGoBack];
+}
+
+bool NavigationManagerImpl::CanGoForward() const {
+ return [session_controller_ canGoForward];
+}
+
+void NavigationManagerImpl::GoBack() {
+ if (CanGoBack()) {
+ [session_controller_ goBack];
+ // Signal the delegate to load the old page.
+ delegate_->NavigateToPendingEntry();
+ }
+}
+
+void NavigationManagerImpl::GoForward() {
+ if (CanGoForward()) {
+ [session_controller_ goForward];
+ // Signal the delegate to load the new page.
+ delegate_->NavigateToPendingEntry();
+ }
+}
+
+std::vector<NavigationItem*> NavigationManagerImpl::GetItems() {
+ std::vector<NavigationItem*> items;
+ size_t i = 0;
+ items.resize([session_controller_ entries].count);
+ for (CRWSessionEntry* entry in [session_controller_ entries]) {
+ items[i++] = entry.navigationItem;
+ }
+ return items;
+}
+
+BrowserState* NavigationManagerImpl::GetBrowserState() const {
+ return browser_state_;
+}
+
+WebState* NavigationManagerImpl::GetWebState() const {
+ return delegate_->GetWebState();
+}
+
+void NavigationManagerImpl::CopyState(
+ NavigationManagerImpl* navigation_manager) {
+ SetSessionController(
+ [[navigation_manager->GetSessionController() copy] autorelease]);
+}
+
+} // namespace web
+
diff --git a/ios/web/navigation/navigation_manager_impl_unittest.mm b/ios/web/navigation/navigation_manager_impl_unittest.mm
new file mode 100644
index 0000000..9460fb0
--- /dev/null
+++ b/ios/web/navigation/navigation_manager_impl_unittest.mm
@@ -0,0 +1,32 @@
+// 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 "base/logging.h"
+#include "ios/web/navigation/navigation_manager_impl.h"
+#include "ios/web/public/test/test_browser_state.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+
+namespace web {
+namespace {
+
+class NavigationManagerTest : public PlatformTest {
+ protected:
+ void SetUp() override {
+ manager_.reset(new NavigationManagerImpl(NULL, &browser_state_));
+ }
+ scoped_ptr<NavigationManagerImpl> manager_;
+ TestBrowserState browser_state_;
+};
+
+// TODO(stuartmorgan): Remove this once NavigationManager has actual
+// functionality to test, instead of just being pass-throughs.
+TEST_F(NavigationManagerTest, Dummy) {
+ EXPECT_FALSE(manager_->CanGoBack());
+ EXPECT_FALSE(manager_->CanGoForward());
+}
+
+} // namespace
+} // namespace web
diff --git a/ios/web/navigation/web_load_params.h b/ios/web/navigation/web_load_params.h
new file mode 100644
index 0000000..0fea4df
--- /dev/null
+++ b/ios/web/navigation/web_load_params.h
@@ -0,0 +1,60 @@
+// 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_WEB_LOAD_PARAMS_H_
+#define IOS_WEB_NAVIGATION_WEB_LOAD_PARAMS_H_
+
+#import <Foundation/Foundation.h>
+
+#import "base/mac/scoped_nsobject.h"
+#include "ios/net/request_tracker.h"
+#include "ios/web/public/referrer.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+namespace web {
+
+// Parameters for creating a tab. Most paramaters are optional, and
+// can be left at the default values set by the constructor.
+// TODO(stuartmorgan): Remove the dependency on RequestTracker, then move
+// this into NavigationManager to parallel NavigationController's
+// LoadURLParams (which this is modeled after).
+struct WebLoadParams {
+ public:
+ // The URL to load. Must be set.
+ GURL url;
+
+ // The referrer for the load. May be empty.
+ Referrer referrer;
+
+ // The transition type for the load. Defaults to PAGE_TRANSITION_LINK.
+ ui::PageTransition transition_type;
+
+ // True for renderer-initiated navigations. This is
+ // important for tracking whether to display pending URLs.
+ bool is_renderer_initiated;
+
+ // The cache mode to load with. Defaults to CACHE_NORMAL.
+ net::RequestTracker::CacheMode cache_mode;
+
+ // Any extra HTTP headers to add to the load.
+ base::scoped_nsobject<NSDictionary> extra_headers;
+
+ // Any post data to send with the load. When setting this, you should
+ // generally set a Content-Type header as well.
+ base::scoped_nsobject<NSData> post_data;
+
+ // Create a new WebLoadParams with the given URL and defaults for all other
+ // parameters.
+ explicit WebLoadParams(const GURL& url);
+ ~WebLoadParams();
+
+ // Allow copying WebLoadParams.
+ WebLoadParams(const WebLoadParams& other);
+ WebLoadParams& operator=(const WebLoadParams& other);
+};
+
+} // namespace web
+
+#endif // IOS_WEB_NAVIGATION_WEB_LOAD_PARAMS_H_
diff --git a/ios/web/navigation/web_load_params.mm b/ios/web/navigation/web_load_params.mm
new file mode 100644
index 0000000..2c5a82c
--- /dev/null
+++ b/ios/web/navigation/web_load_params.mm
@@ -0,0 +1,42 @@
+// 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/web_load_params.h"
+
+namespace web {
+
+WebLoadParams::WebLoadParams(const GURL& url)
+ : url(url),
+ transition_type(ui::PAGE_TRANSITION_LINK),
+ is_renderer_initiated(false),
+ cache_mode(net::RequestTracker::CACHE_NORMAL),
+ post_data(NULL) {
+}
+
+WebLoadParams::~WebLoadParams() {}
+
+WebLoadParams::WebLoadParams(const WebLoadParams& other)
+ : url(other.url),
+ referrer(other.referrer),
+ transition_type(other.transition_type),
+ is_renderer_initiated(other.is_renderer_initiated),
+ cache_mode(other.cache_mode),
+ extra_headers([other.extra_headers copy]),
+ post_data([other.post_data copy]) {
+}
+
+WebLoadParams& WebLoadParams::operator=(
+ const WebLoadParams& other) {
+ url = other.url;
+ referrer = other.referrer;
+ is_renderer_initiated = other.is_renderer_initiated;
+ transition_type = other.transition_type;
+ cache_mode = other.cache_mode;
+ extra_headers.reset([other.extra_headers copy]);
+ post_data.reset([other.post_data copy]);
+
+ return *this;
+}
+
+} // namespace web
diff --git a/ios/web/public/navigation_item.h b/ios/web/public/navigation_item.h
index a17a7e4..e067a82 100644
--- a/ios/web/public/navigation_item.h
+++ b/ios/web/public/navigation_item.h
@@ -8,6 +8,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/strings/string16.h"
#include "base/time/time.h"
+#include "ios/web/public/web_state/page_scroll_state.h"
#include "ui/base/page_transition_types.h"
class GURL;
@@ -68,6 +69,10 @@ class NavigationItem {
virtual void SetPageID(int page_id) = 0;
virtual int32 GetPageID() const = 0;
+ // Stores the NavigationItem's last recorded scroll offset and zoom scale.
+ virtual void SetPageScrollState(const PageScrollState& scroll_state) = 0;
+ virtual const PageScrollState& GetPageScrollState() const = 0;
+
// Page-related helpers ------------------------------------------------------
// Returns the title to be displayed on the tab. This could be the title of
@@ -104,6 +109,11 @@ class NavigationItem {
// - or this navigation was copied from a foreign session.
virtual void SetTimestamp(base::Time timestamp) = 0;
virtual base::Time GetTimestamp() const = 0;
+
+ // |true| if this item contains unsafe resources and will be removed. This
+ // property doesn't get serialized.
+ virtual void SetUnsafe(bool is_unsafe) = 0;
+ virtual bool IsUnsafe() const = 0;
};
} // namespace web
diff --git a/ios/web/public/web_state/page_scroll_state.h b/ios/web/public/web_state/page_scroll_state.h
new file mode 100644
index 0000000..9984019
--- /dev/null
+++ b/ios/web/public/web_state/page_scroll_state.h
@@ -0,0 +1,75 @@
+// 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_WEB_STATE_PAGE_SCROLL_STATE_H_
+#define IOS_WEB_PUBLIC_WEB_STATE_PAGE_SCROLL_STATE_H_
+
+namespace web {
+
+// Class used to represent the scrolling offset and the zoom scale of a webview.
+class PageScrollState {
+ public:
+ // Default constructor. Initializes scroll offsets and zoom scales to NAN.
+ PageScrollState();
+ // Constructor with initial values.
+ PageScrollState(double scroll_offset_x,
+ double scroll_offset_y,
+ double minimum_zoom_scale,
+ double maximum_zoom_scale,
+ double zoom_scale);
+ ~PageScrollState();
+
+ // PageScrollStates cannot be applied until the scroll offset and zoom scale
+ // are both valid.
+ bool IsValid() const;
+
+ // The scroll offset is valid if its x and y values are both non-NAN.
+ bool IsScrollOffsetValid() const;
+
+ // Non-legacy zoom scales are valid if all three values are non-NAN and the
+ // zoom scale is within the minimum and maximum scales. Legacy-format
+ // PageScrollStates are considered valid if the minimum and maximum scales
+ // are NAN and the zoom scale is greater than zero.
+ bool IsZoomScaleValid() const;
+
+ // PageScrollStates restored from the legacy serialization format make
+ // assumptions about the web view's implementation of zooming, and contain a
+ // non-NAN zoom scale and a NAN minimum and maximum scale. Legacy zoom scales
+ // can only be applied to CRWUIWebViewWebControllers.
+ bool IsZoomScaleLegacyFormat() const;
+
+ // Accessors for scroll offsets and zoom scale.
+ double scroll_offset_x() const { return scroll_offset_x_; }
+ void set_scroll_offset_x(double scroll_offset_x) {
+ scroll_offset_x_ = scroll_offset_x;
+ }
+ double scroll_offset_y() const { return scroll_offset_y_; }
+ void set_scroll_offset_y(double scroll_offset_y) {
+ scroll_offset_y_ = scroll_offset_y;
+ }
+ double minimum_zoom_scale() const { return minimum_zoom_scale_; }
+ void set_minimum_zoom_scale(double minimum_zoom_scale) {
+ minimum_zoom_scale_ = minimum_zoom_scale;
+ }
+ double maximum_zoom_scale() const { return maximum_zoom_scale_; }
+ void set_maximum_zoom_scale(double maximum_zoom_scale) {
+ maximum_zoom_scale_ = maximum_zoom_scale;
+ }
+ double zoom_scale() const { return zoom_scale_; }
+ void set_zoom_scale(double zoom_scale) { zoom_scale_ = zoom_scale; }
+
+ // Comparator operator.
+ bool operator==(const PageScrollState& other) const;
+
+ private:
+ double scroll_offset_x_;
+ double scroll_offset_y_;
+ double minimum_zoom_scale_;
+ double maximum_zoom_scale_;
+ double zoom_scale_;
+};
+
+} // namespace web
+
+#endif // IOS_WEB_PUBLIC_WEB_STATE_PAGE_SCROLL_STATE_H_
diff --git a/ios/web/public/web_state/page_scroll_state.mm b/ios/web/public/web_state/page_scroll_state.mm
new file mode 100644
index 0000000..b7479fd
--- /dev/null
+++ b/ios/web/public/web_state/page_scroll_state.mm
@@ -0,0 +1,72 @@
+// 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/public/web_state/page_scroll_state.h"
+
+#include <cmath>
+
+namespace web {
+
+namespace {
+// Returns true if:
+// - both |value1| and |value2| are NAN, or
+// - |value1| and |value2| are equal non-NAN values.
+inline bool StateValuesAreEqual(double value1, double value2) {
+ return std::isnan(value1) ? std::isnan(value2) : value1 == value2;
+}
+} // namespace
+
+PageScrollState::PageScrollState()
+ : scroll_offset_x_(NAN),
+ scroll_offset_y_(NAN),
+ minimum_zoom_scale_(NAN),
+ maximum_zoom_scale_(NAN),
+ zoom_scale_(NAN) {
+}
+
+PageScrollState::PageScrollState(double scroll_offset_x,
+ double scroll_offset_y,
+ double minimum_zoom_scale,
+ double maximum_zoom_scale,
+ double zoom_scale)
+ : scroll_offset_x_(scroll_offset_x),
+ scroll_offset_y_(scroll_offset_y),
+ minimum_zoom_scale_(minimum_zoom_scale),
+ maximum_zoom_scale_(maximum_zoom_scale),
+ zoom_scale_(zoom_scale) {
+}
+
+PageScrollState::~PageScrollState() {
+}
+
+bool PageScrollState::IsValid() const {
+ return IsScrollOffsetValid() && IsZoomScaleValid();
+}
+
+bool PageScrollState::IsScrollOffsetValid() const {
+ return !std::isnan(scroll_offset_x_) && !std::isnan(scroll_offset_y_);
+}
+
+bool PageScrollState::IsZoomScaleValid() const {
+ return IsZoomScaleLegacyFormat() ||
+ (!std::isnan(minimum_zoom_scale_) &&
+ !std::isnan(maximum_zoom_scale_) && !std::isnan(zoom_scale_) &&
+ zoom_scale_ >= minimum_zoom_scale_ &&
+ zoom_scale_ <= maximum_zoom_scale_);
+}
+
+bool PageScrollState::IsZoomScaleLegacyFormat() const {
+ return std::isnan(minimum_zoom_scale_) && std::isnan(maximum_zoom_scale_) &&
+ zoom_scale_ > 0.0;
+}
+
+bool PageScrollState::operator==(const PageScrollState& other) const {
+ return StateValuesAreEqual(scroll_offset_x_, other.scroll_offset_x_) &&
+ StateValuesAreEqual(scroll_offset_y_, other.scroll_offset_y_) &&
+ StateValuesAreEqual(minimum_zoom_scale_, other.minimum_zoom_scale_) &&
+ StateValuesAreEqual(maximum_zoom_scale_, other.maximum_zoom_scale_) &&
+ StateValuesAreEqual(zoom_scale_, other.zoom_scale_);
+}
+
+} // namespace web