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"
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;
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
59 NSInteger _currentNavigationIndex;
60 // Identifies the index of the previous navigation in the CRWSessionEntry
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
126 - (ui::PageTransition)transitionForIndex:(NSUInteger)index;
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 {
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];
168 - (id)initWithNavigationItems:(ScopedVector<web::NavigationItem>)scoped_items
169 currentIndex:(NSUInteger)currentIndex
170 browserState:(web::BrowserState*)browserState {
173 _propertyReleaser_CRWSessionController.Init(self,
174 [CRWSessionController class]);
175 _tabId = [[self uniqueID] retain];
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];
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;
197 _previousNavigationIndex = -1;
198 _lastVisitedTimestamp = [[NSDate date] timeIntervalSince1970];
199 _sessionCertificatePolicyManager =
200 [[CRWSessionCertificatePolicyManager alloc] init];
205 - (id)initWithCoder:(NSCoder*)aDecoder {
208 _propertyReleaser_CRWSessionController.Init(self,
209 [CRWSessionController class]);
210 NSString* uuid = [aDecoder decodeObjectForKey:kTabIdKey];
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];
240 _xCallbackParameters =
241 [[aDecoder decodeObjectForKey:kXCallbackParametersKey] retain];
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];
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();
291 DCHECK_EQ(_browserState, _navigationManager->GetBrowserState());
295 - (NSString*)description {
298 @"id: %@\nname: %@\nlast visit: %f\ncurrent index: %" PRIdNS
299 @"\nprevious index: %" PRIdNS "\n%@\npending: %@\nxCallback:\n%@\n",
302 _lastVisitedTimestamp,
303 _currentNavigationIndex,
304 _previousNavigationIndex,
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 {
314 return _transientEntry.get();
316 return _pendingEntry.get();
317 return [self lastCommittedEntry];
320 // See NavigationController::GetVisibleEntry for the motivation for this
322 - (CRWSessionEntry*)visibleEntry {
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]);
332 (!pendingItemImpl->is_renderer_initiated() ||
333 pendingItemImpl->IsUnsafe())) {
334 return _pendingEntry.get();
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)
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]))
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;
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) {
384 if (item->GetURL() == url &&
385 (!PageTransitionCoreTypeIs(trans, ui::PAGE_TRANSITION_FORM_SUBMIT) ||
386 PageTransitionCoreTypeIs(item->GetTransitionType(),
387 ui::PAGE_TRANSITION_FORM_SUBMIT) ||
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
393 if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
394 _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
400 BOOL useDesktopUserAgent = _useDesktopUserAgentForNextPendingEntry ||
401 self.currentEntry.useDesktopUserAgent;
402 _useDesktopUserAgentForNextPendingEntry = NO;
403 _pendingEntry.reset([[self sessionEntryWithURL:url
406 useDesktopUserAgent:useDesktopUserAgent
407 rendererInitiated:rendererInitiated] retain]);
409 if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
410 _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
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.
421 web::NavigationItem* item = [_pendingEntry navigationItem];
422 if (url != item->GetURL()) {
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];
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();
442 - (void)clearForwardEntries {
443 [self discardTransientEntry];
445 NSInteger forwardEntryStartIndex = _currentNavigationIndex + 1;
446 DCHECK(forwardEntryStartIndex >= 0);
448 if (forwardEntryStartIndex >= static_cast<NSInteger>([_entries count]))
451 NSRange remove = NSMakeRange(forwardEntryStartIndex,
452 [_entries count] - forwardEntryStartIndex);
453 // Store removed items in temporary NSArray so they can be deallocated after
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(
466 - (void)commitPendingEntry {
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();
481 CRWSessionEntry* currentEntry = self.currentEntry;
482 web::NavigationItem* item = currentEntry.navigationItem;
483 // Update the navigation timestamp now that it's actually happened.
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);
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
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;
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
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);
584 [_entries removeAllObjects];
585 _currentNavigationIndex = -1;
586 _previousNavigationIndex = -1;
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
604 if (![otherEntries count])
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;
615 // If this CRWSessionController has no entries initially, reset
616 // |currentNavigationIndex_| to be in bounds.
617 if (!numInitialEntries) {
619 _currentNavigationIndex = [otherSession currentNavigationIndex];
620 _previousNavigationIndex = [otherSession previousNavigationIndex];
622 _currentNavigationIndex = maxCopyIndex;
625 DCHECK_LT((NSUInteger)_currentNavigationIndex, [_entries count]);
628 - (ui::PageTransition)transitionForIndex:(NSUInteger)index {
629 return [[_entries objectAtIndex:index] navigationItem]->GetTransitionType();
633 if ([_entries count] == 0)
636 NSInteger lastNonRedirectedIndex = _currentNavigationIndex;
637 while (lastNonRedirectedIndex >= 0 &&
638 ui::PageTransitionIsRedirect(
639 [self transitionForIndex:lastNonRedirectedIndex])) {
640 --lastNonRedirectedIndex;
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.
651 // If the current index is less than the last element, there are entries to
653 const NSInteger count = [_entries count];
654 return count && _currentNavigationIndex < (count - 1);
658 if (![self canGoBack])
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;
674 if (_currentNavigationIndex)
675 --_currentNavigationIndex;
679 [self discardTransientEntry];
681 web::RecordAction(UserMetricsAction("Forward"));
682 if (_currentNavigationIndex + 1 < static_cast<NSInteger>([_entries count])) {
683 _previousNavigationIndex = _currentNavigationIndex;
684 ++_currentNavigationIndex;
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;
698 - (void)goDelta:(int)delta {
700 while ([self canGoBack] && delta < 0) {
705 while ([self canGoForward] && delta > 0) {
712 - (void)goToEntry:(CRWSessionEntry*)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);
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];
746 --lastNonRedirectedIndex;
748 // Remove the currently displayed entry.
749 [entries removeObjectAtIndex:0];
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];
762 ++lastNonRedirectedIndex;
767 - (std::vector<GURL>)currentRedirectedUrls {
768 std::vector<GURL> results;
770 web::NavigationItem* item = [_pendingEntry navigationItem];
771 results.push_back(item->GetURL());
773 if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
777 if (![_entries count])
780 NSInteger index = _currentNavigationIndex;
781 // Add urls in the redirected entries.
783 web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
784 if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
786 results.push_back(item->GetURL());
789 // Add the last non-redirected entry.
791 web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
792 results.push_back(item->GetURL());
797 - (BOOL)isPushStateNavigationBetweenEntry:(CRWSessionEntry*)firstEntry
798 andEntry:(CRWSessionEntry*)secondEntry {
801 if (firstEntry == secondEntry)
803 NSUInteger firstIndex = [_entries indexOfObject:firstEntry];
804 NSUInteger secondIndex = [_entries indexOfObject:secondEntry];
805 if (firstIndex == NSNotFound || secondIndex == NSNotFound)
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)
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()))
825 - (CRWSessionEntry*)lastUserEntry {
826 if (![_entries count])
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.
833 [self transitionForIndex:index] &
834 ui::PAGE_TRANSITION_IS_REDIRECT_MASK) {
837 return [_entries objectAtIndex:index];
840 - (void)useDesktopUserAgentForNextPendingEntry {
842 [_pendingEntry setUseDesktopUserAgent:YES];
844 _useDesktopUserAgentForNextPendingEntry = YES;
848 #pragma mark Private methods
850 - (NSString*)uniqueID {
851 CFUUIDRef uuidRef = CFUUIDCreate(NULL);
852 CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
854 NSString* uuid = [NSString stringWithString:(NSString*)uuidStringRef];
855 CFRelease(uuidStringRef);
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,
867 return [[[CRWSessionEntry alloc] initWithUrl:loaded_url
869 transition:transition
870 useDesktopUserAgent:useDesktopUserAgent
871 rendererInitiated:rendererInitiated] autorelease];