blob: a4e073289f53cdb6e63fec01a40eb0e98263ac89 [file] [log] [blame]
// Copyright 2013 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/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 {
// Checks whether or not two URL are an in-page navigation (differing only
// in the fragment).
bool AreURLsInPageNavigation(const GURL& existing_url, const GURL& new_url) {
if (existing_url == new_url || !new_url.has_ref())
return false;
return existing_url.EqualsIgnoringRef(new_url);
}
} // anonymous namespace
namespace web {
NavigationManager::WebLoadParams::WebLoadParams(const GURL& url)
: url(url),
transition_type(ui::PAGE_TRANSITION_LINK),
user_agent_override_option(UserAgentOverrideOption::INHERIT),
is_renderer_initiated(false),
post_data(nil) {}
NavigationManager::WebLoadParams::~WebLoadParams() {}
NavigationManager::WebLoadParams::WebLoadParams(const WebLoadParams& other)
: url(other.url),
referrer(other.referrer),
transition_type(other.transition_type),
user_agent_override_option(other.user_agent_override_option),
is_renderer_initiated(other.is_renderer_initiated),
extra_headers([other.extra_headers copy]),
post_data([other.post_data copy]) {}
NavigationManager::WebLoadParams& NavigationManager::WebLoadParams::operator=(
const WebLoadParams& other) {
url = other.url;
referrer = other.referrer;
is_renderer_initiated = other.is_renderer_initiated;
transition_type = other.transition_type;
user_agent_override_option = other.user_agent_override_option;
extra_headers.reset([other.extra_headers copy]);
post_data.reset([other.post_data copy]);
return *this;
}
NavigationManagerImpl::NavigationManagerImpl()
: delegate_(nullptr), browser_state_(nullptr) {}
NavigationManagerImpl::~NavigationManagerImpl() {
[session_controller_ setNavigationManager:nullptr];
}
void NavigationManagerImpl::SetDelegate(NavigationManagerDelegate* delegate) {
delegate_ = delegate;
}
void NavigationManagerImpl::SetBrowserState(BrowserState* browser_state) {
browser_state_ = browser_state;
[session_controller_ setBrowserState:browser_state];
}
void NavigationManagerImpl::SetSessionController(
CRWSessionController* session_controller) {
session_controller_.reset(session_controller);
[session_controller_ setNavigationManager:this];
}
void NavigationManagerImpl::InitializeSession() {
SetSessionController(
[[CRWSessionController alloc] initWithBrowserState:browser_state_]);
}
void NavigationManagerImpl::ReplaceSessionHistory(
std::vector<std::unique_ptr<web::NavigationItem>> items,
int lastCommittedItemIndex) {
SetSessionController([[CRWSessionController alloc]
initWithBrowserState:browser_state_
navigationItems:std::move(items)
lastCommittedItemIndex:lastCommittedItemIndex]);
}
void NavigationManagerImpl::OnNavigationItemsPruned(size_t pruned_item_count) {
delegate_->OnNavigationItemsPruned(pruned_item_count);
}
void NavigationManagerImpl::OnNavigationItemChanged() {
delegate_->OnNavigationItemChanged();
}
void NavigationManagerImpl::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.previous_url = [session_controller_ previousItem]->GetURL();
details.is_in_page =
AreURLsInPageNavigation(details.previous_url, details.item->GetURL());
} else {
details.previous_url = GURL();
details.is_in_page = NO;
}
delegate_->OnNavigationItemCommitted(details);
}
CRWSessionController* NavigationManagerImpl::GetSessionController() {
return session_controller_;
}
void NavigationManagerImpl::LoadURL(const GURL& url,
const web::Referrer& referrer,
ui::PageTransition type) {
WebState::OpenURLParams params(url, referrer,
WindowOpenDisposition::CURRENT_TAB, type, NO);
delegate_->GetWebState()->OpenURL(params);
}
void NavigationManagerImpl::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();
DCHECK(item->GetUserAgentType() != UserAgentType::NONE);
GetTransientItem()->SetUserAgentType(item->GetUserAgentType());
}
void NavigationManagerImpl::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];
// Set the user agent type for web URLs.
NavigationItem* pending_item = GetPendingItem();
if (!pending_item)
return;
// |user_agent_override_option| must be INHERIT if |pending_item|'s
// UserAgentType is NONE, as requesting a desktop or mobile user agent should
// be disabled for app-specific URLs.
DCHECK(pending_item->GetUserAgentType() != UserAgentType::NONE ||
user_agent_override_option == UserAgentOverrideOption::INHERIT);
// Newly created pending items are created with UserAgentType::NONE for native
// pages or UserAgentType::MOBILE for non-native pages. If the pending item's
// URL is non-native, check which user agent type it should be created with
// based on |user_agent_override_option|.
DCHECK_NE(UserAgentType::DESKTOP, pending_item->GetUserAgentType());
if (pending_item->GetUserAgentType() == UserAgentType::NONE)
return;
switch (user_agent_override_option) {
case UserAgentOverrideOption::DESKTOP:
pending_item->SetUserAgentType(UserAgentType::DESKTOP);
break;
case UserAgentOverrideOption::MOBILE:
pending_item->SetUserAgentType(UserAgentType::MOBILE);
break;
case UserAgentOverrideOption::INHERIT: {
// Propagate the last committed non-native item's UserAgentType if there
// is one, otherwise keep the default value, which is mobile.
NavigationItem* last_non_native_item =
GetLastCommittedNonAppSpecificItem();
DCHECK(!last_non_native_item ||
last_non_native_item->GetUserAgentType() != UserAgentType::NONE);
if (last_non_native_item) {
pending_item->SetUserAgentType(
last_non_native_item->GetUserAgentType());
}
break;
}
}
}
void NavigationManagerImpl::CommitPendingItem() {
[session_controller_ commitPendingItem];
}
BrowserState* NavigationManagerImpl::GetBrowserState() const {
return browser_state_;
}
WebState* NavigationManagerImpl::GetWebState() const {
return delegate_->GetWebState();
}
NavigationItem* NavigationManagerImpl::GetVisibleItem() const {
return [session_controller_ visibleItem];
}
NavigationItem* NavigationManagerImpl::GetLastCommittedItem() const {
return [session_controller_ lastCommittedItem];
}
NavigationItem* NavigationManagerImpl::GetPendingItem() const {
return [session_controller_ pendingItem];
}
NavigationItem* NavigationManagerImpl::GetTransientItem() const {
return [session_controller_ transientItem];
}
void NavigationManagerImpl::DiscardNonCommittedItems() {
[session_controller_ discardNonCommittedItems];
}
void NavigationManagerImpl::LoadURLWithParams(
const NavigationManager::WebLoadParams& params) {
delegate_->LoadURLWithParams(params);
}
void NavigationManagerImpl::AddTransientURLRewriter(
BrowserURLRewriter::URLRewriter rewriter) {
DCHECK(rewriter);
if (!transient_url_rewriters_) {
transient_url_rewriters_.reset(
new std::vector<BrowserURLRewriter::URLRewriter>());
}
transient_url_rewriters_->push_back(rewriter);
}
int NavigationManagerImpl::GetItemCount() const {
return [session_controller_ items].size();
}
NavigationItem* NavigationManagerImpl::GetItemAtIndex(size_t index) const {
return [session_controller_ itemAtIndex:index];
}
int NavigationManagerImpl::GetIndexOfItem(
const web::NavigationItem* item) const {
return [session_controller_ indexOfItem:item];
}
int NavigationManagerImpl::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 NavigationManagerImpl::GetLastCommittedItemIndex() const {
if (GetItemCount() == 0)
return -1;
return [session_controller_ lastCommittedItemIndex];
}
bool NavigationManagerImpl::RemoveItemAtIndex(int index) {
if (index == GetLastCommittedItemIndex() || index == GetPendingItemIndex())
return false;
if (index < 0 || index >= GetItemCount())
return false;
[session_controller_ removeItemAtIndex:index];
return true;
}
bool NavigationManagerImpl::CanGoBack() const {
return CanGoToOffset(-1);
}
bool NavigationManagerImpl::CanGoForward() const {
return CanGoToOffset(1);
}
bool NavigationManagerImpl::CanGoToOffset(int offset) const {
int index = GetIndexForOffset(offset);
return 0 <= index && index < GetItemCount();
}
void NavigationManagerImpl::GoBack() {
delegate_->GoToIndex(GetIndexForOffset(-1));
}
void NavigationManagerImpl::GoForward() {
delegate_->GoToIndex(GetIndexForOffset(1));
}
void NavigationManagerImpl::GoToIndex(int index) {
delegate_->GoToIndex(index);
}
NavigationItemList NavigationManagerImpl::GetBackwardItems() const {
return [session_controller_ backwardItems];
}
NavigationItemList NavigationManagerImpl::GetForwardItems() const {
return [session_controller_ forwardItems];
}
void NavigationManagerImpl::Reload(ReloadType reload_type,
bool check_for_reposts) {
if (!GetTransientItem() && !GetPendingItem() && !GetLastCommittedItem())
return;
// Reload with ORIGINAL_REQUEST_URL type should reload with the original
// request url of the transient item, or pending item if transient doesn't
// exist, or last committed item if both of them don't exist. The reason is
// that a server side redirect may change the item's url.
// For example, the user visits www.chromium.org and is then redirected
// to m.chromium.org, when the user wants to refresh the page with a different
// configuration (e.g. user agent), the user would be expecting to visit
// www.chromium.org instead of m.chromium.org.
if (reload_type == web::ReloadType::ORIGINAL_REQUEST_URL) {
NavigationItem* reload_item = nullptr;
if (GetTransientItem())
reload_item = GetTransientItem();
else if (GetPendingItem())
reload_item = GetPendingItem();
else
reload_item = GetLastCommittedItem();
DCHECK(reload_item);
reload_item->SetURL(reload_item->GetOriginalRequestURL());
}
delegate_->Reload();
}
void NavigationManagerImpl::CopyStateFromAndPrune(
const NavigationManager* manager) {
DCHECK(manager);
CRWSessionController* other_session =
static_cast<const NavigationManagerImpl*>(manager)->session_controller_;
[session_controller_ copyStateFromSessionControllerAndPrune:other_session];
}
bool NavigationManagerImpl::CanPruneAllButLastCommittedItem() const {
return [session_controller_ canPruneAllButLastCommittedItem];
}
std::unique_ptr<std::vector<BrowserURLRewriter::URLRewriter>>
NavigationManagerImpl::GetTransientURLRewriters() {
return std::move(transient_url_rewriters_);
}
void NavigationManagerImpl::RemoveTransientURLRewriters() {
transient_url_rewriters_.reset();
}
int NavigationManagerImpl::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 && IsRedirectItemAtIndex(result)) {
--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() && IsRedirectItemAtIndex(result + 1)) {
++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;
}
bool NavigationManagerImpl::IsRedirectItemAtIndex(int index) const {
DCHECK_GE(index, 0);
DCHECK_LT(index, GetItemCount());
ui::PageTransition transition = GetItemAtIndex(index)->GetTransitionType();
return transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK;
}
NavigationItem* NavigationManagerImpl::GetLastCommittedNonAppSpecificItem()
const {
int index = GetLastCommittedItemIndex();
if (index == -1)
return nullptr;
WebClient* client = GetWebClient();
const ScopedNavigationItemImplList& items = [session_controller_ items];
while (index >= 0) {
NavigationItem* item = items[index--].get();
if (!client->IsAppSpecificURL(item->GetVirtualURL()))
return item;
}
return nullptr;
}
} // namespace web