net: Remove code for session cookie deletion experiment.
[chromium-blink-merge.git] / ios / web / navigation / crw_session_controller.mm
blobf5c1c3c39729450bae55985a5c4a830797cf44d2
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ios/web/navigation/crw_session_controller.h"
7 #include <algorithm>
8 #include <vector>
10 #include "base/format_macros.h"
11 #include "base/logging.h"
12 #include "base/mac/objc_property_releaser.h"
13 #import "base/mac/scoped_nsobject.h"
14 #include "base/metrics/user_metrics_action.h"
15 #include "base/strings/sys_string_conversions.h"
16 #import "ios/web/history_state_util.h"
17 #import "ios/web/navigation/crw_session_certificate_policy_manager.h"
18 #import "ios/web/navigation/crw_session_controller+private_constructors.h"
19 #import "ios/web/navigation/crw_session_entry.h"
20 #include "ios/web/navigation/navigation_item_impl.h"
21 #import "ios/web/navigation/navigation_manager_facade_delegate.h"
22 #import "ios/web/navigation/navigation_manager_impl.h"
23 #include "ios/web/navigation/time_smoother.h"
24 #include "ios/web/public/browser_state.h"
25 #include "ios/web/public/browser_url_rewriter.h"
26 #include "ios/web/public/referrer.h"
27 #include "ios/web/public/ssl_status.h"
28 #include "ios/web/public/user_metrics.h"
30 using base::UserMetricsAction;
32 namespace {
33 NSString* const kCertificatePolicyManagerKey = @"certificatePolicyManager";
34 NSString* const kCurrentNavigationIndexKey = @"currentNavigationIndex";
35 NSString* const kEntriesKey = @"entries";
36 NSString* const kLastVisitedTimestampKey = @"lastVisitedTimestamp";
37 NSString* const kOpenerIdKey = @"openerId";
38 NSString* const kOpenedByDOMKey = @"openedByDOM";
39 NSString* const kOpenerNavigationIndexKey = @"openerNavigationIndex";
40 NSString* const kPreviousNavigationIndexKey = @"previousNavigationIndex";
41 NSString* const kTabIdKey = @"tabId";
42 NSString* const kWindowNameKey = @"windowName";
43 NSString* const kXCallbackParametersKey = @"xCallbackParameters";
44 }  // anonymous namespace
46 @interface CRWSessionController () {
47   // Weak pointer back to the owning NavigationManager. This is to facilitate
48   // the incremental merging of the two classes.
49   web::NavigationManagerImpl* _navigationManager;
51   NSString* _tabId;  // Unique id of the tab.
52   NSString* _openerId;  // Id of tab who opened this tab, empty/nil if none.
53   // Navigation index of the tab which opened this tab. Do not rely on the
54   // value of this member variable to indicate whether or not this tab has
55   // an opener, as both 0 and -1 are used as navigationIndex values.
56   NSInteger _openerNavigationIndex;
57   // Identifies the index of the current navigation in the CRWSessionEntry
58   // array.
59   NSInteger _currentNavigationIndex;
60   // Identifies the index of the previous navigation in the CRWSessionEntry
61   // array.
62   NSInteger _previousNavigationIndex;
63   // Ordered array of |CRWSessionEntry| objects, one for each site in session
64   // history. End of the list is the most recent load.
65   NSMutableArray* _entries;
67   // An entry we haven't gotten a response for yet. This will be discarded
68   // when we navigate again. It's used only so we know what the currently
69   // displayed tab is.  It backs the property of the same name and should only
70   // be set through its setter.
71   base::scoped_nsobject<CRWSessionEntry> _pendingEntry;
73   // The transient entry, if any. A transient entry is discarded on any
74   // navigation, and is used for representing interstitials that need to be
75   // represented in the session.  It backs the property of the same name and
76   // should only be set through its setter.
77   base::scoped_nsobject<CRWSessionEntry> _transientEntry;
79   // The window name associated with the session.
80   NSString* _windowName;
82    // Stores the certificate policies decided by the user.
83   CRWSessionCertificatePolicyManager* _sessionCertificatePolicyManager;
85   // The timestamp of the last time this tab is visited, represented in time
86   // interval since 1970.
87   NSTimeInterval _lastVisitedTimestamp;
89   // If |YES|, override |currentEntry.useDesktopUserAgent| and create the
90   // pending entry using the desktop user agent.
91   BOOL _useDesktopUserAgentForNextPendingEntry;
93   // The browser state associated with this CRWSessionController;
94   __weak web::BrowserState* _browserState;
96   // Time smoother for navigation entry timestamps; see comment in
97   // navigation_controller_impl.h
98   web::TimeSmoother _timeSmoother;
100   // XCallback parameters used to create (or clobber) the tab. Can be nil.
101   XCallbackParameters* _xCallbackParameters;
103   base::mac::ObjCPropertyReleaser _propertyReleaser_CRWSessionController;
106 // Redefine as readwrite.
107 @property(nonatomic, readwrite, assign) NSInteger currentNavigationIndex;
109 // TODO(rohitrao): These properties must be redefined readwrite to work around a
110 // clang bug. crbug.com/228650
111 @property(nonatomic, readwrite, retain) NSString* tabId;
112 @property(nonatomic, readwrite, retain) NSArray* entries;
113 @property(nonatomic, readwrite, retain)
114     CRWSessionCertificatePolicyManager* sessionCertificatePolicyManager;
116 - (NSString*)uniqueID;
117 // Removes all entries after currentNavigationIndex_.
118 - (void)clearForwardEntries;
119 // Discards the transient entry, if any.
120 - (void)discardTransientEntry;
121 // Create a new autoreleased session entry.
122 - (CRWSessionEntry*)sessionEntryWithURL:(const GURL&)url
123                                referrer:(const web::Referrer&)referrer
124                              transition:(ui::PageTransition)transition
125                     useDesktopUserAgent:(BOOL)useDesktopUserAgent
126                       rendererInitiated:(BOOL)rendererInitiated;
127 // Return the PageTransition for the underlying navigationItem at |index| in
128 // |entries_|
129 - (ui::PageTransition)transitionForIndex:(NSUInteger)index;
130 @end
132 @implementation CRWSessionController
134 @synthesize tabId = _tabId;
135 @synthesize currentNavigationIndex = _currentNavigationIndex;
136 @synthesize previousNavigationIndex = _previousNavigationIndex;
137 @synthesize entries = _entries;
138 @synthesize windowName = _windowName;
139 @synthesize lastVisitedTimestamp = _lastVisitedTimestamp;
140 @synthesize openerId = _openerId;
141 @synthesize openedByDOM = _openedByDOM;
142 @synthesize openerNavigationIndex = _openerNavigationIndex;
143 @synthesize sessionCertificatePolicyManager = _sessionCertificatePolicyManager;
144 @synthesize xCallbackParameters = _xCallbackParameters;
146 - (id)initWithWindowName:(NSString*)windowName
147                 openerId:(NSString*)openerId
148              openedByDOM:(BOOL)openedByDOM
149    openerNavigationIndex:(NSInteger)openerIndex
150             browserState:(web::BrowserState*)browserState {
151   self = [super init];
152   if (self) {
153     _propertyReleaser_CRWSessionController.Init(self,
154                                                 [CRWSessionController class]);
155     self.windowName = windowName;
156     _tabId = [[self uniqueID] retain];
157     _openerId = [openerId copy];
158     _openedByDOM = openedByDOM;
159     _openerNavigationIndex = openerIndex;
160     _browserState = browserState;
161     _entries = [[NSMutableArray array] retain];
162     _lastVisitedTimestamp = [[NSDate date] timeIntervalSince1970];
163     _currentNavigationIndex = -1;
164     _previousNavigationIndex = -1;
165     _sessionCertificatePolicyManager =
166         [[CRWSessionCertificatePolicyManager alloc] init];
167   }
168   return self;
171 - (id)initWithNavigationItems:(ScopedVector<web::NavigationItem>)scoped_items
172                  currentIndex:(NSUInteger)currentIndex
173                  browserState:(web::BrowserState*)browserState {
174   self = [super init];
175   if (self) {
176     _propertyReleaser_CRWSessionController.Init(self,
177                                                 [CRWSessionController class]);
178     _tabId = [[self uniqueID] retain];
179     _openerId = nil;
180     _browserState = browserState;
182     // Create entries array from list of navigations.
183     _entries = [[NSMutableArray alloc] initWithCapacity:scoped_items.size()];
184     std::vector<web::NavigationItem*> items;
185     scoped_items.release(&items);
187     for (size_t i = 0; i < items.size(); ++i) {
188       scoped_ptr<web::NavigationItem> item(items[i]);
189       base::scoped_nsobject<CRWSessionEntry> entry(
190           [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass()]);
191       [_entries addObject:entry];
192     }
193     self.currentNavigationIndex = currentIndex;
194     // Prior to M34, 0 was used as "no index" instead of -1; adjust for that.
195     if (![_entries count])
196       self.currentNavigationIndex = -1;
197     if (_currentNavigationIndex >= static_cast<NSInteger>(items.size())) {
198       self.currentNavigationIndex = static_cast<NSInteger>(items.size()) - 1;
199     }
200     _previousNavigationIndex = -1;
201     _lastVisitedTimestamp = [[NSDate date] timeIntervalSince1970];
202     _sessionCertificatePolicyManager =
203         [[CRWSessionCertificatePolicyManager alloc] init];
204   }
205   return self;
208 - (id)initWithCoder:(NSCoder*)aDecoder {
209   self = [super init];
210   if (self) {
211     _propertyReleaser_CRWSessionController.Init(self,
212                                                 [CRWSessionController class]);
213     NSString* uuid = [aDecoder decodeObjectForKey:kTabIdKey];
214     if (!uuid)
215       uuid = [self uniqueID];
217     self.windowName = [aDecoder decodeObjectForKey:kWindowNameKey];
218     _tabId = [uuid retain];
219     _openerId = [[aDecoder decodeObjectForKey:kOpenerIdKey] copy];
220     _openedByDOM = [aDecoder decodeBoolForKey:kOpenedByDOMKey];
221     _openerNavigationIndex =
222         [aDecoder decodeIntForKey:kOpenerNavigationIndexKey];
223     _currentNavigationIndex =
224         [aDecoder decodeIntForKey:kCurrentNavigationIndexKey];
225     _previousNavigationIndex =
226         [aDecoder decodeIntForKey:kPreviousNavigationIndexKey];
227     _lastVisitedTimestamp =
228        [aDecoder decodeDoubleForKey:kLastVisitedTimestampKey];
229     NSMutableArray* temp =
230         [NSMutableArray arrayWithArray:
231             [aDecoder decodeObjectForKey:kEntriesKey]];
232     _entries = [temp retain];
233     // Prior to M34, 0 was used as "no index" instead of -1; adjust for that.
234     if (![_entries count])
235       _currentNavigationIndex = -1;
236     _sessionCertificatePolicyManager =
237        [[aDecoder decodeObjectForKey:kCertificatePolicyManagerKey] retain];
238     if (!_sessionCertificatePolicyManager) {
239       _sessionCertificatePolicyManager =
240           [[CRWSessionCertificatePolicyManager alloc] init];
241     }
243     _xCallbackParameters =
244         [[aDecoder decodeObjectForKey:kXCallbackParametersKey] retain];
245   }
246   return self;
249 - (void)encodeWithCoder:(NSCoder*)aCoder {
250   [aCoder encodeObject:_tabId forKey:kTabIdKey];
251   [aCoder encodeObject:_openerId forKey:kOpenerIdKey];
252   [aCoder encodeBool:_openedByDOM forKey:kOpenedByDOMKey];
253   [aCoder encodeInt:_openerNavigationIndex forKey:kOpenerNavigationIndexKey];
254   [aCoder encodeObject:_windowName forKey:kWindowNameKey];
255   [aCoder encodeInt:_currentNavigationIndex forKey:kCurrentNavigationIndexKey];
256   [aCoder encodeInt:_previousNavigationIndex
257              forKey:kPreviousNavigationIndexKey];
258   [aCoder encodeDouble:_lastVisitedTimestamp forKey:kLastVisitedTimestampKey];
259   [aCoder encodeObject:_entries forKey:kEntriesKey];
260   [aCoder encodeObject:_sessionCertificatePolicyManager
261                 forKey:kCertificatePolicyManagerKey];
262   [aCoder encodeObject:_xCallbackParameters forKey:kXCallbackParametersKey];
263   // rendererInitiated is deliberately not preserved, as upstream.
266 - (id)copyWithZone:(NSZone*)zone {
267   CRWSessionController* copy = [[[self class] alloc] init];
268   copy->_propertyReleaser_CRWSessionController.Init(
269       copy, [CRWSessionController class]);
270   copy->_tabId = [_tabId copy];
271   copy->_openerId = [_openerId copy];
272   copy->_openedByDOM = _openedByDOM;
273   copy->_openerNavigationIndex = _openerNavigationIndex;
274   copy.windowName = self.windowName;
275   copy->_currentNavigationIndex = _currentNavigationIndex;
276   copy->_previousNavigationIndex = _previousNavigationIndex;
277   copy->_lastVisitedTimestamp = _lastVisitedTimestamp;
278   copy->_entries =
279       [[NSMutableArray alloc] initWithArray:_entries copyItems:YES];
280   copy->_sessionCertificatePolicyManager =
281       [_sessionCertificatePolicyManager copy];
282   copy->_xCallbackParameters = [_xCallbackParameters copy];
283   return copy;
286 - (void)setCurrentNavigationIndex:(NSInteger)currentNavigationIndex {
287   if (_currentNavigationIndex != currentNavigationIndex) {
288     _currentNavigationIndex = currentNavigationIndex;
289     if (_navigationManager)
290       _navigationManager->RemoveTransientURLRewriters();
291   }
294 - (void)setNavigationManager:(web::NavigationManagerImpl*)navigationManager {
295   _navigationManager = navigationManager;
296   if (_navigationManager) {
297     // _browserState will be nullptr if CRWSessionController has been
298     // initialized with -initWithCoder: method. Take _browserState from
299     // NavigationManagerImpl if that's the case.
300     if (!_browserState) {
301       _browserState = _navigationManager->GetBrowserState();
302     }
303     DCHECK_EQ(_browserState, _navigationManager->GetBrowserState());
304   }
307 - (NSString*)description {
308   return [NSString
309       stringWithFormat:
310           @"id: %@\nname: %@\nlast visit: %f\ncurrent index: %" PRIdNS
311           @"\nprevious index: %" PRIdNS "\n%@\npending: %@\nxCallback:\n%@\n",
312           _tabId,
313           self.windowName,
314           _lastVisitedTimestamp,
315           _currentNavigationIndex,
316           _previousNavigationIndex,
317           _entries,
318           _pendingEntry.get(),
319           _xCallbackParameters];
322 // Returns the current entry in the session list, or the pending entry if there
323 // is a navigation in progress.
324 - (CRWSessionEntry*)currentEntry {
325   if (_transientEntry)
326     return _transientEntry.get();
327   if (_pendingEntry)
328     return _pendingEntry.get();
329   return [self lastCommittedEntry];
332 // See NavigationController::GetVisibleEntry for the motivation for this
333 // distinction.
334 - (CRWSessionEntry*)visibleEntry {
335   if (_transientEntry)
336     return _transientEntry.get();
337   // Only return the pending_entry for:
338   //   (a) new (non-history), browser-initiated navigations, and
339   //   (b) pending unsafe navigations (while showing the interstitial)
340   // in order to prevent URL spoof attacks.
341   web::NavigationItemImpl* pendingItem = [_pendingEntry navigationItemImpl];
342   if (pendingItem &&
343       (!pendingItem->is_renderer_initiated() || pendingItem->IsUnsafe())) {
344     return _pendingEntry.get();
345   }
346   return [self lastCommittedEntry];
349 - (CRWSessionEntry*)pendingEntry {
350   return _pendingEntry.get();
353 - (CRWSessionEntry*)transientEntry {
354   return _transientEntry.get();
357 - (CRWSessionEntry*)lastCommittedEntry {
358   if (_currentNavigationIndex == -1)
359     return nil;
360   return [_entries objectAtIndex:_currentNavigationIndex];
363 // Returns the previous entry in the session list, or nil if there isn't any.
364 - (CRWSessionEntry*)previousEntry {
365   if ((_previousNavigationIndex < 0) || (![_entries count]))
366     return nil;
367   return [_entries objectAtIndex:_previousNavigationIndex];
370 - (void)addPendingEntry:(const GURL&)url
371                referrer:(const web::Referrer&)ref
372              transition:(ui::PageTransition)trans
373       rendererInitiated:(BOOL)rendererInitiated {
374   [self discardTransientEntry];
376   // Don't create a new entry if it's already the same as the current entry,
377   // allowing this routine to be called multiple times in a row without issue.
378   // Note: CRWSessionController currently has the responsibility to distinguish
379   // between new navigations and history stack navigation, hence the inclusion
380   // of specific transiton type logic here, in order to make it reliable with
381   // real-world observed behavior.
382   // TODO(stuartmorgan): Fix the way changes are detected/reported elsewhere
383   // in the web layer so that this hack can be removed.
384   // Remove the workaround code from -presentSafeBrowsingWarningForResource:.
385   CRWSessionEntry* currentEntry = self.currentEntry;
386   if (currentEntry) {
387     // If the current entry is known-unsafe (and thus not visible and likely to
388     // be removed), ignore any renderer-initated updates and don't worry about
389     // sending a notification.
390     web::NavigationItem* item = [currentEntry navigationItem];
391     if (item->IsUnsafe() && rendererInitiated) {
392       return;
393     }
394     if (item->GetURL() == url &&
395         (!PageTransitionCoreTypeIs(trans, ui::PAGE_TRANSITION_FORM_SUBMIT) ||
396          PageTransitionCoreTypeIs(item->GetTransitionType(),
397                                   ui::PAGE_TRANSITION_FORM_SUBMIT) ||
398          item->IsUnsafe())) {
399       // Send the notification anyway, to preserve old behavior. It's unknown
400       // whether anything currently relies on this, but since both this whole
401       // hack and the content facade will both be going away, it's not worth
402       // trying to unwind.
403       if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
404         _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
405       }
406       return;
407     }
408   }
410   BOOL useDesktopUserAgent =
411       _useDesktopUserAgentForNextPendingEntry ||
412       (self.currentEntry.navigationItem &&
413        self.currentEntry.navigationItem->IsOverridingUserAgent());
414   _useDesktopUserAgentForNextPendingEntry = NO;
415   _pendingEntry.reset([[self sessionEntryWithURL:url
416                                         referrer:ref
417                                       transition:trans
418                              useDesktopUserAgent:useDesktopUserAgent
419                                rendererInitiated:rendererInitiated] retain]);
421   if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
422     _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
423   }
426 - (void)updatePendingEntry:(const GURL&)url {
427   [self discardTransientEntry];
429   // If there is no pending entry, navigation is probably happening within the
430   // session history. Don't modify the entry list.
431   if (!_pendingEntry)
432     return;
434   web::NavigationItemImpl* item = [_pendingEntry navigationItemImpl];
435   if (url != item->GetURL()) {
436     item->SetURL(url);
437     item->SetVirtualURL(url);
438     // Redirects (3xx response code), or client side navigation must change
439     // POST requests to GETs.
440     item->SetPostData(nil);
441     item->ResetHttpRequestHeaders();
442   }
444   // This should probably not be sent if the URLs matched, but that's what was
445   // done before, so preserve behavior in case something relies on it.
446   if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
447     _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
448   }
451 - (void)clearForwardEntries {
452   [self discardTransientEntry];
454   NSInteger forwardEntryStartIndex = _currentNavigationIndex + 1;
455   DCHECK(forwardEntryStartIndex >= 0);
457   if (forwardEntryStartIndex >= static_cast<NSInteger>([_entries count]))
458     return;
460   NSRange remove = NSMakeRange(forwardEntryStartIndex,
461                                [_entries count] - forwardEntryStartIndex);
462   // Store removed items in temporary NSArray so they can be deallocated after
463   // their facades.
464   base::scoped_nsobject<NSArray> removedItems(
465       [[_entries subarrayWithRange:remove] retain]);
466   [_entries removeObjectsInRange:remove];
467   if (_previousNavigationIndex >= forwardEntryStartIndex)
468     _previousNavigationIndex = -1;
469   if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
470     _navigationManager->GetFacadeDelegate()->OnNavigationItemsPruned(
471         remove.length);
472   }
475 - (void)commitPendingEntry {
476   if (_pendingEntry) {
477     [self clearForwardEntries];
478     // Add the new entry at the end.
479     [_entries addObject:_pendingEntry];
480     _previousNavigationIndex = _currentNavigationIndex;
481     self.currentNavigationIndex = [_entries count] - 1;
482     // Once an entry is committed it's not renderer-initiated any more. (Matches
483     // the implementation in NavigationController.)
484     [_pendingEntry navigationItemImpl]->ResetForCommit();
485     _pendingEntry.reset();
486   }
488   CRWSessionEntry* currentEntry = self.currentEntry;
489   web::NavigationItem* item = currentEntry.navigationItem;
490   // Update the navigation timestamp now that it's actually happened.
491   if (item)
492     item->SetTimestamp(_timeSmoother.GetSmoothedTime(base::Time::Now()));
494   if (_navigationManager && item)
495     _navigationManager->OnNavigationItemCommitted();
498 - (void)addTransientEntry:(const GURL&)url
499                     title:(const base::string16&)title
500                 sslStatus:(const web::SSLStatus*)status {
501   // TODO(stuartmorgan): Don't do this; this is here only to preserve the old
502   // behavior from when transient entries were faked with pending entries, so
503   // any actual pending entry had to be committed. This shouldn't be necessary
504   // now, but things may rely on the old behavior and need to be fixed
505   // (crbug.com/524491).
506   [self commitPendingEntry];
508   _transientEntry.reset(
509       [[self sessionEntryWithURL:url
510                         referrer:web::Referrer()
511                       transition:ui::PAGE_TRANSITION_CLIENT_REDIRECT
512              useDesktopUserAgent:NO
513                rendererInitiated:NO] retain]);
515   web::NavigationItem* navigationItem = [_transientEntry navigationItem];
516   DCHECK(navigationItem);
517   if (status)
518     navigationItem->GetSSL() = *status;
519   navigationItem->SetTitle(title);
520   navigationItem->SetTimestamp(
521       _timeSmoother.GetSmoothedTime(base::Time::Now()));
523   // This doesn't match upstream, but matches what we've traditionally done and
524   // will hopefully continue to be good enough for as long as we need the
525   // facade.
526   if (_navigationManager)
527     _navigationManager->OnNavigationItemChanged();
530 - (void)pushNewEntryWithURL:(const GURL&)URL
531                 stateObject:(NSString*)stateObject
532                  transition:(ui::PageTransition)transition {
533   DCHECK([self currentEntry]);
534   web::NavigationItem* item = [self currentEntry].navigationItem;
535   CHECK(
536       web::history_state_util::IsHistoryStateChangeValid(item->GetURL(), URL));
537   web::Referrer referrer(item->GetURL(), web::ReferrerPolicyDefault);
538   bool overrideUserAgent =
539       self.currentEntry.navigationItem->IsOverridingUserAgent();
540   base::scoped_nsobject<CRWSessionEntry> pushedEntry(
541       [[self sessionEntryWithURL:URL
542                         referrer:referrer
543                       transition:transition
544              useDesktopUserAgent:overrideUserAgent
545                rendererInitiated:NO] retain]);
546   web::NavigationItemImpl* pushedItem = [pushedEntry navigationItemImpl];
547   pushedItem->SetSerializedStateObject(stateObject);
548   pushedItem->SetIsCreatedFromPushState(true);
549   web::SSLStatus& sslStatus = [self currentEntry].navigationItem->GetSSL();
550   pushedEntry.get().navigationItem->GetSSL() = sslStatus;
552   [self clearForwardEntries];
553   // Add the new entry at the end.
554   [_entries addObject:pushedEntry];
555   _previousNavigationIndex = _currentNavigationIndex;
556   self.currentNavigationIndex = [_entries count] - 1;
558   if (_navigationManager)
559     _navigationManager->OnNavigationItemCommitted();
562 - (void)updateCurrentEntryWithURL:(const GURL&)url
563                       stateObject:(NSString*)stateObject {
564   DCHECK(!_transientEntry);
565   CRWSessionEntry* currentEntry = self.currentEntry;
566   web::NavigationItemImpl* currentItem = self.currentEntry.navigationItemImpl;
567   currentItem->SetURL(url);
568   currentItem->SetSerializedStateObject(stateObject);
569   currentEntry.navigationItem->SetURL(url);
570   // If the change is to a committed entry, notify interested parties.
571   if (currentEntry != self.pendingEntry && _navigationManager)
572     _navigationManager->OnNavigationItemChanged();
575 - (void)discardNonCommittedEntries {
576   [self discardTransientEntry];
577   _pendingEntry.reset();
580 - (void)discardTransientEntry {
581   // Keep the entry alive temporarily. There are flows that get the current
582   // entry, do some navigation operation, and then try to use that old current
583   // entry; since navigations clear the transient entry, these flows might
584   // crash. (This should be removable once more session management is handled
585   // within this class and/or NavigationManager).
586   [[_transientEntry retain] autorelease];
587   _transientEntry.reset();
590 - (BOOL)hasPendingEntry {
591   return _pendingEntry != nil;
594 - (void)copyStateFromAndPrune:(CRWSessionController*)otherSession
595                  replaceState:(BOOL)replaceState {
596   DCHECK(otherSession);
597   if (replaceState) {
598     [_entries removeAllObjects];
599     self.currentNavigationIndex = -1;
600     _previousNavigationIndex = -1;
601   }
602   self.xCallbackParameters =
603       [[otherSession.xCallbackParameters copy] autorelease];
604   self.windowName = otherSession.windowName;
605   NSInteger numInitialEntries = [_entries count];
607   // Cycle through the entries from the other session and insert them before any
608   // entries from this session.  Do not copy anything that comes after the other
609   // session's current entry unless replaceState is true.
610   NSArray* otherEntries = [otherSession entries];
612   // The other session may not have any entries, in which case there is nothing
613   // to copy or prune.  The other session's currentNavigationEntry will be bogus
614   // in such cases, so ignore it and return early.
615   // TODO(rohitrao): Do we need to copy over any pending entries?  We might not
616   // add the prerendered page into the back/forward history if we don't copy
617   // pending entries.
618   if (![otherEntries count])
619     return;
621   NSInteger maxCopyIndex = replaceState ? [otherEntries count] - 1 :
622                                           [otherSession currentNavigationIndex];
623   for (NSInteger i = 0; i <= maxCopyIndex; ++i) {
624     [_entries insertObject:[otherEntries objectAtIndex:i] atIndex:i];
625     ++_currentNavigationIndex;
626     _previousNavigationIndex = -1;
627   }
629   // If this CRWSessionController has no entries initially, reset
630   // |currentNavigationIndex_| to be in bounds.
631   if (!numInitialEntries) {
632     if (replaceState) {
633       self.currentNavigationIndex = [otherSession currentNavigationIndex];
634       _previousNavigationIndex = [otherSession previousNavigationIndex];
635     } else {
636       self.currentNavigationIndex = maxCopyIndex;
637     }
638   }
639   DCHECK_LT((NSUInteger)_currentNavigationIndex, [_entries count]);
642 - (ui::PageTransition)transitionForIndex:(NSUInteger)index {
643   return [[_entries objectAtIndex:index] navigationItem]->GetTransitionType();
646 - (BOOL)canGoBack {
647   if ([_entries count] == 0)
648     return NO;
650   NSInteger lastNonRedirectedIndex = _currentNavigationIndex;
651   while (lastNonRedirectedIndex >= 0 &&
652          ui::PageTransitionIsRedirect(
653             [self transitionForIndex:lastNonRedirectedIndex])) {
654     --lastNonRedirectedIndex;
655   }
657   return lastNonRedirectedIndex > 0;
660 - (BOOL)canGoForward {
661   // In case there are pending entries return no since when the entry will be
662   // committed the history will be cleared from that point forward.
663   if (_pendingEntry)
664     return NO;
665   // If the current index is less than the last element, there are entries to
666   // go forward to.
667   const NSInteger count = [_entries count];
668   return count && _currentNavigationIndex < (count - 1);
671 - (void)goBack {
672   if (![self canGoBack])
673     return;
675   [self discardTransientEntry];
677   web::RecordAction(UserMetricsAction("Back"));
678   _previousNavigationIndex = _currentNavigationIndex;
679   // To stop the user getting 'stuck' on redirecting pages they weren't even
680   // aware existed, it is necessary to pass over pages that would immediately
681   // result in a redirect (the entry *before* the redirected page).
682   while (_currentNavigationIndex &&
683          [self transitionForIndex:_currentNavigationIndex] &
684              ui::PAGE_TRANSITION_IS_REDIRECT_MASK) {
685     --_currentNavigationIndex;
686   }
688   if (_currentNavigationIndex)
689     --_currentNavigationIndex;
692 - (void)goForward {
693   [self discardTransientEntry];
695   web::RecordAction(UserMetricsAction("Forward"));
696   if (_currentNavigationIndex + 1 < static_cast<NSInteger>([_entries count])) {
697     _previousNavigationIndex = _currentNavigationIndex;
698     ++_currentNavigationIndex;
699   }
700   // To reduce the chance of a redirect kicking in (truncating the history
701   // stack) we skip over any pages that might do this; we detect this by
702   // looking for when the *next* page had rediection transition type (was
703   // auto redirected to).
704   while (_currentNavigationIndex + 1 <
705          (static_cast<NSInteger>([_entries count])) &&
706          ([self transitionForIndex:_currentNavigationIndex + 1] &
707           ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
708     ++_currentNavigationIndex;
709   }
712 - (void)goDelta:(int)delta {
713   if (delta < 0) {
714     while ([self canGoBack] && delta < 0) {
715       [self goBack];
716       ++delta;
717     }
718   } else {
719     while ([self canGoForward] && delta > 0) {
720       [self goForward];
721       --delta;
722     }
723   }
726 - (void)goToEntry:(CRWSessionEntry*)entry {
727   DCHECK(entry);
729   [self discardTransientEntry];
731   // Check that |entries_| still contains |entry|. |entry| could have been
732   // removed by -clearForwardEntries.
733   if ([_entries containsObject:entry])
734     self.currentNavigationIndex = [_entries indexOfObject:entry];
737 - (void)removeEntryAtIndex:(NSInteger)index {
738   DCHECK(index < static_cast<NSInteger>([_entries count]));
739   DCHECK(index != _currentNavigationIndex);
740   DCHECK(index >= 0);
742   [self discardNonCommittedEntries];
744   [_entries removeObjectAtIndex:index];
745   if (_currentNavigationIndex > index)
746     _currentNavigationIndex--;
747   if (_previousNavigationIndex >= index)
748     _previousNavigationIndex--;
751 - (NSArray*)backwardEntries {
752   NSMutableArray* entries = [NSMutableArray array];
753   NSInteger lastNonRedirectedIndex = _currentNavigationIndex;
754   while (lastNonRedirectedIndex >= 0) {
755     CRWSessionEntry* entry = [_entries objectAtIndex:lastNonRedirectedIndex];
756     if (!ui::PageTransitionIsRedirect(
757             entry.navigationItem->GetTransitionType())) {
758       [entries addObject:entry];
759     }
760     --lastNonRedirectedIndex;
761   }
762   // Remove the currently displayed entry.
763   [entries removeObjectAtIndex:0];
764   return entries;
767 - (NSArray*)forwardEntries {
768   NSMutableArray* entries = [NSMutableArray array];
769   NSUInteger lastNonRedirectedIndex = _currentNavigationIndex + 1;
770   while (lastNonRedirectedIndex < [_entries count]) {
771     CRWSessionEntry* entry = [_entries objectAtIndex:lastNonRedirectedIndex];
772     if (!ui::PageTransitionIsRedirect(
773             entry.navigationItem->GetTransitionType())) {
774       [entries addObject:entry];
775     }
776     ++lastNonRedirectedIndex;
777   }
778   return entries;
781 - (std::vector<GURL>)currentRedirectedUrls {
782   std::vector<GURL> results;
783   if (_pendingEntry) {
784     web::NavigationItem* item = [_pendingEntry navigationItem];
785     results.push_back(item->GetURL());
787     if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
788       return results;
789   }
791   if (![_entries count])
792     return results;
794   NSInteger index = _currentNavigationIndex;
795   // Add urls in the redirected entries.
796   while (index >= 0) {
797     web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
798     if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
799       break;
800     results.push_back(item->GetURL());
801     --index;
802   }
803   // Add the last non-redirected entry.
804   if (index >= 0) {
805     web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
806     results.push_back(item->GetURL());
807   }
808   return results;
811 - (BOOL)isPushStateNavigationBetweenEntry:(CRWSessionEntry*)firstEntry
812                                  andEntry:(CRWSessionEntry*)secondEntry {
813   DCHECK(firstEntry);
814   DCHECK(secondEntry);
815   if (firstEntry == secondEntry)
816     return NO;
817   NSUInteger firstIndex = [_entries indexOfObject:firstEntry];
818   NSUInteger secondIndex = [_entries indexOfObject:secondEntry];
819   if (firstIndex == NSNotFound || secondIndex == NSNotFound)
820     return NO;
821   NSUInteger startIndex = firstIndex < secondIndex ? firstIndex : secondIndex;
822   NSUInteger endIndex = firstIndex < secondIndex ? secondIndex : firstIndex;
824   for (NSUInteger i = startIndex + 1; i <= endIndex; i++) {
825     web::NavigationItemImpl* item = [_entries[i] navigationItemImpl];
826     // Every entry in the sequence has to be created from a pushState() call.
827     if (!item->IsCreatedFromPushState())
828       return NO;
829     // Every entry in the sequence has to have a URL that could have been
830     // created from a pushState() call.
831     if (!web::history_state_util::IsHistoryStateChangeValid(
832             firstEntry.navigationItem->GetURL(), item->GetURL()))
833       return NO;
834   }
835   return YES;
838 - (CRWSessionEntry*)lastUserEntry {
839   if (![_entries count])
840     return nil;
842   NSInteger index = _currentNavigationIndex;
843   // This will return the first session entry if all other entries are
844   // redirects, regardless of the transition state of the first entry.
845   while (index > 0 &&
846          [self transitionForIndex:index] &
847          ui::PAGE_TRANSITION_IS_REDIRECT_MASK) {
848     --index;
849   }
850   return [_entries objectAtIndex:index];
853 - (void)useDesktopUserAgentForNextPendingEntry {
854   if (_pendingEntry)
855     [_pendingEntry navigationItem]->SetIsOverridingUserAgent(true);
856   else
857     _useDesktopUserAgentForNextPendingEntry = YES;
860 #pragma mark -
861 #pragma mark Private methods
863 - (NSString*)uniqueID {
864   CFUUIDRef uuidRef = CFUUIDCreate(NULL);
865   CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
866   CFRelease(uuidRef);
867   NSString* uuid = [NSString stringWithString:(NSString*)uuidStringRef];
868   CFRelease(uuidStringRef);
869   return uuid;
872 - (CRWSessionEntry*)sessionEntryWithURL:(const GURL&)url
873                                referrer:(const web::Referrer&)referrer
874                              transition:(ui::PageTransition)transition
875                     useDesktopUserAgent:(BOOL)useDesktopUserAgent
876                       rendererInitiated:(BOOL)rendererInitiated {
877   GURL loaded_url(url);
878   BOOL urlWasRewritten = NO;
879   if (_navigationManager) {
880     scoped_ptr<std::vector<web::BrowserURLRewriter::URLRewriter>>
881         transientRewriters = _navigationManager->GetTransientURLRewriters();
882     if (transientRewriters) {
883       urlWasRewritten = web::BrowserURLRewriter::RewriteURLWithWriters(
884           &loaded_url, _browserState, *transientRewriters.get());
885     }
886   }
887   if (!urlWasRewritten) {
888     web::BrowserURLRewriter::GetInstance()->RewriteURLIfNecessary(
889         &loaded_url, _browserState);
890   }
891   scoped_ptr<web::NavigationItemImpl> item(new web::NavigationItemImpl());
892   item->SetURL(loaded_url);
893   item->SetReferrer(referrer);
894   item->SetTransitionType(transition);
895   item->SetIsOverridingUserAgent(useDesktopUserAgent);
896   item->set_is_renderer_initiated(rendererInitiated);
897   return [
898       [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass()] autorelease];
901 @end