#include "content/browser/web_contents/navigation_controller_impl.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "content/browser/browser_url_handler_impl.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/dom_storage/dom_storage_context_impl.h"
#include "content/browser/dom_storage/session_storage_namespace_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/web_contents/debug_urls.h"
#include "content/browser/web_contents/interstitial_page_impl.h"
#include "content/browser/web_contents/navigation_entry_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/invalidate_type.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_constants.h"
#include "content/public/common/url_constants.h"
#include "net/base/escape.h"
#include "net/base/mime_util.h"
#include "net/base/net_util.h"
#include "webkit/glue/webkit_glue.h"
using content::BrowserContext;
using content::DOMStorageContext;
using content::GetContentClient;
using content::GlobalRequestID;
using content::NavigationController;
using content::NavigationEntry;
using content::NavigationEntryImpl;
using content::RenderViewHostImpl;
using content::SessionStorageNamespace;
using content::SessionStorageNamespaceMap;
using content::SiteInstance;
using content::UserMetricsAction;
using content::WebContents;
namespace {
const int kInvalidateAll = 0xFFFFFFFF;
void NotifyPrunedEntries(NavigationControllerImpl* nav_controller,
bool from_front,
int count) {
content::PrunedDetails details;
details.from_front = from_front;
details.count = count;
content::NotificationService::current()->Notify(
content::NOTIFICATION_NAV_LIST_PRUNED,
content::Source<NavigationController>(nav_controller),
content::Details<content::PrunedDetails>(&details));
}
void SetContentStateIfEmpty(NavigationEntryImpl* entry) {
if (entry->GetContentState().empty()) {
entry->SetContentState(
webkit_glue::CreateHistoryStateForURL(entry->GetURL()));
}
}
void ConfigureEntriesForRestore(
std::vector<linked_ptr<NavigationEntryImpl> >* entries,
bool from_last_session) {
for (size_t i = 0; i < entries->size(); ++i) {
(*entries)[i]->SetTransitionType(content::PAGE_TRANSITION_RELOAD);
(*entries)[i]->set_restore_type(from_last_session ?
NavigationEntryImpl::RESTORE_LAST_SESSION :
NavigationEntryImpl::RESTORE_CURRENT_SESSION);
SetContentStateIfEmpty((*entries)[i].get());
}
}
bool AreURLsInPageNavigation(const GURL& existing_url,
const GURL& new_url,
bool renderer_says_in_page) {
if (existing_url == new_url)
return renderer_says_in_page;
if (!new_url.has_ref()) {
return false;
}
url_canon::Replacements<char> replacements;
replacements.ClearRef();
return existing_url.ReplaceComponents(replacements) ==
new_url.ReplaceComponents(replacements);
}
bool ShouldKeepOverride(const content::NavigationEntry* last_entry) {
return last_entry && last_entry->GetIsOverridingUserAgent();
}
}
const size_t kMaxEntryCountForTestingNotSet = -1;
size_t NavigationControllerImpl::max_entry_count_for_testing_ =
kMaxEntryCountForTestingNotSet;
static bool g_check_for_repost = true;
namespace content {
NavigationEntry* NavigationController::CreateNavigationEntry(
const GURL& url,
const Referrer& referrer,
PageTransition transition,
bool is_renderer_initiated,
const std::string& extra_headers,
BrowserContext* browser_context) {
GURL loaded_url(url);
bool reverse_on_redirect = false;
BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary(
&loaded_url, browser_context, &reverse_on_redirect);
NavigationEntryImpl* entry = new NavigationEntryImpl(
NULL,
-1,
loaded_url,
referrer,
string16(),
transition,
is_renderer_initiated);
entry->SetVirtualURL(url);
entry->set_user_typed_url(url);
entry->set_update_virtual_url_with_url(reverse_on_redirect);
entry->set_extra_headers(extra_headers);
return entry;
}
void NavigationController::DisablePromptOnRepost() {
g_check_for_repost = false;
}
}
NavigationControllerImpl::NavigationControllerImpl(
WebContentsImpl* web_contents,
BrowserContext* browser_context)
: browser_context_(browser_context),
pending_entry_(NULL),
last_committed_entry_index_(-1),
pending_entry_index_(-1),
transient_entry_index_(-1),
web_contents_(web_contents),
max_restored_page_id_(-1),
ALLOW_THIS_IN_INITIALIZER_LIST(ssl_manager_(this)),
needs_reload_(false),
is_initial_navigation_(true),
pending_reload_(NO_RELOAD) {
DCHECK(browser_context_);
}
NavigationControllerImpl::~NavigationControllerImpl() {
DiscardNonCommittedEntriesInternal();
}
WebContents* NavigationControllerImpl::GetWebContents() const {
return web_contents_;
}
BrowserContext* NavigationControllerImpl::GetBrowserContext() const {
return browser_context_;
}
void NavigationControllerImpl::SetBrowserContext(
BrowserContext* browser_context) {
browser_context_ = browser_context;
}
void NavigationControllerImpl::Restore(
int selected_navigation,
bool from_last_session,
std::vector<NavigationEntry*>* entries) {
DCHECK(GetEntryCount() == 0 && !GetPendingEntry());
DCHECK(selected_navigation >= 0 &&
selected_navigation < static_cast<int>(entries->size()));
needs_reload_ = true;
for (size_t i = 0; i < entries->size(); ++i) {
NavigationEntryImpl* entry =
NavigationEntryImpl::FromNavigationEntry((*entries)[i]);
entries_.push_back(linked_ptr<NavigationEntryImpl>(entry));
}
entries->clear();
FinishRestore(selected_navigation, from_last_session);
}
void NavigationControllerImpl::Reload(bool check_for_repost) {
ReloadInternal(check_for_repost, RELOAD);
}
void NavigationControllerImpl::ReloadIgnoringCache(bool check_for_repost) {
ReloadInternal(check_for_repost, RELOAD_IGNORING_CACHE);
}
void NavigationControllerImpl::ReloadOriginalRequestURL(bool check_for_repost) {
ReloadInternal(check_for_repost, RELOAD_ORIGINAL_REQUEST_URL);
}
void NavigationControllerImpl::ReloadInternal(bool check_for_repost,
ReloadType reload_type) {
if (transient_entry_index_ != -1) {
content::NavigationEntryImpl* active_entry =
NavigationEntryImpl::FromNavigationEntry(GetActiveEntry());
if (!active_entry)
return;
LoadURL(active_entry->GetURL(),
content::Referrer(),
content::PAGE_TRANSITION_RELOAD,
active_entry->extra_headers());
return;
}
DiscardNonCommittedEntriesInternal();
int current_index = GetCurrentEntryIndex();
if (current_index == -1) {
return;
}
if (g_check_for_repost && check_for_repost &&
GetEntryAtIndex(current_index)->GetHasPostData()) {
content::NotificationService::current()->Notify(
content::NOTIFICATION_REPOST_WARNING_SHOWN,
content::Source<NavigationController>(this),
content::NotificationService::NoDetails());
pending_reload_ = reload_type;
web_contents_->Activate();
web_contents_->GetDelegate()->ShowRepostFormWarningDialog(web_contents_);
} else {
DiscardNonCommittedEntriesInternal();
NavigationEntryImpl* entry = entries_[current_index].get();
SiteInstanceImpl* site_instance = entry->site_instance();
DCHECK(site_instance);
if (site_instance &&
site_instance->HasWrongProcessForURL(entry->GetURL())) {
NavigationEntryImpl* nav_entry = NavigationEntryImpl::FromNavigationEntry(
CreateNavigationEntry(
entry->GetURL(), entry->GetReferrer(), entry->GetTransitionType(),
false, entry->extra_headers(), browser_context_));
reload_type = NavigationController::NO_RELOAD;
nav_entry->set_is_cross_site_reload(true);
pending_entry_ = nav_entry;
} else {
pending_entry_index_ = current_index;
entries_[pending_entry_index_]->SetTitle(string16());
entries_[pending_entry_index_]->SetTransitionType(
content::PAGE_TRANSITION_RELOAD);
}
NavigateToPendingEntry(reload_type);
}
}
void NavigationControllerImpl::CancelPendingReload() {
DCHECK(pending_reload_ != NO_RELOAD);
pending_reload_ = NO_RELOAD;
}
void NavigationControllerImpl::ContinuePendingReload() {
if (pending_reload_ == NO_RELOAD) {
NOTREACHED();
} else {
ReloadInternal(false, pending_reload_);
pending_reload_ = NO_RELOAD;
}
}
bool NavigationControllerImpl::IsInitialNavigation() {
return is_initial_navigation_;
}
NavigationEntryImpl* NavigationControllerImpl::GetEntryWithPageID(
SiteInstance* instance, int32 page_id) const {
int index = GetEntryIndexWithPageID(instance, page_id);
return (index != -1) ? entries_[index].get() : NULL;
}
void NavigationControllerImpl::LoadEntry(NavigationEntryImpl* entry) {
ChildProcessSecurityPolicyImpl* policy =
ChildProcessSecurityPolicyImpl::GetInstance();
if (policy->IsDisabledScheme(entry->GetURL().scheme()) ||
policy->IsDisabledScheme(entry->GetVirtualURL().scheme())) {
VLOG(1) << "URL not loaded because the scheme is blocked by policy: "
<< entry->GetURL();
delete entry;
return;
}
DiscardNonCommittedEntriesInternal();
pending_entry_ = entry;
content::NotificationService::current()->Notify(
content::NOTIFICATION_NAV_ENTRY_PENDING,
content::Source<NavigationController>(this),
content::Details<NavigationEntry>(entry));
NavigateToPendingEntry(NO_RELOAD);
}
NavigationEntry* NavigationControllerImpl::GetActiveEntry() const {
if (transient_entry_index_ != -1)
return entries_[transient_entry_index_].get();
if (pending_entry_)
return pending_entry_;
return GetLastCommittedEntry();
}
NavigationEntry* NavigationControllerImpl::GetVisibleEntry() const {
if (transient_entry_index_ != -1)
return entries_[transient_entry_index_].get();
if (pending_entry_ &&
pending_entry_->GetPageID() == -1 &&
!pending_entry_->is_renderer_initiated())
return pending_entry_;
return GetLastCommittedEntry();
}
int NavigationControllerImpl::GetCurrentEntryIndex() const {
if (transient_entry_index_ != -1)
return transient_entry_index_;
if (pending_entry_index_ != -1)
return pending_entry_index_;
return last_committed_entry_index_;
}
NavigationEntry* NavigationControllerImpl::GetLastCommittedEntry() const {
if (last_committed_entry_index_ == -1)
return NULL;
return entries_[last_committed_entry_index_].get();
}
bool NavigationControllerImpl::CanViewSource() const {
bool is_supported_mime_type = net::IsSupportedNonImageMimeType(
web_contents_->GetContentsMimeType().c_str());
NavigationEntry* active_entry = GetActiveEntry();
return active_entry && !active_entry->IsViewSourceMode() &&
is_supported_mime_type && !web_contents_->GetInterstitialPage();
}
int NavigationControllerImpl::GetLastCommittedEntryIndex() const {
return last_committed_entry_index_;
}
int NavigationControllerImpl::GetEntryCount() const {
DCHECK(entries_.size() <= max_entry_count());
return static_cast<int>(entries_.size());
}
NavigationEntry* NavigationControllerImpl::GetEntryAtIndex(
int index) const {
return entries_.at(index).get();
}
NavigationEntry* NavigationControllerImpl::GetEntryAtOffset(
int offset) const {
int index = GetIndexForOffset(offset);
if (index < 0 || index >= GetEntryCount())
return NULL;
return entries_[index].get();
}
int NavigationControllerImpl::GetIndexForOffset(int offset) const {
return (transient_entry_index_ != -1) ?
transient_entry_index_ + offset :
last_committed_entry_index_ + offset;
}
bool NavigationControllerImpl::CanGoBack() const {
return entries_.size() > 1 && GetCurrentEntryIndex() > 0;
}
bool NavigationControllerImpl::CanGoForward() const {
int index = GetCurrentEntryIndex();
return index >= 0 && index < (static_cast<int>(entries_.size()) - 1);
}
bool NavigationControllerImpl::CanGoToOffset(int offset) const {
int index = GetIndexForOffset(offset);
return index >= 0 && index < GetEntryCount();
}
void NavigationControllerImpl::GoBack() {
if (!CanGoBack()) {
NOTREACHED();
return;
}
int current_index = GetCurrentEntryIndex();
DiscardNonCommittedEntries();
pending_entry_index_ = current_index - 1;
entries_[pending_entry_index_]->SetTransitionType(
content::PageTransitionFromInt(
entries_[pending_entry_index_]->GetTransitionType() |
content::PAGE_TRANSITION_FORWARD_BACK));
NavigateToPendingEntry(NO_RELOAD);
}
void NavigationControllerImpl::GoForward() {
if (!CanGoForward()) {
NOTREACHED();
return;
}
bool transient = (transient_entry_index_ != -1);
int current_index = GetCurrentEntryIndex();
DiscardNonCommittedEntries();
pending_entry_index_ = current_index;
if (!transient)
pending_entry_index_++;
entries_[pending_entry_index_]->SetTransitionType(
content::PageTransitionFromInt(
entries_[pending_entry_index_]->GetTransitionType() |
content::PAGE_TRANSITION_FORWARD_BACK));
NavigateToPendingEntry(NO_RELOAD);
}
void NavigationControllerImpl::GoToIndex(int index) {
if (index < 0 || index >= static_cast<int>(entries_.size())) {
NOTREACHED();
return;
}
if (transient_entry_index_ != -1) {
if (index == transient_entry_index_) {
return;
}
if (index > transient_entry_index_) {
index--;
}
}
DiscardNonCommittedEntries();
pending_entry_index_ = index;
entries_[pending_entry_index_]->SetTransitionType(
content::PageTransitionFromInt(
entries_[pending_entry_index_]->GetTransitionType() |
content::PAGE_TRANSITION_FORWARD_BACK));
NavigateToPendingEntry(NO_RELOAD);
}
void NavigationControllerImpl::GoToOffset(int offset) {
if (!CanGoToOffset(offset))
return;
GoToIndex(GetIndexForOffset(offset));
}
void NavigationControllerImpl::RemoveEntryAtIndex(int index) {
if (index == last_committed_entry_index_)
return;
RemoveEntryAtIndexInternal(index);
}
void NavigationControllerImpl::UpdateVirtualURLToURL(
NavigationEntryImpl* entry, const GURL& new_url) {
GURL new_virtual_url(new_url);
if (BrowserURLHandlerImpl::GetInstance()->ReverseURLRewrite(
&new_virtual_url, entry->GetVirtualURL(), browser_context_)) {
entry->SetVirtualURL(new_virtual_url);
}
}
void NavigationControllerImpl::AddTransientEntry(NavigationEntryImpl* entry) {
int index = 0;
if (last_committed_entry_index_ != -1)
index = last_committed_entry_index_ + 1;
DiscardTransientEntry();
entries_.insert(
entries_.begin() + index, linked_ptr<NavigationEntryImpl>(entry));
transient_entry_index_ = index;
web_contents_->NotifyNavigationStateChanged(kInvalidateAll);
}
void NavigationControllerImpl::LoadURL(
const GURL& url,
const content::Referrer& referrer,
content::PageTransition transition,
const std::string& extra_headers) {
LoadURLParams params(url);
params.referrer = referrer;
params.transition_type = transition;
params.extra_headers = extra_headers;
LoadURLWithParams(params);
}
void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) {
if (content::HandleDebugURL(params.url, params.transition_type))
return;
switch (params.load_type) {
case LOAD_TYPE_DEFAULT:
break;
case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
if (!params.url.SchemeIs(chrome::kHttpScheme) &&
!params.url.SchemeIs(chrome::kHttpsScheme)) {
NOTREACHED() << "Http post load must use http(s) scheme.";
return;
}
break;
case LOAD_TYPE_DATA:
if (!params.url.SchemeIs(chrome::kDataScheme)) {
NOTREACHED() << "Data load must use data scheme.";
return;
}
break;
default:
NOTREACHED();
break;
};
needs_reload_ = false;
bool override = false;
switch (params.override_user_agent) {
case UA_OVERRIDE_INHERIT:
override = ShouldKeepOverride(GetLastCommittedEntry());
break;
case UA_OVERRIDE_TRUE:
override = true;
break;
case UA_OVERRIDE_FALSE:
override = false;
break;
default:
NOTREACHED();
break;
}
NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
CreateNavigationEntry(
params.url,
params.referrer,
params.transition_type,
params.is_renderer_initiated,
params.extra_headers,
browser_context_));
entry->SetIsOverridingUserAgent(override);
entry->set_transferred_global_request_id(
params.transferred_global_request_id);
switch (params.load_type) {
case LOAD_TYPE_DEFAULT:
break;
case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
entry->SetHasPostData(true);
entry->SetBrowserInitiatedPostData(
params.browser_initiated_post_data);
break;
case LOAD_TYPE_DATA:
entry->SetBaseURLForDataURL(params.base_url_for_data_url);
entry->SetVirtualURL(params.virtual_url_for_data_url);
break;
default:
NOTREACHED();
break;
};
LoadEntry(entry);
}
void NavigationControllerImpl::DocumentLoadedInFrame() {
is_initial_navigation_ = false;
}
bool NavigationControllerImpl::RendererDidNavigate(
const ViewHostMsg_FrameNavigate_Params& params,
content::LoadCommittedDetails* details) {
if (GetLastCommittedEntry()) {
details->previous_url = GetLastCommittedEntry()->GetURL();
details->previous_entry_index = GetLastCommittedEntryIndex();
} else {
details->previous_url = GURL();
details->previous_entry_index = -1;
}
DCHECK(pending_entry_index_ == -1 || pending_entry_->site_instance());
details->did_replace_entry =
pending_entry_ && pending_entry_->is_cross_site_reload();
details->is_in_page = IsURLInPageNavigation(
params.url, params.was_within_same_page);
details->type = ClassifyNavigation(params);
switch (details->type) {
case content::NAVIGATION_TYPE_NEW_PAGE:
RendererDidNavigateToNewPage(params, details->did_replace_entry);
break;
case content::NAVIGATION_TYPE_EXISTING_PAGE:
RendererDidNavigateToExistingPage(params);
break;
case content::NAVIGATION_TYPE_SAME_PAGE:
RendererDidNavigateToSamePage(params);
break;
case content::NAVIGATION_TYPE_IN_PAGE:
RendererDidNavigateInPage(params, &details->did_replace_entry);
break;
case content::NAVIGATION_TYPE_NEW_SUBFRAME:
RendererDidNavigateNewSubframe(params);
break;
case content::NAVIGATION_TYPE_AUTO_SUBFRAME:
if (!RendererDidNavigateAutoSubframe(params))
return false;
break;
case content::NAVIGATION_TYPE_NAV_IGNORE:
if (pending_entry_) {
DiscardNonCommittedEntries();
web_contents_->NotifyNavigationStateChanged(
content::INVALIDATE_TYPE_URL);
}
return false;
default:
NOTREACHED();
}
DCHECK(!params.content_state.empty());
NavigationEntryImpl* active_entry =
NavigationEntryImpl::FromNavigationEntry(GetActiveEntry());
active_entry->SetContentState(params.content_state);
active_entry->SetBrowserInitiatedPostData(NULL);
active_entry->set_is_renderer_initiated(false);
DCHECK(active_entry->site_instance() == web_contents_->GetSiteInstance());
details->entry = active_entry;
details->is_main_frame =
content::PageTransitionIsMainFrame(params.transition);
details->serialized_security_info = params.security_info;
details->http_status_code = params.http_status_code;
NotifyNavigationEntryCommitted(details);
return true;
}
content::NavigationType NavigationControllerImpl::ClassifyNavigation(
const ViewHostMsg_FrameNavigate_Params& params) const {
if (params.page_id == -1) {
return content::NAVIGATION_TYPE_NAV_IGNORE;
}
if (params.page_id > web_contents_->GetMaxPageID()) {
if (content::PageTransitionIsMainFrame(params.transition))
return content::NAVIGATION_TYPE_NEW_PAGE;
if (!GetLastCommittedEntry())
return content::NAVIGATION_TYPE_NAV_IGNORE;
return content::NAVIGATION_TYPE_NEW_SUBFRAME;
}
int existing_entry_index = GetEntryIndexWithPageID(
web_contents_->GetSiteInstance(),
params.page_id);
if (existing_entry_index == -1) {
NOTREACHED();
LOG(ERROR) << "terminating renderer for bad navigation: " << params.url;
content::RecordAction(UserMetricsAction("BadMessageTerminate_NC"));
std::string temp = params.url.spec();
temp.append("#page");
temp.append(base::IntToString(params.page_id));
temp.append("#max");
temp.append(base::IntToString(web_contents_->GetMaxPageID()));
temp.append("#frame");
temp.append(base::IntToString(params.frame_id));
temp.append("#ids");
for (int i = 0; i < static_cast<int>(entries_.size()); ++i) {
temp.append(base::IntToString(entries_[i]->GetPageID()));
temp.append("_");
if (entries_[i]->site_instance())
temp.append(base::IntToString(entries_[i]->site_instance()->GetId()));
else
temp.append("N");
if (entries_[i]->site_instance() != web_contents_->GetSiteInstance())
temp.append("x");
temp.append(",");
}
GURL url(temp);
static_cast<RenderViewHostImpl*>(
web_contents_->GetRenderViewHost())->Send(
new ViewMsg_TempCrashWithData(url));
return content::NAVIGATION_TYPE_NAV_IGNORE;
}
NavigationEntryImpl* existing_entry = entries_[existing_entry_index].get();
if (!content::PageTransitionIsMainFrame(params.transition)) {
DCHECK(GetLastCommittedEntry());
return content::NAVIGATION_TYPE_AUTO_SUBFRAME;
}
if (pending_entry_ &&
existing_entry != pending_entry_ &&
pending_entry_->GetPageID() == -1 &&
existing_entry == GetLastCommittedEntry()) {
return content::NAVIGATION_TYPE_SAME_PAGE;
}
if (AreURLsInPageNavigation(existing_entry->GetURL(), params.url,
params.was_within_same_page)) {
return content::NAVIGATION_TYPE_IN_PAGE;
}
return content::NAVIGATION_TYPE_EXISTING_PAGE;
}
bool NavigationControllerImpl::IsRedirect(
const ViewHostMsg_FrameNavigate_Params& params) {
if (content::PageTransitionIsMainFrame(params.transition)) {
return content::PageTransitionIsRedirect(params.transition);
}
return params.redirects.size() > 1;
}
void NavigationControllerImpl::RendererDidNavigateToNewPage(
const ViewHostMsg_FrameNavigate_Params& params, bool replace_entry) {
NavigationEntryImpl* new_entry;
bool update_virtual_url;
if (pending_entry_) {
new_entry = new NavigationEntryImpl(*pending_entry_);
new_entry->set_page_type(content::PAGE_TYPE_NORMAL);
update_virtual_url = new_entry->update_virtual_url_with_url();
} else {
new_entry = new NavigationEntryImpl;
GURL url = params.url;
bool needs_update = false;
BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary(
&url, browser_context_, &needs_update);
new_entry->set_update_virtual_url_with_url(needs_update);
update_virtual_url = needs_update;
}
new_entry->SetURL(params.url);
if (update_virtual_url)
UpdateVirtualURLToURL(new_entry, params.url);
new_entry->SetReferrer(params.referrer);
new_entry->SetPageID(params.page_id);
new_entry->SetTransitionType(params.transition);
new_entry->set_site_instance(
static_cast<SiteInstanceImpl*>(web_contents_->GetSiteInstance()));
new_entry->SetHasPostData(params.is_post);
new_entry->SetPostID(params.post_id);
new_entry->SetOriginalRequestURL(params.original_request_url);
new_entry->SetIsOverridingUserAgent(params.is_overriding_user_agent);
InsertOrReplaceEntry(new_entry, replace_entry);
}
void NavigationControllerImpl::RendererDidNavigateToExistingPage(
const ViewHostMsg_FrameNavigate_Params& params) {
DCHECK(content::PageTransitionIsMainFrame(params.transition));
int entry_index = GetEntryIndexWithPageID(web_contents_->GetSiteInstance(),
params.page_id);
DCHECK(entry_index >= 0 &&
entry_index < static_cast<int>(entries_.size()));
NavigationEntryImpl* entry = entries_[entry_index].get();
entry->SetURL(params.url);
if (entry->update_virtual_url_with_url())
UpdateVirtualURLToURL(entry, params.url);
DCHECK(entry->site_instance() == NULL ||
entry->site_instance() == web_contents_->GetSiteInstance());
entry->set_site_instance(
static_cast<SiteInstanceImpl*>(web_contents_->GetSiteInstance()));
entry->SetHasPostData(params.is_post);
entry->SetPostID(params.post_id);
if (pending_entry_)
DiscardNonCommittedEntriesInternal();
last_committed_entry_index_ =
GetEntryIndexWithPageID(web_contents_->GetSiteInstance(), params.page_id);
}
void NavigationControllerImpl::RendererDidNavigateToSamePage(
const ViewHostMsg_FrameNavigate_Params& params) {
NavigationEntryImpl* existing_entry = GetEntryWithPageID(
web_contents_->GetSiteInstance(), params.page_id);
existing_entry->set_unique_id(pending_entry_->GetUniqueID());
if (existing_entry->update_virtual_url_with_url())
UpdateVirtualURLToURL(existing_entry, params.url);
existing_entry->SetURL(params.url);
DiscardNonCommittedEntries();
}
void NavigationControllerImpl::RendererDidNavigateInPage(
const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) {
DCHECK(content::PageTransitionIsMainFrame(params.transition)) <<
"WebKit should only tell us about in-page navs for the main frame.";
NavigationEntryImpl* existing_entry = GetEntryWithPageID(
web_contents_->GetSiteInstance(), params.page_id);
existing_entry->SetURL(params.url);
if (existing_entry->update_virtual_url_with_url())
UpdateVirtualURLToURL(existing_entry, params.url);
*did_replace_entry = true;
if (pending_entry_)
DiscardNonCommittedEntriesInternal();
last_committed_entry_index_ =
GetEntryIndexWithPageID(web_contents_->GetSiteInstance(), params.page_id);
}
void NavigationControllerImpl::RendererDidNavigateNewSubframe(
const ViewHostMsg_FrameNavigate_Params& params) {
if (content::PageTransitionStripQualifier(params.transition) ==
content::PAGE_TRANSITION_AUTO_SUBFRAME) {
return;
}
DCHECK(GetLastCommittedEntry()) << "ClassifyNavigation should guarantee "
<< "that a last committed entry exists.";
NavigationEntryImpl* new_entry = new NavigationEntryImpl(
*NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry()));
new_entry->SetPageID(params.page_id);
InsertOrReplaceEntry(new_entry, false);
}
bool NavigationControllerImpl::RendererDidNavigateAutoSubframe(
const ViewHostMsg_FrameNavigate_Params& params) {
DCHECK(GetLastCommittedEntry());
int entry_index = GetEntryIndexWithPageID(
web_contents_->GetSiteInstance(),
params.page_id);
if (entry_index < 0 ||
entry_index >= static_cast<int>(entries_.size())) {
NOTREACHED();
return false;
}
if (entry_index != last_committed_entry_index_) {
last_committed_entry_index_ = entry_index;
return true;
}
return false;
}
int NavigationControllerImpl::GetIndexOfEntry(
const NavigationEntryImpl* entry) const {
const NavigationEntries::const_iterator i(std::find(
entries_.begin(),
entries_.end(),
entry));
return (i == entries_.end()) ? -1 : static_cast<int>(i - entries_.begin());
}
bool NavigationControllerImpl::IsURLInPageNavigation(
const GURL& url, bool renderer_says_in_page) const {
NavigationEntry* last_committed = GetLastCommittedEntry();
return last_committed && AreURLsInPageNavigation(
last_committed->GetURL(), url, renderer_says_in_page);
}
void NavigationControllerImpl::CopyStateFrom(
const NavigationController& temp) {
const NavigationControllerImpl& source =
static_cast<const NavigationControllerImpl&>(temp);
DCHECK(GetEntryCount() == 0 && !GetPendingEntry());
if (source.GetEntryCount() == 0)
return;
needs_reload_ = true;
InsertEntriesFrom(source, source.GetEntryCount());
for (SessionStorageNamespaceMap::const_iterator it =
source.session_storage_namespace_map_.begin();
it != source.session_storage_namespace_map_.end();
++it) {
SessionStorageNamespaceImpl* source_namespace =
static_cast<SessionStorageNamespaceImpl*>(it->second.get());
session_storage_namespace_map_.insert(
make_pair(it->first, source_namespace->Clone()));
}
FinishRestore(source.last_committed_entry_index_, false);
web_contents_->CopyMaxPageIDsFrom(source.web_contents());
}
void NavigationControllerImpl::CopyStateFromAndPrune(
NavigationController* temp) {
NavigationControllerImpl* source =
static_cast<NavigationControllerImpl*>(temp);
NavigationEntryImpl* last_committed =
NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry());
scoped_refptr<SiteInstance> site_instance(
last_committed ? last_committed->site_instance() : NULL);
int32 minimum_page_id = last_committed ? last_committed->GetPageID() : -1;
int32 max_page_id = last_committed ?
web_contents_->GetMaxPageIDForSiteInstance(site_instance.get()) : -1;
DCHECK(
(transient_entry_index_ != -1 &&
transient_entry_index_ == GetEntryCount() - 1) ||
(pending_entry_ && (pending_entry_index_ == -1 ||
pending_entry_index_ == GetEntryCount() - 1)) ||
(!pending_entry_ && last_committed_entry_index_ == GetEntryCount() - 1));
PruneAllButActive();
DCHECK(GetEntryCount() == 0 || GetEntryCount() == 1);
if (GetEntryCount() > 0)
source->PruneOldestEntryIfFull();
int max_source_index = source->pending_entry_index_ != -1 ?
source->pending_entry_index_ : source->last_committed_entry_index_;
if (max_source_index == -1)
max_source_index = source->GetEntryCount();
else
max_source_index++;
InsertEntriesFrom(*source, max_source_index);
last_committed_entry_index_ = GetEntryCount() - 1;
if (pending_entry_index_ != -1)
pending_entry_index_ = GetEntryCount() - 1;
if (transient_entry_index_ != -1) {
transient_entry_index_ = GetEntryCount() - 1;
if (last_committed_entry_index_ != -1)
last_committed_entry_index_--;
}
web_contents_->SetHistoryLengthAndPrune(site_instance.get(),
max_source_index,
minimum_page_id);
web_contents_->CopyMaxPageIDsFrom(source->web_contents());
if (max_page_id > -1) {
web_contents_->UpdateMaxPageIDForSiteInstance(site_instance.get(),
max_page_id);
}
}
void NavigationControllerImpl::PruneAllButActive() {
if (transient_entry_index_ != -1) {
DCHECK_EQ(GetEntryCount() - 1, transient_entry_index_);
entries_.erase(entries_.begin(), entries_.begin() + transient_entry_index_);
transient_entry_index_ = 0;
last_committed_entry_index_ = -1;
pending_entry_index_ = -1;
} else if (!pending_entry_) {
if (!GetEntryCount())
return;
DCHECK(last_committed_entry_index_ >= 0);
entries_.erase(entries_.begin(),
entries_.begin() + last_committed_entry_index_);
entries_.erase(entries_.begin() + 1, entries_.end());
last_committed_entry_index_ = 0;
} else if (pending_entry_index_ != -1) {
entries_.erase(entries_.begin(), entries_.begin() + pending_entry_index_);
entries_.erase(entries_.begin() + 1, entries_.end());
pending_entry_index_ = 0;
last_committed_entry_index_ = 0;
} else {
pending_entry_index_ = -1;
last_committed_entry_index_ = -1;
entries_.clear();
}
if (web_contents_->GetInterstitialPage()) {
static_cast<InterstitialPageImpl*>(web_contents_->GetInterstitialPage())->
set_reload_on_dont_proceed(true);
}
}
void NavigationControllerImpl::SetSessionStorageNamespace(
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) {
if (!session_storage_namespace)
return;
bool successful_insert = session_storage_namespace_map_.insert(
make_pair(partition_id,
static_cast<SessionStorageNamespaceImpl*>(
session_storage_namespace)))
.second;
CHECK(successful_insert) << "Cannot replace existing SessionStorageNamespace";
}
void NavigationControllerImpl::SetMaxRestoredPageID(int32 max_id) {
max_restored_page_id_ = max_id;
}
int32 NavigationControllerImpl::GetMaxRestoredPageID() const {
return max_restored_page_id_;
}
SessionStorageNamespace*
NavigationControllerImpl::GetSessionStorageNamespace(
content::SiteInstance* instance) {
std::string partition_id;
if (instance) {
partition_id =
GetContentClient()->browser()->GetStoragePartitionIdForSite(
browser_context_, instance->GetSite());
}
SessionStorageNamespaceMap::const_iterator it =
session_storage_namespace_map_.find(partition_id);
if (it != session_storage_namespace_map_.end())
return it->second.get();
content::StoragePartition* partition =
BrowserContext::GetStoragePartition(browser_context_, instance);
SessionStorageNamespaceImpl* session_storage_namespace =
new SessionStorageNamespaceImpl(
static_cast<DOMStorageContextImpl*>(
partition->GetDOMStorageContext()));
session_storage_namespace_map_[partition_id] = session_storage_namespace;
return session_storage_namespace;
}
SessionStorageNamespace*
NavigationControllerImpl::GetDefaultSessionStorageNamespace() {
return GetSessionStorageNamespace(NULL);
}
const SessionStorageNamespaceMap&
NavigationControllerImpl::GetSessionStorageNamespaceMap() const {
return session_storage_namespace_map_;
}
bool NavigationControllerImpl::NeedsReload() const {
return needs_reload_;
}
void NavigationControllerImpl::RemoveEntryAtIndexInternal(int index) {
DCHECK(index < GetEntryCount());
DCHECK(index != last_committed_entry_index_);
DiscardNonCommittedEntries();
entries_.erase(entries_.begin() + index);
if (last_committed_entry_index_ > index)
last_committed_entry_index_--;
}
void NavigationControllerImpl::DiscardNonCommittedEntries() {
bool transient = transient_entry_index_ != -1;
DiscardNonCommittedEntriesInternal();
if (transient) {
web_contents_->NotifyNavigationStateChanged(kInvalidateAll);
}
}
NavigationEntry* NavigationControllerImpl::GetPendingEntry() const {
return pending_entry_;
}
int NavigationControllerImpl::GetPendingEntryIndex() const {
return pending_entry_index_;
}
void NavigationControllerImpl::InsertOrReplaceEntry(NavigationEntryImpl* entry,
bool replace) {
DCHECK(entry->GetTransitionType() != content::PAGE_TRANSITION_AUTO_SUBFRAME);
const NavigationEntryImpl* const pending_entry =
(pending_entry_index_ == -1) ?
pending_entry_ : entries_[pending_entry_index_].get();
if (pending_entry)
entry->set_unique_id(pending_entry->GetUniqueID());
DiscardNonCommittedEntriesInternal();
int current_size = static_cast<int>(entries_.size());
if (current_size > 0) {
if (replace)
--last_committed_entry_index_;
int num_pruned = 0;
while (last_committed_entry_index_ < (current_size - 1)) {
num_pruned++;
entries_.pop_back();
current_size--;
}
if (num_pruned > 0)
NotifyPrunedEntries(this, false, num_pruned);
}
PruneOldestEntryIfFull();
entries_.push_back(linked_ptr<NavigationEntryImpl>(entry));
last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1;
web_contents_->UpdateMaxPageID(entry->GetPageID());
}
void NavigationControllerImpl::PruneOldestEntryIfFull() {
if (entries_.size() >= max_entry_count()) {
DCHECK_EQ(max_entry_count(), entries_.size());
DCHECK(last_committed_entry_index_ > 0);
RemoveEntryAtIndex(0);
NotifyPrunedEntries(this, true, 1);
}
}
void NavigationControllerImpl::NavigateToPendingEntry(ReloadType reload_type) {
needs_reload_ = false;
if (pending_entry_index_ != -1 &&
pending_entry_index_ == last_committed_entry_index_ &&
(entries_[pending_entry_index_]->restore_type() ==
NavigationEntryImpl::RESTORE_NONE) &&
(entries_[pending_entry_index_]->GetTransitionType() &
content::PAGE_TRANSITION_FORWARD_BACK)) {
web_contents_->Stop();
if (web_contents_->GetInterstitialPage())
web_contents_->GetInterstitialPage()->DontProceed();
DiscardNonCommittedEntries();
return;
}
if (web_contents_->GetInterstitialPage()) {
static_cast<InterstitialPageImpl*>(web_contents_->GetInterstitialPage())->
CancelForNavigation();
}
if (!pending_entry_) {
DCHECK_NE(pending_entry_index_, -1);
pending_entry_ = entries_[pending_entry_index_].get();
}
if (!web_contents_->NavigateToPendingEntry(reload_type))
DiscardNonCommittedEntries();
if (pending_entry_ && !pending_entry_->site_instance() &&
pending_entry_->restore_type() != NavigationEntryImpl::RESTORE_NONE) {
pending_entry_->set_site_instance(static_cast<SiteInstanceImpl*>(
web_contents_->GetPendingSiteInstance()));
pending_entry_->set_restore_type(NavigationEntryImpl::RESTORE_NONE);
}
}
void NavigationControllerImpl::NotifyNavigationEntryCommitted(
content::LoadCommittedDetails* details) {
details->entry = GetActiveEntry();
content::NotificationDetails notification_details =
content::Details<content::LoadCommittedDetails>(details);
ssl_manager_.DidCommitProvisionalLoad(notification_details);
web_contents_->NotifyNavigationStateChanged(kInvalidateAll);
content::NotificationService::current()->Notify(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::Source<NavigationController>(this),
notification_details);
}
size_t NavigationControllerImpl::max_entry_count() {
if (max_entry_count_for_testing_ != kMaxEntryCountForTestingNotSet)
return max_entry_count_for_testing_;
return content::kMaxSessionHistoryEntries;
}
void NavigationControllerImpl::SetActive(bool is_active) {
if (is_active && needs_reload_)
LoadIfNecessary();
}
void NavigationControllerImpl::LoadIfNecessary() {
if (!needs_reload_)
return;
pending_entry_index_ = last_committed_entry_index_;
NavigateToPendingEntry(NO_RELOAD);
}
void NavigationControllerImpl::NotifyEntryChanged(const NavigationEntry* entry,
int index) {
content::EntryChangedDetails det;
det.changed_entry = entry;
det.index = index;
content::NotificationService::current()->Notify(
content::NOTIFICATION_NAV_ENTRY_CHANGED,
content::Source<NavigationController>(this),
content::Details<content::EntryChangedDetails>(&det));
}
void NavigationControllerImpl::FinishRestore(int selected_index,
bool from_last_session) {
DCHECK(selected_index >= 0 && selected_index < GetEntryCount());
ConfigureEntriesForRestore(&entries_, from_last_session);
SetMaxRestoredPageID(static_cast<int32>(GetEntryCount()));
last_committed_entry_index_ = selected_index;
}
void NavigationControllerImpl::DiscardNonCommittedEntriesInternal() {
if (pending_entry_index_ == -1)
delete pending_entry_;
pending_entry_ = NULL;
pending_entry_index_ = -1;
DiscardTransientEntry();
}
void NavigationControllerImpl::DiscardTransientEntry() {
if (transient_entry_index_ == -1)
return;
entries_.erase(entries_.begin() + transient_entry_index_);
if (last_committed_entry_index_ > transient_entry_index_)
last_committed_entry_index_--;
transient_entry_index_ = -1;
}
int NavigationControllerImpl::GetEntryIndexWithPageID(
SiteInstance* instance, int32 page_id) const {
for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) {
if ((entries_[i]->site_instance() == instance) &&
(entries_[i]->GetPageID() == page_id))
return i;
}
return -1;
}
NavigationEntry* NavigationControllerImpl::GetTransientEntry() const {
if (transient_entry_index_ == -1)
return NULL;
return entries_[transient_entry_index_].get();
}
void NavigationControllerImpl::InsertEntriesFrom(
const NavigationControllerImpl& source,
int max_index) {
DCHECK_LE(max_index, source.GetEntryCount());
size_t insert_index = 0;
for (int i = 0; i < max_index; i++) {
if (source.entries_[i].get()->GetPageType() !=
content::PAGE_TYPE_INTERSTITIAL) {
entries_.insert(entries_.begin() + insert_index++,
linked_ptr<NavigationEntryImpl>(
new NavigationEntryImpl(*source.entries_[i])));
}
}
}