| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/web/navigation/legacy_navigation_manager_impl.h" |
| |
| #include <stddef.h> |
| |
| #include <utility> |
| |
| #include "base/logging.h" |
| #import "ios/web/navigation/crw_session_controller+private_constructors.h" |
| #import "ios/web/navigation/crw_session_controller.h" |
| #import "ios/web/navigation/navigation_item_impl.h" |
| #import "ios/web/navigation/navigation_item_impl_list.h" |
| #import "ios/web/navigation/navigation_manager_delegate.h" |
| #include "ios/web/public/load_committed_details.h" |
| #import "ios/web/public/navigation_item.h" |
| #include "ios/web/public/reload_type.h" |
| #import "ios/web/public/web_client.h" |
| #import "ios/web/public/web_state/web_state.h" |
| #include "ui/base/page_transition_types.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace web { |
| |
| LegacyNavigationManagerImpl::LegacyNavigationManagerImpl() = default; |
| |
| LegacyNavigationManagerImpl::~LegacyNavigationManagerImpl() { |
| [session_controller_ setNavigationManager:nullptr]; |
| } |
| |
| void LegacyNavigationManagerImpl::SetBrowserState(BrowserState* browser_state) { |
| NavigationManagerImpl::SetBrowserState(browser_state); |
| [session_controller_ setBrowserState:browser_state]; |
| } |
| |
| void LegacyNavigationManagerImpl::SetSessionController( |
| CRWSessionController* session_controller) { |
| session_controller_ = session_controller; |
| [session_controller_ setNavigationManager:this]; |
| } |
| |
| void LegacyNavigationManagerImpl::InitializeSession() { |
| SetSessionController( |
| [[CRWSessionController alloc] initWithBrowserState:browser_state_]); |
| } |
| |
| void LegacyNavigationManagerImpl::OnNavigationItemsPruned( |
| size_t pruned_item_count) { |
| delegate_->OnNavigationItemsPruned(pruned_item_count); |
| } |
| |
| void LegacyNavigationManagerImpl::OnNavigationItemChanged() { |
| delegate_->OnNavigationItemChanged(); |
| } |
| |
| void LegacyNavigationManagerImpl::OnNavigationItemCommitted() { |
| LoadCommittedDetails details; |
| details.item = GetLastCommittedItem(); |
| DCHECK(details.item); |
| details.previous_item_index = [session_controller_ previousItemIndex]; |
| if (details.previous_item_index >= 0) { |
| DCHECK([session_controller_ previousItem]); |
| details.is_in_page = IsFragmentChangeNavigationBetweenUrls( |
| [session_controller_ previousItem]->GetURL(), details.item->GetURL()); |
| } else { |
| details.is_in_page = NO; |
| } |
| |
| delegate_->OnNavigationItemCommitted(details); |
| } |
| |
| CRWSessionController* LegacyNavigationManagerImpl::GetSessionController() |
| const { |
| return session_controller_; |
| } |
| |
| void LegacyNavigationManagerImpl::AddTransientItem(const GURL& url) { |
| [session_controller_ addTransientItemWithURL:url]; |
| |
| // TODO(crbug.com/676129): Transient item is only supposed to be added for |
| // pending non-app-specific loads, but pending item can be null because of the |
| // bug. The workaround should be removed once the bug is fixed. |
| NavigationItem* item = GetPendingItem(); |
| if (!item) |
| item = GetLastCommittedNonAppSpecificItem(); |
| // |item| may still be nullptr if NTP is the only entry in the session. |
| // See https://crbug.com/822908 for details. |
| if (item) { |
| DCHECK(item->GetUserAgentType() != UserAgentType::NONE); |
| GetTransientItem()->SetUserAgentType(item->GetUserAgentType()); |
| } |
| } |
| |
| void LegacyNavigationManagerImpl::AddPendingItem( |
| const GURL& url, |
| const web::Referrer& referrer, |
| ui::PageTransition navigation_type, |
| NavigationInitiationType initiation_type, |
| UserAgentOverrideOption user_agent_override_option) { |
| [session_controller_ addPendingItem:url |
| referrer:referrer |
| transition:navigation_type |
| initiationType:initiation_type |
| userAgentOverrideOption:user_agent_override_option]; |
| |
| if (!GetPendingItem()) { |
| return; |
| } |
| |
| UpdatePendingItemUserAgentType(user_agent_override_option, |
| GetLastCommittedNonAppSpecificItem(), |
| GetPendingItem()); |
| } |
| |
| void LegacyNavigationManagerImpl::CommitPendingItem() { |
| [session_controller_ commitPendingItem]; |
| } |
| |
| BrowserState* LegacyNavigationManagerImpl::GetBrowserState() const { |
| return browser_state_; |
| } |
| |
| WebState* LegacyNavigationManagerImpl::GetWebState() const { |
| return delegate_->GetWebState(); |
| } |
| |
| NavigationItem* LegacyNavigationManagerImpl::GetVisibleItem() const { |
| return [session_controller_ visibleItem]; |
| } |
| |
| void LegacyNavigationManagerImpl::DiscardNonCommittedItems() { |
| [session_controller_ discardNonCommittedItems]; |
| } |
| |
| int LegacyNavigationManagerImpl::GetItemCount() const { |
| return session_controller_ ? [session_controller_ items].size() : 0; |
| } |
| |
| NavigationItem* LegacyNavigationManagerImpl::GetItemAtIndex( |
| size_t index) const { |
| return GetNavigationItemImplAtIndex(index); |
| } |
| |
| NavigationItemImpl* LegacyNavigationManagerImpl::GetNavigationItemImplAtIndex( |
| size_t index) const { |
| return [session_controller_ itemAtIndex:index]; |
| } |
| |
| int LegacyNavigationManagerImpl::GetIndexOfItem( |
| const web::NavigationItem* item) const { |
| return [session_controller_ indexOfItem:item]; |
| } |
| |
| int LegacyNavigationManagerImpl::GetPendingItemIndex() const { |
| if (GetPendingItem()) { |
| if ([session_controller_ pendingItemIndex] != -1) { |
| return [session_controller_ pendingItemIndex]; |
| } |
| // TODO(crbug.com/665189): understand why last committed item index is |
| // returned here. |
| return GetLastCommittedItemIndex(); |
| } |
| return -1; |
| } |
| |
| int LegacyNavigationManagerImpl::GetLastCommittedItemIndex() const { |
| if (GetItemCount() == 0) |
| return -1; |
| return [session_controller_ lastCommittedItemIndex]; |
| } |
| |
| bool LegacyNavigationManagerImpl::RemoveItemAtIndex(int index) { |
| if (index == GetLastCommittedItemIndex() || index == GetPendingItemIndex()) |
| return false; |
| |
| if (index < 0 || index >= GetItemCount()) |
| return false; |
| |
| [session_controller_ removeItemAtIndex:index]; |
| return true; |
| } |
| |
| bool LegacyNavigationManagerImpl::CanGoBack() const { |
| return CanGoToOffset(-1); |
| } |
| |
| bool LegacyNavigationManagerImpl::CanGoForward() const { |
| return CanGoToOffset(1); |
| } |
| |
| bool LegacyNavigationManagerImpl::CanGoToOffset(int offset) const { |
| int index = GetIndexForOffset(offset); |
| return 0 <= index && index < GetItemCount(); |
| } |
| |
| void LegacyNavigationManagerImpl::GoBack() { |
| GoToIndex(GetIndexForOffset(-1)); |
| } |
| |
| void LegacyNavigationManagerImpl::GoForward() { |
| GoToIndex(GetIndexForOffset(1)); |
| } |
| |
| NavigationItemList LegacyNavigationManagerImpl::GetBackwardItems() const { |
| return [session_controller_ backwardItems]; |
| } |
| |
| NavigationItemList LegacyNavigationManagerImpl::GetForwardItems() const { |
| return [session_controller_ forwardItems]; |
| } |
| |
| void LegacyNavigationManagerImpl::Restore( |
| int last_committed_item_index, |
| std::vector<std::unique_ptr<NavigationItem>> items) { |
| WillRestore(items.size()); |
| |
| DCHECK(GetItemCount() == 0 && !GetPendingItem()); |
| DCHECK_LT(last_committed_item_index, static_cast<int>(items.size())); |
| DCHECK(items.empty() || last_committed_item_index >= 0); |
| SetSessionController([[CRWSessionController alloc] |
| initWithBrowserState:browser_state_ |
| navigationItems:std::move(items) |
| lastCommittedItemIndex:last_committed_item_index]); |
| } |
| |
| void LegacyNavigationManagerImpl::CopyStateFromAndPrune( |
| const NavigationManager* manager) { |
| DCHECK(manager); |
| CRWSessionController* other_session = |
| static_cast<const NavigationManagerImpl*>(manager) |
| ->GetSessionController(); |
| [session_controller_ copyStateFromSessionControllerAndPrune:other_session]; |
| } |
| |
| bool LegacyNavigationManagerImpl::CanPruneAllButLastCommittedItem() const { |
| return [session_controller_ canPruneAllButLastCommittedItem]; |
| } |
| |
| int LegacyNavigationManagerImpl::GetIndexForOffset(int offset) const { |
| int result = [session_controller_ pendingItemIndex] == -1 |
| ? GetLastCommittedItemIndex() |
| : static_cast<int>([session_controller_ pendingItemIndex]); |
| |
| if (offset < 0) { |
| if (GetTransientItem() && [session_controller_ pendingItemIndex] == -1) { |
| // Going back from transient item that added to the end navigation stack |
| // is a matter of discarding it as there is no need to move navigation |
| // index back. |
| offset++; |
| } |
| |
| while (offset < 0 && result > 0) { |
| // To stop the user getting 'stuck' on redirecting pages they weren't |
| // even aware existed, it is necessary to pass over pages that would |
| // immediately result in a redirect (the item *before* the redirected |
| // page). |
| while (result > 0) { |
| const NavigationItem* item = GetItemAtIndex(result); |
| if (!ui::PageTransitionIsRedirect(item->GetTransitionType())) |
| break; |
| --result; |
| } |
| --result; |
| ++offset; |
| } |
| // Result may be out of bounds, so stop trying to skip redirect items and |
| // simply add the remainder. |
| result += offset; |
| if (result > GetItemCount() /* overflow */) |
| result = INT_MIN; |
| } else if (offset > 0) { |
| while (offset > 0 && result < GetItemCount()) { |
| ++result; |
| --offset; |
| // As with going back, skip over redirects. |
| while (result + 1 < GetItemCount()) { |
| const NavigationItem* item = GetItemAtIndex(result + 1); |
| if (!ui::PageTransitionIsRedirect(item->GetTransitionType())) |
| break; |
| ++result; |
| } |
| } |
| // Result may be out of bounds, so stop trying to skip redirect items and |
| // simply add the remainder. |
| result += offset; |
| if (result < 0 /* overflow */) |
| result = INT_MAX; |
| } |
| |
| return result; |
| } |
| |
| NavigationItemImpl* LegacyNavigationManagerImpl::GetLastCommittedItemImpl() |
| const { |
| return [session_controller_ lastCommittedItem]; |
| } |
| |
| NavigationItemImpl* LegacyNavigationManagerImpl::GetPendingItemImpl() const { |
| return [session_controller_ pendingItem]; |
| } |
| |
| NavigationItemImpl* LegacyNavigationManagerImpl::GetTransientItemImpl() const { |
| return [session_controller_ transientItem]; |
| } |
| |
| void LegacyNavigationManagerImpl::FinishGoToIndex(int index, |
| NavigationInitiationType type, |
| bool has_user_gesture) { |
| const ScopedNavigationItemImplList& items = [session_controller_ items]; |
| NavigationItem* to_item = items[index].get(); |
| NavigationItem* previous_item = GetLastCommittedItem(); |
| |
| to_item->SetTransitionType(ui::PageTransitionFromInt( |
| to_item->GetTransitionType() | ui::PAGE_TRANSITION_FORWARD_BACK)); |
| |
| bool same_document_navigation = |
| [session_controller_ isSameDocumentNavigationBetweenItem:previous_item |
| andItem:to_item]; |
| if (same_document_navigation) { |
| [session_controller_ goToItemAtIndex:index discardNonCommittedItems:YES]; |
| delegate_->OnGoToIndexSameDocumentNavigation(type, has_user_gesture); |
| } else { |
| [session_controller_ discardNonCommittedItems]; |
| [session_controller_ setPendingItemIndex:index]; |
| delegate_->LoadCurrentItem(); |
| } |
| } |
| |
| int LegacyNavigationManagerImpl::GetPreviousItemIndex() const { |
| return base::checked_cast<int>([session_controller_ previousItemIndex]); |
| } |
| |
| void LegacyNavigationManagerImpl::SetPreviousItemIndex( |
| int previous_item_index) { |
| [session_controller_ setPreviousItemIndex:previous_item_index]; |
| } |
| |
| void LegacyNavigationManagerImpl::AddPushStateItemIfNecessary( |
| const GURL& url, |
| NSString* state_object, |
| ui::PageTransition transition) { |
| [session_controller_ pushNewItemWithURL:url |
| stateObject:state_object |
| transition:transition]; |
| } |
| |
| } // namespace web |