OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #import "ios/web/navigation/crw_session_controller.h" |
| 6 |
| 7 #include <algorithm> |
| 8 #include <vector> |
| 9 |
| 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" |
| 29 |
| 30 using base::UserMetricsAction; |
| 31 |
| 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 |
| 45 |
| 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; |
| 50 |
| 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; |
| 66 |
| 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; |
| 72 |
| 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; |
| 78 |
| 79 // The window name associated with the session. |
| 80 NSString* _windowName; |
| 81 |
| 82 // Stores the certificate policies decided by the user. |
| 83 CRWSessionCertificatePolicyManager* _sessionCertificatePolicyManager; |
| 84 |
| 85 // The timestamp of the last time this tab is visited, represented in time |
| 86 // interval since 1970. |
| 87 NSTimeInterval _lastVisitedTimestamp; |
| 88 |
| 89 // If |YES|, override |currentEntry.useDesktopUserAgent| and create the |
| 90 // pending entry using the desktop user agent. |
| 91 BOOL _useDesktopUserAgentForNextPendingEntry; |
| 92 |
| 93 // The browser state associated with this CRWSessionController; |
| 94 __weak web::BrowserState* _browserState; |
| 95 |
| 96 // Time smoother for navigation entry timestamps; see comment in |
| 97 // navigation_controller_impl.h |
| 98 web::TimeSmoother _timeSmoother; |
| 99 |
| 100 // XCallback parameters used to create (or clobber) the tab. Can be nil. |
| 101 XCallbackParameters* _xCallbackParameters; |
| 102 |
| 103 base::mac::ObjCPropertyReleaser _propertyReleaser_CRWSessionController; |
| 104 } |
| 105 |
| 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; |
| 112 |
| 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 |
| 128 |
| 129 @implementation CRWSessionController |
| 130 |
| 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; |
| 142 |
| 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; |
| 166 } |
| 167 |
| 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; |
| 178 |
| 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); |
| 183 |
| 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; |
| 203 } |
| 204 |
| 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]; |
| 213 |
| 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 } |
| 239 |
| 240 _xCallbackParameters = |
| 241 [[aDecoder decodeObjectForKey:kXCallbackParametersKey] retain]; |
| 242 } |
| 243 return self; |
| 244 } |
| 245 |
| 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. |
| 261 } |
| 262 |
| 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; |
| 280 } |
| 281 |
| 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 } |
| 293 } |
| 294 |
| 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]; |
| 308 } |
| 309 |
| 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]; |
| 318 } |
| 319 |
| 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]; |
| 337 } |
| 338 |
| 339 - (CRWSessionEntry*)pendingEntry { |
| 340 return _pendingEntry.get(); |
| 341 } |
| 342 |
| 343 - (CRWSessionEntry*)transientEntry { |
| 344 return _transientEntry.get(); |
| 345 } |
| 346 |
| 347 - (CRWSessionEntry*)lastCommittedEntry { |
| 348 if (_currentNavigationIndex == -1) |
| 349 return nil; |
| 350 return [_entries objectAtIndex:_currentNavigationIndex]; |
| 351 } |
| 352 |
| 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]; |
| 358 } |
| 359 |
| 360 - (void)addPendingEntry:(const GURL&)url |
| 361 referrer:(const web::Referrer&)ref |
| 362 transition:(ui::PageTransition)trans |
| 363 rendererInitiated:(BOOL)rendererInitiated { |
| 364 [self discardTransientEntry]; |
| 365 |
| 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 } |
| 399 |
| 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]); |
| 408 |
| 409 if (_navigationManager && _navigationManager->GetFacadeDelegate()) { |
| 410 _navigationManager->GetFacadeDelegate()->OnNavigationItemPending(); |
| 411 } |
| 412 } |
| 413 |
| 414 - (void)updatePendingEntry:(const GURL&)url { |
| 415 [self discardTransientEntry]; |
| 416 |
| 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 } |
| 434 |
| 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 } |
| 440 } |
| 441 |
| 442 - (void)clearForwardEntries { |
| 443 [self discardTransientEntry]; |
| 444 |
| 445 NSInteger forwardEntryStartIndex = _currentNavigationIndex + 1; |
| 446 DCHECK(forwardEntryStartIndex >= 0); |
| 447 |
| 448 if (forwardEntryStartIndex >= static_cast<NSInteger>([_entries count])) |
| 449 return; |
| 450 |
| 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 } |
| 464 } |
| 465 |
| 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 } |
| 480 |
| 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())); |
| 486 |
| 487 if (_navigationManager && item) |
| 488 _navigationManager->OnNavigationItemCommitted(); |
| 489 } |
| 490 |
| 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]; |
| 499 |
| 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]); |
| 506 |
| 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())); |
| 514 |
| 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(); |
| 520 } |
| 521 |
| 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; |
| 539 |
| 540 [self clearForwardEntries]; |
| 541 // Add the new entry at the end. |
| 542 [_entries addObject:pushedEntry]; |
| 543 _previousNavigationIndex = _currentNavigationIndex; |
| 544 _currentNavigationIndex = [_entries count] - 1; |
| 545 |
| 546 if (_navigationManager) |
| 547 _navigationManager->OnNavigationItemCommitted(); |
| 548 } |
| 549 |
| 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(); |
| 559 } |
| 560 |
| 561 - (void)discardNonCommittedEntries { |
| 562 [self discardTransientEntry]; |
| 563 _pendingEntry.reset(); |
| 564 } |
| 565 |
| 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(); |
| 574 } |
| 575 |
| 576 - (BOOL)hasPendingEntry { |
| 577 return _pendingEntry != nil; |
| 578 } |
| 579 |
| 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]; |
| 592 |
| 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]; |
| 597 |
| 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; |
| 606 |
| 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 } |
| 614 |
| 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]); |
| 626 } |
| 627 |
| 628 - (ui::PageTransition)transitionForIndex:(NSUInteger)index { |
| 629 return [[_entries objectAtIndex:index] navigationItem]->GetTransitionType(); |
| 630 } |
| 631 |
| 632 - (BOOL)canGoBack { |
| 633 if ([_entries count] == 0) |
| 634 return NO; |
| 635 |
| 636 NSInteger lastNonRedirectedIndex = _currentNavigationIndex; |
| 637 while (lastNonRedirectedIndex >= 0 && |
| 638 ui::PageTransitionIsRedirect( |
| 639 [self transitionForIndex:lastNonRedirectedIndex])) { |
| 640 --lastNonRedirectedIndex; |
| 641 } |
| 642 |
| 643 return lastNonRedirectedIndex > 0; |
| 644 } |
| 645 |
| 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); |
| 655 } |
| 656 |
| 657 - (void)goBack { |
| 658 if (![self canGoBack]) |
| 659 return; |
| 660 |
| 661 [self discardTransientEntry]; |
| 662 |
| 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 } |
| 673 |
| 674 if (_currentNavigationIndex) |
| 675 --_currentNavigationIndex; |
| 676 } |
| 677 |
| 678 - (void)goForward { |
| 679 [self discardTransientEntry]; |
| 680 |
| 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 } |
| 696 } |
| 697 |
| 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 } |
| 710 } |
| 711 |
| 712 - (void)goToEntry:(CRWSessionEntry*)entry { |
| 713 DCHECK(entry); |
| 714 |
| 715 [self discardTransientEntry]; |
| 716 |
| 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]; |
| 721 } |
| 722 |
| 723 - (void)removeEntryAtIndex:(NSInteger)index { |
| 724 DCHECK(index < static_cast<NSInteger>([_entries count])); |
| 725 DCHECK(index != _currentNavigationIndex); |
| 726 DCHECK(index >= 0); |
| 727 |
| 728 [self discardNonCommittedEntries]; |
| 729 |
| 730 [_entries removeObjectAtIndex:index]; |
| 731 if (_currentNavigationIndex > index) |
| 732 _currentNavigationIndex--; |
| 733 if (_previousNavigationIndex >= index) |
| 734 _previousNavigationIndex--; |
| 735 } |
| 736 |
| 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; |
| 751 } |
| 752 |
| 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; |
| 765 } |
| 766 |
| 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()); |
| 772 |
| 773 if (!ui::PageTransitionIsRedirect(item->GetTransitionType())) |
| 774 return results; |
| 775 } |
| 776 |
| 777 if (![_entries count]) |
| 778 return results; |
| 779 |
| 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; |
| 795 } |
| 796 |
| 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; |
| 809 |
| 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; |
| 823 } |
| 824 |
| 825 - (CRWSessionEntry*)lastUserEntry { |
| 826 if (![_entries count]) |
| 827 return nil; |
| 828 |
| 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]; |
| 838 } |
| 839 |
| 840 - (void)useDesktopUserAgentForNextPendingEntry { |
| 841 if (_pendingEntry) |
| 842 [_pendingEntry setUseDesktopUserAgent:YES]; |
| 843 else |
| 844 _useDesktopUserAgentForNextPendingEntry = YES; |
| 845 } |
| 846 |
| 847 #pragma mark - |
| 848 #pragma mark Private methods |
| 849 |
| 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; |
| 857 } |
| 858 |
| 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]; |
| 872 } |
| 873 |
| 874 @end |
OLD | NEW |