summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
authorjam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-18 08:17:44 +0000
committerjam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-18 08:17:44 +0000
commit0dd3a0ab424a01e887ac7f69dbbae6aaee6843c0 (patch)
treee29d015f414f9a33fff40171b743261bd9f3c82f /content
parent9f60a236323e348ab1a0343d6acdafa99125ac3b (diff)
downloadchromium_src-0dd3a0ab424a01e887ac7f69dbbae6aaee6843c0.zip
chromium_src-0dd3a0ab424a01e887ac7f69dbbae6aaee6843c0.tar.gz
chromium_src-0dd3a0ab424a01e887ac7f69dbbae6aaee6843c0.tar.bz2
Start moving core pieces of Chrome multi-process code to src\content. I'm starting with tab_contents directory.In future changes the headers that include these files will be updated. Once all the files are moved (i.e. renderer_host, rest of browser, renderer etc), then refactoring can begin so that content\DEPS doesn't have chrome in it.
Review URL: http://codereview.chromium.org/6537015 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75369 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r--content/DEPS23
-rw-r--r--content/OWNERS4
-rw-r--r--content/browser/tab_contents/background_contents.cc269
-rw-r--r--content/browser/tab_contents/background_contents.h186
-rw-r--r--content/browser/tab_contents/constrained_window.h62
-rw-r--r--content/browser/tab_contents/infobar_delegate.cc83
-rw-r--r--content/browser/tab_contents/infobar_delegate.h118
-rw-r--r--content/browser/tab_contents/interstitial_page.cc732
-rw-r--r--content/browser/tab_contents/interstitial_page.h245
-rw-r--r--content/browser/tab_contents/language_state.cc76
-rw-r--r--content/browser/tab_contents/language_state.h109
-rw-r--r--content/browser/tab_contents/navigation_controller.cc1203
-rw-r--r--content/browser/tab_contents/navigation_controller.h603
-rw-r--r--content/browser/tab_contents/navigation_controller_unittest.cc2025
-rw-r--r--content/browser/tab_contents/navigation_entry.cc106
-rw-r--r--content/browser/tab_contents/navigation_entry.h429
-rw-r--r--content/browser/tab_contents/navigation_entry_unittest.cc183
-rw-r--r--content/browser/tab_contents/page_navigator.h30
-rw-r--r--content/browser/tab_contents/provisional_load_details.cc34
-rw-r--r--content/browser/tab_contents/provisional_load_details.h82
-rw-r--r--content/browser/tab_contents/render_view_host_manager.cc729
-rw-r--r--content/browser/tab_contents/render_view_host_manager.h273
-rw-r--r--content/browser/tab_contents/render_view_host_manager_unittest.cc345
-rw-r--r--content/browser/tab_contents/tab_contents.cc2851
-rw-r--r--content/browser/tab_contents/tab_contents.h1258
-rw-r--r--content/browser/tab_contents/tab_contents_delegate.cc227
-rw-r--r--content/browser/tab_contents/tab_contents_delegate.h331
-rw-r--r--content/browser/tab_contents/tab_contents_observer.cc32
-rw-r--r--content/browser/tab_contents/tab_contents_observer.h69
-rw-r--r--content/browser/tab_contents/tab_contents_view.cc169
-rw-r--r--content/browser/tab_contents/tab_contents_view.h218
-rw-r--r--content/browser/tab_contents/test_tab_contents.cc107
-rw-r--r--content/browser/tab_contents/test_tab_contents.h81
-rw-r--r--content/content.gyp13
-rw-r--r--content/content_browser.gypi60
-rw-r--r--content/content_common.gypi25
36 files changed, 13390 insertions, 0 deletions
diff --git a/content/DEPS b/content/DEPS
new file mode 100644
index 0000000..fb297a3
--- /dev/null
+++ b/content/DEPS
@@ -0,0 +1,23 @@
+# Do NOT add chrome to the list below. We shouldn't be including files from
+# src/chrome in src/content.
+include_rules = [
+ "+app",
+
+ # TEMPORARY ONLY WHILE WE REDUCE THE DEPENDENCIES.
+ # When the src\content refactoring is complete, this will be unnecessary (and
+ # in fact, a layering violation).
+ "+chrome",
+
+ "+grit",
+ "+net",
+
+ # Don't allow inclusion of these other libs we shouldn't be calling directly.
+ "-v8",
+ "-tools",
+
+ # Allow inclusion of WebKit API files.
+ "+third_party/WebKit/Source/WebKit/chromium",
+
+ "+ui",
+ "+webkit",
+]
diff --git a/content/OWNERS b/content/OWNERS
new file mode 100644
index 0000000..f8773be
--- /dev/null
+++ b/content/OWNERS
@@ -0,0 +1,4 @@
+ben@chromium.org
+darin@chromium.org
+jam@chromium.org
+brettw@chromium.org
diff --git a/content/browser/tab_contents/background_contents.cc b/content/browser/tab_contents/background_contents.cc
new file mode 100644
index 0000000..d8c11e0
--- /dev/null
+++ b/content/browser/tab_contents/background_contents.cc
@@ -0,0 +1,269 @@
+// Copyright (c) 2011 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/tab_contents/background_contents.h"
+
+#include "chrome/browser/background_contents_service.h"
+#include "chrome/browser/browsing_instance.h"
+#include "chrome/browser/desktop_notification_handler.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/renderer_preferences_util.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/common/view_types.h"
+#include "chrome/common/render_messages_params.h"
+#include "ui/gfx/rect.h"
+
+////////////////
+// BackgroundContents
+
+BackgroundContents::BackgroundContents(SiteInstance* site_instance,
+ int routing_id,
+ Delegate* delegate)
+ : delegate_(delegate) {
+ Profile* profile = site_instance->browsing_instance()->profile();
+
+ // TODO(rafaelw): Implement correct session storage.
+ render_view_host_ = new RenderViewHost(site_instance, this, routing_id, NULL);
+ render_view_host_->AllowScriptToClose(true);
+
+ // Close ourselves when the application is shutting down.
+ registrar_.Add(this, NotificationType::APP_TERMINATING,
+ NotificationService::AllSources());
+
+ // Register for our parent profile to shutdown, so we can shut ourselves down
+ // as well (should only be called for OTR profiles, as we should receive
+ // APP_TERMINATING before non-OTR profiles are destroyed).
+ registrar_.Add(this, NotificationType::PROFILE_DESTROYED,
+ Source<Profile>(profile));
+ desktop_notification_handler_.reset(
+ new DesktopNotificationHandler(NULL, site_instance->GetProcess()));
+}
+
+// Exposed to allow creating mocks.
+BackgroundContents::BackgroundContents()
+ : delegate_(NULL),
+ render_view_host_(NULL) {
+}
+
+BackgroundContents::~BackgroundContents() {
+ if (!render_view_host_) // Will be null for unit tests.
+ return;
+ Profile* profile = render_view_host_->process()->profile();
+ NotificationService::current()->Notify(
+ NotificationType::BACKGROUND_CONTENTS_DELETED,
+ Source<Profile>(profile),
+ Details<BackgroundContents>(this));
+ render_view_host_->Shutdown(); // deletes render_view_host
+}
+
+BackgroundContents* BackgroundContents::GetAsBackgroundContents() {
+ return this;
+}
+
+RenderViewHostDelegate::View* BackgroundContents::GetViewDelegate() {
+ return this;
+}
+
+const GURL& BackgroundContents::GetURL() const {
+ return url_;
+}
+
+ViewType::Type BackgroundContents::GetRenderViewType() const {
+ return ViewType::BACKGROUND_CONTENTS;
+}
+
+int BackgroundContents::GetBrowserWindowID() const {
+ return extension_misc::kUnknownWindowId;
+}
+
+void BackgroundContents::DidNavigate(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // We only care when the outer frame changes.
+ if (!PageTransition::IsMainFrame(params.transition))
+ return;
+
+ // Note: because BackgroundContents are only available to extension apps,
+ // navigation is limited to urls within the app's extent. This is enforced in
+ // RenderView::decidePolicyForNaviation. If BackgroundContents become
+ // available as a part of the web platform, it probably makes sense to have
+ // some way to scope navigation of a background page to its opener's security
+ // origin. Note: if the first navigation is to a URL outside the app's
+ // extent a background page will be opened but will remain at about:blank.
+ url_ = params.url;
+
+ Profile* profile = render_view_host->process()->profile();
+ NotificationService::current()->Notify(
+ NotificationType::BACKGROUND_CONTENTS_NAVIGATED,
+ Source<Profile>(profile),
+ Details<BackgroundContents>(this));
+}
+
+void BackgroundContents::RunJavaScriptMessage(
+ const std::wstring& message,
+ const std::wstring& default_prompt,
+ const GURL& frame_url,
+ const int flags,
+ IPC::Message* reply_msg,
+ bool* did_suppress_message) {
+ // TODO(rafaelw): Implement, The JavaScriptModalDialog needs to learn about
+ // BackgroundContents.
+ *did_suppress_message = true;
+}
+
+bool BackgroundContents::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ return false;
+}
+
+void BackgroundContents::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ // TODO(rafaelw): Implement pagegroup ref-counting so that non-persistent
+ // background pages are closed when the last referencing frame is closed.
+ switch (type.value) {
+ case NotificationType::PROFILE_DESTROYED:
+ case NotificationType::APP_TERMINATING: {
+ delete this;
+ break;
+ }
+ default:
+ NOTREACHED() << "Unexpected notification sent.";
+ break;
+ }
+}
+
+void BackgroundContents::OnMessageBoxClosed(IPC::Message* reply_msg,
+ bool success,
+ const std::wstring& prompt) {
+ render_view_host_->JavaScriptMessageBoxClosed(reply_msg, success, prompt);
+}
+
+gfx::NativeWindow BackgroundContents::GetMessageBoxRootWindow() {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+TabContents* BackgroundContents::AsTabContents() {
+ return NULL;
+}
+
+ExtensionHost* BackgroundContents::AsExtensionHost() {
+ return NULL;
+}
+
+void BackgroundContents::UpdateInspectorSetting(const std::string& key,
+ const std::string& value) {
+ Profile* profile = render_view_host_->process()->profile();
+ RenderViewHostDelegateHelper::UpdateInspectorSetting(profile, key, value);
+}
+
+void BackgroundContents::ClearInspectorSettings() {
+ Profile* profile = render_view_host_->process()->profile();
+ RenderViewHostDelegateHelper::ClearInspectorSettings(profile);
+}
+
+void BackgroundContents::Close(RenderViewHost* render_view_host) {
+ Profile* profile = render_view_host->process()->profile();
+ NotificationService::current()->Notify(
+ NotificationType::BACKGROUND_CONTENTS_CLOSED,
+ Source<Profile>(profile),
+ Details<BackgroundContents>(this));
+ delete this;
+}
+
+void BackgroundContents::RenderViewGone(RenderViewHost* rvh,
+ base::TerminationStatus status,
+ int error_code) {
+ // Our RenderView went away, so we should go away also, so killing the process
+ // via the TaskManager doesn't permanently leave a BackgroundContents hanging
+ // around the system, blocking future instances from being created
+ // (http://crbug.com/65189).
+ delete this;
+}
+
+bool BackgroundContents::OnMessageReceived(const IPC::Message& message) {
+ // Forward desktop notification IPCs if any to the
+ // DesktopNotificationHandler.
+ return desktop_notification_handler_->OnMessageReceived(message);
+}
+
+RendererPreferences BackgroundContents::GetRendererPrefs(
+ Profile* profile) const {
+ RendererPreferences preferences;
+ renderer_preferences_util::UpdateFromSystemSettings(&preferences, profile);
+ return preferences;
+}
+
+WebPreferences BackgroundContents::GetWebkitPrefs() {
+ // TODO(rafaelw): Consider enabling the webkit_prefs.dom_paste_enabled for
+ // apps.
+ Profile* profile = render_view_host_->process()->profile();
+ return RenderViewHostDelegateHelper::GetWebkitPrefs(profile,
+ false); // is_dom_ui
+}
+
+void BackgroundContents::ProcessWebUIMessage(
+ const ViewHostMsg_DomMessage_Params& params) {
+ // TODO(rafaelw): It may make sense for extensions to be able to open
+ // BackgroundContents to chrome-extension://<id> pages. Consider implementing.
+ render_view_host_->BlockExtensionRequest(params.request_id);
+}
+
+void BackgroundContents::CreateNewWindow(
+ int route_id,
+ const ViewHostMsg_CreateWindow_Params& params) {
+ delegate_view_helper_.CreateNewWindow(
+ route_id,
+ render_view_host_->process()->profile(),
+ render_view_host_->site_instance(),
+ WebUIFactory::GetWebUIType(render_view_host_->process()->profile(), url_),
+ this,
+ params.window_container_type,
+ params.frame_name);
+}
+
+void BackgroundContents::CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) {
+ NOTREACHED();
+}
+
+void BackgroundContents::CreateNewFullscreenWidget(int route_id) {
+ NOTREACHED();
+}
+
+void BackgroundContents::ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ TabContents* contents = delegate_view_helper_.GetCreatedWindow(route_id);
+ if (contents)
+ delegate_->AddTabContents(contents, disposition, initial_pos, user_gesture);
+}
+
+void BackgroundContents::ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) {
+ NOTIMPLEMENTED();
+}
+
+void BackgroundContents::ShowCreatedFullscreenWidget(int route_id) {
+ NOTIMPLEMENTED();
+}
+
+// static
+BackgroundContents*
+BackgroundContents::GetBackgroundContentsByID(int render_process_id,
+ int render_view_id) {
+ RenderViewHost* render_view_host =
+ RenderViewHost::FromID(render_process_id, render_view_id);
+ if (!render_view_host)
+ return NULL;
+
+ return render_view_host->delegate()->GetAsBackgroundContents();
+}
diff --git a/content/browser/tab_contents/background_contents.h b/content/browser/tab_contents/background_contents.h
new file mode 100644
index 0000000..e4455fb
--- /dev/null
+++ b/content/browser/tab_contents/background_contents.h
@@ -0,0 +1,186 @@
+// Copyright (c) 2011 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_TAB_CONTENTS_BACKGROUND_CONTENTS_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_BACKGROUND_CONTENTS_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#include "chrome/browser/tab_contents/render_view_host_delegate_helper.h"
+#include "chrome/browser/ui/app_modal_dialogs/js_modal_dialog.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/view_types.h"
+#include "chrome/common/window_container_type.h"
+#include "webkit/glue/window_open_disposition.h"
+
+class TabContents;
+struct WebPreferences;
+class DesktopNotificationHandler;
+
+namespace gfx {
+class Rect;
+}
+
+// This class is a peer of TabContents. It can host a renderer, but does not
+// have any visible display. Its navigation is not managed by a
+// NavigationController because is has no facility for navigating (other than
+// programatically view window.location.href) or RenderViewHostManager because
+// it is never allowed to navigate across a SiteInstance boundary.
+class BackgroundContents : public RenderViewHostDelegate,
+ public RenderViewHostDelegate::View,
+ public NotificationObserver,
+ public JavaScriptAppModalDialogDelegate {
+ public:
+ class Delegate {
+ public:
+ // Called by ShowCreatedWindow. Asks the delegate to attach the opened
+ // TabContents to a suitable container (e.g. browser) or to show it if it's
+ // a popup window.
+ virtual void AddTabContents(TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ BackgroundContents(SiteInstance* site_instance,
+ int routing_id,
+ Delegate* delegate);
+ virtual ~BackgroundContents();
+
+ // Provide access to the RenderViewHost for the
+ // RenderViewHostDelegateViewHelper
+ RenderViewHost* render_view_host() { return render_view_host_; }
+
+ // RenderViewHostDelegate implementation.
+ virtual BackgroundContents* GetAsBackgroundContents();
+ virtual RenderViewHostDelegate::View* GetViewDelegate();
+ virtual const GURL& GetURL() const;
+ virtual ViewType::Type GetRenderViewType() const;
+ virtual int GetBrowserWindowID() const;
+ virtual void DidNavigate(RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params);
+ virtual WebPreferences GetWebkitPrefs();
+ virtual void ProcessWebUIMessage(const ViewHostMsg_DomMessage_Params& params);
+ virtual void RunJavaScriptMessage(const std::wstring& message,
+ const std::wstring& default_prompt,
+ const GURL& frame_url,
+ const int flags,
+ IPC::Message* reply_msg,
+ bool* did_suppress_message);
+ virtual void Close(RenderViewHost* render_view_host);
+ virtual RendererPreferences GetRendererPrefs(Profile* profile) const;
+ virtual void RenderViewGone(RenderViewHost* rvh,
+ base::TerminationStatus status,
+ int error_code);
+ virtual bool OnMessageReceived(const IPC::Message& message);
+
+ // RenderViewHostDelegate::View
+ 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 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,
+ WebKit::WebDragOperationsMask allowed_operations,
+ const SkBitmap& image,
+ const gfx::Point& image_offset) {}
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) {}
+ virtual void GotFocus() {}
+ virtual void TakeFocus(bool reverse) {}
+ virtual void LostCapture() {}
+ virtual void Activate() {}
+ virtual void Deactivate() {}
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut);
+ virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {}
+ virtual void HandleMouseMove() {}
+ virtual void HandleMouseDown() {}
+ virtual void HandleMouseLeave() {}
+ virtual void HandleMouseUp() {}
+ virtual void HandleMouseActivate() {}
+ virtual void UpdatePreferredSize(const gfx::Size& new_size) {}
+
+ // NotificationObserver
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // Overridden from JavaScriptAppModalDialogDelegate:
+ virtual void OnMessageBoxClosed(IPC::Message* reply_msg,
+ bool success,
+ const std::wstring& prompt);
+ virtual void SetSuppressMessageBoxes(bool suppress_message_boxes) {}
+ virtual gfx::NativeWindow GetMessageBoxRootWindow();
+ virtual TabContents* AsTabContents();
+ virtual ExtensionHost* AsExtensionHost();
+
+ virtual void UpdateInspectorSetting(const std::string& key,
+ const std::string& value);
+ virtual void ClearInspectorSettings();
+
+ // Helper to find the BackgroundContents that originated the given request.
+ // Can be NULL if the page has been closed or some other error occurs.
+ // Should only be called from the UI thread, since it accesses
+ // BackgroundContents.
+ static BackgroundContents* GetBackgroundContentsByID(int render_process_id,
+ int render_view_id);
+
+ protected:
+ // Exposed for testing.
+ BackgroundContents();
+
+ private:
+ // The delegate for this BackgroundContents.
+ Delegate* delegate_;
+
+ // The host for our HTML content.
+ RenderViewHost* render_view_host_;
+
+ // Common implementations of some RenderViewHostDelegate::View methods.
+ RenderViewHostDelegateViewHelper delegate_view_helper_;
+
+ // The URL being hosted.
+ GURL url_;
+
+ NotificationRegistrar registrar_;
+
+ // Handles desktop notification IPCs.
+ scoped_ptr<DesktopNotificationHandler> desktop_notification_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundContents);
+};
+
+// This is the data sent out as the details with BACKGROUND_CONTENTS_OPENED.
+struct BackgroundContentsOpenedDetails {
+ // The BackgroundContents object that has just been opened.
+ BackgroundContents* contents;
+
+ // The name of the parent frame for these contents.
+ const string16& frame_name;
+
+ // The ID of the parent application (if any).
+ const string16& application_id;
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_BACKGROUND_CONTENTS_H_
diff --git a/content/browser/tab_contents/constrained_window.h b/content/browser/tab_contents/constrained_window.h
new file mode 100644
index 0000000..7dd0b40
--- /dev/null
+++ b/content/browser/tab_contents/constrained_window.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2006-2008 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_TAB_CONTENTS_CONSTRAINED_WINDOW_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_CONSTRAINED_WINDOW_H_
+#pragma once
+
+#include "build/build_config.h"
+
+// The different platform specific subclasses use different delegates for their
+// dialogs.
+#if defined(OS_WIN)
+namespace views {
+class WindowDelegate;
+class DialogDelegate;
+}
+typedef views::WindowDelegate ConstrainedWindowDelegate;
+typedef views::DialogDelegate ConstrainedDialogDelegate;
+#elif defined(OS_MACOSX)
+class ConstrainedWindowMacDelegate;
+class ConstrainedWindowMacDelegateSystemSheet;
+typedef ConstrainedWindowMacDelegate ConstrainedWindowDelegate;
+typedef ConstrainedWindowMacDelegateSystemSheet ConstrainedDialogDelegate;
+#elif defined(TOOLKIT_USES_GTK)
+class ConstrainedWindowGtkDelegate;
+typedef ConstrainedWindowGtkDelegate ConstrainedWindowDelegate;
+typedef ConstrainedWindowGtkDelegate ConstrainedDialogDelegate;
+#endif
+
+class TabContents;
+
+///////////////////////////////////////////////////////////////////////////////
+// ConstrainedWindow
+//
+// This interface represents a window that is constrained to a TabContents'
+// bounds.
+//
+class ConstrainedWindow {
+ public:
+ // Create a Constrained Window that contains a platform specific client
+ // area. Typical uses include the HTTP Basic Auth prompt. The caller must
+ // provide a delegate to describe the content area and to respond to events.
+ static ConstrainedWindow* CreateConstrainedDialog(
+ TabContents* owner,
+ ConstrainedWindowDelegate* delegate);
+
+ // Makes the Constrained Window visible. Only one Constrained Window is shown
+ // at a time per tab.
+ virtual void ShowConstrainedWindow() = 0;
+
+ // Closes the Constrained Window.
+ virtual void CloseConstrainedWindow() = 0;
+
+ // Sets focus on the Constrained Window.
+ virtual void FocusConstrainedWindow() {}
+
+ protected:
+ virtual ~ConstrainedWindow() {}
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_CONSTRAINED_WINDOW_H_
diff --git a/content/browser/tab_contents/infobar_delegate.cc b/content/browser/tab_contents/infobar_delegate.cc
new file mode 100644
index 0000000..77b76c9
--- /dev/null
+++ b/content/browser/tab_contents/infobar_delegate.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 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/tab_contents/infobar_delegate.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "content/browser/tab_contents/navigation_entry.h"
+#include "content/browser/tab_contents/navigation_controller.h"
+#include "content/browser/tab_contents/tab_contents.h"
+
+// InfoBarDelegate ------------------------------------------------------------
+
+InfoBarDelegate::~InfoBarDelegate() {
+}
+
+bool InfoBarDelegate::EqualsDelegate(InfoBarDelegate* delegate) const {
+ return false;
+}
+
+bool InfoBarDelegate::ShouldExpire(
+ const NavigationController::LoadCommittedDetails& details) const {
+ return (contents_unique_id_ != details.entry->unique_id()) ||
+ (PageTransition::StripQualifier(details.entry->transition_type()) ==
+ PageTransition::RELOAD);
+}
+
+void InfoBarDelegate::InfoBarDismissed() {
+}
+
+void InfoBarDelegate::InfoBarClosed() {
+}
+
+SkBitmap* InfoBarDelegate::GetIcon() const {
+ return NULL;
+}
+
+InfoBarDelegate::Type InfoBarDelegate::GetInfoBarType() const {
+ return WARNING_TYPE;
+}
+
+ConfirmInfoBarDelegate* InfoBarDelegate::AsConfirmInfoBarDelegate() {
+ return NULL;
+}
+
+CrashedExtensionInfoBarDelegate*
+ InfoBarDelegate::AsCrashedExtensionInfoBarDelegate() {
+ return NULL;
+}
+
+ExtensionInfoBarDelegate* InfoBarDelegate::AsExtensionInfoBarDelegate() {
+ return NULL;
+}
+
+LinkInfoBarDelegate* InfoBarDelegate::AsLinkInfoBarDelegate() {
+ return NULL;
+}
+
+PluginInstallerInfoBarDelegate*
+ InfoBarDelegate::AsPluginInstallerInfoBarDelegate() {
+ return NULL;
+}
+
+ThemeInstalledInfoBarDelegate*
+ InfoBarDelegate::AsThemePreviewInfobarDelegate() {
+ return NULL;
+}
+
+TranslateInfoBarDelegate* InfoBarDelegate::AsTranslateInfoBarDelegate() {
+ return NULL;
+}
+
+InfoBarDelegate::InfoBarDelegate(TabContents* contents)
+ : contents_unique_id_(0) {
+ if (contents)
+ StoreActiveEntryUniqueID(contents);
+}
+
+void InfoBarDelegate::StoreActiveEntryUniqueID(TabContents* contents) {
+ NavigationEntry* active_entry = contents->controller().GetActiveEntry();
+ contents_unique_id_ = active_entry ? active_entry->unique_id() : 0;
+}
diff --git a/content/browser/tab_contents/infobar_delegate.h b/content/browser/tab_contents/infobar_delegate.h
new file mode 100644
index 0000000..b4c6921
--- /dev/null
+++ b/content/browser/tab_contents/infobar_delegate.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2011 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_TAB_CONTENTS_INFOBAR_DELEGATE_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_INFOBAR_DELEGATE_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/string16.h"
+#include "content/browser/tab_contents/navigation_controller.h"
+#include "webkit/glue/window_open_disposition.h"
+
+class ConfirmInfoBarDelegate;
+class CrashedExtensionInfoBarDelegate;
+class ExtensionInfoBarDelegate;
+class InfoBar;
+class LinkInfoBarDelegate;
+class PluginInstallerInfoBarDelegate;
+class SkBitmap;
+class ThemeInstalledInfoBarDelegate;
+class TranslateInfoBarDelegate;
+
+// An interface implemented by objects wishing to control an InfoBar.
+// Implementing this interface is not sufficient to use an InfoBar, since it
+// does not map to a specific InfoBar type. Instead, you must implement either
+// LinkInfoBarDelegate or ConfirmInfoBarDelegate, or override with your own
+// delegate for your own InfoBar variety.
+//
+// --- WARNING ---
+// When creating your InfoBarDelegate subclass, it is recommended that you
+// design it such that you instantiate a brand new delegate for every call to
+// AddInfoBar, rather than re-using/sharing a delegate object. Otherwise,
+// you need to consider the fact that more than one InfoBar instance can exist
+// and reference the same delegate -- even though it is also true that we only
+// ever fully show one infobar (they don't stack). The dual-references occur
+// because a second InfoBar can be added while the first one is in the process
+// of closing (the animations). This can cause problems because when the first
+// one does finally fully close InfoBarDelegate::InfoBarClosed() is called,
+// and the delegate is free to clean itself up or reset state, which may have
+// fatal consequences for the InfoBar that was in the process of opening (or is
+// now fully opened) -- it is referencing a delegate that may not even exist
+// anymore.
+// As such, it is generally much safer to dedicate a delegate instance to
+// AddInfoBar!
+class InfoBarDelegate {
+ public:
+ // The type of the infobar. It controls its appearance, such as its background
+ // color.
+ enum Type {
+ WARNING_TYPE,
+ PAGE_ACTION_TYPE,
+ };
+
+ virtual ~InfoBarDelegate();
+
+ // Called to create the InfoBar. Implementation of this method is
+ // platform-specific.
+ virtual InfoBar* CreateInfoBar() = 0;
+
+ // Returns true if the supplied |delegate| is equal to this one. Equality is
+ // left to the implementation to define. This function is called by the
+ // TabContents when determining whether or not a delegate should be added
+ // because a matching one already exists. If this function returns true, the
+ // TabContents will not add the new delegate because it considers one to
+ // already be present.
+ virtual bool EqualsDelegate(InfoBarDelegate* delegate) const;
+
+ // Returns true if the InfoBar should be closed automatically after the page
+ // is navigated. The default behavior is to return true if the page is
+ // navigated somewhere else or reloaded.
+ virtual bool ShouldExpire(
+ const NavigationController::LoadCommittedDetails& details) const;
+
+ // Called when the user clicks on the close button to dismiss the infobar.
+ virtual void InfoBarDismissed();
+
+ // Called after the InfoBar is closed. The delegate is free to delete itself
+ // at this point.
+ virtual void InfoBarClosed();
+
+ // Return the icon to be shown for this InfoBar. If the returned bitmap is
+ // NULL, no icon is shown.
+ virtual SkBitmap* GetIcon() const;
+
+ // Returns the type of the infobar. The type determines the appearance (such
+ // as background color) of the infobar.
+ virtual Type GetInfoBarType() const;
+
+ // Type-checking downcast routines:
+ virtual ConfirmInfoBarDelegate* AsConfirmInfoBarDelegate();
+ virtual CrashedExtensionInfoBarDelegate* AsCrashedExtensionInfoBarDelegate();
+ virtual ExtensionInfoBarDelegate* AsExtensionInfoBarDelegate();
+ virtual LinkInfoBarDelegate* AsLinkInfoBarDelegate();
+ virtual PluginInstallerInfoBarDelegate* AsPluginInstallerInfoBarDelegate();
+ virtual ThemeInstalledInfoBarDelegate* AsThemePreviewInfobarDelegate();
+ virtual TranslateInfoBarDelegate* AsTranslateInfoBarDelegate();
+
+ protected:
+ // Provided to subclasses as a convenience to initialize the state of this
+ // object. If |contents| is non-NULL, its active entry's unique ID will be
+ // stored using StoreActiveEntryUniqueID automatically.
+ explicit InfoBarDelegate(TabContents* contents);
+
+ // Store the unique id for the active entry in the specified TabContents, to
+ // be used later upon navigation to determine if this InfoBarDelegate should
+ // be expired from |contents_|.
+ void StoreActiveEntryUniqueID(TabContents* contents);
+
+ private:
+ // The unique id of the active NavigationEntry of the TabContents that we were
+ // opened for. Used to help expire on navigations.
+ int contents_unique_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(InfoBarDelegate);
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_INFOBAR_DELEGATE_H_
diff --git a/content/browser/tab_contents/interstitial_page.cc b/content/browser/tab_contents/interstitial_page.cc
new file mode 100644
index 0000000..d13965e
--- /dev/null
+++ b/content/browser/tab_contents/interstitial_page.cc
@@ -0,0 +1,732 @@
+// Copyright (c) 2011 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/tab_contents/interstitial_page.h"
+
+#include <vector>
+
+#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 "chrome/browser/browser_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_thread.h"
+#include "chrome/browser/dom_operation_notification_details.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/renderer_host/render_process_host.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/renderer_preferences_util.h"
+#include "chrome/common/bindings_policy.h"
+#include "chrome/common/dom_storage_common.h"
+#include "chrome/common/net/url_request_context_getter.h"
+#include "chrome/common/notification_source.h"
+#include "content/browser/tab_contents/navigation_controller.h"
+#include "content/browser/tab_contents/navigation_entry.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/tab_contents/tab_contents_view.h"
+#include "net/base/escape.h"
+
+#if defined(TOOLKIT_GTK)
+#include "chrome/browser/ui/gtk/gtk_theme_provider.h"
+#endif
+
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationsMask;
+
+namespace {
+
+class ResourceRequestTask : public Task {
+ public:
+ ResourceRequestTask(int process_id,
+ int render_view_host_id,
+ ResourceRequestAction action)
+ : action_(action),
+ process_id_(process_id),
+ render_view_host_id_(render_view_host_id),
+ resource_dispatcher_host_(
+ g_browser_process->resource_dispatcher_host()) {
+ }
+
+ virtual void Run() {
+ switch (action_) {
+ case BLOCK:
+ resource_dispatcher_host_->BlockRequestsForRoute(
+ process_id_, render_view_host_id_);
+ break;
+ case RESUME:
+ resource_dispatcher_host_->ResumeBlockedRequestsForRoute(
+ process_id_, render_view_host_id_);
+ break;
+ case CANCEL:
+ resource_dispatcher_host_->CancelBlockedRequestsForRoute(
+ process_id_, render_view_host_id_);
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ private:
+ ResourceRequestAction action_;
+ int process_id_;
+ int render_view_host_id_;
+ ResourceDispatcherHost* resource_dispatcher_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceRequestTask);
+};
+
+} // namespace
+
+class InterstitialPage::InterstitialPageRVHViewDelegate
+ : public RenderViewHostDelegate::View {
+ public:
+ explicit InterstitialPageRVHViewDelegate(InterstitialPage* 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 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 LostCapture();
+ virtual void Activate();
+ virtual void Deactivate();
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut);
+ virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event);
+ virtual void HandleMouseMove();
+ virtual void HandleMouseDown();
+ virtual void HandleMouseLeave();
+ virtual void HandleMouseUp();
+ virtual void HandleMouseActivate();
+ virtual void OnFindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+ virtual void UpdatePreferredSize(const gfx::Size& pref_size);
+
+ private:
+ InterstitialPage* interstitial_page_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPageRVHViewDelegate);
+};
+
+// static
+InterstitialPage::InterstitialPageMap*
+ InterstitialPage::tab_to_interstitial_page_ = NULL;
+
+InterstitialPage::InterstitialPage(TabContents* tab,
+ bool new_navigation,
+ const GURL& url)
+ : tab_(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->render_view_host()->process()->id()),
+ original_rvh_id_(tab->render_view_host()->routing_id()),
+ should_revert_tab_title_(false),
+ resource_dispatcher_host_notified_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(rvh_view_delegate_(
+ new InterstitialPageRVHViewDelegate(this))) {
+ renderer_preferences_util::UpdateFromSystemSettings(
+ &renderer_preferences_, tab_->profile());
+
+ 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->controller().pending_entry());
+}
+
+InterstitialPage::~InterstitialPage() {
+ InterstitialPageMap::iterator iter = tab_to_interstitial_page_->find(tab_);
+ DCHECK(iter != tab_to_interstitial_page_->end());
+ if (iter != tab_to_interstitial_page_->end())
+ tab_to_interstitial_page_->erase(iter);
+ DCHECK(!render_view_host_);
+}
+
+void InterstitialPage::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 =
+ tab_to_interstitial_page_->find(tab_);
+ if (iter != tab_to_interstitial_page_->end()) {
+ InterstitialPage* interstitial = iter->second;
+ if (interstitial->action_taken_ != NO_ACTION) {
+ interstitial->Hide();
+ } 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, NotificationType::RENDER_WIDGET_HOST_DESTROYED,
+ Source<RenderWidgetHost>(tab_->render_view_host()));
+
+ // Update the tab_to_interstitial_page_ map.
+ iter = tab_to_interstitial_page_->find(tab_);
+ DCHECK(iter == tab_to_interstitial_page_->end());
+ (*tab_to_interstitial_page_)[tab_] = this;
+
+ if (new_navigation_) {
+ NavigationEntry* entry = new NavigationEntry;
+ entry->set_url(url_);
+ entry->set_virtual_url(url_);
+ entry->set_page_type(INTERSTITIAL_PAGE);
+
+ // Give sub-classes a chance to set some states on the navigation entry.
+ UpdateEntry(entry);
+
+ tab_->controller().AddTransientEntry(entry);
+ }
+
+ DCHECK(!render_view_host_);
+ render_view_host_ = CreateRenderViewHost();
+ CreateTabContentsView();
+
+ std::string data_url = "data:text/html;charset=utf-8," +
+ EscapePath(GetHTMLContents());
+ render_view_host_->NavigateToURL(GURL(data_url));
+
+ notification_registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(tab_));
+ notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(&tab_->controller()));
+ notification_registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING,
+ Source<NavigationController>(&tab_->controller()));
+}
+
+void InterstitialPage::Hide() {
+ RenderWidgetHostView* old_view = tab_->render_view_host()->view();
+ if (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_->view() && render_view_host_->view()->HasFocus() &&
+ tab_->render_view_host()->view()) {
+ tab_->render_view_host()->view()->Focus();
+ }
+
+ render_view_host_->Shutdown();
+ render_view_host_ = NULL;
+ if (tab_->interstitial_page())
+ tab_->remove_interstitial_page();
+ // Let's revert to the original title if necessary.
+ NavigationEntry* entry = tab_->controller().GetActiveEntry();
+ if (!new_navigation_ && should_revert_tab_title_) {
+ entry->set_title(WideToUTF16Hack(original_tab_title_));
+ tab_->NotifyNavigationStateChanged(TabContents::INVALIDATE_TITLE);
+ }
+ delete this;
+}
+
+void InterstitialPage::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::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 NotificationType::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 = Source<RenderViewHost>(source).ptr();
+ DCHECK(rvh->process()->id() == original_child_id_ &&
+ rvh->routing_id() == original_rvh_id_);
+ TakeActionOnResourceDispatcher(CANCEL);
+ }
+ break;
+ case NotificationType::TAB_CONTENTS_DESTROYED:
+ case NotificationType::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();
+ // WARNING: we are now deleted!
+ }
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+RenderViewHostDelegate::View* InterstitialPage::GetViewDelegate() {
+ return rvh_view_delegate_.get();
+}
+
+const GURL& InterstitialPage::GetURL() const {
+ return url_;
+}
+
+void InterstitialPage::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 InterstitialPage::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;
+ }
+
+ // The RenderViewHost has loaded its contents, we can show it now.
+ render_view_host_->view()->Show();
+ tab_->set_interstitial_page(this);
+
+ RenderWidgetHostView* rwh_view = tab_->render_view_host()->view();
+
+ // 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_->SetIsLoading(false, NULL);
+}
+
+void InterstitialPage::UpdateTitle(RenderViewHost* render_view_host,
+ int32 page_id,
+ const std::wstring& title) {
+ DCHECK(render_view_host == render_view_host_);
+ NavigationEntry* entry = tab_->controller().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_ = UTF16ToWideHack(entry->title());
+ should_revert_tab_title_ = true;
+ }
+ entry->set_title(WideToUTF16Hack(title));
+ tab_->NotifyNavigationStateChanged(TabContents::INVALIDATE_TITLE);
+}
+
+void InterstitialPage::DomOperationResponse(const std::string& json_string,
+ int automation_id) {
+ if (enabled_)
+ CommandReceived(json_string);
+}
+
+RendererPreferences InterstitialPage::GetRendererPrefs(Profile* profile) const {
+ return renderer_preferences_;
+}
+
+RenderViewHost* InterstitialPage::CreateRenderViewHost() {
+ RenderViewHost* render_view_host = new RenderViewHost(
+ SiteInstance::CreateSiteInstance(tab()->profile()),
+ this, MSG_ROUTING_NONE, kInvalidSessionStorageNamespaceId);
+ return render_view_host;
+}
+
+TabContentsView* InterstitialPage::CreateTabContentsView() {
+ TabContentsView* tab_contents_view = tab()->view();
+ RenderWidgetHostView* view =
+ tab_contents_view->CreateViewForWidget(render_view_host_);
+ render_view_host_->set_view(view);
+ render_view_host_->AllowBindings(BindingsPolicy::DOM_AUTOMATION);
+
+ render_view_host_->CreateRenderView(string16());
+ view->SetSize(tab_contents_view->GetContainerSize());
+ // Don't show the interstitial until we have navigated to it.
+ view->Hide();
+ return tab_contents_view;
+}
+
+void InterstitialPage::Proceed() {
+ if (action_taken_ != NO_ACTION) {
+ NOTREACHED();
+ return;
+ }
+ Disable();
+ action_taken_ = PROCEED_ACTION;
+
+ // Resumes the throbber.
+ 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();
+ // WARNING: we are now deleted!
+ }
+}
+
+std::string InterstitialPage::GetHTMLContents() {
+ return std::string();
+}
+
+void InterstitialPage::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_->controller().DiscardNonCommittedEntries();
+ }
+
+ if (reload_on_dont_proceed_)
+ tab_->controller().Reload(true);
+
+ Hide();
+ // WARNING: we are now deleted!
+}
+
+void InterstitialPage::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 InterstitialPage::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_->view())
+ render_view_host_->view()->SetSize(size);
+#else
+ // TODO(port): Does Mac need to SetSize?
+ NOTIMPLEMENTED();
+#endif
+}
+
+void InterstitialPage::Focus() {
+ // Focus the native window.
+ render_view_host_->view()->Focus();
+}
+
+void InterstitialPage::FocusThroughTabTraversal(bool reverse) {
+ render_view_host_->SetInitialFocus(reverse);
+}
+
+ViewType::Type InterstitialPage::GetRenderViewType() const {
+ return ViewType::INTERSTITIAL_PAGE;
+}
+
+void InterstitialPage::Disable() {
+ enabled_ = false;
+}
+
+void InterstitialPage::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 ResourceDispatcherHost, as when unit-tests
+ // we don't have one.
+ RenderViewHost* rvh = RenderViewHost::FromID(original_child_id_,
+ original_rvh_id_);
+ if (!rvh || !g_browser_process->resource_dispatcher_host())
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ new ResourceRequestTask(original_child_id_, original_rvh_id_, action));
+}
+
+// static
+void InterstitialPage::InitInterstitialPageMap() {
+ if (!tab_to_interstitial_page_)
+ tab_to_interstitial_page_ = new InterstitialPageMap;
+}
+
+// static
+InterstitialPage* InterstitialPage::GetInterstitialPage(
+ TabContents* tab_contents) {
+ InitInterstitialPageMap();
+ InterstitialPageMap::const_iterator iter =
+ tab_to_interstitial_page_->find(tab_contents);
+ if (iter == tab_to_interstitial_page_->end())
+ return NULL;
+
+ return iter->second;
+}
+
+InterstitialPage::InterstitialPageRVHViewDelegate::
+ InterstitialPageRVHViewDelegate(InterstitialPage* page)
+ : interstitial_page_(page) {
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::CreateNewWindow(
+ int route_id,
+ const ViewHostMsg_CreateWindow_Params& params) {
+ NOTREACHED() << "InterstitialPage does not support showing popups yet.";
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::CreateNewWidget(
+ int route_id, WebKit::WebPopupType popup_type) {
+ NOTREACHED() << "InterstitialPage does not support showing drop-downs yet.";
+}
+
+void
+InterstitialPage::InterstitialPageRVHViewDelegate::CreateNewFullscreenWidget(
+ int route_id) {
+ NOTREACHED()
+ << "InterstitialPage does not support showing full screen popups.";
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::ShowCreatedWindow(
+ int route_id, WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos, bool user_gesture) {
+ NOTREACHED() << "InterstitialPage does not support showing popups yet.";
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::ShowCreatedWidget(
+ int route_id, const gfx::Rect& initial_pos) {
+ NOTREACHED() << "InterstitialPage does not support showing drop-downs yet.";
+}
+
+void
+InterstitialPage::InterstitialPageRVHViewDelegate::ShowCreatedFullscreenWidget(
+ int route_id) {
+ NOTREACHED()
+ << "InterstitialPage does not support showing full screen popups.";
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::ShowContextMenu(
+ const ContextMenuParams& params) {
+}
+
+void InterstitialPage::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 InterstitialPage::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 InterstitialPage::InterstitialPageRVHViewDelegate::UpdateDragCursor(
+ WebDragOperation) {
+ NOTREACHED() << "InterstitialPage does not support dragging yet.";
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::GotFocus() {
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::UpdatePreferredSize(
+ const gfx::Size& pref_size) {
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::TakeFocus(
+ bool reverse) {
+ if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate())
+ interstitial_page_->tab()->GetViewDelegate()->TakeFocus(reverse);
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::LostCapture() {
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::Activate() {
+ if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate())
+ interstitial_page_->tab()->GetViewDelegate()->Activate();
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::Deactivate() {
+ if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate())
+ interstitial_page_->tab()->GetViewDelegate()->Deactivate();
+}
+
+bool InterstitialPage::InterstitialPageRVHViewDelegate::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) {
+ if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate())
+ return interstitial_page_->tab()->GetViewDelegate()->PreHandleKeyboardEvent(
+ event, is_keyboard_shortcut);
+ return false;
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate())
+ interstitial_page_->tab()->GetViewDelegate()->HandleKeyboardEvent(event);
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseMove() {
+ if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate())
+ interstitial_page_->tab()->GetViewDelegate()->HandleMouseMove();
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseDown() {
+ if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate())
+ interstitial_page_->tab()->GetViewDelegate()->HandleMouseDown();
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseLeave() {
+ if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate())
+ interstitial_page_->tab()->GetViewDelegate()->HandleMouseLeave();
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseUp() {
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseActivate() {
+}
+
+void InterstitialPage::InterstitialPageRVHViewDelegate::OnFindReply(
+ int request_id, int number_of_matches, const gfx::Rect& selection_rect,
+ int active_match_ordinal, bool final_update) {
+}
+
+int InterstitialPage::GetBrowserWindowID() const {
+ return tab_->GetBrowserWindowID();
+}
+
+void InterstitialPage::UpdateInspectorSetting(const std::string& key,
+ const std::string& value) {
+ RenderViewHostDelegateHelper::UpdateInspectorSetting(
+ tab_->profile(), key, value);
+}
+
+void InterstitialPage::ClearInspectorSettings() {
+ RenderViewHostDelegateHelper::ClearInspectorSettings(tab_->profile());
+}
diff --git a/content/browser/tab_contents/interstitial_page.h b/content/browser/tab_contents/interstitial_page.h
new file mode 100644
index 0000000..b3f9e1c
--- /dev/null
+++ b/content/browser/tab_contents/interstitial_page.h
@@ -0,0 +1,245 @@
+// Copyright (c) 2009 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_TAB_CONTENTS_INTERSTITIAL_PAGE_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_INTERSTITIAL_PAGE_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/process_util.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/renderer_preferences.h"
+#include "googleurl/src/gurl.h"
+#include "ui/gfx/size.h"
+
+class NavigationEntry;
+class TabContents;
+class TabContentsView;
+
+// This class is a base class for interstitial pages, pages that show some
+// informative message asking for user validation before reaching the target
+// page. (Navigating to a page served over bad HTTPS or a page containing
+// malware are typical cases where an interstitial is required.)
+//
+// If specified in its constructor, this class creates a navigation entry so
+// that when the interstitial shows, the current entry is the target URL.
+//
+// InterstitialPage instances take care of deleting themselves when closed
+// through a navigation, the TabContents closing them or the tab containing them
+// being closed.
+
+enum ResourceRequestAction {
+ BLOCK,
+ RESUME,
+ CANCEL
+};
+
+class InterstitialPage : public NotificationObserver,
+ public 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.
+ };
+
+ // Creates an interstitial page to show in |tab|. |new_navigation| should be
+ // set to true when the interstitial is caused by loading a new page, in which
+ // case a temporary navigation entry is created with the URL |url| and
+ // added to the navigation controller (so the interstitial page appears as a
+ // new navigation entry). |new_navigation| should be false when the
+ // interstitial was triggered by a loading a sub-resource in a page.
+ InterstitialPage(TabContents* tab, bool new_navigation, const GURL& url);
+ virtual ~InterstitialPage();
+
+ // Shows the interstitial page in the tab.
+ virtual void Show();
+
+ // Hides the interstitial page. Warning: this deletes the InterstitialPage.
+ void Hide();
+
+ // Retrieves the InterstitialPage if any associated with the specified
+ // |tab_contents| (used by ui tests).
+ static InterstitialPage* GetInterstitialPage(TabContents* tab_contents);
+
+ // Sub-classes should return the HTML that should be displayed in the page.
+ virtual std::string GetHTMLContents();
+
+ // Reverts to the page showing before the interstitial.
+ // Sub-classes should call this method when the user has chosen NOT to proceed
+ // to the target URL.
+ // Warning: if |new_navigation| was set to true in the constructor, 'this'
+ // will be deleted when this method returns.
+ virtual void DontProceed();
+
+ // Sub-classes should call this method when the user has chosen to proceed to
+ // the target URL.
+ // Warning: 'this' has been deleted when this method returns.
+ virtual void Proceed();
+
+ // 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();
+
+ // Sizes the RenderViewHost showing the actual interstitial page contents.
+ void SetSize(const gfx::Size& size);
+
+ ActionState action_taken() const { return action_taken_; }
+
+ // Sets the focus to the interstitial.
+ void Focus();
+
+ // Focus the first (last if reverse is true) element in the interstitial page.
+ // Called when tab traversing.
+ void FocusThroughTabTraversal(bool reverse);
+
+ virtual ViewType::Type GetRenderViewType() const;
+ virtual int GetBrowserWindowID() const;
+
+ // 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_; }
+
+ virtual void UpdateInspectorSetting(const std::string& key,
+ const std::string& value);
+ virtual void ClearInspectorSettings();
+
+ protected:
+ // NotificationObserver method:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // RenderViewHostDelegate implementation:
+ virtual View* GetViewDelegate();
+ virtual const GURL& GetURL() const;
+ virtual void RenderViewGone(RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code);
+ virtual void DidNavigate(RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params);
+ virtual void UpdateTitle(RenderViewHost* render_view_host,
+ int32 page_id,
+ const std::wstring& title);
+ virtual void DomOperationResponse(const std::string& json_string,
+ int automation_id);
+ virtual RendererPreferences GetRendererPrefs(Profile* profile) const;
+
+ // Invoked when the page sent a command through DOMAutomation.
+ virtual void CommandReceived(const std::string& command) {}
+
+ // Invoked with the NavigationEntry that is going to be added to the
+ // navigation controller.
+ // Gives an opportunity to sub-classes to set states on the |entry|.
+ // Note that this is only called if the InterstitialPage was constructed with
+ // |create_navigation_entry| set to true.
+ virtual void UpdateEntry(NavigationEntry* entry) {}
+
+ TabContents* tab() const { return tab_; }
+ const GURL& url() const { return url_; }
+ RenderViewHost* render_view_host() const { return render_view_host_; }
+
+ // Creates the RenderViewHost containing the interstitial content.
+ // Overriden in unit tests.
+ virtual RenderViewHost* CreateRenderViewHost();
+
+ // Creates the TabContentsView that shows the interstitial RVH.
+ // Overriden in unit tests.
+ virtual TabContentsView* CreateTabContentsView();
+
+ private:
+ // AutomationProvider needs access to Proceed and DontProceed to simulate
+ // user actions.
+ friend class AutomationProvider;
+
+ class InterstitialPageRVHViewDelegate;
+
+ // Initializes tab_to_interstitial_page_ in a thread-safe manner.
+ // Should be called before accessing tab_to_interstitial_page_.
+ static void InitInterstitialPageMap();
+
+ // 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 tab in which we are displayed.
+ TabContents* 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_;
+
+ // Notification magic.
+ NotificationRegistrar notification_registrar_;
+
+ // The RenderViewHost displaying the interstitial contents.
+ RenderViewHost* 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 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.
+ std::wstring original_tab_title_;
+
+ // Our RenderViewHostViewDelegate, necessary for accelerators to work.
+ scoped_ptr<InterstitialPageRVHViewDelegate> rvh_view_delegate_;
+
+ // We keep a map of the various blocking pages shown as the UI tests need to
+ // be able to retrieve them.
+ typedef std::map<TabContents*, InterstitialPage*> InterstitialPageMap;
+ static InterstitialPageMap* tab_to_interstitial_page_;
+
+ // Settings passed to the renderer.
+ RendererPreferences renderer_preferences_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPage);
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_INTERSTITIAL_PAGE_H_
diff --git a/content/browser/tab_contents/language_state.cc b/content/browser/tab_contents/language_state.cc
new file mode 100644
index 0000000..86712d1
--- /dev/null
+++ b/content/browser/tab_contents/language_state.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2010 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/tab_contents/language_state.h"
+
+#include "content/browser/tab_contents/navigation_controller.h"
+#include "content/browser/tab_contents/navigation_entry.h"
+
+LanguageState::LanguageState(NavigationController* nav_controller)
+ : navigation_controller_(nav_controller),
+ page_translatable_(false),
+ translation_pending_(false),
+ translation_declined_(false),
+ in_page_navigation_(false) {
+}
+
+LanguageState::~LanguageState() {
+}
+
+void LanguageState::DidNavigate(
+ const NavigationController::LoadCommittedDetails& details) {
+ in_page_navigation_ = details.is_in_page;
+ if (in_page_navigation_ || !details.is_main_frame)
+ return; // Don't reset our states, the page has not changed.
+
+ bool reload = details.entry->transition_type() == PageTransition::RELOAD ||
+ details.type == NavigationType::SAME_PAGE;
+ if (reload) {
+ // We might not get a LanguageDetermined notifications on reloads. Make sure
+ // to keep the original language and to set current_lang_ so
+ // IsPageTranslated() returns false.
+ current_lang_ = original_lang_;
+ } else {
+ prev_original_lang_ = original_lang_;
+ prev_current_lang_ = current_lang_;
+ original_lang_.clear();
+ current_lang_.clear();
+ }
+
+ translation_pending_ = false;
+ translation_declined_ = false;
+}
+
+void LanguageState::LanguageDetermined(const std::string& page_language,
+ bool page_translatable) {
+ if (in_page_navigation_ && !original_lang_.empty()) {
+ // In-page navigation, we don't expect our states to change.
+ // Note that we'll set the languages if original_lang_ is empty. This might
+ // happen if the we did not get called on the top-page.
+ return;
+ }
+ page_translatable_ = page_translatable;
+ original_lang_ = page_language;
+ current_lang_ = page_language;
+}
+
+std::string LanguageState::AutoTranslateTo() const {
+ // Only auto-translate if:
+ // - no translation is pending
+ // - this page is in the same language as the previous page
+ // - the previous page had been translated
+ // - this page is not already translated
+ // - the new page was navigated through a link.
+ if (!translation_pending_ &&
+ prev_original_lang_ == original_lang_ &&
+ prev_original_lang_ != prev_current_lang_ &&
+ original_lang_ == current_lang_ &&
+ navigation_controller_->GetActiveEntry() &&
+ navigation_controller_->GetActiveEntry()->transition_type() ==
+ PageTransition::LINK) {
+ return prev_current_lang_;
+ }
+
+ return std::string();
+}
diff --git a/content/browser/tab_contents/language_state.h b/content/browser/tab_contents/language_state.h
new file mode 100644
index 0000000..d0c6204
--- /dev/null
+++ b/content/browser/tab_contents/language_state.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2010 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_TAB_CONTENTS_LANGUAGE_STATE_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_LANGUAGE_STATE_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "content/browser/tab_contents/navigation_controller.h"
+
+// This class holds the language state of the current page.
+// There is one LanguageState instance per TabContents.
+// It is used to determine when navigating to a new page whether it should
+// automatically be translated.
+// This auto-translate behavior is the expected behavior when:
+// - user is on page in language A that they had translated to language B.
+// - user clicks a link in that page that takes them to a page also in language
+// A.
+
+class LanguageState {
+ public:
+ explicit LanguageState(NavigationController* nav_controller);
+ ~LanguageState();
+
+ // Should be called when the page did a new navigation (whether it is a main
+ // frame or sub-frame navigation).
+ void DidNavigate(const NavigationController::LoadCommittedDetails& details);
+
+ // Should be called when the language of the page has been determined.
+ // |page_translatable| when false indicates that the browser should not offer
+ // to translate the page.
+ void LanguageDetermined(const std::string& page_language,
+ bool page_translatable);
+
+ // Returns the language the current page should be translated to, based on the
+ // previous page languages and the transition. This should be called after
+ // the language page has been determined.
+ // Returns an empty string if the page should not be auto-translated.
+ std::string AutoTranslateTo() const;
+
+ // Returns true if the current page in the associated tab has been translated.
+ bool IsPageTranslated() const { return original_lang_ != current_lang_; }
+
+ const std::string& original_language() const { return original_lang_; }
+
+ void set_current_language(const std::string& language) {
+ current_lang_ = language;
+ }
+ const std::string& current_language() const { return current_lang_; }
+
+ bool page_translatable() const { return page_translatable_; }
+
+ // Whether the page is currently in the process of being translated.
+ bool translation_pending() const { return translation_pending_; }
+ void set_translation_pending(bool value) { translation_pending_ = value; }
+
+ // Whether the user has already declined to translate the page.
+ bool translation_declined() const { return translation_declined_; }
+ void set_translation_declined(bool value) { translation_declined_ = value; }
+
+ // Whether the current page was navigated through an in-page (fragment)
+ // navigation.
+ bool in_page_navigation() const { return in_page_navigation_; }
+
+ private:
+ // The languages this page is in. Note that current_lang_ is different from
+ // original_lang_ when the page has been translated.
+ // Note that these might be empty if the page language has not been determined
+ // yet.
+ std::string original_lang_;
+ std::string current_lang_;
+
+ // Same as above but for the previous page.
+ std::string prev_original_lang_;
+ std::string prev_current_lang_;
+
+ // The navigation controller of the tab we are associated with.
+ NavigationController* navigation_controller_;
+
+ // Whether it is OK to offer to translate the page. Some pages explictly
+ // specify that they should not be translated by the browser (this is the case
+ // for GMail for example, which provides its own translation features).
+ bool page_translatable_;
+
+ // Whether a translation is currently pending (TabContents waiting for the
+ // PAGE_TRANSLATED notification). This is needed to avoid sending duplicate
+ // translate requests to a page. TranslateManager initiates translations
+ // when it received the LANGUAGE_DETERMINED notification. This is sent by
+ // the renderer with the page contents, every time the load stops for the
+ // main frame, so we may get several.
+ // TODO(jcampan): make the renderer send the language just once per navigation
+ // then we can get rid of that state.
+ bool translation_pending_;
+
+ // Whether the user has declined to translate the page (by closing the infobar
+ // for example). This is necessary as a new infobar could be shown if a new
+ // load happens in the page after the user closed the infobar.
+ bool translation_declined_;
+
+ // Whether the current navigation is a fragment navigation (in page).
+ bool in_page_navigation_;
+
+ DISALLOW_COPY_AND_ASSIGN(LanguageState);
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_LANGUAGE_STATE_H_
diff --git a/content/browser/tab_contents/navigation_controller.cc b/content/browser/tab_contents/navigation_controller.cc
new file mode 100644
index 0000000..2ac9aba
--- /dev/null
+++ b/content/browser/tab_contents/navigation_controller.cc
@@ -0,0 +1,1203 @@
+// Copyright (c) 2010 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/tab_contents/navigation_controller.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/browser_about_handler.h"
+#include "chrome/browser/browser_url_handler.h"
+#include "chrome/browser/in_process_webkit/session_storage_namespace.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/sessions/session_types.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/navigation_types.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/render_messages_params.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/tab_contents/interstitial_page.h"
+#include "content/browser/tab_contents/navigation_entry.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/tab_contents/tab_contents_delegate.h"
+#include "grit/app_resources.h"
+#include "net/base/escape.h"
+#include "net/base/net_util.h"
+#include "net/base/mime_util.h"
+#include "webkit/glue/webkit_glue.h"
+
+namespace {
+
+const int kInvalidateAllButShelves =
+ 0xFFFFFFFF & ~TabContents::INVALIDATE_BOOKMARK_BAR;
+
+// 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(NavigationController* nav_controller,
+ bool from_front,
+ int count) {
+ NavigationController::PrunedDetails details;
+ details.from_front = from_front;
+ details.count = count;
+ NotificationService::current()->Notify(
+ NotificationType::NAV_LIST_PRUNED,
+ Source<NavigationController>(nav_controller),
+ Details<NavigationController::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(NavigationEntry* entry) {
+ if (entry->content_state().empty()) {
+ entry->set_content_state(
+ webkit_glue::CreateHistoryStateForURL(entry->url()));
+ }
+}
+
+// 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<NavigationEntry> >* 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]->set_transition_type(PageTransition::RELOAD);
+ (*entries)[i]->set_restore_type(from_last_session ?
+ NavigationEntry::RESTORE_LAST_SESSION :
+ NavigationEntry::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
+
+// NavigationController ---------------------------------------------------
+
+// static
+size_t NavigationController::max_entry_count_ =
+ chrome::kMaxSessionHistoryEntries;
+
+// static
+bool NavigationController::check_for_repost_ = true;
+
+NavigationController::NavigationController(
+ TabContents* contents,
+ Profile* profile,
+ SessionStorageNamespace* session_storage_namespace)
+ : profile_(profile),
+ 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(profile_);
+ if (!session_storage_namespace_)
+ session_storage_namespace_ = new SessionStorageNamespace(profile_);
+}
+
+NavigationController::~NavigationController() {
+ DiscardNonCommittedEntriesInternal();
+
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CLOSED,
+ Source<NavigationController>(this),
+ NotificationService::NoDetails());
+}
+
+void NavigationController::RestoreFromState(
+ const std::vector<TabNavigation>& navigations,
+ int selected_navigation,
+ bool from_last_session) {
+ // Verify that this controller is unused and that the input is valid.
+ DCHECK(entry_count() == 0 && !pending_entry());
+ DCHECK(selected_navigation >= 0 &&
+ selected_navigation < static_cast<int>(navigations.size()));
+
+ // Populate entries_ from the supplied TabNavigations.
+ needs_reload_ = true;
+ CreateNavigationEntriesFromTabNavigations(navigations, &entries_);
+
+ // And finish the restore.
+ FinishRestore(selected_navigation, from_last_session);
+}
+
+void NavigationController::Reload(bool check_for_repost) {
+ ReloadInternal(check_for_repost, RELOAD);
+}
+void NavigationController::ReloadIgnoringCache(bool check_for_repost) {
+ ReloadInternal(check_for_repost, RELOAD_IGNORING_CACHE);
+}
+
+void NavigationController::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 (check_for_repost_ && check_for_repost &&
+ GetEntryAtIndex(current_index)->has_post_data()) {
+ // 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.
+ NotificationService::current()->Notify(
+ NotificationType::REPOST_WARNING_SHOWN,
+ Source<NavigationController>(this),
+ NotificationService::NoDetails());
+
+ pending_reload_ = reload_type;
+ tab_contents_->Activate();
+ tab_contents_->delegate()->ShowRepostFormWarningDialog(tab_contents_);
+ } else {
+ DiscardNonCommittedEntriesInternal();
+
+ pending_entry_index_ = current_index;
+ entries_[pending_entry_index_]->set_transition_type(PageTransition::RELOAD);
+ NavigateToPendingEntry(reload_type);
+ }
+}
+
+void NavigationController::CancelPendingReload() {
+ DCHECK(pending_reload_ != NO_RELOAD);
+ pending_reload_ = NO_RELOAD;
+}
+
+void NavigationController::ContinuePendingReload() {
+ if (pending_reload_ == NO_RELOAD) {
+ NOTREACHED();
+ } else {
+ ReloadInternal(false, pending_reload_);
+ pending_reload_ = NO_RELOAD;
+ }
+}
+
+bool NavigationController::IsInitialNavigation() {
+ return last_document_loaded_.is_null();
+}
+
+// static
+NavigationEntry* NavigationController::CreateNavigationEntry(
+ const GURL& url, const GURL& referrer, PageTransition::Type transition,
+ Profile* profile) {
+ // 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;
+ BrowserURLHandler::RewriteURLIfNecessary(
+ &loaded_url, profile, &reverse_on_redirect);
+
+ NavigationEntry* entry = new NavigationEntry(
+ NULL, // The site instance for tabs is sent on navigation
+ // (TabContents::GetSiteInstance).
+ -1,
+ loaded_url,
+ referrer,
+ string16(),
+ transition);
+ entry->set_virtual_url(url);
+ entry->set_user_typed_url(url);
+ entry->set_update_virtual_url_with_url(reverse_on_redirect);
+ if (url.SchemeIsFile()) {
+ // Use the filename as the title, not the full path.
+ // We need to call FormatUrl() to perform URL de-escaping;
+ // it's a bit ugly to grab the filename out of the resulting string.
+ std::string languages =
+ profile->GetPrefs()->GetString(prefs::kAcceptLanguages);
+ std::wstring formatted = UTF16ToWideHack(net::FormatUrl(url, languages));
+ std::wstring filename =
+ FilePath::FromWStringHack(formatted).BaseName().ToWStringHack();
+ entry->set_title(WideToUTF16Hack(filename));
+ }
+ return entry;
+}
+
+NavigationEntry* NavigationController::GetEntryWithPageID(
+ SiteInstance* instance, int32 page_id) const {
+ int index = GetEntryIndexWithPageID(instance, page_id);
+ return (index != -1) ? entries_[index].get() : NULL;
+}
+
+void NavigationController::LoadEntry(NavigationEntry* entry) {
+ // Handle non-navigational URLs that popup dialogs and such, these should not
+ // actually navigate.
+ if (HandleNonNavigationAboutURL(entry->url()))
+ 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;
+ NotificationService::current()->Notify(
+ NotificationType::NAV_ENTRY_PENDING,
+ Source<NavigationController>(this),
+ NotificationService::NoDetails());
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+NavigationEntry* NavigationController::GetActiveEntry() const {
+ if (transient_entry_index_ != -1)
+ return entries_[transient_entry_index_].get();
+ if (pending_entry_)
+ return pending_entry_;
+ return GetLastCommittedEntry();
+}
+
+int NavigationController::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* NavigationController::GetLastCommittedEntry() const {
+ if (last_committed_entry_index_ == -1)
+ return NULL;
+ return entries_[last_committed_entry_index_].get();
+}
+
+bool NavigationController::CanViewSource() const {
+ bool is_supported_mime_type = net::IsSupportedNonImageMimeType(
+ tab_contents_->contents_mime_type().c_str());
+ NavigationEntry* active_entry = GetActiveEntry();
+ return active_entry && !active_entry->IsViewSourceMode() &&
+ is_supported_mime_type;
+}
+
+NavigationEntry* NavigationController::GetEntryAtOffset(int offset) const {
+ int index = (transient_entry_index_ != -1) ?
+ transient_entry_index_ + offset :
+ last_committed_entry_index_ + offset;
+ if (index < 0 || index >= entry_count())
+ return NULL;
+
+ return entries_[index].get();
+}
+
+bool NavigationController::CanGoBack() const {
+ return entries_.size() > 1 && GetCurrentEntryIndex() > 0;
+}
+
+bool NavigationController::CanGoForward() const {
+ int index = GetCurrentEntryIndex();
+ return index >= 0 && index < (static_cast<int>(entries_.size()) - 1);
+}
+
+void NavigationController::GoBack() {
+ if (!CanGoBack()) {
+ NOTREACHED();
+ return;
+ }
+
+ // If an interstitial page is showing, going back is equivalent to hiding the
+ // interstitial.
+ if (tab_contents_->interstitial_page()) {
+ tab_contents_->interstitial_page()->DontProceed();
+ return;
+ }
+
+ // Base the navigation on where we are now...
+ int current_index = GetCurrentEntryIndex();
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = current_index - 1;
+ entries_[pending_entry_index_]->set_transition_type(
+ entries_[pending_entry_index_]->transition_type() |
+ PageTransition::FORWARD_BACK);
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationController::GoForward() {
+ if (!CanGoForward()) {
+ NOTREACHED();
+ 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_->interstitial_page()) {
+ tab_contents_->interstitial_page()->CancelForNavigation();
+ }
+
+ 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_]->set_transition_type(
+ entries_[pending_entry_index_]->transition_type() |
+ PageTransition::FORWARD_BACK);
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationController::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--;
+ }
+ }
+
+ // If an interstitial page is showing, the previous renderer is blocked and
+ // cannot make new requests.
+ if (tab_contents_->interstitial_page()) {
+ if (index == GetCurrentEntryIndex() - 1) {
+ // Going back one entry is equivalent to hiding the interstitial.
+ tab_contents_->interstitial_page()->DontProceed();
+ return;
+ } else {
+ // Unblock the renderer (and disable the interstitial) to allow this
+ // navigation to succeed. The interstitial will stay visible until the
+ // resulting DidNavigate.
+ tab_contents_->interstitial_page()->CancelForNavigation();
+ }
+ }
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = index;
+ entries_[pending_entry_index_]->set_transition_type(
+ entries_[pending_entry_index_]->transition_type() |
+ PageTransition::FORWARD_BACK);
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationController::GoToOffset(int offset) {
+ int index = (transient_entry_index_ != -1) ?
+ transient_entry_index_ + offset :
+ last_committed_entry_index_ + offset;
+ if (index < 0 || index >= entry_count())
+ return;
+
+ GoToIndex(index);
+}
+
+void NavigationController::RemoveEntryAtIndex(int index,
+ const GURL& default_url) {
+ int size = static_cast<int>(entries_.size());
+ DCHECK(index < size);
+
+ DiscardNonCommittedEntries();
+
+ entries_.erase(entries_.begin() + index);
+
+ if (last_committed_entry_index_ == index) {
+ last_committed_entry_index_--;
+ // We removed the currently shown entry, so we have to load something else.
+ if (last_committed_entry_index_ != -1) {
+ pending_entry_index_ = last_committed_entry_index_;
+ NavigateToPendingEntry(NO_RELOAD);
+ } else {
+ // If there is nothing to show, show a default page.
+ LoadURL(default_url.is_empty() ? GURL("about:blank") : default_url,
+ GURL(), PageTransition::START_PAGE);
+ }
+ } else if (last_committed_entry_index_ > index) {
+ last_committed_entry_index_--;
+ }
+}
+
+void NavigationController::UpdateVirtualURLToURL(
+ NavigationEntry* entry, const GURL& new_url) {
+ GURL new_virtual_url(new_url);
+ if (BrowserURLHandler::ReverseURLRewrite(
+ &new_virtual_url, entry->virtual_url(), profile_)) {
+ entry->set_virtual_url(new_virtual_url);
+ }
+}
+
+void NavigationController::AddTransientEntry(NavigationEntry* 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<NavigationEntry>(entry));
+ transient_entry_index_ = index;
+ tab_contents_->NotifyNavigationStateChanged(kInvalidateAllButShelves);
+}
+
+void NavigationController::LoadURL(const GURL& url, const GURL& referrer,
+ PageTransition::Type transition) {
+ // The user initiated a load, we don't need to reload anymore.
+ needs_reload_ = false;
+
+ NavigationEntry* entry = CreateNavigationEntry(url, referrer, transition,
+ profile_);
+
+ LoadEntry(entry);
+}
+
+void NavigationController::DocumentLoadedInFrame() {
+ last_document_loaded_ = base::TimeTicks::Now();
+}
+
+bool NavigationController::RendererDidNavigate(
+ const ViewHostMsg_FrameNavigate_Params& params,
+ int extra_invalidate_flags,
+ LoadCommittedDetails* details) {
+
+ // Save the previous state before we clobber it.
+ if (GetLastCommittedEntry()) {
+ details->previous_url = GetLastCommittedEntry()->url();
+ details->previous_entry_index = last_committed_entry_index();
+ } else {
+ details->previous_url = GURL();
+ details->previous_entry_index = -1;
+ }
+
+ // Assign the current site instance to any pending entry, so we can find it
+ // later by calling GetEntryIndexWithPageID. We only care about this if the
+ // pending entry is an existing navigation and not a new one (or else we
+ // wouldn't care about finding it with GetEntryIndexWithPageID).
+ //
+ // TODO(brettw) this seems slightly bogus as we don't really know if the
+ // pending entry is what this navigation is for. There is a similar TODO
+ // w.r.t. the pending entry in RendererDidNavigateToNewPage.
+ if (pending_entry_index_ >= 0) {
+ pending_entry_->set_site_instance(tab_contents_->GetSiteInstance());
+ pending_entry_->set_restore_type(NavigationEntry::RESTORE_NONE);
+ }
+
+ // 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 NavigationType::NEW_PAGE:
+ RendererDidNavigateToNewPage(params, &(details->did_replace_entry));
+ break;
+ case NavigationType::EXISTING_PAGE:
+ RendererDidNavigateToExistingPage(params);
+ break;
+ case NavigationType::SAME_PAGE:
+ RendererDidNavigateToSamePage(params);
+ break;
+ case NavigationType::IN_PAGE:
+ RendererDidNavigateInPage(params, &(details->did_replace_entry));
+ break;
+ case NavigationType::NEW_SUBFRAME:
+ RendererDidNavigateNewSubframe(params);
+ break;
+ case NavigationType::AUTO_SUBFRAME:
+ if (!RendererDidNavigateAutoSubframe(params))
+ return false;
+ break;
+ case NavigationType::NAV_IGNORE:
+ // There is nothing we can do with this navigation, so we just return to
+ // the caller that nothing has happened.
+ 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).
+ SetContentStateIfEmpty(GetActiveEntry());
+
+ // WebKit doesn't set the "auto" transition on meta refreshes properly (bug
+ // 1051891) so we manually set it for redirects which we normally treat as
+ // "non-user-gestures" where we want to update stuff after navigations.
+ //
+ // Note that the redirect check also checks for a pending entry to
+ // differentiate real redirects from browser initiated navigations to a
+ // redirected entry. This happens when you hit back to go to a page that was
+ // the destination of a redirect, we don't want to treat it as a redirect
+ // even though that's what its transition will be. See bug 1117048.
+ //
+ // TODO(brettw) write a test for this complicated logic.
+ details->is_auto = (PageTransition::IsRedirect(params.transition) &&
+ !pending_entry()) ||
+ params.gesture == NavigationGestureAuto;
+
+ // Now prep the rest of the details for the notification and broadcast.
+ details->entry = GetActiveEntry();
+ details->is_main_frame = PageTransition::IsMainFrame(params.transition);
+ details->serialized_security_info = params.security_info;
+ details->is_content_filtered = params.is_content_filtered;
+ details->http_status_code = params.http_status_code;
+ NotifyNavigationEntryCommitted(details, extra_invalidate_flags);
+
+ return true;
+}
+
+NavigationType::Type NavigationController::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 NavigationType::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 (PageTransition::IsMainFrame(params.transition))
+ return NavigationType::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 NavigationType::NAV_IGNORE;
+
+ // Valid subframe navigation.
+ return NavigationType::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();
+ return NavigationType::NAV_IGNORE;
+ }
+ NavigationEntry* existing_entry = entries_[existing_entry_index].get();
+
+ if (!PageTransition::IsMainFrame(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 NavigationType::AUTO_SUBFRAME;
+ }
+
+ // Anything below here we know is a main frame navigation.
+ if (pending_entry_ &&
+ existing_entry != pending_entry_ &&
+ pending_entry_->page_id() == -1) {
+ // 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). In this case, we want to just ignore the pending entry and go
+ // back to where we were (the "existing entry").
+ return NavigationType::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->url(), params.url))
+ return NavigationType::IN_PAGE;
+
+ // Since we weeded out "new" navigations above, we know this is an existing
+ // (back/forward) navigation.
+ return NavigationType::EXISTING_PAGE;
+}
+
+bool NavigationController::IsRedirect(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // For main frame transition, we judge by params.transition.
+ // Otherwise, by params.redirects.
+ if (PageTransition::IsMainFrame(params.transition)) {
+ return PageTransition::IsRedirect(params.transition);
+ }
+ return params.redirects.size() > 1;
+}
+
+void NavigationController::CreateNavigationEntriesFromTabNavigations(
+ const std::vector<TabNavigation>& navigations,
+ std::vector<linked_ptr<NavigationEntry> >* entries) {
+ // Create a NavigationEntry for each of the navigations.
+ int page_id = 0;
+ for (std::vector<TabNavigation>::const_iterator i =
+ navigations.begin(); i != navigations.end(); ++i, ++page_id) {
+ linked_ptr<NavigationEntry> entry(i->ToNavigationEntry(page_id, profile_));
+ entries->push_back(entry);
+ }
+}
+
+void NavigationController::RendererDidNavigateToNewPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) {
+ NavigationEntry* new_entry;
+ 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. This goes along
+ // with a similar TODO at the top of RendererDidNavigate where we blindly
+ // set the site instance on the pending entry.
+ new_entry = new NavigationEntry(*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(NORMAL_PAGE);
+ } else {
+ new_entry = new NavigationEntry;
+ }
+
+ new_entry->set_url(params.url);
+ if (new_entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(new_entry, params.url);
+ new_entry->set_referrer(params.referrer);
+ new_entry->set_page_id(params.page_id);
+ new_entry->set_transition_type(params.transition);
+ new_entry->set_site_instance(tab_contents_->GetSiteInstance());
+ new_entry->set_has_post_data(params.is_post);
+
+ InsertOrReplaceEntry(new_entry, *did_replace_entry);
+}
+
+void NavigationController::RendererDidNavigateToExistingPage(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // We should only get here for main frame navigations.
+ DCHECK(PageTransition::IsMainFrame(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()));
+ NavigationEntry* 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->set_url(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(tab_contents_->GetSiteInstance());
+
+ entry->set_has_post_data(params.is_post);
+
+ // 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).
+ //
+ // 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 (entry == 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 NavigationController::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.
+ NavigationEntry* 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_->unique_id());
+
+ // The URL may have changed due to redirects.
+ if (existing_entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(existing_entry, params.url);
+ existing_entry->set_url(params.url);
+
+ DiscardNonCommittedEntries();
+}
+
+void NavigationController::RendererDidNavigateInPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) {
+ DCHECK(PageTransition::IsMainFrame(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.
+ NavigationEntry* 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).
+ NavigationEntry* new_entry = new NavigationEntry(*existing_entry);
+ new_entry->set_page_id(params.page_id);
+ if (new_entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(new_entry, params.url);
+ new_entry->set_url(params.url);
+
+ // This replaces the existing entry since the page ID didn't change.
+ *did_replace_entry = true;
+ InsertOrReplaceEntry(new_entry, true);
+}
+
+void NavigationController::RendererDidNavigateNewSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ if (PageTransition::StripQualifier(params.transition) ==
+ PageTransition::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.";
+ NavigationEntry* new_entry = new NavigationEntry(*GetLastCommittedEntry());
+ new_entry->set_page_id(params.page_id);
+ InsertOrReplaceEntry(new_entry, false);
+}
+
+bool NavigationController::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;
+}
+
+// TODO(brettw) I think this function is unnecessary.
+void NavigationController::CommitPendingEntry() {
+ DiscardTransientEntry();
+
+ if (!pending_entry())
+ return; // Nothing to do.
+
+ // Need to save the previous URL for the notification.
+ LoadCommittedDetails details;
+ if (GetLastCommittedEntry()) {
+ details.previous_url = GetLastCommittedEntry()->url();
+ details.previous_entry_index = last_committed_entry_index();
+ } else {
+ details.previous_entry_index = -1;
+ }
+
+ if (pending_entry_index_ >= 0) {
+ // This is a previous navigation (back/forward) that we're just now
+ // committing. Just mark it as committed.
+ details.type = NavigationType::EXISTING_PAGE;
+ int new_entry_index = pending_entry_index_;
+ DiscardNonCommittedEntriesInternal();
+
+ // Mark that entry as committed.
+ last_committed_entry_index_ = new_entry_index;
+ } else {
+ // This is a new navigation. It's easiest to just copy the entry and insert
+ // it new again, since InsertOrReplaceEntry expects to take ownership and
+ // also discard the pending entry. We also need to synthesize a page ID. We
+ // can only do this because this function will only be called by our custom
+ // TabContents types. For TabContents, the IDs are generated by the
+ // renderer, so we can't do this.
+ details.type = NavigationType::NEW_PAGE;
+ pending_entry_->set_page_id(tab_contents_->GetMaxPageID() + 1);
+ tab_contents_->UpdateMaxPageID(pending_entry_->page_id());
+ InsertOrReplaceEntry(new NavigationEntry(*pending_entry_), false);
+ }
+
+ // Broadcast the notification of the navigation.
+ details.entry = GetActiveEntry();
+ details.is_auto = false;
+ details.is_in_page = AreURLsInPageNavigation(details.previous_url,
+ details.entry->url());
+ details.is_main_frame = true;
+ NotifyNavigationEntryCommitted(&details, 0);
+}
+
+int NavigationController::GetIndexOfEntry(
+ const NavigationEntry* 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 NavigationController::IsURLInPageNavigation(const GURL& url) const {
+ NavigationEntry* last_committed = GetLastCommittedEntry();
+ if (!last_committed)
+ return false;
+ return AreURLsInPageNavigation(last_committed->url(), url);
+}
+
+void NavigationController::CopyStateFrom(const NavigationController& source) {
+ // Verify that we look new.
+ DCHECK(entry_count() == 0 && !pending_entry());
+
+ if (source.entry_count() == 0)
+ return; // Nothing new to do.
+
+ needs_reload_ = true;
+ InsertEntriesFrom(source, source.entry_count());
+
+ session_storage_namespace_ = source.session_storage_namespace_->Clone();
+
+ FinishRestore(source.last_committed_entry_index_, false);
+}
+
+void NavigationController::CopyStateFromAndPrune(NavigationController* source) {
+ // This code is intended for use when the last entry is the active entry.
+ DCHECK((transient_entry_index_ != -1 &&
+ transient_entry_index_ == entry_count() - 1) ||
+ (pending_entry_ && (pending_entry_index_ == -1 ||
+ pending_entry_index_ == entry_count() - 1)) ||
+ (!pending_entry_ && last_committed_entry_index_ == entry_count() - 1));
+
+ // Remove all the entries leaving the active entry.
+ PruneAllButActive();
+
+ // 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->entry_count();
+ 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_ = entry_count() - 1;
+ if (pending_entry_index_ != -1)
+ pending_entry_index_ = entry_count() - 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_ = entry_count() - 1;
+ if (last_committed_entry_index_ != -1)
+ last_committed_entry_index_--;
+ }
+}
+
+void NavigationController::PruneAllButActive() {
+ if (transient_entry_index_ != -1) {
+ // There is a transient entry. Prune up to it.
+ DCHECK_EQ(entry_count() - 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 (!entry_count())
+ 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_->interstitial_page()) {
+ // 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.
+ tab_contents_->interstitial_page()->set_reload_on_dont_proceed(true);
+ }
+}
+
+void NavigationController::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(kInvalidateAllButShelves);
+ }
+}
+
+void NavigationController::InsertOrReplaceEntry(NavigationEntry* entry,
+ bool replace) {
+ DCHECK(entry->transition_type() != PageTransition::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 NavigationEntry* const pending_entry = (pending_entry_index_ == -1) ?
+ pending_entry_ : entries_[pending_entry_index_].get();
+ if (pending_entry)
+ entry->set_unique_id(pending_entry->unique_id());
+
+ 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.
+ int prune_up_to = replace ? last_committed_entry_index_ - 1
+ : last_committed_entry_index_;
+ int num_pruned = 0;
+ while (prune_up_to < (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);
+ }
+
+ if (entries_.size() >= max_entry_count_) {
+ RemoveEntryAtIndex(0, GURL());
+ NotifyPrunedEntries(this, true, 1);
+ }
+
+ entries_.push_back(linked_ptr<NavigationEntry>(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->page_id());
+}
+
+void NavigationController::SetWindowID(const SessionID& id) {
+ window_id_ = id;
+ NotificationService::current()->Notify(NotificationType::TAB_PARENTED,
+ Source<NavigationController>(this),
+ NotificationService::NoDetails());
+}
+
+void NavigationController::NavigateToPendingEntry(ReloadType reload_type) {
+ needs_reload_ = false;
+
+ // 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();
+}
+
+void NavigationController::NotifyNavigationEntryCommitted(
+ LoadCommittedDetails* details,
+ int extra_invalidate_flags) {
+ details->entry = GetActiveEntry();
+ NotificationDetails notification_details =
+ Details<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(
+ kInvalidateAllButShelves | extra_invalidate_flags);
+
+ NotificationService::current()->Notify(
+ NotificationType::NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(this),
+ notification_details);
+}
+
+// static
+void NavigationController::DisablePromptOnRepost() {
+ check_for_repost_ = false;
+}
+
+void NavigationController::SetActive(bool is_active) {
+ if (is_active && needs_reload_)
+ LoadIfNecessary();
+}
+
+void NavigationController::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 NavigationController::NotifyEntryChanged(const NavigationEntry* entry,
+ int index) {
+ EntryChangedDetails det;
+ det.changed_entry = entry;
+ det.index = index;
+ NotificationService::current()->Notify(NotificationType::NAV_ENTRY_CHANGED,
+ Source<NavigationController>(this),
+ Details<EntryChangedDetails>(&det));
+}
+
+void NavigationController::FinishRestore(int selected_index,
+ bool from_last_session) {
+ DCHECK(selected_index >= 0 && selected_index < entry_count());
+ ConfigureEntriesForRestore(&entries_, from_last_session);
+
+ set_max_restored_page_id(static_cast<int32>(entry_count()));
+
+ last_committed_entry_index_ = selected_index;
+}
+
+void NavigationController::DiscardNonCommittedEntriesInternal() {
+ if (pending_entry_index_ == -1)
+ delete pending_entry_;
+ pending_entry_ = NULL;
+ pending_entry_index_ = -1;
+
+ DiscardTransientEntry();
+}
+
+void NavigationController::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 NavigationController::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]->page_id() == page_id))
+ return i;
+ }
+ return -1;
+}
+
+NavigationEntry* NavigationController::GetTransientEntry() const {
+ if (transient_entry_index_ == -1)
+ return NULL;
+ return entries_[transient_entry_index_].get();
+}
+
+void NavigationController::InsertEntriesFrom(
+ const NavigationController& source,
+ int max_index) {
+ DCHECK_LE(max_index, source.entry_count());
+ 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()->page_type() != INTERSTITIAL_PAGE) {
+ entries_.insert(entries_.begin() + insert_index++,
+ linked_ptr<NavigationEntry>(
+ new NavigationEntry(*source.entries_[i])));
+ }
+ }
+}
diff --git a/content/browser/tab_contents/navigation_controller.h b/content/browser/tab_contents/navigation_controller.h
new file mode 100644
index 0000000..f348368
--- /dev/null
+++ b/content/browser/tab_contents/navigation_controller.h
@@ -0,0 +1,603 @@
+// Copyright (c) 2010 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_TAB_CONTENTS_NAVIGATION_CONTROLLER_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_NAVIGATION_CONTROLLER_H_
+#pragma once
+
+#include "build/build_config.h"
+
+#include <string>
+#include <vector>
+
+#include "base/linked_ptr.h"
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+#include "chrome/browser/sessions/session_id.h"
+#include "chrome/browser/ssl/ssl_manager.h"
+#include "chrome/common/navigation_types.h"
+#include "chrome/common/page_transition_types.h"
+
+class NavigationEntry;
+class Profile;
+class SessionStorageNamespace;
+class SiteInstance;
+class TabContents;
+class TabNavigation;
+struct ViewHostMsg_FrameNavigate_Params;
+
+// A NavigationController maintains the back-forward list for a single tab and
+// manages all navigation within that list.
+//
+// The NavigationController also owns all TabContents for the tab. This is to
+// make sure that we have at most one TabContents instance per type.
+class NavigationController {
+ public:
+ // Notification details ------------------------------------------------------
+
+ // Provides the details for a NOTIFY_NAV_ENTRY_CHANGED notification.
+ struct EntryChangedDetails {
+ // The changed navigation entry after it has been updated.
+ const NavigationEntry* changed_entry;
+
+ // Indicates the current index in the back/forward list of the entry.
+ int index;
+ };
+
+ // Provides the details for a NOTIFY_NAV_ENTRY_COMMITTED notification.
+ // TODO(brettw) this mostly duplicates ProvisionalLoadDetails, it would be
+ // nice to unify these somehow.
+ struct LoadCommittedDetails {
+ // By default, the entry will be filled according to a new main frame
+ // navigation.
+ LoadCommittedDetails()
+ : entry(NULL),
+ type(NavigationType::UNKNOWN),
+ previous_entry_index(-1),
+ is_auto(false),
+ did_replace_entry(false),
+ is_in_page(false),
+ is_main_frame(true),
+ is_content_filtered(false),
+ http_status_code(0) {
+ }
+
+ // The committed entry. This will be the active entry in the controller.
+ NavigationEntry* entry;
+
+ // The type of navigation that just occurred. Note that not all types of
+ // navigations in the enum are valid here, since some of them don't actually
+ // cause a "commit" and won't generate this notification.
+ NavigationType::Type type;
+
+ // The index of the previously committed navigation entry. This will be -1
+ // if there are no previous entries.
+ int previous_entry_index;
+
+ // The previous URL that the user was on. This may be empty if none.
+ GURL previous_url;
+
+ // True when this load was non-user initated. This corresponds to a
+ // a NavigationGestureAuto call from WebKit (see webview_delegate.h).
+ // We also count reloads and meta-refreshes as "auto" to account for the
+ // fact that WebKit doesn't always set the user gesture properly in these
+ // cases (see bug 1051891).
+ bool is_auto;
+
+ // True if the committed entry has replaced the exisiting one.
+ // A non-user initiated redirect causes such replacement.
+ // This is somewhat similiar to is_auto, but not exactly the same.
+ bool did_replace_entry;
+
+ // True if the navigation was in-page. This means that the active entry's
+ // URL and the |previous_url| are the same except for reference fragments.
+ bool is_in_page;
+
+ // True when the main frame was navigated. False means the navigation was a
+ // sub-frame.
+ bool is_main_frame;
+
+ // Whether the content of this frame has been altered/blocked because it was
+ // unsafe.
+ bool is_content_filtered;
+
+ // When the committed load is a web page from the renderer, this string
+ // specifies the security state if the page is secure.
+ // See ViewHostMsg_FrameNavigate_Params.security_info, where it comes from.
+ // Use SSLManager::DeserializeSecurityInfo to decode it.
+ std::string serialized_security_info;
+
+ // Returns whether the user probably felt like they navigated somewhere new.
+ // We often need this logic for showing or hiding something, and this
+ // returns true only for main frame loads that the user initiated, that go
+ // to a new page.
+ bool is_user_initiated_main_frame_load() const {
+ return !is_auto && !is_in_page && is_main_frame;
+ }
+
+ // The HTTP status code for this entry..
+ int http_status_code;
+ };
+
+ // Details sent for NOTIFY_NAV_LIST_PRUNED.
+ struct PrunedDetails {
+ // If true, count items were removed from the front of the list, otherwise
+ // count items were removed from the back of the list.
+ bool from_front;
+
+ // Number of items removed.
+ int count;
+ };
+
+ enum ReloadType {
+ NO_RELOAD, // Normal load.
+ RELOAD, // Normal (cache-validating) reload.
+ RELOAD_IGNORING_CACHE // Reload bypassing the cache, aka shift-reload.
+ };
+
+ // ---------------------------------------------------------------------------
+
+ NavigationController(TabContents* tab_contents,
+ Profile* profile,
+ SessionStorageNamespace* session_storage_namespace);
+ ~NavigationController();
+
+ // Returns the profile for this controller. It can never be NULL.
+ Profile* profile() const {
+ return profile_;
+ }
+
+ // Sets the profile for this controller.
+ void set_profile(Profile* profile) {
+ profile_ = profile;
+ }
+
+ // Initializes this NavigationController with the given saved navigations,
+ // using selected_navigation as the currently loaded entry. Before this call
+ // the controller should be unused (there should be no current entry). If
+ // from_last_session is true, navigations are from the previous session,
+ // otherwise they are from the current session (undo tab close).
+ // This is used for session restore.
+ void RestoreFromState(const std::vector<TabNavigation>& navigations,
+ int selected_navigation, bool from_last_session);
+
+ // Active entry --------------------------------------------------------------
+
+ // Returns the active entry, which is the transient entry if any, the pending
+ // entry if a navigation is in progress or the last committed entry otherwise.
+ // NOTE: This can be NULL!!
+ //
+ // If you are trying to get the current state of the NavigationController,
+ // this is the method you will typically want to call.
+ NavigationEntry* GetActiveEntry() const;
+
+ // Returns the index from which we would go back/forward or reload. This is
+ // the last_committed_entry_index_ if pending_entry_index_ is -1. Otherwise,
+ // it is the pending_entry_index_.
+ int GetCurrentEntryIndex() const;
+
+ // Returns the last committed entry, which may be null if there are no
+ // committed entries.
+ NavigationEntry* GetLastCommittedEntry() const;
+
+ // Returns true if the source for the current entry can be viewed.
+ bool CanViewSource() const;
+
+ // Returns the index of the last committed entry.
+ int last_committed_entry_index() const {
+ return last_committed_entry_index_;
+ }
+
+ // Navigation list -----------------------------------------------------------
+
+ // Returns the number of entries in the NavigationController, excluding
+ // the pending entry if there is one, but including the transient entry if
+ // any.
+ int entry_count() const {
+ return static_cast<int>(entries_.size());
+ }
+
+ NavigationEntry* GetEntryAtIndex(int index) const {
+ return entries_.at(index).get();
+ }
+
+ // Returns the entry at the specified offset from current. Returns NULL
+ // if out of bounds.
+ NavigationEntry* GetEntryAtOffset(int offset) const;
+
+ // Returns the index of the specified entry, or -1 if entry is not contained
+ // in this NavigationController.
+ int GetIndexOfEntry(const NavigationEntry* entry) const;
+
+ // Return the index of the entry with the corresponding instance and page_id,
+ // or -1 if not found.
+ int GetEntryIndexWithPageID(SiteInstance* instance,
+ int32 page_id) const;
+
+ // Return the entry with the corresponding instance and page_id, or NULL if
+ // not found.
+ NavigationEntry* GetEntryWithPageID(SiteInstance* instance,
+ int32 page_id) const;
+
+ // Pending entry -------------------------------------------------------------
+
+ // Commits the current pending entry and issues the NOTIFY_NAV_ENTRY_COMMIT
+ // notification. No changes are made to the entry during this process, it is
+ // just moved from pending to committed. This is an alternative to
+ // RendererDidNavigate for simple TabContents types.
+ //
+ // When the pending entry is a new navigation, it will have a page ID of -1.
+ // The caller should leave this as-is. CommitPendingEntry will generate a
+ // new page ID for you and update the TabContents with that ID.
+ void CommitPendingEntry();
+
+ // Discards the pending and transient entries if any.
+ void DiscardNonCommittedEntries();
+
+ // Returns the pending entry corresponding to the navigation that is
+ // currently in progress, or null if there is none.
+ NavigationEntry* pending_entry() const {
+ return pending_entry_;
+ }
+
+ // Returns the index of the pending entry or -1 if the pending entry
+ // corresponds to a new navigation (created via LoadURL).
+ int pending_entry_index() const {
+ return pending_entry_index_;
+ }
+
+ // 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(NavigationEntry* entry);
+
+ // Returns the transient entry if any. Note that the returned entry is owned
+ // by the navigation controller and may be deleted at any time.
+ NavigationEntry* GetTransientEntry() const;
+
+ // New navigations -----------------------------------------------------------
+
+ // Loads the specified URL.
+ void LoadURL(const GURL& url, const GURL& referrer,
+ PageTransition::Type type);
+
+ // Loads the current page if this NavigationController was restored from
+ // history and the current page has not loaded yet.
+ void LoadIfNecessary();
+
+ // Renavigation --------------------------------------------------------------
+
+ // Navigation relative to the "current entry"
+ bool CanGoBack() const;
+ bool CanGoForward() const;
+ void GoBack();
+ void GoForward();
+
+ // Navigates to the specified absolute index.
+ void GoToIndex(int index);
+
+ // Navigates to the specified offset from the "current entry". Does nothing if
+ // the offset is out of bounds.
+ void GoToOffset(int offset);
+
+ // Reloads the current entry. If |check_for_repost| is true and the current
+ // entry has POST data the user is prompted to see if they really want to
+ // reload the page. In nearly all cases pass in true.
+ void Reload(bool check_for_repost);
+ // Like Reload(), but don't use caches (aka "shift-reload").
+ void ReloadIgnoringCache(bool check_for_repost);
+
+ // Removing of entries -------------------------------------------------------
+
+ // Removes the entry at the specified |index|. This call dicards any pending
+ // and transient entries. |default_url| is the URL that the navigation
+ // controller navigates to if there are no more entries after the removal.
+ // If |default_url| is empty, we default to "about:blank".
+ void RemoveEntryAtIndex(int index, const GURL& default_url);
+
+ // TabContents ---------------------------------------------------------------
+
+ // Returns the tab contents associated with this controller. Non-NULL except
+ // during set-up of the tab.
+ TabContents* 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 TabContents ----------------------------------------------------
+
+ // Handles updating the navigation state after the renderer has navigated.
+ // This is used by the TabContents. Simpler tab contents types can use
+ // CommitPendingEntry below.
+ //
+ // 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.
+ //
+ // |extra_invalidate_flags| are an additional set of flags (InvalidateTypes)
+ // added to the flags sent to the delegate's NotifyNavigationStateChanged.
+ bool RendererDidNavigate(const ViewHostMsg_FrameNavigate_Params& params,
+ int extra_invalidate_flags,
+ LoadCommittedDetails* details);
+
+ // Notifies us that we just became active. This is used by the TabContents
+ // so that we know to load URLs that were pending as "lazy" loads.
+ void SetActive(bool is_active);
+
+ // Broadcasts the NOTIFY_NAV_ENTRY_CHANGED notification for the given entry
+ // (which must be at the given index). This will keep things in sync like
+ // the saved session.
+ void NotifyEntryChanged(const NavigationEntry* entry, int index);
+
+ // 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;
+
+ // Copies the navigation state from the given controller to this one. This
+ // one should be empty (just created).
+ void CopyStateFrom(const NavigationController& source);
+
+ // A variant of CopyStateFrom. Removes all entries from this except the last
+ // entry, inserts all entries from |source| before and including the active
+ // entry. This method is intended for use when the last entry of |this| is the
+ // active entry. For example:
+ // source: A B *C* D
+ // this: E F *G* (last must be active or pending)
+ // result: A B *G*
+ // This ignores the transient index of the source and honors that of 'this'.
+ void CopyStateFromAndPrune(NavigationController* source);
+
+ // Removes all the entries except the active entry. If there is a new pending
+ // navigation it is preserved.
+ void PruneAllButActive();
+
+ // Random data ---------------------------------------------------------------
+
+ // Returns the identifier used by session restore.
+ const SessionID& session_id() const { return session_id_; }
+
+ // Identifier of the window we're in.
+ void SetWindowID(const SessionID& id);
+ const SessionID& window_id() const { return window_id_; }
+
+ SSLManager* ssl_manager() { return &ssl_manager_; }
+
+ // Returns true if a reload happens when activated (SetActive(true) is
+ // invoked). This is true for session/tab restore and cloned tabs.
+ bool needs_reload() const { return needs_reload_; }
+
+ // Sets the max restored page ID this NavigationController has seen, if it
+ // was restored from a previous session.
+ void set_max_restored_page_id(int32 max_id) {
+ max_restored_page_id_ = max_id;
+ }
+
+ // Returns the largest restored page ID seen in this navigation controller,
+ // if it was restored from a previous session. (-1 otherwise)
+ int32 max_restored_page_id() const { return max_restored_page_id_; }
+
+ // The session storage namespace that all child render views should use.
+ SessionStorageNamespace* session_storage_namespace() const {
+ return session_storage_namespace_;
+ }
+
+ // Disables checking for a repost and prompting the user. This is used during
+ // testing.
+ static void DisablePromptOnRepost();
+
+ // Maximum number of entries before we start removing entries from the front.
+#ifdef UNIT_TEST
+ static void set_max_entry_count(size_t max_entry_count) {
+ max_entry_count_ = max_entry_count;
+ }
+#endif
+ static size_t max_entry_count() { return max_entry_count_; }
+
+ // Cancels a repost that brought up a warning.
+ void CancelPendingReload();
+ // Continues a repost that brought up a warning.
+ void ContinuePendingReload();
+
+ // Returns true if we are navigating to the URL the tab is opened with.
+ bool IsInitialNavigation();
+
+ // Creates navigation entry and translates the virtual url to a real one.
+ // Used when restoring a tab from a TabNavigation object and when navigating
+ // to a new URL using LoadURL.
+ static NavigationEntry* CreateNavigationEntry(const GURL& url,
+ const GURL& referrer,
+ PageTransition::Type transition,
+ Profile* profile);
+
+ private:
+ class RestoreHelper;
+ friend class RestoreHelper;
+ friend class TabContents; // For invoking OnReservedPageIDRange.
+
+ // Classifies the given renderer navigation (see the NavigationType enum).
+ NavigationType::Type 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(NavigationEntry* 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.
+ //
+ // |extra_invalidate_flags| are an additional set of flags (InvalidateTypes)
+ // added to the flags sent to the delegate's NotifyNavigationStateChanged.
+ void NotifyNavigationEntryCommitted(LoadCommittedDetails* details,
+ int extra_invalidate_flags);
+
+ // 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(NavigationEntry* 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(NavigationEntry* entry, bool replace);
+
+ // Discards the pending and transient entries.
+ void DiscardNonCommittedEntriesInternal();
+
+ // Discards the transient entry.
+ void DiscardTransientEntry();
+
+ // 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);
+
+ // Creates a new NavigationEntry for each TabNavigation in navigations, adding
+ // the NavigationEntry to entries. This is used during session restore.
+ void CreateNavigationEntriesFromTabNavigations(
+ const std::vector<TabNavigation>& navigations,
+ std::vector<linked_ptr<NavigationEntry> >* entries);
+
+ // 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 NavigationController& source, int max_index);
+
+ // ---------------------------------------------------------------------------
+
+ // The user profile associated with this controller
+ Profile* profile_;
+
+ // List of NavigationEntry for this tab
+ typedef std::vector<linked_ptr<NavigationEntry> > 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.
+ NavigationEntry* 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.
+ TabContents* tab_contents_;
+
+ // The max restored page ID in this controller, if it was restored. We must
+ // store this so that TabContents 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_;
+
+ // Unique identifier of this controller for session restore. This id is only
+ // unique within the current session, and is not guaranteed to be unique
+ // across sessions.
+ SessionID session_id_;
+
+ // Unique identifier of the window we're in. Used by session restore.
+ SessionID window_id_;
+
+ // 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<SessionStorageNamespace> session_storage_namespace_;
+
+ // Should Reload check for post data? The default is true, but is set to false
+ // when testing.
+ static bool check_for_repost_;
+
+ // The maximum number of entries that a navigation controller can store.
+ static size_t max_entry_count_;
+
+ // If a repost is pending, its type (RELOAD or RELOAD_IGNORING_CACHE),
+ // NO_RELOAD otherwise.
+ ReloadType pending_reload_;
+
+ DISALLOW_COPY_AND_ASSIGN(NavigationController);
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_NAVIGATION_CONTROLLER_H_
diff --git a/content/browser/tab_contents/navigation_controller_unittest.cc b/content/browser/tab_contents/navigation_controller_unittest.cc
new file mode 100644
index 0000000..2691305
--- /dev/null
+++ b/content/browser/tab_contents/navigation_controller_unittest.cc
@@ -0,0 +1,2025 @@
+// Copyright (c) 2010 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/path_service.h"
+#include "base/scoped_ptr.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/renderer_host/test/test_render_view_host.h"
+#include "chrome/browser/sessions/session_service.h"
+#include "chrome/browser/sessions/session_service_test_helper.h"
+#include "chrome/browser/sessions/session_types.h"
+#include "chrome/browser/tab_contents/navigation_controller.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/tab_contents/tab_contents_delegate.h"
+#include "chrome/browser/tab_contents/test_tab_contents.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/render_messages_params.h"
+#include "chrome/test/test_notification_tracker.h"
+#include "chrome/test/testing_profile.h"
+#include "net/base/net_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/glue/webkit_glue.h"
+
+using base::Time;
+
+// NavigationControllerTest ----------------------------------------------------
+
+class NavigationControllerTest : public RenderViewHostTestHarness {
+ public:
+ NavigationControllerTest() {}
+};
+
+// 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());
+ profile()->set_session_service(service);
+ service->SetWindowType(window_id, Browser::TYPE_NORMAL);
+ 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.
+ profile()->set_session_service(NULL);
+ session_helper_.set_service(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(new MessageLoop::QuitTask);
+ 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);
+ profile()->set_session_service(NULL);
+
+ SessionService* service = new SessionService(profile());
+ profile()->set_session_service(service);
+ session_helper_.set_service(service);
+ }
+
+ void GetLastSession() {
+ profile()->GetSessionService()->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_;
+};
+
+void RegisterForAllNavNotifications(TestNotificationTracker* tracker,
+ NavigationController* controller) {
+ tracker->ListenFor(NotificationType::NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(controller));
+ tracker->ListenFor(NotificationType::NAV_LIST_PRUNED,
+ Source<NavigationController>(controller));
+ tracker->ListenFor(NotificationType::NAV_ENTRY_CHANGED,
+ Source<NavigationController>(controller));
+}
+
+// -----------------------------------------------------------------------------
+
+TEST_F(NavigationControllerTest, Defaults) {
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_FALSE(controller().GetLastCommittedEntry());
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_EQ(controller().last_committed_entry_index(), -1);
+ EXPECT_EQ(controller().entry_count(), 0);
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+}
+
+TEST_F(NavigationControllerTest, LoadURL) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ // 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().entry_count(), 0);
+ EXPECT_EQ(controller().last_committed_entry_index(), -1);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_FALSE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(controller().pending_entry());
+ 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());
+
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // The load should now be committed.
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 0);
+
+ // Load another...
+ controller().LoadURL(url2, GURL(), PageTransition::TYPED);
+
+ // The load should now be pending.
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(controller().pending_entry());
+ // TODO(darin): maybe this should really be true?
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 0);
+
+ rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // The load should now be committed.
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ 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) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ EXPECT_EQ(0U, notifications.size());
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ EXPECT_EQ(0U, notifications.size());
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // We should not have produced a new session history entry.
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+}
+
+// Tests loading a URL but discarding it before the load commits.
+TEST_F(NavigationControllerTest, LoadURL_Discarded) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ EXPECT_EQ(0U, notifications.size());
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().LoadURL(url2, GURL(), PageTransition::TYPED);
+ controller().DiscardNonCommittedEntries();
+ EXPECT_EQ(0U, notifications.size());
+
+ // Should not have produced a new session history entry.
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ 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) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ // First make an existing committed entry.
+ const GURL kExistingURL1("http://eh");
+ controller().LoadURL(kExistingURL1, GURL(),
+ PageTransition::TYPED);
+ rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // Do a new navigation without making a pending one.
+ const GURL kNewURL("http://see");
+ 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(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(-1, controller().pending_entry_index());
+ EXPECT_EQ(1, controller().last_committed_entry_index());
+ EXPECT_EQ(kNewURL, controller().GetActiveEntry()->url());
+}
+
+// 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) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ // First make an existing committed entry.
+ const GURL kExistingURL1("http://eh");
+ controller().LoadURL(kExistingURL1, GURL(),
+ PageTransition::TYPED);
+ rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // Make a pending entry to somewhere new.
+ const GURL kExistingURL2("http://bee");
+ controller().LoadURL(kExistingURL2, GURL(),
+ PageTransition::TYPED);
+ EXPECT_EQ(0U, notifications.size());
+
+ // Before that commits, do a new navigation.
+ const GURL kNewURL("http://see");
+ 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(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(-1, controller().pending_entry_index());
+ EXPECT_EQ(1, controller().last_committed_entry_index());
+ EXPECT_EQ(kNewURL, controller().GetActiveEntry()->url());
+}
+
+// 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) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ // First make some history.
+ const GURL kExistingURL1("http://eh");
+ controller().LoadURL(kExistingURL1, GURL(),
+ PageTransition::TYPED);
+ rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ const GURL kExistingURL2("http://bee");
+ controller().LoadURL(kExistingURL2, GURL(),
+ PageTransition::TYPED);
+ rvh()->SendNavigate(1, kExistingURL2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::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().pending_entry_index());
+ EXPECT_EQ(1, controller().last_committed_entry_index());
+
+ // Before that commits, do a new navigation.
+ const GURL kNewURL("http://see");
+ NavigationController::LoadCommittedDetails details;
+ 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(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(-1, controller().pending_entry_index());
+ EXPECT_EQ(2, controller().last_committed_entry_index());
+ EXPECT_EQ(kNewURL, controller().GetActiveEntry()->url());
+}
+
+TEST_F(NavigationControllerTest, Reload) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ EXPECT_EQ(0U, notifications.size());
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().Reload(true);
+ EXPECT_EQ(0U, notifications.size());
+
+ // The reload is pending.
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_EQ(controller().pending_entry_index(), 0);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(controller().pending_entry());
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+}
+
+// Tests what happens when a reload navigation produces a new page.
+TEST_F(NavigationControllerTest, Reload_GeneratesNewPage) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().Reload(true);
+ EXPECT_EQ(0U, notifications.size());
+
+ rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_TRUE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+}
+
+// Tests what happens when we navigate back successfully
+TEST_F(NavigationControllerTest, Back) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ const GURL url2("http://foo2");
+ rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().GoBack();
+ EXPECT_EQ(0U, notifications.size());
+
+ // We should now have a pending navigation to go back.
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ EXPECT_EQ(controller().pending_entry_index(), 0);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(controller().pending_entry());
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_TRUE(controller().CanGoForward());
+
+ rvh()->SendNavigate(0, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // The back navigation completed successfully.
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_TRUE(controller().CanGoForward());
+}
+
+// Tests what happens when a back navigation produces a new page.
+TEST_F(NavigationControllerTest, Back_GeneratesNewPage) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().LoadURL(url2, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().GoBack();
+ EXPECT_EQ(0U, notifications.size());
+
+ // We should now have a pending navigation to go back.
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ EXPECT_EQ(controller().pending_entry_index(), 0);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(controller().pending_entry());
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_TRUE(controller().CanGoForward());
+
+ rvh()->SendNavigate(2, url3);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::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().entry_count(), 3);
+ EXPECT_EQ(controller().last_committed_entry_index(), 2);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ 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) {
+ 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.
+ rvh()->SendNavigate(0, kUrl1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // controller().LoadURL(kUrl2, PageTransition::TYPED);
+ rvh()->SendNavigate(1, kUrl2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // Now start a new pending navigation and go back before it commits.
+ controller().LoadURL(kUrl3, GURL(), PageTransition::TYPED);
+ EXPECT_EQ(-1, controller().pending_entry_index());
+ EXPECT_EQ(kUrl3, controller().pending_entry()->url());
+ controller().GoBack();
+
+ // The pending navigation should now be the "back" item and the new one
+ // should be gone.
+ EXPECT_EQ(0, controller().pending_entry_index());
+ EXPECT_EQ(kUrl1, controller().pending_entry()->url());
+}
+
+// Receives a back message when there is a different renavigation already
+// pending.
+TEST_F(NavigationControllerTest, Back_OtherBackPending) {
+ 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.
+ rvh()->SendNavigate(0, kUrl1);
+ rvh()->SendNavigate(1, kUrl2);
+ rvh()->SendNavigate(2, kUrl3);
+
+ // With nothing pending, say we get a navigation to the second entry.
+ 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 =
+ 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)->url());
+ EXPECT_EQ(1, controller().last_committed_entry_index());
+ EXPECT_EQ(-1, controller().pending_entry_index());
+
+ // Now go forward to the last item again and say it was committed.
+ controller().GoForward();
+ rvh()->SendNavigate(2, kUrl3);
+
+ // Now start going back one to the second page. It will be pending.
+ controller().GoBack();
+ EXPECT_EQ(1, controller().pending_entry_index());
+ EXPECT_EQ(2, controller().last_committed_entry_index());
+
+ // Not synthesize a totally new back event to the first page. This will not
+ // match the pending one.
+ rvh()->SendNavigate(0, kUrl1);
+
+ // The navigation should not have affected the pending entry.
+ EXPECT_EQ(1, controller().pending_entry_index());
+
+ // But the navigated entry should be the last committed.
+ EXPECT_EQ(0, controller().last_committed_entry_index());
+ EXPECT_EQ(kUrl1, controller().GetLastCommittedEntry()->url());
+}
+
+// Tests what happens when we navigate forward successfully.
+TEST_F(NavigationControllerTest, Forward) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().GoBack();
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().GoForward();
+
+ // We should now have a pending navigation to go forward.
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_EQ(controller().pending_entry_index(), 1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(controller().pending_entry());
+ EXPECT_TRUE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+
+ rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // The forward navigation completed successfully.
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_TRUE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+}
+
+// Tests what happens when a forward navigation produces a new page.
+TEST_F(NavigationControllerTest, Forward_GeneratesNewPage) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().GoBack();
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ controller().GoForward();
+ EXPECT_EQ(0U, notifications.size());
+
+ // Should now have a pending navigation to go forward.
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_EQ(controller().pending_entry_index(), 1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(controller().pending_entry());
+ EXPECT_TRUE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+
+ rvh()->SendNavigate(2, url3);
+ EXPECT_TRUE(notifications.Check2AndReset(
+ NotificationType::NAV_LIST_PRUNED,
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ 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) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+
+ EXPECT_EQ(0U, notifications.size());
+ rvh()->SendNavigate(0, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // Second request
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+
+ EXPECT_TRUE(controller().pending_entry());
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_EQ(url1, controller().GetActiveEntry()->url());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PageTransition::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;
+
+ NavigationController::LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ EXPECT_TRUE(details.type == NavigationType::SAME_PAGE);
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_EQ(url2, controller().GetActiveEntry()->url());
+
+ 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) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request as POST
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ controller().GetActiveEntry()->set_has_post_data(true);
+
+ EXPECT_EQ(0U, notifications.size());
+ rvh()->SendNavigate(0, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // Second request
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+
+ EXPECT_TRUE(controller().pending_entry());
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_EQ(url1, controller().GetActiveEntry()->url());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PageTransition::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;
+
+ NavigationController::LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ EXPECT_TRUE(details.type == NavigationType::SAME_PAGE);
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_EQ(url2, controller().GetActiveEntry()->url());
+ EXPECT_FALSE(controller().GetActiveEntry()->has_post_data());
+
+ EXPECT_FALSE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+}
+
+// A redirect right off the bat should be a NEW_PAGE.
+TEST_F(NavigationControllerTest, ImmediateRedirect) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+
+ EXPECT_TRUE(controller().pending_entry());
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_EQ(url1, controller().GetActiveEntry()->url());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PageTransition::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;
+
+ NavigationController::LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ EXPECT_TRUE(details.type == NavigationType::NEW_PAGE);
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_EQ(url2, controller().GetActiveEntry()->url());
+
+ 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) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = PageTransition::MANUAL_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+
+ NavigationController::LoadCommittedDetails details;
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(url1, details.previous_url);
+ EXPECT_FALSE(details.is_auto);
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_FALSE(details.is_main_frame);
+
+ // The new entry should be appended.
+ EXPECT_EQ(2, controller().entry_count());
+
+ // New entry should refer to the new page, but the old URL (entries only
+ // reflect the toplevel URL).
+ EXPECT_EQ(url1, details.entry->url());
+ EXPECT_EQ(params.page_id, details.entry->page_id());
+}
+
+// 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) {
+ 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 = PageTransition::AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+
+ NavigationController::LoadCommittedDetails details;
+ EXPECT_FALSE(controller().RendererDidNavigate(params, 0, &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) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PageTransition::AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+
+ // Navigating should do nothing.
+ NavigationController::LoadCommittedDetails details;
+ EXPECT_FALSE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_EQ(0U, notifications.size());
+
+ // There should still be only one entry.
+ EXPECT_EQ(1, controller().entry_count());
+}
+
+// Tests navigation and then going back to a subframe navigation.
+TEST_F(NavigationControllerTest, BackSubframe) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ // Main page.
+ const GURL url1("http://foo1");
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::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 = PageTransition::MANUAL_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+
+ // This should generate a new entry.
+ NavigationController::LoadCommittedDetails details;
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(2, controller().entry_count());
+
+ // 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, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(3, controller().entry_count());
+ EXPECT_EQ(2, controller().GetCurrentEntryIndex());
+
+ // Go back one.
+ controller().GoBack();
+ params.url = url2;
+ params.page_id = 1;
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(3, controller().entry_count());
+ EXPECT_EQ(1, controller().GetCurrentEntryIndex());
+
+ // Go back one more.
+ controller().GoBack();
+ params.url = url1;
+ params.page_id = 0;
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(3, controller().entry_count());
+ EXPECT_EQ(0, controller().GetCurrentEntryIndex());
+}
+
+TEST_F(NavigationControllerTest, LinkClick) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ rvh()->SendNavigate(1, url2);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // Should not have produced a new session history entry.
+ EXPECT_EQ(controller().entry_count(), 2);
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_TRUE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+}
+
+TEST_F(NavigationControllerTest, InPage) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ // Main page.
+ const GURL url1("http://foo");
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+
+ // First navigation.
+ const GURL url2("http://foo#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = PageTransition::LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+
+ // This should generate a new entry.
+ NavigationController::LoadCommittedDetails details;
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_FALSE(details.did_replace_entry);
+ EXPECT_EQ(2, controller().entry_count());
+
+ // 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, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::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().entry_count());
+ EXPECT_EQ(0, controller().GetCurrentEntryIndex());
+ EXPECT_EQ(back_params.url, controller().GetActiveEntry()->url());
+
+ // 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, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_EQ(2, controller().entry_count());
+ EXPECT_EQ(1, controller().GetCurrentEntryIndex());
+ EXPECT_EQ(forward_params.url,
+ controller().GetActiveEntry()->url());
+
+ // 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, 0, &details));
+ controller().GoForward();
+ EXPECT_TRUE(controller().RendererDidNavigate(forward_params, 0, &details));
+ EXPECT_EQ(forward_params.url,
+ controller().GetActiveEntry()->url());
+
+ // 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, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_FALSE(details.is_in_page);
+}
+
+TEST_F(NavigationControllerTest, InPage_Replace) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ // Main page.
+ const GURL url1("http://foo");
+ rvh()->SendNavigate(0, url1);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::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 = PageTransition::LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+
+ // This should NOT generate a new entry.
+ NavigationController::LoadCommittedDetails details;
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check2AndReset(
+ NotificationType::NAV_LIST_PRUNED,
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(1, controller().entry_count());
+}
+
+// Tests for http://crbug.com/40395
+// Simulates this:
+// <script>
+// window.location.replace("#a");
+// window.location='http://foo3/';
+// </script>
+TEST_F(NavigationControllerTest, ClientRedirectAfterInPageNavigation) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ // Load an initial page.
+ {
+ const GURL url("http://foo/");
+ rvh()->SendNavigate(0, url);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ }
+
+ // Navigate to a new page.
+ {
+ const GURL url("http://foo2/");
+ rvh()->SendNavigate(1, url);
+ controller().DocumentLoadedInFrame();
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::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 = PageTransition::LINK;
+ params.redirects.push_back(url);
+ params.should_update_history = true;
+ params.gesture = NavigationGestureUnknown;
+ params.is_post = false;
+
+ // This should NOT generate a new entry.
+ NavigationController::LoadCommittedDetails details;
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check2AndReset(
+ NotificationType::NAV_LIST_PRUNED,
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(2, controller().entry_count());
+ }
+
+ // 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 = PageTransition::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;
+
+ // This SHOULD generate a new entry.
+ NavigationController::LoadCommittedDetails details;
+ EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details));
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_EQ(3, controller().entry_count());
+ }
+
+ // Verify that BACK brings us back to http://foo2/.
+ {
+ const GURL url("http://foo2/");
+ controller().GoBack();
+ rvh()->SendNavigate(1, url);
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::NAV_ENTRY_COMMITTED));
+ EXPECT_EQ(url, controller().GetActiveEntry()->url());
+ }
+}
+
+// NotificationObserver implementation used in verifying we've received the
+// NotificationType::NAV_LIST_PRUNED method.
+class PrunedListener : public NotificationObserver {
+ public:
+ explicit PrunedListener(NavigationController* controller)
+ : notification_count_(0) {
+ registrar_.Add(this, NotificationType::NAV_LIST_PRUNED,
+ Source<NavigationController>(controller));
+ }
+
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NotificationType::NAV_LIST_PRUNED) {
+ notification_count_++;
+ details_ = *(Details<NavigationController::PrunedDetails>(details).ptr());
+ }
+ }
+
+ // Number of times NAV_LIST_PRUNED has been observed.
+ int notification_count_;
+
+ // Details from the last NAV_LIST_PRUNED.
+ NavigationController::PrunedDetails details_;
+
+ private:
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrunedListener);
+};
+
+// Tests that we limit the number of navigation entries created correctly.
+TEST_F(NavigationControllerTest, EnforceMaxNavigationCount) {
+ size_t original_count = NavigationController::max_entry_count();
+ const int kMaxEntryCount = 5;
+
+ NavigationController::set_max_entry_count(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, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(url_index, url);
+ }
+
+ EXPECT_EQ(controller().entry_count(), 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, GURL(), PageTransition::TYPED);
+ 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().entry_count(), kMaxEntryCount);
+ EXPECT_EQ(controller().GetEntryAtIndex(0)->url(),
+ 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, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(url_index, url);
+ url_index++;
+ }
+ EXPECT_EQ(controller().entry_count(), kMaxEntryCount);
+ EXPECT_EQ(controller().GetEntryAtIndex(0)->url(),
+ GURL("http:////www.a.com/4"));
+
+ NavigationController::set_max_entry_count(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<TabNavigation> navigations;
+ navigations.push_back(TabNavigation(0, url, GURL(),
+ ASCIIToUTF16("Title"), "state",
+ PageTransition::LINK));
+ TabContents our_contents(profile(), NULL, MSG_ROUTING_NONE, NULL, NULL);
+ NavigationController& our_controller = our_contents.controller();
+ our_controller.RestoreFromState(navigations, 0, true);
+ our_controller.GoToIndex(0);
+
+ // We should now have one entry, and it should be "pending".
+ EXPECT_EQ(1, our_controller.entry_count());
+ EXPECT_EQ(our_controller.GetEntryAtIndex(0),
+ our_controller.pending_entry());
+ EXPECT_EQ(0, our_controller.GetEntryAtIndex(0)->page_id());
+ EXPECT_EQ(NavigationEntry::RESTORE_LAST_SESSION,
+ our_controller.GetEntryAtIndex(0)->restore_type());
+
+ // Say we navigated to that entry.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url;
+ params.transition = PageTransition::LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ NavigationController::LoadCommittedDetails details;
+ our_controller.RendererDidNavigate(params, 0, &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.entry_count());
+ EXPECT_EQ(0, our_controller.last_committed_entry_index());
+ EXPECT_FALSE(our_controller.pending_entry());
+ EXPECT_EQ(url,
+ our_controller.GetLastCommittedEntry()->site_instance()->site());
+ EXPECT_EQ(NavigationEntry::RESTORE_NONE,
+ our_controller.GetEntryAtIndex(0)->restore_type());
+}
+
+// Make sure that the page type and stuff is correct after an interstitial.
+TEST_F(NavigationControllerTest, Interstitial) {
+ // First navigate somewhere normal.
+ const GURL url1("http://foo");
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(0, url1);
+
+ // Now navigate somewhere with an interstitial.
+ const GURL url2("http://bar");
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ controller().pending_entry()->set_page_type(INTERSTITIAL_PAGE);
+
+ // At this point the interstitial will be displayed and the load will still
+ // be pending. If the user continues, the load will commit.
+ rvh()->SendNavigate(1, url2);
+
+ // The page should be a normal page again.
+ EXPECT_EQ(url2, controller().GetLastCommittedEntry()->url());
+ EXPECT_EQ(NORMAL_PAGE, controller().GetLastCommittedEntry()->page_type());
+}
+
+TEST_F(NavigationControllerTest, RemoveEntry) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+ const GURL url5("http://foo5");
+ const GURL pending_url("http://pending");
+ const GURL default_url("http://default");
+
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(0, url1);
+ controller().LoadURL(url2, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(1, url2);
+ controller().LoadURL(url3, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(2, url3);
+ controller().LoadURL(url4, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(3, url4);
+ controller().LoadURL(url5, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(4, url5);
+
+ // Remove the last entry.
+ controller().RemoveEntryAtIndex(
+ controller().entry_count() - 1, default_url);
+ EXPECT_EQ(4, controller().entry_count());
+ EXPECT_EQ(3, controller().last_committed_entry_index());
+ NavigationEntry* pending_entry = controller().pending_entry();
+ EXPECT_TRUE(pending_entry && pending_entry->url() == url4);
+
+ // Add a pending entry.
+ controller().LoadURL(pending_url, GURL(), PageTransition::TYPED);
+ // Now remove the last entry.
+ controller().RemoveEntryAtIndex(
+ controller().entry_count() - 1, default_url);
+ // The pending entry should have been discarded and the last committed entry
+ // removed.
+ EXPECT_EQ(3, controller().entry_count());
+ EXPECT_EQ(2, controller().last_committed_entry_index());
+ pending_entry = controller().pending_entry();
+ EXPECT_TRUE(pending_entry && pending_entry->url() == url3);
+
+ // Remove an entry which is not the last committed one.
+ controller().RemoveEntryAtIndex(0, default_url);
+ EXPECT_EQ(2, controller().entry_count());
+ EXPECT_EQ(1, controller().last_committed_entry_index());
+ // No navigation should have been initiated since we did not remove the
+ // current entry.
+ EXPECT_FALSE(controller().pending_entry());
+
+ // Remove the 2 remaining entries.
+ controller().RemoveEntryAtIndex(1, default_url);
+ controller().RemoveEntryAtIndex(0, default_url);
+
+ // This should have created a pending default entry.
+ EXPECT_EQ(0, controller().entry_count());
+ EXPECT_EQ(-1, controller().last_committed_entry_index());
+ pending_entry = controller().pending_entry();
+ EXPECT_TRUE(pending_entry && pending_entry->url() == default_url);
+}
+
+// Tests the transient entry, making sure it goes away with all navigations.
+TEST_F(NavigationControllerTest, TransientEntry) {
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller());
+
+ const GURL url0("http://foo0");
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+ const GURL transient_url("http://transient");
+
+ controller().LoadURL(url0, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(0, url0);
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(1, url1);
+
+ notifications.Reset();
+
+ // Adding a transient with no pending entry.
+ NavigationEntry* transient_entry = new NavigationEntry;
+ transient_entry->set_url(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()->url());
+ EXPECT_EQ(controller().entry_count(), 3);
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ EXPECT_EQ(controller().pending_entry_index(), -1);
+ EXPECT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_FALSE(controller().pending_entry());
+ EXPECT_TRUE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 1);
+
+ // Navigate.
+ controller().LoadURL(url2, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(2, url2);
+
+ // We should have navigated, transient entry should be gone.
+ EXPECT_EQ(url2, controller().GetActiveEntry()->url());
+ EXPECT_EQ(controller().entry_count(), 3);
+
+ // Add a transient again, then navigate with no pending entry this time.
+ transient_entry = new NavigationEntry;
+ transient_entry->set_url(transient_url);
+ controller().AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller().GetActiveEntry()->url());
+ rvh()->SendNavigate(3, url3);
+ // Transient entry should be gone.
+ EXPECT_EQ(url3, controller().GetActiveEntry()->url());
+ EXPECT_EQ(controller().entry_count(), 4);
+
+ // Initiate a navigation, add a transient then commit navigation.
+ controller().LoadURL(url4, GURL(), PageTransition::TYPED);
+ transient_entry = new NavigationEntry;
+ transient_entry->set_url(transient_url);
+ controller().AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller().GetActiveEntry()->url());
+ rvh()->SendNavigate(4, url4);
+ EXPECT_EQ(url4, controller().GetActiveEntry()->url());
+ EXPECT_EQ(controller().entry_count(), 5);
+
+ // Add a transient and go back. This should simply remove the transient.
+ transient_entry = new NavigationEntry;
+ transient_entry->set_url(transient_url);
+ controller().AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller().GetActiveEntry()->url());
+ EXPECT_TRUE(controller().CanGoBack());
+ EXPECT_FALSE(controller().CanGoForward());
+ controller().GoBack();
+ // Transient entry should be gone.
+ EXPECT_EQ(url4, controller().GetActiveEntry()->url());
+ EXPECT_EQ(controller().entry_count(), 5);
+ rvh()->SendNavigate(3, url3);
+
+ // Add a transient and go to an entry before the current one.
+ transient_entry = new NavigationEntry;
+ transient_entry->set_url(transient_url);
+ controller().AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller().GetActiveEntry()->url());
+ controller().GoToIndex(1);
+ // The navigation should have been initiated, transient entry should be gone.
+ EXPECT_EQ(url1, controller().GetActiveEntry()->url());
+ rvh()->SendNavigate(1, url1);
+
+ // Add a transient and go to an entry after the current one.
+ transient_entry = new NavigationEntry;
+ transient_entry->set_url(transient_url);
+ controller().AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller().GetActiveEntry()->url());
+ 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.
+ EXPECT_EQ(url2, controller().GetActiveEntry()->url());
+ rvh()->SendNavigate(2, url2);
+
+ // Add a transient and go forward.
+ transient_entry = new NavigationEntry;
+ transient_entry->set_url(transient_url);
+ controller().AddTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller().GetActiveEntry()->url());
+ EXPECT_TRUE(controller().CanGoForward());
+ controller().GoForward();
+ // We should have navigated, transient entry should be gone.
+ EXPECT_EQ(url3, controller().GetActiveEntry()->url());
+ rvh()->SendNavigate(3, url3);
+
+ // Ensure the URLS are correct.
+ EXPECT_EQ(controller().entry_count(), 5);
+ EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), url0);
+ EXPECT_EQ(controller().GetEntryAtIndex(1)->url(), url1);
+ EXPECT_EQ(controller().GetEntryAtIndex(2)->url(), url2);
+ EXPECT_EQ(controller().GetEntryAtIndex(3)->url(), url3);
+ EXPECT_EQ(controller().GetEntryAtIndex(4)->url(), url4);
+}
+
+// Tests that IsInPageNavigation returns appropriate results. Prevents
+// regression for bug 1126349.
+TEST_F(NavigationControllerTest, IsInPageNavigation) {
+ // Navigate to URL with no refs.
+ const GURL url("http://www.google.com/home.html");
+ 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.
+ 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) {
+ // Navigate the main frame.
+ const GURL url("http://www.google.com/");
+ rvh()->SendNavigate(0, url);
+
+ // We should be at the first navigation entry.
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 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 = PageTransition::AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ NavigationController::LoadCommittedDetails details;
+ EXPECT_FALSE(controller().RendererDidNavigate(params, 0, &details));
+
+ // Nothing should have changed.
+ EXPECT_EQ(controller().entry_count(), 1);
+ EXPECT_EQ(controller().last_committed_entry_index(), 0);
+}
+
+// Test view source redirection is reflected in title bar.
+TEST_F(NavigationControllerTest, ViewSourceRedirect) {
+ const char kUrl[] = "view-source:http://redirect.to/google.com";
+ const char kResult[] = "http://google.com";
+ const char kExpected[] = "view-source:google.com";
+ const GURL url(kUrl);
+ const GURL result_url(kResult);
+
+ controller().LoadURL(url, GURL(), PageTransition::TYPED);
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = result_url;
+ params.transition = PageTransition::SERVER_REDIRECT;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ NavigationController::LoadCommittedDetails details;
+ controller().RendererDidNavigate(params, 0, &details);
+
+ EXPECT_EQ(ASCIIToUTF16(kExpected), contents()->GetTitle());
+ EXPECT_TRUE(contents()->ShouldDisplayURL());
+}
+
+// Make sure that on cloning a tabcontents and going back needs_reload is false.
+TEST_F(NavigationControllerTest, CloneAndGoBack) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<TabContents> clone(controller().tab_contents()->Clone());
+
+ ASSERT_EQ(2, clone->controller().entry_count());
+ EXPECT_TRUE(clone->controller().needs_reload());
+ clone->controller().GoBack();
+ // Navigating back should have triggered needs_reload_ to go false.
+ EXPECT_FALSE(clone->controller().needs_reload());
+}
+
+// Make sure that cloning a tabcontents doesn't copy interstitials.
+TEST_F(NavigationControllerTest, CloneOmitsInterstitials) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ // Add an interstitial entry. Should be deleted with controller.
+ NavigationEntry* interstitial_entry = new NavigationEntry();
+ interstitial_entry->set_page_type(INTERSTITIAL_PAGE);
+ controller().AddTransientEntry(interstitial_entry);
+
+ scoped_ptr<TabContents> clone(controller().tab_contents()->Clone());
+
+ ASSERT_EQ(2, clone->controller().entry_count());
+}
+
+// Tests a subframe navigation while a toplevel navigation is pending.
+// http://crbug.com/43967
+TEST_F(NavigationControllerTest, SubframeWhilePending) {
+ // 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, GURL(), PageTransition::TYPED);
+
+ // 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()->page_id();
+ params.url = url1_sub;
+ params.transition = PageTransition::AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ NavigationController::LoadCommittedDetails details;
+
+ // This should return false meaning that nothing was actually updated.
+ EXPECT_FALSE(controller().RendererDidNavigate(params, 0, &details));
+
+ // The notification should have updated the last committed one, and not
+ // the pending load.
+ EXPECT_EQ(url1, controller().GetLastCommittedEntry()->url());
+
+ // The active entry should be unchanged by the subframe load.
+ EXPECT_EQ(url2, controller().GetActiveEntry()->url());
+}
+
+// Tests CopyStateFromAndPrune with 2 urls in source, 1 in dest.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune) {
+ SessionID id(controller().session_id());
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<TestTabContents> other_contents(CreateTestTabContents());
+ NavigationController& other_controller = other_contents->controller();
+ SessionID other_id(other_controller.session_id());
+ other_contents->NavigateAndCommit(url3);
+ other_controller.CopyStateFromAndPrune(&controller());
+
+ // other_controller should now contain the 3 urls: url1, url2 and url3.
+
+ ASSERT_EQ(3, other_controller.entry_count());
+
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->url());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->url());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(2)->url());
+
+ // Make sure session ids didn't change.
+ EXPECT_EQ(id.id(), controller().session_id().id());
+ EXPECT_EQ(other_id.id(), other_controller.session_id().id());
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the first selected and nothing in
+// the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune2) {
+ SessionID id(controller().session_id());
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller().GoBack();
+
+ scoped_ptr<TestTabContents> other_contents(CreateTestTabContents());
+ NavigationController& other_controller = other_contents->controller();
+ SessionID other_id(other_controller.session_id());
+ other_controller.CopyStateFromAndPrune(&controller());
+
+ // other_controller should now contain the 1 url: url1.
+
+ ASSERT_EQ(1, other_controller.entry_count());
+
+ ASSERT_EQ(0, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->url());
+
+ // Make sure session ids didn't change.
+ EXPECT_EQ(id.id(), controller().session_id().id());
+ EXPECT_EQ(other_id.id(), other_controller.session_id().id());
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the first selected and nothing in
+// the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune3) {
+ SessionID id(controller().session_id());
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller().GoBack();
+
+ scoped_ptr<TestTabContents> other_contents(CreateTestTabContents());
+ NavigationController& other_controller = other_contents->controller();
+ SessionID other_id(other_controller.session_id());
+ other_controller.LoadURL(url3, GURL(), PageTransition::TYPED);
+ other_controller.CopyStateFromAndPrune(&controller());
+
+ // other_controller should now contain 1 entry for url1, and a pending entry
+ // for url3.
+
+ ASSERT_EQ(1, other_controller.entry_count());
+
+ EXPECT_EQ(0, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->url());
+
+ // And there should be a pending entry for url3.
+ ASSERT_TRUE(other_controller.pending_entry());
+
+ EXPECT_EQ(url3, other_controller.pending_entry()->url());
+
+ // Make sure session ids didn't change.
+ EXPECT_EQ(id.id(), controller().session_id().id());
+ EXPECT_EQ(other_id.id(), other_controller.session_id().id());
+}
+
+// Tests that navigations initiated from the page (with the history object)
+// work as expected without navigation entries.
+TEST_F(NavigationControllerTest, HistoryNavigate) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ 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().pending_entry_index());
+ // 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().pending_entry_index());
+ // 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().pending_entry_index());
+ message = process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ EXPECT_TRUE(message == NULL);
+}
+
+// Test call to PruneAllButActive for the only entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForSingle) {
+ const GURL url1("http://foo1");
+ NavigateAndCommit(url1);
+ controller().PruneAllButActive();
+
+ EXPECT_EQ(-1, controller().pending_entry_index());
+ EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), url1);
+}
+
+// Test call to PruneAllButActive for last entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForLast) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller().GoBack();
+ controller().GoBack();
+ contents()->CommitPendingNavigation();
+
+ controller().PruneAllButActive();
+
+ EXPECT_EQ(-1, controller().pending_entry_index());
+ EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), url1);
+}
+
+// Test call to PruneAllButActive for intermediate entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForIntermediate) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller().GoBack();
+ contents()->CommitPendingNavigation();
+
+ controller().PruneAllButActive();
+
+ EXPECT_EQ(-1, controller().pending_entry_index());
+ EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), url2);
+}
+
+// Test call to PruneAllButActive for intermediate entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForPending) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller().GoBack();
+
+ controller().PruneAllButActive();
+
+ EXPECT_EQ(0, controller().pending_entry_index());
+}
+
+// Test call to PruneAllButActive for transient entry.
+TEST_F(NavigationControllerTest, PruneAllButActiveForTransient) {
+ const GURL url0("http://foo0");
+ const GURL url1("http://foo1");
+ const GURL transient_url("http://transient");
+
+ controller().LoadURL(url0, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(0, url0);
+ controller().LoadURL(url1, GURL(), PageTransition::TYPED);
+ rvh()->SendNavigate(1, url1);
+
+ // Adding a transient with no pending entry.
+ NavigationEntry* transient_entry = new NavigationEntry;
+ transient_entry->set_url(transient_url);
+ controller().AddTransientEntry(transient_entry);
+
+ controller().PruneAllButActive();
+
+ EXPECT_EQ(-1, controller().pending_entry_index());
+ EXPECT_EQ(-1, controller().pending_entry_index());
+ EXPECT_EQ(controller().GetTransientEntry()->url(), transient_url);
+}
+
+/* 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.
+
+// A basic test case. Navigates to a single url, and make sure the history
+// db matches.
+TEST_F(NavigationControllerHistoryTest, Basic) {
+ controller().LoadURL(url0, GURL(), PageTransition::LINK);
+ 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),
+ PageTransition::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) {
+ rvh()->SendNavigate(0, url0);
+ rvh()->SendNavigate(1, url1);
+ rvh()->SendNavigate(2, url2);
+
+ controller().GoBack();
+ 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),
+ PageTransition::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) {
+ rvh()->SendNavigate(0, url0);
+ rvh()->SendNavigate(1, url1);
+ rvh()->SendNavigate(2, url2);
+
+ controller().GoBack();
+ rvh()->SendNavigate(1, url1);
+
+ controller().GoBack();
+ rvh()->SendNavigate(0, url0);
+
+ 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),
+ PageTransition::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/tab_contents/navigation_entry.cc b/content/browser/tab_contents/navigation_entry.cc
new file mode 100644
index 0000000..147461b
--- /dev/null
+++ b/content/browser/tab_contents/navigation_entry.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2006-2008 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/tab_contents/navigation_entry.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/tab_contents/navigation_controller.h"
+#include "grit/app_resources.h"
+#include "net/base/net_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/text/text_elider.h"
+
+// 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 GetUniqueID() {
+ static int unique_id_counter = 0;
+ return ++unique_id_counter;
+}
+
+NavigationEntry::SSLStatus::SSLStatus()
+ : security_style_(SECURITY_STYLE_UNKNOWN),
+ cert_id_(0),
+ cert_status_(0),
+ security_bits_(-1),
+ connection_status_(0),
+ content_status_(NORMAL_CONTENT) {
+}
+
+NavigationEntry::FaviconStatus::FaviconStatus() : valid_(false) {
+ ResourceBundle &rb = ResourceBundle::GetSharedInstance();
+ bitmap_ = *rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
+}
+
+
+NavigationEntry::NavigationEntry()
+ : unique_id_(GetUniqueID()),
+ site_instance_(NULL),
+ page_type_(NORMAL_PAGE),
+ update_virtual_url_with_url_(false),
+ page_id_(-1),
+ transition_type_(PageTransition::LINK),
+ has_post_data_(false),
+ restore_type_(RESTORE_NONE) {
+}
+
+NavigationEntry::NavigationEntry(SiteInstance* instance,
+ int page_id,
+ const GURL& url,
+ const GURL& referrer,
+ const string16& title,
+ PageTransition::Type transition_type)
+ : unique_id_(GetUniqueID()),
+ site_instance_(instance),
+ page_type_(NORMAL_PAGE),
+ url_(url),
+ referrer_(referrer),
+ update_virtual_url_with_url_(false),
+ title_(title),
+ page_id_(page_id),
+ transition_type_(transition_type),
+ has_post_data_(false),
+ restore_type_(RESTORE_NONE) {
+}
+
+NavigationEntry::~NavigationEntry() {
+}
+
+void NavigationEntry::set_site_instance(SiteInstance* site_instance) {
+ site_instance_ = site_instance;
+}
+
+const string16& NavigationEntry::GetTitleForDisplay(
+ const std::string& languages) {
+ // 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;
+ std::wstring elided_title;
+ if (!virtual_url_.is_empty()) {
+ title = net::FormatUrl(virtual_url_, languages);
+ } else if (!url_.is_empty()) {
+ title = net::FormatUrl(url_, languages);
+ }
+ ui::ElideString(UTF16ToWideHack(title), chrome::kMaxTitleChars,
+ &elided_title);
+ cached_display_title_ = WideToUTF16Hack(elided_title);
+ return cached_display_title_;
+}
+
+bool NavigationEntry::IsViewSourceMode() const {
+ return virtual_url_.SchemeIs(chrome::kViewSourceScheme);
+}
diff --git a/content/browser/tab_contents/navigation_entry.h b/content/browser/tab_contents/navigation_entry.h
new file mode 100644
index 0000000..84db5ef2
--- /dev/null
+++ b/content/browser/tab_contents/navigation_entry.h
@@ -0,0 +1,429 @@
+// Copyright (c) 2010 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_TAB_CONTENTS_NAVIGATION_ENTRY_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_NAVIGATION_ENTRY_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "chrome/common/page_type.h"
+#include "chrome/common/page_transition_types.h"
+#include "chrome/common/security_style.h"
+#include "googleurl/src/gurl.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+class SiteInstance;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// NavigationEntry class
+//
+// A NavigationEntry is a data structure that captures all the information
+// required to recreate a browsing state. This includes some opaque binary
+// state as provided by the TabContents as well as some clear text title and
+// URL which is used for our user interface.
+//
+////////////////////////////////////////////////////////////////////////////////
+class NavigationEntry {
+ public:
+ // SSL -----------------------------------------------------------------------
+
+ // Collects the SSL information for this NavigationEntry.
+ class SSLStatus {
+ public:
+ // Flags used for the page security content status.
+ enum ContentStatusFlags {
+ // HTTP page, or HTTPS page with no insecure content.
+ NORMAL_CONTENT = 0,
+
+ // HTTPS page containing "displayed" HTTP resources (e.g. images, CSS).
+ DISPLAYED_INSECURE_CONTENT = 1 << 0,
+
+ // HTTPS page containing "executed" HTTP resources (i.e. script).
+ // Also currently used for HTTPS page containing broken-HTTPS resources;
+ // this is wrong and should be fixed (see comments in
+ // SSLPolicy::OnRequestStarted()).
+ RAN_INSECURE_CONTENT = 1 << 1,
+ };
+
+ SSLStatus();
+
+ bool Equals(const SSLStatus& status) const {
+ return security_style_ == status.security_style_ &&
+ cert_id_ == status.cert_id_ &&
+ cert_status_ == status.cert_status_ &&
+ security_bits_ == status.security_bits_ &&
+ content_status_ == status.content_status_;
+ }
+
+ void set_security_style(SecurityStyle security_style) {
+ security_style_ = security_style;
+ }
+ SecurityStyle security_style() const {
+ return security_style_;
+ }
+
+ void set_cert_id(int ssl_cert_id) {
+ cert_id_ = ssl_cert_id;
+ }
+ int cert_id() const {
+ return cert_id_;
+ }
+
+ void set_cert_status(int ssl_cert_status) {
+ cert_status_ = ssl_cert_status;
+ }
+ int cert_status() const {
+ return cert_status_;
+ }
+
+ void set_security_bits(int security_bits) {
+ security_bits_ = security_bits;
+ }
+ int security_bits() const {
+ return security_bits_;
+ }
+
+ void set_displayed_insecure_content() {
+ content_status_ |= DISPLAYED_INSECURE_CONTENT;
+ }
+ bool displayed_insecure_content() const {
+ return (content_status_ & DISPLAYED_INSECURE_CONTENT) != 0;
+ }
+
+ void set_ran_insecure_content() {
+ content_status_ |= RAN_INSECURE_CONTENT;
+ }
+ bool ran_insecure_content() const {
+ return (content_status_ & RAN_INSECURE_CONTENT) != 0;
+ }
+
+ void set_connection_status(int connection_status) {
+ connection_status_ = connection_status;
+ }
+ int connection_status() const {
+ return connection_status_;
+ }
+
+ // Raw accessors for all the content status flags. This contains a
+ // combination of any of the ContentStatusFlags defined above. It is used
+ // by some tests for checking and for certain copying. Use the per-status
+ // functions for normal usage.
+ void set_content_status(int content_status) {
+ content_status_ = content_status;
+ }
+ int content_status() const {
+ return content_status_;
+ }
+
+ private:
+ // See the accessors above for descriptions.
+ SecurityStyle security_style_;
+ int cert_id_;
+ int cert_status_;
+ int security_bits_;
+ int connection_status_;
+ int content_status_;
+
+ // Copy and assignment is explicitly allowed for this class.
+ };
+
+ // Favicon -------------------------------------------------------------------
+
+ // Collects the favicon related information for a NavigationEntry.
+ class FaviconStatus {
+ public:
+ FaviconStatus();
+
+ // Indicates whether we've gotten an official favicon for the page, or are
+ // just using the default favicon.
+ void set_is_valid(bool is_valid) {
+ valid_ = is_valid;
+ }
+ bool is_valid() const {
+ return valid_;
+ }
+
+ // The URL of the favicon which was used to load it off the web.
+ void set_url(const GURL& favicon_url) {
+ url_ = favicon_url;
+ }
+ const GURL& url() const {
+ return url_;
+ }
+
+ // The favicon bitmap for the page. If the favicon has not been explicitly
+ // set or it empty, it will return the default favicon. Note that this is
+ // loaded asynchronously, so even if the favicon URL is valid we may return
+ // the default favicon if we haven't gotten the data yet.
+ void set_bitmap(const SkBitmap& bitmap) {
+ bitmap_ = bitmap;
+ }
+ const SkBitmap& bitmap() const {
+ return bitmap_;
+ }
+
+ private:
+ // See the accessors above for descriptions.
+ bool valid_;
+ GURL url_;
+ SkBitmap bitmap_;
+
+ // Copy and assignment is explicitly allowed for this class.
+ };
+
+ // ---------------------------------------------------------------------------
+
+ NavigationEntry();
+ NavigationEntry(SiteInstance* instance,
+ int page_id,
+ const GURL& url,
+ const GURL& referrer,
+ const string16& title,
+ PageTransition::Type transition_type);
+ ~NavigationEntry();
+
+ // Page-related stuff --------------------------------------------------------
+
+ // A unique ID is preserved across commits and redirects, which means that
+ // sometimes a NavigationEntry's unique ID needs to be set (e.g. when
+ // creating a committed entry to correspond to a to-be-deleted pending entry,
+ // the pending entry's ID must be copied).
+ void set_unique_id(int unique_id) {
+ unique_id_ = unique_id;
+ }
+ int unique_id() const {
+ return 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(SiteInstance* site_instance);
+ SiteInstance* site_instance() const {
+ return site_instance_;
+ }
+
+ // The page type tells us if this entry is for an interstitial or error page.
+ // See the PageType enum above.
+ void set_page_type(PageType page_type) {
+ page_type_ = page_type;
+ }
+ PageType page_type() const {
+ return page_type_;
+ }
+
+ // The actual URL of the page. For some about pages, this may be a scary
+ // data: URL or something like that. Use virtual_url() below for showing to
+ // the user.
+ void set_url(const GURL& url) {
+ url_ = url;
+ cached_display_title_.clear();
+ }
+ const GURL& url() const {
+ return url_;
+ }
+
+ // The referring URL. Can be empty.
+ void set_referrer(const GURL& referrer) {
+ referrer_ = referrer;
+ }
+ const GURL& referrer() const {
+ return referrer_;
+ }
+
+ // The virtual URL, when nonempty, will override the actual URL of the page
+ // when we display it to the user. This allows us to have nice and friendly
+ // URLs that the user sees for things like about: URLs, but actually feed
+ // the renderer a data URL that results in the content loading.
+ //
+ // virtual_url() will return the URL to display to the user in all cases, so
+ // if there is no overridden display URL, it will return the actual one.
+ void set_virtual_url(const GURL& url) {
+ virtual_url_ = (url == url_) ? GURL() : url;
+ cached_display_title_.clear();
+ }
+ bool has_virtual_url() const {
+ return !virtual_url_.is_empty();
+ }
+ const GURL& virtual_url() const {
+ return virtual_url_.is_empty() ? url_ : virtual_url_;
+ }
+
+ 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;
+ }
+
+ // The title as set by the page. This will be empty if there is no title set.
+ // The caller is responsible for detecting when there is no title and
+ // displaying the appropriate "Untitled" label if this is being displayed to
+ // the user.
+ void set_title(const string16& title) {
+ title_ = title;
+ cached_display_title_.clear();
+ }
+ const string16& title() const {
+ return title_;
+ }
+
+ // The favicon data and tracking information. See FaviconStatus above.
+ const FaviconStatus& favicon() const {
+ return favicon_;
+ }
+ FaviconStatus& favicon() {
+ return favicon_;
+ }
+
+ // Content state is an opaque blob created by WebKit that represents the
+ // state of the page. This includes form entries and scroll position for each
+ // frame. We store it so that we can supply it back to WebKit to restore form
+ // state properly when the user goes back and forward.
+ //
+ // WARNING: This state is saved to the file and used to restore previous
+ // states. If the format is modified in the future, we should still be able to
+ // deal with older versions.
+ void set_content_state(const std::string& state) {
+ content_state_ = state;
+ }
+ const std::string& content_state() const {
+ return content_state_;
+ }
+
+ // Describes the current page that the tab represents. For web pages
+ // (TAB_CONTENTS_WEB) this is the ID that the renderer generated for the page
+ // and is how we can tell new versus renavigations.
+ void set_page_id(int page_id) {
+ page_id_ = page_id;
+ }
+ int32 page_id() const {
+ return page_id_;
+ }
+
+ // All the SSL flags and state. See SSLStatus above.
+ const SSLStatus& ssl() const {
+ return ssl_;
+ }
+ SSLStatus& ssl() {
+ return ssl_;
+ }
+
+ // Page-related helpers ------------------------------------------------------
+
+ // Returns the title to be displayed on the tab. This could be the title of
+ // the page if it is available or the URL. |languages| is the list of
+ // accpeted languages (e.g., prefs::kAcceptLanguages) or empty if proper
+ // URL formatting isn't needed (e.g., unit tests).
+ const string16& GetTitleForDisplay(const std::string& languages);
+
+ // Returns true if the current tab is in view source mode. This will be false
+ // if there is no navigation.
+ bool IsViewSourceMode() const;
+
+ // Tracking stuff ------------------------------------------------------------
+
+ // The transition type indicates what the user did to move to this page from
+ // the previous page.
+ void set_transition_type(PageTransition::Type transition_type) {
+ transition_type_ = transition_type;
+ }
+ PageTransition::Type transition_type() const {
+ return transition_type_;
+ }
+
+ // The user typed URL was the URL that the user initiated the navigation
+ // with, regardless of any redirects. This is used to generate keywords, for
+ // example, based on "what the user thinks the site is called" rather than
+ // what it's actually called. For example, if the user types "foo.com", that
+ // may redirect somewhere arbitrary like "bar.com/foo", and we want to use
+ // the name that the user things of the site as having.
+ //
+ // This URL will be is_empty() if the URL was navigated to some other way.
+ // Callers should fall back on using the regular or display URL in this case.
+ void set_user_typed_url(const GURL& user_typed_url) {
+ user_typed_url_ = user_typed_url;
+ }
+ const GURL& user_typed_url() const {
+ return user_typed_url_;
+ }
+
+ // Post data is form data that was posted to get to this page. The data will
+ // have to be reposted to reload the page properly. This flag indicates
+ // whether the page had post data.
+ //
+ // The actual post data is stored in the content_state and is extracted by
+ // WebKit to actually make the request.
+ void set_has_post_data(bool has_post_data) {
+ has_post_data_ = has_post_data;
+ }
+ bool has_post_data() const {
+ return has_post_data_;
+ }
+
+ // 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_;
+ }
+
+ 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<SiteInstance> site_instance_;
+ PageType page_type_;
+ GURL url_;
+ GURL referrer_;
+ GURL virtual_url_;
+ bool update_virtual_url_with_url_;
+ string16 title_;
+ FaviconStatus favicon_;
+ std::string content_state_;
+ int32 page_id_;
+ SSLStatus ssl_;
+ PageTransition::Type transition_type_;
+ GURL user_typed_url_;
+ bool has_post_data_;
+ RestoreType restore_type_;
+
+ // This is a cached version of the result of GetTitleForDisplay. It prevents
+ // us from having to do URL formatting on the URL evey time the title is
+ // displayed. When the URL, virtual URL, or title is set, this should be
+ // cleared to force a refresh.
+ string16 cached_display_title_;
+
+ // Copy and assignment is explicitly allowed for this class.
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_NAVIGATION_ENTRY_H_
diff --git a/content/browser/tab_contents/navigation_entry_unittest.cc b/content/browser/tab_contents/navigation_entry_unittest.cc
new file mode 100644
index 0000000..014817c
--- /dev/null
+++ b/content/browser/tab_contents/navigation_entry_unittest.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2010 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 "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class NavigationEntryTest : public testing::Test {
+ public:
+ NavigationEntryTest() : instance_(NULL) {
+ }
+
+ virtual void SetUp() {
+ entry1_.reset(new NavigationEntry);
+
+ instance_ = SiteInstance::CreateSiteInstance(NULL);
+ entry2_.reset(new NavigationEntry(instance_, 3,
+ GURL("test:url"),
+ GURL("from"),
+ ASCIIToUTF16("title"),
+ PageTransition::TYPED));
+ }
+
+ virtual void TearDown() {
+ }
+
+ protected:
+ scoped_ptr<NavigationEntry> entry1_;
+ scoped_ptr<NavigationEntry> entry2_;
+ // SiteInstances are deleted when their NavigationEntries are gone.
+ SiteInstance* instance_;
+};
+
+// Test unique ID accessors
+TEST_F(NavigationEntryTest, NavigationEntryUniqueIDs) {
+ // Two entries should have different IDs by default
+ EXPECT_NE(entry1_.get()->unique_id(), entry2_.get()->unique_id());
+
+ // Can set an entry to have the same ID as another
+ entry2_.get()->set_unique_id(entry1_.get()->unique_id());
+ EXPECT_EQ(entry1_.get()->unique_id(), entry2_.get()->unique_id());
+}
+
+// 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()->url());
+ EXPECT_EQ(GURL(), entry1_.get()->virtual_url());
+ EXPECT_TRUE(entry1_.get()->GetTitleForDisplay("").empty());
+
+ // Setting URL affects virtual_url and GetTitleForDisplay
+ entry1_.get()->set_url(GURL("http://www.google.com"));
+ EXPECT_EQ(GURL("http://www.google.com"), entry1_.get()->url());
+ EXPECT_EQ(GURL("http://www.google.com"), entry1_.get()->virtual_url());
+ EXPECT_EQ(ASCIIToUTF16("www.google.com"),
+ entry1_.get()->GetTitleForDisplay(""));
+
+ // Title affects GetTitleForDisplay
+ entry1_.get()->set_title(ASCIIToUTF16("Google"));
+ EXPECT_EQ(ASCIIToUTF16("Google"), entry1_.get()->GetTitleForDisplay(""));
+
+ // Setting virtual_url doesn't affect URL
+ entry2_.get()->set_virtual_url(GURL("display:url"));
+ EXPECT_TRUE(entry2_.get()->has_virtual_url());
+ EXPECT_EQ(GURL("test:url"), entry2_.get()->url());
+ EXPECT_EQ(GURL("display:url"), entry2_.get()->virtual_url());
+
+ // 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()->user_typed_url());
+ EXPECT_EQ(GURL(), entry2_.get()->user_typed_url());
+ entry2_.get()->set_user_typed_url(GURL("typedurl"));
+ EXPECT_EQ(GURL("typedurl"), entry2_.get()->user_typed_url());
+}
+
+// Test Favicon inner class
+TEST_F(NavigationEntryTest, NavigationEntryFavicons) {
+ EXPECT_EQ(GURL(), entry1_.get()->favicon().url());
+ entry1_.get()->favicon().set_url(GURL("icon"));
+ EXPECT_EQ(GURL("icon"), entry1_.get()->favicon().url());
+
+ // Validity not affected by setting URL
+ EXPECT_FALSE(entry1_.get()->favicon().is_valid());
+ entry1_.get()->favicon().set_is_valid(true);
+ EXPECT_TRUE(entry1_.get()->favicon().is_valid());
+}
+
+// Test SSLStatus inner class
+TEST_F(NavigationEntryTest, NavigationEntrySSLStatus) {
+ // Default (unknown)
+ EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry1_.get()->ssl().security_style());
+ EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry2_.get()->ssl().security_style());
+ EXPECT_EQ(0, entry1_.get()->ssl().cert_id());
+ EXPECT_EQ(0, entry1_.get()->ssl().cert_status());
+ EXPECT_EQ(-1, entry1_.get()->ssl().security_bits());
+ EXPECT_FALSE(entry1_.get()->ssl().displayed_insecure_content());
+ EXPECT_FALSE(entry1_.get()->ssl().ran_insecure_content());
+
+ // Change from the defaults
+ entry2_.get()->ssl().set_security_style(SECURITY_STYLE_AUTHENTICATED);
+ entry2_.get()->ssl().set_cert_id(4);
+ entry2_.get()->ssl().set_cert_status(1);
+ entry2_.get()->ssl().set_security_bits(0);
+ entry2_.get()->ssl().set_displayed_insecure_content();
+ EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED,
+ entry2_.get()->ssl().security_style());
+ EXPECT_EQ(4, entry2_.get()->ssl().cert_id());
+ EXPECT_EQ(1, entry2_.get()->ssl().cert_status());
+ EXPECT_EQ(0, entry2_.get()->ssl().security_bits());
+ EXPECT_TRUE(entry2_.get()->ssl().displayed_insecure_content());
+
+ entry2_.get()->ssl().set_security_style(SECURITY_STYLE_AUTHENTICATION_BROKEN);
+ entry2_.get()->ssl().set_ran_insecure_content();
+ EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN,
+ entry2_.get()->ssl().security_style());
+ EXPECT_TRUE(entry2_.get()->ssl().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(NORMAL_PAGE, entry1_.get()->page_type());
+ EXPECT_EQ(NORMAL_PAGE, entry2_.get()->page_type());
+ entry2_.get()->set_page_type(INTERSTITIAL_PAGE);
+ EXPECT_EQ(INTERSTITIAL_PAGE, entry2_.get()->page_type());
+
+ // Referrer
+ EXPECT_EQ(GURL(), entry1_.get()->referrer());
+ EXPECT_EQ(GURL("from"), entry2_.get()->referrer());
+ entry2_.get()->set_referrer(GURL("from2"));
+ EXPECT_EQ(GURL("from2"), entry2_.get()->referrer());
+
+ // Title
+ EXPECT_EQ(string16(), entry1_.get()->title());
+ EXPECT_EQ(ASCIIToUTF16("title"), entry2_.get()->title());
+ entry2_.get()->set_title(ASCIIToUTF16("title2"));
+ EXPECT_EQ(ASCIIToUTF16("title2"), entry2_.get()->title());
+
+ // State
+ EXPECT_EQ(std::string(), entry1_.get()->content_state());
+ EXPECT_EQ(std::string(), entry2_.get()->content_state());
+ entry2_.get()->set_content_state("state");
+ EXPECT_EQ("state", entry2_.get()->content_state());
+
+ // Page ID
+ EXPECT_EQ(-1, entry1_.get()->page_id());
+ EXPECT_EQ(3, entry2_.get()->page_id());
+ entry2_.get()->set_page_id(2);
+ EXPECT_EQ(2, entry2_.get()->page_id());
+
+ // Transition type
+ EXPECT_EQ(PageTransition::LINK, entry1_.get()->transition_type());
+ EXPECT_EQ(PageTransition::TYPED, entry2_.get()->transition_type());
+ entry2_.get()->set_transition_type(PageTransition::RELOAD);
+ EXPECT_EQ(PageTransition::RELOAD, entry2_.get()->transition_type());
+
+ // Post Data
+ EXPECT_FALSE(entry1_.get()->has_post_data());
+ EXPECT_FALSE(entry2_.get()->has_post_data());
+ entry2_.get()->set_has_post_data(true);
+ EXPECT_TRUE(entry2_.get()->has_post_data());
+
+ // Restored
+ EXPECT_EQ(NavigationEntry::RESTORE_NONE, entry1_->restore_type());
+ EXPECT_EQ(NavigationEntry::RESTORE_NONE, entry2_->restore_type());
+ entry2_->set_restore_type(NavigationEntry::RESTORE_LAST_SESSION);
+ EXPECT_EQ(NavigationEntry::RESTORE_LAST_SESSION, entry2_->restore_type());
+}
diff --git a/content/browser/tab_contents/page_navigator.h b/content/browser/tab_contents/page_navigator.h
new file mode 100644
index 0000000..e411c24
--- /dev/null
+++ b/content/browser/tab_contents/page_navigator.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2006-2008 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.
+
+// PageNavigator defines an interface that can be used to express the user's
+// intention to navigate to a particular URL. The implementing class should
+// perform the navigation.
+
+#ifndef CONTENT_BROWSER_TAB_CONTENTS_PAGE_NAVIGATOR_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_PAGE_NAVIGATOR_H_
+#pragma once
+
+#include "chrome/common/page_transition_types.h"
+#include "webkit/glue/window_open_disposition.h"
+
+class GURL;
+
+class PageNavigator {
+ public:
+ // Opens a URL with the given disposition. The transition specifies how this
+ // navigation should be recorded in the history system (for example, typed).
+ virtual void OpenURL(const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition) = 0;
+
+ protected:
+ virtual ~PageNavigator() {}
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_PAGE_NAVIGATOR_H_
diff --git a/content/browser/tab_contents/provisional_load_details.cc b/content/browser/tab_contents/provisional_load_details.cc
new file mode 100644
index 0000000..d85d846
--- /dev/null
+++ b/content/browser/tab_contents/provisional_load_details.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2006-2008 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/tab_contents/provisional_load_details.h"
+
+#include "chrome/browser/ssl/ssl_manager.h"
+
+ProvisionalLoadDetails::ProvisionalLoadDetails(bool is_main_frame,
+ bool is_in_page_navigation,
+ const GURL& url,
+ const std::string& security_info,
+ bool is_content_filtered,
+ bool is_error_page,
+ int64 frame_id)
+ : error_code_(net::OK),
+ transition_type_(PageTransition::LINK),
+ url_(url),
+ is_main_frame_(is_main_frame),
+ is_in_page_navigation_(is_in_page_navigation),
+ ssl_cert_id_(0),
+ ssl_cert_status_(0),
+ ssl_security_bits_(-1),
+ ssl_connection_status_(0),
+ is_content_filtered_(is_content_filtered),
+ is_error_page_(is_error_page),
+ frame_id_(frame_id) {
+ SSLManager::DeserializeSecurityInfo(security_info,
+ &ssl_cert_id_,
+ &ssl_cert_status_,
+ &ssl_security_bits_,
+ &ssl_connection_status_);
+}
diff --git a/content/browser/tab_contents/provisional_load_details.h b/content/browser/tab_contents/provisional_load_details.h
new file mode 100644
index 0000000..54fe887
--- /dev/null
+++ b/content/browser/tab_contents/provisional_load_details.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2006-2008 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_TAB_CONTENTS_PROVISIONAL_LOAD_DETAILS_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_PROVISIONAL_LOAD_DETAILS_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/common/page_transition_types.h"
+#include "googleurl/src/gurl.h"
+
+// This class captures some of the information associated to the provisional
+// load of a frame. It is provided as Details with the
+// NOTIFY_FRAME_PROVISIONAL_LOAD_START, NOTIFY_FRAME_PROVISIONAL_LOAD_COMMITTED
+// and NOTIFY_FAIL_PROVISIONAL_LOAD_WITH_ERROR notifications
+// (see notification_types.h).
+
+// TODO(brettw) this mostly duplicates
+// NavigationController::LoadCommittedDetails, it would be nice to unify these
+// somehow.
+class ProvisionalLoadDetails {
+ public:
+ ProvisionalLoadDetails(bool main_frame,
+ bool in_page_navigation,
+ const GURL& url,
+ const std::string& security_info,
+ bool is_filtered,
+ bool is_error_page,
+ int64 frame_id);
+ virtual ~ProvisionalLoadDetails() { }
+
+ void set_error_code(int error_code) { error_code_ = error_code; }
+ int error_code() const { return error_code_; }
+
+ void set_transition_type(PageTransition::Type transition_type) {
+ transition_type_ = transition_type;
+ }
+ PageTransition::Type transition_type() const {
+ return transition_type_;
+ }
+
+ const GURL& url() const { return url_; }
+
+ bool main_frame() const { return is_main_frame_; }
+
+ bool in_page_navigation() const { return is_in_page_navigation_; }
+
+ int ssl_cert_id() const { return ssl_cert_id_; }
+
+ int ssl_cert_status() const { return ssl_cert_status_; }
+
+ int ssl_security_bits() const { return ssl_security_bits_; }
+
+ int ssl_connection_status() const { return ssl_connection_status_; }
+
+ bool is_content_filtered() const { return is_content_filtered_; }
+
+ bool is_error_page() const { return is_error_page_; }
+
+ int64 frame_id() const { return frame_id_; }
+
+ private:
+ int error_code_;
+ PageTransition::Type transition_type_;
+ GURL url_;
+ bool is_main_frame_;
+ bool is_in_page_navigation_;
+ int ssl_cert_id_;
+ int ssl_cert_status_;
+ int ssl_security_bits_;
+ int ssl_connection_status_;
+ bool is_content_filtered_;
+ bool is_error_page_;
+ int64 frame_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProvisionalLoadDetails);
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_PROVISIONAL_LOAD_DETAILS_H_
diff --git a/content/browser/tab_contents/render_view_host_manager.cc b/content/browser/tab_contents/render_view_host_manager.cc
new file mode 100644
index 0000000..556c3b7
--- /dev/null
+++ b/content/browser/tab_contents/render_view_host_manager.cc
@@ -0,0 +1,729 @@
+// Copyright (c) 2011 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/tab_contents/render_view_host_manager.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#include "chrome/browser/renderer_host/render_view_host_factory.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/webui/web_ui.h"
+#include "chrome/browser/webui/web_ui_factory.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/render_messages_params.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/tab_contents/navigation_controller.h"
+#include "content/browser/tab_contents/navigation_entry.h"
+#include "content/browser/tab_contents/tab_contents_view.h"
+
+namespace base {
+class WaitableEvent;
+}
+
+RenderViewHostManager::RenderViewHostManager(
+ RenderViewHostDelegate* render_view_delegate,
+ Delegate* delegate)
+ : delegate_(delegate),
+ cross_navigation_pending_(false),
+ render_view_delegate_(render_view_delegate),
+ render_view_host_(NULL),
+ pending_render_view_host_(NULL),
+ interstitial_page_(NULL) {
+}
+
+RenderViewHostManager::~RenderViewHostManager() {
+ if (pending_render_view_host_)
+ CancelPending();
+
+ // We should always have a main RenderViewHost.
+ RenderViewHost* render_view_host = render_view_host_;
+ render_view_host_ = NULL;
+ render_view_host->Shutdown();
+}
+
+void RenderViewHostManager::Init(Profile* profile,
+ SiteInstance* site_instance,
+ int routing_id) {
+ // Create a RenderViewHost, once we have an instance. It is important to
+ // immediately give this SiteInstance to a RenderViewHost so that it is
+ // ref counted.
+ if (!site_instance)
+ site_instance = SiteInstance::CreateSiteInstance(profile);
+ render_view_host_ = RenderViewHostFactory::Create(
+ site_instance, render_view_delegate_, routing_id, delegate_->
+ GetControllerForRenderManager().session_storage_namespace());
+ NotificationService::current()->Notify(
+ NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB,
+ Source<RenderViewHostManager>(this),
+ Details<RenderViewHost>(render_view_host_));
+}
+
+RenderWidgetHostView* RenderViewHostManager::GetRenderWidgetHostView() const {
+ if (!render_view_host_)
+ return NULL;
+ return render_view_host_->view();
+}
+
+RenderViewHost* RenderViewHostManager::Navigate(const NavigationEntry& entry) {
+ // Create a pending RenderViewHost. It will give us the one we should use
+ RenderViewHost* dest_render_view_host = UpdateRendererStateForNavigate(entry);
+ if (!dest_render_view_host)
+ return NULL; // We weren't able to create a pending render view host.
+
+ // If the current render_view_host_ isn't live, we should create it so
+ // that we don't show a sad tab while the dest_render_view_host fetches
+ // its first page. (Bug 1145340)
+ if (dest_render_view_host != render_view_host_ &&
+ !render_view_host_->IsRenderViewLive()) {
+ // Note: we don't call InitRenderView here because we are navigating away
+ // soon anyway, and we don't have the NavigationEntry for this host.
+ delegate_->CreateRenderViewForRenderManager(render_view_host_);
+ }
+
+ // If the renderer crashed, then try to create a new one to satisfy this
+ // navigation request.
+ if (!dest_render_view_host->IsRenderViewLive()) {
+ if (!InitRenderView(dest_render_view_host, entry))
+ return NULL;
+
+ // Now that we've created a new renderer, be sure to hide it if it isn't
+ // our primary one. Otherwise, we might crash if we try to call Show()
+ // on it later.
+ if (dest_render_view_host != render_view_host_ &&
+ dest_render_view_host->view()) {
+ dest_render_view_host->view()->Hide();
+ } else {
+ // This is our primary renderer, notify here as we won't be calling
+ // CommitPending (which does the notify).
+ RenderViewHostSwitchedDetails details;
+ details.new_host = render_view_host_;
+ details.old_host = NULL;
+ NotificationService::current()->Notify(
+ NotificationType::RENDER_VIEW_HOST_CHANGED,
+ Source<NavigationController>(
+ &delegate_->GetControllerForRenderManager()),
+ Details<RenderViewHostSwitchedDetails>(&details));
+ }
+ }
+
+ return dest_render_view_host;
+}
+
+void RenderViewHostManager::Stop() {
+ render_view_host_->Stop();
+
+ // If we are cross-navigating, we should stop the pending renderers. This
+ // will lead to a DidFailProvisionalLoad, which will properly destroy them.
+ if (cross_navigation_pending_)
+ pending_render_view_host_->Stop();
+}
+
+void RenderViewHostManager::SetIsLoading(bool is_loading) {
+ render_view_host_->SetIsLoading(is_loading);
+ if (pending_render_view_host_)
+ pending_render_view_host_->SetIsLoading(is_loading);
+}
+
+bool RenderViewHostManager::ShouldCloseTabOnUnresponsiveRenderer() {
+ if (!cross_navigation_pending_)
+ return true;
+
+ // If the tab becomes unresponsive during unload while doing a
+ // cross-site navigation, proceed with the navigation. (This assumes that
+ // the pending RenderViewHost is still responsive.)
+ int pending_request_id = pending_render_view_host_->GetPendingRequestId();
+ if (pending_request_id == -1) {
+ // Haven't gotten around to starting the request, because we're still
+ // waiting for the beforeunload handler to finish. We'll pretend that it
+ // did finish, to let the navigation proceed. Note that there's a danger
+ // that the beforeunload handler will later finish and possibly return
+ // false (meaning the navigation should not proceed), but we'll ignore it
+ // in this case because it took too long.
+ if (pending_render_view_host_->are_navigations_suspended())
+ pending_render_view_host_->SetNavigationsSuspended(false);
+ } else {
+ // The request has been started and paused, while we're waiting for the
+ // unload handler to finish. We'll pretend that it did, by notifying the
+ // IO thread to let the response continue. The pending renderer will then
+ // be swapped in as part of the usual DidNavigate logic. (If the unload
+ // handler later finishes, this call will be ignored because the state in
+ // CrossSiteResourceHandler will already be cleaned up.)
+ ViewMsg_ClosePage_Params params;
+ params.closing_process_id =
+ render_view_host_->process()->id();
+ params.closing_route_id = render_view_host_->routing_id();
+ params.for_cross_site_transition = true;
+ params.new_render_process_host_id =
+ pending_render_view_host_->process()->id();
+ params.new_request_id = pending_request_id;
+ current_host()->process()->CrossSiteClosePageACK(params);
+ }
+ return false;
+}
+
+void RenderViewHostManager::DidNavigateMainFrame(
+ RenderViewHost* render_view_host) {
+ if (!cross_navigation_pending_) {
+ DCHECK(!pending_render_view_host_);
+
+ // We should only hear this from our current renderer.
+ DCHECK(render_view_host == render_view_host_);
+
+ // Even when there is no pending RVH, there may be a pending Web UI.
+ if (pending_web_ui_.get())
+ CommitPending();
+ return;
+ }
+
+ if (render_view_host == pending_render_view_host_) {
+ // The pending cross-site navigation completed, so show the renderer.
+ CommitPending();
+ cross_navigation_pending_ = false;
+ } else if (render_view_host == render_view_host_) {
+ // A navigation in the original page has taken place. Cancel the pending
+ // one.
+ CancelPending();
+ cross_navigation_pending_ = false;
+ } else {
+ // No one else should be sending us DidNavigate in this state.
+ DCHECK(false);
+ }
+}
+
+void RenderViewHostManager::SetWebUIPostCommit(WebUI* web_ui) {
+ DCHECK(!web_ui_.get());
+ web_ui_.reset(web_ui);
+}
+
+void RenderViewHostManager::RendererAbortedProvisionalLoad(
+ RenderViewHost* render_view_host) {
+ // We used to cancel the pending renderer here for cross-site downloads.
+ // However, it's not safe to do that because the download logic repeatedly
+ // looks for this TabContents based on a render view ID. Instead, we just
+ // leave the pending renderer around until the next navigation event
+ // (Navigate, DidNavigate, etc), which will clean it up properly.
+ // TODO(creis): All of this will go away when we move the cross-site logic
+ // to ResourceDispatcherHost, so that we intercept responses rather than
+ // navigation events. (That's necessary to support onunload anyway.) Once
+ // we've made that change, we won't create a pending renderer until we know
+ // the response is not a download.
+}
+
+void RenderViewHostManager::ShouldClosePage(bool for_cross_site_transition,
+ bool proceed) {
+ if (for_cross_site_transition) {
+ // Ignore if we're not in a cross-site navigation.
+ if (!cross_navigation_pending_)
+ return;
+
+ if (proceed) {
+ // Ok to unload the current page, so proceed with the cross-site
+ // navigation. Note that if navigations are not currently suspended, it
+ // might be because the renderer was deemed unresponsive and this call was
+ // already made by ShouldCloseTabOnUnresponsiveRenderer. In that case, it
+ // is ok to do nothing here.
+ if (pending_render_view_host_ &&
+ pending_render_view_host_->are_navigations_suspended())
+ pending_render_view_host_->SetNavigationsSuspended(false);
+ } else {
+ // Current page says to cancel.
+ CancelPending();
+ cross_navigation_pending_ = false;
+ }
+ } else {
+ // Non-cross site transition means closing the entire tab.
+ bool proceed_to_fire_unload;
+ delegate_->BeforeUnloadFiredFromRenderManager(proceed,
+ &proceed_to_fire_unload);
+
+ if (proceed_to_fire_unload) {
+ // This is not a cross-site navigation, the tab is being closed.
+ render_view_host_->ClosePage(false, -1, -1);
+ }
+ }
+}
+
+void RenderViewHostManager::OnCrossSiteResponse(int new_render_process_host_id,
+ int new_request_id) {
+ // Should only see this while we have a pending renderer.
+ if (!cross_navigation_pending_)
+ return;
+ DCHECK(pending_render_view_host_);
+
+ // Tell the old renderer to run its onunload handler. When it finishes, it
+ // will send a ClosePage_ACK to the ResourceDispatcherHost with the given
+ // IDs (of the pending RVH's request), allowing the pending RVH's response to
+ // resume.
+ render_view_host_->ClosePage(true,
+ new_render_process_host_id, new_request_id);
+
+ // ResourceDispatcherHost has told us to run the onunload handler, which
+ // means it is not a download or unsafe page, and we are going to perform the
+ // navigation. Thus, we no longer need to remember that the RenderViewHost
+ // is part of a pending cross-site request.
+ pending_render_view_host_->SetHasPendingCrossSiteRequest(false,
+ new_request_id);
+}
+
+void RenderViewHostManager::OnCrossSiteNavigationCanceled() {
+ DCHECK(cross_navigation_pending_);
+ cross_navigation_pending_ = false;
+ if (pending_render_view_host_)
+ CancelPending();
+}
+
+bool RenderViewHostManager::ShouldTransitionCrossSite() {
+ // True if we are using process-per-site-instance (default) or
+ // process-per-site (kProcessPerSite).
+ return !CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerTab);
+}
+
+bool RenderViewHostManager::ShouldSwapProcessesForNavigation(
+ const NavigationEntry* cur_entry,
+ const NavigationEntry* new_entry) const {
+ DCHECK(new_entry);
+
+ // Check for reasons to swap processes even if we are in a process model that
+ // doesn't usually swap (e.g., process-per-tab).
+
+ // For security, we should transition between processes when one is a Web UI
+ // page and one isn't. If there's no cur_entry, check the current RVH's
+ // site, which might already be committed to a Web UI URL (such as the NTP).
+ const GURL& current_url = (cur_entry) ? cur_entry->url() :
+ render_view_host_->site_instance()->site();
+ Profile* profile = delegate_->GetControllerForRenderManager().profile();
+ if (WebUIFactory::UseWebUIForURL(profile, current_url)) {
+ // Force swap if it's not an acceptable URL for Web UI.
+ if (!WebUIFactory::IsURLAcceptableForWebUI(profile, new_entry->url()))
+ return true;
+ } else {
+ // Force swap if it's a Web UI URL.
+ if (WebUIFactory::UseWebUIForURL(profile, new_entry->url()))
+ return true;
+ }
+
+ if (!cur_entry) {
+ // Always choose a new process when navigating to extension URLs. The
+ // process grouping logic will combine all of a given extension's pages
+ // into the same process.
+ if (new_entry->url().SchemeIs(chrome::kExtensionScheme))
+ return true;
+
+ return false;
+ }
+
+ // We can't switch a RenderView between view source and non-view source mode
+ // without screwing up the session history sometimes (when navigating between
+ // "view-source:http://foo.com/" and "http://foo.com/", WebKit doesn't treat
+ // it as a new navigation). So require a view switch.
+ if (cur_entry->IsViewSourceMode() != new_entry->IsViewSourceMode())
+ return true;
+
+ // Also, we must switch if one is an extension and the other is not the exact
+ // same extension.
+ if (cur_entry->url().SchemeIs(chrome::kExtensionScheme) ||
+ new_entry->url().SchemeIs(chrome::kExtensionScheme)) {
+ if (cur_entry->url().GetOrigin() != new_entry->url().GetOrigin())
+ return true;
+ }
+
+ return false;
+}
+
+SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry(
+ const NavigationEntry& entry,
+ SiteInstance* curr_instance) {
+ // NOTE: This is only called when ShouldTransitionCrossSite is true.
+
+ // If the entry has an instance already, we should use it.
+ if (entry.site_instance())
+ return entry.site_instance();
+
+ // (UGLY) HEURISTIC, process-per-site only:
+ //
+ // If this navigation is generated, then it probably corresponds to a search
+ // query. Given that search results typically lead to users navigating to
+ // other sites, we don't really want to use the search engine hostname to
+ // determine the site instance for this navigation.
+ //
+ // NOTE: This can be removed once we have a way to transition between
+ // RenderViews in response to a link click.
+ //
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerSite) &&
+ entry.transition_type() == PageTransition::GENERATED)
+ return curr_instance;
+
+ const GURL& dest_url = entry.url();
+
+ // If we haven't used our SiteInstance (and thus RVH) yet, then we can use it
+ // for this entry. We won't commit the SiteInstance to this site until the
+ // navigation commits (in DidNavigate), unless the navigation entry was
+ // restored or it's a Web UI as described below.
+ if (!curr_instance->has_site()) {
+ // If we've already created a SiteInstance for our destination, we don't
+ // want to use this unused SiteInstance; use the existing one. (We don't
+ // do this check if the curr_instance has a site, because for now, we want
+ // to compare against the current URL and not the SiteInstance's site. In
+ // this case, there is no current URL, so comparing against the site is ok.
+ // See additional comments below.)
+ if (curr_instance->HasRelatedSiteInstance(dest_url)) {
+ return curr_instance->GetRelatedSiteInstance(dest_url);
+ } else {
+ // Normally the "site" on the SiteInstance is set lazily when the load
+ // actually commits. This is to support better process sharing in case
+ // the site redirects to some other site: we want to use the destination
+ // site in the site instance.
+ //
+ // In the case of session restore, as it loads all the pages immediately
+ // we need to set the site first, otherwise after a restore none of the
+ // pages would share renderers.
+ //
+ // For Web UI (this mostly comes up for the new tab page), the
+ // SiteInstance has special meaning: we never want to reassign the
+ // process. If you navigate to another site before the Web UI commits,
+ // we still want to create a new process rather than re-using the
+ // existing Web UI process.
+ if (entry.restore_type() != NavigationEntry::RESTORE_NONE ||
+ WebUIFactory::HasWebUIScheme(dest_url))
+ curr_instance->SetSite(dest_url);
+ return curr_instance;
+ }
+ }
+
+ // Otherwise, only create a new SiteInstance for cross-site navigation.
+
+ // TODO(creis): Once we intercept links and script-based navigations, we
+ // will be able to enforce that all entries in a SiteInstance actually have
+ // the same site, and it will be safe to compare the URL against the
+ // SiteInstance's site, as follows:
+ // const GURL& current_url = curr_instance->site();
+ // For now, though, we're in a hybrid model where you only switch
+ // SiteInstances if you type in a cross-site URL. This means we have to
+ // compare the entry's URL to the last committed entry's URL.
+ NavigationController& controller = delegate_->GetControllerForRenderManager();
+ NavigationEntry* curr_entry = controller.GetLastCommittedEntry();
+ if (interstitial_page_) {
+ // The interstitial is currently the last committed entry, but we want to
+ // compare against the last non-interstitial entry.
+ curr_entry = controller.GetEntryAtOffset(-1);
+ }
+ // If there is no last non-interstitial entry (and curr_instance already
+ // has a site), then we must have been opened from another tab. We want
+ // to compare against the URL of the page that opened us, but we can't
+ // get to it directly. The best we can do is check against the site of
+ // the SiteInstance. This will be correct when we intercept links and
+ // script-based navigations, but for now, it could place some pages in a
+ // new process unnecessarily. We should only hit this case if a page tries
+ // to open a new tab to an interstitial-inducing URL, and then navigates
+ // the page to a different same-site URL. (This seems very unlikely in
+ // practice.)
+ const GURL& current_url = (curr_entry) ? curr_entry->url() :
+ curr_instance->site();
+ Profile* profile = controller.profile();
+
+ if (SiteInstance::IsSameWebSite(profile, current_url, dest_url)) {
+ return curr_instance;
+ } else if (ShouldSwapProcessesForNavigation(curr_entry, &entry)) {
+ // When we're swapping, we need to force the site instance AND browsing
+ // instance to be different ones. This addresses special cases where we use
+ // a single BrowsingInstance for all pages of a certain type (e.g., New Tab
+ // Pages), keeping them in the same process. When you navigate away from
+ // that page, we want to explicity ignore that BrowsingInstance and group
+ // this page into the appropriate SiteInstance for its URL.
+ return SiteInstance::CreateSiteInstanceForURL(profile, dest_url);
+ } else {
+ // Start the new renderer in a new SiteInstance, but in the current
+ // BrowsingInstance. It is important to immediately give this new
+ // SiteInstance to a RenderViewHost (if it is different than our current
+ // SiteInstance), so that it is ref counted. This will happen in
+ // CreatePendingRenderView.
+ return curr_instance->GetRelatedSiteInstance(dest_url);
+ }
+}
+
+bool RenderViewHostManager::CreatePendingRenderView(
+ const NavigationEntry& entry, SiteInstance* instance) {
+ NavigationEntry* curr_entry =
+ delegate_->GetControllerForRenderManager().GetLastCommittedEntry();
+ if (curr_entry) {
+ DCHECK(!curr_entry->content_state().empty());
+ // TODO(creis): Should send a message to the RenderView to let it know
+ // we're about to switch away, so that it sends an UpdateState message.
+ }
+
+ pending_render_view_host_ = RenderViewHostFactory::Create(
+ instance, render_view_delegate_, MSG_ROUTING_NONE, delegate_->
+ GetControllerForRenderManager().session_storage_namespace());
+ NotificationService::current()->Notify(
+ NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB,
+ Source<RenderViewHostManager>(this),
+ Details<RenderViewHost>(pending_render_view_host_));
+
+ bool success = InitRenderView(pending_render_view_host_, entry);
+ if (success) {
+ // Don't show the view until we get a DidNavigate from it.
+ pending_render_view_host_->view()->Hide();
+ } else {
+ CancelPending();
+ }
+ return success;
+}
+
+bool RenderViewHostManager::InitRenderView(RenderViewHost* render_view_host,
+ const NavigationEntry& entry) {
+ // If the pending navigation is to a WebUI, tell the RenderView about any
+ // bindings it will need enabled.
+ if (pending_web_ui_.get())
+ render_view_host->AllowBindings(pending_web_ui_->bindings());
+
+ // Tell the RenderView whether it will be used for an extension process.
+ Profile* profile = delegate_->GetControllerForRenderManager().profile();
+ bool is_extension_process = profile->GetExtensionService() &&
+ profile->GetExtensionService()->ExtensionBindingsAllowed(entry.url());
+ render_view_host->set_is_extension_process(is_extension_process);
+
+ return delegate_->CreateRenderViewForRenderManager(render_view_host);
+}
+
+void RenderViewHostManager::CommitPending() {
+ // First check whether we're going to want to focus the location bar after
+ // this commit. We do this now because the navigation hasn't formally
+ // committed yet, so if we've already cleared |pending_web_ui_| the call chain
+ // this triggers won't be able to figure out what's going on.
+ bool will_focus_location_bar = delegate_->FocusLocationBarByDefault();
+
+ // Next commit the Web UI, if any.
+ web_ui_.swap(pending_web_ui_);
+ if (web_ui_.get() && pending_web_ui_.get() && !pending_render_view_host_)
+ web_ui_->DidBecomeActiveForReusedRenderView();
+ pending_web_ui_.reset();
+
+ // It's possible for the pending_render_view_host_ to be NULL when we aren't
+ // crossing process boundaries. If so, we just needed to handle the Web UI
+ // committing above and we're done.
+ if (!pending_render_view_host_) {
+ if (will_focus_location_bar)
+ delegate_->SetFocusToLocationBar(false);
+ return;
+ }
+
+ // Remember if the page was focused so we can focus the new renderer in
+ // that case.
+ bool focus_render_view = !will_focus_location_bar &&
+ render_view_host_->view() && render_view_host_->view()->HasFocus();
+
+ // Hide the current view and prepare to destroy it.
+ // TODO(creis): Get the old RenderViewHost to send us an UpdateState message
+ // before we destroy it.
+ if (render_view_host_->view())
+ render_view_host_->view()->Hide();
+ RenderViewHost* old_render_view_host = render_view_host_;
+
+ // Swap in the pending view and make it active.
+ render_view_host_ = pending_render_view_host_;
+ pending_render_view_host_ = NULL;
+
+ // If the view is gone, then this RenderViewHost died while it was hidden.
+ // We ignored the RenderViewGone call at the time, so we should send it now
+ // to make sure the sad tab shows up, etc.
+ if (render_view_host_->view())
+ render_view_host_->view()->Show();
+ else
+ delegate_->RenderViewGoneFromRenderManager(render_view_host_);
+
+ // Make sure the size is up to date. (Fix for bug 1079768.)
+ delegate_->UpdateRenderViewSizeForRenderManager();
+
+ if (will_focus_location_bar)
+ delegate_->SetFocusToLocationBar(false);
+ else if (focus_render_view && render_view_host_->view())
+ render_view_host_->view()->Focus();
+
+ RenderViewHostSwitchedDetails details;
+ details.new_host = render_view_host_;
+ details.old_host = old_render_view_host;
+ NotificationService::current()->Notify(
+ NotificationType::RENDER_VIEW_HOST_CHANGED,
+ Source<NavigationController>(&delegate_->GetControllerForRenderManager()),
+ Details<RenderViewHostSwitchedDetails>(&details));
+
+ old_render_view_host->Shutdown();
+
+ // Let the task manager know that we've swapped RenderViewHosts, since it
+ // might need to update its process groupings.
+ delegate_->NotifySwappedFromRenderManager();
+}
+
+RenderViewHost* RenderViewHostManager::UpdateRendererStateForNavigate(
+ const NavigationEntry& entry) {
+ // If we are cross-navigating, then we want to get back to normal and navigate
+ // as usual.
+ if (cross_navigation_pending_) {
+ if (pending_render_view_host_)
+ CancelPending();
+ cross_navigation_pending_ = false;
+ }
+
+ // This will possibly create (set to NULL) a Web UI object for the pending
+ // page. We'll use this later to give the page special access. This must
+ // happen before the new renderer is created below so it will get bindings.
+ // It must also happen after the above conditional call to CancelPending(),
+ // otherwise CancelPending may clear the pending_web_ui_ and the page will
+ // not have it's bindings set appropriately.
+ pending_web_ui_.reset(delegate_->CreateWebUIForRenderManager(entry.url()));
+
+ // render_view_host_ will not be deleted before the end of this method, so we
+ // don't have to worry about this SiteInstance's ref count dropping to zero.
+ SiteInstance* curr_instance = render_view_host_->site_instance();
+
+ // Determine if we need a new SiteInstance for this entry.
+ // Again, new_instance won't be deleted before the end of this method, so it
+ // is safe to use a normal pointer here.
+ SiteInstance* new_instance = curr_instance;
+ bool force_swap = ShouldSwapProcessesForNavigation(
+ delegate_->GetLastCommittedNavigationEntryForRenderManager(),
+ &entry);
+ if (ShouldTransitionCrossSite() || force_swap)
+ new_instance = GetSiteInstanceForEntry(entry, curr_instance);
+
+ if (new_instance != curr_instance || force_swap) {
+ // New SiteInstance.
+ DCHECK(!cross_navigation_pending_);
+
+ // Create a pending RVH and navigate it.
+ bool success = CreatePendingRenderView(entry, new_instance);
+ if (!success)
+ return NULL;
+
+ // Check if our current RVH is live before we set up a transition.
+ if (!render_view_host_->IsRenderViewLive()) {
+ if (!cross_navigation_pending_) {
+ // The current RVH is not live. There's no reason to sit around with a
+ // sad tab or a newly created RVH while we wait for the pending RVH to
+ // navigate. Just switch to the pending RVH now and go back to non
+ // cross-navigating (Note that we don't care about on{before}unload
+ // handlers if the current RVH isn't live.)
+ CommitPending();
+ return render_view_host_;
+ } else {
+ NOTREACHED();
+ return render_view_host_;
+ }
+ }
+ // Otherwise, it's safe to treat this as a pending cross-site transition.
+
+ // Make sure the old render view stops, in case a load is in progress.
+ render_view_host_->Stop();
+
+ // Suspend the new render view (i.e., don't let it send the cross-site
+ // Navigate message) until we hear back from the old renderer's
+ // onbeforeunload handler. If the handler returns false, we'll have to
+ // cancel the request.
+ DCHECK(!pending_render_view_host_->are_navigations_suspended());
+ pending_render_view_host_->SetNavigationsSuspended(true);
+
+ // Tell the CrossSiteRequestManager that this RVH has a pending cross-site
+ // request, so that ResourceDispatcherHost will know to tell us to run the
+ // old page's onunload handler before it sends the response.
+ pending_render_view_host_->SetHasPendingCrossSiteRequest(true, -1);
+
+ // We now have a pending RVH.
+ DCHECK(!cross_navigation_pending_);
+ cross_navigation_pending_ = true;
+
+ // Tell the old render view to run its onbeforeunload handler, since it
+ // doesn't otherwise know that the cross-site request is happening. This
+ // will trigger a call to ShouldClosePage with the reply.
+ render_view_host_->FirePageBeforeUnload(true);
+
+ return pending_render_view_host_;
+ } else {
+ if (pending_web_ui_.get() && render_view_host_->IsRenderViewLive())
+ pending_web_ui_->RenderViewReused(render_view_host_);
+
+ // The renderer can exit view source mode when any error or cancellation
+ // happen. We must overwrite to recover the mode.
+ if (entry.IsViewSourceMode()) {
+ render_view_host_->Send(
+ new ViewMsg_EnableViewSourceMode(render_view_host_->routing_id()));
+ }
+ }
+
+ // Same SiteInstance can be used. Navigate render_view_host_ if we are not
+ // cross navigating.
+ DCHECK(!cross_navigation_pending_);
+ return render_view_host_;
+}
+
+void RenderViewHostManager::CancelPending() {
+ RenderViewHost* pending_render_view_host = pending_render_view_host_;
+ pending_render_view_host_ = NULL;
+ pending_render_view_host->Shutdown();
+
+ pending_web_ui_.reset();
+}
+
+void RenderViewHostManager::RenderViewDeleted(RenderViewHost* rvh) {
+ // We are doing this in order to work around and to track a crasher
+ // (http://crbug.com/23411) where it seems that pending_render_view_host_ is
+ // deleted (not sure from where) but not NULLed.
+ if (rvh == pending_render_view_host_) {
+ // If you hit this NOTREACHED, please report it in the following bug
+ // http://crbug.com/23411 Make sure to include what you were doing when it
+ // happened (navigating to a new page, closing a tab...) and if you can
+ // reproduce.
+ NOTREACHED();
+ pending_render_view_host_ = NULL;
+ }
+}
+
+void RenderViewHostManager::SwapInRenderViewHost(RenderViewHost* rvh) {
+ web_ui_.reset();
+
+ // Hide the current view and prepare to destroy it.
+ if (render_view_host_->view())
+ render_view_host_->view()->Hide();
+ RenderViewHost* old_render_view_host = render_view_host_;
+
+ // Swap in the new view and make it active.
+ render_view_host_ = rvh;
+ render_view_host_->set_delegate(render_view_delegate_);
+ delegate_->CreateViewAndSetSizeForRVH(render_view_host_);
+ render_view_host_->ActivateDeferredPluginHandles();
+ // If the view is gone, then this RenderViewHost died while it was hidden.
+ // We ignored the RenderViewGone call at the time, so we should send it now
+ // to make sure the sad tab shows up, etc.
+ if (render_view_host_->view()) {
+ // TODO(tburkard,cbentzel): Figure out why this hack is needed and/or
+ // if it can be removed. On Windows, prerendering will not work without
+ // doing a Hide before the Show.
+ render_view_host_->view()->Hide();
+ render_view_host_->view()->Show();
+ }
+
+ delegate_->UpdateRenderViewSizeForRenderManager();
+
+ RenderViewHostSwitchedDetails details;
+ details.new_host = render_view_host_;
+ details.old_host = old_render_view_host;
+ NotificationService::current()->Notify(
+ NotificationType::RENDER_VIEW_HOST_CHANGED,
+ Source<NavigationController>(&delegate_->GetControllerForRenderManager()),
+ Details<RenderViewHostSwitchedDetails>(&details));
+
+ // This will cause the old RenderViewHost to delete itself.
+ old_render_view_host->Shutdown();
+
+ // Let the task manager know that we've swapped RenderViewHosts, since it
+ // might need to update its process groupings.
+ delegate_->NotifySwappedFromRenderManager();
+}
diff --git a/content/browser/tab_contents/render_view_host_manager.h b/content/browser/tab_contents/render_view_host_manager.h
new file mode 100644
index 0000000..04e4109
--- /dev/null
+++ b/content/browser/tab_contents/render_view_host_manager.h
@@ -0,0 +1,273 @@
+// Copyright (c) 2011 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_TAB_CONTENTS_RENDER_VIEW_HOST_MANAGER_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_RENDER_VIEW_HOST_MANAGER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#include "chrome/common/notification_registrar.h"
+
+class WebUI;
+class InterstitialPage;
+class NavigationController;
+class NavigationEntry;
+class Profile;
+class RenderWidgetHostView;
+class RenderViewHost;
+class SiteInstance;
+
+// Manages RenderViewHosts for a TabContents. Normally there is only one and
+// it is easy to do. But we can also have transitions of processes (and hence
+// RenderViewHosts) that can get complex.
+class RenderViewHostManager
+ : public RenderViewHostDelegate::RendererManagement {
+ public:
+ // Functions implemented by our owner that we need.
+ //
+ // TODO(brettw) Clean this up! These are all the functions in TabContents that
+ // are required to run this class. The design should probably be better such
+ // that these are more clear.
+ //
+ // There is additional complexity that some of the functions we need in
+ // TabContents are inherited and non-virtual. These are named with
+ // "RenderManager" so that the duplicate implementation of them will be clear.
+ class Delegate {
+ public:
+ // See tab_contents.h's implementation for more.
+ virtual bool CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host) = 0;
+ virtual void BeforeUnloadFiredFromRenderManager(
+ bool proceed, bool* proceed_to_fire_unload) = 0;
+ virtual void DidStartLoadingFromRenderManager(
+ RenderViewHost* render_view_host) = 0;
+ virtual void RenderViewGoneFromRenderManager(
+ RenderViewHost* render_view_host) = 0;
+ virtual void UpdateRenderViewSizeForRenderManager() = 0;
+ virtual void NotifySwappedFromRenderManager() = 0;
+ virtual NavigationController& GetControllerForRenderManager() = 0;
+
+ // Creates a WebUI object for the given URL if one applies. Ownership of the
+ // returned pointer will be passed to the caller. If no WebUI applies,
+ // returns NULL.
+ virtual WebUI* CreateWebUIForRenderManager(const GURL& url) = 0;
+
+ // Returns the navigation entry of the current navigation, or NULL if there
+ // is none.
+ virtual NavigationEntry*
+ GetLastCommittedNavigationEntryForRenderManager() = 0;
+
+ // Returns true if the location bar should be focused by default rather than
+ // the page contents.
+ virtual bool FocusLocationBarByDefault() = 0;
+
+ // Focuses the location bar.
+ virtual void SetFocusToLocationBar(bool select_all) = 0;
+
+ // Creates a view and sets the size for the specified RVH.
+ virtual void CreateViewAndSetSizeForRVH(RenderViewHost* rvh) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // Both delegate pointers must be non-NULL and are not owned by this class.
+ // They must outlive this class. The RenderViewHostDelegate is what will be
+ // installed into all RenderViewHosts that are created.
+ //
+ // You must call Init() before using this class.
+ RenderViewHostManager(RenderViewHostDelegate* render_view_delegate,
+ Delegate* delegate);
+ virtual ~RenderViewHostManager();
+
+ // For arguments, see TabContents constructor.
+ void Init(Profile* profile,
+ SiteInstance* site_instance,
+ int routing_id);
+
+ // Returns the currently actuive RenderViewHost.
+ //
+ // This will be non-NULL between Init() and Shutdown(). You may want to NULL
+ // check it in many cases, however. Windows can send us messages during the
+ // destruction process after it has been shut down.
+ RenderViewHost* current_host() const {
+ return render_view_host_;
+ }
+
+ // Returns the view associated with the current RenderViewHost, or NULL if
+ // there is no current one.
+ RenderWidgetHostView* GetRenderWidgetHostView() const;
+
+ // Returns the pending render view host, or NULL if there is no pending one.
+ RenderViewHost* pending_render_view_host() const {
+ return pending_render_view_host_;
+ }
+
+ // Returns the current committed Web UI or NULL if none applies.
+ WebUI* web_ui() const { return web_ui_.get(); }
+
+ // Returns the Web UI for the pending navigation, or NULL of none applies.
+ WebUI* pending_web_ui() const { return pending_web_ui_.get(); }
+
+ // Called when we want to instruct the renderer to navigate to the given
+ // navigation entry. It may create a new RenderViewHost or re-use an existing
+ // one. The RenderViewHost to navigate will be returned. Returns NULL if one
+ // could not be created.
+ RenderViewHost* Navigate(const NavigationEntry& entry);
+
+ // Instructs the various live views to stop. Called when the user directed the
+ // page to stop loading.
+ void Stop();
+
+ // Notifies the regular and pending RenderViewHosts that a load is or is not
+ // happening. Even though the message is only for one of them, we don't know
+ // which one so we tell both.
+ void SetIsLoading(bool is_loading);
+
+ // Whether to close the tab or not when there is a hang during an unload
+ // handler. If we are mid-crosssite navigation, then we should proceed
+ // with the navigation instead of closing the tab.
+ bool ShouldCloseTabOnUnresponsiveRenderer();
+
+ // Called when a renderer's main frame navigates.
+ void DidNavigateMainFrame(RenderViewHost* render_view_host);
+
+ // Set the WebUI after committing a page load. This is useful for navigations
+ // initiated from a renderer, where we want to give the new renderer WebUI
+ // privileges from the originating renderer.
+ void SetWebUIPostCommit(WebUI* web_ui);
+
+ // Called when a provisional load on the given renderer is aborted.
+ void RendererAbortedProvisionalLoad(RenderViewHost* render_view_host);
+
+ // Sets the passed passed interstitial as the currently showing interstitial.
+ // |interstitial_page| should be non NULL (use the remove_interstitial_page
+ // method to unset the interstitial) and no interstitial page should be set
+ // when there is already a non NULL interstitial page set.
+ void set_interstitial_page(InterstitialPage* interstitial_page) {
+ DCHECK(!interstitial_page_ && interstitial_page);
+ interstitial_page_ = interstitial_page;
+ }
+
+ // Unsets the currently showing interstitial.
+ void remove_interstitial_page() {
+ DCHECK(interstitial_page_);
+ interstitial_page_ = NULL;
+ }
+
+ // Returns the currently showing interstitial, NULL if no interstitial is
+ // showing.
+ InterstitialPage* interstitial_page() const {
+ return interstitial_page_;
+ }
+
+ // RenderViewHostDelegate::RendererManagement implementation.
+ virtual void ShouldClosePage(bool for_cross_site_transition, bool proceed);
+ virtual void OnCrossSiteResponse(int new_render_process_host_id,
+ int new_request_id);
+ virtual void OnCrossSiteNavigationCanceled();
+
+ // Called when a RenderViewHost is about to be deleted.
+ void RenderViewDeleted(RenderViewHost* rvh);
+
+ // Allows a caller to swap in a provided RenderViewHost to replace the
+ // current RenderViewHost. The current RVH will be shutdown and ultimately
+ // deleted.
+ void SwapInRenderViewHost(RenderViewHost* rvh);
+
+ private:
+ friend class TestTabContents;
+ friend class RenderViewHostManagerTest;
+
+ // Returns whether this tab should transition to a new renderer for
+ // cross-site URLs. Enabled unless we see the --process-per-tab command line
+ // switch. Can be overridden in unit tests.
+ bool ShouldTransitionCrossSite();
+
+ // Returns true if the two navigation entries are incompatible in some way
+ // other than site instances. Cases where this can happen include Web UI
+ // to regular web pages. It will cause us to swap RenderViewHosts (and hence
+ // RenderProcessHosts) even if the site instance would otherwise be the same.
+ // As part of this, we'll also force new SiteInstances and BrowsingInstances.
+ // Either of the entries may be NULL.
+ bool ShouldSwapProcessesForNavigation(
+ const NavigationEntry* cur_entry,
+ const NavigationEntry* new_entry) const;
+
+ // Returns an appropriate SiteInstance object for the given NavigationEntry,
+ // possibly reusing the current SiteInstance.
+ // Never called if --process-per-tab is used.
+ SiteInstance* GetSiteInstanceForEntry(const NavigationEntry& entry,
+ SiteInstance* curr_instance);
+
+ // Helper method to create a pending RenderViewHost for a cross-site
+ // navigation.
+ bool CreatePendingRenderView(const NavigationEntry& entry,
+ SiteInstance* instance);
+
+ // Sets up the necessary state for a new RenderViewHost navigating to the
+ // given entry.
+ bool InitRenderView(RenderViewHost* render_view_host,
+ const NavigationEntry& entry);
+
+ // Sets the pending RenderViewHost/WebUI to be the active one. Note that this
+ // doesn't require the pending render_view_host_ pointer to be non-NULL, since
+ // there could be Web UI switching as well. Call this for every commit.
+ void CommitPending();
+
+ // Helper method to terminate the pending RenderViewHost.
+ void CancelPending();
+
+ RenderViewHost* UpdateRendererStateForNavigate(const NavigationEntry& entry);
+
+ // Our delegate, not owned by us. Guaranteed non-NULL.
+ Delegate* delegate_;
+
+ // Whether a navigation requiring different RenderView's is pending. This is
+ // either cross-site request is (in the new process model), or when required
+ // for the view type (like view source versus not).
+ bool cross_navigation_pending_;
+
+ // Implemented by the owner of this class, this delegate is installed into all
+ // the RenderViewHosts that we create.
+ RenderViewHostDelegate* render_view_delegate_;
+
+ // Our RenderView host and its associated Web UI (if any, will be NULL for
+ // non-DOM-UI pages). This object is responsible for all communication with
+ // a child RenderView instance.
+ RenderViewHost* render_view_host_;
+ scoped_ptr<WebUI> web_ui_;
+
+ // A RenderViewHost used to load a cross-site page. This remains hidden
+ // while a cross-site request is pending until it calls DidNavigate. It may
+ // have an associated Web UI, in which case the Web UI pointer will be non-
+ // NULL.
+ //
+ // The pending_web_ui may be non-NULL even when the pending_render_view_host_
+ // is. This will happen when we're transitioning between two Web UI pages:
+ // the RVH won't be swapped, so the pending pointer will be unused, but there
+ // will be a pending Web UI associated with the navigation.
+ RenderViewHost* pending_render_view_host_;
+ scoped_ptr<WebUI> pending_web_ui_;
+
+ // The intersitial page currently shown if any, not own by this class
+ // (the InterstitialPage is self-owned, it deletes itself when hidden).
+ InterstitialPage* interstitial_page_;
+
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostManager);
+};
+
+// The "details" for a NOTIFY_RENDER_VIEW_HOST_CHANGED notification. The old
+// host can be NULL when the first RenderViewHost is set.
+struct RenderViewHostSwitchedDetails {
+ RenderViewHost* old_host;
+ RenderViewHost* new_host;
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_RENDER_VIEW_HOST_MANAGER_H_
diff --git a/content/browser/tab_contents/render_view_host_manager_unittest.cc b/content/browser/tab_contents/render_view_host_manager_unittest.cc
new file mode 100644
index 0000000..a4b82d8
--- /dev/null
+++ b/content/browser/tab_contents/render_view_host_manager_unittest.cc
@@ -0,0 +1,345 @@
+// Copyright (c) 2011 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 "chrome/browser/browser_thread.h"
+#include "chrome/browser/browser_url_handler.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/renderer_host/test/test_render_view_host.h"
+#include "chrome/browser/tab_contents/navigation_controller.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/tab_contents/render_view_host_manager.h"
+#include "chrome/browser/tab_contents/test_tab_contents.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_source.h"
+#include "chrome/common/page_transition_types.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/render_messages_params.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/test_notification_tracker.h"
+#include "chrome/test/testing_profile.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class RenderViewHostManagerTest : public RenderViewHostTestHarness {
+ public:
+ void NavigateActiveAndCommit(const GURL& url) {
+ // Note: we navigate the active RenderViewHost because previous navigations
+ // won't have committed yet, so NavigateAndCommit does the wrong thing
+ // for us.
+ controller().LoadURL(url, GURL(), PageTransition::LINK);
+ active_rvh()->SendNavigate(
+ static_cast<MockRenderProcessHost*>(active_rvh()->process())->
+ max_page_id() + 1,
+ url);
+ }
+
+ bool ShouldSwapProcesses(RenderViewHostManager* manager,
+ const NavigationEntry* cur_entry,
+ const NavigationEntry* new_entry) const {
+ return manager->ShouldSwapProcessesForNavigation(cur_entry, new_entry);
+ }
+};
+
+// Tests that when you navigate from the New TabPage to another page, and
+// then do that same thing in another tab, that the two resulting pages have
+// different SiteInstances, BrowsingInstances, and RenderProcessHosts. This is
+// a regression test for bug 9364.
+TEST_F(RenderViewHostManagerTest, NewTabPageProcesses) {
+ BrowserThread ui_thread(BrowserThread::UI, MessageLoop::current());
+ GURL ntp(chrome::kChromeUINewTabURL);
+ GURL dest("http://www.google.com/");
+
+ // Navigate our first tab to the new tab page and then to the destination.
+ NavigateActiveAndCommit(ntp);
+ NavigateActiveAndCommit(dest);
+
+ // Make a second tab.
+ TestTabContents contents2(profile_.get(), NULL);
+
+ // Load the two URLs in the second tab. Note that the first navigation creates
+ // a RVH that's not pending (since there is no cross-site transition), so
+ // we use the committed one, but the second one is the opposite.
+ contents2.controller().LoadURL(ntp, GURL(), PageTransition::LINK);
+ static_cast<TestRenderViewHost*>(contents2.render_manager()->
+ current_host())->SendNavigate(100, ntp);
+ contents2.controller().LoadURL(dest, GURL(), PageTransition::LINK);
+ static_cast<TestRenderViewHost*>(contents2.render_manager()->
+ pending_render_view_host())->SendNavigate(101, dest);
+
+ // The two RVH's should be different in every way.
+ EXPECT_NE(active_rvh()->process(), contents2.render_view_host()->process());
+ EXPECT_NE(active_rvh()->site_instance(),
+ contents2.render_view_host()->site_instance());
+ EXPECT_NE(active_rvh()->site_instance()->browsing_instance(),
+ contents2.render_view_host()->site_instance()->browsing_instance());
+
+ // Navigate both to the new tab page, and verify that they share a
+ // SiteInstance.
+ NavigateActiveAndCommit(ntp);
+
+ contents2.controller().LoadURL(ntp, GURL(), PageTransition::LINK);
+ static_cast<TestRenderViewHost*>(contents2.render_manager()->
+ pending_render_view_host())->SendNavigate(102, ntp);
+
+ EXPECT_EQ(active_rvh()->site_instance(),
+ contents2.render_view_host()->site_instance());
+}
+
+// When there is an error with the specified page, renderer exits view-source
+// mode. See WebFrameImpl::DidFail(). We check by this test that
+// EnableViewSourceMode message is sent on every navigation regardless
+// RenderView is being newly created or reused.
+TEST_F(RenderViewHostManagerTest, AlwaysSendEnableViewSourceMode) {
+ BrowserThread ui_thread(BrowserThread::UI, MessageLoop::current());
+ const GURL kNtpUrl(chrome::kChromeUINewTabURL);
+ const GURL kUrl("view-source:http://foo");
+
+ // We have to navigate to some page at first since without this, the first
+ // navigation will reuse the SiteInstance created by Init(), and the second
+ // one will create a new SiteInstance. Because current_instance and
+ // new_instance will be different, a new RenderViewHost will be created for
+ // the second navigation. We have to avoid this in order to exercise the
+ // target code patch.
+ NavigateActiveAndCommit(kNtpUrl);
+
+ // Navigate.
+ controller().LoadURL(kUrl, GURL() /* referer */, PageTransition::TYPED);
+ // Simulate response from RenderView for FirePageBeforeUnload.
+ rvh()->TestOnMessageReceived(
+ ViewHostMsg_ShouldClose_ACK(rvh()->routing_id(), true));
+ ASSERT_TRUE(pending_rvh()); // New pending RenderViewHost will be created.
+ RenderViewHost* last_rvh = pending_rvh();
+ int new_id = static_cast<MockRenderProcessHost*>(pending_rvh()->process())->
+ max_page_id() + 1;
+ pending_rvh()->SendNavigate(new_id, kUrl);
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ ASSERT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(kUrl == controller().GetLastCommittedEntry()->url());
+ EXPECT_FALSE(controller().pending_entry());
+ // Because we're using TestTabContents and TestRenderViewHost in this
+ // unittest, no one calls TabContents::RenderViewCreated(). So, we see no
+ // EnableViewSourceMode message, here.
+
+ // Clear queued messages before load.
+ process()->sink().ClearMessages();
+ // Navigate, again.
+ controller().LoadURL(kUrl, GURL() /* referer */, PageTransition::TYPED);
+ // The same RenderViewHost should be reused.
+ EXPECT_FALSE(pending_rvh());
+ EXPECT_TRUE(last_rvh == rvh());
+ rvh()->SendNavigate(new_id, kUrl); // The same page_id returned.
+ EXPECT_EQ(controller().last_committed_entry_index(), 1);
+ EXPECT_FALSE(controller().pending_entry());
+ // New message should be sent out to make sure to enter view-source mode.
+ EXPECT_TRUE(process()->sink().GetUniqueMessageMatching(
+ ViewMsg_EnableViewSourceMode::ID));
+}
+
+// Tests the Init function by checking the initial RenderViewHost.
+TEST_F(RenderViewHostManagerTest, Init) {
+ // Using TestingProfile.
+ SiteInstance* instance = SiteInstance::CreateSiteInstance(profile_.get());
+ EXPECT_FALSE(instance->has_site());
+
+ TestTabContents tab_contents(profile_.get(), instance);
+ RenderViewHostManager manager(&tab_contents, &tab_contents);
+
+ manager.Init(profile_.get(), instance, MSG_ROUTING_NONE);
+
+ RenderViewHost* host = manager.current_host();
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(instance == host->site_instance());
+ EXPECT_TRUE(&tab_contents == host->delegate());
+ EXPECT_TRUE(manager.GetRenderWidgetHostView());
+ EXPECT_FALSE(manager.pending_render_view_host());
+}
+
+// Tests the Navigate function. We navigate three sites consecutively and check
+// how the pending/committed RenderViewHost are modified.
+TEST_F(RenderViewHostManagerTest, Navigate) {
+ TestNotificationTracker notifications;
+
+ SiteInstance* instance = SiteInstance::CreateSiteInstance(profile_.get());
+
+ TestTabContents tab_contents(profile_.get(), instance);
+ notifications.ListenFor(NotificationType::RENDER_VIEW_HOST_CHANGED,
+ Source<NavigationController>(&tab_contents.controller()));
+
+ // Create.
+ RenderViewHostManager manager(&tab_contents, &tab_contents);
+
+ manager.Init(profile_.get(), instance, MSG_ROUTING_NONE);
+
+ RenderViewHost* host;
+
+ // 1) The first navigation. --------------------------
+ GURL url1("http://www.google.com/");
+ NavigationEntry entry1(NULL /* instance */, -1 /* page_id */, url1,
+ GURL() /* referrer */, string16() /* title */,
+ PageTransition::TYPED);
+ host = manager.Navigate(entry1);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ // Commit to SiteInstance should be delayed until RenderView commit.
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_FALSE(host->site_instance()->has_site());
+ host->site_instance()->SetSite(url1);
+
+ // 2) Navigate to next site. -------------------------
+ GURL url2("http://www.google.com/foo");
+ NavigationEntry entry2(NULL /* instance */, -1 /* page_id */, url2,
+ url1 /* referrer */, string16() /* title */,
+ PageTransition::LINK);
+ host = manager.Navigate(entry2);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(host->site_instance()->has_site());
+
+ // 3) Cross-site navigate to next site. --------------
+ GURL url3("http://webkit.org/");
+ NavigationEntry entry3(NULL /* instance */, -1 /* page_id */, url3,
+ url2 /* referrer */, string16() /* title */,
+ PageTransition::LINK);
+ host = manager.Navigate(entry3);
+
+ // A new RenderViewHost should be created.
+ EXPECT_TRUE(manager.pending_render_view_host());
+ EXPECT_TRUE(host == manager.pending_render_view_host());
+
+ notifications.Reset();
+
+ // Commit.
+ manager.DidNavigateMainFrame(manager.pending_render_view_host());
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(host->site_instance()->has_site());
+ // Check the pending RenderViewHost has been committed.
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // We should observe a notification.
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NotificationType::RENDER_VIEW_HOST_CHANGED));
+}
+
+// Tests WebUI creation.
+TEST_F(RenderViewHostManagerTest, WebUI) {
+ BrowserThread ui_thread(BrowserThread::UI, MessageLoop::current());
+ SiteInstance* instance = SiteInstance::CreateSiteInstance(profile_.get());
+
+ TestTabContents tab_contents(profile_.get(), instance);
+ RenderViewHostManager manager(&tab_contents, &tab_contents);
+
+ manager.Init(profile_.get(), instance, MSG_ROUTING_NONE);
+
+ GURL url(chrome::kChromeUINewTabURL);
+ NavigationEntry entry(NULL /* instance */, -1 /* page_id */, url,
+ GURL() /* referrer */, string16() /* title */,
+ PageTransition::TYPED);
+ RenderViewHost* host = manager.Navigate(entry);
+
+ EXPECT_TRUE(host);
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // It's important that the site instance get set on the Web UI page as soon
+ // as the navigation starts, rather than lazily after it commits, so we don't
+ // try to re-use the SiteInstance/process for non DOM-UI things that may
+ // get loaded in between.
+ EXPECT_TRUE(host->site_instance()->has_site());
+ EXPECT_EQ(url, host->site_instance()->site());
+
+ // The Web UI is committed immediately because the RenderViewHost has not been
+ // used yet. UpdateRendererStateForNavigate() took the short cut path.
+ EXPECT_FALSE(manager.pending_web_ui());
+ EXPECT_TRUE(manager.web_ui());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+}
+
+// Tests that chrome: URLs that are not Web UI pages do not get grouped into
+// Web UI renderers, even if --process-per-tab is enabled. In that mode, we
+// still swap processes if ShouldSwapProcessesForNavigation is true.
+// Regression test for bug 46290.
+TEST_F(RenderViewHostManagerTest, NonWebUIChromeURLs) {
+ BrowserThread thread(BrowserThread::UI, &message_loop_);
+ SiteInstance* instance = SiteInstance::CreateSiteInstance(profile_.get());
+ TestTabContents tab_contents(profile_.get(), instance);
+ RenderViewHostManager manager(&tab_contents, &tab_contents);
+ manager.Init(profile_.get(), instance, MSG_ROUTING_NONE);
+
+ // NTP is a Web UI page.
+ GURL ntp_url(chrome::kChromeUINewTabURL);
+ NavigationEntry ntp_entry(NULL /* instance */, -1 /* page_id */, ntp_url,
+ GURL() /* referrer */, string16() /* title */,
+ PageTransition::TYPED);
+
+ // about: URLs are not Web UI pages.
+ GURL about_url(chrome::kAboutMemoryURL);
+ // Rewrite so it looks like chrome://about/memory
+ bool reverse_on_redirect = false;
+ BrowserURLHandler::RewriteURLIfNecessary(
+ &about_url, profile_.get(), &reverse_on_redirect);
+ NavigationEntry about_entry(NULL /* instance */, -1 /* page_id */, about_url,
+ GURL() /* referrer */, string16() /* title */,
+ PageTransition::TYPED);
+
+ EXPECT_TRUE(ShouldSwapProcesses(&manager, &ntp_entry, &about_entry));
+}
+
+// Tests that we don't end up in an inconsistent state if a page does a back and
+// then reload. http://crbug.com/51680
+TEST_F(RenderViewHostManagerTest, PageDoesBackAndReload) {
+ GURL url1("http://www.google.com/");
+ GURL url2("http://www.evil-site.com/");
+
+ // Navigate to a safe site, then an evil site.
+ // This will switch RenderViewHosts. We cannot assert that the first and
+ // second RVHs are different, though, because the first one may be promptly
+ // deleted.
+ contents()->NavigateAndCommit(url1);
+ contents()->NavigateAndCommit(url2);
+ RenderViewHost* evil_rvh = contents()->render_view_host();
+
+ // Now let's simulate the evil page calling history.back().
+ contents()->OnGoToEntryAtOffset(-1);
+ // We should have a new pending RVH.
+ // Note that in this case, the navigation has not committed, so evil_rvh will
+ // not be deleted yet.
+ EXPECT_NE(evil_rvh, contents()->render_manager()->pending_render_view_host());
+
+ // Before that RVH has committed, the evil page reloads itself.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = PageTransition::CLIENT_REDIRECT;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.was_within_same_page = false;
+ params.is_post = false;
+ contents()->TestDidNavigate(evil_rvh, params);
+
+ // That should have cancelled the pending RVH, and the evil RVH should be the
+ // current one.
+ EXPECT_TRUE(contents()->render_manager()->pending_render_view_host() == NULL);
+ EXPECT_EQ(evil_rvh, contents()->render_manager()->current_host());
+
+ // Also we should not have a pending navigation entry.
+ NavigationEntry* entry = contents()->controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_EQ(url2, entry->url());
+}
diff --git a/content/browser/tab_contents/tab_contents.cc b/content/browser/tab_contents/tab_contents.cc
new file mode 100644
index 0000000..30b91e9
--- /dev/null
+++ b/content/browser/tab_contents/tab_contents.cc
@@ -0,0 +1,2851 @@
+// Copyright (c) 2011 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/tab_contents/tab_contents.h"
+
+#include <cmath>
+
+#include "base/auto_reset.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/string16.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/autocomplete_history_manager.h"
+#include "chrome/browser/autofill/autofill_manager.h"
+#include "chrome/browser/blocked_content_container.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_shutdown.h"
+#include "chrome/browser/character_encoding.h"
+#include "chrome/browser/child_process_security_policy.h"
+#include "chrome/browser/content_settings/content_settings_details.h"
+#include "chrome/browser/content_settings/host_content_settings_map.h"
+#include "chrome/browser/debugger/devtools_manager.h"
+#include "chrome/browser/defaults.h"
+#include "chrome/browser/desktop_notification_handler.h"
+#include "chrome/browser/dom_operation_notification_details.h"
+#include "chrome/browser/download/download_item_model.h"
+#include "chrome/browser/download/download_manager.h"
+#include "chrome/browser/download/download_request_limiter.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/external_protocol_handler.h"
+#include "chrome/browser/favicon_service.h"
+#include "chrome/browser/file_select_helper.h"
+#include "chrome/browser/google/google_util.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/history/top_sites.h"
+#include "chrome/browser/host_zoom_map.h"
+#include "chrome/browser/hung_renderer_dialog.h"
+#include "chrome/browser/in_process_webkit/session_storage_namespace.h"
+#include "chrome/browser/load_from_memory_cache_details.h"
+#include "chrome/browser/load_notification_details.h"
+#include "chrome/browser/metrics/metric_event_duration_details.h"
+#include "chrome/browser/metrics/user_metrics.h"
+#include "chrome/browser/modal_html_dialog_delegate.h"
+#include "chrome/browser/omnibox_search_hint.h"
+#include "chrome/browser/pdf_unsupported_feature.h"
+#include "chrome/browser/platform_util.h"
+#include "chrome/browser/plugin_observer.h"
+#include "chrome/browser/prerender/prerender_manager.h"
+#include "chrome/browser/prerender/prerender_plt_recorder.h"
+#include "chrome/browser/printing/print_preview_message_handler.h"
+#include "chrome/browser/printing/print_preview_tab_controller.h"
+#include "chrome/browser/printing/print_view_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/renderer_host/render_process_host.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/browser/renderer_host/resource_request_details.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/renderer_host/web_cache_manager.h"
+#include "chrome/browser/renderer_preferences_util.h"
+#include "chrome/browser/safe_browsing/client_side_detection_host.h"
+#include "chrome/browser/sessions/session_types.h"
+#include "chrome/browser/tab_contents/tab_contents_ssl_helper.h"
+#include "chrome/browser/tab_contents/thumbnail_generator.h"
+#include "chrome/browser/translate/page_translated_details.h"
+#include "chrome/browser/ui/app_modal_dialogs/message_box_handler.h"
+#include "chrome/browser/webui/web_ui.h"
+#include "chrome/common/bindings_policy.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/content_restriction.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_action.h"
+#include "chrome/common/extensions/extension_icon_set.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "chrome/common/navigation_types.h"
+#include "chrome/common/net/url_request_context_getter.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/render_messages_params.h"
+#include "chrome/common/url_constants.h"
+#include "content/browser/tab_contents/infobar_delegate.h"
+#include "content/browser/tab_contents/interstitial_page.h"
+#include "content/browser/tab_contents/navigation_entry.h"
+#include "content/browser/tab_contents/provisional_load_details.h"
+#include "content/browser/tab_contents/tab_contents_delegate.h"
+#include "content/browser/tab_contents/tab_contents_observer.h"
+#include "content/browser/tab_contents/tab_contents_view.h"
+#include "net/base/net_util.h"
+#include "net/base/registry_controlled_domain.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "webkit/glue/password_form.h"
+#include "webkit/glue/webpreferences.h"
+
+#if defined(OS_MACOSX)
+#include "app/surface/io_surface_support_mac.h"
+#endif // defined(OS_MACOSX)
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/locale_change_guard.h"
+#endif // defined(OS_CHROMEOS)
+
+// Cross-Site Navigations
+//
+// If a TabContents is told to navigate to a different web site (as determined
+// by SiteInstance), it will replace its current RenderViewHost with a new
+// RenderViewHost dedicated to the new SiteInstance. This works as follows:
+//
+// - Navigate determines whether the destination is cross-site, and if so,
+// it creates a pending_render_view_host_ and moves into the PENDING
+// RendererState.
+// - The pending RVH is "suspended," so that no navigation messages are sent to
+// its renderer until the onbeforeunload JavaScript handler has a chance to
+// run in the current RVH.
+// - The pending RVH tells CrossSiteRequestManager (a thread-safe singleton)
+// that it has a pending cross-site request. ResourceDispatcherHost will
+// check for this when the response arrives.
+// - The current RVH runs its onbeforeunload handler. If it returns false, we
+// cancel all the pending logic and go back to NORMAL. Otherwise we allow
+// the pending RVH to send the navigation request to its renderer.
+// - ResourceDispatcherHost receives a ResourceRequest on the IO thread. It
+// checks CrossSiteRequestManager to see that the RVH responsible has a
+// pending cross-site request, and then installs a CrossSiteEventHandler.
+// - When RDH receives a response, the BufferedEventHandler determines whether
+// it is a download. If so, it sends a message to the new renderer causing
+// it to cancel the request, and the download proceeds in the download
+// thread. For now, we stay in a PENDING state (with a pending RVH) until
+// the next DidNavigate event for this TabContents. This isn't ideal, but it
+// doesn't affect any functionality.
+// - After RDH receives a response and determines that it is safe and not a
+// download, it pauses the response to first run the old page's onunload
+// handler. It does this by asynchronously calling the OnCrossSiteResponse
+// method of TabContents on the UI thread, which sends a ClosePage message
+// to the current RVH.
+// - Once the onunload handler is finished, a ClosePage_ACK message is sent to
+// the ResourceDispatcherHost, who unpauses the response. Data is then sent
+// to the pending RVH.
+// - The pending renderer sends a FrameNavigate message that invokes the
+// DidNavigate method. This replaces the current RVH with the
+// pending RVH and goes back to the NORMAL RendererState.
+
+namespace {
+
+// Amount of time we wait between when a key event is received and the renderer
+// is queried for its state and pushed to the NavigationEntry.
+const int kQueryStateDelay = 5000;
+
+const int kSyncWaitDelay = 40;
+
+// If another javascript message box is displayed within
+// kJavascriptMessageExpectedDelay of a previous javascript message box being
+// dismissed, display an option to suppress future message boxes from this
+// contents.
+const int kJavascriptMessageExpectedDelay = 1000;
+
+// The list of prefs we want to observe.
+const char* kPrefsToObserve[] = {
+ prefs::kAlternateErrorPagesEnabled,
+ prefs::kDefaultZoomLevel,
+ prefs::kWebKitJavaEnabled,
+ prefs::kWebKitJavascriptEnabled,
+ prefs::kWebKitLoadsImagesAutomatically,
+ prefs::kWebKitPluginsEnabled,
+ prefs::kWebKitUsesUniversalDetector,
+ prefs::kWebKitSerifFontFamily,
+ prefs::kWebKitSansSerifFontFamily,
+ prefs::kWebKitFixedFontFamily,
+ prefs::kWebKitDefaultFontSize,
+ prefs::kWebKitDefaultFixedFontSize,
+ prefs::kWebKitMinimumFontSize,
+ prefs::kWebKitMinimumLogicalFontSize,
+ prefs::kWebkitTabsToLinks,
+ prefs::kDefaultCharset
+ // kWebKitStandardFontIsSerif needs to be added
+ // if we let users pick which font to use, serif or sans-serif when
+ // no font is specified or a CSS generic family (serif or sans-serif)
+ // is not specified.
+};
+
+const int kPrefsToObserveLength = arraysize(kPrefsToObserve);
+
+#if defined(OS_WIN)
+
+BOOL CALLBACK InvalidateWindow(HWND hwnd, LPARAM lparam) {
+ // Note: erase is required to properly paint some widgets borders. This can
+ // be seen with textfields.
+ InvalidateRect(hwnd, NULL, TRUE);
+ return TRUE;
+}
+#endif
+
+ViewMsg_Navigate_Params::NavigationType GetNavigationType(
+ Profile* profile, const NavigationEntry& entry,
+ NavigationController::ReloadType reload_type) {
+ switch (reload_type) {
+ case NavigationController::RELOAD:
+ return ViewMsg_Navigate_Params::RELOAD;
+ case NavigationController::RELOAD_IGNORING_CACHE:
+ return ViewMsg_Navigate_Params::RELOAD_IGNORING_CACHE;
+ case NavigationController::NO_RELOAD:
+ break; // Fall through to rest of function.
+ }
+
+ if (entry.restore_type() == NavigationEntry::RESTORE_LAST_SESSION &&
+ profile->DidLastSessionExitCleanly())
+ return ViewMsg_Navigate_Params::RESTORE;
+
+ return ViewMsg_Navigate_Params::NORMAL;
+}
+
+void MakeNavigateParams(const NavigationEntry& entry,
+ const NavigationController& controller,
+ NavigationController::ReloadType reload_type,
+ ViewMsg_Navigate_Params* params) {
+ params->page_id = entry.page_id();
+ params->pending_history_list_offset = controller.GetIndexOfEntry(&entry);
+ params->current_history_list_offset = controller.last_committed_entry_index();
+ params->current_history_list_length = controller.entry_count();
+ params->url = entry.url();
+ params->referrer = entry.referrer();
+ params->transition = entry.transition_type();
+ params->state = entry.content_state();
+ params->navigation_type =
+ GetNavigationType(controller.profile(), entry, reload_type);
+ params->request_time = base::Time::Now();
+}
+
+} // namespace
+
+
+// TabContents ----------------------------------------------------------------
+
+TabContents::TabContents(Profile* profile,
+ SiteInstance* site_instance,
+ int routing_id,
+ const TabContents* base_tab_contents,
+ SessionStorageNamespace* session_storage_namespace)
+ : delegate_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(controller_(
+ this, profile, session_storage_namespace)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(view_(
+ TabContentsView::Create(this))),
+ ALLOW_THIS_IN_INITIALIZER_LIST(render_manager_(this, this)),
+ bookmark_drag_(NULL),
+ is_loading_(false),
+ crashed_status_(base::TERMINATION_STATUS_STILL_RUNNING),
+ crashed_error_code_(0),
+ waiting_for_response_(false),
+ max_page_id_(-1),
+ load_state_(net::LOAD_STATE_IDLE),
+ upload_size_(0),
+ upload_position_(0),
+ received_page_title_(false),
+ blocked_contents_(NULL),
+ all_contents_blocked_(false),
+ dont_notify_render_view_(false),
+ displayed_insecure_content_(false),
+ extension_app_(NULL),
+ capturing_contents_(false),
+ is_being_destroyed_(false),
+ notify_disconnection_(false),
+#if defined(OS_WIN)
+ message_box_active_(CreateEvent(NULL, TRUE, FALSE, NULL)),
+#endif
+ suppress_javascript_messages_(false),
+ is_showing_before_unload_dialog_(false),
+ opener_web_ui_type_(WebUIFactory::kNoWebUI),
+ language_state_(&controller_),
+ closed_by_user_gesture_(false),
+ minimum_zoom_percent_(
+ static_cast<int>(WebKit::WebView::minTextSizeMultiplier * 100)),
+ maximum_zoom_percent_(
+ static_cast<int>(WebKit::WebView::maxTextSizeMultiplier * 100)),
+ temporary_zoom_settings_(false),
+ content_restrictions_(0) {
+ renderer_preferences_util::UpdateFromSystemSettings(
+ &renderer_preferences_, profile);
+
+ content_settings_delegate_.reset(
+ new TabSpecificContentSettings(this, profile));
+
+ render_manager_.Init(profile, site_instance, routing_id);
+
+ // We have the initial size of the view be based on the size of the passed in
+ // tab contents (normally a tab from the same window).
+ view_->CreateView(base_tab_contents ?
+ base_tab_contents->view()->GetContainerSize() : gfx::Size());
+
+ // Register for notifications about all interested prefs change.
+ PrefService* prefs = profile->GetPrefs();
+ pref_change_registrar_.Init(prefs);
+ if (prefs) {
+ for (int i = 0; i < kPrefsToObserveLength; ++i)
+ pref_change_registrar_.Add(kPrefsToObserve[i], this);
+ }
+
+ registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED,
+ NotificationService::AllSources());
+#if defined(OS_LINUX)
+ registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
+ NotificationService::AllSources());
+#endif
+
+ registrar_.Add(this, NotificationType::USER_STYLE_SHEET_UPDATED,
+ NotificationService::AllSources());
+
+ // Register for notifications about content setting changes.
+ registrar_.Add(this, NotificationType::CONTENT_SETTINGS_CHANGED,
+ NotificationService::AllSources());
+
+ // Listen for extension changes so we can update extension_for_current_page_.
+ registrar_.Add(this, NotificationType::EXTENSION_LOADED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
+ NotificationService::AllSources());
+
+ // Listen for Google URL changes
+ registrar_.Add(this, NotificationType::GOOGLE_URL_UPDATED,
+ NotificationService::AllSources());
+
+ // Set-up the showing of the omnibox search infobar if applicable.
+ if (OmniboxSearchHint::IsEnabled(profile))
+ omnibox_search_hint_.reset(new OmniboxSearchHint(this));
+
+ // Can only add observers after render_manager_.Init() is called, since that's
+ // what sets up the render_view_host which TabContentObserver's constructor
+ // uses to get the routing_id.
+ AddObservers();
+}
+
+TabContents::~TabContents() {
+ is_being_destroyed_ = true;
+
+ // We don't want any notifications while we're running our destructor.
+ registrar_.RemoveAll();
+ pref_change_registrar_.RemoveAll();
+
+ NotifyDisconnected();
+ hung_renderer_dialog::HideForTabContents(this);
+
+ // First cleanly close all child windows.
+ // TODO(mpcomplete): handle case if MaybeCloseChildWindows() already asked
+ // some of these to close. CloseWindows is async, so it might get called
+ // twice before it runs.
+ CloseConstrainedWindows();
+
+ // Close all blocked contents.
+ if (blocked_contents_)
+ blocked_contents_->Destroy();
+
+ // Notify any observer that have a reference on this tab contents.
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(this),
+ NotificationService::NoDetails());
+
+ // Notify any lasting InfobarDelegates that have not yet been removed that
+ // whatever infobar they were handling in this TabContents has closed,
+ // because the TabContents is going away entirely.
+ // This must happen after the TAB_CONTENTS_DESTROYED notification as the
+ // notification may trigger infobars calls that access their delegate. (and
+ // some implementations of InfoBarDelegate do delete themselves on
+ // InfoBarClosed()).
+ for (size_t i = 0; i < infobar_count(); ++i) {
+ InfoBarDelegate* delegate = GetInfoBarDelegateAt(i);
+ delegate->InfoBarClosed();
+ }
+ infobar_delegates_.clear();
+
+ // TODO(brettw) this should be moved to the view.
+#if defined(OS_WIN)
+ // If we still have a window handle, destroy it. GetNativeView can return
+ // NULL if this contents was part of a window that closed.
+ if (GetNativeView()) {
+ RenderViewHost* host = render_view_host();
+ if (host && host->view()) {
+ host->view()->WillWmDestroy();
+ }
+ ::DestroyWindow(GetNativeView());
+ }
+#endif
+
+ // OnCloseStarted isn't called in unit tests.
+ if (!tab_close_start_time_.is_null()) {
+ UMA_HISTOGRAM_TIMES("Tab.Close",
+ base::TimeTicks::Now() - tab_close_start_time_);
+ }
+
+ FOR_EACH_OBSERVER(TabContentsObserver, observers_, set_tab_contents(NULL));
+}
+
+void TabContents::AddObservers() {
+ printing_.reset(new printing::PrintViewManager(this));
+ print_preview_.reset(new printing::PrintPreviewMessageHandler(this));
+ fav_icon_helper_.reset(new FavIconHelper(this));
+ autofill_manager_.reset(new AutoFillManager(this));
+ autocomplete_history_manager_.reset(new AutocompleteHistoryManager(this));
+ prerender_plt_recorder_.reset(new prerender::PrerenderPLTRecorder(this));
+ desktop_notification_handler_.reset(
+ new DesktopNotificationHandlerForTC(this, GetRenderProcessHost()));
+ plugin_observer_.reset(new PluginObserver(this));
+ safebrowsing_detection_host_.reset(new safe_browsing::ClientSideDetectionHost(
+ this));
+}
+
+bool TabContents::OnMessageReceived(const IPC::Message& message) {
+ ObserverListBase<TabContentsObserver>::Iterator it(observers_);
+ TabContentsObserver* observer;
+ while ((observer = it.GetNext()) != NULL)
+ if (observer->OnMessageReceived(message))
+ return true;
+
+ bool handled = true;
+ bool message_is_ok = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(TabContents, message, message_is_ok)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidStartProvisionalLoadForFrame,
+ OnDidStartProvisionalLoadForFrame)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidRedirectProvisionalLoad,
+ OnDidRedirectProvisionalLoad)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidFailProvisionalLoadWithError,
+ OnDidFailProvisionalLoadWithError)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidLoadResourceFromMemoryCache,
+ OnDidLoadResourceFromMemoryCache)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidDisplayInsecureContent,
+ OnDidDisplayInsecureContent)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidRunInsecureContent,
+ OnDidRunInsecureContent)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentLoadedInFrame,
+ OnDocumentLoadedInFrame)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidFinishLoad, OnDidFinishLoad)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateContentRestrictions,
+ OnUpdateContentRestrictions)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_PDFHasUnsupportedFeature,
+ OnPDFHasUnsupportedFeature)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GoToEntryAtOffset, OnGoToEntryAtOffset)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidGetApplicationInfo,
+ OnDidGetApplicationInfo)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_InstallApplication,
+ OnInstallApplication)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_PageContents, OnPageContents)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_PageTranslated, OnPageTranslated)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SetSuggestions, OnSetSuggestions)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_InstantSupportDetermined,
+ OnInstantSupportDetermined)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RunFileChooser, OnRunFileChooser)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ if (!message_is_ok) {
+ UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_RVD"));
+ GetRenderProcessHost()->ReceivedBadMessage();
+ }
+
+ return handled;
+}
+
+// Returns true if contains content rendered by an extension.
+bool TabContents::HostsExtension() const {
+ return GetURL().SchemeIs(chrome::kExtensionScheme);
+}
+
+TabContentsSSLHelper* TabContents::GetSSLHelper() {
+ if (ssl_helper_.get() == NULL)
+ ssl_helper_.reset(new TabContentsSSLHelper(this));
+ return ssl_helper_.get();
+}
+
+RenderProcessHost* TabContents::GetRenderProcessHost() const {
+ return render_manager_.current_host()->process();
+}
+
+void TabContents::SetExtensionApp(const Extension* extension) {
+ DCHECK(!extension || extension->GetFullLaunchURL().is_valid());
+ extension_app_ = extension;
+
+ UpdateExtensionAppIcon(extension_app_);
+
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
+ Source<TabContents>(this),
+ NotificationService::NoDetails());
+}
+
+void TabContents::SetExtensionAppById(const std::string& extension_app_id) {
+ if (extension_app_id.empty())
+ return;
+
+ ExtensionService* extension_service = profile()->GetExtensionService();
+ if (!extension_service || !extension_service->is_ready())
+ return;
+
+ const Extension* extension =
+ extension_service->GetExtensionById(extension_app_id, false);
+ if (extension)
+ SetExtensionApp(extension);
+}
+
+SkBitmap* TabContents::GetExtensionAppIcon() {
+ if (extension_app_icon_.empty())
+ return NULL;
+
+ return &extension_app_icon_;
+}
+
+const GURL& TabContents::GetURL() const {
+ // We may not have a navigation entry yet
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ return entry ? entry->virtual_url() : GURL::EmptyGURL();
+}
+
+const string16& TabContents::GetTitle() const {
+ // Transient entries take precedence. They are used for interstitial pages
+ // that are shown on top of existing pages.
+ NavigationEntry* entry = controller_.GetTransientEntry();
+ if (entry) {
+ return entry->GetTitleForDisplay(profile()->GetPrefs()->
+ GetString(prefs::kAcceptLanguages));
+ }
+ WebUI* our_web_ui = render_manager_.pending_web_ui() ?
+ render_manager_.pending_web_ui() : render_manager_.web_ui();
+ if (our_web_ui) {
+ // Don't override the title in view source mode.
+ entry = controller_.GetActiveEntry();
+ if (!(entry && entry->IsViewSourceMode())) {
+ // Give the Web UI the chance to override our title.
+ const string16& title = our_web_ui->overridden_title();
+ if (!title.empty())
+ return title;
+ }
+ }
+
+ // We use the title for the last committed entry rather than a pending
+ // navigation entry. For example, when the user types in a URL, we want to
+ // keep the old page's title until the new load has committed and we get a new
+ // title.
+ entry = controller_.GetLastCommittedEntry();
+ if (entry) {
+ return entry->GetTitleForDisplay(profile()->GetPrefs()->
+ GetString(prefs::kAcceptLanguages));
+ }
+ return EmptyString16();
+}
+
+int32 TabContents::GetMaxPageID() {
+ if (GetSiteInstance())
+ return GetSiteInstance()->max_page_id();
+ else
+ return max_page_id_;
+}
+
+void TabContents::UpdateMaxPageID(int32 page_id) {
+ // Ensure both the SiteInstance and RenderProcessHost update their max page
+ // IDs in sync. Only TabContents will also have site instances, except during
+ // testing.
+ if (GetSiteInstance())
+ GetSiteInstance()->UpdateMaxPageID(page_id);
+ GetRenderProcessHost()->UpdateMaxPageID(page_id);
+}
+
+SiteInstance* TabContents::GetSiteInstance() const {
+ return render_manager_.current_host()->site_instance();
+}
+
+bool TabContents::ShouldDisplayURL() {
+ // Don't hide the url in view source mode and with interstitials.
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ if (entry && (entry->IsViewSourceMode() ||
+ entry->page_type() == INTERSTITIAL_PAGE)) {
+ return true;
+ }
+
+ // We always display the URL for non-WebUI URLs to prevent spoofing.
+ if (entry && !WebUIFactory::HasWebUIScheme(entry->url()))
+ return true;
+
+ WebUI* web_ui = GetWebUIForCurrentState();
+ if (web_ui)
+ return !web_ui->should_hide_url();
+ return true;
+}
+
+SkBitmap TabContents::GetFavIcon() const {
+ // Like GetTitle(), we also want to use the favicon for the last committed
+ // entry rather than a pending navigation entry.
+ NavigationEntry* entry = controller_.GetTransientEntry();
+ if (entry)
+ return entry->favicon().bitmap();
+
+ entry = controller_.GetLastCommittedEntry();
+ if (entry)
+ return entry->favicon().bitmap();
+ return SkBitmap();
+}
+
+bool TabContents::FavIconIsValid() const {
+ NavigationEntry* entry = controller_.GetTransientEntry();
+ if (entry)
+ return entry->favicon().is_valid();
+
+ entry = controller_.GetLastCommittedEntry();
+ if (entry)
+ return entry->favicon().is_valid();
+
+ return false;
+}
+
+bool TabContents::ShouldDisplayFavIcon() {
+ // Always display a throbber during pending loads.
+ if (controller_.GetLastCommittedEntry() && controller_.pending_entry())
+ return true;
+
+ WebUI* web_ui = GetWebUIForCurrentState();
+ if (web_ui)
+ return !web_ui->hide_favicon();
+ return true;
+}
+
+void TabContents::AddObserver(TabContentsObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void TabContents::RemoveObserver(TabContentsObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void TabContents::SetIsCrashed(base::TerminationStatus status, int error_code) {
+ if (status == crashed_status_)
+ return;
+
+ crashed_status_ = status;
+ crashed_error_code_ = error_code;
+ NotifyNavigationStateChanged(INVALIDATE_TAB);
+}
+
+void TabContents::PageActionStateChanged() {
+ NotifyNavigationStateChanged(TabContents::INVALIDATE_PAGE_ACTIONS);
+}
+
+void TabContents::NotifyNavigationStateChanged(unsigned changed_flags) {
+ if (delegate_)
+ delegate_->NavigationStateChanged(this, changed_flags);
+}
+
+void TabContents::DidBecomeSelected() {
+ controller_.SetActive(true);
+ RenderWidgetHostView* rwhv = GetRenderWidgetHostView();
+ if (rwhv) {
+ rwhv->DidBecomeSelected();
+#if defined(OS_MACOSX)
+ rwhv->SetActive(true);
+#endif
+ }
+
+ WebCacheManager::GetInstance()->ObserveActivity(GetRenderProcessHost()->id());
+ last_selected_time_ = base::TimeTicks::Now();
+#if defined(OS_CHROMEOS)
+ chromeos::LocaleChangeGuard::Check(this);
+#endif
+}
+
+void TabContents::FadeForInstant(bool animate) {
+ RenderWidgetHostView* rwhv = GetRenderWidgetHostView();
+ SkColor whitish = SkColorSetARGB(192, 255, 255, 255);
+ if (rwhv)
+ rwhv->SetVisuallyDeemphasized(&whitish, animate);
+}
+
+void TabContents::CancelInstantFade() {
+ RenderWidgetHostView* rwhv = GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->SetVisuallyDeemphasized(NULL, false);
+}
+
+void TabContents::WasHidden() {
+ if (!capturing_contents()) {
+ // |render_view_host()| can be NULL if the user middle clicks a link to open
+ // a tab in then background, then closes the tab before selecting it. This
+ // is because closing the tab calls TabContents::Destroy(), which removes
+ // the |render_view_host()|; then when we actually destroy the window,
+ // OnWindowPosChanged() notices and calls HideContents() (which calls us).
+ RenderWidgetHostView* rwhv = GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->WasHidden();
+ }
+
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_HIDDEN,
+ Source<TabContents>(this),
+ NotificationService::NoDetails());
+}
+
+void TabContents::Activate() {
+ if (delegate_)
+ delegate_->ActivateContents(this);
+}
+
+void TabContents::Deactivate() {
+ if (delegate_)
+ delegate_->DeactivateContents(this);
+}
+
+void TabContents::ShowContents() {
+ RenderWidgetHostView* rwhv = GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->DidBecomeSelected();
+}
+
+void TabContents::HideContents() {
+ // TODO(pkasting): http://b/1239839 Right now we purposefully don't call
+ // our superclass HideContents(), because some callers want to be very picky
+ // about the order in which these get called. In addition to making the code
+ // here practically impossible to understand, this also means we end up
+ // calling TabContents::WasHidden() twice if callers call both versions of
+ // HideContents() on a TabContents.
+ WasHidden();
+}
+
+bool TabContents::NeedToFireBeforeUnload() {
+ // TODO(creis): Should we fire even for interstitial pages?
+ return notify_disconnection() &&
+ !showing_interstitial_page() &&
+ !render_view_host()->SuddenTerminationAllowed();
+}
+
+void TabContents::OpenURL(const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition) {
+ if (delegate_)
+ delegate_->OpenURLFromTab(this, url, referrer, disposition, transition);
+}
+
+bool TabContents::NavigateToPendingEntry(
+ NavigationController::ReloadType reload_type) {
+ return NavigateToEntry(*controller_.pending_entry(), reload_type);
+}
+
+bool TabContents::NavigateToEntry(
+ const NavigationEntry& entry,
+ NavigationController::ReloadType reload_type) {
+ RenderViewHost* dest_render_view_host = render_manager_.Navigate(entry);
+ if (!dest_render_view_host)
+ return false; // Unable to create the desired render view host.
+
+ if (delegate_ && delegate_->ShouldEnablePreferredSizeNotifications()) {
+ dest_render_view_host->EnablePreferredSizeChangedMode(
+ kPreferredSizeWidth | kPreferredSizeHeightThisIsSlow);
+ }
+
+ // For security, we should never send non-Web-UI URLs to a Web UI renderer.
+ // Double check that here.
+ int enabled_bindings = dest_render_view_host->enabled_bindings();
+ bool is_allowed_in_web_ui_renderer =
+ WebUIFactory::IsURLAcceptableForWebUI(profile(), entry.url());
+ CHECK(!BindingsPolicy::is_web_ui_enabled(enabled_bindings) ||
+ is_allowed_in_web_ui_renderer);
+
+ // Tell DevTools agent that it is attached prior to the navigation.
+ DevToolsManager* devtools_manager = DevToolsManager::GetInstance();
+ if (devtools_manager) { // NULL in unit tests.
+ devtools_manager->OnNavigatingToPendingEntry(render_view_host(),
+ dest_render_view_host,
+ entry.url());
+ }
+
+ // Used for page load time metrics.
+ current_load_start_ = base::TimeTicks::Now();
+
+ // Navigate in the desired RenderViewHost.
+ ViewMsg_Navigate_Params navigate_params;
+ MakeNavigateParams(entry, controller_, reload_type, &navigate_params);
+ if (delegate_) {
+ navigate_params.extra_headers =
+ delegate_->GetNavigationHeaders(navigate_params.url);
+ }
+ dest_render_view_host->Navigate(navigate_params);
+
+ if (entry.page_id() == -1) {
+ // HACK!! This code suppresses javascript: URLs from being added to
+ // session history, which is what we want to do for javascript: URLs that
+ // do not generate content. What we really need is a message from the
+ // renderer telling us that a new page was not created. The same message
+ // could be used for mailto: URLs and the like.
+ if (entry.url().SchemeIs(chrome::kJavaScriptScheme))
+ return false;
+ }
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(TabContentsObserver, observers_, NavigateToPendingEntry());
+
+ if (reload_type != NavigationController::NO_RELOAD &&
+ !profile()->IsOffTheRecord()) {
+ FaviconService* favicon_service =
+ profile()->GetFaviconService(Profile::IMPLICIT_ACCESS);
+ if (favicon_service)
+ favicon_service->SetFaviconOutOfDateForPage(entry.url());
+ }
+
+ return true;
+}
+
+void TabContents::Stop() {
+ render_manager_.Stop();
+ printing_->Stop();
+}
+
+void TabContents::DisassociateFromPopupCount() {
+ render_view_host()->DisassociateFromPopupCount();
+}
+
+TabContents* TabContents::Clone() {
+ // We create a new SiteInstance so that the new tab won't share processes
+ // with the old one. This can be changed in the future if we need it to share
+ // processes for some reason.
+ TabContents* tc = new TabContents(profile(),
+ SiteInstance::CreateSiteInstance(profile()),
+ MSG_ROUTING_NONE, this, NULL);
+ tc->controller().CopyStateFrom(controller_);
+ tc->extension_app_ = extension_app_;
+ tc->extension_app_icon_ = extension_app_icon_;
+ return tc;
+}
+
+void TabContents::ShowPageInfo(const GURL& url,
+ const NavigationEntry::SSLStatus& ssl,
+ bool show_history) {
+ if (!delegate_)
+ return;
+
+ delegate_->ShowPageInfo(profile(), url, ssl, show_history);
+}
+
+void TabContents::SaveFavicon() {
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ if (!entry || entry->url().is_empty())
+ return;
+
+ // Make sure the page is in history, otherwise adding the favicon does
+ // nothing.
+ HistoryService* history = profile()->GetOriginalProfile()->GetHistoryService(
+ Profile::IMPLICIT_ACCESS);
+ if (!history)
+ return;
+ history->AddPageNoVisitForBookmark(entry->url());
+
+ FaviconService* service = profile()->GetOriginalProfile()->GetFaviconService(
+ Profile::IMPLICIT_ACCESS);
+ if (!service)
+ return;
+ const NavigationEntry::FaviconStatus& favicon(entry->favicon());
+ if (!favicon.is_valid() || favicon.url().is_empty() ||
+ favicon.bitmap().empty()) {
+ return;
+ }
+ std::vector<unsigned char> image_data;
+ gfx::PNGCodec::EncodeBGRASkBitmap(favicon.bitmap(), false, &image_data);
+ service->SetFavicon(entry->url(), favicon.url(), image_data);
+}
+
+ConstrainedWindow* TabContents::CreateConstrainedDialog(
+ ConstrainedWindowDelegate* delegate) {
+ ConstrainedWindow* window =
+ ConstrainedWindow::CreateConstrainedDialog(this, delegate);
+ child_windows_.push_back(window);
+
+ if (child_windows_.size() == 1) {
+ window->ShowConstrainedWindow();
+ BlockTabContent(true);
+ }
+
+ return window;
+}
+
+void TabContents::BlockTabContent(bool blocked) {
+ RenderWidgetHostView* rwhv = GetRenderWidgetHostView();
+ // 70% opaque grey.
+ SkColor greyish = SkColorSetARGB(178, 0, 0, 0);
+ if (rwhv)
+ rwhv->SetVisuallyDeemphasized(blocked ? &greyish : NULL, false);
+ render_view_host()->set_ignore_input_events(blocked);
+ if (delegate_)
+ delegate_->SetTabContentBlocked(this, blocked);
+}
+
+void TabContents::AddNewContents(TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ if (all_contents_blocked_) {
+ if (!blocked_contents_)
+ blocked_contents_ = new BlockedContentContainer(this);
+ blocked_contents_->AddTabContents(
+ new_contents, disposition, initial_pos, user_gesture);
+ return;
+ }
+
+ if (!delegate_)
+ return;
+
+ if ((disposition == NEW_POPUP) && !user_gesture &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisablePopupBlocking)) {
+ // Unrequested popups from normal pages are constrained unless they're in
+ // the whitelist. The popup owner will handle checking this.
+ delegate_->GetConstrainingContents(this)->AddPopup(
+ new_contents, initial_pos);
+ } else {
+ new_contents->DisassociateFromPopupCount();
+ delegate_->AddNewContents(this, new_contents, disposition, initial_pos,
+ user_gesture);
+ NotificationService::current()->Notify(
+ NotificationType::TAB_ADDED,
+ Source<TabContentsDelegate>(delegate_),
+ Details<TabContents>(this));
+ }
+
+ // TODO(pkasting): Why is this necessary?
+ PopupNotificationVisibilityChanged(blocked_contents_ != NULL);
+}
+
+bool TabContents::ExecuteCode(int request_id, const std::string& extension_id,
+ bool is_js_code, const std::string& code_string,
+ bool all_frames) {
+ RenderViewHost* host = render_view_host();
+ if (!host)
+ return false;
+
+ return host->Send(new ViewMsg_ExecuteCode(host->routing_id(),
+ ViewMsg_ExecuteCode_Params(request_id, extension_id,
+ is_js_code, code_string, all_frames)));
+}
+
+void TabContents::PopupNotificationVisibilityChanged(bool visible) {
+ if (is_being_destroyed_)
+ return;
+ content_settings_delegate_->SetPopupsBlocked(visible);
+ if (!dont_notify_render_view_)
+ render_view_host()->AllowScriptToClose(!visible);
+}
+
+gfx::NativeView TabContents::GetContentNativeView() const {
+ return view_->GetContentNativeView();
+}
+
+gfx::NativeView TabContents::GetNativeView() const {
+ return view_->GetNativeView();
+}
+
+void TabContents::GetContainerBounds(gfx::Rect *out) const {
+ view_->GetContainerBounds(out);
+}
+
+void TabContents::Focus() {
+ view_->Focus();
+}
+
+void TabContents::FocusThroughTabTraversal(bool reverse) {
+ if (showing_interstitial_page()) {
+ render_manager_.interstitial_page()->FocusThroughTabTraversal(reverse);
+ return;
+ }
+ render_view_host()->SetInitialFocus(reverse);
+}
+
+bool TabContents::FocusLocationBarByDefault() {
+ WebUI* web_ui = GetWebUIForCurrentState();
+ if (web_ui)
+ return web_ui->focus_location_bar_by_default();
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ if (entry && entry->url() == GURL(chrome::kAboutBlankURL))
+ return true;
+ return false;
+}
+
+void TabContents::SetFocusToLocationBar(bool select_all) {
+ if (delegate())
+ delegate()->SetFocusToLocationBar(select_all);
+}
+
+void TabContents::AddInfoBar(InfoBarDelegate* delegate) {
+ if (delegate_ && !delegate_->infobars_enabled()) {
+ delegate->InfoBarClosed();
+ return;
+ }
+
+ // Look through the existing InfoBarDelegates we have for a match. If we've
+ // already got one that matches, then we don't add the new one.
+ for (size_t i = 0; i < infobar_count(); ++i) {
+ if (GetInfoBarDelegateAt(i)->EqualsDelegate(delegate)) {
+ // Tell the new infobar to close so that it can clean itself up.
+ delegate->InfoBarClosed();
+ return;
+ }
+ }
+
+ infobar_delegates_.push_back(delegate);
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_INFOBAR_ADDED, Source<TabContents>(this),
+ Details<InfoBarDelegate>(delegate));
+
+ // Add ourselves as an observer for navigations the first time a delegate is
+ // added. We use this notification to expire InfoBars that need to expire on
+ // page transitions.
+ if (infobar_delegates_.size() == 1) {
+ registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(&controller_));
+ }
+}
+
+void TabContents::RemoveInfoBar(InfoBarDelegate* delegate) {
+ if (delegate_ && !delegate_->infobars_enabled()) {
+ return;
+ }
+
+ std::vector<InfoBarDelegate*>::iterator it =
+ find(infobar_delegates_.begin(), infobar_delegates_.end(), delegate);
+ if (it != infobar_delegates_.end()) {
+ InfoBarDelegate* delegate = *it;
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_INFOBAR_REMOVED,
+ Source<TabContents>(this),
+ Details<InfoBarDelegate>(delegate));
+
+ infobar_delegates_.erase(it);
+ // Remove ourselves as an observer if we are tracking no more InfoBars.
+ if (infobar_delegates_.empty()) {
+ registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(&controller_));
+ }
+ }
+}
+
+void TabContents::ReplaceInfoBar(InfoBarDelegate* old_delegate,
+ InfoBarDelegate* new_delegate) {
+ if (delegate_ && !delegate_->infobars_enabled()) {
+ new_delegate->InfoBarClosed();
+ return;
+ }
+
+ std::vector<InfoBarDelegate*>::iterator it =
+ find(infobar_delegates_.begin(), infobar_delegates_.end(), old_delegate);
+ DCHECK(it != infobar_delegates_.end());
+
+ // Notify the container about the change of plans.
+ scoped_ptr<std::pair<InfoBarDelegate*, InfoBarDelegate*> > details(
+ new std::pair<InfoBarDelegate*, InfoBarDelegate*>(
+ old_delegate, new_delegate));
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_INFOBAR_REPLACED,
+ Source<TabContents>(this),
+ Details<std::pair<InfoBarDelegate*, InfoBarDelegate*> >(details.get()));
+
+ // Remove the old one.
+ infobar_delegates_.erase(it);
+
+ // Add the new one.
+ DCHECK(find(infobar_delegates_.begin(),
+ infobar_delegates_.end(), new_delegate) ==
+ infobar_delegates_.end());
+ infobar_delegates_.push_back(new_delegate);
+}
+
+bool TabContents::ShouldShowBookmarkBar() {
+ if (showing_interstitial_page())
+ return false;
+
+ // Do not show bookmarks bar if bookmarks aren't enabled.
+ if (!browser_defaults::bookmarks_enabled)
+ return false;
+
+ // See GetWebUIForCurrentState() comment for more info. This case is very
+ // similar, but for non-first loads, we want to use the committed entry. This
+ // is so the bookmarks bar disappears at the same time the page does.
+ if (controller_.GetLastCommittedEntry()) {
+ // Not the first load, always use the committed Web UI.
+ return (render_manager_.web_ui() == NULL) ?
+ false : render_manager_.web_ui()->force_bookmark_bar_visible();
+ }
+
+ // When it's the first load, we know either the pending one or the committed
+ // one will have the Web UI in it (see GetWebUIForCurrentState), and only one
+ // of them will be valid, so we can just check both.
+ if (render_manager_.pending_web_ui())
+ return render_manager_.pending_web_ui()->force_bookmark_bar_visible();
+ return (render_manager_.web_ui() == NULL) ?
+ false : render_manager_.web_ui()->force_bookmark_bar_visible();
+}
+
+void TabContents::ToolbarSizeChanged(bool is_animating) {
+ TabContentsDelegate* d = delegate();
+ if (d)
+ d->ToolbarSizeChanged(this, is_animating);
+}
+
+bool TabContents::CanDownload(int request_id) {
+ TabContentsDelegate* d = delegate();
+ if (d)
+ return d->CanDownload(request_id);
+ return true;
+}
+
+void TabContents::OnStartDownload(DownloadItem* download) {
+ DCHECK(download);
+
+ // Download in a constrained popup is shown in the tab that opened it.
+ TabContents* tab_contents = delegate()->GetConstrainingContents(this);
+
+ if (tab_contents && tab_contents->delegate())
+ tab_contents->delegate()->OnStartDownload(download, this);
+}
+
+void TabContents::WillClose(ConstrainedWindow* window) {
+ ConstrainedWindowList::iterator i(
+ std::find(child_windows_.begin(), child_windows_.end(), window));
+ bool removed_topmost_window = i == child_windows_.begin();
+ if (i != child_windows_.end())
+ child_windows_.erase(i);
+ if (child_windows_.empty()) {
+ BlockTabContent(false);
+ } else {
+ if (removed_topmost_window)
+ child_windows_[0]->ShowConstrainedWindow();
+ BlockTabContent(true);
+ }
+}
+
+void TabContents::WillCloseBlockedContentContainer(
+ BlockedContentContainer* container) {
+ DCHECK(blocked_contents_ == container);
+ blocked_contents_ = NULL;
+ PopupNotificationVisibilityChanged(false);
+}
+
+void TabContents::DidMoveOrResize(ConstrainedWindow* window) {
+#if defined(OS_WIN)
+ UpdateWindow(GetNativeView());
+#endif
+}
+
+void TabContents::OnSavePage() {
+ // If we can not save the page, try to download it.
+ if (!SavePackage::IsSavableContents(contents_mime_type())) {
+ DownloadManager* dlm = profile()->GetDownloadManager();
+ const GURL& current_page_url = GetURL();
+ if (dlm && current_page_url.is_valid())
+ dlm->DownloadUrl(current_page_url, GURL(), "", this);
+ return;
+ }
+
+ Stop();
+
+ // Create the save package and possibly prompt the user for the name to save
+ // the page as. The user prompt is an asynchronous operation that runs on
+ // another thread.
+ save_package_ = new SavePackage(this);
+ save_package_->GetSaveInfo();
+}
+
+// Used in automated testing to bypass prompting the user for file names.
+// Instead, the names and paths are hard coded rather than running them through
+// file name sanitation and extension / mime checking.
+bool TabContents::SavePage(const FilePath& main_file, const FilePath& dir_path,
+ SavePackage::SavePackageType save_type) {
+ // Stop the page from navigating.
+ Stop();
+
+ save_package_ = new SavePackage(this, save_type, main_file, dir_path);
+ return save_package_->Init();
+}
+
+void TabContents::EmailPageLocation() {
+ std::string title = EscapeQueryParamValue(UTF16ToUTF8(GetTitle()), false);
+ std::string page_url = EscapeQueryParamValue(GetURL().spec(), false);
+ std::string mailto = std::string("mailto:?subject=Fwd:%20") +
+ title + "&body=%0A%0A" + page_url;
+ platform_util::OpenExternal(GURL(mailto));
+}
+
+void TabContents::PrintPreview() {
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnablePrintPreview)) {
+ if (showing_interstitial_page())
+ return;
+
+ printing::PrintPreviewTabController* tab_controller =
+ printing::PrintPreviewTabController::GetInstance();
+ if (!tab_controller)
+ return;
+ tab_controller->GetOrCreatePreviewTab(this, controller().window_id().id());
+
+ render_view_host()->PrintPreview();
+ } else {
+ PrintNow();
+ }
+}
+
+bool TabContents::PrintNow() {
+ // We can't print interstitial page for now.
+ if (showing_interstitial_page())
+ return false;
+
+ return render_view_host()->PrintPages();
+}
+
+void TabContents::PrintingDone(int document_cookie, bool success) {
+ render_view_host()->PrintingDone(document_cookie, success);
+}
+
+bool TabContents::IsActiveEntry(int32 page_id) {
+ NavigationEntry* active_entry = controller_.GetActiveEntry();
+ return (active_entry != NULL &&
+ active_entry->site_instance() == GetSiteInstance() &&
+ active_entry->page_id() == page_id);
+}
+
+void TabContents::SetOverrideEncoding(const std::string& encoding) {
+ set_encoding(encoding);
+ render_view_host()->SetPageEncoding(encoding);
+}
+
+void TabContents::ResetOverrideEncoding() {
+ reset_encoding();
+ render_view_host()->ResetPageEncodingToDefault();
+}
+
+void TabContents::WindowMoveOrResizeStarted() {
+ render_view_host()->WindowMoveOrResizeStarted();
+}
+
+void TabContents::SetAllContentsBlocked(bool value) {
+ if (all_contents_blocked_ == value)
+ return;
+
+ all_contents_blocked_ = value;
+ if (!all_contents_blocked_ && blocked_contents_) {
+ std::vector<TabContents*> blocked;
+ blocked_contents_->GetBlockedContents(&blocked);
+ for (size_t i = 0; i < blocked.size(); ++i)
+ blocked_contents_->LaunchForContents(blocked[i]);
+ }
+}
+
+void TabContents::LogNewTabTime(const std::string& event_name) {
+ // Not all new tab pages get timed. In those cases, we don't have a
+ // new_tab_start_time_.
+ if (new_tab_start_time_.is_null())
+ return;
+
+ base::TimeDelta duration = base::TimeTicks::Now() - new_tab_start_time_;
+ MetricEventDurationDetails details(event_name,
+ static_cast<int>(duration.InMilliseconds()));
+
+ if (event_name == "Tab.NewTabScriptStart") {
+ UMA_HISTOGRAM_TIMES("Tab.NewTabScriptStart", duration);
+ } else if (event_name == "Tab.NewTabDOMContentLoaded") {
+ UMA_HISTOGRAM_TIMES("Tab.NewTabDOMContentLoaded", duration);
+ } else if (event_name == "Tab.NewTabOnload") {
+ UMA_HISTOGRAM_TIMES("Tab.NewTabOnload", duration);
+ // The new tab page has finished loading; reset it.
+ new_tab_start_time_ = base::TimeTicks();
+ } else {
+ NOTREACHED();
+ }
+ NotificationService::current()->Notify(
+ NotificationType::METRIC_EVENT_DURATION,
+ Source<TabContents>(this),
+ Details<MetricEventDurationDetails>(&details));
+}
+
+void TabContents::OnCloseStarted() {
+ if (tab_close_start_time_.is_null())
+ tab_close_start_time_ = base::TimeTicks::Now();
+}
+
+bool TabContents::ShouldAcceptDragAndDrop() const {
+#if defined(OS_CHROMEOS)
+ // ChromeOS panels (pop-ups) do not take drag-n-drop.
+ // See http://crosbug.com/2413
+ if (delegate() && delegate()->IsPopup(this))
+ return false;
+ return true;
+#else
+ return true;
+#endif
+}
+
+void TabContents::SystemDragEnded() {
+ if (render_view_host())
+ render_view_host()->DragSourceSystemDragEnded();
+ if (delegate())
+ delegate()->DragEnded();
+}
+
+void TabContents::UpdateHistoryForNavigation(
+ scoped_refptr<history::HistoryAddPageArgs> add_page_args) {
+ if (profile()->IsOffTheRecord())
+ return;
+
+ // Add to history service.
+ HistoryService* hs = profile()->GetHistoryService(Profile::IMPLICIT_ACCESS);
+ if (hs)
+ hs->AddPage(*add_page_args);
+}
+
+void TabContents::UpdateHistoryPageTitle(const NavigationEntry& entry) {
+ if (profile()->IsOffTheRecord())
+ return;
+
+ HistoryService* hs = profile()->GetHistoryService(Profile::IMPLICIT_ACCESS);
+ if (hs)
+ hs->SetPageTitle(entry.virtual_url(), entry.title());
+}
+
+double TabContents::GetZoomLevel() const {
+ HostZoomMap* zoom_map = profile()->GetHostZoomMap();
+ if (!zoom_map)
+ return 0;
+
+ double zoom_level;
+ if (temporary_zoom_settings_) {
+ zoom_level = zoom_map->GetTemporaryZoomLevel(
+ GetRenderProcessHost()->id(), render_view_host()->routing_id());
+ } else {
+ zoom_level = zoom_map->GetZoomLevel(GetURL());
+ }
+ return zoom_level;
+}
+
+int TabContents::GetZoomPercent(bool* enable_increment,
+ bool* enable_decrement) {
+ *enable_decrement = *enable_increment = false;
+ int percent = static_cast<int>(
+ WebKit::WebView::zoomLevelToZoomFactor(GetZoomLevel()) * 100);
+ *enable_decrement = percent > minimum_zoom_percent_;
+ *enable_increment = percent < maximum_zoom_percent_;
+ return percent;
+}
+
+void TabContents::ViewSource() {
+ if (!delegate_)
+ return;
+
+ NavigationEntry* active_entry = controller().GetActiveEntry();
+ if (!active_entry)
+ return;
+
+ delegate_->ViewSourceForTab(this, active_entry->url());
+}
+
+void TabContents::OnDidStartProvisionalLoadForFrame(int64 frame_id,
+ bool is_main_frame,
+ const GURL& url) {
+ bool is_error_page = (url.spec() == chrome::kUnreachableWebDataURL);
+ GURL validated_url(url);
+ render_view_host()->FilterURL(ChildProcessSecurityPolicy::GetInstance(),
+ GetRenderProcessHost()->id(), &validated_url);
+
+ ProvisionalLoadDetails details(
+ is_main_frame,
+ controller_.IsURLInPageNavigation(validated_url),
+ validated_url, std::string(), false, is_error_page, frame_id);
+ NotificationService::current()->Notify(
+ NotificationType::FRAME_PROVISIONAL_LOAD_START,
+ Source<NavigationController>(&controller_),
+ Details<ProvisionalLoadDetails>(&details));
+ if (is_main_frame) {
+ // If we're displaying a network error page do not reset the content
+ // settings delegate's cookies so the user has a chance to modify cookie
+ // settings.
+ if (!is_error_page)
+ content_settings_delegate_->ClearCookieSpecificContentSettings();
+ content_settings_delegate_->ClearGeolocationContentSettings();
+
+ // Check if the URL we are about to load has been prerendered by any chance,
+ // and use it if possible.
+ MaybeUsePreloadedPage(url);
+ }
+}
+
+void TabContents::OnDidRedirectProvisionalLoad(int32 page_id,
+ const GURL& source_url,
+ const GURL& target_url) {
+ NavigationEntry* entry;
+ if (page_id == -1)
+ entry = controller_.pending_entry();
+ else
+ entry = controller_.GetEntryWithPageID(GetSiteInstance(), page_id);
+ if (!entry || entry->url() != source_url)
+ return;
+ entry->set_url(target_url);
+
+ // Check if the URL we are about to load has been prerendered by any chance,
+ // and use it if possible.
+ MaybeUsePreloadedPage(target_url);
+}
+
+void TabContents::OnDidFailProvisionalLoadWithError(
+ int64 frame_id,
+ bool is_main_frame,
+ int error_code,
+ const GURL& url,
+ bool showing_repost_interstitial) {
+ VLOG(1) << "Failed Provisional Load: " << url.possibly_invalid_spec()
+ << ", error_code: " << error_code
+ << " is_main_frame: " << is_main_frame
+ << " showing_repost_interstitial: " << showing_repost_interstitial
+ << " frame_id: " << frame_id;
+ GURL validated_url(url);
+ render_view_host()->FilterURL(ChildProcessSecurityPolicy::GetInstance(),
+ GetRenderProcessHost()->id(), &validated_url);
+
+ if (net::ERR_ABORTED == error_code) {
+ // EVIL HACK ALERT! Ignore failed loads when we're showing interstitials.
+ // This means that the interstitial won't be torn down properly, which is
+ // bad. But if we have an interstitial, go back to another tab type, and
+ // then load the same interstitial again, we could end up getting the first
+ // interstitial's "failed" message (as a result of the cancel) when we're on
+ // the second one.
+ //
+ // We can't tell this apart, so we think we're tearing down the current page
+ // which will cause a crash later one. There is also some code in
+ // RenderViewHostManager::RendererAbortedProvisionalLoad that is commented
+ // out because of this problem.
+ //
+ // http://code.google.com/p/chromium/issues/detail?id=2855
+ // Because this will not tear down the interstitial properly, if "back" is
+ // back to another tab type, the interstitial will still be somewhat alive
+ // in the previous tab type. If you navigate somewhere that activates the
+ // tab with the interstitial again, you'll see a flash before the new load
+ // commits of the interstitial page.
+ if (showing_interstitial_page()) {
+ LOG(WARNING) << "Discarding message during interstitial.";
+ return;
+ }
+
+ // This will discard our pending entry if we cancelled the load (e.g., if we
+ // decided to download the file instead of load it). Only discard the
+ // pending entry if the URLs match, otherwise the user initiated a navigate
+ // before the page loaded so that the discard would discard the wrong entry.
+ NavigationEntry* pending_entry = controller_.pending_entry();
+ if (pending_entry && pending_entry->url() == validated_url) {
+ controller_.DiscardNonCommittedEntries();
+ // Update the URL display.
+ NotifyNavigationStateChanged(TabContents::INVALIDATE_URL);
+ }
+
+ render_manager_.RendererAbortedProvisionalLoad(render_view_host());
+ }
+
+ // Send out a notification that we failed a provisional load with an error.
+ ProvisionalLoadDetails details(
+ is_main_frame, controller_.IsURLInPageNavigation(validated_url),
+ validated_url, std::string(), false, false, frame_id);
+ details.set_error_code(error_code);
+
+ NotificationService::current()->Notify(
+ NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR,
+ Source<NavigationController>(&controller_),
+ Details<ProvisionalLoadDetails>(&details));
+}
+
+void TabContents::OnDidLoadResourceFromMemoryCache(
+ const GURL& url,
+ const std::string& security_info) {
+ static base::StatsCounter cache("WebKit.CacheHit");
+ cache.Increment();
+
+ // Send out a notification that we loaded a resource from our memory cache.
+ int cert_id = 0, cert_status = 0, security_bits = -1, connection_status = 0;
+ SSLManager::DeserializeSecurityInfo(security_info,
+ &cert_id, &cert_status,
+ &security_bits,
+ &connection_status);
+ LoadFromMemoryCacheDetails details(url, GetRenderProcessHost()->id(),
+ cert_id, cert_status);
+
+ NotificationService::current()->Notify(
+ NotificationType::LOAD_FROM_MEMORY_CACHE,
+ Source<NavigationController>(&controller_),
+ Details<LoadFromMemoryCacheDetails>(&details));
+}
+
+void TabContents::OnDidDisplayInsecureContent() {
+ displayed_insecure_content_ = true;
+ SSLManager::NotifySSLInternalStateChanged();
+}
+
+void TabContents::OnDidRunInsecureContent(
+ const std::string& security_origin, const GURL& target_url) {
+ LOG(INFO) << security_origin << " ran insecure content from "
+ << target_url.possibly_invalid_spec();
+ controller_.ssl_manager()->DidRunInsecureContent(security_origin);
+}
+
+void TabContents::OnDocumentLoadedInFrame(int64 frame_id) {
+ controller_.DocumentLoadedInFrame();
+ NotificationService::current()->Notify(
+ NotificationType::FRAME_DOM_CONTENT_LOADED,
+ Source<NavigationController>(&controller_),
+ Details<int64>(&frame_id));
+}
+
+void TabContents::OnDidFinishLoad(int64 frame_id) {
+ NotificationService::current()->Notify(
+ NotificationType::FRAME_DID_FINISH_LOAD,
+ Source<NavigationController>(&controller_),
+ Details<int64>(&frame_id));
+}
+
+void TabContents::OnUpdateContentRestrictions(int restrictions) {
+ content_restrictions_ = restrictions;
+ delegate()->ContentRestrictionsChanged(this);
+}
+
+void TabContents::OnPDFHasUnsupportedFeature() {
+ PDFHasUnsupportedFeature(this);
+}
+
+// Notifies the RenderWidgetHost instance about the fact that the page is
+// loading, or done loading and calls the base implementation.
+void TabContents::SetIsLoading(bool is_loading,
+ LoadNotificationDetails* details) {
+ if (is_loading == is_loading_)
+ return;
+
+ if (!is_loading) {
+ load_state_ = net::LOAD_STATE_IDLE;
+ load_state_host_.clear();
+ upload_size_ = 0;
+ upload_position_ = 0;
+ }
+
+ render_manager_.SetIsLoading(is_loading);
+
+ is_loading_ = is_loading;
+ waiting_for_response_ = is_loading;
+
+ if (delegate_)
+ delegate_->LoadingStateChanged(this);
+ NotifyNavigationStateChanged(INVALIDATE_LOAD);
+
+ NotificationType type = is_loading ? NotificationType::LOAD_START :
+ NotificationType::LOAD_STOP;
+ NotificationDetails det = NotificationService::NoDetails();
+ if (details)
+ det = Details<LoadNotificationDetails>(details);
+ NotificationService::current()->Notify(type,
+ Source<NavigationController>(&controller_),
+ det);
+}
+
+void TabContents::AddPopup(TabContents* new_contents,
+ const gfx::Rect& initial_pos) {
+ // A page can't spawn popups (or do anything else, either) until its load
+ // commits, so when we reach here, the popup was spawned by the
+ // NavigationController's last committed entry, not the active entry. For
+ // example, if a page opens a popup in an onunload() handler, then the active
+ // entry is the page to be loaded as we navigate away from the unloading
+ // page. For this reason, we can't use GetURL() to get the opener URL,
+ // because it returns the active entry.
+ NavigationEntry* entry = controller_.GetLastCommittedEntry();
+ GURL creator = entry ? entry->virtual_url() : GURL::EmptyGURL();
+
+ if (creator.is_valid() &&
+ profile()->GetHostContentSettingsMap()->GetContentSetting(
+ creator, CONTENT_SETTINGS_TYPE_POPUPS, "") == CONTENT_SETTING_ALLOW) {
+ AddNewContents(new_contents, NEW_POPUP, initial_pos, true);
+ } else {
+ if (!blocked_contents_)
+ blocked_contents_ = new BlockedContentContainer(this);
+ blocked_contents_->AddTabContents(new_contents, NEW_POPUP, initial_pos,
+ true);
+ content_settings_delegate_->OnContentBlocked(CONTENT_SETTINGS_TYPE_POPUPS,
+ std::string());
+ }
+}
+
+void TabContents::ExpireInfoBars(
+ const NavigationController::LoadCommittedDetails& details) {
+ // Only hide InfoBars when the user has done something that makes the main
+ // frame load. We don't want various automatic or subframe navigations making
+ // it disappear.
+ if (!details.is_user_initiated_main_frame_load())
+ return;
+
+ // NOTE: It is not safe to change the following code to count upwards or use
+ // iterators, as the RemoveInfoBar() call synchronously modifies our delegate
+ // list.
+ for (size_t i = infobar_count(); i > 0; --i) {
+ InfoBarDelegate* delegate = GetInfoBarDelegateAt(i - 1);
+ if (delegate->ShouldExpire(details))
+ RemoveInfoBar(delegate);
+ }
+}
+
+WebUI* TabContents::GetWebUIForCurrentState() {
+ // When there is a pending navigation entry, we want to use the pending WebUI
+ // that goes along with it to control the basic flags. For example, we want to
+ // show the pending URL in the URL bar, so we want the display_url flag to
+ // be from the pending entry.
+ //
+ // The confusion comes because there are multiple possibilities for the
+ // initial load in a tab as a side effect of the way the RenderViewHostManager
+ // works.
+ //
+ // - For the very first tab the load looks "normal". The new tab Web UI is
+ // the pending one, and we want it to apply here.
+ //
+ // - For subsequent new tabs, they'll get a new SiteInstance which will then
+ // get switched to the one previously associated with the new tab pages.
+ // This switching will cause the manager to commit the RVH/WebUI. So we'll
+ // have a committed Web UI in this case.
+ //
+ // This condition handles all of these cases:
+ //
+ // - First load in first tab: no committed nav entry + pending nav entry +
+ // pending dom ui:
+ // -> Use pending Web UI if any.
+ //
+ // - First load in second tab: no committed nav entry + pending nav entry +
+ // no pending Web UI:
+ // -> Use the committed Web UI if any.
+ //
+ // - Second navigation in any tab: committed nav entry + pending nav entry:
+ // -> Use pending Web UI if any.
+ //
+ // - Normal state with no load: committed nav entry + no pending nav entry:
+ // -> Use committed Web UI.
+ if (controller_.pending_entry() &&
+ (controller_.GetLastCommittedEntry() ||
+ render_manager_.pending_web_ui()))
+ return render_manager_.pending_web_ui();
+ return render_manager_.web_ui();
+}
+
+void TabContents::DidNavigateMainFramePostCommit(
+ const NavigationController::LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ if (opener_web_ui_type_ != WebUIFactory::kNoWebUI) {
+ // If this is a window.open navigation, use the same WebUI as the renderer
+ // that opened the window, as long as both renderers have the same
+ // privileges.
+ if (opener_web_ui_type_ ==
+ WebUIFactory::GetWebUIType(profile(), GetURL())) {
+ WebUI* web_ui = WebUIFactory::CreateWebUIForURL(this, GetURL());
+ // web_ui might be NULL if the URL refers to a non-existent extension.
+ if (web_ui) {
+ render_manager_.SetWebUIPostCommit(web_ui);
+ web_ui->RenderViewCreated(render_view_host());
+ }
+ }
+ opener_web_ui_type_ = WebUIFactory::kNoWebUI;
+ }
+
+ if (details.is_user_initiated_main_frame_load()) {
+ // Clear the status bubble. This is a workaround for a bug where WebKit
+ // doesn't let us know that the cursor left an element during a
+ // transition (this is also why the mouse cursor remains as a hand after
+ // clicking on a link); see bugs 1184641 and 980803. We don't want to
+ // clear the bubble when a user navigates to a named anchor in the same
+ // page.
+ UpdateTargetURL(details.entry->page_id(), GURL());
+ }
+
+ // Allow the new page to set the title again.
+ received_page_title_ = false;
+
+ // Get the favicon, either from history or request it from the net.
+ fav_icon_helper_->FetchFavIcon(details.entry->url());
+
+ // Clear all page actions, blocked content notifications and browser actions
+ // for this tab, unless this is an in-page navigation.
+ if (!details.is_in_page) {
+ ExtensionService* service = profile()->GetExtensionService();
+ if (service) {
+ for (size_t i = 0; i < service->extensions()->size(); ++i) {
+ ExtensionAction* browser_action =
+ service->extensions()->at(i)->browser_action();
+ if (browser_action) {
+ browser_action->ClearAllValuesForTab(controller().session_id().id());
+ NotificationService::current()->Notify(
+ NotificationType::EXTENSION_BROWSER_ACTION_UPDATED,
+ Source<ExtensionAction>(browser_action),
+ NotificationService::NoDetails());
+ }
+
+ ExtensionAction* page_action =
+ service->extensions()->at(i)->page_action();
+ if (page_action) {
+ page_action->ClearAllValuesForTab(controller().session_id().id());
+ PageActionStateChanged();
+ }
+ }
+ }
+
+ // Close blocked popups.
+ if (blocked_contents_) {
+ AutoReset<bool> auto_reset(&dont_notify_render_view_, true);
+ blocked_contents_->Destroy();
+ blocked_contents_ = NULL;
+ }
+
+ // Clear "blocked" flags.
+ content_settings_delegate_->ClearBlockedContentSettingsExceptForCookies();
+ content_settings_delegate_->GeolocationDidNavigate(details);
+
+ // Once the main frame is navigated, we're no longer considered to have
+ // displayed insecure content.
+ displayed_insecure_content_ = false;
+ }
+
+ // Close constrained windows if necessary.
+ if (!net::RegistryControlledDomainService::SameDomainOrHost(
+ details.previous_url, details.entry->url()))
+ CloseConstrainedWindows();
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(TabContentsObserver, observers_,
+ DidNavigateMainFramePostCommit(details, params));
+}
+
+void TabContents::DidNavigateAnyFramePostCommit(
+ RenderViewHost* render_view_host,
+ const NavigationController::LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // If we navigate, start showing messages again. This does nothing to prevent
+ // a malicious script from spamming messages, since the script could just
+ // reload the page to stop blocking.
+ suppress_javascript_messages_ = false;
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(TabContentsObserver, observers_,
+ DidNavigateAnyFramePostCommit(details, params));
+
+ // Let the LanguageState clear its state.
+ language_state_.DidNavigate(details);
+}
+
+void TabContents::CloseConstrainedWindows() {
+ // Clear out any constrained windows since we are leaving this page entirely.
+ // We use indices instead of iterators in case CloseWindow does something
+ // that may invalidate an iterator.
+ for (size_t i = 0; i < child_windows_.size(); ++i) {
+ ConstrainedWindow* window = child_windows_[child_windows_.size() - 1 - i];
+ if (window) {
+ window->CloseConstrainedWindow();
+ BlockTabContent(false);
+ }
+ }
+}
+
+void TabContents::UpdateAlternateErrorPageURL() {
+ GURL url = GetAlternateErrorPageURL();
+ render_view_host()->SetAlternateErrorPageURL(url);
+}
+
+void TabContents::UpdateWebPreferences() {
+ render_view_host()->UpdateWebPreferences(GetWebkitPrefs());
+}
+
+void TabContents::UpdateZoomLevel() {
+ render_view_host()->SetZoomLevel(GetZoomLevel());
+}
+
+void TabContents::UpdateMaxPageIDIfNecessary(SiteInstance* site_instance,
+ RenderViewHost* rvh) {
+ // If we are creating a RVH for a restored controller, then we might
+ // have more page IDs than the SiteInstance's current max page ID. We must
+ // make sure that the max page ID is larger than any restored page ID.
+ // Note that it is ok for conflicting page IDs to exist in another tab
+ // (i.e., NavigationController), but if any page ID is larger than the max,
+ // the back/forward list will get confused.
+ int max_restored_page_id = controller_.max_restored_page_id();
+ if (max_restored_page_id > 0) {
+ int curr_max_page_id = site_instance->max_page_id();
+ if (max_restored_page_id > curr_max_page_id) {
+ // Need to update the site instance immediately.
+ site_instance->UpdateMaxPageID(max_restored_page_id);
+
+ // Also tell the renderer to update its internal representation. We
+ // need to reserve enough IDs to make all restored page IDs less than
+ // the max.
+ if (curr_max_page_id < 0)
+ curr_max_page_id = 0;
+ rvh->ReservePageIDRange(max_restored_page_id - curr_max_page_id);
+ }
+ }
+}
+
+scoped_refptr<history::HistoryAddPageArgs>
+TabContents::CreateHistoryAddPageArgs(
+ const GURL& virtual_url,
+ const NavigationController::LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ scoped_refptr<history::HistoryAddPageArgs> add_page_args(
+ new history::HistoryAddPageArgs(
+ params.url, base::Time::Now(), this, params.page_id, params.referrer,
+ params.redirects, params.transition, history::SOURCE_BROWSED,
+ details.did_replace_entry));
+ if (PageTransition::IsMainFrame(params.transition) &&
+ virtual_url != params.url) {
+ // Hack on the "virtual" URL so that it will appear in history. For some
+ // types of URLs, we will display a magic URL that is different from where
+ // the page is actually navigated. We want the user to see in history what
+ // they saw in the URL bar, so we add the virtual URL as a redirect. This
+ // only applies to the main frame, as the virtual URL doesn't apply to
+ // sub-frames.
+ add_page_args->url = virtual_url;
+ if (!add_page_args->redirects.empty())
+ add_page_args->redirects.back() = virtual_url;
+ }
+ return add_page_args;
+}
+
+bool TabContents::UpdateTitleForEntry(NavigationEntry* entry,
+ const std::wstring& title) {
+ // For file URLs without a title, use the pathname instead. In the case of a
+ // synthesized title, we don't want the update to count toward the "one set
+ // per page of the title to history."
+ string16 final_title;
+ bool explicit_set;
+ if (entry->url().SchemeIsFile() && title.empty()) {
+ final_title = UTF8ToUTF16(entry->url().ExtractFileName());
+ explicit_set = false; // Don't count synthetic titles toward the set limit.
+ } else {
+ TrimWhitespace(WideToUTF16Hack(title), TRIM_ALL, &final_title);
+ explicit_set = true;
+ }
+
+ if (final_title == entry->title())
+ return false; // Nothing changed, don't bother.
+
+ entry->set_title(final_title);
+
+ if (!received_page_title_) {
+ UpdateHistoryPageTitle(*entry);
+ received_page_title_ = explicit_set;
+ }
+
+ // Lastly, set the title for the view.
+ view_->SetPageTitle(UTF16ToWideHack(final_title));
+
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_TITLE_UPDATED,
+ Source<TabContents>(this),
+ NotificationService::NoDetails());
+
+ return true;
+}
+
+void TabContents::NotifySwapped() {
+ // After sending out a swap notification, we need to send a disconnect
+ // notification so that clients that pick up a pointer to |this| can NULL the
+ // pointer. See Bug 1230284.
+ notify_disconnection_ = true;
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_SWAPPED,
+ Source<TabContents>(this),
+ NotificationService::NoDetails());
+}
+
+void TabContents::NotifyConnected() {
+ notify_disconnection_ = true;
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_CONNECTED,
+ Source<TabContents>(this),
+ NotificationService::NoDetails());
+}
+
+void TabContents::NotifyDisconnected() {
+ if (!notify_disconnection_)
+ return;
+
+ notify_disconnection_ = false;
+ NotificationService::current()->Notify(
+ NotificationType::TAB_CONTENTS_DISCONNECTED,
+ Source<TabContents>(this),
+ NotificationService::NoDetails());
+}
+
+void TabContents::OnGoToEntryAtOffset(int offset) {
+ if (!delegate_ || delegate_->OnGoToEntryOffset(offset)) {
+ NavigationEntry* entry = controller_.GetEntryAtOffset(offset);
+ if (!entry)
+ return;
+ // Note that we don't call NavigationController::GotToOffset() as we don't
+ // want to create a pending navigation entry (it might end up lingering
+ // http://crbug.com/51680).
+ entry->set_transition_type(entry->transition_type() |
+ PageTransition::FORWARD_BACK);
+ NavigateToEntry(*entry, NavigationController::NO_RELOAD);
+ }
+}
+
+void TabContents::OnDidGetApplicationInfo(int32 page_id,
+ const WebApplicationInfo& info) {
+ web_app_info_ = info;
+
+ if (delegate())
+ delegate()->OnDidGetApplicationInfo(this, page_id);
+}
+
+void TabContents::OnInstallApplication(const WebApplicationInfo& info) {
+ if (delegate())
+ delegate()->OnInstallApplication(this, info);
+}
+
+void TabContents::OnPageContents(const GURL& url,
+ int32 page_id,
+ const string16& contents,
+ const std::string& language,
+ bool page_translatable) {
+ // Don't index any https pages. People generally don't want their bank
+ // accounts, etc. indexed on their computer, especially since some of these
+ // things are not marked cachable.
+ // TODO(brettw) we may want to consider more elaborate heuristics such as
+ // the cachability of the page. We may also want to consider subframes (this
+ // test will still index subframes if the subframe is SSL).
+ // TODO(zelidrag) bug chromium-os:2808 - figure out if we want to reenable
+ // content indexing for chromeos in some future releases.
+#if !defined(OS_CHROMEOS)
+ if (!url.SchemeIsSecure()) {
+ Profile* p = profile();
+ if (p && !p->IsOffTheRecord()) {
+ HistoryService* hs = p->GetHistoryService(Profile::IMPLICIT_ACCESS);
+ if (hs)
+ hs->SetPageContents(url, contents);
+ }
+ }
+#endif
+
+ language_state_.LanguageDetermined(language, page_translatable);
+
+ std::string lang = language;
+ NotificationService::current()->Notify(
+ NotificationType::TAB_LANGUAGE_DETERMINED,
+ Source<TabContents>(this),
+ Details<std::string>(&lang));
+
+ // Generate the thumbnail here if the in-browser thumbnailing is enabled.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableInBrowserThumbnailing)) {
+ ThumbnailGenerator::UpdateThumbnailIfNecessary(this, url);
+ }
+}
+
+void TabContents::OnPageTranslated(int32 page_id,
+ const std::string& original_lang,
+ const std::string& translated_lang,
+ TranslateErrors::Type error_type) {
+ language_state_.set_current_language(translated_lang);
+ language_state_.set_translation_pending(false);
+ PageTranslatedDetails details(original_lang, translated_lang, error_type);
+ NotificationService::current()->Notify(
+ NotificationType::PAGE_TRANSLATED,
+ Source<TabContents>(this),
+ Details<PageTranslatedDetails>(&details));
+}
+
+void TabContents::OnSetSuggestions(
+ int32 page_id,
+ const std::vector<std::string>& suggestions) {
+ if (delegate())
+ delegate()->OnSetSuggestions(page_id, suggestions);
+}
+
+void TabContents::OnInstantSupportDetermined(int32 page_id, bool result) {
+ if (delegate())
+ delegate()->OnInstantSupportDetermined(page_id, result);
+}
+
+void TabContents::OnRunFileChooser(
+ const ViewHostMsg_RunFileChooser_Params& params) {
+ if (file_select_helper_.get() == NULL)
+ file_select_helper_.reset(new FileSelectHelper(profile()));
+ file_select_helper_->RunFileChooser(render_view_host(), params);
+}
+
+
+void TabContents::OnContentSettingsAccessed(bool content_was_blocked) {
+ if (delegate_)
+ delegate_->OnContentSettingsChange(this);
+}
+
+RenderViewHostDelegate::View* TabContents::GetViewDelegate() {
+ return view_.get();
+}
+
+RenderViewHostDelegate::RendererManagement*
+TabContents::GetRendererManagementDelegate() {
+ return &render_manager_;
+}
+
+RenderViewHostDelegate::ContentSettings*
+TabContents::GetContentSettingsDelegate() {
+ return content_settings_delegate_.get();
+}
+
+RenderViewHostDelegate::SSL* TabContents::GetSSLDelegate() {
+ return GetSSLHelper();
+}
+
+AutomationResourceRoutingDelegate*
+TabContents::GetAutomationResourceRoutingDelegate() {
+ return delegate();
+}
+
+RenderViewHostDelegate::BookmarkDrag* TabContents::GetBookmarkDragDelegate() {
+ return bookmark_drag_;
+}
+
+void TabContents::SetBookmarkDragDelegate(
+ RenderViewHostDelegate::BookmarkDrag* bookmark_drag) {
+ bookmark_drag_ = bookmark_drag;
+}
+
+TabSpecificContentSettings* TabContents::GetTabSpecificContentSettings() const {
+ return content_settings_delegate_.get();
+}
+
+RendererPreferences TabContents::GetRendererPrefs(Profile* profile) const {
+ return renderer_preferences_;
+}
+
+TabContents* TabContents::GetAsTabContents() {
+ return this;
+}
+
+ViewType::Type TabContents::GetRenderViewType() const {
+ return ViewType::TAB_CONTENTS;
+}
+
+int TabContents::GetBrowserWindowID() const {
+ return controller().window_id().id();
+}
+
+void TabContents::RenderViewCreated(RenderViewHost* render_view_host) {
+ NotificationService::current()->Notify(
+ NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB,
+ Source<TabContents>(this),
+ Details<RenderViewHost>(render_view_host));
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ if (!entry)
+ return;
+
+ // When we're creating views, we're still doing initial setup, so we always
+ // use the pending Web UI rather than any possibly existing committed one.
+ if (render_manager_.pending_web_ui()) {
+ render_manager_.pending_web_ui()->RenderViewCreated(render_view_host);
+ }
+
+ if (entry->IsViewSourceMode()) {
+ // Put the renderer in view source mode.
+ render_view_host->Send(
+ new ViewMsg_EnableViewSourceMode(render_view_host->routing_id()));
+ }
+
+ view()->RenderViewCreated(render_view_host);
+}
+
+void TabContents::RenderViewReady(RenderViewHost* rvh) {
+ if (rvh != render_view_host()) {
+ // Don't notify the world, since this came from a renderer in the
+ // background.
+ return;
+ }
+
+ NotifyConnected();
+ bool was_crashed = is_crashed();
+ SetIsCrashed(base::TERMINATION_STATUS_STILL_RUNNING, 0);
+
+ // Restore the focus to the tab (otherwise the focus will be on the top
+ // window).
+ if (was_crashed && !FocusLocationBarByDefault() &&
+ (!delegate_ || delegate_->ShouldFocusPageAfterCrash())) {
+ Focus();
+ }
+}
+
+void TabContents::RenderViewGone(RenderViewHost* rvh,
+ base::TerminationStatus status,
+ int error_code) {
+ // Ask the print preview if this renderer was valuable.
+ if (!printing_->OnRenderViewGone(rvh))
+ return;
+ if (rvh != render_view_host()) {
+ // The pending page's RenderViewHost is gone.
+ return;
+ }
+
+ SetIsLoading(false, NULL);
+ NotifyDisconnected();
+ SetIsCrashed(status, error_code);
+
+ // Remove all infobars.
+ while (!infobar_delegates_.empty())
+ RemoveInfoBar(GetInfoBarDelegateAt(infobar_count() - 1));
+
+ // Tell the view that we've crashed so it can prepare the sad tab page.
+ // Only do this if we're not in browser shutdown, so that TabContents
+ // objects that are not in a browser (e.g., HTML dialogs) and thus are
+ // visible do not flash a sad tab page.
+ if (browser_shutdown::GetShutdownType() == browser_shutdown::NOT_VALID)
+ view_->OnTabCrashed(status, error_code);
+
+ // Hide any visible hung renderer warning for this web contents' process.
+ hung_renderer_dialog::HideForTabContents(this);
+}
+
+void TabContents::RenderViewDeleted(RenderViewHost* rvh) {
+ NotificationService::current()->Notify(
+ NotificationType::RENDER_VIEW_HOST_DELETED,
+ Source<TabContents>(this),
+ Details<RenderViewHost>(rvh));
+ render_manager_.RenderViewDeleted(rvh);
+}
+
+void TabContents::DidNavigate(RenderViewHost* rvh,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ int extra_invalidate_flags = 0;
+
+ if (PageTransition::IsMainFrame(params.transition)) {
+ if (MaybeUsePreloadedPage(params.url)) {
+ return;
+ }
+
+ bool was_bookmark_bar_visible = ShouldShowBookmarkBar();
+
+ render_manager_.DidNavigateMainFrame(rvh);
+
+ if (was_bookmark_bar_visible != ShouldShowBookmarkBar())
+ extra_invalidate_flags |= INVALIDATE_BOOKMARK_BAR;
+ }
+
+ // Update the site of the SiteInstance if it doesn't have one yet.
+ if (!GetSiteInstance()->has_site())
+ GetSiteInstance()->SetSite(params.url);
+
+ // Need to update MIME type here because it's referred to in
+ // UpdateNavigationCommands() called by RendererDidNavigate() to
+ // determine whether or not to enable the encoding menu.
+ // It's updated only for the main frame. For a subframe,
+ // RenderView::UpdateURL does not set params.contents_mime_type.
+ // (see http://code.google.com/p/chromium/issues/detail?id=2929 )
+ // TODO(jungshik): Add a test for the encoding menu to avoid
+ // regressing it again.
+ if (PageTransition::IsMainFrame(params.transition))
+ contents_mime_type_ = params.contents_mime_type;
+
+ NavigationController::LoadCommittedDetails details;
+ bool did_navigate = controller_.RendererDidNavigate(
+ params, extra_invalidate_flags, &details);
+
+ // Send notification about committed provisional loads. This notification is
+ // different from the NAV_ENTRY_COMMITTED notification which doesn't include
+ // the actual URL navigated to and isn't sent for AUTO_SUBFRAME navigations.
+ if (details.type != NavigationType::NAV_IGNORE) {
+ // For AUTO_SUBFRAME navigations, an event for the main frame is generated
+ // that is not recorded in the navigation history. For the purpose of
+ // tracking navigation events, we treat this event as a sub frame navigation
+ // event.
+ bool is_main_frame = did_navigate ? details.is_main_frame : false;
+ ProvisionalLoadDetails load_details(
+ is_main_frame, details.is_in_page, params.url, std::string(), false,
+ false, params.frame_id);
+ load_details.set_transition_type(params.transition);
+ // Whether or not a page transition was triggered by going backward or
+ // forward in the history is only stored in the navigation controller's
+ // entry list.
+ if (did_navigate &&
+ (controller_.GetActiveEntry()->transition_type() &
+ PageTransition::FORWARD_BACK)) {
+ load_details.set_transition_type(
+ params.transition | PageTransition::FORWARD_BACK);
+ }
+ NotificationService::current()->Notify(
+ NotificationType::FRAME_PROVISIONAL_LOAD_COMMITTED,
+ Source<NavigationController>(&controller_),
+ Details<ProvisionalLoadDetails>(&load_details));
+ }
+
+ // Update history. Note that this needs to happen after the entry is complete,
+ // which WillNavigate[Main,Sub]Frame will do before this function is called.
+ if (params.should_update_history) {
+ // Most of the time, the displayURL matches the loaded URL, but for about:
+ // URLs, we use a data: URL as the real value. We actually want to save the
+ // about: URL to the history db and keep the data: URL hidden. This is what
+ // the TabContents' URL getter does.
+ scoped_refptr<history::HistoryAddPageArgs> add_page_args(
+ CreateHistoryAddPageArgs(GetURL(), details, params));
+ if (!delegate() ||
+ delegate()->ShouldAddNavigationToHistory(*add_page_args,
+ details.type)) {
+ UpdateHistoryForNavigation(add_page_args);
+ }
+ }
+
+ if (!did_navigate)
+ return; // No navigation happened.
+
+ // DO NOT ADD MORE STUFF TO THIS FUNCTION! Your component should either listen
+ // for the appropriate notification (best) or you can add it to
+ // DidNavigateMainFramePostCommit / DidNavigateAnyFramePostCommit (only if
+ // necessary, please).
+
+ // Run post-commit tasks.
+ if (details.is_main_frame)
+ DidNavigateMainFramePostCommit(details, params);
+ DidNavigateAnyFramePostCommit(rvh, details, params);
+}
+
+void TabContents::UpdateState(RenderViewHost* rvh,
+ int32 page_id,
+ const std::string& state) {
+ DCHECK(rvh == render_view_host());
+
+ // We must be prepared to handle state updates for any page, these occur
+ // when the user is scrolling and entering form data, as well as when we're
+ // leaving a page, in which case our state may have already been moved to
+ // the next page. The navigation controller will look up the appropriate
+ // NavigationEntry and update it when it is notified via the delegate.
+
+ int entry_index = controller_.GetEntryIndexWithPageID(
+ GetSiteInstance(), page_id);
+ if (entry_index < 0)
+ return;
+ NavigationEntry* entry = controller_.GetEntryAtIndex(entry_index);
+
+ if (state == entry->content_state())
+ return; // Nothing to update.
+ entry->set_content_state(state);
+ controller_.NotifyEntryChanged(entry, entry_index);
+}
+
+void TabContents::UpdateTitle(RenderViewHost* rvh,
+ int32 page_id, const std::wstring& title) {
+ // If we have a title, that's a pretty good indication that we've started
+ // getting useful data.
+ SetNotWaitingForResponse();
+
+ DCHECK(rvh == render_view_host());
+ NavigationEntry* entry = controller_.GetEntryWithPageID(rvh->site_instance(),
+ page_id);
+ if (!entry || !UpdateTitleForEntry(entry, title))
+ return;
+
+ // Broadcast notifications when the UI should be updated.
+ if (entry == controller_.GetEntryAtOffset(0))
+ NotifyNavigationStateChanged(INVALIDATE_TITLE);
+}
+
+void TabContents::UpdateEncoding(RenderViewHost* render_view_host,
+ const std::string& encoding) {
+ set_encoding(encoding);
+}
+
+void TabContents::UpdateTargetURL(int32 page_id, const GURL& url) {
+ if (delegate())
+ delegate()->UpdateTargetURL(this, url);
+}
+
+void TabContents::UpdateThumbnail(const GURL& url,
+ const SkBitmap& bitmap,
+ const ThumbnailScore& score) {
+ if (profile()->IsOffTheRecord())
+ return;
+
+ // Tell History about this thumbnail
+ history::TopSites* ts = profile()->GetTopSites();
+ if (ts)
+ ts->SetPageThumbnail(url, bitmap, score);
+}
+
+void TabContents::UpdateInspectorSetting(const std::string& key,
+ const std::string& value) {
+ RenderViewHostDelegateHelper::UpdateInspectorSetting(profile(), key, value);
+}
+
+void TabContents::ClearInspectorSettings() {
+ RenderViewHostDelegateHelper::ClearInspectorSettings(profile());
+}
+
+void TabContents::Close(RenderViewHost* rvh) {
+ // The UI may be in an event-tracking loop, such as between the
+ // mouse-down and mouse-up in text selection or a button click.
+ // Defer the close until after tracking is complete, so that we
+ // don't free objects out from under the UI.
+ // TODO(shess): This could probably be integrated with the
+ // IsDoingDrag() test below. Punting for now because I need more
+ // research to understand how this impacts platforms other than Mac.
+ // TODO(shess): This could get more fine-grained. For instance,
+ // closing a tab in another window while selecting text in the
+ // current window's Omnibox should be just fine.
+ if (view()->IsEventTracking()) {
+ view()->CloseTabAfterEventTracking();
+ return;
+ }
+
+ // If we close the tab while we're in the middle of a drag, we'll crash.
+ // Instead, cancel the drag and close it as soon as the drag ends.
+ if (view()->IsDoingDrag()) {
+ view()->CancelDragAndCloseTab();
+ return;
+ }
+
+ // Ignore this if it comes from a RenderViewHost that we aren't showing.
+ if (delegate() && rvh == render_view_host())
+ delegate()->CloseContents(this);
+}
+
+void TabContents::RequestMove(const gfx::Rect& new_bounds) {
+ if (delegate() && delegate()->IsPopup(this))
+ delegate()->MoveContents(this, new_bounds);
+}
+
+void TabContents::DidStartLoading() {
+ SetIsLoading(true, NULL);
+
+ if (delegate()) {
+ bool is_print_preview_tab =
+ printing::PrintPreviewTabController::IsPrintPreviewTab(this);
+ if (content_restrictions_ || is_print_preview_tab) {
+ content_restrictions_= is_print_preview_tab ?
+ CONTENT_RESTRICTION_PRINT : 0;
+ delegate()->ContentRestrictionsChanged(this);
+ }
+ }
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(TabContentsObserver, observers_, DidStartLoading());
+}
+
+void TabContents::DidStopLoading() {
+ scoped_ptr<LoadNotificationDetails> details;
+
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ // An entry may not exist for a stop when loading an initial blank page or
+ // if an iframe injected by script into a blank page finishes loading.
+ if (entry) {
+ base::TimeDelta elapsed = base::TimeTicks::Now() - current_load_start_;
+
+ details.reset(new LoadNotificationDetails(
+ entry->virtual_url(),
+ entry->transition_type(),
+ elapsed,
+ &controller_,
+ controller_.GetCurrentEntryIndex()));
+ }
+
+ SetIsLoading(false, details.get());
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(TabContentsObserver, observers_, DidStopLoading());
+}
+
+void TabContents::DidChangeLoadProgress(double progress) {
+ if (delegate())
+ delegate()->LoadProgressChanged(progress);
+}
+
+void TabContents::DocumentOnLoadCompletedInMainFrame(
+ RenderViewHost* render_view_host,
+ int32 page_id) {
+ NotificationService::current()->Notify(
+ NotificationType::LOAD_COMPLETED_MAIN_FRAME,
+ Source<TabContents>(this),
+ Details<int>(&page_id));
+}
+
+void TabContents::RequestOpenURL(const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition) {
+ if (render_manager_.web_ui()) {
+ // When we're a Web UI, it will provide a page transition type for us (this
+ // is so the new tab page can specify AUTO_BOOKMARK for automatically
+ // generated suggestions).
+ //
+ // Note also that we hide the referrer for Web UI pages. We don't really
+ // want web sites to see a referrer of "chrome://blah" (and some
+ // chrome: URLs might have search terms or other stuff we don't want to
+ // send to the site), so we send no referrer.
+ OpenURL(url, GURL(), disposition,
+ render_manager_.web_ui()->link_transition_type());
+ } else {
+ OpenURL(url, referrer, disposition, PageTransition::LINK);
+ }
+}
+
+void TabContents::DomOperationResponse(const std::string& json_string,
+ int automation_id) {
+}
+
+void TabContents::ProcessWebUIMessage(
+ const ViewHostMsg_DomMessage_Params& params) {
+ if (!render_manager_.web_ui()) {
+ // This can happen if someone uses window.open() to open an extension URL
+ // from a non-extension context.
+ render_view_host()->BlockExtensionRequest(params.request_id);
+ return;
+ }
+ render_manager_.web_ui()->ProcessWebUIMessage(params);
+}
+
+void TabContents::ProcessExternalHostMessage(const std::string& message,
+ const std::string& origin,
+ const std::string& target) {
+ if (delegate())
+ delegate()->ForwardMessageToExternalHost(message, origin, target);
+}
+
+void TabContents::RunJavaScriptMessage(
+ const std::wstring& message,
+ const std::wstring& default_prompt,
+ const GURL& frame_url,
+ const int flags,
+ IPC::Message* reply_msg,
+ bool* did_suppress_message) {
+ // Suppress javascript messages when requested and when inside a constrained
+ // popup window (because that activates them and breaks them out of the
+ // constrained window jail).
+ // Also suppress messages when showing an interstitial. The interstitial is
+ // shown over the previous page, we don't want the hidden page dialogs to
+ // interfere with the interstitial.
+ bool suppress_this_message =
+ suppress_javascript_messages_ ||
+ showing_interstitial_page() ||
+ (delegate() && delegate()->ShouldSuppressDialogs());
+ if (delegate())
+ suppress_this_message |=
+ (delegate()->GetConstrainingContents(this) != this);
+
+ *did_suppress_message = suppress_this_message;
+
+ if (!suppress_this_message) {
+ base::TimeDelta time_since_last_message(
+ base::TimeTicks::Now() - last_javascript_message_dismissal_);
+ bool show_suppress_checkbox = false;
+ // Show a checkbox offering to suppress further messages if this message is
+ // being displayed within kJavascriptMessageExpectedDelay of the last one.
+ if (time_since_last_message <
+ base::TimeDelta::FromMilliseconds(kJavascriptMessageExpectedDelay))
+ show_suppress_checkbox = true;
+
+ RunJavascriptMessageBox(profile(), this, frame_url, flags, message,
+ default_prompt, show_suppress_checkbox, reply_msg);
+ } else {
+ // If we are suppressing messages, just reply as is if the user immediately
+ // pressed "Cancel".
+ OnMessageBoxClosed(reply_msg, false, std::wstring());
+ }
+}
+
+void TabContents::RunBeforeUnloadConfirm(const std::wstring& message,
+ IPC::Message* reply_msg) {
+ if (delegate())
+ delegate()->WillRunBeforeUnloadConfirm();
+ if (delegate() && delegate()->ShouldSuppressDialogs()) {
+ render_view_host()->JavaScriptMessageBoxClosed(reply_msg, true,
+ std::wstring());
+ return;
+ }
+ is_showing_before_unload_dialog_ = true;
+ RunBeforeUnloadDialog(this, message, reply_msg);
+}
+
+void TabContents::ShowModalHTMLDialog(const GURL& url, int width, int height,
+ const std::string& json_arguments,
+ IPC::Message* reply_msg) {
+ if (delegate()) {
+ HtmlDialogUIDelegate* dialog_delegate =
+ new ModalHtmlDialogDelegate(url, width, height, json_arguments,
+ reply_msg, this);
+ delegate()->ShowHtmlDialog(dialog_delegate, NULL);
+ }
+}
+
+GURL TabContents::GetAlternateErrorPageURL() const {
+ GURL url;
+ // Disable alternate error pages when in OffTheRecord/Incognito mode.
+ if (profile()->IsOffTheRecord())
+ return url;
+
+ PrefService* prefs = profile()->GetPrefs();
+ DCHECK(prefs);
+ if (prefs->GetBoolean(prefs::kAlternateErrorPagesEnabled)) {
+ url = google_util::AppendGoogleLocaleParam(
+ GURL(google_util::kLinkDoctorBaseURL));
+ url = google_util::AppendGoogleTLDParam(url);
+ }
+ return url;
+}
+
+WebPreferences TabContents::GetWebkitPrefs() {
+ Profile* profile = render_view_host()->process()->profile();
+ bool is_web_ui = false;
+ WebPreferences web_prefs =
+ RenderViewHostDelegateHelper::GetWebkitPrefs(profile, is_web_ui);
+
+ // Force accelerated compositing and 2d canvas off for chrome: and
+ // chrome-extension: pages.
+ if (GetURL().SchemeIs(chrome::kChromeDevToolsScheme) ||
+ GetURL().SchemeIs(chrome::kChromeUIScheme)) {
+ web_prefs.accelerated_compositing_enabled = false;
+ web_prefs.accelerated_2d_canvas_enabled = false;
+ }
+
+#if defined(OS_MACOSX)
+ // Disable accelerated compositing if IOSurface's are not supported,
+ // as is the case in 10.5.
+ if (!IOSurfaceSupport::Initialize())
+ web_prefs.accelerated_compositing_enabled = false;
+#endif
+
+ return web_prefs;
+}
+
+void TabContents::OnUserGesture() {
+ // See comment in RenderViewHostDelegate::OnUserGesture as to why we do this.
+ DownloadRequestLimiter* limiter =
+ g_browser_process->download_request_limiter();
+ if (limiter)
+ limiter->OnUserGesture(this);
+ ExternalProtocolHandler::PermitLaunchUrl();
+}
+
+void TabContents::OnIgnoredUIEvent() {
+ if (constrained_window_count()) {
+ ConstrainedWindow* window = *constrained_window_begin();
+ window->FocusConstrainedWindow();
+ }
+}
+
+void TabContents::OnCrossSiteResponse(int new_render_process_host_id,
+ int new_request_id) {
+ // Allows the TabContents to react when a cross-site response is ready to be
+ // delivered to a pending RenderViewHost. We must first run the onunload
+ // handler of the old RenderViewHost before we can allow it to proceed.
+ render_manager_.OnCrossSiteResponse(new_render_process_host_id,
+ new_request_id);
+}
+
+void TabContents::RendererUnresponsive(RenderViewHost* rvh,
+ bool is_during_unload) {
+ if (is_during_unload) {
+ // Hang occurred while firing the beforeunload/unload handler.
+ // Pretend the handler fired so tab closing continues as if it had.
+ rvh->set_sudden_termination_allowed(true);
+
+ if (!render_manager_.ShouldCloseTabOnUnresponsiveRenderer())
+ return;
+
+ // If the tab hangs in the beforeunload/unload handler there's really
+ // nothing we can do to recover. Pretend the unload listeners have
+ // all fired and close the tab. If the hang is in the beforeunload handler
+ // then the user will not have the option of cancelling the close.
+ Close(rvh);
+ return;
+ }
+
+ if (render_view_host() && render_view_host()->IsRenderViewLive() &&
+ (!delegate() || delegate()->ShouldShowHungRendererDialog())) {
+ hung_renderer_dialog::ShowForTabContents(this);
+ }
+}
+
+void TabContents::RendererResponsive(RenderViewHost* render_view_host) {
+ hung_renderer_dialog::HideForTabContents(this);
+}
+
+void TabContents::LoadStateChanged(const GURL& url,
+ net::LoadState load_state,
+ uint64 upload_position,
+ uint64 upload_size) {
+ load_state_ = load_state;
+ upload_position_ = upload_position;
+ upload_size_ = upload_size;
+ std::wstring languages =
+ UTF8ToWide(profile()->GetPrefs()->GetString(prefs::kAcceptLanguages));
+ std::string host = url.host();
+ load_state_host_ = WideToUTF16Hack(
+ net::IDNToUnicode(host.c_str(), host.size(), languages, NULL));
+ if (load_state_ == net::LOAD_STATE_READING_RESPONSE)
+ SetNotWaitingForResponse();
+ if (is_loading())
+ NotifyNavigationStateChanged(INVALIDATE_LOAD | INVALIDATE_TAB);
+}
+
+bool TabContents::IsExternalTabContainer() const {
+ if (!delegate())
+ return false;
+
+ return delegate()->IsExternalTabContainer();
+}
+
+void TabContents::DidInsertCSS() {
+ // This RVHDelegate function is used for extensions and not us.
+}
+
+void TabContents::FocusedNodeChanged(bool is_editable_node) {
+ NotificationService::current()->Notify(
+ NotificationType::FOCUS_CHANGED_IN_PAGE,
+ Source<TabContents>(this),
+ Details<const bool>(&is_editable_node));
+}
+
+void TabContents::UpdateZoomLimits(int minimum_percent,
+ int maximum_percent,
+ bool remember) {
+ minimum_zoom_percent_ = minimum_percent;
+ maximum_zoom_percent_ = maximum_percent;
+ temporary_zoom_settings_ = !remember;
+}
+
+void TabContents::WorkerCrashed() {
+ if (delegate())
+ delegate()->WorkerCrashed();
+}
+
+void TabContents::BeforeUnloadFiredFromRenderManager(
+ bool proceed,
+ bool* proceed_to_fire_unload) {
+ if (delegate())
+ delegate()->BeforeUnloadFired(this, proceed, proceed_to_fire_unload);
+}
+
+void TabContents::DidStartLoadingFromRenderManager(
+ RenderViewHost* render_view_host) {
+ DidStartLoading();
+}
+
+void TabContents::RenderViewGoneFromRenderManager(
+ RenderViewHost* render_view_host) {
+ DCHECK(crashed_status_ != base::TERMINATION_STATUS_STILL_RUNNING);
+ RenderViewGone(render_view_host, crashed_status_, crashed_error_code_);
+}
+
+void TabContents::UpdateRenderViewSizeForRenderManager() {
+ // TODO(brettw) this is a hack. See TabContentsView::SizeContents.
+ gfx::Size size = view_->GetContainerSize();
+ // 0x0 isn't a valid window size (minimal window size is 1x1) but it may be
+ // here during container initialization and normal window size will be set
+ // later. In case of tab duplication this resizing to 0x0 prevents setting
+ // normal size later so just ignore it.
+ if (!size.IsEmpty())
+ view_->SizeContents(size);
+}
+
+void TabContents::NotifySwappedFromRenderManager() {
+ NotifySwapped();
+}
+
+NavigationController& TabContents::GetControllerForRenderManager() {
+ return controller();
+}
+
+WebUI* TabContents::CreateWebUIForRenderManager(const GURL& url) {
+ return WebUIFactory::CreateWebUIForURL(this, url);
+}
+
+NavigationEntry*
+TabContents::GetLastCommittedNavigationEntryForRenderManager() {
+ return controller_.GetLastCommittedEntry();
+}
+
+bool TabContents::CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host) {
+ RenderWidgetHostView* rwh_view = view_->CreateViewForWidget(render_view_host);
+
+ if (!render_view_host->CreateRenderView(string16()))
+ return false;
+
+ // Now that the RenderView has been created, we need to tell it its size.
+ rwh_view->SetSize(view_->GetContainerSize());
+
+ UpdateMaxPageIDIfNecessary(render_view_host->site_instance(),
+ render_view_host);
+ return true;
+}
+
+void TabContents::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::PREF_CHANGED: {
+ std::string* pref_name_in = Details<std::string>(details).ptr();
+ DCHECK(Source<PrefService>(source).ptr() == profile()->GetPrefs());
+ if (*pref_name_in == prefs::kAlternateErrorPagesEnabled) {
+ UpdateAlternateErrorPageURL();
+ } else if ((*pref_name_in == prefs::kDefaultCharset) ||
+ StartsWithASCII(*pref_name_in, "webkit.webprefs.", true)) {
+ UpdateWebPreferences();
+ } else if (*pref_name_in == prefs::kDefaultZoomLevel) {
+ UpdateZoomLevel();
+ } else {
+ NOTREACHED() << "unexpected pref change notification" << *pref_name_in;
+ }
+ break;
+ }
+ case NotificationType::RENDER_WIDGET_HOST_DESTROYED:
+ view_->RenderWidgetHostDestroyed(Source<RenderWidgetHost>(source).ptr());
+ break;
+
+ case NotificationType::NAV_ENTRY_COMMITTED: {
+ DCHECK(&controller_ == Source<NavigationController>(source).ptr());
+
+ NavigationController::LoadCommittedDetails& committed_details =
+ *(Details<NavigationController::LoadCommittedDetails>(details).ptr());
+ ExpireInfoBars(committed_details);
+ break;
+ }
+
+#if defined(OS_LINUX)
+ case NotificationType::BROWSER_THEME_CHANGED: {
+ renderer_preferences_util::UpdateFromSystemSettings(
+ &renderer_preferences_, profile());
+ render_view_host()->SyncRendererPrefs();
+ break;
+ }
+#endif
+
+ case NotificationType::USER_STYLE_SHEET_UPDATED:
+ UpdateWebPreferences();
+ break;
+
+ case NotificationType::CONTENT_SETTINGS_CHANGED: {
+ Details<const ContentSettingsDetails> settings_details(details);
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ GURL entry_url;
+ if (entry)
+ entry_url = entry->url();
+ if (settings_details.ptr()->update_all() ||
+ settings_details.ptr()->pattern().Matches(entry_url)) {
+ render_view_host()->SendContentSettings(entry_url,
+ profile()->GetHostContentSettingsMap()->
+ GetContentSettings(entry_url));
+ }
+ break;
+ }
+
+ case NotificationType::EXTENSION_LOADED:
+ break;
+
+ case NotificationType::EXTENSION_UNLOADED:
+ break;
+
+ case NotificationType::GOOGLE_URL_UPDATED:
+ UpdateAlternateErrorPageURL();
+ break;
+
+ default:
+ NOTREACHED();
+ }
+}
+
+void TabContents::UpdateExtensionAppIcon(const Extension* extension) {
+ extension_app_icon_.reset();
+
+ if (extension) {
+ extension_app_image_loader_.reset(new ImageLoadingTracker(this));
+ extension_app_image_loader_->LoadImage(
+ extension,
+ extension->GetIconResource(Extension::EXTENSION_ICON_SMALLISH,
+ ExtensionIconSet::MATCH_EXACTLY),
+ gfx::Size(Extension::EXTENSION_ICON_SMALLISH,
+ Extension::EXTENSION_ICON_SMALLISH),
+ ImageLoadingTracker::CACHE);
+ } else {
+ extension_app_image_loader_.reset(NULL);
+ }
+}
+
+const Extension* TabContents::GetExtensionContaining(const GURL& url) {
+ ExtensionService* extensions_service = profile()->GetExtensionService();
+ if (!extensions_service)
+ return NULL;
+
+ const Extension* extension = extensions_service->GetExtensionByURL(url);
+ return extension ?
+ extension : extensions_service->GetExtensionByWebExtent(url);
+}
+
+void TabContents::OnImageLoaded(SkBitmap* image, ExtensionResource resource,
+ int index) {
+ if (image) {
+ extension_app_icon_ = *image;
+ NotifyNavigationStateChanged(INVALIDATE_TAB);
+ }
+}
+
+gfx::NativeWindow TabContents::GetMessageBoxRootWindow() {
+ return view_->GetTopLevelNativeWindow();
+}
+
+void TabContents::OnMessageBoxClosed(IPC::Message* reply_msg,
+ bool success,
+ const std::wstring& prompt) {
+ last_javascript_message_dismissal_ = base::TimeTicks::Now();
+ if (is_showing_before_unload_dialog_ && !success) {
+ // If a beforeunload dialog is canceled, we need to stop the throbber from
+ // spinning, since we forced it to start spinning in Navigate.
+ DidStopLoading();
+
+ tab_close_start_time_ = base::TimeTicks();
+ }
+ is_showing_before_unload_dialog_ = false;
+ render_view_host()->JavaScriptMessageBoxClosed(reply_msg, success, prompt);
+}
+
+void TabContents::SetSuppressMessageBoxes(bool suppress_message_boxes) {
+ set_suppress_javascript_messages(suppress_message_boxes);
+}
+
+TabContents* TabContents::AsTabContents() {
+ return this;
+}
+
+ExtensionHost* TabContents::AsExtensionHost() {
+ return NULL;
+}
+
+void TabContents::set_encoding(const std::string& encoding) {
+ encoding_ = CharacterEncoding::GetCanonicalEncodingNameByAliasName(encoding);
+}
+
+void TabContents::SetAppIcon(const SkBitmap& app_icon) {
+ app_icon_ = app_icon;
+ NotifyNavigationStateChanged(INVALIDATE_TITLE);
+}
+
+void TabContents::SwapInRenderViewHost(RenderViewHost* rvh) {
+ render_manager_.SwapInRenderViewHost(rvh);
+}
+
+void TabContents::CreateViewAndSetSizeForRVH(RenderViewHost* rvh) {
+ RenderWidgetHostView* rwh_view = view()->CreateViewForWidget(rvh);
+ rwh_view->SetSize(view()->GetContainerSize());
+}
+
+bool TabContents::MaybeUsePreloadedPage(const GURL& url) {
+ prerender::PrerenderManager* pm = profile()->GetPrerenderManager();
+ if (pm != NULL) {
+ if (pm->MaybeUsePreloadedPage(this, url))
+ return true;
+ }
+ return false;
+}
diff --git a/content/browser/tab_contents/tab_contents.h b/content/browser/tab_contents/tab_contents.h
new file mode 100644
index 0000000..318e9fc
--- /dev/null
+++ b/content/browser/tab_contents/tab_contents.h
@@ -0,0 +1,1258 @@
+// Copyright (c) 2011 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_TAB_CONTENTS_TAB_CONTENTS_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_H_
+#pragma once
+
+#include <deque>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/scoped_ptr.h"
+#include "base/string16.h"
+#include "chrome/browser/download/save_package.h"
+#include "chrome/browser/extensions/image_loading_tracker.h"
+#include "chrome/browser/fav_icon_helper.h"
+#include "chrome/browser/prefs/pref_change_registrar.h"
+#include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#include "chrome/browser/tab_contents/tab_specific_content_settings.h"
+#include "chrome/browser/ui/app_modal_dialogs/js_modal_dialog.h"
+#include "chrome/browser/webui/web_ui_factory.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/property_bag.h"
+#include "chrome/common/renderer_preferences.h"
+#include "chrome/common/translate_errors.h"
+#include "chrome/common/web_apps.h"
+#include "content/browser/tab_contents/constrained_window.h"
+#include "content/browser/tab_contents/language_state.h"
+#include "content/browser/tab_contents/navigation_controller.h"
+#include "content/browser/tab_contents/navigation_entry.h"
+#include "content/browser/tab_contents/page_navigator.h"
+#include "content/browser/tab_contents/render_view_host_manager.h"
+#include "net/base/load_states.h"
+#include "ui/gfx/native_widget_types.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
+namespace gfx {
+class Rect;
+}
+
+namespace history {
+class HistoryAddPageArgs;
+}
+
+namespace prerender {
+class PrerenderManager;
+class PrerenderPLTRecorder;
+}
+
+namespace printing {
+class PrintPreviewMessageHandler;
+class PrintViewManager;
+}
+
+namespace safe_browsing {
+class ClientSideDetectionHost;
+}
+
+class AutocompleteHistoryManager;
+class AutoFillManager;
+class BlockedContentContainer;
+class WebUI;
+class DesktopNotificationHandlerForTC;
+class DownloadItem;
+class Extension;
+class FileSelectHelper;
+class InfoBarDelegate;
+class LoadNotificationDetails;
+class OmniboxSearchHint;
+class PluginObserver;
+class Profile;
+class RenderViewHost;
+class SessionStorageNamespace;
+class SiteInstance;
+class SkBitmap;
+class TabContents;
+class TabContentsDelegate;
+class TabContentsObserver;
+class TabContentsSSLHelper;
+class TabContentsView;
+class URLPattern;
+struct RendererPreferences;
+struct ThumbnailScore;
+struct ViewHostMsg_DomMessage_Params;
+struct ViewHostMsg_FrameNavigate_Params;
+struct ViewHostMsg_RunFileChooser_Params;
+struct WebPreferences;
+
+// Describes what goes in the main content area of a tab. TabContents is
+// the only type of TabContents, and these should be merged together.
+class TabContents : public PageNavigator,
+ public NotificationObserver,
+ public RenderViewHostDelegate,
+ public RenderViewHostManager::Delegate,
+ public JavaScriptAppModalDialogDelegate,
+ public ImageLoadingTracker::Observer,
+ public TabSpecificContentSettings::Delegate {
+ public:
+ // Flags passed to the TabContentsDelegate.NavigationStateChanged to tell it
+ // what has changed. Combine them to update more than one thing.
+ enum InvalidateTypes {
+ INVALIDATE_URL = 1 << 0, // The URL has changed.
+ INVALIDATE_TAB = 1 << 1, // The favicon, app icon, or crashed
+ // state changed.
+ INVALIDATE_LOAD = 1 << 2, // The loading state has changed.
+ INVALIDATE_PAGE_ACTIONS = 1 << 3, // Page action icons have changed.
+ INVALIDATE_BOOKMARK_BAR = 1 << 4, // State of ShouldShowBookmarkBar
+ // changed.
+ INVALIDATE_TITLE = 1 << 5, // The title changed.
+ };
+
+ // |base_tab_contents| is used if we want to size the new tab contents view
+ // based on an existing tab contents view. This can be NULL if not needed.
+ //
+ // The session storage namespace parameter allows multiple render views and
+ // tab contentses to share the same session storage (part of the WebStorage
+ // spec) space. This is useful when restoring tabs, but most callers should
+ // pass in NULL which will cause a new SessionStorageNamespace to be created.
+ TabContents(Profile* profile,
+ SiteInstance* site_instance,
+ int routing_id,
+ const TabContents* base_tab_contents,
+ SessionStorageNamespace* session_storage_namespace);
+ virtual ~TabContents();
+
+ // Intrinsic tab state -------------------------------------------------------
+
+ // Returns the property bag for this tab contents, where callers can add
+ // extra data they may wish to associate with the tab. Returns a pointer
+ // rather than a reference since the PropertyAccessors expect this.
+ const PropertyBag* property_bag() const { return &property_bag_; }
+ PropertyBag* property_bag() { return &property_bag_; }
+
+ TabContentsDelegate* delegate() const { return delegate_; }
+ void set_delegate(TabContentsDelegate* d) { delegate_ = d; }
+
+ // Gets the controller for this tab contents.
+ NavigationController& controller() { return controller_; }
+ const NavigationController& controller() const { return controller_; }
+
+ // Returns the user profile associated with this TabContents (via the
+ // NavigationController).
+ Profile* profile() const { return controller_.profile(); }
+
+ // Returns true if contains content rendered by an extension.
+ bool HostsExtension() const;
+
+ // Returns the TabContentsSSLHelper, creating it if necessary.
+ TabContentsSSLHelper* GetSSLHelper();
+
+ // Returns the SavePackage which manages the page saving job. May be NULL.
+ SavePackage* save_package() const { return save_package_.get(); }
+
+ // Return the currently active RenderProcessHost and RenderViewHost. Each of
+ // these may change over time.
+ RenderProcessHost* GetRenderProcessHost() const;
+ RenderViewHost* render_view_host() const {
+ return render_manager_.current_host();
+ }
+
+ WebUI* web_ui() const {
+ return render_manager_.web_ui() ? render_manager_.web_ui()
+ : render_manager_.pending_web_ui();
+ }
+
+ // Returns the currently active RenderWidgetHostView. This may change over
+ // time and can be NULL (during setup and teardown).
+ RenderWidgetHostView* GetRenderWidgetHostView() const {
+ return render_manager_.GetRenderWidgetHostView();
+ }
+
+ // The TabContentsView will never change and is guaranteed non-NULL.
+ TabContentsView* view() const {
+ return view_.get();
+ }
+
+ // Returns the FavIconHelper of this TabContents.
+ FavIconHelper& fav_icon_helper() {
+ return *fav_icon_helper_.get();
+ }
+
+ // App extensions ------------------------------------------------------------
+
+ // Sets the extension denoting this as an app. If |extension| is non-null this
+ // tab becomes an app-tab. TabContents does not listen for unload events for
+ // the extension. It's up to consumers of TabContents to do that.
+ //
+ // NOTE: this should only be manipulated before the tab is added to a browser.
+ // TODO(sky): resolve if this is the right way to identify an app tab. If it
+ // is, than this should be passed in the constructor.
+ void SetExtensionApp(const Extension* extension);
+
+ // Convenience for setting the app extension by id. This does nothing if
+ // |extension_app_id| is empty, or an extension can't be found given the
+ // specified id.
+ void SetExtensionAppById(const std::string& extension_app_id);
+
+ const Extension* extension_app() const { return extension_app_; }
+ bool is_app() const { return extension_app_ != NULL; }
+
+ // If an app extension has been explicitly set for this TabContents its icon
+ // is returned.
+ //
+ // NOTE: the returned icon is larger than 16x16 (its size is
+ // Extension::EXTENSION_ICON_SMALLISH).
+ SkBitmap* GetExtensionAppIcon();
+
+ // Tab navigation state ------------------------------------------------------
+
+ // Returns the current navigation properties, which if a navigation is
+ // pending may be provisional (e.g., the navigation could result in a
+ // download, in which case the URL would revert to what it was previously).
+ virtual const GURL& GetURL() const;
+ virtual const string16& GetTitle() const;
+
+ // The max PageID of any page that this TabContents has loaded. PageIDs
+ // increase with each new page that is loaded by a tab. If this is a
+ // TabContents, then the max PageID is kept separately on each SiteInstance.
+ // Returns -1 if no PageIDs have yet been seen.
+ int32 GetMaxPageID();
+
+ // Updates the max PageID to be at least the given PageID.
+ void UpdateMaxPageID(int32 page_id);
+
+ // Returns the site instance associated with the current page. By default,
+ // there is no site instance. TabContents overrides this to provide proper
+ // access to its site instance.
+ virtual SiteInstance* GetSiteInstance() const;
+
+ // Defines whether this tab's URL should be displayed in the browser's URL
+ // bar. Normally this is true so you can see the URL. This is set to false
+ // for the new tab page and related pages so that the URL bar is empty and
+ // the user is invited to type into it.
+ virtual bool ShouldDisplayURL();
+
+ // Returns the favicon for this tab, or an isNull() bitmap if the tab does not
+ // have a favicon. The default implementation uses the current navigation
+ // entry.
+ SkBitmap GetFavIcon() const;
+
+ // Returns true if we are not using the default favicon.
+ bool FavIconIsValid() const;
+
+ // Returns whether the favicon should be displayed. If this returns false, no
+ // space is provided for the favicon, and the favicon is never displayed.
+ virtual bool ShouldDisplayFavIcon();
+
+ // Add and remove observers for page navigation notifications. Adding or
+ // removing multiple times has no effect. The order in which notifications
+ // are sent to observers is undefined. Clients must be sure to remove the
+ // observer before they go away.
+ void AddObserver(TabContentsObserver* observer);
+ void RemoveObserver(TabContentsObserver* observer);
+
+ // Return whether this tab contents is loading a resource.
+ bool is_loading() const { return is_loading_; }
+
+ // Returns whether this tab contents is waiting for a first-response for the
+ // main resource of the page. This controls whether the throbber state is
+ // "waiting" or "loading."
+ bool waiting_for_response() const { return waiting_for_response_; }
+
+ net::LoadState load_state() const { return load_state_; }
+ string16 load_state_host() const { return load_state_host_; }
+ uint64 upload_size() const { return upload_size_; }
+ uint64 upload_position() const { return upload_position_; }
+
+ const std::string& encoding() const { return encoding_; }
+ void set_encoding(const std::string& encoding);
+ void reset_encoding() {
+ encoding_.clear();
+ }
+
+ const WebApplicationInfo& web_app_info() const {
+ return web_app_info_;
+ }
+
+ const SkBitmap& app_icon() const { return app_icon_; }
+
+ // Sets an app icon associated with TabContents and fires an INVALIDATE_TITLE
+ // navigation state change to trigger repaint of title.
+ void SetAppIcon(const SkBitmap& app_icon);
+
+ bool displayed_insecure_content() const {
+ return displayed_insecure_content_;
+ }
+
+ // Internal state ------------------------------------------------------------
+
+ // This flag indicates whether the tab contents is currently being
+ // screenshotted by the DraggedTabController.
+ bool capturing_contents() const { return capturing_contents_; }
+ void set_capturing_contents(bool cap) { capturing_contents_ = cap; }
+
+ // Indicates whether this tab should be considered crashed. The setter will
+ // also notify the delegate when the flag is changed.
+ bool is_crashed() const {
+ return (crashed_status_ == base::TERMINATION_STATUS_PROCESS_CRASHED ||
+ crashed_status_ == base::TERMINATION_STATUS_ABNORMAL_TERMINATION ||
+ crashed_status_ == base::TERMINATION_STATUS_PROCESS_WAS_KILLED);
+ }
+ base::TerminationStatus crashed_status() const { return crashed_status_; }
+ int crashed_error_code() const { return crashed_error_code_; }
+ void SetIsCrashed(base::TerminationStatus status, int error_code);
+
+ // Call this after updating a page action to notify clients about the changes.
+ void PageActionStateChanged();
+
+ // Whether the tab is in the process of being destroyed.
+ // Added as a tentative work-around for focus related bug #4633. This allows
+ // us not to store focus when a tab is being closed.
+ bool is_being_destroyed() const { return is_being_destroyed_; }
+
+ // Convenience method for notifying the delegate of a navigation state
+ // change. See TabContentsDelegate.
+ void NotifyNavigationStateChanged(unsigned changed_flags);
+
+ // Invoked when the tab contents becomes selected. If you override, be sure
+ // and invoke super's implementation.
+ virtual void DidBecomeSelected();
+ base::TimeTicks last_selected_time() const {
+ return last_selected_time_;
+ }
+
+ // Invoked when the tab contents becomes hidden.
+ // NOTE: If you override this, call the superclass version too!
+ virtual void WasHidden();
+
+ // Activates this contents within its containing window, bringing that window
+ // to the foreground if necessary.
+ void Activate();
+
+ // Deactivates this contents by deactivating its containing window.
+ void Deactivate();
+
+ // TODO(brettw) document these.
+ virtual void ShowContents();
+ virtual void HideContents();
+
+ // Returns true if the before unload and unload listeners need to be
+ // fired. The value of this changes over time. For example, if true and the
+ // before unload listener is executed and allows the user to exit, then this
+ // returns false.
+ bool NeedToFireBeforeUnload();
+
+#ifdef UNIT_TEST
+ // Expose the render manager for testing.
+ RenderViewHostManager* render_manager() { return &render_manager_; }
+#endif
+
+ // In the underlying RenderViewHostManager, swaps in the provided
+ // RenderViewHost to replace the current RenderViewHost. The current RVH
+ // will be shutdown and ultimately deleted.
+ void SwapInRenderViewHost(RenderViewHost* rvh);
+
+ // Commands ------------------------------------------------------------------
+
+ // Implementation of PageNavigator.
+ virtual void OpenURL(const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition);
+
+ // Called by the NavigationController to cause the TabContents to navigate to
+ // the current pending entry. The NavigationController should be called back
+ // with CommitPendingEntry/RendererDidNavigate on success or
+ // DiscardPendingEntry. The callbacks can be inside of this function, or at
+ // some future time.
+ //
+ // The entry has a PageID of -1 if newly created (corresponding to navigation
+ // to a new URL).
+ //
+ // If this method returns false, then the navigation is discarded (equivalent
+ // to calling DiscardPendingEntry on the NavigationController).
+ virtual bool NavigateToPendingEntry(
+ NavigationController::ReloadType reload_type);
+
+ // Stop any pending navigation.
+ virtual void Stop();
+
+ // Called on a TabContents when it isn't a popup, but a new window.
+ virtual void DisassociateFromPopupCount();
+
+ // Creates a new TabContents with the same state as this one. The returned
+ // heap-allocated pointer is owned by the caller.
+ virtual TabContents* Clone();
+
+ // Shows the page info.
+ void ShowPageInfo(const GURL& url,
+ const NavigationEntry::SSLStatus& ssl,
+ bool show_history);
+
+ // Saves the favicon for the current page.
+ void SaveFavicon();
+
+ // Window management ---------------------------------------------------------
+
+ // Create a new window constrained to this TabContents' clip and visibility.
+ // The window is initialized by using the supplied delegate to obtain basic
+ // window characteristics, and the supplied view for the content. Note that
+ // the returned ConstrainedWindow might not yet be visible.
+ ConstrainedWindow* CreateConstrainedDialog(
+ ConstrainedWindowDelegate* delegate);
+
+ // Adds a new tab or window with the given already-created contents
+ void AddNewContents(TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture);
+
+ // Execute code in this tab. Returns true if the message was successfully
+ // sent.
+ bool ExecuteCode(int request_id, const std::string& extension_id,
+ bool is_js_code, const std::string& code_string,
+ bool all_frames);
+
+ // Called when the blocked popup notification is shown or hidden.
+ virtual void PopupNotificationVisibilityChanged(bool visible);
+
+ // Returns the number of constrained windows in this tab. Used by tests.
+ size_t constrained_window_count() { return child_windows_.size(); }
+
+ typedef std::deque<ConstrainedWindow*> ConstrainedWindowList;
+
+ // Return an iterator for the first constrained window in this tab contents.
+ ConstrainedWindowList::iterator constrained_window_begin()
+ { return child_windows_.begin(); }
+
+ // Return an iterator for the last constrained window in this tab contents.
+ ConstrainedWindowList::iterator constrained_window_end()
+ { return child_windows_.end(); }
+
+ // Views and focus -----------------------------------------------------------
+ // TODO(brettw): Most of these should be removed and the caller should call
+ // the view directly.
+
+ // Returns the actual window that is focused when this TabContents is shown.
+ gfx::NativeView GetContentNativeView() const;
+
+ // Returns the NativeView associated with this TabContents. Outside of
+ // automation in the context of the UI, this is required to be implemented.
+ gfx::NativeView GetNativeView() const;
+
+ // Returns the bounds of this TabContents in the screen coordinate system.
+ void GetContainerBounds(gfx::Rect *out) const;
+
+ // Makes the tab the focused window.
+ void Focus();
+
+ // Focuses the first (last if |reverse| is true) element in the page.
+ // Invoked when this tab is getting the focus through tab traversal (|reverse|
+ // is true when using Shift-Tab).
+ void FocusThroughTabTraversal(bool reverse);
+
+ // These next two functions are declared on RenderViewHostManager::Delegate
+ // but also accessed directly by other callers.
+
+ // Returns true if the location bar should be focused by default rather than
+ // the page contents. The view calls this function when the tab is focused
+ // to see what it should do.
+ virtual bool FocusLocationBarByDefault();
+
+ // Focuses the location bar.
+ virtual void SetFocusToLocationBar(bool select_all);
+
+ // Creates a view and sets the size for the specified RVH.
+ virtual void CreateViewAndSetSizeForRVH(RenderViewHost* rvh);
+
+ // Infobars ------------------------------------------------------------------
+
+ // Adds an InfoBar for the specified |delegate|.
+ virtual void AddInfoBar(InfoBarDelegate* delegate);
+
+ // Removes the InfoBar for the specified |delegate|.
+ void RemoveInfoBar(InfoBarDelegate* delegate);
+
+ // Replaces one infobar with another, without any animation in between.
+ void ReplaceInfoBar(InfoBarDelegate* old_delegate,
+ InfoBarDelegate* new_delegate);
+
+ // Enumeration and access functions.
+ size_t infobar_count() const { return infobar_delegates_.size(); }
+ // WARNING: This does not sanity-check |index|!
+ InfoBarDelegate* GetInfoBarDelegateAt(size_t index) {
+ return infobar_delegates_[index];
+ }
+
+ // Toolbars and such ---------------------------------------------------------
+
+ // Returns true if a Bookmark Bar should be shown for this tab.
+ virtual bool ShouldShowBookmarkBar();
+
+ // Notifies the delegate that a download is about to be started.
+ // This notification is fired before a local temporary file has been created.
+ bool CanDownload(int request_id);
+
+ // Notifies the delegate that a download started.
+ void OnStartDownload(DownloadItem* download);
+
+ // Notify our delegate that some of our content has animated.
+ void ToolbarSizeChanged(bool is_animating);
+
+ // Called when a ConstrainedWindow we own is about to be closed.
+ void WillClose(ConstrainedWindow* window);
+
+ // Called when a BlockedContentContainer we own is about to be closed.
+ void WillCloseBlockedContentContainer(BlockedContentContainer* container);
+
+ // Called when a ConstrainedWindow we own is moved or resized.
+ void DidMoveOrResize(ConstrainedWindow* window);
+
+ // Interstitials -------------------------------------------------------------
+
+ // Various other systems need to know about our interstitials.
+ bool showing_interstitial_page() const {
+ return render_manager_.interstitial_page() != NULL;
+ }
+
+ // Sets the passed passed interstitial as the currently showing interstitial.
+ // |interstitial_page| should be non NULL (use the remove_interstitial_page
+ // method to unset the interstitial) and no interstitial page should be set
+ // when there is already a non NULL interstitial page set.
+ void set_interstitial_page(InterstitialPage* interstitial_page) {
+ render_manager_.set_interstitial_page(interstitial_page);
+ }
+
+ // Unsets the currently showing interstitial.
+ void remove_interstitial_page() {
+ render_manager_.remove_interstitial_page();
+ }
+
+ // Returns the currently showing interstitial, NULL if no interstitial is
+ // showing.
+ InterstitialPage* interstitial_page() const {
+ return render_manager_.interstitial_page();
+ }
+
+ // Misc state & callbacks ----------------------------------------------------
+
+ // Set whether the contents should block javascript message boxes or not.
+ // Default is not to block any message boxes.
+ void set_suppress_javascript_messages(bool suppress_javascript_messages) {
+ suppress_javascript_messages_ = suppress_javascript_messages;
+ }
+
+ // Prepare for saving the current web page to disk.
+ void OnSavePage();
+
+ // Save page with the main HTML file path, the directory for saving resources,
+ // and the save type: HTML only or complete web page. Returns true if the
+ // saving process has been initiated successfully.
+ bool SavePage(const FilePath& main_file, const FilePath& dir_path,
+ SavePackage::SavePackageType save_type);
+
+ // Tells the user's email client to open a compose window containing the
+ // current page's URL.
+ void EmailPageLocation();
+
+ // Displays asynchronously a print preview (generated by the renderer) if not
+ // already displayed and ask the user for its preferred print settings with
+ // the "Print..." dialog box. (managed by the print worker thread).
+ // TODO(maruel): Creates a snapshot of the renderer to be used for the new
+ // tab for the printing facility.
+ void PrintPreview();
+
+ // Prints the current document immediately. Since the rendering is
+ // asynchronous, the actual printing will not be completed on the return of
+ // this function. Returns false if printing is impossible at the moment.
+ bool PrintNow();
+
+ // Notify the completion of a printing job.
+ void PrintingDone(int document_cookie, bool success);
+
+ // Returns true if the active NavigationEntry's page_id equals page_id.
+ bool IsActiveEntry(int32 page_id);
+
+ const std::string& contents_mime_type() const {
+ return contents_mime_type_;
+ }
+
+ // Returns true if this TabContents will notify about disconnection.
+ bool notify_disconnection() const { return notify_disconnection_; }
+
+ // Override the encoding and reload the page by sending down
+ // ViewMsg_SetPageEncoding to the renderer. |UpdateEncoding| is kinda
+ // the opposite of this, by which 'browser' is notified of
+ // the encoding of the current tab from 'renderer' (determined by
+ // auto-detect, http header, meta, bom detection, etc).
+ void SetOverrideEncoding(const std::string& encoding);
+
+ // Remove any user-defined override encoding and reload by sending down
+ // ViewMsg_ResetPageEncodingToDefault to the renderer.
+ void ResetOverrideEncoding();
+
+ void WindowMoveOrResizeStarted();
+
+ // Sets whether all TabContents added by way of |AddNewContents| should be
+ // blocked. Transitioning from all blocked to not all blocked results in
+ // reevaluating any blocked TabContents, which may result in unblocking some
+ // of the blocked TabContents.
+ void SetAllContentsBlocked(bool value);
+
+ BlockedContentContainer* blocked_content_container() const {
+ return blocked_contents_;
+ }
+
+ RendererPreferences* GetMutableRendererPrefs() {
+ return &renderer_preferences_;
+ }
+
+ void set_opener_web_ui_type(WebUITypeID opener_web_ui_type) {
+ opener_web_ui_type_ = opener_web_ui_type;
+ }
+
+ // We want to time how long it takes to create a new tab page. This method
+ // gets called as parts of the new tab page have loaded.
+ void LogNewTabTime(const std::string& event_name);
+
+ // Set the time when we started to create the new tab page. This time is
+ // from before we created this TabContents.
+ void set_new_tab_start_time(const base::TimeTicks& time) {
+ new_tab_start_time_ = time;
+ }
+
+ // Notification that tab closing has started. This can be called multiple
+ // times, subsequent calls are ignored.
+ void OnCloseStarted();
+
+ LanguageState& language_state() {
+ return language_state_;
+ }
+
+ // Returns true if underlying TabContentsView should accept drag-n-drop.
+ bool ShouldAcceptDragAndDrop() const;
+
+ // A render view-originated drag has ended. Informs the render view host and
+ // tab contents delegate.
+ void SystemDragEnded();
+
+ // Indicates if this tab was explicitly closed by the user (control-w, close
+ // tab menu item...). This is false for actions that indirectly close the tab,
+ // such as closing the window. The setter is maintained by TabStripModel, and
+ // the getter only useful from within TAB_CLOSED notification
+ void set_closed_by_user_gesture(bool value) {
+ closed_by_user_gesture_ = value;
+ }
+ bool closed_by_user_gesture() const { return closed_by_user_gesture_; }
+
+ // Overridden from JavaScriptAppModalDialogDelegate:
+ virtual void OnMessageBoxClosed(IPC::Message* reply_msg,
+ bool success,
+ const std::wstring& prompt);
+ virtual void SetSuppressMessageBoxes(bool suppress_message_boxes);
+ virtual gfx::NativeWindow GetMessageBoxRootWindow();
+ virtual TabContents* AsTabContents();
+ virtual ExtensionHost* AsExtensionHost();
+
+ // The BookmarkDragDelegate is used to forward bookmark drag and drop events
+ // to extensions.
+ virtual RenderViewHostDelegate::BookmarkDrag* GetBookmarkDragDelegate();
+
+ // It is up to callers to call SetBookmarkDragDelegate(NULL) when
+ // |bookmark_drag| is deleted since this class does not take ownership of
+ // |bookmark_drag|.
+ virtual void SetBookmarkDragDelegate(
+ RenderViewHostDelegate::BookmarkDrag* bookmark_drag);
+
+ // The TabSpecificContentSettings object is used to query the blocked content
+ // state by various UI elements.
+ TabSpecificContentSettings* GetTabSpecificContentSettings() const;
+
+ // Updates history with the specified navigation. This is called by
+ // OnMsgNavigate to update history state.
+ void UpdateHistoryForNavigation(
+ scoped_refptr<history::HistoryAddPageArgs> add_page_args);
+
+ // Sends the page title to the history service. This is called when we receive
+ // the page title and we know we want to update history.
+ void UpdateHistoryPageTitle(const NavigationEntry& entry);
+
+ // Gets the zoom level for this tab.
+ double GetZoomLevel() const;
+
+ // Gets the zoom percent for this tab.
+ int GetZoomPercent(bool* enable_increment, bool* enable_decrement);
+
+ // Shows a fade effect over this tab contents. Repeated calls will be ignored
+ // until the fade is canceled. If |animate| is true the fade should animate.
+ void FadeForInstant(bool animate);
+
+ // Immediately removes the fade.
+ void CancelInstantFade();
+
+ // Opens view-source tab for this contents.
+ void ViewSource();
+
+ // Gets the minimum/maximum zoom percent.
+ int minimum_zoom_percent() const { return minimum_zoom_percent_; }
+ int maximum_zoom_percent() const { return maximum_zoom_percent_; }
+
+ int content_restrictions() const { return content_restrictions_; }
+
+ AutocompleteHistoryManager* autocomplete_history_manager() {
+ return autocomplete_history_manager_.get();
+ }
+ AutoFillManager* autofill_manager() { return autofill_manager_.get(); }
+
+ safe_browsing::ClientSideDetectionHost* safebrowsing_detection_host() {
+ return safebrowsing_detection_host_.get();
+ }
+
+ protected:
+ // from RenderViewHostDelegate.
+ virtual bool OnMessageReceived(const IPC::Message& message);
+
+ private:
+ friend class NavigationController;
+ // Used to access the child_windows_ (ConstrainedWindowList) for testing
+ // automation purposes.
+ friend class TestingAutomationProvider;
+
+ FRIEND_TEST_ALL_PREFIXES(TabContentsTest, NoJSMessageOnInterstitials);
+ FRIEND_TEST_ALL_PREFIXES(TabContentsTest, UpdateTitle);
+ FRIEND_TEST_ALL_PREFIXES(TabContentsTest, CrossSiteCantPreemptAfterUnload);
+ FRIEND_TEST_ALL_PREFIXES(FormStructureBrowserTest, HTMLFiles);
+ FRIEND_TEST_ALL_PREFIXES(NavigationControllerTest, HistoryNavigate);
+ FRIEND_TEST_ALL_PREFIXES(RenderViewHostManagerTest, PageDoesBackAndReload);
+
+ // Temporary until the view/contents separation is complete.
+ friend class TabContentsView;
+#if defined(OS_WIN)
+ friend class TabContentsViewWin;
+#elif defined(OS_MACOSX)
+ friend class TabContentsViewMac;
+#elif defined(TOOLKIT_USES_GTK)
+ friend class TabContentsViewGtk;
+#endif
+
+ // So InterstitialPage can access SetIsLoading.
+ friend class InterstitialPage;
+
+ // TODO(brettw) TestTabContents shouldn't exist!
+ friend class TestTabContents;
+
+ // Used to access the CreateHistoryAddPageArgs member function.
+ friend class ExternalTabContainer;
+
+ // Used to access RVH Delegates.
+ friend class prerender::PrerenderManager;
+
+ // Add all the TabContentObservers.
+ void AddObservers();
+
+ // Message handlers.
+ void OnDidStartProvisionalLoadForFrame(int64 frame_id,
+ bool main_frame,
+ const GURL& url);
+ void OnDidRedirectProvisionalLoad(int32 page_id,
+ const GURL& source_url,
+ const GURL& target_url);
+ void OnDidFailProvisionalLoadWithError(int64 frame_id,
+ bool main_frame,
+ int error_code,
+ const GURL& url,
+ bool showing_repost_interstitial);
+ void OnDidLoadResourceFromMemoryCache(const GURL& url,
+ const std::string& security_info);
+ void OnDidDisplayInsecureContent();
+ void OnDidRunInsecureContent(const std::string& security_origin,
+ const GURL& target_url);
+ void OnDocumentLoadedInFrame(int64 frame_id);
+ void OnDidFinishLoad(int64 frame_id);
+ void OnUpdateContentRestrictions(int restrictions);
+ void OnPDFHasUnsupportedFeature();
+
+ void OnGoToEntryAtOffset(int offset);
+ void OnDidGetApplicationInfo(int32 page_id, const WebApplicationInfo& info);
+ void OnInstallApplication(const WebApplicationInfo& info);
+ void OnPageContents(const GURL& url,
+ int32 page_id,
+ const string16& contents,
+ const std::string& language,
+ bool page_translatable);
+ void OnPageTranslated(int32 page_id,
+ const std::string& original_lang,
+ const std::string& translated_lang,
+ TranslateErrors::Type error_type);
+ void OnSetSuggestions(int32 page_id,
+ const std::vector<std::string>& suggestions);
+ void OnInstantSupportDetermined(int32 page_id, bool result);
+ void OnRunFileChooser(const ViewHostMsg_RunFileChooser_Params& params);
+
+ // Changes the IsLoading state and notifies delegate as needed
+ // |details| is used to provide details on the load that just finished
+ // (but can be null if not applicable). Can be overridden.
+ void SetIsLoading(bool is_loading,
+ LoadNotificationDetails* details);
+
+ // Adds the incoming |new_contents| to the |blocked_contents_| container.
+ void AddPopup(TabContents* new_contents,
+ const gfx::Rect& initial_pos);
+
+ // Called by derived classes to indicate that we're no longer waiting for a
+ // response. This won't actually update the throbber, but it will get picked
+ // up at the next animation step if the throbber is going.
+ void SetNotWaitingForResponse() { waiting_for_response_ = false; }
+
+ ConstrainedWindowList child_windows_;
+
+ // Expires InfoBars that need to be expired, according to the state carried
+ // in |details|, in response to a new NavigationEntry being committed (the
+ // user navigated to another page).
+ void ExpireInfoBars(
+ const NavigationController::LoadCommittedDetails& details);
+
+ // Returns the WebUI for the current state of the tab. This will either be
+ // the pending WebUI, the committed WebUI, or NULL.
+ WebUI* GetWebUIForCurrentState();
+
+ // Navigation helpers --------------------------------------------------------
+ //
+ // These functions are helpers for Navigate() and DidNavigate().
+
+ // Handles post-navigation tasks in DidNavigate AFTER the entry has been
+ // committed to the navigation controller. Note that the navigation entry is
+ // not provided since it may be invalid/changed after being committed. The
+ // current navigation entry is in the NavigationController at this point.
+ void DidNavigateMainFramePostCommit(
+ const NavigationController::LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void DidNavigateAnyFramePostCommit(
+ RenderViewHost* render_view_host,
+ const NavigationController::LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Closes all constrained windows.
+ void CloseConstrainedWindows();
+
+ // Send the alternate error page URL to the renderer. This method is virtual
+ // so special html pages can override this (e.g., the new tab page).
+ virtual void UpdateAlternateErrorPageURL();
+
+ // Send webkit specific settings to the renderer.
+ void UpdateWebPreferences();
+
+ // Instruct the renderer to update the zoom level.
+ void UpdateZoomLevel();
+
+ // If our controller was restored and the page id is > than the site
+ // instance's page id, the site instances page id is updated as well as the
+ // renderers max page id.
+ void UpdateMaxPageIDIfNecessary(SiteInstance* site_instance,
+ RenderViewHost* rvh);
+
+ // Returns the history::HistoryAddPageArgs to use for adding a page to
+ // history.
+ scoped_refptr<history::HistoryAddPageArgs> CreateHistoryAddPageArgs(
+ const GURL& virtual_url,
+ const NavigationController::LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Saves the given title to the navigation entry and does associated work. It
+ // will update history and the view for the new title, and also synthesize
+ // titles for file URLs that have none (so we require that the URL of the
+ // entry already be set).
+ //
+ // This is used as the backend for state updates, which include a new title,
+ // or the dedicated set title message. It returns true if the new title is
+ // different and was therefore updated.
+ bool UpdateTitleForEntry(NavigationEntry* entry, const std::wstring& title);
+
+ // Causes the TabContents to navigate in the right renderer to |entry|, which
+ // must be already part of the entries in the navigation controller.
+ // This does not change the NavigationController state.
+ bool NavigateToEntry(const NavigationEntry& entry,
+ NavigationController::ReloadType reload_type);
+
+ // Misc non-view stuff -------------------------------------------------------
+
+ // Helper functions for sending notifications.
+ void NotifySwapped();
+ void NotifyConnected();
+ void NotifyDisconnected();
+
+ // TabSpecificContentSettings::Delegate implementation.
+ virtual void OnContentSettingsAccessed(bool content_was_blocked);
+
+ // RenderViewHostDelegate ----------------------------------------------------
+
+ // RenderViewHostDelegate implementation.
+ virtual RenderViewHostDelegate::View* GetViewDelegate();
+ virtual RenderViewHostDelegate::RendererManagement*
+ GetRendererManagementDelegate();
+ virtual RenderViewHostDelegate::ContentSettings* GetContentSettingsDelegate();
+ virtual RenderViewHostDelegate::SSL* GetSSLDelegate();
+ virtual AutomationResourceRoutingDelegate*
+ GetAutomationResourceRoutingDelegate();
+ virtual TabContents* GetAsTabContents();
+ virtual ViewType::Type GetRenderViewType() const;
+ virtual int GetBrowserWindowID() const;
+ virtual void RenderViewCreated(RenderViewHost* render_view_host);
+ virtual void RenderViewReady(RenderViewHost* render_view_host);
+ virtual void RenderViewGone(RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code);
+ virtual void RenderViewDeleted(RenderViewHost* render_view_host);
+ virtual void DidNavigate(RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params);
+ virtual void UpdateState(RenderViewHost* render_view_host,
+ int32 page_id,
+ const std::string& state);
+ virtual void UpdateTitle(RenderViewHost* render_view_host,
+ int32 page_id,
+ const std::wstring& title);
+ virtual void UpdateEncoding(RenderViewHost* render_view_host,
+ const std::string& encoding);
+ virtual void UpdateTargetURL(int32 page_id, const GURL& url);
+ virtual void UpdateThumbnail(const GURL& url,
+ const SkBitmap& bitmap,
+ const ThumbnailScore& score);
+ virtual void UpdateInspectorSetting(const std::string& key,
+ const std::string& value);
+ virtual void ClearInspectorSettings();
+ virtual void Close(RenderViewHost* render_view_host);
+ virtual void RequestMove(const gfx::Rect& new_bounds);
+ virtual void DidStartLoading();
+ virtual void DidStopLoading();
+ virtual void DidChangeLoadProgress(double progress);
+ virtual void DocumentOnLoadCompletedInMainFrame(
+ RenderViewHost* render_view_host,
+ int32 page_id);
+ virtual void RequestOpenURL(const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition);
+ virtual void DomOperationResponse(const std::string& json_string,
+ int automation_id);
+ virtual void ProcessWebUIMessage(const ViewHostMsg_DomMessage_Params& params);
+ virtual void ProcessExternalHostMessage(const std::string& message,
+ const std::string& origin,
+ const std::string& target);
+ virtual void RunJavaScriptMessage(const std::wstring& message,
+ const std::wstring& default_prompt,
+ const GURL& frame_url,
+ const int flags,
+ IPC::Message* reply_msg,
+ bool* did_suppress_message);
+ virtual void RunBeforeUnloadConfirm(const std::wstring& message,
+ IPC::Message* reply_msg);
+ virtual void ShowModalHTMLDialog(const GURL& url, int width, int height,
+ const std::string& json_arguments,
+ IPC::Message* reply_msg);
+ virtual GURL GetAlternateErrorPageURL() const;
+ virtual RendererPreferences GetRendererPrefs(Profile* profile) const;
+ virtual WebPreferences GetWebkitPrefs();
+ virtual void OnUserGesture();
+ virtual void OnIgnoredUIEvent();
+ virtual void OnCrossSiteResponse(int new_render_process_host_id,
+ int new_request_id);
+ virtual void RendererUnresponsive(RenderViewHost* render_view_host,
+ bool is_during_unload);
+ virtual void RendererResponsive(RenderViewHost* render_view_host);
+ virtual void LoadStateChanged(const GURL& url, net::LoadState load_state,
+ uint64 upload_position, uint64 upload_size);
+ virtual bool IsExternalTabContainer() const;
+ virtual void DidInsertCSS();
+ virtual void FocusedNodeChanged(bool is_editable_node);
+ virtual void UpdateZoomLimits(int minimum_percent,
+ int maximum_percent,
+ bool remember);
+ virtual void WorkerCrashed();
+
+ // RenderViewHostManager::Delegate -------------------------------------------
+
+ // Blocks/unblocks interaction with renderer process.
+ void BlockTabContent(bool blocked);
+
+ virtual void BeforeUnloadFiredFromRenderManager(
+ bool proceed,
+ bool* proceed_to_fire_unload);
+ virtual void DidStartLoadingFromRenderManager(
+ RenderViewHost* render_view_host);
+ virtual void RenderViewGoneFromRenderManager(
+ RenderViewHost* render_view_host);
+ virtual void UpdateRenderViewSizeForRenderManager();
+ virtual void NotifySwappedFromRenderManager();
+ virtual NavigationController& GetControllerForRenderManager();
+ virtual WebUI* CreateWebUIForRenderManager(const GURL& url);
+ virtual NavigationEntry* GetLastCommittedNavigationEntryForRenderManager();
+
+ // Initializes the given renderer if necessary and creates the view ID
+ // corresponding to this view host. If this method is not called and the
+ // process is not shared, then the TabContents will act as though the renderer
+ // is not running (i.e., it will render "sad tab"). This method is
+ // automatically called from LoadURL.
+ //
+ // If you are attaching to an already-existing RenderView, you should call
+ // InitWithExistingID.
+ virtual bool CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host);
+
+ // NotificationObserver ------------------------------------------------------
+
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // App extensions related methods:
+
+ // Returns the first extension whose extent contains |url|.
+ const Extension* GetExtensionContaining(const GURL& url);
+
+ // Resets app_icon_ and if |extension| is non-null creates a new
+ // ImageLoadingTracker to load the extension's image.
+ void UpdateExtensionAppIcon(const Extension* extension);
+
+ // ImageLoadingTracker::Observer.
+ virtual void OnImageLoaded(SkBitmap* image, ExtensionResource resource,
+ int index);
+
+ // Checks with the PrerenderManager if the specified URL has been preloaded,
+ // and if so, swap the RenderViewHost with the preload into this TabContents
+ // object.
+ bool MaybeUsePreloadedPage(const GURL& url);
+
+ // Data for core operation ---------------------------------------------------
+
+ // Delegate for notifying our owner about stuff. Not owned by us.
+ TabContentsDelegate* delegate_;
+
+ // Handles the back/forward list and loading.
+ NavigationController controller_;
+
+ // The corresponding view.
+ scoped_ptr<TabContentsView> view_;
+
+ // Helper classes ------------------------------------------------------------
+
+ // Manages creation and swapping of render views.
+ RenderViewHostManager render_manager_;
+
+ // Stores random bits of data for others to associate with this object.
+ PropertyBag property_bag_;
+
+ // Registers and unregisters us for notifications.
+ NotificationRegistrar registrar_;
+
+ // Registers and unregisters for pref notifications.
+ PrefChangeRegistrar pref_change_registrar_;
+
+ // Handles print job for this contents.
+ scoped_ptr<printing::PrintViewManager> printing_;
+
+ // Handles print preview for this contents.
+ scoped_ptr<printing::PrintPreviewMessageHandler> print_preview_;
+
+ // SavePackage, lazily created.
+ scoped_refptr<SavePackage> save_package_;
+
+ // AutocompleteHistoryManager.
+ scoped_ptr<AutocompleteHistoryManager> autocomplete_history_manager_;
+
+ // AutoFillManager.
+ scoped_ptr<AutoFillManager> autofill_manager_;
+
+ // Handles plugin messages.
+ scoped_ptr<PluginObserver> plugin_observer_;
+
+ // Prerender PageLoadTime Recorder.
+ scoped_ptr<prerender::PrerenderPLTRecorder> prerender_plt_recorder_;
+
+ // TabContentsSSLHelper, lazily created.
+ scoped_ptr<TabContentsSSLHelper> ssl_helper_;
+
+ // FileSelectHelper, lazily created.
+ scoped_ptr<FileSelectHelper> file_select_helper_;
+
+ // Handles drag and drop event forwarding to extensions.
+ BookmarkDrag* bookmark_drag_;
+
+ // Handles downloading favicons.
+ scoped_ptr<FavIconHelper> fav_icon_helper_;
+
+ // Cached web app info data.
+ WebApplicationInfo web_app_info_;
+
+ // Cached web app icon.
+ SkBitmap app_icon_;
+
+ // RenderViewHost::ContentSettingsDelegate.
+ scoped_ptr<TabSpecificContentSettings> content_settings_delegate_;
+
+ // Handles desktop notification IPCs.
+ scoped_ptr<DesktopNotificationHandlerForTC> desktop_notification_handler_;
+
+ // Handles IPCs related to SafeBrowsing client-side phishing detection.
+ scoped_ptr<safe_browsing::ClientSideDetectionHost>
+ safebrowsing_detection_host_;
+
+ // Data for loading state ----------------------------------------------------
+
+ // Indicates whether we're currently loading a resource.
+ bool is_loading_;
+
+ // Indicates if the tab is considered crashed.
+ base::TerminationStatus crashed_status_;
+ int crashed_error_code_;
+
+ // See waiting_for_response() above.
+ bool waiting_for_response_;
+
+ // Indicates the largest PageID we've seen. This field is ignored if we are
+ // a TabContents, in which case the max page ID is stored separately with
+ // each SiteInstance.
+ // TODO(brettw) this seems like it can be removed according to the comment.
+ int32 max_page_id_;
+
+ // System time at which the current load was started.
+ base::TimeTicks current_load_start_;
+
+ // The current load state and the URL associated with it.
+ net::LoadState load_state_;
+ string16 load_state_host_;
+ // Upload progress, for displaying in the status bar.
+ // Set to zero when there is no significant upload happening.
+ uint64 upload_size_;
+ uint64 upload_position_;
+
+ // Data for current page -----------------------------------------------------
+
+ // Whether we have a (non-empty) title for the current page.
+ // Used to prevent subsequent title updates from affecting history. This
+ // prevents some weirdness because some AJAXy apps use titles for status
+ // messages.
+ bool received_page_title_;
+
+ // When a navigation occurs, we record its contents MIME type. It can be
+ // used to check whether we can do something for some special contents.
+ std::string contents_mime_type_;
+
+ // Character encoding.
+ std::string encoding_;
+
+ // Object that holds any blocked TabContents spawned from this TabContents.
+ BlockedContentContainer* blocked_contents_;
+
+ // Should we block all child TabContents this attempts to spawn.
+ bool all_contents_blocked_;
+
+ // TODO(pkasting): Hack to try and fix Linux browser tests.
+ bool dont_notify_render_view_;
+
+ // True if this is a secure page which displayed insecure content.
+ bool displayed_insecure_content_;
+
+ // Data for shelves and stuff ------------------------------------------------
+
+ // Delegates for InfoBars associated with this TabContents.
+ std::vector<InfoBarDelegate*> infobar_delegates_;
+
+ // Data for app extensions ---------------------------------------------------
+
+ // If non-null this tab is an app tab and this is the extension the tab was
+ // created for.
+ const Extension* extension_app_;
+
+ // Icon for extension_app_ (if non-null) or extension_for_current_page_.
+ SkBitmap extension_app_icon_;
+
+ // Used for loading extension_app_icon_.
+ scoped_ptr<ImageLoadingTracker> extension_app_image_loader_;
+
+ // Data for misc internal state ----------------------------------------------
+
+ // See capturing_contents() above.
+ bool capturing_contents_;
+
+ // See getter above.
+ bool is_being_destroyed_;
+
+ // Indicates whether we should notify about disconnection of this
+ // TabContents. This is used to ensure disconnection notifications only
+ // happen if a connection notification has happened and that they happen only
+ // once.
+ bool notify_disconnection_;
+
+ // Maps from handle to page_id.
+ typedef std::map<FaviconService::Handle, int32> HistoryRequestMap;
+ HistoryRequestMap history_requests_;
+
+#if defined(OS_WIN)
+ // Handle to an event that's set when the page is showing a message box (or
+ // equivalent constrained window). Plugin processes check this to know if
+ // they should pump messages then.
+ base::win::ScopedHandle message_box_active_;
+#endif
+
+ // The time that the last javascript message was dismissed.
+ base::TimeTicks last_javascript_message_dismissal_;
+
+ // True if the user has decided to block future javascript messages. This is
+ // reset on navigations to false on navigations.
+ bool suppress_javascript_messages_;
+
+ // Set to true when there is an active "before unload" dialog. When true,
+ // we've forced the throbber to start in Navigate, and we need to remember to
+ // turn it off in OnJavaScriptMessageBoxClosed if the navigation is canceled.
+ bool is_showing_before_unload_dialog_;
+
+ // Shows an info-bar to users when they search from a known search engine and
+ // have never used the monibox for search before.
+ scoped_ptr<OmniboxSearchHint> omnibox_search_hint_;
+
+ // Settings that get passed to the renderer process.
+ RendererPreferences renderer_preferences_;
+
+ // If this tab was created from a renderer using window.open, this will be
+ // non-NULL and represent the WebUI of the opening renderer.
+ WebUITypeID opener_web_ui_type_;
+
+ // The time that we started to create the new tab page.
+ base::TimeTicks new_tab_start_time_;
+
+ // The time that we started to close the tab.
+ base::TimeTicks tab_close_start_time_;
+
+ // The time that this tab was last selected.
+ base::TimeTicks last_selected_time_;
+
+ // Information about the language the page is in and has been translated to.
+ LanguageState language_state_;
+
+ // See description above setter.
+ bool closed_by_user_gesture_;
+
+ // Minimum/maximum zoom percent.
+ int minimum_zoom_percent_;
+ int maximum_zoom_percent_;
+ // If true, the default zoom limits have been overriden for this tab, in which
+ // case we don't want saved settings to apply to it and we don't want to
+ // remember it.
+ bool temporary_zoom_settings_;
+
+ // A list of observers notified when page state changes. Weak references.
+ ObserverList<TabContentsObserver> observers_;
+
+ // Content restrictions, used to disable print/copy etc based on content's
+ // (full-page plugins for now only) permissions.
+ int content_restrictions_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabContents);
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_H_
diff --git a/content/browser/tab_contents/tab_contents_delegate.cc b/content/browser/tab_contents/tab_contents_delegate.cc
new file mode 100644
index 0000000..0510adf
--- /dev/null
+++ b/content/browser/tab_contents/tab_contents_delegate.cc
@@ -0,0 +1,227 @@
+// Copyright (c) 2011 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/tab_contents/tab_contents_delegate.h"
+
+#include "chrome/common/url_constants.h"
+#include "ui/gfx/rect.h"
+
+std::string TabContentsDelegate::GetNavigationHeaders(const GURL& url) {
+ return std::string();
+}
+
+void TabContentsDelegate::LoadProgressChanged(double progress) {
+}
+
+void TabContentsDelegate::DetachContents(TabContents* source) {
+}
+
+bool TabContentsDelegate::IsPopup(const TabContents* source) const {
+ return false;
+}
+
+TabContents* TabContentsDelegate::GetConstrainingContents(TabContents* source) {
+ return source;
+}
+
+bool TabContentsDelegate::ShouldFocusConstrainedWindow() {
+ return true;
+}
+
+void TabContentsDelegate::WillShowConstrainedWindow(TabContents* source) {
+}
+
+void TabContentsDelegate::ContentsMouseEvent(
+ TabContents* source, const gfx::Point& location, bool motion) {
+}
+
+void TabContentsDelegate::ContentsZoomChange(bool zoom_in) { }
+
+void TabContentsDelegate::OnContentSettingsChange(TabContents* source) { }
+
+bool TabContentsDelegate::IsApplication() const { return false; }
+
+void TabContentsDelegate::ConvertContentsToApplication(TabContents* source) { }
+
+bool TabContentsDelegate::CanReloadContents(TabContents* source) const {
+ return true;
+}
+
+void TabContentsDelegate::ShowHtmlDialog(HtmlDialogUIDelegate* delegate,
+ gfx::NativeWindow parent_window) {
+}
+
+void TabContentsDelegate::WillRunBeforeUnloadConfirm() {
+}
+
+bool TabContentsDelegate::ShouldSuppressDialogs() {
+ return false;
+}
+
+void TabContentsDelegate::BeforeUnloadFired(TabContents* tab,
+ bool proceed,
+ bool* proceed_to_fire_unload) {
+ *proceed_to_fire_unload = true;
+}
+
+void TabContentsDelegate::ForwardMessageToExternalHost(
+ const std::string& message,
+ const std::string& origin,
+ const std::string& target) {
+}
+
+bool TabContentsDelegate::IsExternalTabContainer() const { return false; }
+
+void TabContentsDelegate::SetFocusToLocationBar(bool select_all) {}
+
+bool TabContentsDelegate::ShouldFocusPageAfterCrash() {
+ return true;
+}
+
+void TabContentsDelegate::RenderWidgetShowing() {}
+
+bool TabContentsDelegate::TakeFocus(bool reverse) {
+ return false;
+}
+
+void TabContentsDelegate::LostCapture() {
+}
+
+void TabContentsDelegate::SetTabContentBlocked(
+ TabContents* contents, bool blocked) {
+}
+
+void TabContentsDelegate::TabContentsFocused(TabContents* tab_content) {
+}
+
+int TabContentsDelegate::GetExtraRenderViewHeight() const {
+ return 0;
+}
+
+bool TabContentsDelegate::CanDownload(int request_id) {
+ return true;
+}
+
+void TabContentsDelegate::OnStartDownload(DownloadItem* download,
+ TabContents* tab) {
+}
+
+bool TabContentsDelegate::HandleContextMenu(const ContextMenuParams& params) {
+ return false;
+}
+
+bool TabContentsDelegate::ExecuteContextMenuCommand(int command) {
+ return false;
+}
+
+void TabContentsDelegate::ShowPageInfo(Profile* profile,
+ const GURL& url,
+ const NavigationEntry::SSLStatus& ssl,
+ bool show_history) {
+}
+
+void TabContentsDelegate::ViewSourceForTab(TabContents* source,
+ const GURL& page_url) {
+ // Fall back implementation based entirely on the view-source scheme.
+ // It suffers from http://crbug.com/523 and that is why browser overrides
+ // it with proper implementation.
+ GURL url = GURL(chrome::kViewSourceScheme + std::string(":") +
+ page_url.spec());
+ OpenURLFromTab(source,
+ url,
+ GURL(),
+ NEW_FOREGROUND_TAB,
+ PageTransition::LINK);
+}
+
+bool TabContentsDelegate::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ return false;
+}
+
+void TabContentsDelegate::HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+}
+
+void TabContentsDelegate::HandleMouseUp() {
+}
+
+void TabContentsDelegate::HandleMouseActivate() {
+}
+
+void TabContentsDelegate::DragEnded() {
+}
+
+void TabContentsDelegate::ShowRepostFormWarningDialog(
+ TabContents* tab_contents) {
+}
+
+void TabContentsDelegate::ShowContentSettingsWindow(
+ ContentSettingsType content_type) {
+}
+
+void TabContentsDelegate::ShowCollectedCookiesDialog(
+ TabContents* tab_contents) {
+}
+
+bool TabContentsDelegate::OnGoToEntryOffset(int offset) {
+ return true;
+}
+
+bool TabContentsDelegate::ShouldAddNavigationToHistory(
+ const history::HistoryAddPageArgs& add_page_args,
+ NavigationType::Type navigation_type) {
+ return true;
+}
+
+void TabContentsDelegate::OnDidGetApplicationInfo(TabContents* tab_contents,
+ int32 page_id) {
+}
+
+// Notification when an application programmatically requests installation.
+void TabContentsDelegate::OnInstallApplication(
+ TabContents* tab_contents,
+ const WebApplicationInfo& app_info) {
+}
+
+gfx::NativeWindow TabContentsDelegate::GetFrameNativeWindow() {
+ return NULL;
+}
+
+void TabContentsDelegate::TabContentsCreated(TabContents* new_contents) {
+}
+
+bool TabContentsDelegate::infobars_enabled() {
+ return true;
+}
+
+bool TabContentsDelegate::ShouldEnablePreferredSizeNotifications() {
+ return false;
+}
+
+void TabContentsDelegate::UpdatePreferredSize(const gfx::Size& pref_size) {
+}
+
+void TabContentsDelegate::OnSetSuggestions(
+ int32 page_id,
+ const std::vector<std::string>& suggestions) {
+}
+
+void TabContentsDelegate::OnInstantSupportDetermined(int32 page_id,
+ bool result) {
+}
+
+void TabContentsDelegate::ContentRestrictionsChanged(TabContents* source) {
+}
+
+bool TabContentsDelegate::ShouldShowHungRendererDialog() {
+ return true;
+}
+
+void TabContentsDelegate::WorkerCrashed() {
+}
+
+TabContentsDelegate::~TabContentsDelegate() {
+}
diff --git a/content/browser/tab_contents/tab_contents_delegate.h b/content/browser/tab_contents/tab_contents_delegate.h
new file mode 100644
index 0000000..8d2805b
--- /dev/null
+++ b/content/browser/tab_contents/tab_contents_delegate.h
@@ -0,0 +1,331 @@
+// Copyright (c) 2011 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_TAB_CONTENTS_TAB_CONTENTS_DELEGATE_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_DELEGATE_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "chrome/browser/automation/automation_resource_routing_delegate.h"
+#include "chrome/common/content_settings_types.h"
+#include "chrome/common/navigation_types.h"
+#include "chrome/common/page_transition_types.h"
+#include "content/browser/tab_contents/navigation_entry.h"
+#include "ui/gfx/native_widget_types.h"
+#include "webkit/glue/window_open_disposition.h"
+
+namespace gfx {
+class Point;
+class Rect;
+class Size;
+}
+
+namespace history {
+class HistoryAddPageArgs;
+}
+
+struct ContextMenuParams;
+class DownloadItem;
+class GURL;
+class HtmlDialogUIDelegate;
+struct NativeWebKeyboardEvent;
+class Profile;
+class RenderViewHost;
+class TabContents;
+struct WebApplicationInfo;
+
+// Objects implement this interface to get notified about changes in the
+// TabContents and to provide necessary functionality.
+class TabContentsDelegate : public AutomationResourceRoutingDelegate {
+ public:
+ // Opens a new URL inside the passed in TabContents (if source is 0 open
+ // in the current front-most tab), unless |disposition| indicates the url
+ // should be opened in a new tab or window.
+ //
+ // A NULL source indicates the current tab (callers should probably use
+ // OpenURL() for these cases which does it for you).
+ virtual void OpenURLFromTab(TabContents* source,
+ const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition) = 0;
+
+ // Called to inform the delegate that the tab content's navigation state
+ // changed. The |changed_flags| indicates the parts of the navigation state
+ // that have been updated, and is any combination of the
+ // |TabContents::InvalidateTypes| bits.
+ virtual void NavigationStateChanged(const TabContents* source,
+ unsigned changed_flags) = 0;
+
+ // Returns the set of headers to add to the navigation request. Use
+ // net::HttpUtil::AppendHeaderIfMissing to build the set of headers.
+ virtual std::string GetNavigationHeaders(const GURL& url);
+
+ // Creates a new tab with the already-created TabContents 'new_contents'.
+ // The window for the added contents should be reparented correctly when this
+ // method returns. If |disposition| is NEW_POPUP, |pos| should hold the
+ // initial position.
+ virtual void AddNewContents(TabContents* source,
+ TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) = 0;
+
+ // Selects the specified contents, bringing its container to the front.
+ virtual void ActivateContents(TabContents* contents) = 0;
+
+ // Deactivates the specified contents by deactivating its container and
+ // potentialy moving it to the back of the Z order.
+ virtual void DeactivateContents(TabContents* contents) = 0;
+
+ // Notifies the delegate that this contents is starting or is done loading
+ // some resource. The delegate should use this notification to represent
+ // loading feedback. See TabContents::is_loading()
+ virtual void LoadingStateChanged(TabContents* source) = 0;
+
+ // Notifies the delegate that the page has made some progress loading.
+ // |progress| is a value between 0.0 (nothing loaded) to 1.0 (page fully
+ // loaded).
+ // Note that to receive this notification, you must have called
+ // SetReportLoadProgressEnabled(true) in the render view.
+ virtual void LoadProgressChanged(double progress);
+
+ // Request the delegate to close this tab contents, and do whatever cleanup
+ // it needs to do.
+ virtual void CloseContents(TabContents* source) = 0;
+
+ // Request the delegate to move this tab contents to the specified position
+ // in screen coordinates.
+ virtual void MoveContents(TabContents* source, const gfx::Rect& pos) = 0;
+
+ // Causes the delegate to detach |source| and clean up any internal data
+ // pointing to it. After this call ownership of |source| passes to the
+ // caller, and it is safe to call "source->set_delegate(someone_else);".
+ virtual void DetachContents(TabContents* source);
+
+ // Called to determine if the TabContents is contained in a popup window.
+ virtual bool IsPopup(const TabContents* source) const;
+
+ // If |source| is constrained, returns the tab containing it. Otherwise
+ // returns |source|.
+ virtual TabContents* GetConstrainingContents(TabContents* source);
+
+ // Returns true if constrained windows should be focused. Default is true.
+ virtual bool ShouldFocusConstrainedWindow();
+
+ // Invoked prior to the TabContents showing a constrained window.
+ virtual void WillShowConstrainedWindow(TabContents* source);
+
+ // Notification that some of our content has changed size as
+ // part of an animation.
+ virtual void ToolbarSizeChanged(TabContents* source, bool is_animating) = 0;
+
+ // Notification that the target URL has changed.
+ virtual void UpdateTargetURL(TabContents* source, const GURL& url) = 0;
+
+ // Notification that there was a mouse event, along with the absolute
+ // coordinates of the mouse pointer and whether it was a normal motion event
+ // (otherwise, the pointer left the contents area).
+ virtual void ContentsMouseEvent(
+ TabContents* source, const gfx::Point& location, bool motion);
+
+ // Request the delegate to change the zoom level of the current tab.
+ virtual void ContentsZoomChange(bool zoom_in);
+
+ // Notifies the delegate that something has changed about what content the
+ // TabContents is blocking. Interested parties should call
+ // TabContents::IsContentBlocked() to see if something they care about has
+ // changed.
+ virtual void OnContentSettingsChange(TabContents* source);
+
+ // Check whether this contents is inside a window dedicated to running a web
+ // application.
+ virtual bool IsApplication() const;
+
+ // Detach the given tab and convert it to a "webapp" view. The tab must be
+ // a TabContents with a valid WebApp set.
+ virtual void ConvertContentsToApplication(TabContents* source);
+
+ // Whether the specified tab can be reloaded.
+ // Reloading can be disabled e. g. for the DevTools window.
+ virtual bool CanReloadContents(TabContents* source) const;
+
+ // Show a dialog with HTML content. |delegate| contains a pointer to the
+ // delegate who knows how to display the dialog (which file URL and JSON
+ // string input to use during initialization). |parent_window| is the window
+ // that should be parent of the dialog, or NULL for the default.
+ virtual void ShowHtmlDialog(HtmlDialogUIDelegate* delegate,
+ gfx::NativeWindow parent_window);
+
+ // Invoked prior to showing before unload handler confirmation dialog.
+ virtual void WillRunBeforeUnloadConfirm();
+
+ // Returns true if javascript dialogs and unload alerts are suppressed.
+ // Default is false.
+ virtual bool ShouldSuppressDialogs();
+
+ // Tells us that we've finished firing this tab's beforeunload event.
+ // The proceed bool tells us whether the user chose to proceed closing the
+ // tab. Returns true if the tab can continue on firing it's unload event.
+ // If we're closing the entire browser, then we'll want to delay firing
+ // unload events until all the beforeunload events have fired.
+ virtual void BeforeUnloadFired(TabContents* tab,
+ bool proceed,
+ bool* proceed_to_fire_unload);
+
+ // Send IPC to external host. Default implementation is do nothing.
+ virtual void ForwardMessageToExternalHost(const std::string& message,
+ const std::string& origin,
+ const std::string& target);
+
+ // If the delegate is hosting tabs externally.
+ virtual bool IsExternalTabContainer() const;
+
+ // Sets focus to the location bar or some other place that is appropriate.
+ // This is called when the tab wants to encourage user input, like for the
+ // new tab page.
+ virtual void SetFocusToLocationBar(bool select_all);
+
+ // Returns whether the page should be focused when transitioning from crashed
+ // to live. Default is true.
+ virtual bool ShouldFocusPageAfterCrash();
+
+ // Called when a popup select is about to be displayed. The delegate can use
+ // this to disable inactive rendering for the frame in the window the select
+ // is opened within if necessary.
+ virtual void RenderWidgetShowing();
+
+ // This is called when WebKit tells us that it is done tabbing through
+ // controls on the page. Provides a way for TabContentsDelegates to handle
+ // this. Returns true if the delegate successfully handled it.
+ virtual bool TakeFocus(bool reverse);
+
+ // Invoked when the page loses mouse capture.
+ virtual void LostCapture();
+
+ // Changes the blocked state of the tab at |index|. TabContents are
+ // considered blocked while displaying a tab modal dialog. During that time
+ // renderer host will ignore any UI interaction within TabContent outside of
+ // the currently displaying dialog.
+ virtual void SetTabContentBlocked(TabContents* contents, bool blocked);
+
+ // Notification that |tab_contents| has gained focus.
+ virtual void TabContentsFocused(TabContents* tab_content);
+
+ // Return much extra vertical space should be allotted to the
+ // render view widget during various animations (e.g. infobar closing).
+ // This is used to make painting look smoother.
+ virtual int GetExtraRenderViewHeight() const;
+
+ virtual bool CanDownload(int request_id);
+
+ virtual void OnStartDownload(DownloadItem* download, TabContents* tab);
+
+ // Returns true if the context menu operation was handled by the delegate.
+ virtual bool HandleContextMenu(const ContextMenuParams& params);
+
+ // Returns true if the context menu command was handled
+ virtual bool ExecuteContextMenuCommand(int command);
+
+ // Shows the page info using the specified information.
+ // |url| is the url of the page/frame the info applies to, |ssl| is the SSL
+ // information for that page/frame. If |show_history| is true, a section
+ // showing how many times that URL has been visited is added to the page info.
+ virtual void ShowPageInfo(Profile* profile,
+ const GURL& url,
+ const NavigationEntry::SSLStatus& ssl,
+ bool show_history);
+
+ // Opens source view for given tab contents that is navigated to the given
+ // page url.
+ virtual void ViewSourceForTab(TabContents* source, const GURL& page_url);
+
+ // Allows delegates to handle keyboard events before sending to the renderer.
+ // Returns true if the |event| was handled. Otherwise, if the |event| would be
+ // handled in HandleKeyboardEvent() method as a normal keyboard shortcut,
+ // |*is_keyboard_shortcut| should be set to true.
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut);
+
+ // Allows delegates to handle unhandled keyboard messages coming back from
+ // the renderer.
+ virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event);
+
+ virtual void HandleMouseUp();
+ virtual void HandleMouseActivate();
+
+ // Render view drag n drop ended.
+ virtual void DragEnded();
+
+ // Shows the repost form confirmation dialog box.
+ virtual void ShowRepostFormWarningDialog(TabContents* tab_contents);
+
+ // Shows the Content Settings dialog for a given content type.
+ virtual void ShowContentSettingsWindow(ContentSettingsType content_type);
+
+ // Shows the cookies collected in the tab contents.
+ virtual void ShowCollectedCookiesDialog(TabContents* tab_contents);
+
+ // Allows delegate to override navigation to the history entries.
+ // Returns true to allow TabContents to continue with the default processing.
+ virtual bool OnGoToEntryOffset(int offset);
+
+ // Returns whether this tab contents should add the specified navigation to
+ // history.
+ virtual bool ShouldAddNavigationToHistory(
+ const history::HistoryAddPageArgs& add_page_args,
+ NavigationType::Type navigation_type);
+
+ // Notification that a user's request to install an application has completed.
+ virtual void OnDidGetApplicationInfo(TabContents* tab_contents,
+ int32 page_id);
+
+ // Notification when an application programmatically requests installation.
+ virtual void OnInstallApplication(TabContents* tab_contents,
+ const WebApplicationInfo& app_info);
+
+ // Returns the native window framing the view containing the tab contents.
+ virtual gfx::NativeWindow GetFrameNativeWindow();
+
+ // Notifies the delegate about the creation of a new TabContents. This
+ // typically happens when popups are created.
+ virtual void TabContentsCreated(TabContents* new_contents);
+
+ // Returns whether infobars are enabled. Overrideable by child classes.
+ virtual bool infobars_enabled();
+
+ // Whether the renderer should report its preferred size when it changes by
+ // calling UpdatePreferredSize().
+ // Note that this is set when the RenderViewHost is created and cannot be
+ // changed after that.
+ virtual bool ShouldEnablePreferredSizeNotifications();
+
+ // Notification that the preferred size of the contents has changed.
+ // Only called if ShouldEnablePreferredSizeNotifications() returns true.
+ virtual void UpdatePreferredSize(const gfx::Size& pref_size);
+
+ // Notifies the delegate that the page has a suggest result.
+ virtual void OnSetSuggestions(int32 page_id,
+ const std::vector<std::string>& result);
+
+ // Notifies the delegate whether the page supports instant-style interaction.
+ virtual void OnInstantSupportDetermined(int32 page_id, bool result);
+
+ // Notifies the delegate that the content restrictions for this tab has
+ // changed.
+ virtual void ContentRestrictionsChanged(TabContents* source);
+
+ // Returns true if the hung renderer dialog should be shown. Default is true.
+ virtual bool ShouldShowHungRendererDialog();
+
+ // Notification that a worker associated with this tab has crashed.
+ virtual void WorkerCrashed();
+
+ protected:
+ virtual ~TabContentsDelegate();
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_DELEGATE_H_
diff --git a/content/browser/tab_contents/tab_contents_observer.cc b/content/browser/tab_contents/tab_contents_observer.cc
new file mode 100644
index 0000000..00c8210
--- /dev/null
+++ b/content/browser/tab_contents/tab_contents_observer.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2011 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/tab_contents/tab_contents_observer.h"
+
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "content/browser/tab_contents/tab_contents.h"
+
+TabContentsObserver::TabContentsObserver(TabContents* tab_contents)
+ : tab_contents_(tab_contents),
+ routing_id_(tab_contents->render_view_host()->routing_id()) {
+ tab_contents_->AddObserver(this);
+}
+
+TabContentsObserver::~TabContentsObserver() {
+ if (tab_contents_)
+ tab_contents_->RemoveObserver(this);
+}
+
+bool TabContentsObserver::OnMessageReceived(const IPC::Message& message) {
+ return false;
+}
+
+bool TabContentsObserver::Send(IPC::Message* message) {
+ if (!tab_contents_->render_view_host()) {
+ delete message;
+ return false;
+ }
+
+ return tab_contents_->render_view_host()->Send(message);
+}
diff --git a/content/browser/tab_contents/tab_contents_observer.h b/content/browser/tab_contents/tab_contents_observer.h
new file mode 100644
index 0000000..b8bb343
--- /dev/null
+++ b/content/browser/tab_contents/tab_contents_observer.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2011 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_TAB_CONTENTS_TAB_CONTENTS_OBSERVER_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_OBSERVER_H_
+
+#include "content/browser/tab_contents/navigation_controller.h"
+#include "ipc/ipc_channel.h"
+
+struct ViewHostMsg_FrameNavigate_Params;
+
+// An observer API implemented by classes which are interested in various page
+// load events from TabContents. They also get a chance to filter IPC messages.
+class TabContentsObserver : public IPC::Channel::Listener {
+ public:
+ virtual void NavigateToPendingEntry() { }
+
+ virtual void DidNavigateMainFramePostCommit(
+ const NavigationController::LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params) { }
+ virtual void DidNavigateAnyFramePostCommit(
+ const NavigationController::LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params) { }
+
+ virtual void DidStartLoading() { }
+ virtual void DidStopLoading() { }
+
+#if 0
+ // For unifying with delegate...
+
+ // Notifies the delegate that this contents is starting or is done loading
+ // some resource. The delegate should use this notification to represent
+ // loading feedback. See TabContents::is_loading()
+ virtual void LoadingStateChanged(TabContents* contents) { }
+ // Called to inform the delegate that the tab content's navigation state
+ // changed. The |changed_flags| indicates the parts of the navigation state
+ // that have been updated, and is any combination of the
+ // |TabContents::InvalidateTypes| bits.
+ virtual void NavigationStateChanged(const TabContents* source,
+ unsigned changed_flags) { }
+#endif
+
+ protected:
+ TabContentsObserver(TabContents* tab_contents);
+ virtual ~TabContentsObserver();
+
+ // IPC::Channel::Listener implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message);
+
+ // IPC::Message::Sender implementation.
+ virtual bool Send(IPC::Message* message);
+
+ TabContents* tab_contents() { return tab_contents_; }
+ int routing_id() { return routing_id_; }
+
+ private:
+ friend class TabContents;
+
+ void set_tab_contents(TabContents* tc) { tab_contents_ = tc; }
+
+ TabContents* tab_contents_;
+ // The routing ID of the associated TabContents.
+ int routing_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabContentsObserver);
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_OBSERVER_H_
diff --git a/content/browser/tab_contents/tab_contents_view.cc b/content/browser/tab_contents/tab_contents_view.cc
new file mode 100644
index 0000000..8c395af
--- /dev/null
+++ b/content/browser/tab_contents/tab_contents_view.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2011 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/tab_contents/tab_contents_view.h"
+
+#include "chrome/browser/renderer_host/render_process_host.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host.h"
+#include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/render_messages_params.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/tab_contents/tab_contents_delegate.h"
+
+TabContentsView::TabContentsView(TabContents* tab_contents)
+ : tab_contents_(tab_contents) {
+}
+
+TabContentsView::~TabContentsView() {}
+
+void TabContentsView::RenderWidgetHostDestroyed(RenderWidgetHost* host) {
+ if (host->view())
+ host->view()->WillDestroyRenderWidget(host);
+ delegate_view_helper_.RenderWidgetHostDestroyed(host);
+}
+
+void TabContentsView::RenderViewCreated(RenderViewHost* host) {
+ // Default implementation does nothing. Platforms may override.
+}
+
+void TabContentsView::CreateNewWindow(
+ int route_id,
+ const ViewHostMsg_CreateWindow_Params& params) {
+ TabContents* new_contents = delegate_view_helper_.CreateNewWindow(
+ route_id,
+ tab_contents_->profile(),
+ tab_contents_->GetSiteInstance(),
+ WebUIFactory::GetWebUIType(tab_contents_->profile(),
+ tab_contents_->GetURL()),
+ tab_contents_,
+ params.window_container_type,
+ params.frame_name);
+
+ if (new_contents) {
+ NotificationService::current()->Notify(
+ NotificationType::CREATING_NEW_WINDOW,
+ Source<TabContents>(tab_contents_),
+ Details<const ViewHostMsg_CreateWindow_Params>(&params));
+
+ if (tab_contents_->delegate())
+ tab_contents_->delegate()->TabContentsCreated(new_contents);
+ }
+}
+
+void TabContentsView::CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) {
+ CreateNewWidgetInternal(route_id, popup_type);
+}
+
+void TabContentsView::CreateNewFullscreenWidget(int route_id) {
+ CreateNewFullscreenWidgetInternal(route_id);
+}
+
+void TabContentsView::ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ TabContents* contents = delegate_view_helper_.GetCreatedWindow(route_id);
+ if (contents) {
+ tab_contents()->AddNewContents(contents, disposition, initial_pos,
+ user_gesture);
+ }
+}
+
+void TabContentsView::ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) {
+ RenderWidgetHostView* widget_host_view =
+ delegate_view_helper_.GetCreatedWidget(route_id);
+ ShowCreatedWidgetInternal(widget_host_view, initial_pos);
+}
+
+void TabContentsView::Activate() {
+ tab_contents_->Activate();
+}
+
+void TabContentsView::Deactivate() {
+ tab_contents_->Deactivate();
+}
+
+void TabContentsView::ShowCreatedFullscreenWidget(int route_id) {
+ RenderWidgetHostView* widget_host_view =
+ delegate_view_helper_.GetCreatedWidget(route_id);
+ ShowCreatedFullscreenWidgetInternal(widget_host_view);
+}
+
+void TabContentsView::LostCapture() {
+ if (tab_contents_->delegate())
+ tab_contents_->delegate()->LostCapture();
+}
+
+bool TabContentsView::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) {
+ return tab_contents_->delegate() &&
+ tab_contents_->delegate()->PreHandleKeyboardEvent(
+ event, is_keyboard_shortcut);
+}
+
+void TabContentsView::UpdatePreferredSize(const gfx::Size& pref_size) {
+ if (tab_contents_->delegate())
+ tab_contents_->delegate()->UpdatePreferredSize(pref_size);
+}
+
+bool TabContentsView::IsDoingDrag() const {
+ return false;
+}
+
+bool TabContentsView::IsEventTracking() const {
+ return false;
+}
+
+TabContentsView::TabContentsView() : tab_contents_(NULL) {}
+
+void TabContentsView::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {
+ if (tab_contents_->delegate())
+ tab_contents_->delegate()->HandleKeyboardEvent(event);
+}
+
+void TabContentsView::HandleMouseUp() {
+ if (tab_contents_->delegate())
+ tab_contents_->delegate()->HandleMouseUp();
+}
+
+void TabContentsView::HandleMouseActivate() {
+ if (tab_contents_->delegate())
+ tab_contents_->delegate()->HandleMouseActivate();
+}
+
+RenderWidgetHostView* TabContentsView::CreateNewWidgetInternal(
+ int route_id, WebKit::WebPopupType popup_type) {
+ return delegate_view_helper_.CreateNewWidget(route_id, popup_type,
+ tab_contents()->render_view_host()->process());
+}
+
+RenderWidgetHostView* TabContentsView::CreateNewFullscreenWidgetInternal(
+ int route_id) {
+ return delegate_view_helper_.CreateNewFullscreenWidget(
+ route_id, tab_contents()->render_view_host()->process());
+}
+
+void TabContentsView::ShowCreatedWidgetInternal(
+ RenderWidgetHostView* widget_host_view, const gfx::Rect& initial_pos) {
+ if (tab_contents_->delegate())
+ tab_contents_->delegate()->RenderWidgetShowing();
+
+ widget_host_view->InitAsPopup(tab_contents_->GetRenderWidgetHostView(),
+ initial_pos);
+ widget_host_view->GetRenderWidgetHost()->Init();
+}
+
+void TabContentsView::ShowCreatedFullscreenWidgetInternal(
+ RenderWidgetHostView* widget_host_view) {
+ if (tab_contents_->delegate())
+ tab_contents_->delegate()->RenderWidgetShowing();
+
+ widget_host_view->InitAsFullscreen();
+ widget_host_view->GetRenderWidgetHost()->Init();
+}
diff --git a/content/browser/tab_contents/tab_contents_view.h b/content/browser/tab_contents/tab_contents_view.h
new file mode 100644
index 0000000..9d5a04f
--- /dev/null
+++ b/content/browser/tab_contents/tab_contents_view.h
@@ -0,0 +1,218 @@
+// Copyright (c) 2010 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_TAB_CONTENTS_TAB_CONTENTS_VIEW_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_VIEW_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#include "chrome/browser/tab_contents/render_view_host_delegate_helper.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+class RenderViewHost;
+class RenderWidgetHost;
+class RenderWidgetHostView;
+class TabContents;
+
+// The TabContentsView is an interface that is implemented by the platform-
+// dependent web contents views. The TabContents uses this interface to talk to
+// them. View-related messages will also get forwarded directly to this class
+// from RenderViewHost via RenderViewHostDelegate::View.
+//
+// It contains a small amount of logic with respect to creating new sub-view
+// that should be the same for all platforms.
+class TabContentsView : public RenderViewHostDelegate::View {
+ public:
+ explicit TabContentsView(TabContents* tab_contents);
+ virtual ~TabContentsView();
+
+ // Creates the appropriate type of TabContentsView for the current system.
+ // The return value is a new heap allocated view with ownership passing to
+ // the caller.
+ static TabContentsView* Create(TabContents* tab_contents);
+
+ TabContents* tab_contents() const { return tab_contents_; }
+
+ virtual void CreateView(const gfx::Size& initial_size) = 0;
+
+ // Sets up the View that holds the rendered web page, receives messages for
+ // it and contains page plugins. The host view should be sized to the current
+ // size of the TabContents.
+ virtual RenderWidgetHostView* CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) = 0;
+
+ // Returns the native widget that contains the contents of the tab.
+ virtual gfx::NativeView GetNativeView() const = 0;
+
+ // Returns the native widget with the main content of the tab (i.e. the main
+ // render view host, though there may be many popups in the tab as children of
+ // the container).
+ virtual gfx::NativeView GetContentNativeView() const = 0;
+
+ // Returns the outermost native view. This will be used as the parent for
+ // dialog boxes.
+ virtual gfx::NativeWindow GetTopLevelNativeWindow() const = 0;
+
+ // Computes the rectangle for the native widget that contains the contents of
+ // the tab relative to its parent.
+ virtual void GetContainerBounds(gfx::Rect *out) const = 0;
+
+ // Helper function for GetContainerBounds. Most callers just want to know the
+ // size, and this makes it more clear.
+ gfx::Size GetContainerSize() const {
+ gfx::Rect rc;
+ GetContainerBounds(&rc);
+ return gfx::Size(rc.width(), rc.height());
+ }
+
+ // Sets the page title for the native widgets corresponding to the view. This
+ // is not strictly necessary and isn't expected to be displayed anywhere, but
+ // can aid certain debugging tools such as Spy++ on Windows where you are
+ // trying to find a specific window.
+ virtual void SetPageTitle(const std::wstring& title) = 0;
+
+ // Used to notify the view that a tab has crashed so each platform can
+ // prepare the sad tab.
+ virtual void OnTabCrashed(base::TerminationStatus status,
+ int error_code) = 0;
+
+ // TODO(brettw) this is a hack. It's used in two places at the time of this
+ // writing: (1) when render view hosts switch, we need to size the replaced
+ // one to be correct, since it wouldn't have known about sizes that happened
+ // while it was hidden; (2) in constrained windows.
+ //
+ // (1) will be fixed once interstitials are cleaned up. (2) seems like it
+ // should be cleaned up or done some other way, since this works for normal
+ // TabContents without the special code.
+ virtual void SizeContents(const gfx::Size& size) = 0;
+
+ // Invoked from the platform dependent web contents view when a
+ // RenderWidgetHost is deleted. Removes |host| from internal maps.
+ void RenderWidgetHostDestroyed(RenderWidgetHost* host);
+
+ // Invoked when the TabContents is notified that the RenderView has been
+ // fully created. The default implementation does nothing; override
+ // for platform-specific behavior is needed.
+ virtual void RenderViewCreated(RenderViewHost* host);
+
+ // Sets focus to the native widget for this tab.
+ virtual void Focus() = 0;
+
+ // Sets focus to the appropriate element when the tab contents is shown the
+ // first time.
+ virtual void SetInitialFocus() = 0;
+
+ // Stores the currently focused view.
+ virtual void StoreFocus() = 0;
+
+ // Restores focus to the last focus view. If StoreFocus has not yet been
+ // invoked, SetInitialFocus is invoked.
+ virtual void RestoreFocus() = 0;
+
+ // RenderViewHostDelegate::View method. Forwards to the TabContentsDelegate.
+ virtual void LostCapture();
+
+ // Keyboard events forwarding from the RenderViewHost.
+ // The default implementation just forward the events to the
+ // TabContentsDelegate object.
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut);
+
+ // Keyboard events forwarding from the RenderViewHost.
+ // The default implementation just forward the events to the
+ // TabContentsDelegate object.
+ virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event);
+
+ // Simple mouse event forwarding from the RenderViewHost.
+ virtual void HandleMouseMove() {}
+ virtual void HandleMouseDown() {}
+ virtual void HandleMouseLeave() {}
+ virtual void HandleMouseUp();
+ virtual void HandleMouseActivate();
+
+ // Notification that the preferred size of the contents has changed.
+ virtual void UpdatePreferredSize(const gfx::Size& pref_size);
+
+ // If we try to close the tab while a drag is in progress, we crash. These
+ // methods allow the tab contents to determine if a drag is in progress and
+ // postpone the tab closing.
+ virtual bool IsDoingDrag() const;
+ virtual void CancelDragAndCloseTab() {}
+
+ // If we close the tab while a UI control is in an event-tracking
+ // loop, the control may message freed objects and crash.
+ // TabContents::Close() calls IsEventTracking(), and if it returns
+ // true CloseTabAfterEventTracking() is called and the close is not
+ // completed.
+ virtual bool IsEventTracking() const;
+ virtual void CloseTabAfterEventTracking() {}
+
+ // Get the bounds of the View, relative to the parent.
+ virtual void GetViewBounds(gfx::Rect* out) const = 0;
+
+ protected:
+ TabContentsView(); // Abstract interface.
+
+ // Internal functions used to support the CreateNewWidget() method. If a
+ // platform requires plugging into widget creation at a lower level then a
+ // subclass might want to override these functions, but otherwise they should
+ // be fine just implementing RenderWidgetHostView::InitAsPopup().
+ //
+ // The Create function returns the newly created widget so it can be
+ // associated with the given route. When the widget needs to be shown later,
+ // we'll look it up again and pass the object to the Show functions rather
+ // than the route ID.
+ virtual RenderWidgetHostView* CreateNewWidgetInternal(
+ int route_id,
+ WebKit::WebPopupType popup_type);
+ virtual void ShowCreatedWidgetInternal(RenderWidgetHostView* widget_host_view,
+ const gfx::Rect& initial_pos);
+ virtual void ShowCreatedFullscreenWidgetInternal(
+ RenderWidgetHostView* widget_host_view);
+ virtual RenderWidgetHostView* CreateNewFullscreenWidgetInternal(int route_id);
+
+ // Common implementations of some RenderViewHostDelegate::View methods.
+ RenderViewHostDelegateViewHelper delegate_view_helper_;
+
+ private:
+ // We implement these functions on RenderViewHostDelegate::View directly and
+ // do some book-keeping associated with the request. The request is then
+ // forwarded to *Internal which does platform-specific work.
+ 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 Activate();
+ virtual void Deactivate();
+ virtual void ShowCreatedFullscreenWidget(int route_id);
+
+ // The TabContents whose contents we display.
+ TabContents* tab_contents_;
+
+ // Tracks created TabContents objects that have not been shown yet. They are
+ // identified by the route ID passed to CreateNewWindow.
+ typedef std::map<int, TabContents*> PendingContents;
+ PendingContents pending_contents_;
+
+ // These maps hold on to the widgets that we created on behalf of the
+ // renderer that haven't shown yet.
+ typedef std::map<int, RenderWidgetHostView*> PendingWidgetViews;
+ PendingWidgetViews pending_widget_views_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabContentsView);
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_VIEW_H_
diff --git a/content/browser/tab_contents/test_tab_contents.cc b/content/browser/tab_contents/test_tab_contents.cc
new file mode 100644
index 0000000..69fc0cf
--- /dev/null
+++ b/content/browser/tab_contents/test_tab_contents.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2009 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 "chrome/browser/tab_contents/test_tab_contents.h"
+
+#include <utility>
+
+#include "chrome/browser/browser_url_handler.h"
+#include "chrome/browser/renderer_host/mock_render_process_host.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/renderer_host/test/test_render_view_host.h"
+#include "chrome/browser/tab_contents/infobar_delegate.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_source.h"
+#include "chrome/common/page_transition_types.h"
+
+TestTabContents::TestTabContents(Profile* profile, SiteInstance* instance)
+ : TabContents(profile, instance, MSG_ROUTING_NONE, NULL, NULL),
+ transition_cross_site(false) {
+ // Listen for infobar events so we can call InfoBarClosed() on the infobar
+ // delegates and give them an opportunity to delete themselves. (Since we
+ // have no InfobarContainer in TestTabContents, InfoBarClosed() is not called
+ // most likely leading to the infobar delegates being leaked.)
+ Source<TabContents> source(this);
+ registrar_.Add(this, NotificationType::TAB_CONTENTS_INFOBAR_REMOVED,
+ source);
+ registrar_.Add(this, NotificationType::TAB_CONTENTS_INFOBAR_REPLACED,
+ source);
+}
+
+void TestTabContents::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ // TabContents does not handle TAB_CONTENTS_INFOBAR_* so we don't pass it
+ // these notifications.
+ switch (type.value) {
+ case NotificationType::TAB_CONTENTS_INFOBAR_REMOVED:
+ Details<InfoBarDelegate>(details).ptr()->InfoBarClosed();
+ break;
+ case NotificationType::TAB_CONTENTS_INFOBAR_REPLACED:
+ Details<std::pair<InfoBarDelegate*, InfoBarDelegate*> >(details).ptr()->
+ first->InfoBarClosed();
+ break;
+ default:
+ TabContents::Observe(type, source, details);
+ break;
+ }
+}
+
+TestRenderViewHost* TestTabContents::pending_rvh() const {
+ return static_cast<TestRenderViewHost*>(
+ render_manager_.pending_render_view_host_);
+}
+
+bool TestTabContents::CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host) {
+ // This will go to a TestRenderViewHost.
+ render_view_host->CreateRenderView(string16());
+ return true;
+}
+
+TabContents* TestTabContents::Clone() {
+ TabContents* tc = new TestTabContents(
+ profile(), SiteInstance::CreateSiteInstance(profile()));
+ tc->controller().CopyStateFrom(controller_);
+ return tc;
+}
+
+void TestTabContents::NavigateAndCommit(const GURL& url) {
+ controller().LoadURL(url, GURL(), PageTransition::LINK);
+ GURL loaded_url(url);
+ bool reverse_on_redirect = false;
+ BrowserURLHandler::RewriteURLIfNecessary(
+ &loaded_url, profile(), &reverse_on_redirect);
+
+ // LoadURL created a navigation entry, now simulate the RenderView sending
+ // a notification that it actually navigated.
+ CommitPendingNavigation();
+}
+
+void TestTabContents::CommitPendingNavigation() {
+ // If we are doing a cross-site navigation, this simulates the current RVH
+ // notifying that it has unloaded so the pending RVH is resumed and can
+ // navigate.
+ ProceedWithCrossSiteNavigation();
+ TestRenderViewHost* rvh = pending_rvh();
+ if (!rvh)
+ rvh = static_cast<TestRenderViewHost*>(render_manager_.current_host());
+
+ const NavigationEntry* entry = controller().pending_entry();
+ DCHECK(entry);
+ int page_id = entry->page_id();
+ if (page_id == -1) {
+ // It's a new navigation, assign a never-seen page id to it.
+ page_id =
+ static_cast<MockRenderProcessHost*>(rvh->process())->max_page_id() + 1;
+ }
+ rvh->SendNavigate(page_id, entry->url());
+}
+
+void TestTabContents::ProceedWithCrossSiteNavigation() {
+ if (!pending_rvh())
+ return;
+ render_manager_.ShouldClosePage(true, true);
+}
diff --git a/content/browser/tab_contents/test_tab_contents.h b/content/browser/tab_contents/test_tab_contents.h
new file mode 100644
index 0000000..83c251a
--- /dev/null
+++ b/content/browser/tab_contents/test_tab_contents.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2011 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_TAB_CONTENTS_TEST_TAB_CONTENTS_H_
+#define CONTENT_BROWSER_TAB_CONTENTS_TEST_TAB_CONTENTS_H_
+#pragma once
+
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/notification_registrar.h"
+#include "webkit/glue/webpreferences.h"
+
+class Profile;
+class TestRenderViewHost;
+
+// Subclass TabContents to ensure it creates TestRenderViewHosts and does
+// not do anything involving views.
+class TestTabContents : public TabContents {
+ public:
+ // The render view host factory will be passed on to the
+ TestTabContents(Profile* profile, SiteInstance* instance);
+
+ TestRenderViewHost* pending_rvh() const;
+
+ // State accessor.
+ bool cross_navigation_pending() {
+ return render_manager_.cross_navigation_pending_;
+ }
+
+ // Overrides TabContents::ShouldTransitionCrossSite so that we can test both
+ // alternatives without using command-line switches.
+ bool ShouldTransitionCrossSite() { return transition_cross_site; }
+
+ // Overrides TabContents::Observe. We are listening to infobar related
+ // notifications so we can call InfoBarClosed() on the infobar delegates to
+ // prevent them from leaking.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // Promote DidNavigate to public.
+ void TestDidNavigate(RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ DidNavigate(render_view_host, params);
+ }
+
+ // Promote GetWebkitPrefs to public.
+ WebPreferences TestGetWebkitPrefs() {
+ return GetWebkitPrefs();
+ }
+
+ // Prevent interaction with views.
+ virtual bool CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host);
+ virtual void UpdateRenderViewSizeForRenderManager() {}
+
+ // Returns a clone of this TestTabContents. The returned object is also a
+ // TestTabContents. The caller owns the returned object.
+ virtual TabContents* Clone();
+
+ // Creates a pending navigation to the given URL with the default parameters
+ // and then commits the load with a page ID one larger than any seen. This
+ // emulates what happens on a new navigation.
+ void NavigateAndCommit(const GURL& url);
+
+ // Simulates the appropriate RenderView (pending if any, current otherwise)
+ // sending a navigate notification for the NavigationController pending entry.
+ void CommitPendingNavigation();
+
+ // Simulates the current RVH notifying that it has unloaded so that the
+ // pending RVH navigation can proceed.
+ // Does nothing if no cross-navigation is pending.
+ void ProceedWithCrossSiteNavigation();
+
+ // Set by individual tests.
+ bool transition_cross_site;
+
+ NotificationRegistrar registrar_;
+};
+
+#endif // CONTENT_BROWSER_TAB_CONTENTS_TEST_TAB_CONTENTS_H_
diff --git a/content/content.gyp b/content/content.gyp
new file mode 100644
index 0000000..db71289
--- /dev/null
+++ b/content/content.gyp
@@ -0,0 +1,13 @@
+# Copyright (c) 2011 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1, # Use higher warning level.
+ },
+ 'includes': [
+ 'content_browser.gypi',
+ 'content_common.gypi',
+ ],
+}
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
new file mode 100644
index 0000000..51bf09c
--- /dev/null
+++ b/content/content_browser.gypi
@@ -0,0 +1,60 @@
+# Copyright (c) 2011 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'content_browser',
+ 'type': '<(library)',
+ 'dependencies': [
+ # Don't include now since it's empty and so will cause a linker error.
+ #'content_common',
+ '../app/app.gyp:app_resources',
+ '../skia/skia.gyp:skia',
+ '../ui/ui.gyp:ui_base',
+ ],
+ 'include_dirs': [
+ '..',
+ ],
+ 'sources': [
+ 'browser/tab_contents/background_contents.cc',
+ 'browser/tab_contents/background_contents.h',
+ 'browser/tab_contents/constrained_window.h',
+ 'browser/tab_contents/infobar_delegate.cc',
+ 'browser/tab_contents/infobar_delegate.h',
+ 'browser/tab_contents/interstitial_page.cc',
+ 'browser/tab_contents/interstitial_page.h',
+ 'browser/tab_contents/language_state.cc',
+ 'browser/tab_contents/language_state.h',
+ 'browser/tab_contents/navigation_controller.cc',
+ 'browser/tab_contents/navigation_controller.h',
+ 'browser/tab_contents/navigation_entry.cc',
+ 'browser/tab_contents/navigation_entry.h',
+ 'browser/tab_contents/page_navigator.h',
+ 'browser/tab_contents/provisional_load_details.cc',
+ 'browser/tab_contents/provisional_load_details.h',
+ 'browser/tab_contents/render_view_host_manager.cc',
+ 'browser/tab_contents/render_view_host_manager.h',
+ 'browser/tab_contents/tab_contents.cc',
+ 'browser/tab_contents/tab_contents.h',
+ 'browser/tab_contents/tab_contents_delegate.cc',
+ 'browser/tab_contents/tab_contents_delegate.h',
+ 'browser/tab_contents/tab_contents_observer.cc',
+ 'browser/tab_contents/tab_contents_observer.h',
+ 'browser/tab_contents/tab_contents_view.cc',
+ 'browser/tab_contents/tab_contents_view.h',
+ ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'msvs_guid': '639DB58D-32C2-435A-A711-65A12F62E442',
+ }],
+ ['OS=="linux" or OS=="freebsd" or OS=="openbsd" or OS=="solaris"', {
+ 'dependencies': [
+ '../build/linux/system.gyp:gtk',
+ ],
+ }],
+ ],
+ },
+ ],
+}
diff --git a/content/content_common.gypi b/content/content_common.gypi
new file mode 100644
index 0000000..0ef8194
--- /dev/null
+++ b/content/content_common.gypi
@@ -0,0 +1,25 @@
+# Copyright (c) 2011 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'content_common',
+ 'type': '<(library)',
+ 'dependencies': [
+ '../ipc/ipc.gyp:ipc',
+ ],
+ 'include_dirs': [
+ '..',
+ ],
+ 'sources': [
+ ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'msvs_guid': '062E9260-304A-4657-A74C-0D3AA1A0A0A4',
+ }],
+ ],
+ },
+ ],
+}