summaryrefslogtreecommitdiffstats
path: root/content/browser/web_contents
diff options
context:
space:
mode:
authoravi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-10 21:26:37 +0000
committeravi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-10 21:26:37 +0000
commitf9e4daec200c22fb09df67bcd7f542a2d52fc9ed (patch)
tree5016666f8a1aea4d60dc911e1bcef4feef167e12 /content/browser/web_contents
parente5e84e275e9ef65d280d66f2e8ee3ccb666a1cdb (diff)
downloadchromium_src-f9e4daec200c22fb09df67bcd7f542a2d52fc9ed.zip
chromium_src-f9e4daec200c22fb09df67bcd7f542a2d52fc9ed.tar.gz
chromium_src-f9e4daec200c22fb09df67bcd7f542a2d52fc9ed.tar.bz2
TabContents -> WebContentsImpl, part 3.
BUG=105875 TEST=no change Review URL: http://codereview.chromium.org/9960071 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@131634 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/web_contents')
-rw-r--r--content/browser/web_contents/debug_urls.cc49
-rw-r--r--content/browser/web_contents/debug_urls.h21
-rw-r--r--content/browser/web_contents/drag_utils_gtk.cc38
-rw-r--r--content/browser/web_contents/drag_utils_gtk.h25
-rw-r--r--content/browser/web_contents/interstitial_page_impl.cc750
-rw-r--r--content/browser/web_contents/interstitial_page_impl.h198
-rw-r--r--content/browser/web_contents/navigation_controller_impl.cc1441
-rw-r--r--content/browser/web_contents/navigation_controller_impl.h332
-rw-r--r--content/browser/web_contents/navigation_controller_impl_unittest.cc2592
-rw-r--r--content/browser/web_contents/navigation_entry_impl.cc221
-rw-r--r--content/browser/web_contents/navigation_entry_impl.h217
-rw-r--r--content/browser/web_contents/navigation_entry_impl_unittest.cc178
-rw-r--r--content/browser/web_contents/web_drag_dest_gtk.cc2
-rw-r--r--content/browser/web_contents/web_drag_source_gtk.cc2
14 files changed, 6064 insertions, 2 deletions
diff --git a/content/browser/web_contents/debug_urls.cc b/content/browser/web_contents/debug_urls.cc
new file mode 100644
index 0000000..05f9c41
--- /dev/null
+++ b/content/browser/web_contents/debug_urls.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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.
+
+#include "content/browser/web_contents/debug_urls.h"
+
+#include "content/browser/gpu/gpu_process_host_ui_shim.h"
+#include "content/public/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+
+namespace content {
+
+bool HandleDebugURL(const GURL& url, content::PageTransition transition) {
+ content::PageTransition base_transition =
+ content::PageTransitionStripQualifier(transition);
+ if (base_transition != content::PAGE_TRANSITION_TYPED)
+ return false;
+
+ if (url.host() == chrome::kChromeUIBrowserCrashHost) {
+ // Induce an intentional crash in the browser process.
+ CHECK(false);
+ return true;
+ }
+
+ if (url == GURL(chrome::kChromeUIGpuCleanURL)) {
+ GpuProcessHostUIShim* shim = GpuProcessHostUIShim::GetOneInstance();
+ if (shim)
+ shim->SimulateRemoveAllContext();
+ return true;
+ }
+
+ if (url == GURL(chrome::kChromeUIGpuCrashURL)) {
+ GpuProcessHostUIShim* shim = GpuProcessHostUIShim::GetOneInstance();
+ if (shim)
+ shim->SimulateCrash();
+ return true;
+ }
+
+ if (url == GURL(chrome::kChromeUIGpuHangURL)) {
+ GpuProcessHostUIShim* shim = GpuProcessHostUIShim::GetOneInstance();
+ if (shim)
+ shim->SimulateHang();
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace content
diff --git a/content/browser/web_contents/debug_urls.h b/content/browser/web_contents/debug_urls.h
new file mode 100644
index 0000000..e148675
--- /dev/null
+++ b/content/browser/web_contents/debug_urls.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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.
+
+#ifndef CONTENT_BROWSER_WEB_CONTENTS_DEBUG_URLS_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_DEBUG_URLS_H_
+#pragma once
+
+#include "content/public/common/page_transition_types.h"
+
+class GURL;
+
+namespace content {
+
+// Checks if the given url is a url used for debugging purposes, and if so
+// handles it and returns true.
+bool HandleDebugURL(const GURL& url, content::PageTransition transition);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_DEBUG_URLS_H_
diff --git a/content/browser/web_contents/drag_utils_gtk.cc b/content/browser/web_contents/drag_utils_gtk.cc
new file mode 100644
index 0000000..8164c53
--- /dev/null
+++ b/content/browser/web_contents/drag_utils_gtk.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 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.
+
+#include "content/browser/web_contents/drag_utils_gtk.h"
+
+using WebKit::WebDragOperationsMask;
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationNone;
+using WebKit::WebDragOperationCopy;
+using WebKit::WebDragOperationLink;
+using WebKit::WebDragOperationMove;
+
+namespace content {
+
+GdkDragAction WebDragOpToGdkDragAction(WebDragOperationsMask op) {
+ GdkDragAction action = static_cast<GdkDragAction>(0);
+ if (op & WebDragOperationCopy)
+ action = static_cast<GdkDragAction>(action | GDK_ACTION_COPY);
+ if (op & WebDragOperationLink)
+ action = static_cast<GdkDragAction>(action | GDK_ACTION_LINK);
+ if (op & WebDragOperationMove)
+ action = static_cast<GdkDragAction>(action | GDK_ACTION_MOVE);
+ return action;
+}
+
+WebDragOperationsMask GdkDragActionToWebDragOp(GdkDragAction action) {
+ WebDragOperationsMask op = WebDragOperationNone;
+ if (action & GDK_ACTION_COPY)
+ op = static_cast<WebDragOperationsMask>(op | WebDragOperationCopy);
+ if (action & GDK_ACTION_LINK)
+ op = static_cast<WebDragOperationsMask>(op | WebDragOperationLink);
+ if (action & GDK_ACTION_MOVE)
+ op = static_cast<WebDragOperationsMask>(op | WebDragOperationMove);
+ return op;
+}
+
+} // namespace content
diff --git a/content/browser/web_contents/drag_utils_gtk.h b/content/browser/web_contents/drag_utils_gtk.h
new file mode 100644
index 0000000..f94eeda
--- /dev/null
+++ b/content/browser/web_contents/drag_utils_gtk.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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.
+
+#ifndef CONTENT_BROWSER_WEB_CONTENTS_DRAG_UTILS_GTK_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_DRAG_UTILS_GTK_H_
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "content/common/content_export.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragOperation.h"
+
+namespace content {
+
+// Convenience methods for converting between web drag operations and the GDK
+// equivalent.
+CONTENT_EXPORT GdkDragAction WebDragOpToGdkDragAction(
+ WebKit::WebDragOperationsMask op);
+CONTENT_EXPORT WebKit::WebDragOperationsMask GdkDragActionToWebDragOp(
+ GdkDragAction action);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_DRAG_UTILS_GTK_H_
diff --git a/content/browser/web_contents/interstitial_page_impl.cc b/content/browser/web_contents/interstitial_page_impl.cc
new file mode 100644
index 0000000..0c47523
--- /dev/null
+++ b/content/browser/web_contents/interstitial_page_impl.cc
@@ -0,0 +1,750 @@
+// Copyright (c) 2012 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.
+
+#include "content/browser/web_contents/interstitial_page_impl.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "base/threading/thread.h"
+#include "base/utf_string_conversions.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/resource_dispatcher_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/common/dom_storage_common.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/dom_operation_notification_details.h"
+#include "content/public/browser/interstitial_page_delegate.h"
+#include "content/public/browser/invalidate_type.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/page_transition_types.h"
+#include "content/public/common/view_type.h"
+#include "net/base/escape.h"
+#include "net/url_request/url_request_context_getter.h"
+
+using content::BrowserThread;
+using content::DomOperationNotificationDetails;
+using content::InterstitialPageDelegate;
+using content::NavigationController;
+using content::NavigationEntry;
+using content::NavigationEntryImpl;
+using content::RenderViewHost;
+using content::RenderViewHostImpl;
+using content::RenderViewHostDelegate;
+using content::RenderWidgetHost;
+using content::RenderWidgetHostImpl;
+using content::RenderWidgetHostView;
+using content::RenderWidgetHostViewPort;
+using content::ResourceDispatcherHostImpl;
+using content::SiteInstance;
+using content::WebContents;
+using content::WebContentsView;
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationsMask;
+
+namespace {
+
+void ResourceRequestHelper(ResourceDispatcherHostImpl* rdh,
+ int process_id,
+ int render_view_host_id,
+ ResourceRequestAction action) {
+ switch (action) {
+ case BLOCK:
+ rdh->BlockRequestsForRoute(process_id, render_view_host_id);
+ break;
+ case RESUME:
+ rdh->ResumeBlockedRequestsForRoute(process_id, render_view_host_id);
+ break;
+ case CANCEL:
+ rdh->CancelBlockedRequestsForRoute(process_id, render_view_host_id);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+} // namespace
+
+class InterstitialPageImpl::InterstitialPageRVHViewDelegate
+ : public RenderViewHostDelegate::View {
+ public:
+ explicit InterstitialPageRVHViewDelegate(InterstitialPageImpl* page);
+
+ // RenderViewHostDelegate::View implementation:
+ virtual void CreateNewWindow(
+ int route_id,
+ const ViewHostMsg_CreateWindow_Params& params);
+ virtual void CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type);
+ virtual void CreateNewFullscreenWidget(int route_id);
+ virtual void ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture);
+ virtual void ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos);
+ virtual void ShowCreatedFullscreenWidget(int route_id);
+ virtual void ShowContextMenu(const content::ContextMenuParams& params);
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<WebMenuItem>& items,
+ bool right_aligned);
+ virtual void StartDragging(const WebDropData& drop_data,
+ WebDragOperationsMask operations_allowed,
+ const SkBitmap& image,
+ const gfx::Point& image_offset);
+ virtual void UpdateDragCursor(WebDragOperation operation);
+ virtual void GotFocus();
+ virtual void TakeFocus(bool reverse);
+ virtual void OnFindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+
+ private:
+ InterstitialPageImpl* interstitial_page_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPageRVHViewDelegate);
+};
+
+
+// We keep a map of the various blocking pages shown as the UI tests need to
+// be able to retrieve them.
+typedef std::map<WebContents*, InterstitialPageImpl*> InterstitialPageMap;
+static InterstitialPageMap* g_tab_to_interstitial_page;
+
+// Initializes g_tab_to_interstitial_page in a thread-safe manner.
+// Should be called before accessing g_tab_to_interstitial_page.
+static void InitInterstitialPageMap() {
+ if (!g_tab_to_interstitial_page)
+ g_tab_to_interstitial_page = new InterstitialPageMap;
+}
+
+namespace content {
+
+InterstitialPage* InterstitialPage::Create(WebContents* tab,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialPageDelegate* delegate) {
+ return new InterstitialPageImpl(tab, new_navigation, url, delegate);
+}
+
+InterstitialPage* InterstitialPage::GetInterstitialPage(
+ WebContents* web_contents) {
+ InitInterstitialPageMap();
+ InterstitialPageMap::const_iterator iter =
+ g_tab_to_interstitial_page->find(web_contents);
+ if (iter == g_tab_to_interstitial_page->end())
+ return NULL;
+
+ return iter->second;
+}
+
+} // namespace content
+
+InterstitialPageImpl::InterstitialPageImpl(WebContents* tab,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialPageDelegate* delegate)
+ : tab_(static_cast<TabContents*>(tab)),
+ url_(url),
+ new_navigation_(new_navigation),
+ should_discard_pending_nav_entry_(new_navigation),
+ reload_on_dont_proceed_(false),
+ enabled_(true),
+ action_taken_(NO_ACTION),
+ render_view_host_(NULL),
+ original_child_id_(tab->GetRenderProcessHost()->GetID()),
+ original_rvh_id_(tab->GetRenderViewHost()->GetRoutingID()),
+ should_revert_tab_title_(false),
+ tab_was_loading_(false),
+ resource_dispatcher_host_notified_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(rvh_view_delegate_(
+ new InterstitialPageRVHViewDelegate(this))),
+ create_view_(true),
+ delegate_(delegate) {
+ InitInterstitialPageMap();
+ // It would be inconsistent to create an interstitial with no new navigation
+ // (which is the case when the interstitial was triggered by a sub-resource on
+ // a page) when we have a pending entry (in the process of loading a new top
+ // frame).
+ DCHECK(new_navigation || !tab->GetController().GetPendingEntry());
+}
+
+InterstitialPageImpl::~InterstitialPageImpl() {
+}
+
+void InterstitialPageImpl::Show() {
+ // If an interstitial is already showing or about to be shown, close it before
+ // showing the new one.
+ // Be careful not to take an action on the old interstitial more than once.
+ InterstitialPageMap::const_iterator iter =
+ g_tab_to_interstitial_page->find(tab_);
+ if (iter != g_tab_to_interstitial_page->end()) {
+ InterstitialPageImpl* interstitial = iter->second;
+ if (interstitial->action_taken_ != NO_ACTION) {
+ interstitial->Hide();
+ delete interstitial;
+ } else {
+ // If we are currently showing an interstitial page for which we created
+ // a transient entry and a new interstitial is shown as the result of a
+ // new browser initiated navigation, then that transient entry has already
+ // been discarded and a new pending navigation entry created.
+ // So we should not discard that new pending navigation entry.
+ // See http://crbug.com/9791
+ if (new_navigation_ && interstitial->new_navigation_)
+ interstitial->should_discard_pending_nav_entry_= false;
+ interstitial->DontProceed();
+ }
+ }
+
+ // Block the resource requests for the render view host while it is hidden.
+ TakeActionOnResourceDispatcher(BLOCK);
+ // We need to be notified when the RenderViewHost is destroyed so we can
+ // cancel the blocked requests. We cannot do that on
+ // NOTIFY_TAB_CONTENTS_DESTROYED as at that point the RenderViewHost has
+ // already been destroyed.
+ notification_registrar_.Add(
+ this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
+ content::Source<RenderWidgetHost>(tab_->GetRenderViewHost()));
+
+ // Update the g_tab_to_interstitial_page map.
+ iter = g_tab_to_interstitial_page->find(tab_);
+ DCHECK(iter == g_tab_to_interstitial_page->end());
+ (*g_tab_to_interstitial_page)[tab_] = this;
+
+ if (new_navigation_) {
+ NavigationEntryImpl* entry = new NavigationEntryImpl;
+ entry->SetURL(url_);
+ entry->SetVirtualURL(url_);
+ entry->set_page_type(content::PAGE_TYPE_INTERSTITIAL);
+
+ // Give delegates a chance to set some states on the navigation entry.
+ delegate_->OverrideEntry(entry);
+
+ tab_->GetControllerImpl().AddTransientEntry(entry);
+ }
+
+ DCHECK(!render_view_host_);
+ render_view_host_ = static_cast<RenderViewHostImpl*>(CreateRenderViewHost());
+ CreateWebContentsView();
+
+ std::string data_url = "data:text/html;charset=utf-8," +
+ net::EscapePath(delegate_->GetHTMLContents());
+ render_view_host_->NavigateToURL(GURL(data_url));
+
+ notification_registrar_.Add(this,
+ content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ content::Source<WebContents>(tab_));
+ notification_registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
+ content::Source<NavigationController>(&tab_->GetController()));
+ notification_registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
+ content::Source<NavigationController>(&tab_->GetController()));
+ notification_registrar_.Add(
+ this, content::NOTIFICATION_DOM_OPERATION_RESPONSE,
+ content::Source<RenderViewHost>(render_view_host_));
+}
+
+void InterstitialPageImpl::Hide() {
+ RenderWidgetHostView* old_view = tab_->GetRenderViewHost()->GetView();
+ if (tab_->GetInterstitialPage() == this &&
+ old_view && !old_view->IsShowing()) {
+ // Show the original RVH since we're going away. Note it might not exist if
+ // the renderer crashed while the interstitial was showing.
+ // Note that it is important that we don't call Show() if the view is
+ // already showing. That would result in bad things (unparented HWND on
+ // Windows for example) happening.
+ old_view->Show();
+ }
+
+ // If the focus was on the interstitial, let's keep it to the page.
+ // (Note that in unit-tests the RVH may not have a view).
+ if (render_view_host_->GetView() &&
+ render_view_host_->GetView()->HasFocus() &&
+ tab_->GetRenderViewHost()->GetView()) {
+ RenderWidgetHostViewPort::FromRWHV(
+ tab_->GetRenderViewHost()->GetView())->Focus();
+ }
+
+ render_view_host_->Shutdown();
+ render_view_host_ = NULL;
+ if (tab_->GetInterstitialPage())
+ tab_->remove_interstitial_page();
+ // Let's revert to the original title if necessary.
+ NavigationEntry* entry = tab_->GetController().GetActiveEntry();
+ if (!new_navigation_ && should_revert_tab_title_) {
+ entry->SetTitle(original_tab_title_);
+ tab_->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
+ }
+
+ content::NotificationService::current()->Notify(
+ content::NOTIFICATION_INTERSTITIAL_DETACHED,
+ content::Source<WebContents>(tab_),
+ content::NotificationService::NoDetails());
+
+ InterstitialPageMap::iterator iter = g_tab_to_interstitial_page->find(tab_);
+ DCHECK(iter != g_tab_to_interstitial_page->end());
+ if (iter != g_tab_to_interstitial_page->end())
+ g_tab_to_interstitial_page->erase(iter);
+}
+
+void InterstitialPageImpl::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case content::NOTIFICATION_NAV_ENTRY_PENDING:
+ // We are navigating away from the interstitial (the user has typed a URL
+ // in the location bar or clicked a bookmark). Make sure clicking on the
+ // interstitial will have no effect. Also cancel any blocked requests
+ // on the ResourceDispatcherHost. Note that when we get this notification
+ // the RenderViewHost has not yet navigated so we'll unblock the
+ // RenderViewHost before the resource request for the new page we are
+ // navigating arrives in the ResourceDispatcherHost. This ensures that
+ // request won't be blocked if the same RenderViewHost was used for the
+ // new navigation.
+ Disable();
+ TakeActionOnResourceDispatcher(CANCEL);
+ break;
+ case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED:
+ if (action_taken_ == NO_ACTION) {
+ // The RenderViewHost is being destroyed (as part of the tab being
+ // closed); make sure we clear the blocked requests.
+ RenderViewHost* rvh = static_cast<RenderViewHost*>(
+ static_cast<RenderViewHostImpl*>(
+ RenderWidgetHostImpl::From(
+ content::Source<RenderWidgetHost>(source).ptr())));
+ DCHECK(rvh->GetProcess()->GetID() == original_child_id_ &&
+ rvh->GetRoutingID() == original_rvh_id_);
+ TakeActionOnResourceDispatcher(CANCEL);
+ }
+ break;
+ case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
+ case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
+ if (action_taken_ == NO_ACTION) {
+ // We are navigating away from the interstitial or closing a tab with an
+ // interstitial. Default to DontProceed(). We don't just call Hide as
+ // subclasses will almost certainly override DontProceed to do some work
+ // (ex: close pending connections).
+ DontProceed();
+ } else {
+ // User decided to proceed and either the navigation was committed or
+ // the tab was closed before that.
+ Hide();
+ delete this;
+ }
+ break;
+ case content::NOTIFICATION_DOM_OPERATION_RESPONSE:
+ if (enabled()) {
+ content::Details<DomOperationNotificationDetails> dom_op_details(
+ details);
+ delegate_->CommandReceived(dom_op_details->json);
+ }
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+RenderViewHostDelegate::View* InterstitialPageImpl::GetViewDelegate() {
+ return rvh_view_delegate_.get();
+}
+
+const GURL& InterstitialPageImpl::GetURL() const {
+ return url_;
+}
+
+void InterstitialPageImpl::RenderViewGone(RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code) {
+ // Our renderer died. This should not happen in normal cases.
+ // Just dismiss the interstitial.
+ DontProceed();
+}
+
+void InterstitialPageImpl::DidNavigate(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // A fast user could have navigated away from the page that triggered the
+ // interstitial while the interstitial was loading, that would have disabled
+ // us. In that case we can dismiss ourselves.
+ if (!enabled_) {
+ DontProceed();
+ return;
+ }
+ if (params.transition == content::PAGE_TRANSITION_AUTO_SUBFRAME) {
+ // No need to handle navigate message from iframe in the interstitial page.
+ return;
+ }
+
+ // The RenderViewHost has loaded its contents, we can show it now.
+ render_view_host_->GetView()->Show();
+ tab_->set_interstitial_page(this);
+
+ // This notification hides the bookmark bar. Note that this has to happen
+ // after the interstitial page was registered with |tab_|, since there will be
+ // a callback to |tab_| testing if an interstitial page is showing before
+ // hiding the bookmark bar.
+ content::NotificationService::current()->Notify(
+ content::NOTIFICATION_INTERSTITIAL_ATTACHED,
+ content::Source<WebContents>(tab_),
+ content::NotificationService::NoDetails());
+
+ RenderWidgetHostView* rwh_view = tab_->GetRenderViewHost()->GetView();
+
+ // The RenderViewHost may already have crashed before we even get here.
+ if (rwh_view) {
+ // If the page has focus, focus the interstitial.
+ if (rwh_view->HasFocus())
+ Focus();
+
+ // Hide the original RVH since we're showing the interstitial instead.
+ rwh_view->Hide();
+ }
+
+ // Notify the tab we are not loading so the throbber is stopped. It also
+ // causes a NOTIFY_LOAD_STOP notification, that the AutomationProvider (used
+ // by the UI tests) expects to consider a navigation as complete. Without
+ // this, navigating in a UI test to a URL that triggers an interstitial would
+ // hang.
+ tab_was_loading_ = tab_->IsLoading();
+ tab_->SetIsLoading(false, NULL);
+}
+
+void InterstitialPageImpl::UpdateTitle(
+ RenderViewHost* render_view_host,
+ int32 page_id,
+ const string16& title,
+ base::i18n::TextDirection title_direction) {
+ DCHECK(render_view_host == render_view_host_);
+ NavigationEntry* entry = tab_->GetController().GetActiveEntry();
+ if (!entry) {
+ // Crash reports from the field indicate this can be NULL.
+ // This is unexpected as InterstitialPages constructed with the
+ // new_navigation flag set to true create a transient navigation entry
+ // (that is returned as the active entry). And the only case so far of
+ // interstitial created with that flag set to false is with the
+ // SafeBrowsingBlockingPage, when the resource triggering the interstitial
+ // is a sub-resource, meaning the main page has already been loaded and a
+ // navigation entry should have been created.
+ NOTREACHED();
+ return;
+ }
+
+ // If this interstitial is shown on an existing navigation entry, we'll need
+ // to remember its title so we can revert to it when hidden.
+ if (!new_navigation_ && !should_revert_tab_title_) {
+ original_tab_title_ = entry->GetTitle();
+ should_revert_tab_title_ = true;
+ }
+ // TODO(evan): make use of title_direction.
+ // http://code.google.com/p/chromium/issues/detail?id=27094
+ entry->SetTitle(title);
+ tab_->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
+}
+
+content::RendererPreferences InterstitialPageImpl::GetRendererPrefs(
+ content::BrowserContext* browser_context) const {
+ delegate_->OverrideRendererPrefs(&renderer_preferences_);
+ return renderer_preferences_;
+}
+
+WebPreferences InterstitialPageImpl::GetWebkitPrefs() {
+ return TabContents::GetWebkitPrefs(render_view_host_, url_);
+}
+
+bool InterstitialPageImpl::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ return tab_->PreHandleKeyboardEvent(event, is_keyboard_shortcut);
+}
+
+void InterstitialPageImpl::HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ return tab_->HandleKeyboardEvent(event);
+}
+
+WebContents* InterstitialPageImpl::tab() const {
+ return tab_;
+}
+
+RenderViewHost* InterstitialPageImpl::CreateRenderViewHost() {
+ RenderViewHostImpl* render_view_host = new RenderViewHostImpl(
+ SiteInstance::Create(tab()->GetBrowserContext()),
+ this, MSG_ROUTING_NONE, kInvalidSessionStorageNamespaceId);
+ return render_view_host;
+}
+
+WebContentsView* InterstitialPageImpl::CreateWebContentsView() {
+ if (!create_view_)
+ return NULL;
+ WebContentsView* web_contents_view = tab()->GetView();
+ RenderWidgetHostView* view =
+ web_contents_view->CreateViewForWidget(render_view_host_);
+ render_view_host_->SetView(view);
+ render_view_host_->AllowBindings(content::BINDINGS_POLICY_DOM_AUTOMATION);
+
+ int32 max_page_id =
+ tab()->GetMaxPageIDForSiteInstance(render_view_host_->GetSiteInstance());
+ render_view_host_->CreateRenderView(string16(), max_page_id);
+ view->SetSize(web_contents_view->GetContainerSize());
+ // Don't show the interstitial until we have navigated to it.
+ view->Hide();
+ return web_contents_view;
+}
+
+void InterstitialPageImpl::Proceed() {
+ if (action_taken_ != NO_ACTION) {
+ NOTREACHED();
+ return;
+ }
+ Disable();
+ action_taken_ = PROCEED_ACTION;
+
+ // Resumes the throbber, if applicable.
+ if (tab_was_loading_)
+ tab_->SetIsLoading(true, NULL);
+
+ // If this is a new navigation, the old page is going away, so we cancel any
+ // blocked requests for it. If it is not a new navigation, then it means the
+ // interstitial was shown as a result of a resource loading in the page.
+ // Since the user wants to proceed, we'll let any blocked request go through.
+ if (new_navigation_)
+ TakeActionOnResourceDispatcher(CANCEL);
+ else
+ TakeActionOnResourceDispatcher(RESUME);
+
+ // No need to hide if we are a new navigation, we'll get hidden when the
+ // navigation is committed.
+ if (!new_navigation_) {
+ Hide();
+ delegate_->OnProceed();
+ delete this;
+ return;
+ }
+
+ delegate_->OnProceed();
+}
+
+void InterstitialPageImpl::DontProceed() {
+ DCHECK(action_taken_ != DONT_PROCEED_ACTION);
+
+ Disable();
+ action_taken_ = DONT_PROCEED_ACTION;
+
+ // If this is a new navigation, we are returning to the original page, so we
+ // resume blocked requests for it. If it is not a new navigation, then it
+ // means the interstitial was shown as a result of a resource loading in the
+ // page and we won't return to the original page, so we cancel blocked
+ // requests in that case.
+ if (new_navigation_)
+ TakeActionOnResourceDispatcher(RESUME);
+ else
+ TakeActionOnResourceDispatcher(CANCEL);
+
+ if (should_discard_pending_nav_entry_) {
+ // Since no navigation happens we have to discard the transient entry
+ // explicitely. Note that by calling DiscardNonCommittedEntries() we also
+ // discard the pending entry, which is what we want, since the navigation is
+ // cancelled.
+ tab_->GetController().DiscardNonCommittedEntries();
+ }
+
+ if (reload_on_dont_proceed_)
+ tab_->GetController().Reload(true);
+
+ Hide();
+ delegate_->OnDontProceed();
+ delete this;
+}
+
+void InterstitialPageImpl::CancelForNavigation() {
+ // The user is trying to navigate away. We should unblock the renderer and
+ // disable the interstitial, but keep it visible until the navigation
+ // completes.
+ Disable();
+ // If this interstitial was shown for a new navigation, allow any navigations
+ // on the original page to resume (e.g., subresource requests, XHRs, etc).
+ // Otherwise, cancel the pending, possibly dangerous navigations.
+ if (new_navigation_)
+ TakeActionOnResourceDispatcher(RESUME);
+ else
+ TakeActionOnResourceDispatcher(CANCEL);
+}
+
+void InterstitialPageImpl::SetSize(const gfx::Size& size) {
+#if !defined(OS_MACOSX)
+ // When a tab is closed, we might be resized after our view was NULLed
+ // (typically if there was an info-bar).
+ if (render_view_host_->GetView())
+ render_view_host_->GetView()->SetSize(size);
+#else
+ // TODO(port): Does Mac need to SetSize?
+ NOTIMPLEMENTED();
+#endif
+}
+
+void InterstitialPageImpl::Focus() {
+ // Focus the native window.
+ RenderWidgetHostViewPort::FromRWHV(render_view_host_->GetView())->Focus();
+}
+
+void InterstitialPageImpl::FocusThroughTabTraversal(bool reverse) {
+ render_view_host_->SetInitialFocus(reverse);
+}
+
+RenderViewHost* InterstitialPageImpl::GetRenderViewHostForTesting() const {
+ return render_view_host_;
+}
+
+InterstitialPageDelegate* InterstitialPageImpl::GetDelegateForTesting() {
+ return delegate_.get();
+}
+
+void InterstitialPageImpl::DontCreateViewForTesting() {
+ create_view_ = false;
+}
+
+content::ViewType InterstitialPageImpl::GetRenderViewType() const {
+ return content::VIEW_TYPE_INTERSTITIAL_PAGE;
+}
+
+gfx::Rect InterstitialPageImpl::GetRootWindowResizerRect() const {
+ return gfx::Rect();
+}
+
+void InterstitialPageImpl::Disable() {
+ enabled_ = false;
+}
+
+void InterstitialPageImpl::TakeActionOnResourceDispatcher(
+ ResourceRequestAction action) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)) <<
+ "TakeActionOnResourceDispatcher should be called on the main thread.";
+
+ if (action == CANCEL || action == RESUME) {
+ if (resource_dispatcher_host_notified_)
+ return;
+ resource_dispatcher_host_notified_ = true;
+ }
+
+ // The tab might not have a render_view_host if it was closed (in which case,
+ // we have taken care of the blocked requests when processing
+ // NOTIFY_RENDER_WIDGET_HOST_DESTROYED.
+ // Also we need to test there is a ResourceDispatcherHostImpl, as when unit-
+ // tests we don't have one.
+ RenderViewHostImpl* rvh = RenderViewHostImpl::FromID(original_child_id_,
+ original_rvh_id_);
+ if (!rvh || !ResourceDispatcherHostImpl::Get())
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &ResourceRequestHelper,
+ ResourceDispatcherHostImpl::Get(),
+ original_child_id_,
+ original_rvh_id_,
+ action));
+}
+
+InterstitialPageImpl::InterstitialPageRVHViewDelegate::
+ InterstitialPageRVHViewDelegate(InterstitialPageImpl* page)
+ : interstitial_page_(page) {
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::CreateNewWindow(
+ int route_id,
+ const ViewHostMsg_CreateWindow_Params& params) {
+ NOTREACHED() << "InterstitialPage does not support showing popups yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::CreateNewWidget(
+ int route_id, WebKit::WebPopupType popup_type) {
+ NOTREACHED() << "InterstitialPage does not support showing drop-downs yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::
+ CreateNewFullscreenWidget(int route_id) {
+ NOTREACHED()
+ << "InterstitialPage does not support showing full screen popups.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::ShowCreatedWindow(
+ int route_id, WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos, bool user_gesture) {
+ NOTREACHED() << "InterstitialPage does not support showing popups yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::ShowCreatedWidget(
+ int route_id, const gfx::Rect& initial_pos) {
+ NOTREACHED() << "InterstitialPage does not support showing drop-downs yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::
+ ShowCreatedFullscreenWidget(int route_id) {
+ NOTREACHED()
+ << "InterstitialPage does not support showing full screen popups.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::ShowContextMenu(
+ const content::ContextMenuParams& params) {
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::ShowPopupMenu(
+ const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<WebMenuItem>& items,
+ bool right_aligned) {
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::StartDragging(
+ const WebDropData& drop_data,
+ WebDragOperationsMask allowed_operations,
+ const SkBitmap& image,
+ const gfx::Point& image_offset) {
+ NOTREACHED() << "InterstitialPage does not support dragging yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::UpdateDragCursor(
+ WebDragOperation) {
+ NOTREACHED() << "InterstitialPage does not support dragging yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::GotFocus() {
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::TakeFocus(
+ bool reverse) {
+ if (!interstitial_page_->tab())
+ return;
+ TabContents* tab = static_cast<TabContents*>(interstitial_page_->tab());
+ if (!tab->GetViewDelegate())
+ return;
+
+ tab->GetViewDelegate()->TakeFocus(reverse);
+}
+
+void InterstitialPageImpl::InterstitialPageRVHViewDelegate::OnFindReply(
+ int request_id, int number_of_matches, const gfx::Rect& selection_rect,
+ int active_match_ordinal, bool final_update) {
+}
diff --git a/content/browser/web_contents/interstitial_page_impl.h b/content/browser/web_contents/interstitial_page_impl.h
new file mode 100644
index 0000000..2b6c026
--- /dev/null
+++ b/content/browser/web_contents/interstitial_page_impl.h
@@ -0,0 +1,198 @@
+// Copyright (c) 2012 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.
+
+#ifndef CONTENT_BROWSER_WEB_CONTENTS_INTERSTITIAL_PAGE_IMPL_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_INTERSTITIAL_PAGE_IMPL_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process_util.h"
+#include "content/public/browser/interstitial_page.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/render_view_host_delegate.h"
+#include "content/public/common/renderer_preferences.h"
+#include "googleurl/src/gurl.h"
+
+class WebContentsImpl;
+
+namespace content {
+class NavigationEntry;
+class RenderViewHostImpl;
+class WebContentsView;
+}
+
+enum ResourceRequestAction {
+ BLOCK,
+ RESUME,
+ CANCEL
+};
+
+class CONTENT_EXPORT InterstitialPageImpl
+ : public NON_EXPORTED_BASE(content::InterstitialPage),
+ public content::NotificationObserver,
+ public content::RenderViewHostDelegate {
+ public:
+ // The different state of actions the user can take in an interstitial.
+ enum ActionState {
+ NO_ACTION, // No action has been taken yet.
+ PROCEED_ACTION, // "Proceed" was selected.
+ DONT_PROCEED_ACTION // "Don't proceed" was selected.
+ };
+
+ InterstitialPageImpl(content::WebContents* tab,
+ bool new_navigation,
+ const GURL& url,
+ content::InterstitialPageDelegate* delegate);
+ virtual ~InterstitialPageImpl();
+
+ // InterstitialPage implementation:
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual void DontProceed() OVERRIDE;
+ virtual void Proceed() OVERRIDE;
+ virtual content::RenderViewHost* GetRenderViewHostForTesting() const OVERRIDE;
+ virtual content::InterstitialPageDelegate* GetDelegateForTesting() OVERRIDE;
+ virtual void DontCreateViewForTesting() OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+
+ // Allows the user to navigate away by disabling the interstitial, canceling
+ // the pending request, and unblocking the hidden renderer. The interstitial
+ // will stay visible until the navigation completes.
+ void CancelForNavigation();
+
+ // Focus the first (last if reverse is true) element in the interstitial page.
+ // Called when tab traversing.
+ void FocusThroughTabTraversal(bool reverse);
+
+ // See description above field.
+ void set_reload_on_dont_proceed(bool value) {
+ reload_on_dont_proceed_ = value;
+ }
+ bool reload_on_dont_proceed() const { return reload_on_dont_proceed_; }
+
+ protected:
+ // content::NotificationObserver method:
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // RenderViewHostDelegate implementation:
+ virtual View* GetViewDelegate() OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual void RenderViewGone(content::RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void DidNavigate(
+ content::RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) OVERRIDE;
+ virtual void UpdateTitle(content::RenderViewHost* render_view_host,
+ int32 page_id,
+ const string16& title,
+ base::i18n::TextDirection title_direction) OVERRIDE;
+ virtual content::RendererPreferences GetRendererPrefs(
+ content::BrowserContext* browser_context) const OVERRIDE;
+ virtual WebPreferences GetWebkitPrefs() OVERRIDE;
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) OVERRIDE;
+ virtual void HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) OVERRIDE;
+ virtual content::ViewType GetRenderViewType() const OVERRIDE;
+ virtual gfx::Rect GetRootWindowResizerRect() const OVERRIDE;
+
+ bool enabled() const { return enabled_; }
+ content::WebContents* tab() const;
+ const GURL& url() const { return url_; }
+
+ // Creates the RenderViewHost containing the interstitial content.
+ // Overriden in unit tests.
+ virtual content::RenderViewHost* CreateRenderViewHost();
+
+ // Creates the WebContentsView that shows the interstitial RVH.
+ // Overriden in unit tests.
+ virtual content::WebContentsView* CreateWebContentsView();
+
+ // Notification magic.
+ content::NotificationRegistrar notification_registrar_;
+
+ private:
+ class InterstitialPageRVHViewDelegate;
+
+ // Disable the interstitial:
+ // - if it is not yet showing, then it won't be shown.
+ // - any command sent by the RenderViewHost will be ignored.
+ void Disable();
+
+ // Executes the passed action on the ResourceDispatcher (on the IO thread).
+ // Used to block/resume/cancel requests for the RenderViewHost hidden by this
+ // interstitial.
+ void TakeActionOnResourceDispatcher(ResourceRequestAction action);
+
+ // The contents in which we are displayed.
+ WebContentsImpl* tab_;
+
+ // The URL that is shown when the interstitial is showing.
+ GURL url_;
+
+ // Whether this interstitial is shown as a result of a new navigation (in
+ // which case a transient navigation entry is created).
+ bool new_navigation_;
+
+ // Whether we should discard the pending navigation entry when not proceeding.
+ // This is to deal with cases where |new_navigation_| is true but a new
+ // pending entry was created since this interstitial was shown and we should
+ // not discard it.
+ bool should_discard_pending_nav_entry_;
+
+ // If true and the user chooses not to proceed the target NavigationController
+ // is reloaded. This is used when two NavigationControllers are merged
+ // (CopyStateFromAndPrune).
+ // The default is false.
+ bool reload_on_dont_proceed_;
+
+ // Whether this interstitial is enabled. See Disable() for more info.
+ bool enabled_;
+
+ // Whether the Proceed or DontProceed methods have been called yet.
+ ActionState action_taken_;
+
+ // The RenderViewHost displaying the interstitial contents.
+ content::RenderViewHostImpl* render_view_host_;
+
+ // The IDs for the Render[View|Process]Host hidden by this interstitial.
+ int original_child_id_;
+ int original_rvh_id_;
+
+ // Whether or not we should change the title of the tab when hidden (to revert
+ // it to its original value).
+ bool should_revert_tab_title_;
+
+ // Whether or not the tab was loading resources when the interstitial was
+ // shown. We restore this state if the user proceeds from the interstitial.
+ bool tab_was_loading_;
+
+ // Whether the ResourceDispatcherHost has been notified to cancel/resume the
+ // resource requests blocked for the RenderViewHost.
+ bool resource_dispatcher_host_notified_;
+
+ // The original title of the tab that should be reverted to when the
+ // interstitial is hidden.
+ string16 original_tab_title_;
+
+ // Our RenderViewHostViewDelegate, necessary for accelerators to work.
+ scoped_ptr<InterstitialPageRVHViewDelegate> rvh_view_delegate_;
+
+ // Settings passed to the renderer.
+ mutable content::RendererPreferences renderer_preferences_;
+
+ bool create_view_;
+
+ scoped_ptr<content::InterstitialPageDelegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPageImpl);
+};
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_INTERSTITIAL_PAGE_IMPL_H_
diff --git a/content/browser/web_contents/navigation_controller_impl.cc b/content/browser/web_contents/navigation_controller_impl.cc
new file mode 100644
index 0000000..6b43023
--- /dev/null
+++ b/content/browser/web_contents/navigation_controller_impl.cc
@@ -0,0 +1,1441 @@
+// Copyright (c) 2012 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.
+
+#include "content/browser/web_contents/navigation_controller_impl.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_number_conversions.h" // Temporary
+#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/in_process_webkit/dom_storage_context_impl.h"
+#include "content/browser/in_process_webkit/session_storage_namespace_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h" // Temporary
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/tab_contents/tab_contents.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/common/view_messages.h"
+#include "content/public/browser/browser_context.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/user_metrics.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/common/content_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::GlobalRequestID;
+using content::NavigationController;
+using content::NavigationEntry;
+using content::NavigationEntryImpl;
+using content::RenderViewHostImpl;
+using content::SessionStorageNamespace;
+using content::SiteInstance;
+using content::UserMetricsAction;
+using content::WebContents;
+
+namespace {
+
+const int kInvalidateAll = 0xFFFFFFFF;
+
+// Invoked when entries have been pruned, or removed. For example, if the
+// current entries are [google, digg, yahoo], with the current entry google,
+// and the user types in cnet, then digg and yahoo are pruned.
+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));
+}
+
+// Ensure the given NavigationEntry has a valid state, so that WebKit does not
+// get confused if we navigate back to it.
+//
+// An empty state is treated as a new navigation by WebKit, which would mean
+// losing the navigation entries and generating a new navigation entry after
+// this one. We don't want that. To avoid this we create a valid state which
+// WebKit will not treat as a new navigation.
+void SetContentStateIfEmpty(NavigationEntryImpl* entry) {
+ if (entry->GetContentState().empty()) {
+ entry->SetContentState(
+ webkit_glue::CreateHistoryStateForURL(entry->GetURL()));
+ }
+}
+
+// Configure all the NavigationEntries in entries for restore. This resets
+// the transition type to reload and makes sure the content state isn't empty.
+void ConfigureEntriesForRestore(
+ std::vector<linked_ptr<NavigationEntryImpl> >* entries,
+ bool from_last_session) {
+ for (size_t i = 0; i < entries->size(); ++i) {
+ // Use a transition type of reload so that we don't incorrectly increase
+ // the typed count.
+ (*entries)[i]->SetTransitionType(content::PAGE_TRANSITION_RELOAD);
+ (*entries)[i]->set_restore_type(from_last_session ?
+ NavigationEntryImpl::RESTORE_LAST_SESSION :
+ NavigationEntryImpl::RESTORE_CURRENT_SESSION);
+ // NOTE(darin): This code is only needed for backwards compat.
+ SetContentStateIfEmpty((*entries)[i].get());
+ }
+}
+
+// See NavigationController::IsURLInPageNavigation for how this works and why.
+bool AreURLsInPageNavigation(const GURL& existing_url, const GURL& new_url) {
+ if (existing_url == new_url || !new_url.has_ref()) {
+ // TODO(jcampan): what about when navigating back from a ref URL to the top
+ // non ref URL? Nothing is loaded in that case but we return false here.
+ // The user could also navigate from the ref URL to the non ref URL by
+ // entering the non ref URL in the location bar or through a bookmark, in
+ // which case there would be a load. I am not sure if the non-load/load
+ // scenarios can be differentiated with the TransitionType.
+ return false;
+ }
+
+ url_canon::Replacements<char> replacements;
+ replacements.ClearRef();
+ return existing_url.ReplaceComponents(replacements) ==
+ new_url.ReplaceComponents(replacements);
+}
+
+} // namespace
+
+// NavigationControllerImpl ----------------------------------------------------
+
+const size_t kMaxEntryCountForTestingNotSet = -1;
+
+// static
+size_t NavigationControllerImpl::max_entry_count_for_testing_ =
+ kMaxEntryCountForTestingNotSet;
+
+ // Should Reload check for post data? The default is true, but is set to false
+// when testing.
+static bool g_check_for_repost = true;
+
+namespace content {
+// static
+NavigationEntry* NavigationController::CreateNavigationEntry(
+ const GURL& url,
+ const Referrer& referrer,
+ PageTransition transition,
+ bool is_renderer_initiated,
+ const std::string& extra_headers,
+ BrowserContext* browser_context) {
+ // Allow the browser URL handler to rewrite the URL. This will, for example,
+ // remove "view-source:" from the beginning of the URL to get the URL that
+ // will actually be loaded. This real URL won't be shown to the user, just
+ // used internally.
+ GURL loaded_url(url);
+ bool reverse_on_redirect = false;
+ BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary(
+ &loaded_url, browser_context, &reverse_on_redirect);
+
+ NavigationEntryImpl* entry = new NavigationEntryImpl(
+ NULL, // The site instance for tabs is sent on navigation
+ // (TabContents::GetSiteInstance).
+ -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;
+}
+
+// static
+void NavigationController::DisablePromptOnRepost() {
+ g_check_for_repost = false;
+}
+
+} // namespace content
+
+NavigationControllerImpl::NavigationControllerImpl(
+ TabContents* contents,
+ BrowserContext* browser_context,
+ SessionStorageNamespaceImpl* session_storage_namespace)
+ : browser_context_(browser_context),
+ pending_entry_(NULL),
+ last_committed_entry_index_(-1),
+ pending_entry_index_(-1),
+ transient_entry_index_(-1),
+ tab_contents_(contents),
+ max_restored_page_id_(-1),
+ ALLOW_THIS_IN_INITIALIZER_LIST(ssl_manager_(this)),
+ needs_reload_(false),
+ session_storage_namespace_(session_storage_namespace),
+ pending_reload_(NO_RELOAD) {
+ DCHECK(browser_context_);
+ if (!session_storage_namespace_) {
+ session_storage_namespace_ = new SessionStorageNamespaceImpl(
+ static_cast<DOMStorageContextImpl*>(
+ BrowserContext::GetDOMStorageContext(browser_context_)));
+ }
+}
+
+NavigationControllerImpl::~NavigationControllerImpl() {
+ DiscardNonCommittedEntriesInternal();
+
+ content::NotificationService::current()->Notify(
+ content::NOTIFICATION_TAB_CLOSED,
+ content::Source<NavigationController>(this),
+ content::NotificationService::NoDetails());
+}
+
+WebContents* NavigationControllerImpl::GetWebContents() const {
+ return tab_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) {
+ // Verify that this controller is unused and that the input is valid.
+ 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();
+
+ // And finish the restore.
+ 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::ReloadInternal(bool check_for_repost,
+ ReloadType reload_type) {
+ // Reloading a transient entry does nothing.
+ if (transient_entry_index_ != -1)
+ return;
+
+ DiscardNonCommittedEntriesInternal();
+ int current_index = GetCurrentEntryIndex();
+ // If we are no where, then we can't reload. TODO(darin): We should add a
+ // CanReload method.
+ if (current_index == -1) {
+ return;
+ }
+
+ if (g_check_for_repost && check_for_repost &&
+ GetEntryAtIndex(current_index)->GetHasPostData()) {
+ // The user is asking to reload a page with POST data. Prompt to make sure
+ // they really want to do this. If they do, the dialog will call us back
+ // with check_for_repost = false.
+ content::NotificationService::current()->Notify(
+ content::NOTIFICATION_REPOST_WARNING_SHOWN,
+ content::Source<NavigationController>(this),
+ content::NotificationService::NoDetails());
+
+ pending_reload_ = reload_type;
+ tab_contents_->Activate();
+ tab_contents_->GetDelegate()->ShowRepostFormWarningDialog(tab_contents_);
+ } else {
+ DiscardNonCommittedEntriesInternal();
+
+ NavigationEntryImpl* entry = entries_[current_index].get();
+ SiteInstanceImpl* site_instance = entry->site_instance();
+ DCHECK(site_instance);
+
+ // If we are reloading an entry that no longer belongs to the current
+ // site instance (for example, refreshing a page for just installed app),
+ // the reload must happen in a new process.
+ // The new entry must have a new page_id and site instance, so it behaves
+ // as new navigation (which happens to clear forward history).
+ // Tabs that are discarded due to low memory conditions may not have a site
+ // instance, and should not be treated as a cross-site reload.
+ if (site_instance &&
+ site_instance->HasWrongProcessForURL(entry->GetURL())) {
+ // Create a navigation entry that resembles the current one, but do not
+ // copy page id, site instance, and content state.
+ NavigationEntryImpl* nav_entry = NavigationEntryImpl::FromNavigationEntry(
+ CreateNavigationEntry(
+ entry->GetURL(), entry->GetReferrer(), entry->GetTransitionType(),
+ false, entry->extra_headers(), browser_context_));
+
+ // Mark the reload type as NO_RELOAD, so navigation will not be considered
+ // a reload in the renderer.
+ reload_type = NavigationController::NO_RELOAD;
+
+ nav_entry->set_is_cross_site_reload(true);
+ pending_entry_ = nav_entry;
+ } else {
+ pending_entry_index_ = current_index;
+
+ // The title of the page being reloaded might have been removed in the
+ // meanwhile, so we need to revert to the default title upon reload and
+ // invalidate the previously cached title (SetTitle will do both).
+ // See Chromium issue 96041.
+ 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 last_document_loaded_.is_null();
+}
+
+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) {
+ // Don't navigate to URLs disabled by policy. This prevents showing the URL
+ // on the Omnibar when it is also going to be blocked by
+ // ChildProcessSecurityPolicy::CanRequestURL.
+ 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;
+ }
+
+ // When navigating to a new page, we don't know for sure if we will actually
+ // end up leaving the current page. The new page load could for example
+ // result in a download or a 'no content' response (e.g., a mailto: URL).
+ 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();
+ // Only return the pending_entry for new (non-history), browser-initiated
+ // navigations, in order to prevent URL spoof attacks.
+ // Ideally we would also show the pending entry's URL for new renderer-
+ // initiated navigations with no last committed entry (e.g., a link opening
+ // in a new tab), but an attacker can insert content into the about:blank
+ // page while the pending URL loads in that case.
+ 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(
+ tab_contents_->GetContentsMimeType().c_str());
+ NavigationEntry* active_entry = GetActiveEntry();
+ return active_entry && !active_entry->IsViewSourceMode() &&
+ is_supported_mime_type && !tab_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 = (transient_entry_index_ != -1) ?
+ transient_entry_index_ + offset :
+ last_committed_entry_index_ + offset;
+ if (index < 0 || index >= GetEntryCount())
+ return NULL;
+
+ return entries_[index].get();
+}
+
+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);
+}
+
+void NavigationControllerImpl::GoBack() {
+ if (!CanGoBack()) {
+ NOTREACHED();
+ return;
+ }
+
+ // Base the navigation on where we are now...
+ 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);
+
+ // Base the navigation on where we are now...
+ int current_index = GetCurrentEntryIndex();
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = current_index;
+ // If there was a transient entry, we removed it making the current index
+ // the next page.
+ 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_) {
+ // Nothing to do when navigating to the transient.
+ return;
+ }
+ if (index > transient_entry_index_) {
+ // Removing the transient is goint to shift all entries by 1.
+ 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) {
+ int index = (transient_entry_index_ != -1) ?
+ transient_entry_index_ + offset :
+ last_committed_entry_index_ + offset;
+ if (index < 0 || index >= GetEntryCount())
+ return;
+
+ GoToIndex(index);
+}
+
+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) {
+ // Discard any current transient entry, we can only have one at a time.
+ 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;
+ tab_contents_->NotifyNavigationStateChanged(kInvalidateAll);
+}
+
+void NavigationControllerImpl::TransferURL(
+ const GURL& url,
+ const content::Referrer& referrer,
+ content::PageTransition transition,
+ const std::string& extra_headers,
+ const GlobalRequestID& transferred_global_request_id,
+ bool is_renderer_initiated) {
+ // The user initiated a load, we don't need to reload anymore.
+ needs_reload_ = false;
+
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ CreateNavigationEntry(
+ url, referrer, transition, is_renderer_initiated, extra_headers,
+ browser_context_));
+ entry->set_transferred_global_request_id(transferred_global_request_id);
+
+ LoadEntry(entry);
+}
+
+void NavigationControllerImpl::LoadURL(
+ const GURL& url,
+ const content::Referrer& referrer,
+ content::PageTransition transition,
+ const std::string& extra_headers) {
+ if (content::HandleDebugURL(url, transition))
+ return;
+
+ // The user initiated a load, we don't need to reload anymore.
+ needs_reload_ = false;
+
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ CreateNavigationEntry(
+ url, referrer, transition, false, extra_headers, browser_context_));
+
+ LoadEntry(entry);
+}
+
+void NavigationControllerImpl::LoadURLFromRenderer(
+ const GURL& url,
+ const content::Referrer& referrer,
+ content::PageTransition transition,
+ const std::string& extra_headers) {
+ // The user initiated a load, we don't need to reload anymore.
+ needs_reload_ = false;
+
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ CreateNavigationEntry(
+ url, referrer, transition, true, extra_headers, browser_context_));
+
+ LoadEntry(entry);
+}
+
+void NavigationControllerImpl::DocumentLoadedInFrame() {
+ last_document_loaded_ = base::TimeTicks::Now();
+}
+
+bool NavigationControllerImpl::RendererDidNavigate(
+ const ViewHostMsg_FrameNavigate_Params& params,
+ content::LoadCommittedDetails* details) {
+
+ // Save the previous state before we clobber it.
+ if (GetLastCommittedEntry()) {
+ details->previous_url = GetLastCommittedEntry()->GetURL();
+ details->previous_entry_index = GetLastCommittedEntryIndex();
+ } else {
+ details->previous_url = GURL();
+ details->previous_entry_index = -1;
+ }
+
+ // If we have a pending entry at this point, it should have a SiteInstance.
+ // Restored entries start out with a null SiteInstance, but we should have
+ // assigned one in NavigateToPendingEntry.
+ DCHECK(pending_entry_index_ == -1 || pending_entry_->site_instance());
+
+ // If we are doing a cross-site reload, we need to replace the existing
+ // navigation entry, not add another entry to the history. This has the side
+ // effect of removing forward browsing history, if such existed.
+ if (pending_entry_ != NULL) {
+ details->did_replace_entry = pending_entry_->is_cross_site_reload();
+ }
+
+ // is_in_page must be computed before the entry gets committed.
+ details->is_in_page = IsURLInPageNavigation(params.url);
+
+ // Do navigation-type specific actions. These will make and commit an entry.
+ 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 a pending navigation was in progress, this canceled it. We should
+ // discard it and make sure it is removed from the URL bar. After that,
+ // there is nothing we can do with this navigation, so we just return to
+ // the caller that nothing has happened.
+ if (pending_entry_) {
+ DiscardNonCommittedEntries();
+ tab_contents_->NotifyNavigationStateChanged(
+ content::INVALIDATE_TYPE_URL);
+ }
+ return false;
+ default:
+ NOTREACHED();
+ }
+
+ // All committed entries should have nonempty content state so WebKit doesn't
+ // get confused when we go back to them (see the function for details).
+ DCHECK(!params.content_state.empty());
+ NavigationEntryImpl* active_entry =
+ NavigationEntryImpl::FromNavigationEntry(GetActiveEntry());
+ active_entry->SetContentState(params.content_state);
+
+ // Once committed, we do not need to track if the entry was initiated by
+ // the renderer.
+ active_entry->set_is_renderer_initiated(false);
+
+ // The active entry's SiteInstance should match our SiteInstance.
+ DCHECK(active_entry->site_instance() == tab_contents_->GetSiteInstance());
+
+ // Now prep the rest of the details for the notification and broadcast.
+ 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) {
+ // The renderer generates the page IDs, and so if it gives us the invalid
+ // page ID (-1) we know it didn't actually navigate. This happens in a few
+ // cases:
+ //
+ // - If a page makes a popup navigated to about blank, and then writes
+ // stuff like a subframe navigated to a real page. We'll get the commit
+ // for the subframe, but there won't be any commit for the outer page.
+ //
+ // - We were also getting these for failed loads (for example, bug 21849).
+ // The guess is that we get a "load commit" for the alternate error page,
+ // but that doesn't affect the page ID, so we get the "old" one, which
+ // could be invalid. This can also happen for a cross-site transition
+ // that causes us to swap processes. Then the error page load will be in
+ // a new process with no page IDs ever assigned (and hence a -1 value),
+ // yet the navigation controller still might have previous pages in its
+ // list.
+ //
+ // In these cases, there's nothing we can do with them, so ignore.
+ return content::NAVIGATION_TYPE_NAV_IGNORE;
+ }
+
+ if (params.page_id > tab_contents_->GetMaxPageID()) {
+ // Greater page IDs than we've ever seen before are new pages. We may or may
+ // not have a pending entry for the page, and this may or may not be the
+ // main frame.
+ if (content::PageTransitionIsMainFrame(params.transition))
+ return content::NAVIGATION_TYPE_NEW_PAGE;
+
+ // When this is a new subframe navigation, we should have a committed page
+ // for which it's a suframe in. This may not be the case when an iframe is
+ // navigated on a popup navigated to about:blank (the iframe would be
+ // written into the popup by script on the main page). For these cases,
+ // there isn't any navigation stuff we can do, so just ignore it.
+ if (!GetLastCommittedEntry())
+ return content::NAVIGATION_TYPE_NAV_IGNORE;
+
+ // Valid subframe navigation.
+ return content::NAVIGATION_TYPE_NEW_SUBFRAME;
+ }
+
+ // Now we know that the notification is for an existing page. Find that entry.
+ int existing_entry_index = GetEntryIndexWithPageID(
+ tab_contents_->GetSiteInstance(),
+ params.page_id);
+ if (existing_entry_index == -1) {
+ // The page was not found. It could have been pruned because of the limit on
+ // back/forward entries (not likely since we'll usually tell it to navigate
+ // to such entries). It could also mean that the renderer is smoking crack.
+ NOTREACHED();
+
+ // Because the unknown entry has committed, we risk showing the wrong URL in
+ // release builds. Instead, we'll kill the renderer process to be safe.
+ LOG(ERROR) << "terminating renderer for bad navigation: " << params.url;
+ content::RecordAction(UserMetricsAction("BadMessageTerminate_NC"));
+
+ // Temporary code so we can get more information. Format:
+ // http://url/foo.html#page1#max3#frame1#ids:2_Nx,1_1x,3_2
+ std::string temp = params.url.spec();
+ temp.append("#page");
+ temp.append(base::IntToString(params.page_id));
+ temp.append("#max");
+ temp.append(base::IntToString(tab_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) {
+ // Append entry metadata (e.g., 3_7x):
+ // 3: page_id
+ // 7: SiteInstance ID, or N for null
+ // x: appended if not from the current SiteInstance
+ 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() != tab_contents_->GetSiteInstance())
+ temp.append("x");
+ temp.append(",");
+ }
+ GURL url(temp);
+ static_cast<RenderViewHostImpl*>(
+ tab_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)) {
+ // All manual subframes would get new IDs and were handled above, so we
+ // know this is auto. Since the current page was found in the navigation
+ // entry list, we're guaranteed to have a last committed entry.
+ DCHECK(GetLastCommittedEntry());
+ return content::NAVIGATION_TYPE_AUTO_SUBFRAME;
+ }
+
+ // Anything below here we know is a main frame navigation.
+ if (pending_entry_ &&
+ existing_entry != pending_entry_ &&
+ pending_entry_->GetPageID() == -1 &&
+ existing_entry == GetLastCommittedEntry()) {
+ // In this case, we have a pending entry for a URL but WebCore didn't do a
+ // new navigation. This happens when you press enter in the URL bar to
+ // reload. We will create a pending entry, but WebKit will convert it to
+ // a reload since it's the same page and not create a new entry for it
+ // (the user doesn't want to have a new back/forward entry when they do
+ // this). If this matches the last committed entry, we want to just ignore
+ // the pending entry and go back to where we were (the "existing entry").
+ return content::NAVIGATION_TYPE_SAME_PAGE;
+ }
+
+ // Any toplevel navigations with the same base (minus the reference fragment)
+ // are in-page navigations. We weeded out subframe navigations above. Most of
+ // the time this doesn't matter since WebKit doesn't tell us about subframe
+ // navigations that don't actually navigate, but it can happen when there is
+ // an encoding override (it always sends a navigation request).
+ if (AreURLsInPageNavigation(existing_entry->GetURL(), params.url))
+ return content::NAVIGATION_TYPE_IN_PAGE;
+
+ // Since we weeded out "new" navigations above, we know this is an existing
+ // (back/forward) navigation.
+ return content::NAVIGATION_TYPE_EXISTING_PAGE;
+}
+
+bool NavigationControllerImpl::IsRedirect(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // For main frame transition, we judge by params.transition.
+ // Otherwise, by params.redirects.
+ if (content::PageTransitionIsMainFrame(params.transition)) {
+ return content::PageTransitionIsRedirect(params.transition);
+ }
+ return params.redirects.size() > 1;
+}
+
+void NavigationControllerImpl::RendererDidNavigateToNewPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) {
+ NavigationEntryImpl* new_entry;
+ bool update_virtual_url;
+ if (pending_entry_) {
+ // TODO(brettw) this assumes that the pending entry is appropriate for the
+ // new page that was just loaded. I don't think this is necessarily the
+ // case! We should have some more tracking to know for sure.
+ new_entry = new NavigationEntryImpl(*pending_entry_);
+
+ // Don't use the page type from the pending entry. Some interstitial page
+ // may have set the type to interstitial. Once we commit, however, the page
+ // type must always be normal.
+ new_entry->set_page_type(content::PAGE_TYPE_NORMAL);
+ update_virtual_url = new_entry->update_virtual_url_with_url();
+ } else {
+ new_entry = new NavigationEntryImpl;
+ // When navigating to a new page, give the browser URL handler a chance to
+ // update the virtual URL based on the new URL. For example, this is needed
+ // to show chrome://bookmarks/#1 when the bookmarks webui extension changes
+ // the URL.
+ update_virtual_url = true;
+ }
+
+ 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*>(tab_contents_->GetSiteInstance()));
+ new_entry->SetHasPostData(params.is_post);
+ new_entry->SetPostID(params.post_id);
+
+ InsertOrReplaceEntry(new_entry, *did_replace_entry);
+}
+
+void NavigationControllerImpl::RendererDidNavigateToExistingPage(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // We should only get here for main frame navigations.
+ DCHECK(content::PageTransitionIsMainFrame(params.transition));
+
+ // This is a back/forward navigation. The existing page for the ID is
+ // guaranteed to exist by ClassifyNavigation, and we just need to update it
+ // with new information from the renderer.
+ int entry_index = GetEntryIndexWithPageID(tab_contents_->GetSiteInstance(),
+ params.page_id);
+ DCHECK(entry_index >= 0 &&
+ entry_index < static_cast<int>(entries_.size()));
+ NavigationEntryImpl* entry = entries_[entry_index].get();
+
+ // The URL may have changed due to redirects. The site instance will normally
+ // be the same except during session restore, when no site instance will be
+ // assigned.
+ entry->SetURL(params.url);
+ if (entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(entry, params.url);
+ DCHECK(entry->site_instance() == NULL ||
+ entry->site_instance() == tab_contents_->GetSiteInstance());
+ entry->set_site_instance(
+ static_cast<SiteInstanceImpl*>(tab_contents_->GetSiteInstance()));
+
+ entry->SetHasPostData(params.is_post);
+ entry->SetPostID(params.post_id);
+
+ // The entry we found in the list might be pending if the user hit
+ // back/forward/reload. This load should commit it (since it's already in the
+ // list, we can just discard the pending pointer). We should also discard the
+ // pending entry if it corresponds to a different navigation, since that one
+ // is now likely canceled. If it is not canceled, we will treat it as a new
+ // navigation when it arrives, which is also ok.
+ //
+ // Note that we need to use the "internal" version since we don't want to
+ // actually change any other state, just kill the pointer.
+ if (pending_entry_)
+ DiscardNonCommittedEntriesInternal();
+
+ // If a transient entry was removed, the indices might have changed, so we
+ // have to query the entry index again.
+ last_committed_entry_index_ =
+ GetEntryIndexWithPageID(tab_contents_->GetSiteInstance(), params.page_id);
+}
+
+void NavigationControllerImpl::RendererDidNavigateToSamePage(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // This mode implies we have a pending entry that's the same as an existing
+ // entry for this page ID. This entry is guaranteed to exist by
+ // ClassifyNavigation. All we need to do is update the existing entry.
+ NavigationEntryImpl* existing_entry = GetEntryWithPageID(
+ tab_contents_->GetSiteInstance(), params.page_id);
+
+ // We assign the entry's unique ID to be that of the new one. Since this is
+ // always the result of a user action, we want to dismiss infobars, etc. like
+ // a regular user-initiated navigation.
+ existing_entry->set_unique_id(pending_entry_->GetUniqueID());
+
+ // The URL may have changed due to redirects.
+ 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.";
+ // We're guaranteed to have an entry for this one.
+ NavigationEntryImpl* existing_entry = GetEntryWithPageID(
+ tab_contents_->GetSiteInstance(), params.page_id);
+
+ // Reference fragment navigation. We're guaranteed to have the last_committed
+ // entry and it will be the same page as the new navigation (minus the
+ // reference fragments, of course). We'll update the URL of the existing
+ // entry without pruning the forward history.
+ existing_entry->SetURL(params.url);
+ if (existing_entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(existing_entry, params.url);
+
+ // This replaces the existing entry since the page ID didn't change.
+ *did_replace_entry = true;
+
+ if (pending_entry_)
+ DiscardNonCommittedEntriesInternal();
+
+ // If a transient entry was removed, the indices might have changed, so we
+ // have to query the entry index again.
+ last_committed_entry_index_ =
+ GetEntryIndexWithPageID(tab_contents_->GetSiteInstance(), params.page_id);
+}
+
+void NavigationControllerImpl::RendererDidNavigateNewSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ if (content::PageTransitionStripQualifier(params.transition) ==
+ content::PAGE_TRANSITION_AUTO_SUBFRAME) {
+ // This is not user-initiated. Ignore.
+ return;
+ }
+
+ // Manual subframe navigations just get the current entry cloned so the user
+ // can go back or forward to it. The actual subframe information will be
+ // stored in the page state for each of those entries. This happens out of
+ // band with the actual navigations.
+ 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) {
+ // We're guaranteed to have a previously committed entry, and we now need to
+ // handle navigation inside of a subframe in it without creating a new entry.
+ DCHECK(GetLastCommittedEntry());
+
+ // Handle the case where we're navigating back/forward to a previous subframe
+ // navigation entry. This is case "2." in NAV_AUTO_SUBFRAME comment in the
+ // header file. In case "1." this will be a NOP.
+ int entry_index = GetEntryIndexWithPageID(
+ tab_contents_->GetSiteInstance(),
+ params.page_id);
+ if (entry_index < 0 ||
+ entry_index >= static_cast<int>(entries_.size())) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Update the current navigation entry in case we're going back/forward.
+ 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) const {
+ NavigationEntry* last_committed = GetLastCommittedEntry();
+ if (!last_committed)
+ return false;
+ return AreURLsInPageNavigation(last_committed->GetURL(), url);
+}
+
+void NavigationControllerImpl::CopyStateFrom(
+ const NavigationController& temp) {
+ const NavigationControllerImpl& source =
+ static_cast<const NavigationControllerImpl&>(temp);
+ // Verify that we look new.
+ DCHECK(GetEntryCount() == 0 && !GetPendingEntry());
+
+ if (source.GetEntryCount() == 0)
+ return; // Nothing new to do.
+
+ needs_reload_ = true;
+ InsertEntriesFrom(source, source.GetEntryCount());
+
+ session_storage_namespace_ = source.session_storage_namespace_->Clone();
+
+ FinishRestore(source.last_committed_entry_index_, false);
+
+ // Copy the max page id map from the old tab to the new tab. This ensures
+ // that new and existing navigations in the tab's current SiteInstances
+ // are identified properly.
+ tab_contents_->CopyMaxPageIDsFrom(source.tab_contents());
+}
+
+void NavigationControllerImpl::CopyStateFromAndPrune(
+ NavigationController* temp) {
+ NavigationControllerImpl* source =
+ static_cast<NavigationControllerImpl*>(temp);
+ // The SiteInstance and page_id of the last committed entry needs to be
+ // remembered at this point, in case there is only one committed entry
+ // and it is pruned. We use a scoped_refptr to ensure the SiteInstance
+ // can't be freed during this time period.
+ 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 ?
+ tab_contents_->GetMaxPageIDForSiteInstance(site_instance.get()) : -1;
+
+ // This code is intended for use when the last entry is the active entry.
+ 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));
+
+ // Remove all the entries leaving the active entry.
+ PruneAllButActive();
+
+ // We now have zero or one entries. Ensure that adding the entries from
+ // source won't put us over the limit.
+ DCHECK(GetEntryCount() == 0 || GetEntryCount() == 1);
+ if (GetEntryCount() > 0)
+ source->PruneOldestEntryIfFull();
+
+ // Insert the entries from source. Don't use source->GetCurrentEntryIndex as
+ // we don't want to copy over the transient entry.
+ 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);
+
+ // Adjust indices such that the last entry and pending are at the end now.
+ last_committed_entry_index_ = GetEntryCount() - 1;
+ if (pending_entry_index_ != -1)
+ pending_entry_index_ = GetEntryCount() - 1;
+ if (transient_entry_index_ != -1) {
+ // There's a transient entry. In this case we want the last committed to
+ // point to the previous entry.
+ transient_entry_index_ = GetEntryCount() - 1;
+ if (last_committed_entry_index_ != -1)
+ last_committed_entry_index_--;
+ }
+
+ tab_contents_->SetHistoryLengthAndPrune(site_instance.get(),
+ max_source_index,
+ minimum_page_id);
+
+ // Copy the max page id map from the old tab to the new tab. This ensures
+ // that new and existing navigations in the tab's current SiteInstances
+ // are identified properly.
+ tab_contents_->CopyMaxPageIDsFrom(source->tab_contents());
+
+ // If there is a last committed entry, be sure to include it in the new
+ // max page ID map.
+ if (max_page_id > -1) {
+ tab_contents_->UpdateMaxPageIDForSiteInstance(site_instance.get(),
+ max_page_id);
+ }
+}
+
+void NavigationControllerImpl::PruneAllButActive() {
+ if (transient_entry_index_ != -1) {
+ // There is a transient entry. Prune up to it.
+ 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_) {
+ // There's no pending entry. Leave the last entry (if there is one).
+ 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 {
+ // There is a pending_entry, but it's not in entries_.
+ pending_entry_index_ = -1;
+ last_committed_entry_index_ = -1;
+ entries_.clear();
+ }
+
+ if (tab_contents_->GetInterstitialPage()) {
+ // Normally the interstitial page hides itself if the user doesn't proceeed.
+ // This would result in showing a NavigationEntry we just removed. Set this
+ // so the interstitial triggers a reload if the user doesn't proceed.
+ static_cast<InterstitialPageImpl*>(tab_contents_->GetInterstitialPage())->
+ set_reload_on_dont_proceed(true);
+ }
+}
+
+void NavigationControllerImpl::SetMaxRestoredPageID(int32 max_id) {
+ max_restored_page_id_ = max_id;
+}
+
+int32 NavigationControllerImpl::GetMaxRestoredPageID() const {
+ return max_restored_page_id_;
+}
+
+SessionStorageNamespace*
+ NavigationControllerImpl::GetSessionStorageNamespace() const {
+ return session_storage_namespace_;
+}
+
+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 there was a transient entry, invalidate everything so the new active
+ // entry state is shown.
+ if (transient) {
+ tab_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);
+
+ // Copy the pending entry's unique ID to the committed entry.
+ // I don't know if pending_entry_index_ can be other than -1 here.
+ 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) {
+ // Prune any entries which are in front of the current entry.
+ // Also prune the current entry if we are to replace the current entry.
+ // last_committed_entry_index_ must be updated here since calls to
+ // NotifyPrunedEntries() below may re-enter and we must make sure
+ // last_committed_entry_index_ is not left in an invalid state.
+ 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) // Only notify if we did prune something.
+ NotifyPrunedEntries(this, false, num_pruned);
+ }
+
+ PruneOldestEntryIfFull();
+
+ entries_.push_back(linked_ptr<NavigationEntryImpl>(entry));
+ last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1;
+
+ // This is a new page ID, so we need everybody to know about it.
+ tab_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 we were navigating to a slow-to-commit page, and the user performs
+ // a session history navigation to the last committed page, RenderViewHost
+ // will force the throbber to start, but WebKit will essentially ignore the
+ // navigation, and won't send a message to stop the throbber. To prevent this
+ // from happening, we drop the navigation here and stop the slow-to-commit
+ // page from loading (which would normally happen during the navigation).
+ 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)) {
+ tab_contents_->Stop();
+
+ // If an interstitial page is showing, we want to close it to get back
+ // to what was showing before.
+ if (tab_contents_->GetInterstitialPage())
+ tab_contents_->GetInterstitialPage()->DontProceed();
+
+ DiscardNonCommittedEntries();
+ return;
+ }
+
+ // If an interstitial page is showing, the previous renderer is blocked and
+ // cannot make new requests. Unblock (and disable) it to allow this
+ // navigation to succeed. The interstitial will stay visible until the
+ // resulting DidNavigate.
+ if (tab_contents_->GetInterstitialPage()) {
+ static_cast<InterstitialPageImpl*>(tab_contents_->GetInterstitialPage())->
+ CancelForNavigation();
+ }
+
+ // For session history navigations only the pending_entry_index_ is set.
+ if (!pending_entry_) {
+ DCHECK_NE(pending_entry_index_, -1);
+ pending_entry_ = entries_[pending_entry_index_].get();
+ }
+
+ if (!tab_contents_->NavigateToPendingEntry(reload_type))
+ DiscardNonCommittedEntries();
+
+ // If the entry is being restored and doesn't have a SiteInstance yet, fill
+ // it in now that we know. This allows us to find the entry when it commits.
+ // This works for browser-initiated navigations. We handle renderer-initiated
+ // navigations to restored entries in TabContents::OnGoToEntryAtOffset.
+ if (pending_entry_ && !pending_entry_->site_instance() &&
+ pending_entry_->restore_type() != NavigationEntryImpl::RESTORE_NONE) {
+ pending_entry_->set_site_instance(static_cast<SiteInstanceImpl*>(
+ tab_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);
+
+ // We need to notify the ssl_manager_ before the tab_contents_ so the
+ // location bar will have up-to-date information about the security style
+ // when it wants to draw. See http://crbug.com/11157
+ ssl_manager_.DidCommitProvisionalLoad(notification_details);
+
+ // TODO(pkasting): http://b/1113079 Probably these explicit notification paths
+ // should be removed, and interested parties should just listen for the
+ // notification below instead.
+ tab_contents_->NotifyNavigationStateChanged(kInvalidateAll);
+
+ content::NotificationService::current()->Notify(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED,
+ content::Source<NavigationController>(this),
+ notification_details);
+}
+
+// static
+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;
+
+ // Calling Reload() results in ignoring state, and not loading.
+ // Explicitly use NavigateToPendingEntry so that the renderer uses the
+ // cached state.
+ 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++) {
+ // When cloning a tab, copy all entries except interstitial pages
+ if (source.entries_[i].get()->GetPageType() !=
+ content::PAGE_TYPE_INTERSTITIAL) {
+ entries_.insert(entries_.begin() + insert_index++,
+ linked_ptr<NavigationEntryImpl>(
+ new NavigationEntryImpl(*source.entries_[i])));
+ }
+ }
+}
diff --git a/content/browser/web_contents/navigation_controller_impl.h b/content/browser/web_contents/navigation_controller_impl.h
new file mode 100644
index 0000000..71f1f52
--- /dev/null
+++ b/content/browser/web_contents/navigation_controller_impl.h
@@ -0,0 +1,332 @@
+// Copyright (c) 2012 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.
+
+#ifndef CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_CONTROLLER_IMPL_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_CONTROLLER_IMPL_H_
+#pragma once
+
+#include "build/build_config.h"
+#include "base/compiler_specific.h"
+#include "base/memory/linked_ptr.h"
+#include "base/time.h"
+#include "content/browser/ssl/ssl_manager.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_type.h"
+
+class SessionStorageNamespaceImpl;
+struct ViewHostMsg_FrameNavigate_Params;
+class WebContentsImpl;
+
+namespace content {
+class NavigationEntryImpl;
+struct LoadCommittedDetails;
+class SiteInstance;
+}
+
+class CONTENT_EXPORT NavigationControllerImpl
+ : public NON_EXPORTED_BASE(content::NavigationController) {
+ public:
+ NavigationControllerImpl(
+ WebContentsImpl* tab_contents,
+ content::BrowserContext* browser_context,
+ SessionStorageNamespaceImpl* session_storage_namespace);
+ virtual ~NavigationControllerImpl();
+
+ // NavigationController implementation:
+ virtual content::WebContents* GetWebContents() const OVERRIDE;
+ virtual content::BrowserContext* GetBrowserContext() const OVERRIDE;
+ virtual void SetBrowserContext(
+ content::BrowserContext* browser_context) OVERRIDE;
+ virtual void Restore(
+ int selected_navigation,
+ bool from_last_session,
+ std::vector<content::NavigationEntry*>* entries) OVERRIDE;
+ virtual content::NavigationEntry* GetActiveEntry() const OVERRIDE;
+ virtual content::NavigationEntry* GetVisibleEntry() const OVERRIDE;
+ virtual int GetCurrentEntryIndex() const OVERRIDE;
+ virtual content::NavigationEntry* GetLastCommittedEntry() const OVERRIDE;
+ virtual int GetLastCommittedEntryIndex() const OVERRIDE;
+ virtual bool CanViewSource() const OVERRIDE;
+ virtual int GetEntryCount() const OVERRIDE;
+ virtual content::NavigationEntry* GetEntryAtIndex(int index) const OVERRIDE;
+ virtual content::NavigationEntry* GetEntryAtOffset(int offset) const OVERRIDE;
+ virtual void DiscardNonCommittedEntries() OVERRIDE;
+ virtual content::NavigationEntry* GetPendingEntry() const OVERRIDE;
+ virtual int GetPendingEntryIndex() const OVERRIDE;
+ virtual content::NavigationEntry* GetTransientEntry() const OVERRIDE;
+ virtual void LoadURL(const GURL& url,
+ const content::Referrer& referrer,
+ content::PageTransition type,
+ const std::string& extra_headers) OVERRIDE;
+ virtual void LoadURLFromRenderer(const GURL& url,
+ const content::Referrer& referrer,
+ content::PageTransition type,
+ const std::string& extra_headers) OVERRIDE;
+ virtual void TransferURL(
+ const GURL& url,
+ const content::Referrer& referrer,
+ content::PageTransition transition,
+ const std::string& extra_headers,
+ const content::GlobalRequestID& transferred_global_request_id,
+ bool is_renderer_initiated) OVERRIDE;
+ virtual void LoadIfNecessary() OVERRIDE;
+ virtual bool CanGoBack() const OVERRIDE;
+ virtual bool CanGoForward() const OVERRIDE;
+ virtual void GoBack() OVERRIDE;
+ virtual void GoForward() OVERRIDE;
+ virtual void GoToIndex(int index) OVERRIDE;
+ virtual void GoToOffset(int offset) OVERRIDE;
+ virtual void RemoveEntryAtIndex(int index) OVERRIDE;
+ virtual content::SessionStorageNamespace*
+ GetSessionStorageNamespace() const OVERRIDE;
+ virtual void SetMaxRestoredPageID(int32 max_id) OVERRIDE;
+ virtual int32 GetMaxRestoredPageID() const OVERRIDE;
+ virtual bool NeedsReload() const OVERRIDE;
+ virtual void CancelPendingReload() OVERRIDE;
+ virtual void ContinuePendingReload() OVERRIDE;
+ virtual bool IsInitialNavigation() OVERRIDE;
+ virtual void Reload(bool check_for_repost) OVERRIDE;
+ virtual void ReloadIgnoringCache(bool check_for_repost) OVERRIDE;
+ virtual void NotifyEntryChanged(const content::NavigationEntry* entry,
+ int index) OVERRIDE;
+ virtual void CopyStateFrom(
+ const content::NavigationController& source) OVERRIDE;
+ virtual void CopyStateFromAndPrune(
+ content::NavigationController* source) OVERRIDE;
+ virtual void PruneAllButActive() OVERRIDE;
+
+ // Returns the index of the specified entry, or -1 if entry is not contained
+ // in this NavigationController.
+ int GetIndexOfEntry(const content::NavigationEntryImpl* entry) const;
+
+ // Return the index of the entry with the corresponding instance and page_id,
+ // or -1 if not found.
+ int GetEntryIndexWithPageID(content::SiteInstance* instance,
+ int32 page_id) const;
+
+ // Return the entry with the corresponding instance and page_id, or NULL if
+ // not found.
+ content::NavigationEntryImpl* GetEntryWithPageID(
+ content::SiteInstance* instance,
+ int32 page_id) const;
+
+ // Transient entry -----------------------------------------------------------
+
+ // Adds an entry that is returned by GetActiveEntry(). The entry is
+ // transient: any navigation causes it to be removed and discarded.
+ // The NavigationController becomes the owner of |entry| and deletes it when
+ // it discards it. This is useful with interstitial page that need to be
+ // represented as an entry, but should go away when the user navigates away
+ // from them.
+ // Note that adding a transient entry does not change the active contents.
+ void AddTransientEntry(content::NavigationEntryImpl* entry);
+
+ // WebContentsImpl -----------------------------------------------------------
+
+ WebContentsImpl* tab_contents() const {
+ // This currently returns the active tab contents which should be renamed to
+ // tab_contents.
+ return tab_contents_;
+ }
+
+ // Called when a document has been loaded in a frame.
+ void DocumentLoadedInFrame();
+
+ // For use by WebContentsImpl ------------------------------------------------
+
+ // Handles updating the navigation state after the renderer has navigated.
+ // This is used by the WebContentsImpl.
+ //
+ // If a new entry is created, it will return true and will have filled the
+ // given details structure and broadcast the NOTIFY_NAV_ENTRY_COMMITTED
+ // notification. The caller can then use the details without worrying about
+ // listening for the notification.
+ //
+ // In the case that nothing has changed, the details structure is undefined
+ // and it will return false.
+ bool RendererDidNavigate(const ViewHostMsg_FrameNavigate_Params& params,
+ content::LoadCommittedDetails* details);
+
+ // Notifies us that we just became active. This is used by the WebContentsImpl
+ // so that we know to load URLs that were pending as "lazy" loads.
+ void SetActive(bool is_active);
+
+ // Returns true if the given URL would be an in-page navigation (i.e. only
+ // the reference fragment is different) from the "last committed entry". We do
+ // not compare it against the "active entry" since the active entry can be
+ // pending and in page navigations only happen on committed pages. If there
+ // is no last committed entry, then nothing will be in-page.
+ //
+ // Special note: if the URLs are the same, it does NOT count as an in-page
+ // navigation. Neither does an input URL that has no ref, even if the rest is
+ // the same. This may seem weird, but when we're considering whether a
+ // navigation happened without loading anything, the same URL would be a
+ // reload, while only a different ref would be in-page (pages can't clear
+ // refs without reload, only change to "#" which we don't count as empty).
+ bool IsURLInPageNavigation(const GURL& url) const;
+
+ // Random data ---------------------------------------------------------------
+
+ SSLManager* ssl_manager() { return &ssl_manager_; }
+
+ // Maximum number of entries before we start removing entries from the front.
+ static void set_max_entry_count_for_testing(size_t max_entry_count) {
+ max_entry_count_for_testing_ = max_entry_count;
+ }
+ static size_t max_entry_count();
+
+ private:
+ class RestoreHelper;
+ friend class RestoreHelper;
+ friend class WebContentsImpl; // For invoking OnReservedPageIDRange.
+
+ // Classifies the given renderer navigation (see the NavigationType enum).
+ content::NavigationType ClassifyNavigation(
+ const ViewHostMsg_FrameNavigate_Params& params) const;
+
+ // Causes the controller to load the specified entry. The function assumes
+ // ownership of the pointer since it is put in the navigation list.
+ // NOTE: Do not pass an entry that the controller already owns!
+ void LoadEntry(content::NavigationEntryImpl* entry);
+
+ // Handlers for the different types of navigation types. They will actually
+ // handle the navigations corresponding to the different NavClasses above.
+ // They will NOT broadcast the commit notification, that should be handled by
+ // the caller.
+ //
+ // RendererDidNavigateAutoSubframe is special, it may not actually change
+ // anything if some random subframe is loaded. It will return true if anything
+ // changed, or false if not.
+ //
+ // The functions taking |did_replace_entry| will fill into the given variable
+ // whether the last entry has been replaced or not.
+ // See LoadCommittedDetails.did_replace_entry.
+ void RendererDidNavigateToNewPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry);
+ void RendererDidNavigateToExistingPage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateToSamePage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateInPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry);
+ void RendererDidNavigateNewSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ bool RendererDidNavigateAutoSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Helper function for code shared between Reload() and ReloadIgnoringCache().
+ void ReloadInternal(bool check_for_repost, ReloadType reload_type);
+
+ // Actually issues the navigation held in pending_entry.
+ void NavigateToPendingEntry(ReloadType reload_type);
+
+ // Allows the derived class to issue notifications that a load has been
+ // committed. This will fill in the active entry to the details structure.
+ void NotifyNavigationEntryCommitted(content::LoadCommittedDetails* details);
+
+ // Updates the virtual URL of an entry to match a new URL, for cases where
+ // the real renderer URL is derived from the virtual URL, like view-source:
+ void UpdateVirtualURLToURL(content::NavigationEntryImpl* entry,
+ const GURL& new_url);
+
+ // Invoked after session/tab restore or cloning a tab. Resets the transition
+ // type of the entries, updates the max page id and creates the active
+ // contents. See RestoreFromState for a description of from_last_session.
+ void FinishRestore(int selected_index, bool from_last_session);
+
+ // Inserts a new entry or replaces the current entry with a new one, removing
+ // all entries after it. The new entry will become the active one.
+ void InsertOrReplaceEntry(content::NavigationEntryImpl* entry, bool replace);
+
+ // Removes the entry at |index|, as long as it is not the current entry.
+ void RemoveEntryAtIndexInternal(int index);
+
+ // Discards the pending and transient entries.
+ void DiscardNonCommittedEntriesInternal();
+
+ // Discards the transient entry.
+ void DiscardTransientEntry();
+
+ // If we have the maximum number of entries, remove the oldest one in
+ // preparation to add another.
+ void PruneOldestEntryIfFull();
+
+ // Returns true if the navigation is redirect.
+ bool IsRedirect(const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Returns true if the navigation is likley to be automatic rather than
+ // user-initiated.
+ bool IsLikelyAutoNavigation(base::TimeTicks now);
+
+ // Inserts up to |max_index| entries from |source| into this. This does NOT
+ // adjust any of the members that reference entries_
+ // (last_committed_entry_index_, pending_entry_index_ or
+ // transient_entry_index_).
+ void InsertEntriesFrom(const NavigationControllerImpl& source, int max_index);
+
+ // ---------------------------------------------------------------------------
+
+ // The user browser context associated with this controller.
+ content::BrowserContext* browser_context_;
+
+ // List of NavigationEntry for this tab
+ typedef std::vector<linked_ptr<content::NavigationEntryImpl> >
+ NavigationEntries;
+ NavigationEntries entries_;
+
+ // An entry we haven't gotten a response for yet. This will be discarded
+ // when we navigate again. It's used only so we know what the currently
+ // displayed tab is.
+ //
+ // This may refer to an item in the entries_ list if the pending_entry_index_
+ // == -1, or it may be its own entry that should be deleted. Be careful with
+ // the memory management.
+ content::NavigationEntryImpl* pending_entry_;
+
+ // currently visible entry
+ int last_committed_entry_index_;
+
+ // index of pending entry if it is in entries_, or -1 if pending_entry_ is a
+ // new entry (created by LoadURL).
+ int pending_entry_index_;
+
+ // The index for the entry that is shown until a navigation occurs. This is
+ // used for interstitial pages. -1 if there are no such entry.
+ // Note that this entry really appears in the list of entries, but only
+ // temporarily (until the next navigation). Any index pointing to an entry
+ // after the transient entry will become invalid if you navigate forward.
+ int transient_entry_index_;
+
+ // The tab contents associated with the controller. Possibly NULL during
+ // setup.
+ WebContentsImpl* tab_contents_;
+
+ // The max restored page ID in this controller, if it was restored. We must
+ // store this so that WebContentsImpl can tell any renderer in charge of one
+ // of the restored entries to update its max page ID.
+ int32 max_restored_page_id_;
+
+ // Manages the SSL security UI
+ SSLManager ssl_manager_;
+
+ // Whether we need to be reloaded when made active.
+ bool needs_reload_;
+
+ // The time ticks at which the last document was loaded.
+ base::TimeTicks last_document_loaded_;
+
+ // The session storage id that any (indirectly) owned RenderView should use.
+ scoped_refptr<SessionStorageNamespaceImpl> session_storage_namespace_;
+
+ // The maximum number of entries that a navigation controller can store.
+ static size_t max_entry_count_for_testing_;
+
+ // If a repost is pending, its type (RELOAD or RELOAD_IGNORING_CACHE),
+ // NO_RELOAD otherwise.
+ ReloadType pending_reload_;
+
+ DISALLOW_COPY_AND_ASSIGN(NavigationControllerImpl);
+};
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_CONTROLLER_IMPL_H_
diff --git a/content/browser/web_contents/navigation_controller_impl_unittest.cc b/content/browser/web_contents/navigation_controller_impl_unittest.cc
new file mode 100644
index 0000000..023e83d
--- /dev/null
+++ b/content/browser/web_contents/navigation_controller_impl_unittest.cc
@@ -0,0 +1,2592 @@
+// Copyright (c) 2012 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.
+
+#include "base/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+// These are only used for commented out tests. If someone wants to enable
+// them, they should be moved to chrome first.
+// #include "chrome/browser/history/history.h"
+// #include "chrome/browser/profiles/profile_manager.h"
+// #include "chrome/browser/sessions/session_service.h"
+// #include "chrome/browser/sessions/session_service_factory.h"
+// #include "chrome/browser/sessions/session_service_test_helper.h"
+// #include "chrome/browser/sessions/session_types.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/tab_contents/test_web_contents.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/test/mock_render_process_host.h"
+#include "content/test/test_browser_context.h"
+#include "content/test/test_notification_tracker.h"
+#include "net/base/net_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/glue/webkit_glue.h"
+
+using base::Time;
+using content::NavigationController;
+using content::NavigationEntry;
+using content::NavigationEntryImpl;
+using content::RenderViewHostImplTestHarness;
+using content::SiteInstance;
+using content::TestRenderViewHost;
+using content::TestWebContents;
+using content::WebContents;
+
+// NavigationControllerTest ----------------------------------------------------
+
+class NavigationControllerTest : public RenderViewHostImplTestHarness {
+ public:
+ NavigationControllerTest() {}
+
+ NavigationControllerImpl& controller_impl() {
+ return static_cast<NavigationControllerImpl&>(controller());
+ }
+};
+
+void RegisterForAllNavNotifications(TestNotificationTracker* tracker,
+ NavigationController* controller) {
+ tracker->ListenFor(content::NOTIFICATION_NAV_ENTRY_COMMITTED,
+ content::Source<NavigationController>(
+ controller));
+ tracker->ListenFor(content::NOTIFICATION_NAV_LIST_PRUNED,
+ content::Source<NavigationController>(
+ controller));
+ tracker->ListenFor(content::NOTIFICATION_NAV_ENTRY_CHANGED,
+ content::Source<NavigationController>(
+ controller));
+}
+
+SiteInstance* GetSiteInstanceFromEntry(NavigationEntry* entry) {
+ return NavigationEntryImpl::FromNavigationEntry(entry)->site_instance();
+}
+
+class TestWebContentsDelegate : public content::WebContentsDelegate {
+ public:
+ explicit TestWebContentsDelegate() :
+ navigation_state_change_count_(0) {}
+
+ int navigation_state_change_count() {
+ return navigation_state_change_count_;
+ }
+
+ // Keep track of whether the tab has notified us of a navigation state change.
+ virtual void NavigationStateChanged(const WebContents* source,
+ unsigned changed_flags) {
+ navigation_state_change_count_++;
+ }
+
+ private:
+ // The number of times NavigationStateChanged has been called.
+ int navigation_state_change_count_;
+};
+
+// -----------------------------------------------------------------------------
+
+TEST_F(NavigationControllerTest, Defaults) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), -1);
+ EXPECT_EQ(controller.GetEntryCount(), 0);
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+TEST_F(NavigationControllerTest, LoadURL) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ // Creating a pending notification should not have issued any of the
+ // notifications we're listening for.
+ EXPECT_EQ(0U, notifications.size());
+
+ // The load should now be pending.
+ EXPECT_EQ(controller.GetEntryCount(), 0);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), -1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), -1);
+
+ // We should have gotten no notifications from the preceeding checks.
+ EXPECT_EQ(0U, notifications.size());
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // The load should now be committed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 0);
+
+ // Load another...
+ controller.LoadURL(
+ url2, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+
+ // The load should now be pending.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ // TODO(darin): maybe this should really be true?
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 0);
+
+ // Simulate the beforeunload ack for the cross-site transition, and then the
+ // commit.
+ test_rvh()->SendShouldCloseACK(true);
+ static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost())->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // The load should now be committed.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 1);
+}
+
+// Tests what happens when the same page is loaded again. Should not create a
+// new session history entry. This is what happens when you press enter in the
+// URL bar to reload: a pending entry is created and then it is discarded when
+// the load commits (because WebCore didn't actually make a new entry).
+TEST_F(NavigationControllerTest, LoadURL_SamePage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // We should not have produced a new session history entry.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Tests loading a URL but discarding it before the load commits.
+TEST_F(NavigationControllerTest, LoadURL_Discarded) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.LoadURL(
+ url2, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ controller.DiscardNonCommittedEntries();
+ EXPECT_EQ(0U, notifications.size());
+
+ // Should not have produced a new session history entry.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Tests navigations that come in unrequested. This happens when the user
+// navigates from the web page, and here we test that there is no pending entry.
+TEST_F(NavigationControllerTest, LoadURL_NoPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make an existing committed entry.
+ const GURL kExistingURL1("http://eh");
+ controller.LoadURL(kExistingURL1, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Do a new navigation without making a pending one.
+ const GURL kNewURL("http://see");
+ test_rvh()->SendNavigate(99, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetActiveEntry()->GetURL());
+}
+
+// Tests navigating to a new URL when there is a new pending navigation that is
+// not the one that just loaded. This will happen if the user types in a URL to
+// somewhere slow, and then navigates the current page before the typed URL
+// commits.
+TEST_F(NavigationControllerTest, LoadURL_NewPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make an existing committed entry.
+ const GURL kExistingURL1("http://eh");
+ controller.LoadURL(kExistingURL1, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Make a pending entry to somewhere new.
+ const GURL kExistingURL2("http://bee");
+ controller.LoadURL(kExistingURL2, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+
+ // After the beforeunload but before it commits, do a new navigation.
+ test_rvh()->SendShouldCloseACK(true);
+ const GURL kNewURL("http://see");
+ static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost())->SendNavigate(3, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetActiveEntry()->GetURL());
+}
+
+// Tests navigating to a new URL when there is a pending back/forward
+// navigation. This will happen if the user hits back, but before that commits,
+// they navigate somewhere new.
+TEST_F(NavigationControllerTest, LoadURL_ExistingPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make some history.
+ const GURL kExistingURL1("http://foo/eh");
+ controller.LoadURL(kExistingURL1, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ const GURL kExistingURL2("http://foo/bee");
+ controller.LoadURL(kExistingURL2, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, kExistingURL2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Now make a pending back/forward navigation. The zeroth entry should be
+ // pending.
+ controller.GoBack();
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(0, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+
+ // Before that commits, do a new navigation.
+ const GURL kNewURL("http://foo/see");
+ content::LoadCommittedDetails details;
+ test_rvh()->SendNavigate(3, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetActiveEntry()->GetURL());
+}
+
+// Tests navigating to an existing URL when there is a pending new navigation.
+// This will happen if the user enters a URL, but before that commits, the
+// current page fires history.back().
+TEST_F(NavigationControllerTest, LoadURL_BackPreemptsPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make some history.
+ const GURL kExistingURL1("http://foo/eh");
+ controller.LoadURL(kExistingURL1, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ const GURL kExistingURL2("http://foo/bee");
+ controller.LoadURL(kExistingURL2, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, kExistingURL2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Now make a pending new navigation.
+ const GURL kNewURL("http://foo/see");
+ controller.LoadURL(
+ kNewURL, content::Referrer(), content::PAGE_TRANSITION_TYPED,
+ std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+
+ // Before that commits, a back navigation from the renderer commits.
+ test_rvh()->SendNavigate(0, kExistingURL1);
+
+ // There should no longer be any pending entry, and the back navigation we
+ // just made should be committed.
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kExistingURL1, controller.GetActiveEntry()->GetURL());
+}
+
+// Tests an ignored navigation when there is a pending new navigation.
+// This will happen if the user enters a URL, but before that commits, the
+// current blank page reloads. See http://crbug.com/77507.
+TEST_F(NavigationControllerTest, LoadURL_IgnorePreemptsPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Set a WebContentsDelegate to listen for state changes.
+ scoped_ptr<TestWebContentsDelegate> delegate(new TestWebContentsDelegate());
+ EXPECT_FALSE(contents()->GetDelegate());
+ contents()->SetDelegate(delegate.get());
+
+ // Without any navigations, the renderer starts at about:blank.
+ const GURL kExistingURL("about:blank");
+
+ // Now make a pending new navigation.
+ const GURL kNewURL("http://eh");
+ controller.LoadURL(
+ kNewURL, content::Referrer(), content::PAGE_TRANSITION_TYPED,
+ std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(1, delegate->navigation_state_change_count());
+
+ // Before that commits, a document.write and location.reload can cause the
+ // renderer to send a FrameNavigate with page_id -1.
+ test_rvh()->SendNavigate(-1, kExistingURL);
+
+ // This should clear the pending entry and notify of a navigation state
+ // change, so that we do not keep displaying kNewURL.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(2, delegate->navigation_state_change_count());
+
+ contents()->SetDelegate(NULL);
+}
+
+// Tests that the pending entry state is correct after an abort.
+TEST_F(NavigationControllerTest, LoadURL_AbortCancelsPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Set a WebContentsDelegate to listen for state changes.
+ scoped_ptr<TestWebContentsDelegate> delegate(new TestWebContentsDelegate());
+ EXPECT_FALSE(contents()->GetDelegate());
+ contents()->SetDelegate(delegate.get());
+
+ // Without any navigations, the renderer starts at about:blank.
+ const GURL kExistingURL("about:blank");
+
+ // Now make a pending new navigation.
+ const GURL kNewURL("http://eh");
+ controller.LoadURL(
+ kNewURL, content::Referrer(), content::PAGE_TRANSITION_TYPED,
+ std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(1, delegate->navigation_state_change_count());
+
+ // It may abort before committing, if it's a download or due to a stop or
+ // a new navigation from the user.
+ ViewHostMsg_DidFailProvisionalLoadWithError_Params params;
+ params.frame_id = 1;
+ params.is_main_frame = true;
+ params.error_code = net::ERR_ABORTED;
+ params.error_description = string16();
+ params.url = kNewURL;
+ params.showing_repost_interstitial = false;
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidFailProvisionalLoadWithError(0, // routing_id
+ params));
+
+ // This should clear the pending entry and notify of a navigation state
+ // change, so that we do not keep displaying kNewURL.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(2, delegate->navigation_state_change_count());
+
+ contents()->SetDelegate(NULL);
+}
+
+// Tests that the pending entry state is correct after a redirect and abort.
+// http://crbug.com/83031.
+TEST_F(NavigationControllerTest, LoadURL_RedirectAbortCancelsPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Set a WebContentsDelegate to listen for state changes.
+ scoped_ptr<TestWebContentsDelegate> delegate(new TestWebContentsDelegate());
+ EXPECT_FALSE(contents()->GetDelegate());
+ contents()->SetDelegate(delegate.get());
+
+ // Without any navigations, the renderer starts at about:blank.
+ const GURL kExistingURL("about:blank");
+
+ // Now make a pending new navigation.
+ const GURL kNewURL("http://eh");
+ controller.LoadURL(
+ kNewURL, content::Referrer(), content::PAGE_TRANSITION_TYPED,
+ std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(1, delegate->navigation_state_change_count());
+
+ // Now the navigation redirects.
+ const GURL kRedirectURL("http://bee");
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidRedirectProvisionalLoad(0, // routing_id
+ -1, // pending page_id
+ GURL(), // opener
+ kNewURL, // old url
+ kRedirectURL)); // new url
+
+ // We don't want to change the NavigationEntry's url, in case it cancels.
+ // Prevents regression of http://crbug.com/77786.
+ EXPECT_EQ(kNewURL, controller.GetPendingEntry()->GetURL());
+
+ // It may abort before committing, if it's a download or due to a stop or
+ // a new navigation from the user.
+ ViewHostMsg_DidFailProvisionalLoadWithError_Params params;
+ params.frame_id = 1;
+ params.is_main_frame = true;
+ params.error_code = net::ERR_ABORTED;
+ params.error_description = string16();
+ params.url = kRedirectURL;
+ params.showing_repost_interstitial = false;
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidFailProvisionalLoadWithError(0, // routing_id
+ params));
+
+ // This should clear the pending entry and notify of a navigation state
+ // change, so that we do not keep displaying kNewURL.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(2, delegate->navigation_state_change_count());
+
+ contents()->SetDelegate(NULL);
+}
+
+TEST_F(NavigationControllerTest, Reload) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ controller.GetActiveEntry()->SetTitle(ASCIIToUTF16("Title"));
+ controller.Reload(true);
+ EXPECT_EQ(0U, notifications.size());
+
+ // The reload is pending.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ // Make sure the title has been cleared (will be redrawn just after reload).
+ // Avoids a stale cached title when the new page being reloaded has no title.
+ // See http://crbug.com/96041.
+ EXPECT_TRUE(controller.GetActiveEntry()->GetTitle().empty());
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Tests what happens when a reload navigation produces a new page.
+TEST_F(NavigationControllerTest, Reload_GeneratesNewPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.Reload(true);
+ EXPECT_EQ(0U, notifications.size());
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Tests what happens when we navigate back successfully
+TEST_F(NavigationControllerTest, Back) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ const GURL url2("http://foo2");
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.GoBack();
+ EXPECT_EQ(0U, notifications.size());
+
+ // We should now have a pending navigation to go back.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoForward());
+
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // The back navigation completed successfully.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoForward());
+}
+
+// Tests what happens when a back navigation produces a new page.
+TEST_F(NavigationControllerTest, Back_GeneratesNewPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.LoadURL(
+ url2, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.GoBack();
+ EXPECT_EQ(0U, notifications.size());
+
+ // We should now have a pending navigation to go back.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoForward());
+
+ test_rvh()->SendNavigate(2, url3);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // The back navigation resulted in a completely new navigation.
+ // TODO(darin): perhaps this behavior will be confusing to users?
+ EXPECT_EQ(controller.GetEntryCount(), 3);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 2);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Receives a back message when there is a new pending navigation entry.
+TEST_F(NavigationControllerTest, Back_NewPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL kUrl1("http://foo1");
+ const GURL kUrl2("http://foo2");
+ const GURL kUrl3("http://foo3");
+
+ // First navigate two places so we have some back history.
+ test_rvh()->SendNavigate(0, kUrl1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // controller.LoadURL(kUrl2, content::PAGE_TRANSITION_TYPED);
+ test_rvh()->SendNavigate(1, kUrl2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Now start a new pending navigation and go back before it commits.
+ controller.LoadURL(
+ kUrl3, content::Referrer(), content::PAGE_TRANSITION_TYPED,
+ std::string());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(kUrl3, controller.GetPendingEntry()->GetURL());
+ controller.GoBack();
+
+ // The pending navigation should now be the "back" item and the new one
+ // should be gone.
+ EXPECT_EQ(0, controller.GetPendingEntryIndex());
+ EXPECT_EQ(kUrl1, controller.GetPendingEntry()->GetURL());
+}
+
+// Receives a back message when there is a different renavigation already
+// pending.
+TEST_F(NavigationControllerTest, Back_OtherBackPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL kUrl1("http://foo/1");
+ const GURL kUrl2("http://foo/2");
+ const GURL kUrl3("http://foo/3");
+
+ // First navigate three places so we have some back history.
+ test_rvh()->SendNavigate(0, kUrl1);
+ test_rvh()->SendNavigate(1, kUrl2);
+ test_rvh()->SendNavigate(2, kUrl3);
+
+ // With nothing pending, say we get a navigation to the second entry.
+ test_rvh()->SendNavigate(1, kUrl2);
+
+ // We know all the entries have the same site instance, so we can just grab
+ // a random one for looking up other entries.
+ SiteInstance* site_instance =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->site_instance();
+
+ // That second URL should be the last committed and it should have gotten the
+ // new title.
+ EXPECT_EQ(kUrl2, controller.GetEntryWithPageID(site_instance, 1)->GetURL());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+
+ // Now go forward to the last item again and say it was committed.
+ controller.GoForward();
+ test_rvh()->SendNavigate(2, kUrl3);
+
+ // Now start going back one to the second page. It will be pending.
+ controller.GoBack();
+ EXPECT_EQ(1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+
+ // Not synthesize a totally new back event to the first page. This will not
+ // match the pending one.
+ test_rvh()->SendNavigate(0, kUrl1);
+
+ // The committed navigation should clear the pending entry.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+
+ // But the navigated entry should be the last committed.
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kUrl1, controller.GetLastCommittedEntry()->GetURL());
+}
+
+// Tests what happens when we navigate forward successfully.
+TEST_F(NavigationControllerTest, Forward) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.GoForward();
+
+ // We should now have a pending navigation to go forward.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // The forward navigation completed successfully.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Tests what happens when a forward navigation produces a new page.
+TEST_F(NavigationControllerTest, Forward_GeneratesNewPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ controller.GoForward();
+ EXPECT_EQ(0U, notifications.size());
+
+ // Should now have a pending navigation to go forward.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ test_rvh()->SendNavigate(2, url3);
+ EXPECT_TRUE(notifications.Check2AndReset(
+ content::NOTIFICATION_NAV_LIST_PRUNED,
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Two consequent navigation for the same URL entered in should be considered
+// as SAME_PAGE navigation even when we are redirected to some other page.
+TEST_F(NavigationControllerTest, Redirect) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Second request
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = content::PAGE_TRANSITION_SERVER_REDIRECT;
+ params.redirects.push_back(GURL("http://foo1"));
+ params.redirects.push_back(GURL("http://foo2"));
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url2));
+
+ content::LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ EXPECT_TRUE(details.type == content::NAVIGATION_TYPE_SAME_PAGE);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Similar to Redirect above, but the first URL is requested by POST,
+// the second URL is requested by GET. NavigationEntry::has_post_data_
+// must be cleared. http://crbug.com/21245
+TEST_F(NavigationControllerTest, PostThenRedirect) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request as POST
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ controller.GetActiveEntry()->SetHasPostData(true);
+
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Second request
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = content::PAGE_TRANSITION_SERVER_REDIRECT;
+ params.redirects.push_back(GURL("http://foo1"));
+ params.redirects.push_back(GURL("http://foo2"));
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url2));
+
+ content::LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ EXPECT_TRUE(details.type == content::NAVIGATION_TYPE_SAME_PAGE);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+ EXPECT_FALSE(controller.GetActiveEntry()->GetHasPostData());
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// A redirect right off the bat should be a NEW_PAGE.
+TEST_F(NavigationControllerTest, ImmediateRedirect) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = content::PAGE_TRANSITION_SERVER_REDIRECT;
+ params.redirects.push_back(GURL("http://foo1"));
+ params.redirects.push_back(GURL("http://foo2"));
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url2));
+
+ content::LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ EXPECT_TRUE(details.type == content::NAVIGATION_TYPE_NEW_PAGE);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Tests navigation via link click within a subframe. A new navigation entry
+// should be created.
+TEST_F(NavigationControllerTest, NewSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = content::PAGE_TRANSITION_MANUAL_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url2));
+
+ content::LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(url1, details.previous_url);
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_FALSE(details.is_main_frame);
+
+ // The new entry should be appended.
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ // New entry should refer to the new page, but the old URL (entries only
+ // reflect the toplevel URL).
+ EXPECT_EQ(url1, details.entry->GetURL());
+ EXPECT_EQ(params.page_id, details.entry->GetPageID());
+}
+
+// Some pages create a popup, then write an iframe into it. This causes a
+// subframe navigation without having any committed entry. Such navigations
+// just get thrown on the ground, but we shouldn't crash.
+TEST_F(NavigationControllerTest, SubframeOnEmptyPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Navigation controller currently has no entries.
+ const GURL url("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url;
+ params.transition = content::PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url));
+
+ content::LoadCommittedDetails details;
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(0U, notifications.size());
+}
+
+// Auto subframes are ones the page loads automatically like ads. They should
+// not create new navigation entries.
+TEST_F(NavigationControllerTest, AutoSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = content::PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url2));
+
+ // Navigating should do nothing.
+ content::LoadCommittedDetails details;
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(0U, notifications.size());
+
+ // There should still be only one entry.
+ EXPECT_EQ(1, controller.GetEntryCount());
+}
+
+// Tests navigation and then going back to a subframe navigation.
+TEST_F(NavigationControllerTest, BackSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Main page.
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // First manual subframe navigation.
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = content::PAGE_TRANSITION_MANUAL_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url2));
+
+ // This should generate a new entry.
+ content::LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ // Second manual subframe navigation should also make a new entry.
+ const GURL url3("http://foo3");
+ params.page_id = 2;
+ params.url = url3;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(2, controller.GetCurrentEntryIndex());
+
+ // Go back one.
+ controller.GoBack();
+ params.url = url2;
+ params.page_id = 1;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(1, controller.GetCurrentEntryIndex());
+
+ // Go back one more.
+ controller.GoBack();
+ params.url = url1;
+ params.page_id = 0;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetCurrentEntryIndex());
+}
+
+TEST_F(NavigationControllerTest, LinkClick) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // Should not have produced a new session history entry.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+TEST_F(NavigationControllerTest, InPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Main page.
+ const GURL url1("http://foo");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // First navigation.
+ const GURL url2("http://foo#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = content::PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url2));
+
+ // This should generate a new entry.
+ content::LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_FALSE(details.did_replace_entry);
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ // Go back one.
+ ViewHostMsg_FrameNavigate_Params back_params(params);
+ controller.GoBack();
+ back_params.url = url1;
+ back_params.page_id = 0;
+ EXPECT_TRUE(controller.RendererDidNavigate(back_params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ // is_in_page is false in that case but should be true.
+ // See comment in AreURLsInPageNavigation() in navigation_controller.cc
+ // EXPECT_TRUE(details.is_in_page);
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(back_params.url, controller.GetActiveEntry()->GetURL());
+
+ // Go forward
+ ViewHostMsg_FrameNavigate_Params forward_params(params);
+ controller.GoForward();
+ forward_params.url = url2;
+ forward_params.page_id = 1;
+ EXPECT_TRUE(controller.RendererDidNavigate(forward_params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(1, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(forward_params.url,
+ controller.GetActiveEntry()->GetURL());
+
+ // Now go back and forward again. This is to work around a bug where we would
+ // compare the incoming URL with the last committed entry rather than the
+ // one identified by an existing page ID. This would result in the second URL
+ // losing the reference fragment when you navigate away from it and then back.
+ controller.GoBack();
+ EXPECT_TRUE(controller.RendererDidNavigate(back_params, &details));
+ controller.GoForward();
+ EXPECT_TRUE(controller.RendererDidNavigate(forward_params, &details));
+ EXPECT_EQ(forward_params.url,
+ controller.GetActiveEntry()->GetURL());
+
+ // Finally, navigate to an unrelated URL to make sure in_page is not sticky.
+ const GURL url3("http://bar");
+ params.page_id = 2;
+ params.url = url3;
+ notifications.Reset();
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_FALSE(details.is_in_page);
+}
+
+TEST_F(NavigationControllerTest, InPage_Replace) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Main page.
+ const GURL url1("http://foo");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+
+ // First navigation.
+ const GURL url2("http://foo#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0; // Same page_id
+ params.url = url2;
+ params.transition = content::PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url2));
+
+ // This should NOT generate a new entry, nor prune the list.
+ content::LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(1, controller.GetEntryCount());
+}
+
+// Tests for http://crbug.com/40395
+// Simulates this:
+// <script>
+// window.location.replace("#a");
+// window.location='http://foo3/';
+// </script>
+TEST_F(NavigationControllerTest, ClientRedirectAfterInPageNavigation) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Load an initial page.
+ {
+ const GURL url("http://foo/");
+ test_rvh()->SendNavigate(0, url);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ }
+
+ // Navigate to a new page.
+ {
+ const GURL url("http://foo2/");
+ test_rvh()->SendNavigate(1, url);
+ controller.DocumentLoadedInFrame();
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ }
+
+ // Navigate within the page.
+ {
+ const GURL url("http://foo2/#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1; // Same page_id
+ params.url = url;
+ params.transition = content::PAGE_TRANSITION_LINK;
+ params.redirects.push_back(url);
+ params.should_update_history = true;
+ params.gesture = NavigationGestureUnknown;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url));
+
+ // This should NOT generate a new entry, nor prune the list.
+ content::LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(2, controller.GetEntryCount());
+ }
+
+ // Perform a client redirect to a new page.
+ {
+ const GURL url("http://foo3/");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 2; // New page_id
+ params.url = url;
+ params.transition = content::PAGE_TRANSITION_CLIENT_REDIRECT;
+ params.redirects.push_back(GURL("http://foo2/#a"));
+ params.redirects.push_back(url);
+ params.should_update_history = true;
+ params.gesture = NavigationGestureUnknown;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url));
+
+ // This SHOULD generate a new entry.
+ content::LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_EQ(3, controller.GetEntryCount());
+ }
+
+ // Verify that BACK brings us back to http://foo2/.
+ {
+ const GURL url("http://foo2/");
+ controller.GoBack();
+ test_rvh()->SendNavigate(1, url);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(url, controller.GetActiveEntry()->GetURL());
+ }
+}
+
+// NotificationObserver implementation used in verifying we've received the
+// content::NOTIFICATION_NAV_LIST_PRUNED method.
+class PrunedListener : public content::NotificationObserver {
+ public:
+ explicit PrunedListener(NavigationControllerImpl* controller)
+ : notification_count_(0) {
+ registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
+ content::Source<NavigationController>(controller));
+ }
+
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ if (type == content::NOTIFICATION_NAV_LIST_PRUNED) {
+ notification_count_++;
+ details_ = *(content::Details<content::PrunedDetails>(details).ptr());
+ }
+ }
+
+ // Number of times NAV_LIST_PRUNED has been observed.
+ int notification_count_;
+
+ // Details from the last NAV_LIST_PRUNED.
+ content::PrunedDetails details_;
+
+ private:
+ content::NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrunedListener);
+};
+
+// Tests that we limit the number of navigation entries created correctly.
+TEST_F(NavigationControllerTest, EnforceMaxNavigationCount) {
+ NavigationControllerImpl& controller = controller_impl();
+ size_t original_count = NavigationControllerImpl::max_entry_count();
+ const int kMaxEntryCount = 5;
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(kMaxEntryCount);
+
+ int url_index;
+ // Load up to the max count, all entries should be there.
+ for (url_index = 0; url_index < kMaxEntryCount; url_index++) {
+ GURL url(StringPrintf("http://www.a.com/%d", url_index));
+ controller.LoadURL(
+ url, content::Referrer(), content::PAGE_TRANSITION_TYPED,
+ std::string());
+ test_rvh()->SendNavigate(url_index, url);
+ }
+
+ EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
+
+ // Created a PrunedListener to observe prune notifications.
+ PrunedListener listener(&controller);
+
+ // Navigate some more.
+ GURL url(StringPrintf("http://www.a.com/%d", url_index));
+ controller.LoadURL(
+ url, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(url_index, url);
+ url_index++;
+
+ // We should have got a pruned navigation.
+ EXPECT_EQ(1, listener.notification_count_);
+ EXPECT_TRUE(listener.details_.from_front);
+ EXPECT_EQ(1, listener.details_.count);
+
+ // We expect http://www.a.com/0 to be gone.
+ EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(),
+ GURL("http:////www.a.com/1"));
+
+ // More navigations.
+ for (int i = 0; i < 3; i++) {
+ url = GURL(StringPrintf("http:////www.a.com/%d", url_index));
+ controller.LoadURL(
+ url, content::Referrer(), content::PAGE_TRANSITION_TYPED,
+ std::string());
+ test_rvh()->SendNavigate(url_index, url);
+ url_index++;
+ }
+ EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(),
+ GURL("http:////www.a.com/4"));
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(original_count);
+}
+
+// Tests that we can do a restore and navigate to the restored entries and
+// everything is updated properly. This can be tricky since there is no
+// SiteInstance for the entries created initially.
+TEST_F(NavigationControllerTest, RestoreNavigate) {
+ // Create a NavigationController with a restored set of tabs.
+ GURL url("http://foo");
+ std::vector<NavigationEntry*> entries;
+ NavigationEntry* entry = NavigationControllerImpl::CreateNavigationEntry(
+ url, content::Referrer(), content::PAGE_TRANSITION_RELOAD, false,
+ std::string(), browser_context());
+ entry->SetPageID(0);
+ entry->SetTitle(ASCIIToUTF16("Title"));
+ entry->SetContentState("state");
+ entries.push_back(entry);
+ TabContents our_contents(
+ browser_context(), NULL, MSG_ROUTING_NONE, NULL, NULL);
+ NavigationControllerImpl& our_controller = our_contents.GetControllerImpl();
+ our_controller.Restore(0, true, &entries);
+ ASSERT_EQ(0u, entries.size());
+
+ // Before navigating to the restored entry, it should have a restore_type
+ // and no SiteInstance.
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_LAST_SESSION,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_FALSE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // After navigating, we should have one entry, and it should be "pending".
+ // It should now have a SiteInstance and no restore_type.
+ our_controller.GoToIndex(0);
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(our_controller.GetEntryAtIndex(0),
+ our_controller.GetPendingEntry());
+ EXPECT_EQ(0, our_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry
+ (our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_TRUE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // Say we navigated to that entry.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url;
+ params.transition = content::PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url));
+ content::LoadCommittedDetails details;
+ our_controller.RendererDidNavigate(params, &details);
+
+ // There should be no longer any pending entry and one committed one. This
+ // means that we were able to locate the entry, assign its site instance, and
+ // commit it properly.
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(0, our_controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(our_controller.GetPendingEntry());
+ EXPECT_EQ(url,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetLastCommittedEntry())->site_instance()->
+ GetSite());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+}
+
+// Tests that we can still navigate to a restored entry after a different
+// navigation fails and clears the pending entry. http://crbug.com/90085
+TEST_F(NavigationControllerTest, RestoreNavigateAfterFailure) {
+ // Create a NavigationController with a restored set of tabs.
+ GURL url("http://foo");
+ std::vector<NavigationEntry*> entries;
+ NavigationEntry* entry = NavigationControllerImpl::CreateNavigationEntry(
+ url, content::Referrer(), content::PAGE_TRANSITION_RELOAD, false,
+ std::string(), browser_context());
+ entry->SetPageID(0);
+ entry->SetTitle(ASCIIToUTF16("Title"));
+ entry->SetContentState("state");
+ entries.push_back(entry);
+ TabContents our_contents(
+ browser_context(), NULL, MSG_ROUTING_NONE, NULL, NULL);
+ NavigationControllerImpl& our_controller = our_contents.GetControllerImpl();
+ our_controller.Restore(0, true, &entries);
+ ASSERT_EQ(0u, entries.size());
+
+ // Before navigating to the restored entry, it should have a restore_type
+ // and no SiteInstance.
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_LAST_SESSION,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_FALSE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // After navigating, we should have one entry, and it should be "pending".
+ // It should now have a SiteInstance and no restore_type.
+ our_controller.GoToIndex(0);
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(our_controller.GetEntryAtIndex(0),
+ our_controller.GetPendingEntry());
+ EXPECT_EQ(0, our_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_TRUE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // This pending navigation may have caused a different navigation to fail,
+ // which causes the pending entry to be cleared.
+ TestRenderViewHost* rvh =
+ static_cast<TestRenderViewHost*>(our_contents.GetRenderViewHost());
+ ViewHostMsg_DidFailProvisionalLoadWithError_Params fail_load_params;
+ fail_load_params.frame_id = 1;
+ fail_load_params.is_main_frame = true;
+ fail_load_params.error_code = net::ERR_ABORTED;
+ fail_load_params.error_description = string16();
+ fail_load_params.url = url;
+ fail_load_params.showing_repost_interstitial = false;
+ rvh->OnMessageReceived(
+ ViewHostMsg_DidFailProvisionalLoadWithError(0, // routing_id
+ fail_load_params));
+
+ // Now the pending restored entry commits.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url;
+ params.transition = content::PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url));
+ content::LoadCommittedDetails details;
+ our_controller.RendererDidNavigate(params, &details);
+
+ // There should be no pending entry and one committed one.
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(0, our_controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(our_controller.GetPendingEntry());
+ EXPECT_EQ(url,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetLastCommittedEntry())->site_instance()->
+ GetSite());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+}
+
+// Make sure that the page type and stuff is correct after an interstitial.
+TEST_F(NavigationControllerTest, Interstitial) {
+ NavigationControllerImpl& controller = controller_impl();
+ // First navigate somewhere normal.
+ const GURL url1("http://foo");
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+
+ // Now navigate somewhere with an interstitial.
+ const GURL url2("http://bar");
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ set_page_type(content::PAGE_TYPE_INTERSTITIAL);
+
+ // At this point the interstitial will be displayed and the load will still
+ // be pending. If the user continues, the load will commit.
+ test_rvh()->SendNavigate(1, url2);
+
+ // The page should be a normal page again.
+ EXPECT_EQ(url2, controller.GetLastCommittedEntry()->GetURL());
+ EXPECT_EQ(content::PAGE_TYPE_NORMAL,
+ controller.GetLastCommittedEntry()->GetPageType());
+}
+
+TEST_F(NavigationControllerTest, RemoveEntry) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+ const GURL url4("http://foo/4");
+ const GURL url5("http://foo/5");
+ const GURL pending_url("http://foo/pending");
+ const GURL default_url("http://foo/default");
+
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+ controller.LoadURL(
+ url2, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url2);
+ controller.LoadURL(
+ url3, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(2, url3);
+ controller.LoadURL(
+ url4, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(3, url4);
+ controller.LoadURL(
+ url5, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(4, url5);
+
+ // Try to remove the last entry. Will fail because it is the current entry.
+ controller.RemoveEntryAtIndex(controller.GetEntryCount() - 1);
+ EXPECT_EQ(5, controller.GetEntryCount());
+ EXPECT_EQ(4, controller.GetLastCommittedEntryIndex());
+
+ // Go back and remove the last entry.
+ controller.GoBack();
+ test_rvh()->SendNavigate(3, url4);
+ controller.RemoveEntryAtIndex(controller.GetEntryCount() - 1);
+ EXPECT_EQ(4, controller.GetEntryCount());
+ EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+
+ // Remove an entry which is not the last committed one.
+ controller.RemoveEntryAtIndex(0);
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+
+ // Remove the 2 remaining entries.
+ controller.RemoveEntryAtIndex(1);
+ controller.RemoveEntryAtIndex(0);
+
+ // This should leave us with only the last committed entry.
+ EXPECT_EQ(1, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+}
+
+// Tests the transient entry, making sure it goes away with all navigations.
+TEST_F(NavigationControllerTest, TransientEntry) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+ const GURL url4("http://foo/4");
+ const GURL transient_url("http://foo/transient");
+
+ controller.LoadURL(
+ url0, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url0);
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url1);
+
+ notifications.Reset();
+
+ // Adding a transient with no pending entry.
+ NavigationEntryImpl* transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.AddTransientEntry(transient_entry);
+
+ // We should not have received any notifications.
+ EXPECT_EQ(0U, notifications.size());
+
+ // Check our state.
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 3);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 1);
+
+ // Navigate.
+ controller.LoadURL(
+ url2, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(2, url2);
+
+ // We should have navigated, transient entry should be gone.
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 3);
+
+ // Add a transient again, then navigate with no pending entry this time.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ test_rvh()->SendNavigate(3, url3);
+ // Transient entry should be gone.
+ EXPECT_EQ(url3, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 4);
+
+ // Initiate a navigation, add a transient then commit navigation.
+ controller.LoadURL(
+ url4, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ test_rvh()->SendNavigate(4, url4);
+ EXPECT_EQ(url4, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 5);
+
+ // Add a transient and go back. This should simply remove the transient.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ controller.GoBack();
+ // Transient entry should be gone.
+ EXPECT_EQ(url4, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 5);
+ test_rvh()->SendNavigate(3, url3);
+
+ // Add a transient and go to an entry before the current one.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ controller.GoToIndex(1);
+ // The navigation should have been initiated, transient entry should be gone.
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+ // Visible entry does not update for history navigations until commit.
+ EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+
+ // Add a transient and go to an entry after the current one.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ controller.GoToIndex(3);
+ // The navigation should have been initiated, transient entry should be gone.
+ // Because of the transient entry that is removed, going to index 3 makes us
+ // land on url2 (which is visible after the commit).
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(2, url2);
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+
+ // Add a transient and go forward.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ EXPECT_TRUE(controller.CanGoForward());
+ controller.GoForward();
+ // We should have navigated, transient entry should be gone.
+ EXPECT_EQ(url3, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(3, url3);
+ EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL());
+
+ // Ensure the URLS are correct.
+ EXPECT_EQ(controller.GetEntryCount(), 5);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0);
+ EXPECT_EQ(controller.GetEntryAtIndex(1)->GetURL(), url1);
+ EXPECT_EQ(controller.GetEntryAtIndex(2)->GetURL(), url2);
+ EXPECT_EQ(controller.GetEntryAtIndex(3)->GetURL(), url3);
+ EXPECT_EQ(controller.GetEntryAtIndex(4)->GetURL(), url4);
+}
+
+// Tests that the URLs for renderer-initiated navigations are not displayed to
+// the user until the navigation commits, to prevent URL spoof attacks.
+// See http://crbug.com/99016.
+TEST_F(NavigationControllerTest, DontShowRendererURLUntilCommit) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+
+ // For typed navigations (browser-initiated), both active and visible entries
+ // should update before commit.
+ controller.LoadURL(url0, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(url0, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url0, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(0, url0);
+
+ // For link clicks (renderer-initiated navigations), the active entry should
+ // update before commit but the visible should not.
+ controller.LoadURLFromRenderer(url1, content::Referrer(),
+ content::PAGE_TRANSITION_LINK,
+ std::string());
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url0, controller.GetVisibleEntry()->GetURL());
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ is_renderer_initiated());
+
+ // After commit, both should be updated, and we should no longer treat the
+ // entry as renderer-initiated.
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+ EXPECT_FALSE(
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->is_renderer_initiated());
+
+ notifications.Reset();
+}
+
+// Tests that IsInPageNavigation returns appropriate results. Prevents
+// regression for bug 1126349.
+TEST_F(NavigationControllerTest, IsInPageNavigation) {
+ NavigationControllerImpl& controller = controller_impl();
+ // Navigate to URL with no refs.
+ const GURL url("http://www.google.com/home.html");
+ test_rvh()->SendNavigate(0, url);
+
+ // Reloading the page is not an in-page navigation.
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url));
+ const GURL other_url("http://www.google.com/add.html");
+ EXPECT_FALSE(controller.IsURLInPageNavigation(other_url));
+ const GURL url_with_ref("http://www.google.com/home.html#my_ref");
+ EXPECT_TRUE(controller.IsURLInPageNavigation(url_with_ref));
+
+ // Navigate to URL with refs.
+ test_rvh()->SendNavigate(1, url_with_ref);
+
+ // Reloading the page is not an in-page navigation.
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url_with_ref));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(other_url));
+ const GURL other_url_with_ref("http://www.google.com/home.html#my_other_ref");
+ EXPECT_TRUE(controller.IsURLInPageNavigation(
+ other_url_with_ref));
+}
+
+// Some pages can have subframes with the same base URL (minus the reference) as
+// the main page. Even though this is hard, it can happen, and we don't want
+// these subframe navigations to affect the toplevel document. They should
+// instead be ignored. http://crbug.com/5585
+TEST_F(NavigationControllerTest, SameSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ // Navigate the main frame.
+ const GURL url("http://www.google.com/");
+ test_rvh()->SendNavigate(0, url);
+
+ // We should be at the first navigation entry.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+
+ // Navigate a subframe that would normally count as in-page.
+ const GURL subframe("http://www.google.com/#");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = subframe;
+ params.transition = content::PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(subframe));
+ content::LoadCommittedDetails details;
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+
+ // Nothing should have changed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+}
+
+// Make sure that on cloning a tabcontents and going back needs_reload is false.
+TEST_F(NavigationControllerTest, CloneAndGoBack) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<WebContents> clone(controller.GetWebContents()->Clone());
+
+ ASSERT_EQ(2, clone->GetController().GetEntryCount());
+ EXPECT_TRUE(clone->GetController().NeedsReload());
+ clone->GetController().GoBack();
+ // Navigating back should have triggered needs_reload_ to go false.
+ EXPECT_FALSE(clone->GetController().NeedsReload());
+}
+
+// Make sure that cloning a tabcontents doesn't copy interstitials.
+TEST_F(NavigationControllerTest, CloneOmitsInterstitials) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ // Add an interstitial entry. Should be deleted with controller.
+ NavigationEntryImpl* interstitial_entry = new NavigationEntryImpl();
+ interstitial_entry->set_page_type(content::PAGE_TYPE_INTERSTITIAL);
+ controller.AddTransientEntry(interstitial_entry);
+
+ scoped_ptr<WebContents> clone(controller.GetWebContents()->Clone());
+
+ ASSERT_EQ(2, clone->GetController().GetEntryCount());
+}
+
+// Tests a subframe navigation while a toplevel navigation is pending.
+// http://crbug.com/43967
+TEST_F(NavigationControllerTest, SubframeWhilePending) {
+ NavigationControllerImpl& controller = controller_impl();
+ // Load the first page.
+ const GURL url1("http://foo/");
+ NavigateAndCommit(url1);
+
+ // Now start a pending load to a totally different page, but don't commit it.
+ const GURL url2("http://bar/");
+ controller.LoadURL(
+ url2, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+
+ // Send a subframe update from the first page, as if one had just
+ // automatically loaded. Auto subframes don't increment the page ID.
+ const GURL url1_sub("http://foo/subframe");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = controller.GetLastCommittedEntry()->GetPageID();
+ params.url = url1_sub;
+ params.transition = content::PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(url1_sub));
+ content::LoadCommittedDetails details;
+
+ // This should return false meaning that nothing was actually updated.
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+
+ // The notification should have updated the last committed one, and not
+ // the pending load.
+ EXPECT_EQ(url1, controller.GetLastCommittedEntry()->GetURL());
+
+ // The active entry should be unchanged by the subframe load.
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+}
+
+// Tests CopyStateFromAndPrune with 2 urls in source, 1 in dest.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ // First two entries should have the same SiteInstance.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(0));
+ SiteInstance* instance2 =
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(1));
+ EXPECT_EQ(instance1, instance2);
+ EXPECT_EQ(0, controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(1, controller.GetEntryAtIndex(1)->GetPageID());
+ EXPECT_EQ(1, contents()->GetMaxPageIDForSiteInstance(instance1));
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller =
+ other_contents->GetControllerImpl();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain the 3 urls: url1, url2 and url3.
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(2)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(1, other_controller.GetEntryAtIndex(1)->GetPageID());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(2)->GetPageID());
+
+ // A new SiteInstance should be used for the new tab.
+ SiteInstance* instance3 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(2));
+ EXPECT_NE(instance3, instance1);
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ EXPECT_EQ(1, other_contents->GetMaxPageIDForSiteInstance(instance1));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance3));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the first selected and nothing in
+// the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune2) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller =
+ other_contents->GetControllerImpl();
+ other_contents->ExpectSetHistoryLengthAndPrune(NULL, 1, -1);
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain the 1 url: url1.
+
+ ASSERT_EQ(1, other_controller.GetEntryCount());
+
+ ASSERT_EQ(0, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(0)->GetPageID());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the first selected and nothing in
+// the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune3) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller =
+ other_contents->GetControllerImpl();
+ other_controller.LoadURL(
+ url3, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ other_contents->ExpectSetHistoryLengthAndPrune(NULL, 1, -1);
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain 1 entry for url1, and a pending entry
+ // for url3.
+
+ ASSERT_EQ(1, other_controller.GetEntryCount());
+
+ EXPECT_EQ(0, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+
+ // And there should be a pending entry for url3.
+ ASSERT_TRUE(other_controller.GetPendingEntry());
+
+ EXPECT_EQ(url3, other_controller.GetPendingEntry()->GetURL());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Tests CopyStateFromAndPrune with 3 urls in source, 1 in dest,
+// when the max entry count is 3. We should prune one entry.
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneMaxEntries) {
+ NavigationControllerImpl& controller = controller_impl();
+ size_t original_count = NavigationControllerImpl::max_entry_count();
+ const int kMaxEntryCount = 3;
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(kMaxEntryCount);
+
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+ const GURL url4("http://foo/4");
+
+ // Create a PrunedListener to observe prune notifications.
+ PrunedListener listener(&controller);
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller =
+ other_contents->GetControllerImpl();
+ other_contents->NavigateAndCommit(url4);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // We should have received a pruned notification.
+ EXPECT_EQ(1, listener.notification_count_);
+ EXPECT_TRUE(listener.details_.from_front);
+ EXPECT_EQ(1, listener.details_.count);
+
+ // other_controller should now contain only 3 urls: url2, url3 and url4.
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url4, other_controller.GetEntryAtIndex(2)->GetURL());
+ EXPECT_EQ(1, other_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(2, other_controller.GetEntryAtIndex(1)->GetPageID());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(2)->GetPageID());
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(original_count);
+}
+
+// Tests that navigations initiated from the page (with the history object)
+// work as expected without navigation entries.
+TEST_F(NavigationControllerTest, HistoryNavigate) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ // Simulate the page calling history.back(), it should not create a pending
+ // entry.
+ contents()->OnGoToEntryAtOffset(-1);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ // The actual cross-navigation is suspended until the current RVH tells us
+ // it unloaded, simulate that.
+ contents()->ProceedWithCrossSiteNavigation();
+ // Also make sure we told the page to navigate.
+ const IPC::Message* message =
+ process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ ASSERT_TRUE(message != NULL);
+ Tuple1<ViewMsg_Navigate_Params> nav_params;
+ ViewMsg_Navigate::Read(message, &nav_params);
+ EXPECT_EQ(url1, nav_params.a.url);
+ process()->sink().ClearMessages();
+
+ // Now test history.forward()
+ contents()->OnGoToEntryAtOffset(1);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ // The actual cross-navigation is suspended until the current RVH tells us
+ // it unloaded, simulate that.
+ contents()->ProceedWithCrossSiteNavigation();
+ message = process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ ASSERT_TRUE(message != NULL);
+ ViewMsg_Navigate::Read(message, &nav_params);
+ EXPECT_EQ(url3, nav_params.a.url);
+ process()->sink().ClearMessages();
+
+ // Make sure an extravagant history.go() doesn't break.
+ contents()->OnGoToEntryAtOffset(120); // Out of bounds.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ message = process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ EXPECT_TRUE(message == NULL);
+}
+
+// Test call to PruneAllButActive for the only entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForSingle) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ NavigateAndCommit(url1);
+ controller.PruneAllButActive();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url1);
+}
+
+// Test call to PruneAllButActive for last entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForLast) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ controller.PruneAllButActive();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url1);
+}
+
+// Test call to PruneAllButActive for intermediate entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForIntermediate) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ controller.PruneAllButActive();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url2);
+}
+
+// Test call to PruneAllButActive for intermediate entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+
+ controller.PruneAllButActive();
+
+ EXPECT_EQ(0, controller.GetPendingEntryIndex());
+}
+
+// Test call to PruneAllButActive for transient entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForTransient) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+ const GURL transient_url("http://foo/transient");
+
+ controller.LoadURL(
+ url0, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url0);
+ controller.LoadURL(
+ url1, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url1);
+
+ // Adding a transient with no pending entry.
+ NavigationEntryImpl* transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.AddTransientEntry(transient_entry);
+
+ controller.PruneAllButActive();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetTransientEntry()->GetURL(), transient_url);
+}
+
+// Test to ensure that when we do a history navigation back to the current
+// committed page (e.g., going forward to a slow-loading page, then pressing
+// the back button), we just stop the navigation to prevent the throbber from
+// running continuously. Otherwise, the RenderViewHost forces the throbber to
+// start, but WebKit essentially ignores the navigation and never sends a
+// message to stop the throbber.
+TEST_F(NavigationControllerTest, StopOnHistoryNavigationToCurrentPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+
+ NavigateAndCommit(url0);
+ NavigateAndCommit(url1);
+
+ // Go back to the original page, then forward to the slow page, then back
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ controller.GoForward();
+ EXPECT_EQ(1, controller.GetPendingEntryIndex());
+
+ controller.GoBack();
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+}
+
+/* TODO(brettw) These test pass on my local machine but fail on the XP buildbot
+ (but not Vista) cleaning up the directory after they run.
+ This should be fixed.
+
+// NavigationControllerHistoryTest ---------------------------------------------
+
+class NavigationControllerHistoryTest : public NavigationControllerTest {
+ public:
+ NavigationControllerHistoryTest()
+ : url0("http://foo1"),
+ url1("http://foo1"),
+ url2("http://foo1"),
+ profile_manager_(NULL) {
+ }
+
+ virtual ~NavigationControllerHistoryTest() {
+ // Prevent our base class from deleting the profile since profile's
+ // lifetime is managed by profile_manager_.
+ STLDeleteElements(&windows_);
+ }
+
+ // testing::Test overrides.
+ virtual void SetUp() {
+ NavigationControllerTest::SetUp();
+
+ // Force the session service to be created.
+ SessionService* service = new SessionService(profile());
+ SessionServiceFactory::SetForTestProfile(profile(), service);
+ service->SetWindowType(window_id, Browser::TYPE_TABBED);
+ service->SetWindowBounds(window_id, gfx::Rect(0, 1, 2, 3), false);
+ service->SetTabIndexInWindow(window_id,
+ controller.session_id(), 0);
+ controller.SetWindowID(window_id);
+
+ session_helper_.set_service(service);
+ }
+
+ virtual void TearDown() {
+ // Release profile's reference to the session service. Otherwise the file
+ // will still be open and we won't be able to delete the directory below.
+ session_helper_.ReleaseService(); // profile owns this
+ SessionServiceFactory::SetForTestProfile(profile(), NULL);
+
+ // Make sure we wait for history to shut down before continuing. The task
+ // we add will cause our message loop to quit once it is destroyed.
+ HistoryService* history =
+ profile()->GetHistoryService(Profile::IMPLICIT_ACCESS);
+ if (history) {
+ history->SetOnBackendDestroyTask(MessageLoop::QuitClosure());
+ MessageLoop::current()->Run();
+ }
+
+ // Do normal cleanup before deleting the profile directory below.
+ NavigationControllerTest::TearDown();
+
+ ASSERT_TRUE(file_util::Delete(test_dir_, true));
+ ASSERT_FALSE(file_util::PathExists(test_dir_));
+ }
+
+ // Deletes the current profile manager and creates a new one. Indirectly this
+ // shuts down the history database and reopens it.
+ void ReopenDatabase() {
+ session_helper_.set_service(NULL);
+ SessionServiceFactory::SetForTestProfile(profile(), NULL);
+
+ SessionService* service = new SessionService(profile());
+ SessionServiceFactory::SetForTestProfile(profile(), service);
+ session_helper_.set_service(service);
+ }
+
+ void GetLastSession() {
+ SessionServiceFactory::GetForProfile(profile())->TabClosed(
+ controller.window_id(), controller.session_id(), false);
+
+ ReopenDatabase();
+ Time close_time;
+
+ session_helper_.ReadWindows(&windows_);
+ }
+
+ CancelableRequestConsumer consumer;
+
+ // URLs for testing.
+ const GURL url0;
+ const GURL url1;
+ const GURL url2;
+
+ std::vector<SessionWindow*> windows_;
+
+ SessionID window_id;
+
+ SessionServiceTestHelper session_helper_;
+
+ private:
+ ProfileManager* profile_manager_;
+ FilePath test_dir_;
+};
+
+// A basic test case. Navigates to a single url, and make sure the history
+// db matches.
+TEST_F(NavigationControllerHistoryTest, Basic) {
+ NavigationControllerImpl& controller = controller_impl();
+ controller.LoadURL(url0, GURL(), content::PAGE_TRANSITION_LINK);
+ test_rvh()->SendNavigate(0, url0);
+
+ GetLastSession();
+
+ session_helper_.AssertSingleWindowWithSingleTab(windows_, 1);
+ session_helper_.AssertTabEquals(0, 0, 1, *(windows_[0]->tabs[0]));
+ TabNavigation nav1(0, url0, GURL(), string16(),
+ webkit_glue::CreateHistoryStateForURL(url0),
+ content::PAGE_TRANSITION_LINK);
+ session_helper_.AssertNavigationEquals(nav1,
+ windows_[0]->tabs[0]->navigations[0]);
+}
+
+// Navigates to three urls, then goes back and make sure the history database
+// is in sync.
+TEST_F(NavigationControllerHistoryTest, NavigationThenBack) {
+ NavigationControllerImpl& controller = controller_impl();
+ test_rvh()->SendNavigate(0, url0);
+ test_rvh()->SendNavigate(1, url1);
+ test_rvh()->SendNavigate(2, url2);
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(1, url1);
+
+ GetLastSession();
+
+ session_helper_.AssertSingleWindowWithSingleTab(windows_, 3);
+ session_helper_.AssertTabEquals(0, 1, 3, *(windows_[0]->tabs[0]));
+
+ TabNavigation nav(0, url0, GURL(), string16(),
+ webkit_glue::CreateHistoryStateForURL(url0),
+ content::PAGE_TRANSITION_LINK);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[0]);
+ nav.set_url(url1);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[1]);
+ nav.set_url(url2);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[2]);
+}
+
+// Navigates to three urls, then goes back twice, then loads a new url.
+TEST_F(NavigationControllerHistoryTest, NavigationPruning) {
+ NavigationControllerImpl& controller = controller_impl();
+ test_rvh()->SendNavigate(0, url0);
+ test_rvh()->SendNavigate(1, url1);
+ test_rvh()->SendNavigate(2, url2);
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(1, url1);
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(0, url0);
+
+ test_rvh()->SendNavigate(3, url2);
+
+ // Now have url0, and url2.
+
+ GetLastSession();
+
+ session_helper_.AssertSingleWindowWithSingleTab(windows_, 2);
+ session_helper_.AssertTabEquals(0, 1, 2, *(windows_[0]->tabs[0]));
+
+ TabNavigation nav(0, url0, GURL(), string16(),
+ webkit_glue::CreateHistoryStateForURL(url0),
+ content::PAGE_TRANSITION_LINK);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[0]);
+ nav.set_url(url2);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[1]);
+}
+*/
diff --git a/content/browser/web_contents/navigation_entry_impl.cc b/content/browser/web_contents/navigation_entry_impl.cc
new file mode 100644
index 0000000..60f58b9
--- /dev/null
+++ b/content/browser/web_contents/navigation_entry_impl.cc
@@ -0,0 +1,221 @@
+// Copyright (c) 2012 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.
+
+#include "content/browser/web_contents/navigation_entry_impl.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/net_util.h"
+#include "ui/base/text/text_elider.h"
+
+using content::SiteInstance;
+
+// Use this to get a new unique ID for a NavigationEntry during construction.
+// The returned ID is guaranteed to be nonzero (which is the "no ID" indicator).
+static int GetUniqueIDInConstructor() {
+ static int unique_id_counter = 0;
+ return ++unique_id_counter;
+}
+
+namespace content {
+
+NavigationEntry* NavigationEntry::Create() {
+ return new NavigationEntryImpl();
+}
+
+NavigationEntry* NavigationEntry::Create(const NavigationEntry& copy) {
+ return new NavigationEntryImpl(static_cast<const NavigationEntryImpl&>(copy));
+}
+
+NavigationEntryImpl* NavigationEntryImpl::FromNavigationEntry(
+ NavigationEntry* entry) {
+ return static_cast<NavigationEntryImpl*>(entry);
+}
+
+NavigationEntryImpl::NavigationEntryImpl()
+ : unique_id_(GetUniqueIDInConstructor()),
+ site_instance_(NULL),
+ page_type_(PAGE_TYPE_NORMAL),
+ update_virtual_url_with_url_(false),
+ page_id_(-1),
+ transition_type_(PAGE_TRANSITION_LINK),
+ has_post_data_(false),
+ post_id_(-1),
+ restore_type_(RESTORE_NONE),
+ is_renderer_initiated_(false),
+ is_cross_site_reload_(false) {
+}
+
+NavigationEntryImpl::NavigationEntryImpl(SiteInstanceImpl* instance,
+ int page_id,
+ const GURL& url,
+ const Referrer& referrer,
+ const string16& title,
+ PageTransition transition_type,
+ bool is_renderer_initiated)
+ : unique_id_(GetUniqueIDInConstructor()),
+ site_instance_(instance),
+ page_type_(PAGE_TYPE_NORMAL),
+ url_(url),
+ referrer_(referrer),
+ update_virtual_url_with_url_(false),
+ title_(title),
+ page_id_(page_id),
+ transition_type_(transition_type),
+ has_post_data_(false),
+ post_id_(-1),
+ restore_type_(RESTORE_NONE),
+ is_renderer_initiated_(is_renderer_initiated),
+ is_cross_site_reload_(false) {
+}
+
+NavigationEntryImpl::~NavigationEntryImpl() {
+}
+
+int NavigationEntryImpl::GetUniqueID() const {
+ return unique_id_;
+}
+
+PageType NavigationEntryImpl::GetPageType() const {
+ return page_type_;
+}
+
+void NavigationEntryImpl::SetURL(const GURL& url) {
+ url_ = url;
+ cached_display_title_.clear();
+}
+
+const GURL& NavigationEntryImpl::GetURL() const {
+ return url_;
+}
+
+void NavigationEntryImpl::SetReferrer(const Referrer& referrer) {
+ referrer_ = referrer;
+}
+
+const Referrer& NavigationEntryImpl::GetReferrer() const {
+ return referrer_;
+}
+
+void NavigationEntryImpl::SetVirtualURL(const GURL& url) {
+ virtual_url_ = (url == url_) ? GURL() : url;
+ cached_display_title_.clear();
+}
+
+const GURL& NavigationEntryImpl::GetVirtualURL() const {
+ return virtual_url_.is_empty() ? url_ : virtual_url_;
+}
+
+void NavigationEntryImpl::SetTitle(const string16& title) {
+ title_ = title;
+ cached_display_title_.clear();
+}
+
+const string16& NavigationEntryImpl::GetTitle() const {
+ return title_;
+}
+
+void NavigationEntryImpl::SetContentState(const std::string& state) {
+ content_state_ = state;
+}
+
+const std::string& NavigationEntryImpl::GetContentState() const {
+ return content_state_;
+}
+
+void NavigationEntryImpl::SetPageID(int page_id) {
+ page_id_ = page_id;
+}
+
+int32 NavigationEntryImpl::GetPageID() const {
+ return page_id_;
+}
+
+void NavigationEntryImpl::set_site_instance(SiteInstanceImpl* site_instance) {
+ site_instance_ = site_instance;
+}
+
+const string16& NavigationEntryImpl::GetTitleForDisplay(
+ const std::string& languages) const {
+ // Most pages have real titles. Don't even bother caching anything if this is
+ // the case.
+ if (!title_.empty())
+ return title_;
+
+ // More complicated cases will use the URLs as the title. This result we will
+ // cache since it's more complicated to compute.
+ if (!cached_display_title_.empty())
+ return cached_display_title_;
+
+ // Use the virtual URL first if any, and fall back on using the real URL.
+ string16 title;
+ if (!virtual_url_.is_empty()) {
+ title = net::FormatUrl(virtual_url_, languages);
+ } else if (!url_.is_empty()) {
+ title = net::FormatUrl(url_, languages);
+ }
+
+ // For file:// URLs use the filename as the title, not the full path.
+ if (url_.SchemeIsFile()) {
+ string16::size_type slashpos = title.rfind('/');
+ if (slashpos != string16::npos)
+ title = title.substr(slashpos + 1);
+ }
+
+ ui::ElideString(title, kMaxTitleChars, &cached_display_title_);
+ return cached_display_title_;
+}
+
+bool NavigationEntryImpl::IsViewSourceMode() const {
+ return virtual_url_.SchemeIs(chrome::kViewSourceScheme);
+}
+
+void NavigationEntryImpl::SetTransitionType(
+ PageTransition transition_type) {
+ transition_type_ = transition_type;
+}
+
+PageTransition NavigationEntryImpl::GetTransitionType() const {
+ return transition_type_;
+}
+
+const GURL& NavigationEntryImpl::GetUserTypedURL() const {
+ return user_typed_url_;
+}
+
+void NavigationEntryImpl::SetHasPostData(bool has_post_data) {
+ has_post_data_ = has_post_data;
+}
+
+bool NavigationEntryImpl::GetHasPostData() const {
+ return has_post_data_;
+}
+
+void NavigationEntryImpl::SetPostID(int64 post_id) {
+ post_id_ = post_id;
+}
+
+int64 NavigationEntryImpl::GetPostID() const {
+ return post_id_;
+}
+
+const FaviconStatus& NavigationEntryImpl::GetFavicon() const {
+ return favicon_;
+}
+
+FaviconStatus& NavigationEntryImpl::GetFavicon() {
+ return favicon_;
+}
+
+const SSLStatus& NavigationEntryImpl::GetSSL() const {
+ return ssl_;
+}
+
+SSLStatus& NavigationEntryImpl::GetSSL() {
+ return ssl_;
+}
+
+} // namespace content
diff --git a/content/browser/web_contents/navigation_entry_impl.h b/content/browser/web_contents/navigation_entry_impl.h
new file mode 100644
index 0000000..ec194fd
--- /dev/null
+++ b/content/browser/web_contents/navigation_entry_impl.h
@@ -0,0 +1,217 @@
+// Copyright (c) 2012 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.
+
+#ifndef CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_ENTRY_IMPL_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_ENTRY_IMPL_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/public/browser/favicon_status.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/common/ssl_status.h"
+
+namespace content {
+
+class CONTENT_EXPORT NavigationEntryImpl
+ : public NON_EXPORTED_BASE(NavigationEntry) {
+ public:
+ static NavigationEntryImpl* FromNavigationEntry(NavigationEntry* entry);
+
+ NavigationEntryImpl();
+ NavigationEntryImpl(SiteInstanceImpl* instance,
+ int page_id,
+ const GURL& url,
+ const Referrer& referrer,
+ const string16& title,
+ PageTransition transition_type,
+ bool is_renderer_initiated);
+ virtual ~NavigationEntryImpl();
+
+ // NavigationEntry implementation:
+ virtual int GetUniqueID() const OVERRIDE;
+ virtual PageType GetPageType() const OVERRIDE;
+ virtual void SetURL(const GURL& url) OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual void SetReferrer(const Referrer& referrer) OVERRIDE;
+ virtual const Referrer& GetReferrer() const OVERRIDE;
+ virtual void SetVirtualURL(const GURL& url) OVERRIDE;
+ virtual const GURL& GetVirtualURL() const OVERRIDE;
+ virtual void SetTitle(const string16& title) OVERRIDE;
+ virtual const string16& GetTitle() const OVERRIDE;
+ virtual void SetContentState(const std::string& state) OVERRIDE;
+ virtual const std::string& GetContentState() const OVERRIDE;
+ virtual void SetPageID(int page_id) OVERRIDE;
+ virtual int32 GetPageID() const OVERRIDE;
+ virtual const string16& GetTitleForDisplay(
+ const std::string& languages) const OVERRIDE;
+ virtual bool IsViewSourceMode() const OVERRIDE;
+ virtual void SetTransitionType(PageTransition transition_type) OVERRIDE;
+ virtual PageTransition GetTransitionType() const OVERRIDE;
+ virtual const GURL& GetUserTypedURL() const OVERRIDE;
+ virtual void SetHasPostData(bool has_post_data) OVERRIDE;
+ virtual bool GetHasPostData() const OVERRIDE;
+ virtual void SetPostID(int64 post_id) OVERRIDE;
+ virtual int64 GetPostID() const OVERRIDE;
+ virtual const FaviconStatus& GetFavicon() const OVERRIDE;
+ virtual FaviconStatus& GetFavicon() OVERRIDE;
+ virtual const SSLStatus& GetSSL() const OVERRIDE;
+ virtual SSLStatus& GetSSL() OVERRIDE;
+
+ void set_unique_id(int unique_id) {
+ unique_id_ = unique_id;
+ }
+
+ // The SiteInstance tells us how to share sub-processes when the tab type is
+ // TAB_CONTENTS_WEB. This will be NULL otherwise. This is a reference counted
+ // pointer to a shared site instance.
+ //
+ // Note that the SiteInstance should usually not be changed after it is set,
+ // but this may happen if the NavigationEntry was cloned and needs to use a
+ // different SiteInstance.
+ void set_site_instance(SiteInstanceImpl* site_instance);
+ SiteInstanceImpl* site_instance() const {
+ return site_instance_.get();
+ }
+
+ void set_page_type(PageType page_type) {
+ page_type_ = page_type;
+ }
+
+ bool has_virtual_url() const {
+ return !virtual_url_.is_empty();
+ }
+
+ bool update_virtual_url_with_url() const {
+ return update_virtual_url_with_url_;
+ }
+ void set_update_virtual_url_with_url(bool update) {
+ update_virtual_url_with_url_ = update;
+ }
+
+ // Extra headers (separated by \n) to send during the request.
+ void set_extra_headers(const std::string& extra_headers) {
+ extra_headers_ = extra_headers;
+ }
+ const std::string& extra_headers() const {
+ return extra_headers_;
+ }
+
+ // Whether this (pending) navigation is renderer-initiated. Resets to false
+ // for all types of navigations after commit.
+ void set_is_renderer_initiated(bool is_renderer_initiated) {
+ is_renderer_initiated_ = is_renderer_initiated;
+ }
+ bool is_renderer_initiated() const {
+ return is_renderer_initiated_;
+ }
+
+ void set_user_typed_url(const GURL& user_typed_url) {
+ user_typed_url_ = user_typed_url;
+ }
+
+ // Enumerations of the possible restore types.
+ enum RestoreType {
+ // The entry has been restored is from the last session.
+ RESTORE_LAST_SESSION,
+
+ // The entry has been restored from the current session. This is used when
+ // the user issues 'reopen closed tab'.
+ RESTORE_CURRENT_SESSION,
+
+ // The entry was not restored.
+ RESTORE_NONE
+ };
+
+ // The RestoreType for this entry. This is set if the entry was retored. This
+ // is set to RESTORE_NONE once the entry is loaded.
+ void set_restore_type(RestoreType type) {
+ restore_type_ = type;
+ }
+ RestoreType restore_type() const {
+ return restore_type_;
+ }
+
+ void set_transferred_global_request_id(
+ const GlobalRequestID& transferred_global_request_id) {
+ transferred_global_request_id_ = transferred_global_request_id;
+ }
+
+ GlobalRequestID transferred_global_request_id() const {
+ return transferred_global_request_id_;
+ }
+
+ // Whether this (pending) navigation is reload across site instances.
+ // Resets to false after commit.
+ void set_is_cross_site_reload(bool is_cross_site_reload) {
+ is_cross_site_reload_ = is_cross_site_reload;
+ }
+ bool is_cross_site_reload() const {
+ return is_cross_site_reload_;
+ }
+
+ private:
+ // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+ // Session/Tab restore save portions of this class so that it can be recreated
+ // later. If you add a new field that needs to be persisted you'll have to
+ // update SessionService/TabRestoreService appropriately.
+ // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+
+ // See the accessors above for descriptions.
+ int unique_id_;
+ scoped_refptr<SiteInstanceImpl> site_instance_;
+ PageType page_type_;
+ GURL url_;
+ Referrer referrer_;
+ GURL virtual_url_;
+ bool update_virtual_url_with_url_;
+ string16 title_;
+ FaviconStatus favicon_;
+ std::string content_state_;
+ int32 page_id_;
+ SSLStatus ssl_;
+ PageTransition transition_type_;
+ GURL user_typed_url_;
+ bool has_post_data_;
+ int64 post_id_;
+ RestoreType restore_type_;
+
+ // This member is not persisted with sesssion restore.
+ std::string extra_headers_;
+
+ // Whether the entry, while loading, was created for a renderer-initiated
+ // navigation. This dictates whether the URL should be displayed before the
+ // navigation commits. It is cleared on commit and not persisted.
+ bool is_renderer_initiated_;
+
+ // This is a cached version of the result of GetTitleForDisplay. It prevents
+ // us from having to do URL formatting on the URL every time the title is
+ // displayed. When the URL, virtual URL, or title is set, this should be
+ // cleared to force a refresh.
+ mutable string16 cached_display_title_;
+
+ // In case a navigation is transferred to a new RVH but the request has
+ // been generated in the renderer already, this identifies the old request so
+ // that it can be resumed. The old request is stored until the
+ // ResourceDispatcher receives the navigation from the renderer which
+ // carries this |transferred_global_request_id_| annotation. Once the request
+ // is transferred to the new process, this is cleared and the request
+ // continues as normal.
+ GlobalRequestID transferred_global_request_id_;
+
+ // This is set to true when this entry is being reloaded and due to changes in
+ // the state of the URL, it has to be reloaded in a different site instance.
+ // In such case, we must treat it as an existing navigation in the new site
+ // instance, instead of a new navigation. This value should not be persisted
+ // and is not needed after the entry commits.
+ bool is_cross_site_reload_;
+
+ // Copy and assignment is explicitly allowed for this class.
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_ENTRY_IMPL_H_
diff --git a/content/browser/web_contents/navigation_entry_impl_unittest.cc b/content/browser/web_contents/navigation_entry_impl_unittest.cc
new file mode 100644
index 0000000..f8fca43
--- /dev/null
+++ b/content/browser/web_contents/navigation_entry_impl_unittest.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2012 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.
+
+#include "base/string16.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/public/common/ssl_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class NavigationEntryTest : public testing::Test {
+ public:
+ NavigationEntryTest() : instance_(NULL) {
+ }
+
+ virtual void SetUp() {
+ entry1_.reset(new NavigationEntryImpl);
+
+ instance_ = static_cast<SiteInstanceImpl*>(SiteInstance::Create(NULL));
+ entry2_.reset(new NavigationEntryImpl(
+ instance_, 3,
+ GURL("test:url"),
+ Referrer(GURL("from"), WebKit::WebReferrerPolicyDefault),
+ ASCIIToUTF16("title"),
+ PAGE_TRANSITION_TYPED,
+ false));
+ }
+
+ virtual void TearDown() {
+ }
+
+ protected:
+ scoped_ptr<NavigationEntryImpl> entry1_;
+ scoped_ptr<NavigationEntryImpl> entry2_;
+ // SiteInstances are deleted when their NavigationEntries are gone.
+ SiteInstanceImpl* instance_;
+};
+
+// Test unique ID accessors
+TEST_F(NavigationEntryTest, NavigationEntryUniqueIDs) {
+ // Two entries should have different IDs by default
+ EXPECT_NE(entry1_.get()->GetUniqueID(), entry2_.get()->GetUniqueID());
+
+ // Can set an entry to have the same ID as another
+ entry2_.get()->set_unique_id(entry1_.get()->GetUniqueID());
+ EXPECT_EQ(entry1_.get()->GetUniqueID(), entry2_.get()->GetUniqueID());
+}
+
+// Test URL accessors
+TEST_F(NavigationEntryTest, NavigationEntryURLs) {
+ // Start with no virtual_url (even if a url is set)
+ EXPECT_FALSE(entry1_.get()->has_virtual_url());
+ EXPECT_FALSE(entry2_.get()->has_virtual_url());
+
+ EXPECT_EQ(GURL(), entry1_.get()->GetURL());
+ EXPECT_EQ(GURL(), entry1_.get()->GetVirtualURL());
+ EXPECT_TRUE(entry1_.get()->GetTitleForDisplay("").empty());
+
+ // Setting URL affects virtual_url and GetTitleForDisplay
+ entry1_.get()->SetURL(GURL("http://www.google.com"));
+ EXPECT_EQ(GURL("http://www.google.com"), entry1_.get()->GetURL());
+ EXPECT_EQ(GURL("http://www.google.com"), entry1_.get()->GetVirtualURL());
+ EXPECT_EQ(ASCIIToUTF16("www.google.com"),
+ entry1_.get()->GetTitleForDisplay(""));
+
+ // file:/// URLs should only show the filename.
+ entry1_.get()->SetURL(GURL("file:///foo/bar baz.txt"));
+ EXPECT_EQ(ASCIIToUTF16("bar baz.txt"),
+ entry1_.get()->GetTitleForDisplay(""));
+
+ // Title affects GetTitleForDisplay
+ entry1_.get()->SetTitle(ASCIIToUTF16("Google"));
+ EXPECT_EQ(ASCIIToUTF16("Google"), entry1_.get()->GetTitleForDisplay(""));
+
+ // Setting virtual_url doesn't affect URL
+ entry2_.get()->SetVirtualURL(GURL("display:url"));
+ EXPECT_TRUE(entry2_.get()->has_virtual_url());
+ EXPECT_EQ(GURL("test:url"), entry2_.get()->GetURL());
+ EXPECT_EQ(GURL("display:url"), entry2_.get()->GetVirtualURL());
+
+ // Having a title set in constructor overrides virtual URL
+ EXPECT_EQ(ASCIIToUTF16("title"), entry2_.get()->GetTitleForDisplay(""));
+
+ // User typed URL is independent of the others
+ EXPECT_EQ(GURL(), entry1_.get()->GetUserTypedURL());
+ EXPECT_EQ(GURL(), entry2_.get()->GetUserTypedURL());
+ entry2_.get()->set_user_typed_url(GURL("typedurl"));
+ EXPECT_EQ(GURL("typedurl"), entry2_.get()->GetUserTypedURL());
+}
+
+// Test Favicon inner class construction.
+TEST_F(NavigationEntryTest, NavigationEntryFavicons) {
+ EXPECT_EQ(GURL(), entry1_.get()->GetFavicon().url);
+ EXPECT_FALSE(entry1_.get()->GetFavicon().valid);
+}
+
+// Test SSLStatus inner class
+TEST_F(NavigationEntryTest, NavigationEntrySSLStatus) {
+ // Default (unknown)
+ EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry1_.get()->GetSSL().security_style);
+ EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry2_.get()->GetSSL().security_style);
+ EXPECT_EQ(0, entry1_.get()->GetSSL().cert_id);
+ EXPECT_EQ(0U, entry1_.get()->GetSSL().cert_status);
+ EXPECT_EQ(-1, entry1_.get()->GetSSL().security_bits);
+ int content_status = entry1_.get()->GetSSL().content_status;
+ EXPECT_FALSE(!!(content_status & SSLStatus::DISPLAYED_INSECURE_CONTENT));
+ EXPECT_FALSE(!!(content_status & SSLStatus::RAN_INSECURE_CONTENT));
+}
+
+// Test other basic accessors
+TEST_F(NavigationEntryTest, NavigationEntryAccessors) {
+ // SiteInstance
+ EXPECT_TRUE(entry1_.get()->site_instance() == NULL);
+ EXPECT_EQ(instance_, entry2_.get()->site_instance());
+ entry1_.get()->set_site_instance(instance_);
+ EXPECT_EQ(instance_, entry1_.get()->site_instance());
+
+ // Page type
+ EXPECT_EQ(PAGE_TYPE_NORMAL, entry1_.get()->GetPageType());
+ EXPECT_EQ(PAGE_TYPE_NORMAL, entry2_.get()->GetPageType());
+ entry2_.get()->set_page_type(PAGE_TYPE_INTERSTITIAL);
+ EXPECT_EQ(PAGE_TYPE_INTERSTITIAL, entry2_.get()->GetPageType());
+
+ // Referrer
+ EXPECT_EQ(GURL(), entry1_.get()->GetReferrer().url);
+ EXPECT_EQ(GURL("from"), entry2_.get()->GetReferrer().url);
+ entry2_.get()->SetReferrer(
+ Referrer(GURL("from2"), WebKit::WebReferrerPolicyDefault));
+ EXPECT_EQ(GURL("from2"), entry2_.get()->GetReferrer().url);
+
+ // Title
+ EXPECT_EQ(string16(), entry1_.get()->GetTitle());
+ EXPECT_EQ(ASCIIToUTF16("title"), entry2_.get()->GetTitle());
+ entry2_.get()->SetTitle(ASCIIToUTF16("title2"));
+ EXPECT_EQ(ASCIIToUTF16("title2"), entry2_.get()->GetTitle());
+
+ // State
+ EXPECT_EQ(std::string(), entry1_.get()->GetContentState());
+ EXPECT_EQ(std::string(), entry2_.get()->GetContentState());
+ entry2_.get()->SetContentState("state");
+ EXPECT_EQ("state", entry2_.get()->GetContentState());
+
+ // Page ID
+ EXPECT_EQ(-1, entry1_.get()->GetPageID());
+ EXPECT_EQ(3, entry2_.get()->GetPageID());
+ entry2_.get()->SetPageID(2);
+ EXPECT_EQ(2, entry2_.get()->GetPageID());
+
+ // Transition type
+ EXPECT_EQ(PAGE_TRANSITION_LINK, entry1_.get()->GetTransitionType());
+ EXPECT_EQ(PAGE_TRANSITION_TYPED, entry2_.get()->GetTransitionType());
+ entry2_.get()->SetTransitionType(PAGE_TRANSITION_RELOAD);
+ EXPECT_EQ(PAGE_TRANSITION_RELOAD, entry2_.get()->GetTransitionType());
+
+ // Is renderer initiated
+ EXPECT_FALSE(entry1_.get()->is_renderer_initiated());
+ EXPECT_FALSE(entry2_.get()->is_renderer_initiated());
+ entry2_.get()->set_is_renderer_initiated(true);
+ EXPECT_TRUE(entry2_.get()->is_renderer_initiated());
+
+ // Post Data
+ EXPECT_FALSE(entry1_.get()->GetHasPostData());
+ EXPECT_FALSE(entry2_.get()->GetHasPostData());
+ entry2_.get()->SetHasPostData(true);
+ EXPECT_TRUE(entry2_.get()->GetHasPostData());
+
+ // Restored
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE, entry1_->restore_type());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE, entry2_->restore_type());
+ entry2_->set_restore_type(NavigationEntryImpl::RESTORE_LAST_SESSION);
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_LAST_SESSION, entry2_->restore_type());
+}
+
+} // namespace content
diff --git a/content/browser/web_contents/web_drag_dest_gtk.cc b/content/browser/web_contents/web_drag_dest_gtk.cc
index 572a135c..3b6572e 100644
--- a/content/browser/web_contents/web_drag_dest_gtk.cc
+++ b/content/browser/web_contents/web_drag_dest_gtk.cc
@@ -11,8 +11,8 @@
#include "base/message_loop.h"
#include "base/utf_string_conversions.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
-#include "content/browser/tab_contents/drag_utils_gtk.h"
#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/web_contents/drag_utils_gtk.h"
#include "content/public/browser/web_drag_dest_delegate.h"
#include "content/public/common/url_constants.h"
#include "net/base/net_util.h"
diff --git a/content/browser/web_contents/web_drag_source_gtk.cc b/content/browser/web_contents/web_drag_source_gtk.cc
index 31e9c03..c18d4a2 100644
--- a/content/browser/web_contents/web_drag_source_gtk.cc
+++ b/content/browser/web_contents/web_drag_source_gtk.cc
@@ -13,8 +13,8 @@
#include "content/browser/download/drag_download_file.h"
#include "content/browser/download/drag_download_util.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
-#include "content/browser/tab_contents/drag_utils_gtk.h"
#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/web_contents/drag_utils_gtk.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_view_host_delegate.h"
#include "content/public/browser/web_contents_view.h"