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 // 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
129 - (ui::PageTransition)transitionForIndex:(NSUInteger)index;
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 {
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];
171 - (id)initWithNavigationItems:(ScopedVector<web::NavigationItem>)scoped_items
172 currentIndex:(NSUInteger)currentIndex
173 browserState:(web::BrowserState*)browserState {
176 _propertyReleaser_CRWSessionController.Init(self,
177 [CRWSessionController class]);
178 _tabId = [[self uniqueID] retain];
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];
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;
200 _previousNavigationIndex = -1;
201 _lastVisitedTimestamp = [[NSDate date] timeIntervalSince1970];
202 _sessionCertificatePolicyManager =
203 [[CRWSessionCertificatePolicyManager alloc] init];
208 - (id)initWithCoder:(NSCoder*)aDecoder {
211 _propertyReleaser_CRWSessionController.Init(self,
212 [CRWSessionController class]);
213 NSString* uuid = [aDecoder decodeObjectForKey:kTabIdKey];
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];
243 _xCallbackParameters =
244 [[aDecoder decodeObjectForKey:kXCallbackParametersKey] retain];
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;
279 [[NSMutableArray alloc] initWithArray:_entries copyItems:YES];
280 copy->_sessionCertificatePolicyManager =
281 [_sessionCertificatePolicyManager copy];
282 copy->_xCallbackParameters = [_xCallbackParameters copy];
286 - (void)setCurrentNavigationIndex:(NSInteger)currentNavigationIndex {
287 if (_currentNavigationIndex != currentNavigationIndex) {
288 _currentNavigationIndex = currentNavigationIndex;
289 if (_navigationManager)
290 _navigationManager->RemoveTransientURLRewriters();
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();
303 DCHECK_EQ(_browserState, _navigationManager->GetBrowserState());
307 - (NSString*)description {
310 @"id: %@\nname: %@\nlast visit: %f\ncurrent index: %" PRIdNS
311 @"\nprevious index: %" PRIdNS "\n%@\npending: %@\nxCallback:\n%@\n",
314 _lastVisitedTimestamp,
315 _currentNavigationIndex,
316 _previousNavigationIndex,
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 {
326 return _transientEntry.get();
328 return _pendingEntry.get();
329 return [self lastCommittedEntry];
332 // See NavigationController::GetVisibleEntry for the motivation for this
334 - (CRWSessionEntry*)visibleEntry {
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];
343 (!pendingItem->is_renderer_initiated() || pendingItem->IsUnsafe())) {
344 return _pendingEntry.get();
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)
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]))
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;
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) {
394 if (item->GetURL() == url &&
395 (!PageTransitionCoreTypeIs(trans, ui::PAGE_TRANSITION_FORM_SUBMIT) ||
396 PageTransitionCoreTypeIs(item->GetTransitionType(),
397 ui::PAGE_TRANSITION_FORM_SUBMIT) ||
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
403 if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
404 _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
410 BOOL useDesktopUserAgent =
411 _useDesktopUserAgentForNextPendingEntry ||
412 (self.currentEntry.navigationItem &&
413 self.currentEntry.navigationItem->IsOverridingUserAgent());
414 _useDesktopUserAgentForNextPendingEntry = NO;
415 _pendingEntry.reset([[self sessionEntryWithURL:url
418 useDesktopUserAgent:useDesktopUserAgent
419 rendererInitiated:rendererInitiated] retain]);
421 if (_navigationManager && _navigationManager->GetFacadeDelegate()) {
422 _navigationManager->GetFacadeDelegate()->OnNavigationItemPending();
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.
434 web::NavigationItemImpl* item = [_pendingEntry navigationItemImpl];
435 if (url != item->GetURL()) {
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();
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();
451 - (void)clearForwardEntries {
452 [self discardTransientEntry];
454 NSInteger forwardEntryStartIndex = _currentNavigationIndex + 1;
455 DCHECK(forwardEntryStartIndex >= 0);
457 if (forwardEntryStartIndex >= static_cast<NSInteger>([_entries count]))
460 NSRange remove = NSMakeRange(forwardEntryStartIndex,
461 [_entries count] - forwardEntryStartIndex);
462 // Store removed items in temporary NSArray so they can be deallocated after
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(
475 - (void)commitPendingEntry {
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();
488 CRWSessionEntry* currentEntry = self.currentEntry;
489 web::NavigationItem* item = currentEntry.navigationItem;
490 // Update the navigation timestamp now that it's actually happened.
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);
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
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;
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
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);
598 [_entries removeAllObjects];
599 self.currentNavigationIndex = -1;
600 _previousNavigationIndex = -1;
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
618 if (![otherEntries count])
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;
629 // If this CRWSessionController has no entries initially, reset
630 // |currentNavigationIndex_| to be in bounds.
631 if (!numInitialEntries) {
633 self.currentNavigationIndex = [otherSession currentNavigationIndex];
634 _previousNavigationIndex = [otherSession previousNavigationIndex];
636 self.currentNavigationIndex = maxCopyIndex;
639 DCHECK_LT((NSUInteger)_currentNavigationIndex, [_entries count]);
642 - (ui::PageTransition)transitionForIndex:(NSUInteger)index {
643 return [[_entries objectAtIndex:index] navigationItem]->GetTransitionType();
647 if ([_entries count] == 0)
650 NSInteger lastNonRedirectedIndex = _currentNavigationIndex;
651 while (lastNonRedirectedIndex >= 0 &&
652 ui::PageTransitionIsRedirect(
653 [self transitionForIndex:lastNonRedirectedIndex])) {
654 --lastNonRedirectedIndex;
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.
665 // If the current index is less than the last element, there are entries to
667 const NSInteger count = [_entries count];
668 return count && _currentNavigationIndex < (count - 1);
672 if (![self canGoBack])
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;
688 if (_currentNavigationIndex)
689 --_currentNavigationIndex;
693 [self discardTransientEntry];
695 web::RecordAction(UserMetricsAction("Forward"));
696 if (_currentNavigationIndex + 1 < static_cast<NSInteger>([_entries count])) {
697 _previousNavigationIndex = _currentNavigationIndex;
698 ++_currentNavigationIndex;
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;
712 - (void)goDelta:(int)delta {
714 while ([self canGoBack] && delta < 0) {
719 while ([self canGoForward] && delta > 0) {
726 - (void)goToEntry:(CRWSessionEntry*)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);
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];
760 --lastNonRedirectedIndex;
762 // Remove the currently displayed entry.
763 [entries removeObjectAtIndex:0];
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];
776 ++lastNonRedirectedIndex;
781 - (std::vector<GURL>)currentRedirectedUrls {
782 std::vector<GURL> results;
784 web::NavigationItem* item = [_pendingEntry navigationItem];
785 results.push_back(item->GetURL());
787 if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
791 if (![_entries count])
794 NSInteger index = _currentNavigationIndex;
795 // Add urls in the redirected entries.
797 web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
798 if (!ui::PageTransitionIsRedirect(item->GetTransitionType()))
800 results.push_back(item->GetURL());
803 // Add the last non-redirected entry.
805 web::NavigationItem* item = [[_entries objectAtIndex:index] navigationItem];
806 results.push_back(item->GetURL());
811 - (BOOL)isPushStateNavigationBetweenEntry:(CRWSessionEntry*)firstEntry
812 andEntry:(CRWSessionEntry*)secondEntry {
815 if (firstEntry == secondEntry)
817 NSUInteger firstIndex = [_entries indexOfObject:firstEntry];
818 NSUInteger secondIndex = [_entries indexOfObject:secondEntry];
819 if (firstIndex == NSNotFound || secondIndex == NSNotFound)
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())
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()))
838 - (CRWSessionEntry*)lastUserEntry {
839 if (![_entries count])
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.
846 [self transitionForIndex:index] &
847 ui::PAGE_TRANSITION_IS_REDIRECT_MASK) {
850 return [_entries objectAtIndex:index];
853 - (void)useDesktopUserAgentForNextPendingEntry {
855 [_pendingEntry navigationItem]->SetIsOverridingUserAgent(true);
857 _useDesktopUserAgentForNextPendingEntry = YES;
861 #pragma mark Private methods
863 - (NSString*)uniqueID {
864 CFUUIDRef uuidRef = CFUUIDCreate(NULL);
865 CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
867 NSString* uuid = [NSString stringWithString:(NSString*)uuidStringRef];
868 CFRelease(uuidStringRef);
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());
887 if (!urlWasRewritten) {
888 web::BrowserURLRewriter::GetInstance()->RewriteURLIfNecessary(
889 &loaded_url, _browserState);
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);
898 [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass()] autorelease];