Index: ios/web/navigation/crw_session_controller.mm |
diff --git a/ios/web/navigation/crw_session_controller.mm b/ios/web/navigation/crw_session_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5407ec31b6afad6fcd83a2884a5ec82aa48949c1 |
--- /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 |