LayoutTestPermissionManager: pass top level origin, not URL.
[chromium-blink-merge.git] / ios / web / navigation / crw_session_controller.mm
blob5407ec31b6afad6fcd83a2884a5ec82aa48949c1
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 // TODO(rohitrao): These properties must be redefined readwrite to work around a
107 // clang bug. crbug.com/228650
108 @property(nonatomic, readwrite, retain) NSString* tabId;
109 @property(nonatomic, readwrite, retain) NSArray* entries;
110 @property(nonatomic, readwrite, retain)
111     CRWSessionCertificatePolicyManager* sessionCertificatePolicyManager;
113 - (NSString*)uniqueID;
114 // Removes all entries after currentNavigationIndex_.
115 - (void)clearForwardEntries;
116 // Discards the transient entry, if any.
117 - (void)discardTransientEntry;
118 // Create a new autoreleased session entry.
119 - (CRWSessionEntry*)sessionEntryWithURL:(const GURL&)url
120                                referrer:(const web::Referrer&)referrer
121                              transition:(ui::PageTransition)transition
122                     useDesktopUserAgent:(BOOL)useDesktopUserAgent
123                       rendererInitiated:(BOOL)rendererInitiated;
124 // Return the PageTransition for the underlying navigationItem at |index| in
125 // |entries_|
126 - (ui::PageTransition)transitionForIndex:(NSUInteger)index;
127 @end
129 @implementation CRWSessionController
131 @synthesize tabId = _tabId;
132 @synthesize currentNavigationIndex = _currentNavigationIndex;
133 @synthesize previousNavigationIndex = _previousNavigationIndex;
134 @synthesize entries = _entries;
135 @synthesize windowName = _windowName;
136 @synthesize lastVisitedTimestamp = _lastVisitedTimestamp;
137 @synthesize openerId = _openerId;
138 @synthesize openedByDOM = _openedByDOM;
139 @synthesize openerNavigationIndex = _openerNavigationIndex;
140 @synthesize sessionCertificatePolicyManager = _sessionCertificatePolicyManager;
141 @synthesize xCallbackParameters = _xCallbackParameters;
143 - (id)initWithWindowName:(NSString*)windowName
144                 openerId:(NSString*)openerId
145              openedByDOM:(BOOL)openedByDOM
146    openerNavigationIndex:(NSInteger)openerIndex
147             browserState:(web::BrowserState*)browserState {
148   self = [super init];
149   if (self) {
150     _propertyReleaser_CRWSessionController.Init(self,
151                                                 [CRWSessionController class]);
152     self.windowName = windowName;
153     _tabId = [[self uniqueID] retain];
154     _openerId = [openerId copy];
155     _openedByDOM = openedByDOM;
156     _openerNavigationIndex = openerIndex;
157     _browserState = browserState;
158     _entries = [[NSMutableArray array] retain];
159     _lastVisitedTimestamp = [[NSDate date] timeIntervalSince1970];
160     _currentNavigationIndex = -1;
161     _previousNavigationIndex = -1;
162     _sessionCertificatePolicyManager =
163         [[CRWSessionCertificatePolicyManager alloc] init];
164   }
165   return self;
168 - (id)initWithNavigationItems:(ScopedVector<web::NavigationItem>)scoped_items
169                  currentIndex:(NSUInteger)currentIndex
170                  browserState:(web::BrowserState*)browserState {
171   self = [super init];
172   if (self) {
173     _propertyReleaser_CRWSessionController.Init(self,
174                                                 [CRWSessionController class]);
175     _tabId = [[self uniqueID] retain];
176     _openerId = nil;
177     _browserState = browserState;
179     // Create entries array from list of navigations.
180     _entries = [[NSMutableArray alloc] initWithCapacity:scoped_items.size()];
181     std::vector<web::NavigationItem*> items;
182     scoped_items.release(&items);
184     for (size_t i = 0; i < items.size(); ++i) {
185       scoped_ptr<web::NavigationItem> item(items[i]);
186       base::scoped_nsobject<CRWSessionEntry> entry(
187           [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass() index:i]);
188       [_entries addObject:entry];
189     }
190     _currentNavigationIndex = currentIndex;
191     // Prior to M34, 0 was used as "no index" instead of -1; adjust for that.
192     if (![_entries count])
193       _currentNavigationIndex = -1;
194     if (_currentNavigationIndex >= static_cast<NSInteger>(items.size())) {
195       _currentNavigationIndex = static_cast<NSInteger>(items.size()) - 1;
196     }
197     _previousNavigationIndex = -1;
198     _lastVisitedTimestamp = [[NSDate date] timeIntervalSince1970];
199     _sessionCertificatePolicyManager =
200         [[CRWSessionCertificatePolicyManager alloc] init];
201   }
202   return self;
205 - (id)initWithCoder:(NSCoder*)aDecoder {
206   self = [super init];
207   if (self) {
208     _propertyReleaser_CRWSessionController.Init(self,
209                                                 [CRWSessionController class]);
210     NSString* uuid = [aDecoder decodeObjectForKey:kTabIdKey];
211     if (!uuid)
212       uuid = [self uniqueID];
214     self.windowName = [aDecoder decodeObjectForKey:kWindowNameKey];
215     _tabId = [uuid retain];
216     _openerId = [[aDecoder decodeObjectForKey:kOpenerIdKey] copy];
217     _openedByDOM = [aDecoder decodeBoolForKey:kOpenedByDOMKey];
218     _openerNavigationIndex =
219         [aDecoder decodeIntForKey:kOpenerNavigationIndexKey];
220     _currentNavigationIndex =
221         [aDecoder decodeIntForKey:kCurrentNavigationIndexKey];
222     _previousNavigationIndex =
223         [aDecoder decodeIntForKey:kPreviousNavigationIndexKey];
224     _lastVisitedTimestamp =
225        [aDecoder decodeDoubleForKey:kLastVisitedTimestampKey];
226     NSMutableArray* temp =
227         [NSMutableArray arrayWithArray:
228             [aDecoder decodeObjectForKey:kEntriesKey]];
229     _entries = [temp retain];
230     // Prior to M34, 0 was used as "no index" instead of -1; adjust for that.
231     if (![_entries count])
232       _currentNavigationIndex = -1;
233     _sessionCertificatePolicyManager =
234        [[aDecoder decodeObjectForKey:kCertificatePolicyManagerKey] retain];
235     if (!_sessionCertificatePolicyManager) {
236       _sessionCertificatePolicyManager =
237           [[CRWSessionCertificatePolicyManager alloc] init];
238     }
240     _xCallbackParameters =
241         [[aDecoder decodeObjectForKey:kXCallbackParametersKey] retain];
242   }
243   return self;
246 - (void)encodeWithCoder:(NSCoder*)aCoder {
247   [aCoder encodeObject:_tabId forKey:kTabIdKey];
248   [aCoder encodeObject:_openerId forKey:kOpenerIdKey];
249   [aCoder encodeBool:_openedByDOM forKey:kOpenedByDOMKey];
250   [aCoder encodeInt:_openerNavigationIndex forKey:kOpenerNavigationIndexKey];
251   [aCoder encodeObject:_windowName forKey:kWindowNameKey];
252   [aCoder encodeInt:_currentNavigationIndex forKey:kCurrentNavigationIndexKey];
253   [aCoder encodeInt:_previousNavigationIndex
254              forKey:kPreviousNavigationIndexKey];
255   [aCoder encodeDouble:_lastVisitedTimestamp forKey:kLastVisitedTimestampKey];
256   [aCoder encodeObject:_entries forKey:kEntriesKey];
257   [aCoder encodeObject:_sessionCertificatePolicyManager
258                 forKey:kCertificatePolicyManagerKey];
259   [aCoder encodeObject:_xCallbackParameters forKey:kXCallbackParametersKey];
260   // rendererInitiated is deliberately not preserved, as upstream.
263 - (id)copyWithZone:(NSZone*)zone {
264   CRWSessionController* copy = [[[self class] alloc] init];
265   copy->_propertyReleaser_CRWSessionController.Init(
266       copy, [CRWSessionController class]);
267   copy->_tabId = [_tabId copy];
268   copy->_openerId = [_openerId copy];
269   copy->_openedByDOM = _openedByDOM;
270   copy->_openerNavigationIndex = _openerNavigationIndex;
271   copy.windowName = self.windowName;
272   copy->_currentNavigationIndex = _currentNavigationIndex;
273   copy->_previousNavigationIndex = _previousNavigationIndex;
274   copy->_lastVisitedTimestamp = _lastVisitedTimestamp;
275   copy->_entries = [_entries copy];
276   copy->_sessionCertificatePolicyManager =
277       [_sessionCertificatePolicyManager copy];
278   copy->_xCallbackParameters = [_xCallbackParameters copy];
279   return copy;
282 - (void)setNavigationManager:(web::NavigationManagerImpl*)navigationManager {
283   _navigationManager = navigationManager;
284   if (_navigationManager) {
285     // _browserState will be nullptr if CRWSessionController has been
286     // initialized with -initWithCoder: method. Take _browserState from
287     // NavigationManagerImpl if that's the case.
288     if (!_browserState) {
289       _browserState = _navigationManager->GetBrowserState();
290     }
291     DCHECK_EQ(_browserState, _navigationManager->GetBrowserState());
292   }
295 - (NSString*)description {
296   return [NSString
297       stringWithFormat:
298           @"id: %@\nname: %@\nlast visit: %f\ncurrent index: %" PRIdNS
299           @"\nprevious index: %" PRIdNS "\n%@\npending: %@\nxCallback:\n%@\n",
300           _tabId,
301           self.windowName,
302           _lastVisitedTimestamp,
303           _currentNavigationIndex,
304           _previousNavigationIndex,
305           _entries,
306           _pendingEntry.get(),
307           _xCallbackParameters];
310 // Returns the current entry in the session list, or the pending entry if there
311 // is a navigation in progress.
312 - (CRWSessionEntry*)currentEntry {
313   if (_transientEntry)
314     return _transientEntry.get();
315   if (_pendingEntry)
316     return _pendingEntry.get();
317   return [self lastCommittedEntry];
320 // See NavigationController::GetVisibleEntry for the motivation for this
321 // distinction.
322 - (CRWSessionEntry*)visibleEntry {
323   if (_transientEntry)
324     return _transientEntry.get();
325   // Only return the pending_entry for:
326   //   (a) new (non-history), browser-initiated navigations, and
327   //   (b) pending unsafe navigations (while showing the interstitial)
328   // in order to prevent URL spoof attacks.
329   web::NavigationItemImpl* pendingItemImpl =
330       static_cast<web::NavigationItemImpl*>([_pendingEntry navigationItem]);
331   if (_pendingEntry &&
332       (!pendingItemImpl->is_renderer_initiated() ||
333        pendingItemImpl->IsUnsafe())) {
334     return _pendingEntry.get();
335   }
336   return [self lastCommittedEntry];
339 - (CRWSessionEntry*)pendingEntry {
340   return _pendingEntry.get();
343 - (CRWSessionEntry*)transientEntry {
344   return _transientEntry.get();
347 - (CRWSessionEntry*)lastCommittedEntry {
348   if (_currentNavigationIndex == -1)
349     return nil;
350   return [_entries objectAtIndex:_currentNavigationIndex];
353 // Returns the previous entry in the session list, or nil if there isn't any.
354 - (CRWSessionEntry*)previousEntry {
355   if ((_previousNavigationIndex < 0) || (![_entries count]))
356     return nil;
357   return [_entries objectAtIndex:_previousNavigationIndex];
360 - (void)addPendingEntry:(const GURL&)url
361                referrer:(const web::Referrer&)ref
362              transition:(ui::PageTransition)trans
363       rendererInitiated:(BOOL)rendererInitiated {
364   [self discardTransientEntry];
366   // Don't create a new entry if it's already the same as the current entry,
367   // allowing this routine to be called multiple times in a row without issue.
368   // Note: CRWSessionController currently has the responsibility to distinguish
369   // between new navigations and history stack navigation, hence the inclusion
370   // of specific transiton type logic here, in order to make it reliable with
371   // real-world observed behavior.
372   // TODO(stuartmorgan): Fix the way changes are detected/reported elsewhere
373   // in the web layer so that this hack can be removed.
374   // Remove the workaround code from -presentSafeBrowsingWarningForResource:.
375   CRWSessionEntry* currentEntry = self.currentEntry;
376   if (currentEntry) {
377     // If the current entry is known-unsafe (and thus not visible and likely to
378     // be removed), ignore any renderer-initated updates and don't worry about
379     // sending a notification.
380     web::NavigationItem* item = [currentEntry navigationItem];
381     if (item->IsUnsafe() && rendererInitiated) {
382       return;
383     }
384     if (item->GetURL() == url &&
385         (!PageTransitionCoreTypeIs(trans, ui::PAGE_TRANSITION_FORM_SUBMIT) ||
386          PageTransitionCoreTypeIs(item->GetTransitionType(),
387                                   ui::PAGE_TRANSITION_FORM_SUBMIT) ||
388          item->IsUnsafe())) {
389       // Send the notification anyway, to preserve old behavior. It's unknown
390       // whether anything currently relies on this, but since both this whole
391       // hack and the content facade will both be going away, it's not worth
392       // trying to unwind.
393       if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
394         _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
395       }
396       return;
397     }
398   }
400   BOOL useDesktopUserAgent = _useDesktopUserAgentForNextPendingEntry ||
401       self.currentEntry.useDesktopUserAgent;
402   _useDesktopUserAgentForNextPendingEntry = NO;
403   _pendingEntry.reset([[self sessionEntryWithURL:url
404                                         referrer:ref
405                                       transition:trans
406                              useDesktopUserAgent:useDesktopUserAgent
407                                rendererInitiated:rendererInitiated] retain]);
409   if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
410     _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
411   }
414 - (void)updatePendingEntry:(const GURL&)url {
415   [self discardTransientEntry];
417   // If there is no pending entry, navigation is probably happening within the
418   // session history. Don't modify the entry list.
419   if (!_pendingEntry)
420     return;
421   web::NavigationItem* item = [_pendingEntry navigationItem];
422   if (url != item->GetURL()) {
423     item->SetURL(url);
424     item->SetVirtualURL(url);
425     // Since updates are caused by page redirects, they are renderer-initiated.
426     web::NavigationItemImpl* pendingItemImpl =
427         static_cast<web::NavigationItemImpl*>([_pendingEntry navigationItem]);
428     pendingItemImpl->set_is_renderer_initiated(true);
429     // Redirects (3xx response code), or client side navigation must change
430     // POST requests to GETs.
431     [_pendingEntry setPOSTData:nil];
432     [_pendingEntry resetHTTPHeaders];
433   }
435   // This should probably not be sent if the URLs matched, but that's what was
436   // done before, so preserve behavior in case something relies on it.
437   if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
438     _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
439   }
442 - (void)clearForwardEntries {
443   [self discardTransientEntry];
445   NSInteger forwardEntryStartIndex = _currentNavigationIndex + 1;
446   DCHECK(forwardEntryStartIndex >= 0);
448   if (forwardEntryStartIndex >= static_cast<NSInteger>([_entries count]))
449     return;
451   NSRange remove = NSMakeRange(forwardEntryStartIndex,
452                                [_entries count] - forwardEntryStartIndex);
453   // Store removed items in temporary NSArray so they can be deallocated after
454   // their facades.
455   base::scoped_nsobject<NSArray> removedItems(
456       [[_entries subarrayWithRange:remove] retain]);
457   [_entries removeObjectsInRange:remove];
458   if (_previousNavigationIndex >= forwardEntryStartIndex)
459     _previousNavigationIndex = -1;
460   if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
461     _navigationManager->GetFacadeDelegate()->OnNavigationItemsPruned(
462         remove.length);
463   }
466 - (void)commitPendingEntry {
467   if (_pendingEntry) {
468     [self clearForwardEntries];
469     // Add the new entry at the end.
470     [_entries addObject:_pendingEntry];
471     _previousNavigationIndex = _currentNavigationIndex;
472     _currentNavigationIndex = [_entries count] - 1;
473     // Once an entry is committed it's not renderer-initiated any more. (Matches
474     // the implementation in NavigationController.)
475     web::NavigationItemImpl* pendingItemImpl =
476         static_cast<web::NavigationItemImpl*>([_pendingEntry navigationItem]);
477     pendingItemImpl->ResetForCommit();
478     _pendingEntry.reset();
479   }
481   CRWSessionEntry* currentEntry = self.currentEntry;
482   web::NavigationItem* item = currentEntry.navigationItem;
483   // Update the navigation timestamp now that it's actually happened.
484   if (item)
485     item->SetTimestamp(_timeSmoother.GetSmoothedTime(base::Time::Now()));
487   if (_navigationManager && item)
488     _navigationManager->OnNavigationItemCommitted();
491 - (void)addTransientEntry:(const GURL&)url
492                     title:(const base::string16&)title
493                 sslStatus:(const web::SSLStatus*)status {
494   // TODO(stuartmorgan): Don't do this; this is here only to preserve the old
495   // behavior from when transient entries were faked with pending entries, so
496   // any actual pending entry had to be committed. This shouldn't be necessary
497   // now, but things may rely on the old behavior and need to be fixed.
498   [self commitPendingEntry];
500   _transientEntry.reset(
501       [[self sessionEntryWithURL:url
502                         referrer:web::Referrer()
503                       transition:ui::PAGE_TRANSITION_CLIENT_REDIRECT
504              useDesktopUserAgent:NO
505                rendererInitiated:NO] retain]);
507   web::NavigationItem* navigationItem = [_transientEntry navigationItem];
508   DCHECK(navigationItem);
509   if (status)
510     navigationItem->GetSSL() = *status;
511   navigationItem->SetTitle(title);
512   navigationItem->SetTimestamp(
513       _timeSmoother.GetSmoothedTime(base::Time::Now()));
515   // This doesn't match upstream, but matches what we've traditionally done and
516   // will hopefully continue to be good enough for as long as we need the
517   // facade.
518   if (_navigationManager)
519     _navigationManager->OnNavigationItemChanged();
522 - (void)pushNewEntryWithURL:(const GURL&)url
523                 stateObject:(NSString*)stateObject {
524   DCHECK([self currentEntry]);
525   web::NavigationItem* item = [self currentEntry].navigationItem;
526   CHECK(
527       web::history_state_util::IsHistoryStateChangeValid(item->GetURL(), url));
528   web::Referrer referrer(item->GetURL(), web::ReferrerPolicyDefault);
529   base::scoped_nsobject<CRWSessionEntry> pushedEntry(
530       [[self sessionEntryWithURL:url
531                         referrer:referrer
532                       transition:ui::PAGE_TRANSITION_LINK
533              useDesktopUserAgent:self.currentEntry.useDesktopUserAgent
534                rendererInitiated:NO] retain]);
535   pushedEntry.get().serializedStateObject = stateObject;
536   pushedEntry.get().createdFromPushState = YES;
537   web::SSLStatus& sslStatus = [self currentEntry].navigationItem->GetSSL();
538   pushedEntry.get().navigationItem->GetSSL() = sslStatus;
540   [self clearForwardEntries];
541   // Add the new entry at the end.
542   [_entries addObject:pushedEntry];
543   _previousNavigationIndex = _currentNavigationIndex;
544   _currentNavigationIndex = [_entries count] - 1;
546   if (_navigationManager)
547     _navigationManager->OnNavigationItemCommitted();
550 - (void)updateCurrentEntryWithURL:(const GURL&)url
551                       stateObject:(NSString*)stateObject {
552   DCHECK(!_transientEntry);
553   CRWSessionEntry* currentEntry = self.currentEntry;
554   currentEntry.navigationItem->SetURL(url);
555   currentEntry.serializedStateObject = stateObject;
556   // If the change is to a committed entry, notify interested parties.
557   if (currentEntry != self.pendingEntry && _navigationManager)
558     _navigationManager->OnNavigationItemChanged();
561 - (void)discardNonCommittedEntries {
562   [self discardTransientEntry];
563   _pendingEntry.reset();
566 - (void)discardTransientEntry {
567   // Keep the entry alive temporarily. There are flows that get the current
568   // entry, do some navigation operation, and then try to use that old current
569   // entry; since navigations clear the transient entry, these flows might
570   // crash. (This should be removable once more session management is handled
571   // within this class and/or NavigationManager).
572   [[_transientEntry retain] autorelease];
573   _transientEntry.reset();
576 - (BOOL)hasPendingEntry {
577   return _pendingEntry != nil;
580 - (void)copyStateFromAndPrune:(CRWSessionController*)otherSession
581                  replaceState:(BOOL)replaceState {
582   DCHECK(otherSession);
583   if (replaceState) {
584     [_entries removeAllObjects];
585     _currentNavigationIndex = -1;
586     _previousNavigationIndex = -1;
587   }
588   self.xCallbackParameters =
589       [[otherSession.xCallbackParameters copy] autorelease];
590   self.windowName = otherSession.windowName;
591   NSInteger numInitialEntries = [_entries count];
593   // Cycle through the entries from the other session and insert them before any
594   // entries from this session.  Do not copy anything that comes after the other
595   // session's current entry unless replaceState is true.
596   NSArray* otherEntries = [otherSession entries];
598   // The other session may not have any entries, in which case there is nothing
599   // to copy or prune.  The other session's currentNavigationEntry will be bogus
600   // in such cases, so ignore it and return early.
601   // TODO(rohitrao): Do we need to copy over any pending entries?  We might not
602   // add the prerendered page into the back/forward history if we don't copy
603   // pending entries.
604   if (![otherEntries count])
605     return;
607   NSInteger maxCopyIndex = replaceState ? [otherEntries count] - 1 :
608                                           [otherSession currentNavigationIndex];
609   for (NSInteger i = 0; i <= maxCopyIndex; ++i) {
610     [_entries insertObject:[otherEntries objectAtIndex:i] atIndex:i];
611     ++_currentNavigationIndex;
612     _previousNavigationIndex = -1;
613   }
615   // If this CRWSessionController has no entries initially, reset
616   // |currentNavigationIndex_| to be in bounds.
617   if (!numInitialEntries) {
618     if (replaceState) {
619       _currentNavigationIndex = [otherSession currentNavigationIndex];
620       _previousNavigationIndex = [otherSession previousNavigationIndex];
621     } else {
622       _currentNavigationIndex = maxCopyIndex;
623     }
624   }
625   DCHECK_LT((NSUInteger)_currentNavigationIndex, [_entries count]);
628 - (ui::PageTransition)transitionForIndex:(NSUInteger)index {
629   return [[_entries objectAtIndex:index] navigationItem]->GetTransitionType();
632 - (BOOL)canGoBack {
633   if ([_entries count] == 0)
634     return NO;
636   NSInteger lastNonRedirectedIndex = _currentNavigationIndex;
637   while (lastNonRedirectedIndex >= 0 &&
638          ui::PageTransitionIsRedirect(
639             [self transitionForIndex:lastNonRedirectedIndex])) {
640     --lastNonRedirectedIndex;
641   }
643   return lastNonRedirectedIndex > 0;
646 - (BOOL)canGoForward {
647   // In case there are pending entries return no since when the entry will be
648   // committed the history will be cleared from that point forward.
649   if (_pendingEntry)
650     return NO;
651   // If the current index is less than the last element, there are entries to
652   // go forward to.
653   const NSInteger count = [_entries count];
654   return count && _currentNavigationIndex < (count - 1);
657 - (void)goBack {
658   if (![self canGoBack])
659     return;
661   [self discardTransientEntry];
663   web::RecordAction(UserMetricsAction("Back"));
664   _previousNavigationIndex = _currentNavigationIndex;
665   // To stop the user getting 'stuck' on redirecting pages they weren't even
666   // aware existed, it is necessary to pass over pages that would immediately
667   // result in a redirect (the entry *before* the redirected page).
668   while (_currentNavigationIndex &&
669          [self transitionForIndex:_currentNavigationIndex] &
670              ui::PAGE_TRANSITION_IS_REDIRECT_MASK) {
671     --_currentNavigationIndex;
672   }
674   if (_currentNavigationIndex)
675     --_currentNavigationIndex;
678 - (void)goForward {
679   [self discardTransientEntry];
681   web::RecordAction(UserMetricsAction("Forward"));
682   if (_currentNavigationIndex + 1 < static_cast<NSInteger>([_entries count])) {
683     _previousNavigationIndex = _currentNavigationIndex;
684     ++_currentNavigationIndex;
685   }
686   // To reduce the chance of a redirect kicking in (truncating the history
687   // stack) we skip over any pages that might do this; we detect this by
688   // looking for when the *next* page had rediection transition type (was
689   // auto redirected to).
690   while (_currentNavigationIndex + 1 <
691          (static_cast<NSInteger>([_entries count])) &&
692          ([self transitionForIndex:_currentNavigationIndex + 1] &
693           ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
694     ++_currentNavigationIndex;
695   }
698 - (void)goDelta:(int)delta {
699   if (delta < 0) {
700     while ([self canGoBack] && delta < 0) {
701       [self goBack];
702       ++delta;
703     }
704   } else {
705     while ([self canGoForward] && delta > 0) {
706       [self goForward];
707       --delta;
708     }
709   }
712 - (void)goToEntry:(CRWSessionEntry*)entry {
713   DCHECK(entry);
715   [self discardTransientEntry];
717   // Check that |entries_| still contains |entry|. |entry| could have been
718   // removed by -clearForwardEntries.
719   if ([_entries containsObject:entry])
720     _currentNavigationIndex = [_entries indexOfObject:entry];
723 - (void)removeEntryAtIndex:(NSInteger)index {
724   DCHECK(index < static_cast<NSInteger>([_entries count]));
725   DCHECK(index != _currentNavigationIndex);
726   DCHECK(index >= 0);
728   [self discardNonCommittedEntries];
730   [_entries removeObjectAtIndex:index];
731   if (_currentNavigationIndex > index)
732     _currentNavigationIndex--;
733   if (_previousNavigationIndex >= index)
734     _previousNavigationIndex--;
737 - (NSArray*)backwardEntries {
738   NSMutableArray* entries = [NSMutableArray array];
739   NSInteger lastNonRedirectedIndex = _currentNavigationIndex;
740   while (lastNonRedirectedIndex >= 0) {
741     CRWSessionEntry* entry = [_entries objectAtIndex:lastNonRedirectedIndex];
742     if (!ui::PageTransitionIsRedirect(
743             entry.navigationItem->GetTransitionType())) {
744       [entries addObject:entry];
745     }
746     --lastNonRedirectedIndex;
747   }
748   // Remove the currently displayed entry.
749   [entries removeObjectAtIndex:0];
750   return entries;
753 - (NSArray*)forwardEntries {
754   NSMutableArray* entries = [NSMutableArray array];
755   NSUInteger lastNonRedirectedIndex = _currentNavigationIndex + 1;
756   while (lastNonRedirectedIndex < [_entries count]) {
757     CRWSessionEntry* entry = [_entries objectAtIndex:lastNonRedirectedIndex];
758     if (!ui::PageTransitionIsRedirect(
759             entry.navigationItem->GetTransitionType())) {
760       [entries addObject:entry];
761     }
762     ++lastNonRedirectedIndex;
763   }
764   return entries;
767 - (std::vector<GURL>)currentRedirectedUrls {
768   std::vector<GURL> results;
769   if (_pendingEntry) {
770     web::NavigationItem* item = [_pendingEntry navigationItem];
771     results.push_back(item->GetURL());
773     if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
774       return results;
775   }
777   if (![_entries count])
778     return results;
780   NSInteger index = _currentNavigationIndex;
781   // Add urls in the redirected entries.
782   while (index >= 0) {
783     web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
784     if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
785       break;
786     results.push_back(item->GetURL());
787     --index;
788   }
789   // Add the last non-redirected entry.
790   if (index >= 0) {
791     web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
792     results.push_back(item->GetURL());
793   }
794   return results;
797 - (BOOL)isPushStateNavigationBetweenEntry:(CRWSessionEntry*)firstEntry
798                                  andEntry:(CRWSessionEntry*)secondEntry {
799   DCHECK(firstEntry);
800   DCHECK(secondEntry);
801   if (firstEntry == secondEntry)
802     return NO;
803   NSUInteger firstIndex = [_entries indexOfObject:firstEntry];
804   NSUInteger secondIndex = [_entries indexOfObject:secondEntry];
805   if (firstIndex == NSNotFound || secondIndex == NSNotFound)
806     return NO;
807   NSUInteger startIndex = firstIndex < secondIndex ? firstIndex : secondIndex;
808   NSUInteger endIndex = firstIndex < secondIndex ? secondIndex : firstIndex;
810   for (NSUInteger i = startIndex + 1; i <= endIndex; i++) {
811     CRWSessionEntry* entry = [_entries objectAtIndex:i];
812     // Every entry in the sequence has to be created from a pushState() call.
813     if (!entry.createdFromPushState)
814       return NO;
815     // Every entry in the sequence has to have a URL that could have been
816     // created from a pushState() call.
817     if (!web::history_state_util::IsHistoryStateChangeValid(
818             firstEntry.navigationItem->GetURL(),
819             entry.navigationItem->GetURL()))
820       return NO;
821   }
822   return YES;
825 - (CRWSessionEntry*)lastUserEntry {
826   if (![_entries count])
827     return nil;
829   NSInteger index = _currentNavigationIndex;
830   // This will return the first session entry if all other entries are
831   // redirects, regardless of the transition state of the first entry.
832   while (index > 0 &&
833          [self transitionForIndex:index] &
834          ui::PAGE_TRANSITION_IS_REDIRECT_MASK) {
835     --index;
836   }
837   return [_entries objectAtIndex:index];
840 - (void)useDesktopUserAgentForNextPendingEntry {
841   if (_pendingEntry)
842     [_pendingEntry setUseDesktopUserAgent:YES];
843   else
844     _useDesktopUserAgentForNextPendingEntry = YES;
847 #pragma mark -
848 #pragma mark Private methods
850 - (NSString*)uniqueID {
851   CFUUIDRef uuidRef = CFUUIDCreate(NULL);
852   CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
853   CFRelease(uuidRef);
854   NSString* uuid = [NSString stringWithString:(NSString*)uuidStringRef];
855   CFRelease(uuidStringRef);
856   return uuid;
859 - (CRWSessionEntry*)sessionEntryWithURL:(const GURL&)url
860                                referrer:(const web::Referrer&)referrer
861                              transition:(ui::PageTransition)transition
862                     useDesktopUserAgent:(BOOL)useDesktopUserAgent
863                       rendererInitiated:(BOOL)rendererInitiated {
864   GURL loaded_url(url);
865   web::BrowserURLRewriter::GetInstance()->RewriteURLIfNecessary(&loaded_url,
866                                                                 _browserState);
867   return [[[CRWSessionEntry alloc] initWithUrl:loaded_url
868                                       referrer:referrer
869                                     transition:transition
870                            useDesktopUserAgent:useDesktopUserAgent
871                              rendererInitiated:rendererInitiated] autorelease];
874 @end