summaryrefslogtreecommitdiffstats
path: root/chrome/browser/tab_contents
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-01-15 00:59:16 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-01-15 00:59:16 +0000
commitf3ec774a2c177d3c6845553d3bf9735a7b8a5907 (patch)
treeed09ccf69300383bd4b2e8ac7c8b9d1bdeead07b /chrome/browser/tab_contents
parentbb515eda39129537a089a062c3db3152e63f24d9 (diff)
downloadchromium_src-f3ec774a2c177d3c6845553d3bf9735a7b8a5907.zip
chromium_src-f3ec774a2c177d3c6845553d3bf9735a7b8a5907.tar.gz
chromium_src-f3ec774a2c177d3c6845553d3bf9735a7b8a5907.tar.bz2
Move a bunch of TabContents related files into a tab_contents subdir
Review URL: http://codereview.chromium.org/18250 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@8058 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/tab_contents')
-rw-r--r--chrome/browser/tab_contents/about_internets_status_view.cc69
-rw-r--r--chrome/browser/tab_contents/about_internets_status_view.h45
-rw-r--r--chrome/browser/tab_contents/constrained_window.h68
-rw-r--r--chrome/browser/tab_contents/infobar_delegate.cc93
-rw-r--r--chrome/browser/tab_contents/infobar_delegate.h206
-rw-r--r--chrome/browser/tab_contents/interstitial_page.cc362
-rw-r--r--chrome/browser/tab_contents/interstitial_page.h168
-rw-r--r--chrome/browser/tab_contents/ipc_status_view.cc370
-rw-r--r--chrome/browser/tab_contents/ipc_status_view.h89
-rw-r--r--chrome/browser/tab_contents/native_ui_contents.cc671
-rw-r--r--chrome/browser/tab_contents/native_ui_contents.h295
-rw-r--r--chrome/browser/tab_contents/navigation_controller.cc1233
-rw-r--r--chrome/browser/tab_contents/navigation_controller.h550
-rw-r--r--chrome/browser/tab_contents/navigation_entry.cc64
-rw-r--r--chrome/browser/tab_contents/navigation_entry.h399
-rw-r--r--chrome/browser/tab_contents/network_status_view.cc320
-rw-r--r--chrome/browser/tab_contents/network_status_view.h117
-rw-r--r--chrome/browser/tab_contents/page_navigator.h27
-rw-r--r--chrome/browser/tab_contents/provisional_load_details.cc25
-rw-r--r--chrome/browser/tab_contents/provisional_load_details.h60
-rw-r--r--chrome/browser/tab_contents/site_instance.cc145
-rw-r--r--chrome/browser/tab_contents/site_instance.h156
-rw-r--r--chrome/browser/tab_contents/status_view.cc74
-rw-r--r--chrome/browser/tab_contents/status_view.h84
-rw-r--r--chrome/browser/tab_contents/tab_contents.cc605
-rw-r--r--chrome/browser/tab_contents/tab_contents.h560
-rw-r--r--chrome/browser/tab_contents/tab_contents_delegate.h150
-rw-r--r--chrome/browser/tab_contents/tab_contents_factory.cc161
-rw-r--r--chrome/browser/tab_contents/tab_contents_factory.h28
-rw-r--r--chrome/browser/tab_contents/tab_contents_type.h31
-rw-r--r--chrome/browser/tab_contents/tab_util.cc39
-rw-r--r--chrome/browser/tab_contents/tab_util.h26
-rw-r--r--chrome/browser/tab_contents/view_source_contents.cc19
-rw-r--r--chrome/browser/tab_contents/view_source_contents.h27
-rw-r--r--chrome/browser/tab_contents/view_source_uitest.cc110
-rw-r--r--chrome/browser/tab_contents/web_contents.cc1796
-rw-r--r--chrome/browser/tab_contents/web_contents.h572
-rw-r--r--chrome/browser/tab_contents/web_contents_unittest.cc1271
-rw-r--r--chrome/browser/tab_contents/web_contents_view.cc60
-rw-r--r--chrome/browser/tab_contents/web_contents_view.h191
-rw-r--r--chrome/browser/tab_contents/web_contents_view_win.cc646
-rw-r--r--chrome/browser/tab_contents/web_contents_view_win.h128
-rw-r--r--chrome/browser/tab_contents/web_drag_source.cc45
-rw-r--r--chrome/browser/tab_contents/web_drag_source.h49
-rw-r--r--chrome/browser/tab_contents/web_drop_target.cc182
-rw-r--r--chrome/browser/tab_contents/web_drop_target.h74
46 files changed, 12460 insertions, 0 deletions
diff --git a/chrome/browser/tab_contents/about_internets_status_view.cc b/chrome/browser/tab_contents/about_internets_status_view.cc
new file mode 100644
index 0000000..46cf931
--- /dev/null
+++ b/chrome/browser/tab_contents/about_internets_status_view.cc
@@ -0,0 +1,69 @@
+// 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 "base/file_util.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "chrome/browser/tab_contents/about_internets_status_view.h"
+#include "chrome/browser/tab_contents/tab_contents_delegate.h"
+
+AboutInternetsStatusView::AboutInternetsStatusView()
+ : StatusView(TAB_CONTENTS_ABOUT_INTERNETS_STATUS_VIEW) {}
+
+AboutInternetsStatusView::~AboutInternetsStatusView() {
+ if (process_handle_.IsValid())
+ TerminateProcess(process_handle_.Get(), 0);
+}
+
+const std::wstring AboutInternetsStatusView::GetDefaultTitle() const {
+ return L"Don't Clog the Tubes!";
+}
+
+const std::wstring& AboutInternetsStatusView::GetTitle() const {
+ return title_;
+}
+
+void AboutInternetsStatusView::OnCreate(const CRect& rect) {
+ HWND contents_hwnd = GetContainerHWND();
+ STARTUPINFO startup_info;
+ memset(&startup_info, 0, sizeof(startup_info));
+ startup_info.cb = sizeof(startup_info);
+ PROCESS_INFORMATION process_info = {0};
+
+ std::wstring path;
+ PathService::Get(base::DIR_SYSTEM, &path);
+ file_util::AppendToPath(&path, L"sspipes.scr");
+ std::wstring parameters;
+ parameters.append(path.c_str());
+ // Append the handle of the HWND that we want to render the pipes into.
+ parameters.append(L" /p ");
+ parameters.append(
+ Int64ToWString(reinterpret_cast<int64>(contents_hwnd)).c_str());
+ BOOL result =
+ CreateProcess(NULL,
+ const_cast<LPWSTR>(parameters.c_str()),
+ NULL, // LPSECURITY_ATTRIBUTES lpProcessAttributes
+ NULL, // LPSECURITY_ATTRIBUTES lpThreadAttributes
+ FALSE, // BOOL bInheritHandles
+ CREATE_DEFAULT_ERROR_MODE, // DWORD dwCreationFlags
+ NULL, // LPVOID lpEnvironment
+ NULL, // LPCTSTR lpCurrentDirectory
+ &startup_info, // LPstartup_info lpstartup_info
+ &process_info); // LPPROCESS_INFORMATION
+ // lpProcessInformation
+
+ if (result) {
+ title_ = GetDefaultTitle();
+ CloseHandle(process_info.hThread);
+ process_handle_.Set(process_info.hProcess);
+ } else {
+ title_ = L"The Tubes are Clogged!";
+ }
+}
+
+void AboutInternetsStatusView::OnSize(const CRect& rect) {
+ // We're required to implement this because it is abstract, but we don't
+ // actually have anything to do right here.
+}
+
diff --git a/chrome/browser/tab_contents/about_internets_status_view.h b/chrome/browser/tab_contents/about_internets_status_view.h
new file mode 100644
index 0000000..5d7498e
--- /dev/null
+++ b/chrome/browser/tab_contents/about_internets_status_view.h
@@ -0,0 +1,45 @@
+// 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 CHROME_BROWSER_ABOUT_INTERNETS_STATUS_VIEW_H__
+#define CHROME_BROWSER_ABOUT_INTERNETS_STATUS_VIEW_H__
+
+#include "base/scoped_handle.h"
+#include "chrome/browser/tab_contents/status_view.h"
+
+// Displays sspipes.scr in the content HWND.
+class AboutInternetsStatusView : public StatusView {
+ public:
+ AboutInternetsStatusView();
+ virtual ~AboutInternetsStatusView();
+
+ // TabContents overrides
+ virtual const std::wstring GetDefaultTitle() const;
+ virtual const std::wstring& GetTitle() const;
+
+ // StatusView implementations
+
+ // Starts sspipes.scr rendering into the contents HWND. (Actually, it looks
+ // like this creates a child HWND which is the same size as the contents,
+ // and draws into that. Thus, it doesn't resize properly.)
+ // TODO(devint): Fix this resizing issue. A few possibilities:
+ // 1) Restart the process a few seconds after a resize is completed.
+ // 2) Render into an invisible HWND and stretchblt to the current HWND.
+ virtual void OnCreate(const CRect& rect);
+ // Does nothing, but implementation is required by StatusView.
+ virtual void OnSize(const CRect& rect);
+
+ private:
+ // Information about the pipes process, used to close the process when this
+ // view is destroyed.
+ ScopedHandle process_handle_;
+
+ // Title of the page.
+ std::wstring title_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AboutInternetsStatusView);
+};
+
+#endif // CHROME_BROWSER_ABOUT_INTERNETS_STATUS_VIEW_H__
+
diff --git a/chrome/browser/tab_contents/constrained_window.h b/chrome/browser/tab_contents/constrained_window.h
new file mode 100644
index 0000000..d703ad2
--- /dev/null
+++ b/chrome/browser/tab_contents/constrained_window.h
@@ -0,0 +1,68 @@
+// 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 CHROME_BROWSER_CONSTRAINED_WINDOW_H__
+#define CHROME_BROWSER_CONSTRAINED_WINDOW_H__
+
+#include "chrome/common/page_transition_types.h"
+#include "webkit/glue/window_open_disposition.h"
+
+class ConstrainedWindow;
+namespace views {
+class View;
+class WindowDelegate;
+}
+namespace gfx {
+class Point;
+class Rect;
+}
+class GURL;
+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 views::View subclass
+ // that provides the client area. Typical uses include the HTTP Basic Auth
+ // prompt. The caller must provide an object implementing
+ // views::WindowDelegate so that the Constrained Window can be properly
+ // configured. If |initial_bounds| is empty, the dialog will be centered
+ // within the constraining TabContents.
+ static ConstrainedWindow* CreateConstrainedDialog(
+ TabContents* owner,
+ const gfx::Rect& initial_bounds,
+ views::View* contents_view,
+ views::WindowDelegate* window_delegate);
+
+ // Closes the Constrained Window.
+ virtual void CloseConstrainedWindow() = 0;
+
+ // Repositions the Constrained Window so that the lower right corner
+ // of the titlebar is at the passed in |anchor_point|.
+ virtual void RepositionConstrainedWindowTo(
+ const gfx::Point& anchor_point) = 0;
+
+ // Tells the Constrained Window that the constraining TabContents was hidden,
+ // e.g. via a tab switch.
+ virtual void WasHidden() = 0;
+
+ // Tells the Constrained Window that the constraining TabContents became
+ // visible, e.g. via a tab switch.
+ virtual void DidBecomeSelected() = 0;
+
+ // Returns the title of the Constrained Window.
+ virtual std::wstring GetWindowTitle() const = 0;
+
+ // Returns the current display rectangle (relative to its
+ // parent). This method is only called from the unit tests to check
+ // the location/size of a constrained window.
+ virtual const gfx::Rect& GetCurrentBounds() const = 0;
+};
+
+#endif // #ifndef CHROME_BROWSER_CONSTRAINED_WINDOW_H__
diff --git a/chrome/browser/tab_contents/infobar_delegate.cc b/chrome/browser/tab_contents/infobar_delegate.cc
new file mode 100644
index 0000000..eaeb013
--- /dev/null
+++ b/chrome/browser/tab_contents/infobar_delegate.cc
@@ -0,0 +1,93 @@
+// 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 "chrome/browser/tab_contents/infobar_delegate.h"
+
+#include "base/logging.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/tab_contents/navigation_controller.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/l10n_util.h"
+
+#include "generated_resources.h"
+
+// InfoBarDelegate: ------------------------------------------------------------
+
+bool InfoBarDelegate::ShouldExpire(
+ const NavigationController::LoadCommittedDetails& details) const {
+ bool is_reload =
+ PageTransition::StripQualifier(details.entry->transition_type()) ==
+ PageTransition::RELOAD;
+ return is_reload || (contents_unique_id_ != details.entry->unique_id());
+}
+
+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;
+}
+
+// AlertInfoBarDelegate: -------------------------------------------------------
+
+bool AlertInfoBarDelegate::EqualsDelegate(InfoBarDelegate* delegate) const {
+ AlertInfoBarDelegate* alert_delegate = delegate->AsAlertInfoBarDelegate();
+ if (!alert_delegate)
+ return false;
+
+ return alert_delegate->GetMessageText() == GetMessageText();
+}
+
+AlertInfoBarDelegate::AlertInfoBarDelegate(TabContents* contents)
+ : InfoBarDelegate(contents) {
+}
+
+// LinkInfoBarDelegate: --------------------------------------------------------
+
+LinkInfoBarDelegate::LinkInfoBarDelegate(TabContents* contents)
+ : InfoBarDelegate(contents) {
+}
+
+// ConfirmInfoBarDelegate: -----------------------------------------------------
+
+std::wstring ConfirmInfoBarDelegate::GetButtonLabel(
+ InfoBarButton button) const {
+ if (button == BUTTON_OK)
+ return l10n_util::GetString(IDS_OK);
+ if (button == BUTTON_CANCEL)
+ return l10n_util::GetString(IDS_CANCEL);
+ NOTREACHED();
+ return std::wstring();
+}
+
+ConfirmInfoBarDelegate::ConfirmInfoBarDelegate(TabContents* contents)
+ : AlertInfoBarDelegate(contents) {
+}
+
+// SimpleAlertInfoBarDelegate: -------------------------------------------------
+
+SimpleAlertInfoBarDelegate::SimpleAlertInfoBarDelegate(
+ TabContents* contents,
+ const std::wstring& message,
+ SkBitmap* icon)
+ : AlertInfoBarDelegate(contents),
+ message_(message),
+ icon_(icon) {
+}
+
+std::wstring SimpleAlertInfoBarDelegate::GetMessageText() const {
+ return message_;
+}
+
+SkBitmap* SimpleAlertInfoBarDelegate::GetIcon() const {
+ return icon_;
+}
+
+void SimpleAlertInfoBarDelegate::InfoBarClosed() {
+ delete this;
+}
diff --git a/chrome/browser/tab_contents/infobar_delegate.h b/chrome/browser/tab_contents/infobar_delegate.h
new file mode 100644
index 0000000..07f225b
--- /dev/null
+++ b/chrome/browser/tab_contents/infobar_delegate.h
@@ -0,0 +1,206 @@
+// 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 CHROME_BROWSER_INFOBAR_DELEGATE_H_
+#define CHROME_BROWSER_INFOBAR_DELEGATE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/browser/tab_contents/navigation_controller.h"
+#include "skia/include/SkBitmap.h"
+
+class AlertInfoBarDelegate;
+class ConfirmInfoBarDelegate;
+class InfoBar;
+class LinkInfoBarDelegate;
+
+// 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
+// AlertInfoBarDelegate or ConfirmInfoBarDelegate, or override with your own
+// delegate for your own InfoBar variety.
+class InfoBarDelegate {
+ public:
+ // 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 {
+ return false;
+ }
+
+ // 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 after the InfoBar is closed. The delegate is free to delete itself
+ // at this point.
+ virtual void InfoBarClosed() {}
+
+ // Called to create the InfoBar. Implementation of this method is
+ // platform-specific.
+ virtual InfoBar* CreateInfoBar() = 0;
+
+ // Returns a pointer to the AlertInfoBarDelegate interface, if implemented.
+ virtual AlertInfoBarDelegate* AsAlertInfoBarDelegate() {
+ return NULL;
+ }
+
+ // Returns a pointer to the LinkInfoBarDelegate interface, if implemented.
+ virtual LinkInfoBarDelegate* AsLinkInfoBarDelegate() {
+ return NULL;
+ }
+
+ // Returns a pointer to the ConfirmInfoBarDelegate interface, if implemented.
+ virtual ConfirmInfoBarDelegate* AsConfirmInfoBarDelegate() {
+ return NULL;
+ }
+
+ 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 taht we were
+ // opened for. Used to help expire on navigations.
+ int contents_unique_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(InfoBarDelegate);
+};
+
+// An interface derived from InfoBarDelegate implemented by objects wishing to
+// control an AlertInfoBar.
+class AlertInfoBarDelegate : public InfoBarDelegate {
+ public:
+ // Returns the message string to be displayed for the InfoBar.
+ virtual std::wstring GetMessageText() const = 0;
+
+ // Return the icon to be shown for this InfoBar. If the returned bitmap is
+ // NULL, no icon is shown.
+ virtual SkBitmap* GetIcon() const { return NULL; }
+
+ // Overridden from InfoBarDelegate:
+ virtual bool EqualsDelegate(InfoBarDelegate* delegate) const;
+ virtual InfoBar* CreateInfoBar();
+ virtual AlertInfoBarDelegate* AsAlertInfoBarDelegate() { return this; }
+
+ protected:
+ explicit AlertInfoBarDelegate(TabContents* contents);
+
+ DISALLOW_COPY_AND_ASSIGN(AlertInfoBarDelegate);
+};
+
+// An interface derived from InfoBarDelegate implemented by objects wishing to
+// control a LinkInfoBar.
+class LinkInfoBarDelegate : public InfoBarDelegate {
+ public:
+ // Returns the message string to be displayed in the InfoBar. |link_offset|
+ // is the position where the link should be inserted. If |link_offset| is set
+ // to std::wstring::npos (it is by default), the link is right aligned within
+ // the InfoBar rather than being embedded in the message text.
+ virtual std::wstring GetMessageTextWithOffset(size_t* link_offset) const {
+ *link_offset = std::wstring::npos;
+ return std::wstring();
+ }
+
+ // Returns the text of the link to be displayed.
+ virtual std::wstring GetLinkText() const = 0;
+
+ // Returns the icon that should be shown for this InfoBar, or NULL if there is
+ // none.
+ virtual SkBitmap* GetIcon() const { return NULL; }
+
+ // Called when the Link is clicked. The |disposition| specifies how the
+ // resulting document should be loaded (based on the event flags present when
+ // the link was clicked). This function returns true if the InfoBar should be
+ // closed now or false if it should remain until the user explicitly closes
+ // it.
+ virtual bool LinkClicked(WindowOpenDisposition disposition) {
+ return true;
+ }
+
+ // Overridden from InfoBarDelegate:
+ virtual InfoBar* CreateInfoBar();
+ virtual LinkInfoBarDelegate* AsLinkInfoBarDelegate() {
+ return this;
+ }
+
+ protected:
+ explicit LinkInfoBarDelegate(TabContents* contents);
+
+ DISALLOW_COPY_AND_ASSIGN(LinkInfoBarDelegate);
+};
+
+// An interface derived from InfoBarDelegate implemented by objects wishing to
+// control a ConfirmInfoBar.
+class ConfirmInfoBarDelegate : public AlertInfoBarDelegate {
+ public:
+ enum InfoBarButton {
+ BUTTON_NONE = 0,
+ BUTTON_OK,
+ BUTTON_CANCEL
+ };
+
+ // Return the buttons to be shown for this InfoBar.
+ virtual int GetButtons() const {
+ return BUTTON_NONE;
+ }
+
+ // Return the label for the specified button. The default implementation
+ // returns "OK" for the OK button and "Cancel" for the Cancel button.
+ virtual std::wstring GetButtonLabel(InfoBarButton button) const;
+
+ // Called when the OK button is pressed. If the function returns true, the
+ // InfoBarDelegate should be removed from the associated TabContents.
+ virtual bool Accept() { return true; }
+
+ // Called when the Cancel button is pressed. If the function returns true,
+ // the InfoBarDelegate should be removed from the associated TabContents.
+ virtual bool Cancel() { return true; }
+
+ // Overridden from InfoBarDelegate:
+ virtual InfoBar* CreateInfoBar();
+ virtual ConfirmInfoBarDelegate* AsConfirmInfoBarDelegate() {
+ return this;
+ }
+
+ protected:
+ explicit ConfirmInfoBarDelegate(TabContents* contents);
+
+ DISALLOW_COPY_AND_ASSIGN(ConfirmInfoBarDelegate);
+};
+
+// Simple implementations for common use cases ---------------------------------
+
+class SimpleAlertInfoBarDelegate : public AlertInfoBarDelegate {
+ public:
+ SimpleAlertInfoBarDelegate(TabContents* contents,
+ const std::wstring& message,
+ SkBitmap* icon);
+
+ // Overridden from AlertInfoBarDelegate:
+ virtual std::wstring GetMessageText() const;
+ virtual SkBitmap* GetIcon() const;
+ virtual void InfoBarClosed();
+
+ private:
+ std::wstring message_;
+ SkBitmap* icon_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleAlertInfoBarDelegate);
+};
+
+#endif // #ifndef CHROME_BROWSER_INFOBAR_DELEGATE_H_
diff --git a/chrome/browser/tab_contents/interstitial_page.cc b/chrome/browser/tab_contents/interstitial_page.cc
new file mode 100644
index 0000000..ca0f226
--- /dev/null
+++ b/chrome/browser/tab_contents/interstitial_page.cc
@@ -0,0 +1,362 @@
+// 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 "chrome/browser/tab_contents/interstitial_page.h"
+
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/browser_resources.h"
+#include "chrome/browser/dom_operation_notification_details.h"
+#include "chrome/browser/render_widget_host_view_win.h"
+#include "chrome/browser/tab_contents/navigation_controller.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/tab_contents/web_contents.h"
+#include "chrome/browser/tab_contents/web_contents_view_win.h"
+#include "chrome/views/window.h"
+#include "chrome/views/window_delegate.h"
+#include "net/base/escape.h"
+
+enum ResourceRequestAction {
+ BLOCK,
+ RESUME,
+ CANCEL
+};
+
+namespace {
+
+class ResourceRequestTask : public Task {
+ public:
+ ResourceRequestTask(RenderViewHost* render_view_host,
+ ResourceRequestAction action)
+ : action_(action),
+ process_id_(render_view_host->process()->host_id()),
+ render_view_host_id_(render_view_host->routing_id()),
+ resource_dispatcher_host_(
+ g_browser_process->resource_dispatcher_host()) {
+ }
+
+ virtual void Run() {
+ switch (action_) {
+ case BLOCK:
+ resource_dispatcher_host_->BlockRequestsForRenderView(
+ process_id_, render_view_host_id_);
+ break;
+ case RESUME:
+ resource_dispatcher_host_->ResumeBlockedRequestsForRenderView(
+ process_id_, render_view_host_id_);
+ break;
+ case CANCEL:
+ resource_dispatcher_host_->CancelBlockedRequestsForRenderView(
+ 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
+
+// static
+InterstitialPage::InterstitialPageMap*
+ InterstitialPage::tab_to_interstitial_page_ = NULL;
+
+InterstitialPage::InterstitialPage(WebContents* tab,
+ bool new_navigation,
+ const GURL& url)
+ : tab_(tab),
+ url_(url),
+ action_taken_(false),
+ enabled_(true),
+ new_navigation_(new_navigation),
+ render_view_host_(NULL),
+ should_revert_tab_title_(false),
+ ui_loop_(MessageLoop::current()) {
+ 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()->GetPendingEntry());
+}
+
+InterstitialPage::~InterstitialPage() {
+ InterstitialPageMap::iterator iter = tab_to_interstitial_page_->find(tab_);
+ DCHECK(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, close it before showing the new one.
+ if (tab_->interstitial_page())
+ tab_->interstitial_page()->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, NOTIFY_RENDER_WIDGET_HOST_DESTROYED,
+ Source<RenderWidgetHost>(tab_->render_view_host()));
+
+ // Update the tab_to_interstitial_page_ map.
+ InterstitialPageMap::const_iterator 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(TAB_CONTENTS_WEB);
+ entry->set_url(url_);
+ entry->set_display_url(url_);
+ entry->set_page_type(NavigationEntry::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();
+
+ std::string data_url = "data:text/html;charset=utf-8," +
+ EscapePath(GetHTMLContents());
+ render_view_host_->NavigateToURL(GURL(data_url));
+
+ notification_registrar_.Add(this, NOTIFY_TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(tab_));
+ notification_registrar_.Add(this, NOTIFY_NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(tab_->controller()));
+ notification_registrar_.Add(this, NOTIFY_NAV_ENTRY_PENDING,
+ Source<NavigationController>(tab_->controller()));
+}
+
+void InterstitialPage::Hide() {
+ 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(original_tab_title_);
+ tab_->NotifyNavigationStateChanged(TabContents::INVALIDATE_TITLE);
+ }
+ delete this;
+}
+
+void InterstitialPage::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFY_NAV_ENTRY_PENDING:
+ // We are navigating away from the interstitial. Make sure clicking on
+ // the interstitial will have no effect.
+ Disable();
+ break;
+ case NOTIFY_RENDER_WIDGET_HOST_DESTROYED:
+ if (!action_taken_) {
+ // The RenderViewHost is being destroyed (as part of the tab being
+ // closed), make sure we clear the blocked requests.
+ DCHECK(Source<RenderViewHost>(source).ptr() ==
+ tab_->render_view_host());
+ TakeActionOnResourceDispatcher(CANCEL);
+ }
+ break;
+ case NOTIFY_TAB_CONTENTS_DESTROYED:
+ case NOTIFY_NAV_ENTRY_COMMITTED:
+ if (!action_taken_) {
+ // 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();
+ }
+}
+
+RenderViewHost* InterstitialPage::CreateRenderViewHost() {
+ RenderViewHost* render_view_host = new RenderViewHost(
+ SiteInstance::CreateSiteInstance(tab()->profile()),
+ this, MSG_ROUTING_NONE, NULL);
+ RenderWidgetHostViewWin* view =
+ new RenderWidgetHostViewWin(render_view_host);
+ render_view_host->set_view(view);
+ view->Create(tab_->GetContentHWND());
+ view->set_parent_hwnd(tab_->GetContentHWND());
+ WebContentsViewWin* web_contents_view =
+ static_cast<WebContentsViewWin*>(tab_->view());
+ render_view_host->CreateRenderView();
+ // SetSize must be called after CreateRenderView or the HWND won't show.
+ view->SetSize(web_contents_view->GetContainerSize());
+
+ render_view_host->AllowDomAutomationBindings();
+ return render_view_host;
+}
+
+void InterstitialPage::Proceed() {
+ DCHECK(!action_taken_);
+ Disable();
+ action_taken_ = true;
+
+ // 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!
+ }
+}
+
+void InterstitialPage::DontProceed() {
+ DCHECK(!action_taken_);
+ Disable();
+ action_taken_ = true;
+
+ // 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 (new_navigation_) {
+ // 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();
+ }
+
+ Hide();
+ // WARNING: we are now deleted!
+}
+
+void InterstitialPage::SetSize(const gfx::Size& size) {
+ render_view_host_->view()->SetSize(size);
+}
+
+Profile* InterstitialPage::GetProfile() const {
+ return tab_->profile();
+}
+
+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);
+
+ // 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::RendererGone(RenderViewHost* render_view_host) {
+ // Our renderer died. This should not happen in normal cases.
+ // Just dismiss the interstitial.
+ DontProceed();
+}
+
+void InterstitialPage::DomOperationResponse(const std::string& json_string,
+ int automation_id) {
+ if (enabled_)
+ CommandReceived(json_string);
+}
+
+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 this interstitial is shown on an existing navigation entry, we'll need
+ // to remember its title so we can revert to it when hidden.
+ if (!new_navigation_ && !should_revert_tab_title_) {
+ original_tab_title_ = entry->title();
+ should_revert_tab_title_ = true;
+ }
+ entry->set_title(title);
+ tab_->NotifyNavigationStateChanged(TabContents::INVALIDATE_TITLE);
+}
+
+void InterstitialPage::Disable() {
+ enabled_ = false;
+}
+
+void InterstitialPage::TakeActionOnResourceDispatcher(
+ ResourceRequestAction action) {
+ DCHECK(MessageLoop::current() == ui_loop_) <<
+ "TakeActionOnResourceDispatcher should be called on the main thread.";
+ // 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 an IO thread, as when unit-tests we don't
+ // have one.
+ if (tab_->render_view_host() && g_browser_process->io_thread()) {
+ g_browser_process->io_thread()->message_loop()->PostTask(
+ FROM_HERE, new ResourceRequestTask(tab_->render_view_host(), action));
+ }
+}
+
+// static
+void InterstitialPage::InitInterstitialPageMap() {
+ if (!tab_to_interstitial_page_)
+ tab_to_interstitial_page_ = new InterstitialPageMap;
+}
+
+// static
+InterstitialPage* InterstitialPage::GetInterstitialPage(
+ WebContents* web_contents) {
+ InitInterstitialPageMap();
+ InterstitialPageMap::const_iterator iter =
+ tab_to_interstitial_page_->find(web_contents);
+ if (iter == tab_to_interstitial_page_->end())
+ return NULL;
+
+ return iter->second;
+}
diff --git a/chrome/browser/tab_contents/interstitial_page.h b/chrome/browser/tab_contents/interstitial_page.h
new file mode 100644
index 0000000..6c2c76f5
--- /dev/null
+++ b/chrome/browser/tab_contents/interstitial_page.h
@@ -0,0 +1,168 @@
+// 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 CHROME_BROWSER_INTERSTITIAL_PAGE_H_
+#define CHROME_BROWSER_INTERSTITIAL_PAGE_H_
+
+#include <string>
+
+#include "chrome/browser/render_view_host_delegate.h"
+#include "chrome/common/notification_registrar.h"
+#include "googleurl/src/gurl.h"
+
+class NavigationEntry;
+class WebContents;
+
+// 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 WebContents closing them or the tab containing them
+// being closed.
+
+enum ResourceRequestAction;
+
+class InterstitialPage : public NotificationObserver,
+ public RenderViewHostDelegate {
+ public:
+ // 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(WebContents* 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(WebContents* web_contents);
+
+ // Sub-classes should return the HTML that should be displayed in the page.
+ virtual std::string GetHTMLContents() { return std::string(); }
+
+ // 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: 'this' has been 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();
+
+ // Sizes the RenderViewHost showing the actual interstitial page contents.
+ void SetSize(const gfx::Size& size);
+
+ protected:
+ // NotificationObserver method:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // RenderViewHostDelegate implementation:
+ virtual Profile* GetProfile() const;
+ virtual WebPreferences GetWebkitPrefs() {
+ return WebPreferences();
+ }
+ virtual void DidNavigate(RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params);
+ virtual void RendererGone(RenderViewHost* render_view_host);
+ virtual void DomOperationResponse(const std::string& json_string,
+ int automation_id);
+ virtual void UpdateTitle(RenderViewHost* render_view_host,
+ int32 page_id,
+ const std::wstring& title);
+
+ // 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) { }
+
+ WebContents* tab() const { return tab_; }
+ const GURL& url() const { return url_; }
+ RenderViewHost* render_view_host() const { return render_view_host_; }
+
+ // Creates and shows the RenderViewHost containing the interstitial content.
+ // Overriden in unit tests.
+ virtual RenderViewHost* CreateRenderViewHost();
+
+ private:
+ // AutomationProvider needs access to Proceed and DontProceed to simulate
+ // user actions.
+ friend class AutomationProvider;
+
+ // 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.
+ WebContents* 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 this interstitial is enabled. See Disable() for more info.
+ bool enabled_;
+
+ // Whether the Proceed or DontProceed have been called yet.
+ bool action_taken_;
+
+ // Notification magic.
+ NotificationRegistrar notification_registrar_;
+
+ // The RenderViewHost displaying the interstitial contents.
+ RenderViewHost* render_view_host_;
+
+ // 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_;
+
+ // The original title of the tab that should be reverted to when the
+ // interstitial is hidden.
+ std::wstring original_tab_title_;
+
+ MessageLoop* ui_loop_;
+
+ // We keep a map of the various blocking pages shown as the UI tests need to
+ // be able to retrieve them.
+ typedef std::map<WebContents*,InterstitialPage*> InterstitialPageMap;
+ static InterstitialPageMap* tab_to_interstitial_page_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPage);
+};
+
+#endif // #ifndef CHROME_BROWSER_INTERSTITIAL_PAGE_H_
+
diff --git a/chrome/browser/tab_contents/ipc_status_view.cc b/chrome/browser/tab_contents/ipc_status_view.cc
new file mode 100644
index 0000000..1385898
--- /dev/null
+++ b/chrome/browser/tab_contents/ipc_status_view.cc
@@ -0,0 +1,370 @@
+// 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 "chrome/browser/tab_contents/ipc_status_view.h"
+
+#include <stdio.h>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "chrome/app/chrome_dll_resource.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/ipc_logging.h"
+#include "chrome/common/plugin_messages.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "chrome/common/render_messages.h"
+
+#ifdef IPC_MESSAGE_LOG_ENABLED
+
+using base::Time;
+
+namespace {
+const wchar_t kTitleMsg[] = L"IPC Messages";
+
+const wchar_t kStartLoggingMsg[] = L"Start IPC Logging";
+const wchar_t kStopLoggingMsg[] = L"Stop IPC Logging";
+const wchar_t kClearMsg[] = L"Clear";
+const wchar_t kSettingsMsg[] = L"Filter";
+
+enum {
+ kTimeColumn = 0,
+ kChannelColumn,
+ kMessageColumn,
+ kFlagsColumn,
+ kDispatchColumn,
+ kProcessColumn,
+ kParamsColumn,
+};
+
+// This class ensures that we have a link dependency on render_messages.cc and
+// plugin_messages.cc, and at the same time sets up the message logger function
+// mappings.
+class RegisterLoggerFuncs {
+ public:
+ RegisterLoggerFuncs() {
+ RenderMessagesInit();
+ PluginMessagesInit();
+ }
+};
+
+RegisterLoggerFuncs g_register_logger_funcs;
+
+} // namespace
+
+IPCStatusView* IPCStatusView::current_;
+
+IPCStatusView::IPCStatusView()
+ : StatusView(TAB_CONTENTS_IPC_STATUS_VIEW) {
+ DCHECK(!current_);
+ current_ = this;
+ settings_dialog_ = NULL;
+ init_done_ = false;
+ view_ = NULL;
+ view_host_ = NULL;
+ plugin_ = NULL;
+ plugin_host_ = NULL;
+ npobject_ = NULL;
+ plugin_process_ = NULL;
+ plugin_process_host_ = NULL;
+
+ IPC::Logging::current()->SetConsumer(this);
+}
+
+IPCStatusView::~IPCStatusView() {
+ current_ = NULL;
+ IPC::Logging::current()->SetConsumer(NULL);
+
+ if (settings_dialog_ != NULL)
+ ::DestroyWindow(settings_dialog_);
+}
+
+const std::wstring IPCStatusView::GetDefaultTitle() {
+ return kTitleMsg;
+}
+
+void IPCStatusView::SetActive(bool active) {
+ StatusView::set_is_active(active);
+
+ if (!disabled_messages_.empty() || !active)
+ return;
+
+ Profile* current_profile = profile();
+ if (!current_profile)
+ return;
+ PrefService* prefs = current_profile->GetPrefs();
+ if (prefs->IsPrefRegistered(prefs::kIpcDisabledMessages))
+ return;
+ prefs->RegisterListPref(prefs::kIpcDisabledMessages);
+ const ListValue* list = prefs->GetList(prefs::kIpcDisabledMessages);
+ if (!list)
+ return;
+ for (ListValue::const_iterator itr = list->begin();
+ itr != list->end();
+ ++itr) {
+ if (!(*itr)->IsType(Value::TYPE_INTEGER))
+ continue;
+ int value = 0;
+ if (!(*itr)->GetAsInteger(&value))
+ continue;
+ disabled_messages_.insert(value);
+ }
+}
+
+void IPCStatusView::OnCreate(const CRect& rect) {
+ CreateButton(IDC_START_LOGGING, kStartLoggingMsg);
+ CreateButton(IDC_STOP_LOGGING, kStopLoggingMsg);
+ CreateButton(IDC_CLEAR, kClearMsg);
+ CreateButton(IDC_SETTINGS, kSettingsMsg);
+
+ // Initialize the list view for messages.
+ // Don't worry about the size, we'll resize when we get WM_SIZE
+ message_list_.Create(GetContainerHWND(), const_cast<CRect&>(rect), NULL,
+ WS_CHILD | WS_VISIBLE | LVS_SORTASCENDING);
+ message_list_.SetViewType(LVS_REPORT);
+ message_list_.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT);
+
+ int column_index = 0;
+ message_list_.InsertColumn(kTimeColumn, L"time", LVCFMT_LEFT, 80);
+ message_list_.InsertColumn(kChannelColumn, L"channel", LVCFMT_LEFT, 110);
+ message_list_.InsertColumn(kMessageColumn, L"message", LVCFMT_LEFT, 240);
+ message_list_.InsertColumn(kFlagsColumn, L"flags", LVCFMT_LEFT, 50);
+ message_list_.InsertColumn(kDispatchColumn, L"dispatch (ms)", LVCFMT_RIGHT, 80);
+ message_list_.InsertColumn(kProcessColumn, L"process (ms)", LVCFMT_RIGHT, 80);
+ message_list_.InsertColumn(kParamsColumn, L"parameters", LVCFMT_LEFT, 500);
+}
+
+void IPCStatusView::Log(const IPC::LogData& data) {
+ if (disabled_messages_.find(data.type) != disabled_messages_.end())
+ return; // Message type is filtered out.
+
+ Time sent = Time::FromInternalValue(data.sent);
+ Time::Exploded exploded;
+ sent.LocalExplode(&exploded);
+ if (exploded.hour > 12)
+ exploded.hour -= 12;
+
+ std::wstring sent_str = StringPrintf(L"%02d:%02d:%02d.%03d",
+ exploded.hour, exploded.minute, exploded.second, exploded.millisecond);
+
+ int count = message_list_.GetItemCount();
+ int index = message_list_.InsertItem(count, sent_str.c_str());
+
+ message_list_.SetItemText(index, kTimeColumn, sent_str.c_str());
+ message_list_.SetItemText(index, kChannelColumn, data.channel.c_str());
+
+ std::wstring message_name;
+ IPC::Logging::GetMessageText(data.type, &message_name, NULL, NULL);
+ message_list_.SetItemText(index, kMessageColumn, message_name.c_str());
+ message_list_.SetItemText(index, kFlagsColumn, data.flags.c_str());
+
+ int64 time_to_send = (Time::FromInternalValue(data.receive) -
+ sent).InMilliseconds();
+ // time can go backwards by a few ms (see Time), don't show that.
+ time_to_send = std::max(static_cast<int>(time_to_send), 0);
+ std::wstring temp = StringPrintf(L"%d", time_to_send);
+ message_list_.SetItemText(index, kDispatchColumn, temp.c_str());
+
+ int64 time_to_process = (Time::FromInternalValue(data.dispatch) -
+ Time::FromInternalValue(data.receive)).InMilliseconds();
+ time_to_process = std::max(static_cast<int>(time_to_process), 0);
+ temp = StringPrintf(L"%d", time_to_process);
+ message_list_.SetItemText(index, kProcessColumn, temp.c_str());
+
+ message_list_.SetItemText(index, kParamsColumn, data.params.c_str());
+ message_list_.EnsureVisible(index, FALSE);
+}
+
+void IPCStatusView::OnSize(const CRect& rect) {
+ message_list_.MoveWindow(rect);
+}
+
+void IPCStatusView::OnStartLogging(UINT code, int button_id, HWND hwnd) {
+ IPC::Logging::current()->Enable();
+}
+
+void IPCStatusView::OnStopLogging(UINT code, int button_id, HWND hwnd) {
+ IPC::Logging::current()->Disable();
+}
+
+void IPCStatusView::OnClear(UINT code, int button_id, HWND hwnd) {
+ message_list_.DeleteAllItems();
+}
+
+INT_PTR CALLBACK IPCStatusView::DialogProc(
+ HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
+ switch (msg) {
+ case WM_INITDIALOG:
+ current()->InitDialog(hwnd);
+ return FALSE; // Don't set keyboard focus.
+ case WM_SYSCOMMAND:
+ if (wparam == SC_CLOSE) {
+ current()->CloseDialog();
+ return FALSE;
+ }
+ break;
+ case WM_NOTIFY: {
+ NMLISTVIEW* info = reinterpret_cast<NM_LISTVIEW*>(lparam);
+ if ((wparam == IDC_View || wparam == IDC_ViewHost || wparam == IDC_Plugin ||
+ wparam == IDC_PluginHost || wparam == IDC_NPObject ||
+ wparam == IDC_PluginProcess || wparam == IDC_PluginProcessHost) &&
+ info->hdr.code == LVN_ITEMCHANGED) {
+ if (info->uChanged & LVIF_STATE) {
+ bool checked = (info->uNewState >> 12) == 2;
+ current()->OnCheck(static_cast<int>(info->lParam), checked);
+ }
+ return FALSE;
+ }
+ break;
+ }
+ case WM_COMMAND:
+ if (HIWORD(wparam) == BN_CLICKED)
+ current()->OnButtonClick(LOWORD(wparam));
+
+ break;
+ }
+ return FALSE;
+}
+
+void IPCStatusView::InitDialog(HWND hwnd) {
+ CreateColumn(ViewStart, ViewEnd, ::GetDlgItem(hwnd, IDC_View), &view_);
+ CreateColumn(ViewHostStart, ViewHostEnd, ::GetDlgItem(hwnd, IDC_ViewHost),
+ &view_host_);
+ CreateColumn(PluginStart, PluginEnd, ::GetDlgItem(hwnd, IDC_Plugin), &plugin_);
+ CreateColumn(PluginHostStart, PluginHostEnd,
+ ::GetDlgItem(hwnd, IDC_PluginHost), &plugin_host_);
+ CreateColumn(NPObjectStart, NPObjectEnd, ::GetDlgItem(hwnd, IDC_NPObject),
+ &npobject_);
+ CreateColumn(PluginProcessStart, PluginProcessEnd,
+ ::GetDlgItem(hwnd, IDC_PluginProcess), &plugin_process_);
+ CreateColumn(PluginProcessHostStart, PluginProcessHostEnd,
+ ::GetDlgItem(hwnd, IDC_PluginProcessHost), &plugin_process_host_);
+ init_done_ = true;
+}
+
+void IPCStatusView::CreateColumn(
+ uint16 start, uint16 end, HWND hwnd, CListViewCtrl** control) {
+ DCHECK(*control == NULL);
+ *control = new CListViewCtrl(hwnd);
+ CListViewCtrl* control_ptr = *control;
+ control_ptr->SetViewType(LVS_REPORT);
+ control_ptr->SetExtendedListViewStyle(LVS_EX_CHECKBOXES);
+ control_ptr->ModifyStyle(0, LVS_SORTASCENDING | LVS_NOCOLUMNHEADER);
+ control_ptr->InsertColumn(0, L"id", LVCFMT_LEFT, 230);
+
+ std::set<int>* disabled_messages = &current()->disabled_messages_;
+ for (uint16 i = start; i < end; i++) {
+ std::wstring name;
+ IPC::Logging::GetMessageText(i, &name, NULL, NULL);
+
+ int index = control_ptr->InsertItem(
+ LVIF_TEXT | LVIF_PARAM, 0, name.c_str(), 0, 0, 0, i);
+
+ control_ptr->SetItemText(index, 0, name.c_str());
+
+ if (disabled_messages->find(i) == disabled_messages->end())
+ control_ptr->SetCheckState(index, TRUE);
+ }
+}
+
+void IPCStatusView::CloseDialog() {
+ delete view_;
+ delete view_host_;
+ delete plugin_host_;
+ delete npobject_;
+ delete plugin_process_;
+ delete plugin_process_host_;
+ view_ = NULL;
+ view_host_ = NULL;
+ plugin_ = NULL;
+ plugin_host_ = NULL;
+ npobject_ = NULL;
+ plugin_process_ = NULL;
+ plugin_process_host_ = NULL;
+ init_done_ = false;
+
+ ::DestroyWindow(settings_dialog_);
+ settings_dialog_ = NULL;
+
+ Profile* current_profile = profile();
+ if (!current_profile)
+ return;
+ PrefService* prefs = current_profile->GetPrefs();
+ if (!prefs->IsPrefRegistered(prefs::kIpcDisabledMessages))
+ return;
+ ListValue* list = prefs->GetMutableList(prefs::kIpcDisabledMessages);
+ list->Clear();
+ for (std::set<int>::const_iterator itr = disabled_messages_.begin();
+ itr != disabled_messages_.end();
+ ++itr) {
+ list->Append(Value::CreateIntegerValue(*itr));
+ }
+}
+
+void IPCStatusView::OnCheck(int id, bool checked) {
+ if (!init_done_)
+ return;
+
+ if (checked) {
+ disabled_messages_.erase(id);
+ } else {
+ disabled_messages_.insert(id);
+ }
+}
+
+void IPCStatusView::OnButtonClick(int id) {
+ switch(id) {
+ case IDC_ViewAll:
+ CheckButtons(view_, true);
+ break;
+ case IDC_ViewNone:
+ CheckButtons(view_, false);
+ break;
+ case IDC_ViewHostAll:
+ CheckButtons(view_host_, true);
+ break;
+ case IDC_ViewHostNone:
+ CheckButtons(view_host_, false);
+ break;
+ case IDC_PluginAll:
+ CheckButtons(plugin_, true);
+ break;
+ case IDC_PluginNone:
+ CheckButtons(plugin_, false);
+ break;
+ case IDC_PluginHostAll:
+ CheckButtons(plugin_host_, true);
+ break;
+ case IDC_PluginHostNone:
+ CheckButtons(plugin_host_, false);
+ break;
+ case IDC_NPObjectAll:
+ CheckButtons(npobject_, true);
+ break;
+ case IDC_NPObjectNone:
+ CheckButtons(npobject_, false);
+ break;
+ }
+}
+
+void IPCStatusView::CheckButtons(CListViewCtrl* control, bool check) {
+ int count = control->GetItemCount();
+ for (int i = 0; i < count; ++i)
+ control->SetCheckState(i, check);
+}
+
+void IPCStatusView::OnSettings(UINT code, int button_id, HWND hwnd) {
+ if (settings_dialog_ != NULL)
+ return;
+
+ HINSTANCE module_handle = GetModuleHandle(chrome::kBrowserResourcesDll);
+
+ settings_dialog_ = CreateDialog(module_handle,
+ MAKEINTRESOURCE(IDD_IPC_SETTINGS),
+ NULL,
+ IPCStatusView::DialogProc);
+ ::ShowWindow(settings_dialog_, SW_SHOW);
+}
+
+#endif // IPC_MESSAGE_LOG_ENABLED
diff --git a/chrome/browser/tab_contents/ipc_status_view.h b/chrome/browser/tab_contents/ipc_status_view.h
new file mode 100644
index 0000000..790516d
--- /dev/null
+++ b/chrome/browser/tab_contents/ipc_status_view.h
@@ -0,0 +1,89 @@
+// 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 CHROME_BROWSER_IPC_STATUS_VIEW_H__
+#define CHROME_BROWSER_IPC_STATUS_VIEW_H__
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "chrome/browser/tab_contents/status_view.h"
+#include "chrome/common/ipc_logging.h"
+#include "chrome/common/ipc_message_utils.h"
+
+#ifdef IPC_MESSAGE_LOG_ENABLED
+
+class IPCStatusView : public StatusView,
+ public IPC::Logging::Consumer {
+ public:
+ // button types
+ enum {
+ IDC_START_LOGGING = 101,
+ IDC_STOP_LOGGING,
+ IDC_CLEAR,
+ IDC_SETTINGS,
+ };
+
+ IPCStatusView();
+ virtual ~IPCStatusView();
+
+ static IPCStatusView* current() { return current_; }
+ void Log(const IPC::LogData& data);
+
+ // TabContents overrides
+ virtual const std::wstring GetDefaultTitle();
+ virtual void SetActive(bool active);
+
+ // StatusView implementation
+ virtual void OnCreate(const CRect& rect);
+ virtual void OnSize(const CRect& rect);
+
+ BEGIN_MSG_MAP(IPCStatusView)
+ COMMAND_HANDLER_EX(IDC_START_LOGGING, BN_CLICKED, OnStartLogging)
+ COMMAND_HANDLER_EX(IDC_STOP_LOGGING, BN_CLICKED, OnStopLogging)
+ COMMAND_HANDLER_EX(IDC_CLEAR, BN_CLICKED, OnClear)
+ COMMAND_HANDLER_EX(IDC_SETTINGS, BN_CLICKED, OnSettings)
+ CHAIN_MSG_MAP(StatusView);
+ END_MSG_MAP()
+
+ static INT_PTR CALLBACK DialogProc(
+ HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
+ void InitDialog(HWND hwnd);
+ void CloseDialog();
+ static void CreateColumn(
+ uint16 start, uint16 end, HWND hwnd, CListViewCtrl** control);
+ void OnCheck(int id, bool checked);
+ void OnButtonClick(int id);
+ static void CheckButtons(CListViewCtrl* control, bool check);
+
+ private:
+
+ // Event handlers
+ void OnStartLogging(UINT code, int button_id, HWND hwnd);
+ void OnStopLogging(UINT code, int button_id, HWND hwnd);
+ void OnClear(UINT code, int button_id, HWND hwnd);
+ void OnSettings(UINT code, int button_id, HWND hwnd);
+
+ static IPCStatusView* current_;
+ CListViewCtrl message_list_;
+
+ // Used for the filter dialog.
+ CListViewCtrl* view_;
+ CListViewCtrl* view_host_;
+ CListViewCtrl* plugin_;
+ CListViewCtrl* plugin_host_;
+ CListViewCtrl* npobject_;
+ CListViewCtrl* plugin_process_;
+ CListViewCtrl* plugin_process_host_;
+ bool init_done_;
+ HWND settings_dialog_;
+ std::set<int> disabled_messages_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(IPCStatusView);
+};
+
+#endif // IPC_MESSAGE_LOG_ENABLED
+
+#endif // #ifndef CHROME_BROWSER_IPC_STATUS_VIEW_H__
+
diff --git a/chrome/browser/tab_contents/native_ui_contents.cc b/chrome/browser/tab_contents/native_ui_contents.cc
new file mode 100644
index 0000000..9ff65ce
--- /dev/null
+++ b/chrome/browser/tab_contents/native_ui_contents.cc
@@ -0,0 +1,671 @@
+// 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 "chrome/browser/tab_contents/native_ui_contents.h"
+
+#include "chrome/browser/browser.h"
+#include "chrome/browser/history_tab_ui.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/views/download_tab_view.h"
+#include "chrome/common/drag_drop_types.h"
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/common/gfx/chrome_font.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/os_exchange_data.h"
+#include "chrome/common/resource_bundle.h"
+#include "chrome/views/background.h"
+#include "chrome/views/checkbox.h"
+#include "chrome/views/grid_layout.h"
+#include "chrome/views/image_view.h"
+#include "chrome/views/root_view.h"
+#include "chrome/views/scroll_view.h"
+#include "chrome/views/throbber.h"
+#include "chrome/views/widget_win.h"
+
+#include "generated_resources.h"
+
+using views::ColumnSet;
+using views::GridLayout;
+
+//static
+bool NativeUIContents::g_ui_factories_initialized = false;
+
+// The URL scheme currently used.
+static const char kNativeUIContentsScheme[] = "chrome-nativeui";
+
+// Unique page id generator.
+static int g_next_page_id = 0;
+
+// The x-position of the title.
+static const int kDestinationTitleOffset = 38;
+
+// The x-position of the search field.
+static const int kDestinationSearchOffset = 128;
+
+// The width of the search field.
+static const int kDestinationSearchWidth = 360;
+
+// Padding between columns
+static const int kDestinationSmallerMargin = 8;
+
+// The background color.
+static const SkColor kBackground = SkColorSetRGB(255, 255, 255);
+
+// The color of the bottom margin.
+static const SkColor kBottomMarginColor = SkColorSetRGB(246, 249, 255);
+
+// The height of the bottom margin.
+static const int kBottomMargin = 5;
+
+// The Chrome product logo.
+static const SkBitmap* kProductLogo = NULL;
+
+// Padding around the product logo.
+static const int kProductLogoPadding = 8;
+
+namespace {
+
+// NativeRootView --------------------------------------------------------------
+
+// NativeRootView is a trivial RootView subclass that allows URL drops and
+// forwards them to the NavigationController to open.
+
+class NativeRootView : public views::RootView {
+ public:
+ explicit NativeRootView(NativeUIContents* host)
+ : RootView(host),
+ host_(host) { }
+
+ virtual ~NativeRootView() { }
+
+ virtual bool CanDrop(const OSExchangeData& data) {
+ return data.HasURL();
+ }
+
+ virtual int OnDragUpdated(const views::DropTargetEvent& event) {
+ if (event.GetSourceOperations() & DragDropTypes::DRAG_COPY)
+ return DragDropTypes::DRAG_COPY;
+ if (event.GetSourceOperations() & DragDropTypes::DRAG_LINK)
+ return DragDropTypes::DRAG_LINK;
+ return DragDropTypes::DRAG_NONE;
+ }
+
+ virtual int OnPerformDrop(const views::DropTargetEvent& event) {
+ GURL url;
+ std::wstring title;
+ if (!event.GetData().GetURLAndTitle(&url, &title) || !url.is_valid())
+ return DragDropTypes::DRAG_NONE;
+ host_->controller()->LoadURL(url, GURL(), PageTransition::GENERATED);
+ return OnDragUpdated(event);
+ }
+
+ private:
+ NativeUIContents* host_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(NativeRootView);
+};
+
+} // namespace
+
+
+// Returns the end of the scheme and end of the host. This is temporary until
+// bug 772411 is fixed.
+static void GetSchemeAndHostEnd(const GURL& url,
+ size_t* scheme_end,
+ size_t* host_end) {
+ const std::string spec = url.spec();
+ *scheme_end = spec.find("//");
+ DCHECK(*scheme_end != std::string::npos);
+
+ *host_end = spec.find('/', *scheme_end + 2);
+ if (*host_end == std::string::npos)
+ *host_end = spec.size();
+}
+
+NativeUIContents::NativeUIContents(Profile* profile)
+ : TabContents(TAB_CONTENTS_NATIVE_UI),
+ is_visible_(false),
+ current_ui_(NULL),
+ current_view_(NULL),
+ state_(new PageState()) {
+ if (!g_ui_factories_initialized) {
+ InitializeNativeUIFactories();
+ g_ui_factories_initialized = true;
+ }
+}
+
+NativeUIContents::~NativeUIContents() {
+ if (current_ui_) {
+ views::RootView* root_view = GetRootView();
+ current_ui_->WillBecomeInvisible(this);
+ root_view->RemoveChildView(current_view_);
+ current_ui_ = NULL;
+ current_view_ = NULL;
+ }
+
+ STLDeleteContainerPairSecondPointers(path_to_native_uis_.begin(),
+ path_to_native_uis_.end());
+}
+
+void NativeUIContents::CreateView() {
+ set_delete_on_destroy(false);
+ WidgetWin::Init(GetDesktopWindow(), gfx::Rect(), false);
+}
+
+LRESULT NativeUIContents::OnCreate(LPCREATESTRUCT create_struct) {
+ // Set the view container initial size.
+ CRect tmp;
+ ::GetWindowRect(GetHWND(), &tmp);
+ tmp.right = tmp.Width();
+ tmp.bottom = tmp.Height();
+ tmp.left = tmp.top = 0;
+
+ // Install the focus manager so we get notified of Tab key events.
+ views::FocusManager::InstallFocusSubclass(GetHWND(), NULL);
+ GetRootView()->set_background(new NativeUIBackground);
+ return 0;
+}
+
+void NativeUIContents::OnDestroy() {
+ views::FocusManager::UninstallFocusSubclass(GetHWND());
+}
+
+void NativeUIContents::OnSize(UINT size_command, const CSize& new_size) {
+ Layout();
+ ::RedrawWindow(GetHWND(), NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN);
+}
+
+void NativeUIContents::OnWindowPosChanged(WINDOWPOS* position) {
+ // NOTE: this may be invoked even when the visbility didn't change, in which
+ // case hiding and showing are both false.
+ const bool hiding = (position->flags & SWP_HIDEWINDOW) == SWP_HIDEWINDOW;
+ const bool showing = (position->flags & SWP_SHOWWINDOW) == SWP_SHOWWINDOW;
+ if (hiding || showing) {
+ if (is_visible_ != showing) {
+ is_visible_ = showing;
+ if (current_ui_) {
+ if (is_visible_)
+ current_ui_->WillBecomeVisible(this);
+ else
+ current_ui_->WillBecomeInvisible(this);
+ }
+ }
+ }
+ ChangeSize(0, CSize(position->cx, position->cy));
+
+ SetMsgHandled(FALSE);
+}
+
+void NativeUIContents::GetContainerBounds(gfx::Rect* out) const {
+ GetBounds(out, false);
+}
+
+void NativeUIContents::SetPageState(PageState* page_state) {
+ if (!page_state)
+ page_state = new PageState();
+ state_.reset(page_state);
+ NavigationController* ctrl = controller();
+ if (ctrl) {
+ int ne_index = ctrl->GetLastCommittedEntryIndex();
+ NavigationEntry* ne = ctrl->GetEntryAtIndex(ne_index);
+ if (ne) {
+ // NavigationEntry is null if we're being restored.
+ DCHECK(ne);
+ std::string rep;
+ state_->GetByteRepresentation(&rep);
+ ne->set_content_state(rep);
+ ctrl->NotifyEntryChanged(ne, ne_index);
+ }
+ }
+}
+
+bool NativeUIContents::NavigateToPendingEntry(bool reload) {
+ views::RootView* root_view = GetRootView();
+ DCHECK(root_view);
+
+ if (current_ui_) {
+ current_ui_->WillBecomeInvisible(this);
+ root_view->RemoveChildView(current_view_);
+ current_ui_ = NULL;
+ current_view_ = NULL;
+ }
+
+ NavigationEntry* pending_entry = controller()->GetPendingEntry();
+ NativeUI* new_ui = GetNativeUIForURL(pending_entry->url());
+ if (new_ui) {
+ current_ui_ = new_ui;
+ is_visible_ = true;
+ current_ui_->WillBecomeVisible(this);
+ current_view_ = new_ui->GetView();
+ root_view->AddChildView(current_view_);
+
+ std::string s = pending_entry->content_state();
+ if (s.empty())
+ state_->InitWithURL(pending_entry->url());
+ else
+ state_->InitWithBytes(s);
+
+ current_ui_->Navigate(*state_);
+ Layout();
+ }
+
+ // Commit the new load in the navigation controller. If the ID of the
+ // NavigationEntry we were given was -1, that means this is a new load, so
+ // we have to generate a new ID.
+ controller()->CommitPendingEntry();
+
+ // Populate the committed entry.
+ NavigationEntry* committed_entry = controller()->GetLastCommittedEntry();
+ committed_entry->set_title(GetDefaultTitle());
+ committed_entry->favicon().set_bitmap(GetFavIcon());
+ committed_entry->favicon().set_is_valid(true);
+ if (new_ui) {
+ // Strip out the query params, they should have moved to state.
+ // TODO(sky): use GURL methods for replacements once bug is fixed.
+ size_t scheme_end, host_end;
+ GetSchemeAndHostEnd(committed_entry->url(), &scheme_end, &host_end);
+ committed_entry->set_url(
+ GURL(committed_entry->url().spec().substr(0, host_end)));
+ }
+ std::string content_state;
+ state_->GetByteRepresentation(&content_state);
+ committed_entry->set_content_state(content_state);
+
+ // Broadcast the fact that we just updated all that crap.
+ controller()->NotifyEntryChanged(
+ committed_entry,
+ controller()->GetIndexOfEntry(committed_entry));
+ return true;
+}
+
+void NativeUIContents::Layout() {
+ if (current_view_) {
+ views::RootView* root_view = GetRootView();
+ current_view_->SetBounds(0, 0, root_view->width(),
+ root_view->height());
+ current_view_->Layout();
+ }
+}
+
+const std::wstring NativeUIContents::GetDefaultTitle() const {
+ if (current_ui_)
+ return current_ui_->GetTitle();
+ else
+ return std::wstring();
+}
+
+SkBitmap NativeUIContents::GetFavIcon() const {
+ int icon_id;
+
+ if (current_ui_)
+ icon_id = current_ui_->GetFavIconID();
+ else
+ icon_id = IDR_DEFAULT_FAVICON;
+
+ return *ResourceBundle::GetSharedInstance().GetBitmapNamed(icon_id);
+}
+
+void NativeUIContents::DidBecomeSelected() {
+ TabContents::DidBecomeSelected();
+ Layout();
+}
+
+void NativeUIContents::SetInitialFocus() {
+ if (!current_ui_ || !current_ui_->SetInitialFocus()) {
+ int tab_index;
+ Browser* browser = Browser::GetBrowserForController(
+ this->controller(), &tab_index);
+ if (browser)
+ browser->FocusLocationBar();
+ else
+ TabContents::SetInitialFocus(); // Will set focus to our HWND.
+ }
+}
+
+void NativeUIContents::SetIsLoading(bool is_loading,
+ LoadNotificationDetails* details) {
+ TabContents::SetIsLoading(is_loading, details);
+}
+
+// FocusTraversable Implementation
+views::View* NativeUIContents::FindNextFocusableView(
+ views::View* starting_view, bool reverse,
+ views::FocusTraversable::Direction direction, bool dont_loop,
+ views::FocusTraversable** focus_traversable,
+ views::View** focus_traversable_view) {
+ return GetRootView()->FindNextFocusableView(
+ starting_view, reverse, direction, dont_loop,
+ focus_traversable, focus_traversable_view);
+}
+
+//static
+std::string NativeUIContents::GetScheme() {
+ return kNativeUIContentsScheme;
+}
+
+//static
+void NativeUIContents::InitializeNativeUIFactories() {
+ RegisterNativeUIFactory(DownloadTabUI::GetURL(),
+ DownloadTabUI::GetNativeUIFactory());
+ RegisterNativeUIFactory(HistoryTabUI::GetURL(),
+ HistoryTabUI::GetNativeUIFactory());
+}
+
+// static
+std::string NativeUIContents::GetFactoryKey(const GURL& url) {
+ size_t scheme_end;
+ size_t host_end;
+ GetSchemeAndHostEnd(url, &scheme_end, &host_end);
+ return url.spec().substr(scheme_end + 2, host_end - scheme_end - 2);
+}
+
+typedef std::map<std::string, NativeUIFactory*> PathToFactoryMap;
+
+static PathToFactoryMap* g_path_to_factory = NULL;
+
+//static
+void NativeUIContents::RegisterNativeUIFactory(const GURL& url,
+ NativeUIFactory* factory) {
+ const std::string key = GetFactoryKey(url);
+
+ if (!g_path_to_factory)
+ g_path_to_factory = new PathToFactoryMap;
+
+ PathToFactoryMap::iterator i = g_path_to_factory->find(key);
+ if (i != g_path_to_factory->end()) {
+ delete i->second;
+ g_path_to_factory->erase(i);
+ }
+ (*g_path_to_factory)[key] = factory;
+}
+
+views::RootView* NativeUIContents::CreateRootView() {
+ return new NativeRootView(this);
+}
+
+//static
+NativeUI* NativeUIContents::InstantiateNativeUIForURL(
+ const GURL& url, NativeUIContents* contents) {
+ if (!g_path_to_factory)
+ return NULL;
+
+ const std::string key = GetFactoryKey(url);
+
+ NativeUIFactory* factory = (*g_path_to_factory)[key];
+ if (factory)
+ return factory->CreateNativeUIForURL(url, contents);
+ else
+ return NULL;
+}
+
+NativeUI* NativeUIContents::GetNativeUIForURL(const GURL& url) {
+ const std::string key = GetFactoryKey(url);
+
+ PathToUI::iterator i = path_to_native_uis_.find(key);
+ if (i != path_to_native_uis_.end())
+ return i->second;
+
+ NativeUI* ui = InstantiateNativeUIForURL(url, this);
+ if (ui)
+ path_to_native_uis_[key] = ui;
+ return ui;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Standard NativeUI background implementation.
+//
+////////////////////////////////////////////////////////////////////////////////
+NativeUIBackground::NativeUIBackground() {
+}
+
+NativeUIBackground::~NativeUIBackground() {
+}
+
+void NativeUIBackground::Paint(ChromeCanvas* canvas,
+ views::View* view) const {
+ static const SkColor kBackground = SkColorSetRGB(255, 255, 255);
+ canvas->FillRectInt(kBackground, 0, 0, view->width(), view->height());
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// SearchableUIBackground
+// A Background subclass to be used with SearchableUIContainer objects.
+// Paint() is overridden to do nothing here; the background of the bar is
+// painted in SearchableUIContainer::Paint. This class is necessary
+// only for native controls to be able to get query the background
+// brush.
+
+class SearchableUIBackground : public views::Background {
+ public:
+ explicit SearchableUIBackground(SkColor native_control_color) {
+ SetNativeControlColor(native_control_color);
+ }
+ virtual ~SearchableUIBackground() {};
+
+ // Empty implementation.
+ // The actual painting of the bar happens in SearchableUIContainer::Paint.
+ virtual void Paint(ChromeCanvas* canvas, views::View* view) const { }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(SearchableUIBackground);
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// SearchableUIContainer implementation.
+//
+/////////////////////////////////////////////////////////////////////////////
+
+SearchableUIContainer::SearchableUIContainer(
+ SearchableUIContainer::Delegate* delegate)
+ : delegate_(delegate),
+ search_field_(NULL),
+ title_link_(NULL),
+ title_image_(NULL),
+ scroll_view_(NULL) {
+ title_link_ = new views::Link;
+ ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
+ ChromeFont title_font(resource_bundle
+ .GetFont(ResourceBundle::WebFont).DeriveFont(2));
+ title_link_->SetFont(title_font);
+ title_link_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
+ title_link_->SetController(this);
+
+ title_image_ = new views::ImageView();
+ title_image_->SetVisible(false);
+
+ // Get the product logo
+ if (!kProductLogo) {
+ kProductLogo = resource_bundle.GetBitmapNamed(IDR_PRODUCT_LOGO);
+ }
+
+ product_logo_ = new views::ImageView();
+ product_logo_->SetVisible(true);
+ product_logo_->SetImage(*kProductLogo);
+ AddChildView(product_logo_);
+
+ search_field_ = new views::TextField;
+ search_field_->SetFont(ResourceBundle::GetSharedInstance().GetFont(
+ ResourceBundle::WebFont));
+ search_field_->SetController(this);
+
+ scroll_view_ = new views::ScrollView;
+ scroll_view_->set_background(
+ views::Background::CreateSolidBackground(kBackground));
+
+ // Set background class so that native controls can get a color.
+ set_background(new SearchableUIBackground(kBackground));
+
+ throbber_ = new views::SmoothedThrobber(50);
+
+ GridLayout* layout = new GridLayout(this);
+ // View owns the LayoutManager and will delete it along with all the columns
+ // we create here.
+ SetLayoutManager(layout);
+
+ search_button_ =
+ new views::NativeButton(std::wstring());
+ search_button_->SetFont(resource_bundle.GetFont(ResourceBundle::WebFont));
+ search_button_->SetListener(this);
+
+ // Set a background color for the search button. If SearchableUIContainer
+ // provided a background, then the search button could inherit that instead.
+ search_button_->set_background(new SearchableUIBackground(kBackground));
+
+ // For the first row (icon, title/text field, search button and throbber).
+ ColumnSet* column_set = layout->AddColumnSet(0);
+ column_set->AddPaddingColumn(0, kDestinationTitleOffset);
+
+ // Add the icon column.
+ column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
+ GridLayout::USE_PREF,
+ kDestinationSearchOffset - kDestinationTitleOffset -
+ kDestinationSmallerMargin,
+ kDestinationSearchOffset - kDestinationTitleOffset -
+ kDestinationSmallerMargin);
+ column_set->AddPaddingColumn(0, kDestinationSmallerMargin);
+
+ // Add the title/search field column.
+ column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
+ GridLayout::USE_PREF, kDestinationSearchWidth,
+ kDestinationSearchWidth);
+ column_set->AddPaddingColumn(0, kDestinationSmallerMargin);
+
+ // Add the search button column.
+ column_set->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0,
+ GridLayout::USE_PREF, 0, 0);
+ column_set->AddPaddingColumn(0, kDestinationSmallerMargin);
+
+ // Add the throbber column.
+ column_set->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0,
+ GridLayout::USE_PREF, 0, 0);
+
+ // For the scroll view.
+ column_set = layout->AddColumnSet(1);
+ column_set->AddPaddingColumn(0, 1);
+ column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
+ GridLayout::USE_PREF, 0, 0);
+
+ layout->AddPaddingRow(0, kDestinationSmallerMargin);
+ layout->StartRow(0, 0);
+ layout->AddView(title_image_, 1, 2);
+ layout->AddView(title_link_);
+
+ layout->StartRow(0, 0);
+ layout->SkipColumns(1);
+ layout->AddView(search_field_);
+ layout->AddView(search_button_);
+ layout->AddView(throbber_);
+
+ layout->AddPaddingRow(0, kDestinationSmallerMargin);
+ layout->StartRow(1, 1);
+ layout->AddView(scroll_view_);
+}
+
+SearchableUIContainer::~SearchableUIContainer() {
+}
+
+void SearchableUIContainer::SetContents(views::View* contents) {
+ // The column view will resize to accomodate long titles.
+ title_link_->SetText(delegate_->GetTitle());
+
+ int section_icon_id = delegate_->GetSectionIconID();
+ if (section_icon_id != 0) {
+ title_image_->SetImage(*ResourceBundle::GetSharedInstance().
+ GetBitmapNamed(section_icon_id));
+ title_image_->SetVisible(true);
+ }
+
+ search_button_->SetLabel(delegate_->GetSearchButtonText());
+ scroll_view_->SetContents(contents);
+}
+
+views::View* SearchableUIContainer::GetContents() {
+ return scroll_view_->GetContents();
+}
+
+void SearchableUIContainer::Layout() {
+ View::Layout();
+
+ gfx::Size search_button_size = search_button_->GetPreferredSize();
+ gfx::Size product_logo_size = product_logo_->GetPreferredSize();
+
+ int field_width = kDestinationSearchOffset +
+ kDestinationSearchWidth +
+ kDestinationSmallerMargin +
+ static_cast<int>(search_button_size.width()) +
+ kDestinationSmallerMargin;
+
+ product_logo_->SetBounds(std::max(width() - kProductLogo->width() -
+ kProductLogoPadding,
+ field_width),
+ kProductLogoPadding,
+ product_logo_size.width(),
+ product_logo_size.height());
+}
+
+void SearchableUIContainer::Paint(ChromeCanvas* canvas) {
+ SkColor top_color(kBackground);
+ canvas->FillRectInt(top_color, 0, 0,
+ width(), scroll_view_->y());
+
+ canvas->FillRectInt(kBottomMarginColor, 0, scroll_view_->y() -
+ kBottomMargin, width(), kBottomMargin);
+
+ canvas->FillRectInt(SkColorSetRGB(196, 196, 196),
+ 0, scroll_view_->y() - 1, width(), 1);
+}
+
+views::TextField* SearchableUIContainer::GetSearchField() const {
+ return search_field_;
+}
+
+views::ScrollView* SearchableUIContainer::GetScrollView() const {
+ return scroll_view_;
+}
+
+void SearchableUIContainer::SetSearchEnabled(bool enabled) {
+ search_field_->SetReadOnly(!enabled);
+ search_button_->SetEnabled(enabled);
+}
+
+void SearchableUIContainer::StartThrobber() {
+ throbber_->Start();
+}
+
+void SearchableUIContainer::StopThrobber() {
+ throbber_->Stop();
+}
+
+void SearchableUIContainer::ButtonPressed(views::NativeButton* sender) {
+ DoSearch();
+}
+
+void SearchableUIContainer::LinkActivated(views::Link *link,
+ int event_flags) {
+ if (link == title_link_) {
+ search_field_->SetText(std::wstring());
+ DoSearch();
+ }
+}
+
+void SearchableUIContainer::HandleKeystroke(views::TextField* sender,
+ UINT message,
+ TCHAR key,
+ UINT repeat_count,
+ UINT flags) {
+ if (key == VK_RETURN)
+ DoSearch();
+}
+
+void SearchableUIContainer::DoSearch() {
+ if (delegate_)
+ delegate_->DoSearch(search_field_->GetText());
+
+ scroll_view_->ScrollToPosition(scroll_view_->vertical_scroll_bar(), 0);
+}
+
diff --git a/chrome/browser/tab_contents/native_ui_contents.h b/chrome/browser/tab_contents/native_ui_contents.h
new file mode 100644
index 0000000..decf123
--- /dev/null
+++ b/chrome/browser/tab_contents/native_ui_contents.h
@@ -0,0 +1,295 @@
+// 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 CHROME_BROWSER_NATIVE_UI_CONTENTS_H__
+#define CHROME_BROWSER_NATIVE_UI_CONTENTS_H__
+
+#include "chrome/browser/page_state.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/views/background.h"
+#include "chrome/views/link.h"
+#include "chrome/views/native_button.h"
+#include "chrome/views/text_field.h"
+#include "chrome/views/widget_win.h"
+
+namespace views {
+class CheckBox;
+class FocusTraversable;
+class ImageView;
+class ScrollView;
+class Throbber;
+}
+
+class NativeUIFactory;
+class NativeUI;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// NativeUIContents
+//
+// NativeUIContents is a TabContents that is used to show some pages made with
+// some native user interface elements. NativeUIContents maintains a list of URL
+// path mapping to specific NativeUI implementations.
+//
+////////////////////////////////////////////////////////////////////////////////
+class NativeUIContents : public TabContents,
+ public views::WidgetWin {
+ public:
+ explicit NativeUIContents(Profile* profile);
+
+ virtual void CreateView();
+ virtual HWND GetContainerHWND() const { return GetHWND(); }
+ virtual void GetContainerBounds(gfx::Rect* out) const;
+
+ // Sets the page state. NativeUIContents takes ownership of the supplied
+ // PageState. Use a value of NULL to set the state to empty.
+ void SetPageState(PageState* page_state);
+
+ // Returns the page state. This is intended for UIs that want to store page
+ // state.
+ const PageState& page_state() const { return *state_; }
+
+ //
+ // TabContents implementation
+ //
+ virtual bool NavigateToPendingEntry(bool reload);
+ virtual const std::wstring GetDefaultTitle() const;
+ virtual SkBitmap GetFavIcon() const;
+ virtual bool ShouldDisplayURL() { return false; }
+ virtual bool ShouldDisplayFavIcon() { return true; }
+ virtual void DidBecomeSelected();
+ virtual void SetInitialFocus();
+
+ // Sets the current loading state. This is public for NativeUIs to update.
+ void SetIsLoading(bool is_loading, LoadNotificationDetails* details);
+
+ // FocusTraversable Implementation
+ virtual views::View* FindNextFocusableView(
+ views::View* starting_view,
+ bool reverse,
+ views::FocusTraversable::Direction direction,
+ bool dont_loop,
+ views::FocusTraversable** focus_traversable,
+ views::View** focus_traversable_view);
+ virtual views::RootView* GetContentsRootView() { return GetRootView(); }
+
+ // Return the scheme used. We currently use chrome-nativeui:
+ static std::string GetScheme();
+
+ // Register a NativeUIFactory for a given path.
+ static void RegisterNativeUIFactory(const GURL& url,
+ NativeUIFactory* factory);
+
+ protected:
+ // Should be deleted via CloseContents.
+ virtual ~NativeUIContents();
+
+ // Overridden to create a view that that handles drag and drop.
+ virtual views::RootView* CreateRootView();
+
+ private:
+ // Initialize the factories. This is called the first time a NativeUIContents
+ // object is created. If you add a new factory, you need to add a line in this
+ // method.
+ static void InitializeNativeUIFactories();
+
+ // Instantiates a native UI for the provided URL. This is done by using the
+ // native factories which have been registered.
+ static NativeUI* InstantiateNativeUIForURL(const GURL& url,
+ NativeUIContents* contents);
+
+ // Returns the key to use based on the TabUI's url.
+ static std::string GetFactoryKey(const GURL& url);
+
+ // Size the current UI if any.
+ void Layout();
+
+ // Return the Native UI for the provided URL. The NativeUIs are returned from
+ // a cache. Returns NULL if no such UI exists.
+ NativeUI* GetNativeUIForURL(const GURL& url);
+
+ // Windows message handlers.
+ virtual LRESULT OnCreate(LPCREATESTRUCT create_struct);
+ virtual void OnDestroy();
+ virtual void OnSize(UINT size_command, const CSize& new_size);
+ virtual void OnWindowPosChanged(WINDOWPOS* position);
+
+ // Whether this contents is visible.
+ bool is_visible_;
+
+ // Path to NativeUI map. We keep reusing the same UIs.
+ typedef std::map<std::string, NativeUI*> PathToUI;
+ PathToUI path_to_native_uis_;
+
+ // The current UI.
+ NativeUI* current_ui_;
+
+ // The current view for the current UI. We don't ask again just in case the
+ // UI implementation keeps allocating new uis.
+ views::View* current_view_;
+
+ // The current page state for the native contents.
+ scoped_ptr<PageState> state_;
+
+ // Whether factories have been initialized.
+ static bool g_ui_factories_initialized;
+
+ DISALLOW_EVIL_CONSTRUCTORS(NativeUIContents);
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// A native UI needs to implement the following interface to work with the
+// NativeUIContents.
+//
+/////////////////////////////////////////////////////////////////////////////
+class NativeUI {
+ public:
+ virtual ~NativeUI() {}
+
+ // Return the title for this user interface. The title is used as a tab title.
+ virtual const std::wstring GetTitle() const = 0;
+
+ // Return the favicon id for this user interface.
+ virtual const int GetFavIconID () const = 0;
+
+ // Return the view that should be used to render this user interface.
+ virtual views::View* GetView() = 0;
+
+ // Inform the view that it is about to become visible.
+ virtual void WillBecomeVisible(NativeUIContents* parent) = 0;
+
+ // Inform the view that it is about to become invisible.
+ virtual void WillBecomeInvisible(NativeUIContents* parent) = 0;
+
+ // Inform the view that it should recreate the provided state. The state
+ // should be updated as needed by using the current navigation entry of
+ // the provided tab contents.
+ virtual void Navigate(const PageState& state) = 0;
+
+ // Requests the contents set the initial focus. A return value of true
+ // indicates the contents wants focus and requested focus. A return value of
+ // false indicates the contents does not want focus, and that focus should
+ // go to the location bar.
+ virtual bool SetInitialFocus() = 0;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// NativeUIFactory defines the method necessary to instantiate a NativeUI
+// object. Typically, each NativeUI implementation registers an object that
+// can instantiate NativeUI objects given the necessary path.
+//
+/////////////////////////////////////////////////////////////////////////////
+class NativeUIFactory {
+ public:
+ virtual ~NativeUIFactory() {}
+
+ // Request the factory to instantiate a NativeUI object given the provided
+ // url. The url is a nativeui: URL which contains the path for which this
+ // factory was registered.
+ //
+ // See NativeUIContents::RegisterNativeUI().
+ virtual NativeUI* CreateNativeUIForURL(const GURL& url,
+ NativeUIContents* contents) = 0;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// A standard background for native UIs.
+//
+////////////////////////////////////////////////////////////////////////////////
+class NativeUIBackground : public views::Background {
+ public:
+ NativeUIBackground();
+ virtual ~NativeUIBackground();
+
+ virtual void Paint(ChromeCanvas* canvas, views::View* view) const;
+
+ private:
+
+ DISALLOW_EVIL_CONSTRUCTORS(NativeUIBackground);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// A view subclass used to implement native uis that feature a search field.
+// This view contains a search field and a ScrollView for the contents. It
+// implements a consistent look for these UIs.
+//
+////////////////////////////////////////////////////////////////////////////////
+class SearchableUIContainer : public views::View,
+ public views::NativeButton::Listener,
+ public views::LinkController,
+ public views::TextField::Controller {
+ public:
+ // The Delegate is notified when the user clicks the search button.
+ class Delegate {
+ public:
+ virtual void DoSearch(const std::wstring& text) = 0;
+ virtual const std::wstring GetTitle() const = 0;
+ virtual const int GetSectionIconID() const = 0;
+ virtual const std::wstring GetSearchButtonText() const = 0;
+ };
+
+ // Create a new SearchableUIContainer given a delegate.
+ explicit SearchableUIContainer(Delegate* delegate);
+
+ virtual ~SearchableUIContainer();
+
+ // Add the view as the contents of the container.
+ void SetContents(views::View* contents);
+ views::View* GetContents();
+
+ virtual void Layout();
+
+ // Overriden to paint the container.
+ virtual void Paint(ChromeCanvas* canvas);
+
+ // Provide the mode access to various UI elements.
+ views::TextField* GetSearchField() const;
+ views::ScrollView* GetScrollView() const;
+
+ // Enable/disable the search text-field/button.
+ void SetSearchEnabled(bool enabled);
+
+ // Start and stop the throbber.
+ void StartThrobber();
+ void StopThrobber();
+
+ private:
+ // Invoked when the user presses the search button.
+ virtual void ButtonPressed(views::NativeButton* sender);
+
+ // TextField method, does nothing.
+ virtual void ContentsChanged(views::TextField* sender,
+ const std::wstring& new_contents) {}
+
+ // Textfield method, if key is the return key the search is updated.
+ virtual void HandleKeystroke(views::TextField* sender,
+ UINT message,
+ TCHAR key,
+ UINT repeat_count,
+ UINT flags);
+
+ // Notifies the delegate to update the search.
+ void DoSearch();
+
+ void LinkActivated(views::Link* link, int event_flags);
+
+ Delegate* delegate_;
+ views::Link* title_link_;
+ views::ImageView* title_image_;
+ views::ImageView* product_logo_;
+ views::TextField* search_field_;
+ views::NativeButton* search_button_;
+ views::ScrollView* scroll_view_;
+ views::Throbber* throbber_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SearchableUIContainer);
+};
+
+#endif // CHROME_BROWSER_NATIVE_UI_CONTENTS_H__
+
diff --git a/chrome/browser/tab_contents/navigation_controller.cc b/chrome/browser/tab_contents/navigation_controller.cc
new file mode 100644
index 0000000..e9dd832
--- /dev/null
+++ b/chrome/browser/tab_contents/navigation_controller.cc
@@ -0,0 +1,1233 @@
+// 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 "chrome/browser/tab_contents/navigation_controller.h"
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/dom_ui/dom_ui_host.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/repost_form_warning_dialog.h"
+#include "chrome/browser/sessions/session_types.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/tab_contents/site_instance.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/tab_contents/tab_contents_delegate.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/navigation_types.h"
+#include "chrome/common/resource_bundle.h"
+#include "chrome/common/scoped_vector.h"
+#include "net/base/net_util.h"
+#include "webkit/glue/webkit_glue.h"
+
+namespace {
+
+// 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(
+ NOTIFY_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->tab_type() == TAB_CONTENTS_WEB ||
+ entry->tab_type() == TAB_CONTENTS_NEW_TAB_UI ||
+ entry->tab_type() == TAB_CONTENTS_ABOUT_UI ||
+ entry->tab_type() == TAB_CONTENTS_HTML_DIALOG ||
+ entry->tab_type() == TAB_CONTENTS_VIEW_SOURCE)) {
+ 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) {
+ 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_restored(true);
+ // 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())
+ return false;
+
+ url_canon::Replacements<char> replacements;
+ replacements.ClearRef();
+ return existing_url.ReplaceComponents(replacements) ==
+ new_url.ReplaceComponents(replacements);
+}
+
+} // namespace
+
+// TabContentsCollector ---------------------------------------------------
+
+// We never destroy a TabContents synchronously because there are some
+// complex code path that cause the current TabContents to be in the call
+// stack. So instead, we use a TabContentsCollector which either destroys
+// the TabContents or does nothing if it has been cancelled.
+class TabContentsCollector : public Task {
+ public:
+ TabContentsCollector(NavigationController* target,
+ TabContentsType target_type)
+ : target_(target),
+ target_type_(target_type) {
+ }
+
+ void Cancel() {
+ target_ = NULL;
+ }
+
+ virtual void Run() {
+ if (target_) {
+ // Note: this will cancel this task as a side effect so target_ is
+ // now null.
+ TabContents* tc = target_->GetTabContents(target_type_);
+ tc->Destroy();
+ }
+ }
+
+ private:
+ // The NavigationController we are acting on.
+ NavigationController* target_;
+
+ // The TabContentsType that needs to be collected.
+ TabContentsType target_type_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TabContentsCollector);
+};
+
+// NavigationController ---------------------------------------------------
+
+// static
+size_t NavigationController::max_entry_count_ = 50;
+
+// static
+bool NavigationController::check_for_repost_ = true;
+
+// Creates a new NavigationEntry for each TabNavigation in navigations, adding
+// the NavigationEntry to entries. This is used during session restore.
+static void 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) {
+ entries->push_back(
+ linked_ptr<NavigationEntry>(i->ToNavigationEntry(page_id)));
+ }
+}
+
+NavigationController::NavigationController(TabContents* contents,
+ Profile* profile)
+ : profile_(profile),
+ pending_entry_(NULL),
+ last_committed_entry_index_(-1),
+ pending_entry_index_(-1),
+ transient_entry_index_(-1),
+ active_contents_(contents),
+ max_restored_page_id_(-1),
+ ssl_manager_(this, NULL),
+ needs_reload_(false),
+ load_pending_entry_when_active_(false) {
+ if (contents)
+ RegisterTabContents(contents);
+ DCHECK(profile_);
+}
+
+NavigationController::NavigationController(
+ Profile* profile,
+ const std::vector<TabNavigation>& navigations,
+ int selected_navigation)
+ : profile_(profile),
+ pending_entry_(NULL),
+ last_committed_entry_index_(-1),
+ pending_entry_index_(-1),
+ transient_entry_index_(-1),
+ active_contents_(NULL),
+ max_restored_page_id_(-1),
+ ssl_manager_(this, NULL),
+ needs_reload_(true),
+ load_pending_entry_when_active_(false) {
+ DCHECK(profile_);
+ DCHECK(selected_navigation >= 0 &&
+ selected_navigation < static_cast<int>(navigations.size()));
+
+ // Populate entries_ from the supplied TabNavigations.
+ CreateNavigationEntriesFromTabNavigations(navigations, &entries_);
+
+ // And finish the restore.
+ FinishRestore(selected_navigation);
+}
+
+NavigationController::~NavigationController() {
+ DCHECK(tab_contents_map_.empty());
+ DCHECK(tab_contents_collector_map_.empty());
+
+ DiscardNonCommittedEntriesInternal();
+
+ NotificationService::current()->Notify(NOTIFY_TAB_CLOSED,
+ Source<NavigationController>(this),
+ NotificationService::NoDetails());
+}
+
+TabContents* NavigationController::GetTabContents(TabContentsType t) {
+ // Make sure the TabContents is no longer scheduled for collection.
+ CancelTabContentsCollection(t);
+ return tab_contents_map_[t];
+}
+
+void NavigationController::Reload(bool check_for_repost) {
+ // Reloading a transient entry does nothing.
+ if (transient_entry_index_ != -1)
+ return;
+
+ DiscardNonCommittedEntriesInternal();
+ int current_index = GetCurrentEntryIndex();
+ if (check_for_repost_ && check_for_repost && current_index != -1 &&
+ 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, RepostFormWarningDialog calls us
+ // back with ReloadDontCheckForRepost.
+ active_contents_->Activate();
+ RepostFormWarningDialog::RunRepostFormWarningDialog(this);
+ } else {
+ // Base the navigation on where we are now...
+ 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;
+
+ DiscardNonCommittedEntriesInternal();
+
+ pending_entry_index_ = current_index;
+ entries_[pending_entry_index_]->set_transition_type(PageTransition::RELOAD);
+ NavigateToPendingEntry(true);
+ }
+}
+
+NavigationEntry* NavigationController::GetEntryWithPageID(
+ TabContentsType type, SiteInstance* instance, int32 page_id) const {
+ int index = GetEntryIndexWithPageID(type, instance, page_id);
+ return (index != -1) ? entries_[index].get() : NULL;
+}
+
+void NavigationController::LoadEntry(NavigationEntry* entry) {
+ // 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(
+ NOTIFY_NAV_ENTRY_PENDING,
+ Source<NavigationController>(this),
+ NotificationService::NoDetails());
+ NavigateToPendingEntry(false);
+}
+
+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();
+}
+
+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 >= GetEntryCount())
+ 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;
+ }
+
+ // Base the navigation on where we are now...
+ int current_index = GetCurrentEntryIndex();
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = current_index - 1;
+ NavigateToPendingEntry(false);
+}
+
+void NavigationController::GoForward() {
+ if (!CanGoForward()) {
+ NOTREACHED();
+ return;
+ }
+
+ bool transient = (transient_entry_index_ != -1);
+
+ // Base the navigation on where we are now...
+ int current_index = GetCurrentEntryIndex();
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = current_index;
+ // If there was a transient entry, we removed it making the current index
+ // the next page.
+ if (!transient)
+ pending_entry_index_++;
+
+ NavigateToPendingEntry(false);
+}
+
+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--;
+ }
+ }
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = index;
+ NavigateToPendingEntry(false);
+}
+
+void NavigationController::GoToOffset(int offset) {
+ int index = (transient_entry_index_ != -1) ?
+ transient_entry_index_ + offset :
+ last_committed_entry_index_ + offset;
+ if (index < 0 || index >= GetEntryCount())
+ return;
+
+ GoToIndex(index);
+}
+
+void 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(false);
+ } 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::Destroy() {
+ // Close all tab contents owned by this controller. We make a list on the
+ // stack because they are removed from the map as they are Destroyed
+ // (invalidating the iterators), which may or may not occur synchronously.
+ // We also keep track of any NULL entries in the map so that we can clean
+ // them out.
+ std::list<TabContents*> tabs_to_destroy;
+ std::list<TabContentsType> tab_types_to_erase;
+ for (TabContentsMap::iterator i = tab_contents_map_.begin();
+ i != tab_contents_map_.end(); ++i) {
+ if (i->second)
+ tabs_to_destroy.push_back(i->second);
+ else
+ tab_types_to_erase.push_back(i->first);
+ }
+
+ // Clean out all NULL entries in the map so that we know empty map means all
+ // tabs destroyed. This is needed since TabContentsWasDestroyed() won't get
+ // called for types that are in our map with a NULL contents. (We don't do
+ // this by iterating over TAB_CONTENTS_NUM_TYPES because some tests create
+ // additional types.)
+ for (std::list<TabContentsType>::iterator i = tab_types_to_erase.begin();
+ i != tab_types_to_erase.end(); ++i) {
+ TabContentsMap::iterator map_iterator = tab_contents_map_.find(*i);
+ if (map_iterator != tab_contents_map_.end()) {
+ DCHECK(!map_iterator->second);
+ tab_contents_map_.erase(map_iterator);
+ }
+ }
+
+ // Cancel all the TabContentsCollectors.
+ for (TabContentsCollectorMap::iterator i =
+ tab_contents_collector_map_.begin();
+ i != tab_contents_collector_map_.end(); ++i) {
+ DCHECK(i->second);
+ i->second->Cancel();
+ }
+ tab_contents_collector_map_.clear();
+
+
+ // Finally destroy all the tab contents.
+ for (std::list<TabContents*>::iterator i = tabs_to_destroy.begin();
+ i != tabs_to_destroy.end(); ++i) {
+ (*i)->Destroy();
+ }
+ // We are deleted at this point.
+}
+
+void NavigationController::TabContentsWasDestroyed(TabContentsType type) {
+ TabContentsMap::iterator i = tab_contents_map_.find(type);
+ DCHECK(i != tab_contents_map_.end());
+ tab_contents_map_.erase(i);
+
+ // Make sure we cancel any collector for that TabContents.
+ CancelTabContentsCollection(type);
+
+ // If that was the last tab to be destroyed, delete ourselves.
+ if (tab_contents_map_.empty())
+ delete this;
+}
+
+NavigationEntry* NavigationController::CreateNavigationEntry(
+ const GURL& url, const GURL& referrer, PageTransition::Type transition) {
+ GURL real_url = url;
+ TabContentsType type;
+
+ // If the active contents supports |url|, use it.
+ // Note: in both cases, we give TabContents a chance to rewrite the URL.
+ TabContents* active = active_contents();
+ if (active && active->SupportsURL(&real_url))
+ type = active->type();
+ else
+ type = TabContents::TypeForURL(&real_url);
+
+ NavigationEntry* entry = new NavigationEntry(type, NULL, -1, real_url,
+ referrer,
+ std::wstring(), transition);
+ entry->set_display_url(url);
+ entry->set_user_typed_url(url);
+ if (url.SchemeIsFile()) {
+ entry->set_title(file_util::GetFilenameFromPath(UTF8ToWide(url.host() +
+ url.path())));
+ }
+ return entry;
+}
+
+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;
+ active_contents_->NotifyNavigationStateChanged(
+ TabContents::INVALIDATE_EVERYTHING);
+}
+
+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);
+
+ LoadEntry(entry);
+}
+
+void NavigationController::LoadURLLazily(const GURL& url,
+ const GURL& referrer,
+ PageTransition::Type type,
+ const std::wstring& title,
+ SkBitmap* icon) {
+ NavigationEntry* entry = CreateNavigationEntry(url, referrer, type);
+ entry->set_title(title);
+ if (icon)
+ entry->favicon().set_bitmap(*icon);
+
+ DiscardNonCommittedEntriesInternal();
+ pending_entry_ = entry;
+ load_pending_entry_when_active_ = true;
+}
+
+bool NavigationController::LoadingURLLazily() {
+ return load_pending_entry_when_active_;
+}
+
+const std::wstring& NavigationController::GetLazyTitle() const {
+ if (pending_entry_)
+ return pending_entry_->GetTitleForDisplay();
+ else
+ return EmptyWString();
+}
+
+const SkBitmap& NavigationController::GetLazyFavIcon() const {
+ if (pending_entry_) {
+ return pending_entry_->favicon().bitmap();
+ } else {
+ ResourceBundle &rb = ResourceBundle::GetSharedInstance();
+ return *rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
+ }
+}
+
+bool NavigationController::RendererDidNavigate(
+ const ViewHostMsg_FrameNavigate_Params& params,
+ LoadCommittedDetails* details) {
+ // Save the previous state before we clobber it.
+ if (GetLastCommittedEntry()) {
+ details->previous_url = GetLastCommittedEntry()->url();
+ details->previous_entry_index = GetLastCommittedEntryIndex();
+ } 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(active_contents_->GetSiteInstance());
+
+ // 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);
+ break;
+ case NavigationType::EXISTING_PAGE:
+ RendererDidNavigateToExistingPage(params);
+ break;
+ case NavigationType::SAME_PAGE:
+ RendererDidNavigateToSamePage(params);
+ break;
+ case NavigationType::IN_PAGE:
+ RendererDidNavigateInPage(params);
+ 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) &&
+ !GetPendingEntry()) ||
+ params.gesture == NavigationGestureAuto;
+
+ // Now prep the rest of the details for the notification and broadcast.
+ details->entry = GetActiveEntry();
+ details->is_in_page = IsURLInPageNavigation(params.url);
+ details->is_main_frame = PageTransition::IsMainFrame(params.transition);
+ details->serialized_security_info = params.security_info;
+ details->is_content_filtered = params.is_content_filtered;
+ NotifyNavigationEntryCommitted(details);
+
+ // It is now a safe time to schedule collection for any tab contents of a
+ // different type, because a navigation is necessary to get back to them.
+ ScheduleTabContentsCollectionForInactiveTabs();
+ return true;
+}
+
+NavigationType::Type NavigationController::ClassifyNavigation(
+ const ViewHostMsg_FrameNavigate_Params& params) const {
+ // If a page makes a popup navigated to about blank, and then writes stuff
+ // like a subframe navigated to a real site, we'll get a notification with an
+ // invalid page ID. There's nothing we can do with these, so just ignore them.
+ if (params.page_id == -1) {
+ DCHECK(!GetActiveEntry()) << "Got an invalid page ID but we seem to be "
+ " navigated to a valid page. This should be impossible.";
+ return NavigationType::NAV_IGNORE;
+ }
+
+ if (params.page_id > active_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(
+ active_contents_->type(),
+ active_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 (pending_entry_ &&
+ pending_entry_->url() == params.url &&
+ existing_entry != pending_entry_ &&
+ pending_entry_->page_id() == -1 &&
+ pending_entry_->url() == existing_entry->url()) {
+ // 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;
+ }
+
+ 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;
+ }
+
+ // 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;
+}
+
+void NavigationController::RendererDidNavigateToNewPage(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ 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(NavigationEntry::NORMAL_PAGE);
+ } else {
+ new_entry = new NavigationEntry(active_contents_->type());
+ }
+
+ new_entry->set_url(params.url);
+ new_entry->set_page_id(params.page_id);
+ new_entry->set_transition_type(params.transition);
+ new_entry->set_site_instance(active_contents_->GetSiteInstance());
+ new_entry->set_has_post_data(params.is_post);
+
+ InsertEntry(new_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(
+ active_contents_->type(),
+ active_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);
+ DCHECK(entry->site_instance() == NULL ||
+ entry->site_instance() == active_contents_->GetSiteInstance());
+ entry->set_site_instance(active_contents_->GetSiteInstance());
+
+ // 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();
+
+ last_committed_entry_index_ = entry_index;
+}
+
+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(
+ active_contents_->type(),
+ active_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());
+
+ DiscardNonCommittedEntries();
+}
+
+void NavigationController::RendererDidNavigateInPage(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ 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(
+ active_contents_->type(),
+ active_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);
+ new_entry->set_url(params.url);
+ InsertEntry(new_entry);
+}
+
+void NavigationController::RendererDidNavigateNewSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // 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);
+ InsertEntry(new_entry);
+}
+
+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(
+ active_contents_->type(),
+ active_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;
+}
+
+void NavigationController::CommitPendingEntry() {
+ DiscardTransientEntry();
+
+ if (!GetPendingEntry())
+ 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 = GetLastCommittedEntryIndex();
+ } 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 InsertEntry 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 WebContents, the IDs are generated by the
+ // renderer, so we can't do this.
+ details.type = NavigationType::NEW_PAGE;
+ pending_entry_->set_page_id(active_contents_->GetMaxPageID() + 1);
+ active_contents_->UpdateMaxPageID(pending_entry_->page_id());
+ InsertEntry(new NavigationEntry(*pending_entry_));
+ }
+
+ // 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);
+}
+
+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::DiscardNonCommittedEntries() {
+ bool transient = transient_entry_index_ != -1;
+ DiscardNonCommittedEntriesInternal();
+
+ // Synchronize the active_contents_ to the last committed entry.
+ NavigationEntry* last_entry = GetLastCommittedEntry();
+ if (last_entry && last_entry->tab_type() != active_contents_->type()) {
+ TabContents* from_contents = active_contents_;
+ from_contents->set_is_active(false);
+
+ // Switch back to the previous tab contents.
+ active_contents_ = GetTabContents(last_entry->tab_type());
+ DCHECK(active_contents_);
+
+ active_contents_->set_is_active(true);
+
+ // If we are transitioning from two types of WebContents, we need to migrate
+ // the download shelf if it is visible. The download shelf may have been
+ // created before the error that caused us to discard the entry.
+ WebContents::MigrateShelfView(from_contents, active_contents_);
+
+ if (from_contents->delegate()) {
+ from_contents->delegate()->ReplaceContents(from_contents,
+ active_contents_);
+ }
+
+ // The entry we just discarded needed a different TabContents type. We no
+ // longer need it but we can't destroy it just yet because the TabContents
+ // is very likely involved in the current stack.
+ DCHECK(from_contents != active_contents_);
+ ScheduleTabContentsCollection(from_contents->type());
+ }
+
+ // If there was a transient entry, invalidate everything so the new active
+ // entry state is shown.
+ if (transient) {
+ active_contents_->NotifyNavigationStateChanged(
+ TabContents::INVALIDATE_EVERYTHING);
+ }
+}
+
+void NavigationController::InsertEntry(NavigationEntry* entry) {
+ 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());
+
+ // Prune any entries which are in front of the current entry.
+ if (current_size > 0) {
+ int num_pruned = 0;
+ while (last_committed_entry_index_ < (current_size - 1)) {
+ num_pruned++;
+ entries_.pop_back();
+ current_size--;
+ }
+ if (num_pruned > 0) // Only notify if we did prune something.
+ NotifyPrunedEntries(this, false, num_pruned);
+ }
+
+ 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.
+ active_contents_->UpdateMaxPageID(entry->page_id());
+}
+
+void NavigationController::SetWindowID(const SessionID& id) {
+ window_id_ = id;
+ NotificationService::current()->Notify(NOTIFY_TAB_PARENTED,
+ Source<NavigationController>(this),
+ NotificationService::NoDetails());
+}
+
+void NavigationController::NavigateToPendingEntry(bool reload) {
+ TabContents* from_contents = active_contents_;
+
+ // For session history navigations only the pending_entry_index_ is set.
+ if (!pending_entry_) {
+ DCHECK(pending_entry_index_ != -1);
+ pending_entry_ = entries_[pending_entry_index_].get();
+ }
+
+ // Reset the security states as any SSL error may have been resolved since we
+ // last visited that page.
+ pending_entry_->ssl() = NavigationEntry::SSLStatus();
+
+ if (from_contents && from_contents->type() != pending_entry_->tab_type())
+ from_contents->set_is_active(false);
+
+ TabContents* contents = GetTabContentsCreateIfNecessary(*pending_entry_);
+
+ contents->set_is_active(true);
+ active_contents_ = contents;
+
+ if (from_contents && from_contents != contents) {
+ if (from_contents->delegate())
+ from_contents->delegate()->ReplaceContents(from_contents, contents);
+
+ if (from_contents->type() != contents->type()) {
+ // The entry we just discarded needed a different TabContents type. We no
+ // longer need it but we can't destroy it just yet because the TabContents
+ // is very likely involved in the current stack.
+ ScheduleTabContentsCollection(from_contents->type());
+ }
+ }
+
+ NavigationEntry temp_entry(*pending_entry_);
+ if (!contents->NavigateToPendingEntry(reload))
+ DiscardNonCommittedEntries();
+}
+
+void NavigationController::NotifyNavigationEntryCommitted(
+ LoadCommittedDetails* 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.
+ ssl_manager_.NavigationStateChanged();
+ active_contents_->NotifyNavigationStateChanged(
+ TabContents::INVALIDATE_EVERYTHING);
+
+ details->entry = GetActiveEntry();
+ NotificationService::current()->Notify(
+ NOTIFY_NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(this),
+ Details<LoadCommittedDetails>(details));
+}
+
+TabContents* NavigationController::GetTabContentsCreateIfNecessary(
+ const NavigationEntry& entry) {
+ TabContents* contents = GetTabContents(entry.tab_type());
+ if (!contents) {
+ contents = TabContents::CreateWithType(entry.tab_type(), profile_,
+ entry.site_instance());
+ if (!contents->AsWebContents()) {
+ // Update the max page id, otherwise the newly created TabContents may
+ // have reset its max page id resulting in all new navigations. We only
+ // do this for non-WebContents as WebContents takes care of this via its
+ // SiteInstance. If this creation is the result of a restore, WebContents
+ // handles invoking ReservePageIDRange to make sure the renderer's
+ // max_page_id is updated to reflect the restored range of page ids.
+ int32 max_page_id = contents->GetMaxPageID();
+ for (size_t i = 0; i < entries_.size(); ++i) {
+ if (entries_[i]->tab_type() == entry.tab_type())
+ max_page_id = std::max(max_page_id, entries_[i]->page_id());
+ }
+ contents->UpdateMaxPageID(max_page_id);
+ }
+ RegisterTabContents(contents);
+ }
+
+ // We should not be trying to collect this tab contents.
+ DCHECK(tab_contents_collector_map_.find(contents->type()) ==
+ tab_contents_collector_map_.end());
+
+ return contents;
+}
+
+void NavigationController::RegisterTabContents(TabContents* some_contents) {
+ DCHECK(some_contents);
+ TabContentsType t = some_contents->type();
+ TabContents* tc;
+ if ((tc = tab_contents_map_[t]) != some_contents) {
+ if (tc) {
+ NOTREACHED() << "Should not happen. Multiple contents for one type";
+ } else {
+ tab_contents_map_[t] = some_contents;
+ some_contents->set_controller(this);
+ }
+ }
+ if (some_contents->AsDOMUIHost())
+ some_contents->AsDOMUIHost()->AttachMessageHandlers();
+}
+
+// static
+void NavigationController::DisablePromptOnRepost() {
+ check_for_repost_ = false;
+}
+
+void NavigationController::SetActive(bool is_active) {
+ if (is_active) {
+ if (needs_reload_) {
+ LoadIfNecessary();
+ } else if (load_pending_entry_when_active_) {
+ NavigateToPendingEntry(false);
+ load_pending_entry_when_active_ = false;
+ }
+ }
+}
+
+void NavigationController::LoadIfNecessary() {
+ if (!needs_reload_)
+ return;
+
+ needs_reload_ = false;
+ // 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(false);
+}
+
+void NavigationController::NotifyEntryChanged(const NavigationEntry* entry,
+ int index) {
+ EntryChangedDetails det;
+ det.changed_entry = entry;
+ det.index = index;
+ NotificationService::current()->Notify(NOTIFY_NAV_ENTRY_CHANGED,
+ Source<NavigationController>(this),
+ Details<EntryChangedDetails>(&det));
+}
+
+NavigationController* NavigationController::Clone() {
+ NavigationController* nc = new NavigationController(NULL, profile_);
+
+ if (GetEntryCount() == 0)
+ return nc;
+
+ nc->needs_reload_ = true;
+
+ nc->entries_.reserve(entries_.size());
+ for (int i = 0, c = GetEntryCount(); i < c; ++i) {
+ nc->entries_.push_back(linked_ptr<NavigationEntry>(
+ new NavigationEntry(*GetEntryAtIndex(i))));
+ }
+
+ nc->FinishRestore(last_committed_entry_index_);
+
+ return nc;
+}
+
+void NavigationController::ScheduleTabContentsCollectionForInactiveTabs() {
+ int index = GetCurrentEntryIndex();
+ if (index < 0 || GetPendingEntryIndex() != -1)
+ return;
+
+ TabContentsType active_type = GetEntryAtIndex(index)->tab_type();
+ for (TabContentsMap::iterator i = tab_contents_map_.begin();
+ i != tab_contents_map_.end(); ++i) {
+ if (i->first != active_type)
+ ScheduleTabContentsCollection(i->first);
+ }
+}
+
+void NavigationController::ScheduleTabContentsCollection(TabContentsType t) {
+ TabContentsCollectorMap::const_iterator i =
+ tab_contents_collector_map_.find(t);
+
+ // The tab contents is already scheduled for collection.
+ if (i != tab_contents_collector_map_.end())
+ return;
+
+ // If we currently don't have a TabContents for t, skip.
+ if (tab_contents_map_.find(t) == tab_contents_map_.end())
+ return;
+
+ // Create a collector and schedule it.
+ TabContentsCollector* tcc = new TabContentsCollector(this, t);
+ tab_contents_collector_map_[t] = tcc;
+ MessageLoop::current()->PostTask(FROM_HERE, tcc);
+}
+
+void NavigationController::CancelTabContentsCollection(TabContentsType t) {
+ TabContentsCollectorMap::iterator i = tab_contents_collector_map_.find(t);
+
+ if (i != tab_contents_collector_map_.end()) {
+ DCHECK(i->second);
+ i->second->Cancel();
+ tab_contents_collector_map_.erase(i);
+ }
+}
+
+void NavigationController::FinishRestore(int selected_index) {
+ DCHECK(selected_index >= 0 && selected_index < GetEntryCount());
+ ConfigureEntriesForRestore(&entries_);
+
+ set_max_restored_page_id(GetEntryCount());
+
+ last_committed_entry_index_ = selected_index;
+
+ // Callers assume we have an active_contents after restoring, so set it now.
+ active_contents_ = GetTabContentsCreateIfNecessary(*entries_[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_ );
+ transient_entry_index_ = -1;
+}
+
+int NavigationController::GetEntryIndexWithPageID(
+ TabContentsType type, SiteInstance* instance, int32 page_id) const {
+ for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) {
+ if ((entries_[i]->tab_type() == type) &&
+ (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();
+}
diff --git a/chrome/browser/tab_contents/navigation_controller.h b/chrome/browser/tab_contents/navigation_controller.h
new file mode 100644
index 0000000..dac0a37
--- /dev/null
+++ b/chrome/browser/tab_contents/navigation_controller.h
@@ -0,0 +1,550 @@
+// 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 CHROME_BROWSER_NAVIGATION_CONTROLLER_H_
+#define CHROME_BROWSER_NAVIGATION_CONTROLLER_H_
+
+#include <map>
+
+#include "base/linked_ptr.h"
+#include "base/ref_counted.h"
+#include "chrome/browser/sessions/session_id.h"
+#include "chrome/browser/ssl_manager.h"
+#include "chrome/browser/tab_contents/site_instance.h"
+#include "chrome/browser/tab_contents/tab_contents_type.h"
+#include "chrome/common/navigation_types.h"
+
+class GURL;
+class Profile;
+class TabContents;
+class WebContents;
+class TabContentsCollector;
+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),
+ is_auto(false),
+ is_in_page(false),
+ is_main_frame(true) {
+ }
+
+ // 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 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;
+ }
+ };
+
+ // 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;
+ };
+
+ // ---------------------------------------------------------------------------
+
+ NavigationController(TabContents* initial_contents, Profile* profile);
+
+ // Creates a NavigationController from the specified history. Processing
+ // for this is asynchronous and handled via the RestoreHelper (in
+ // navigation_controller.cc).
+ NavigationController(
+ Profile* profile,
+ const std::vector<TabNavigation>& navigations,
+ int selected_navigation);
+ ~NavigationController();
+
+ // Begin the destruction sequence for this NavigationController and all its
+ // registered tabs. The sequence is as follows:
+ // 1. All tabs are asked to Destroy themselves.
+ // 2. When each tab is finished Destroying, it will notify the controller.
+ // 3. Once all tabs are Destroyed, the NavigationController deletes itself.
+ // This ensures that all the TabContents outlive the NavigationController.
+ void Destroy();
+
+ // Clone the receiving navigation controller. Only the active tab contents is
+ // duplicated.
+ NavigationController* Clone();
+
+ // Returns the profile for this controller. It can never be NULL.
+ Profile* profile() const {
+ return profile_;
+ }
+
+ // 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 the index of the last committed entry.
+ int GetLastCommittedEntryIndex() 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 GetEntryCount() 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 type, instance, and
+ // page_id, or -1 if not found. Use a NULL instance if the type is not
+ // TAB_CONTENTS_WEB.
+ int GetEntryIndexWithPageID(TabContentsType type,
+ SiteInstance* instance,
+ int32 page_id) const;
+
+ // Return the entry with the corresponding type, instance, and page_id, or
+ // NULL if not found. Use a NULL instance if the type is not
+ // TAB_CONTENTS_WEB.
+ NavigationEntry* GetEntryWithPageID(TabContentsType type,
+ 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. Calling this may cause
+ // the active tab contents to switch if the current entry corresponds to a
+ // different tab contents type.
+ void DiscardNonCommittedEntries();
+
+ // Returns the pending entry corresponding to the navigation that is
+ // currently in progress, or null if there is none.
+ NavigationEntry* GetPendingEntry() 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 GetPendingEntryIndex() 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);
+
+ // Load the specified URL the next time it becomes active.
+ void LoadURLLazily(const GURL& url, const GURL& referrer,
+ PageTransition::Type type, const std::wstring& title,
+ SkBitmap* icon);
+
+ // 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);
+
+ // 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 ---------------------------------------------------------------
+
+ // Notifies the controller that a TabContents that it owns has been destroyed.
+ // This is part of the NavigationController's Destroy sequence.
+ void TabContentsWasDestroyed(TabContentsType type);
+
+ // Returns the TabContents cached on this controller for the given type or
+ // NULL if there is none.
+ TabContents* GetTabContents(TabContentsType type);
+
+ // Returns the currently-active TabContents associated with this controller.
+ // You should use GetActiveEntry instead of this in most cases.
+ TabContents* active_contents() const {
+ return active_contents_;
+ }
+
+ // For use by TabContents ----------------------------------------------------
+
+ // Handles updating the navigation state after the renderer has navigated.
+ // This is used by the WebContents. 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.
+ bool RendererDidNavigate(const ViewHostMsg_FrameNavigate_Params& params,
+ 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;
+
+ // Random data ---------------------------------------------------------------
+
+ // Returns true if this NavigationController is is configured to load a URL
+ // lazily. If true, use GetLazyTitle() and GetLazyFavIcon() to discover the
+ // titles and favicons. Since no request was made, this is the only info
+ // we have about this page. This feature is used by web application clusters.
+ bool LoadingURLLazily();
+ const std::wstring& GetLazyTitle() const;
+ const SkBitmap& GetLazyFavIcon() const;
+
+ // 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_; }
+
+ // Returns the largest restored page ID seen in this navigation controller,
+ // if it was restored from a previous session. (-1 otherwise)
+ int max_restored_page_id() const { return max_restored_page_id_; }
+
+ // 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.
+ static void set_max_entry_count(size_t max_entry_count) {
+ max_entry_count_ = max_entry_count;
+ }
+ static size_t max_entry_count() { return max_entry_count_; }
+
+ 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.
+ void RendererDidNavigateToNewPage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateToExistingPage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateToSamePage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateInPage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateNewSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ bool RendererDidNavigateAutoSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Actually issues the navigation held in pending_entry.
+ void NavigateToPendingEntry(bool reload);
+
+ // Allows the derived class to issue notifications that a load has been
+ // committed. This will fill in the active entry to the details structure.
+ void NotifyNavigationEntryCommitted(LoadCommittedDetails* details);
+
+ // Returns the TabContents for the |entry|'s type. If the TabContents
+ // doesn't yet exist, it is created. If a new TabContents is created, its
+ // parent is |parent|. Becomes part of |entry|'s SiteInstance.
+ TabContents* GetTabContentsCreateIfNecessary(const NavigationEntry& entry);
+
+ // Register the provided tab contents. This tab contents will be owned
+ // and deleted by this navigation controller
+ void RegisterTabContents(TabContents* some_contents);
+
+ // Sets the max restored page ID this NavigationController has seen, if it
+ // was restored from a previous session.
+ void set_max_restored_page_id(int max_id) { max_restored_page_id_ = max_id; }
+
+ NavigationEntry* CreateNavigationEntry(const GURL& url, const GURL& referrer,
+ PageTransition::Type transition);
+
+ // Invokes ScheduleTabContentsCollection for all TabContents but the active
+ // one.
+ void ScheduleTabContentsCollectionForInactiveTabs();
+
+ // Schedule the TabContents currently allocated for |tc| for collection.
+ // The TabContents will be destroyed later from a different event.
+ void ScheduleTabContentsCollection(TabContentsType t);
+
+ // Cancel the collection of the TabContents allocated for |tc|. This method
+ // is used when we keep using a TabContents because a provisional load failed.
+ void CancelTabContentsCollection(TabContentsType t);
+
+ // 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.
+ void FinishRestore(int selected_index);
+
+ // Inserts an entry after the current position, removing all entries after it.
+ // The new entry will become the active one.
+ void InsertEntry(NavigationEntry* entry);
+
+ // Discards the pending and transient entries without updating
+ // active_contents_.
+ void DiscardNonCommittedEntriesInternal();
+
+ // Discards the transient entry without updating active_contents_.
+ void DiscardTransientEntry();
+
+ // ---------------------------------------------------------------------------
+
+ // 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 poiting to an entry
+ // after the transient entry will become invalid if you navigate forward.
+ int transient_entry_index_;
+
+ // Tab contents. One entry per type used. The tab controller owns
+ // every tab contents used.
+ typedef std::map<TabContentsType, TabContents*> TabContentsMap;
+ TabContentsMap tab_contents_map_;
+
+ // A map of TabContentsType -> TabContentsCollector containing all the
+ // pending collectors.
+ typedef base::hash_map<TabContentsType, TabContentsCollector*>
+ TabContentsCollectorMap;
+ TabContentsCollectorMap tab_contents_collector_map_;
+
+ // The tab contents that is currently active.
+ TabContents* active_contents_;
+
+ // The max restored page ID in this controller, if it was restored. We must
+ // store this so that WebContents can tell any renderer in charge of one of
+ // the restored entries to update its max page ID.
+ int max_restored_page_id_;
+
+ // Manages the SSL security UI
+ SSLManager ssl_manager_;
+
+ // Whether we need to be reloaded when made active.
+ bool needs_reload_;
+
+ // If true, the pending entry is lazy and should be loaded as soon as this
+ // controller becomes active.
+ bool load_pending_entry_when_active_;
+
+ // 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.
+ const SessionID session_id_;
+
+ // Unique identifier of the window we're in. Used by session restore.
+ SessionID window_id_;
+
+ // 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(NavigationController);
+};
+
+#endif // CHROME_BROWSER_NAVIGATION_CONTROLLER_H_
diff --git a/chrome/browser/tab_contents/navigation_entry.cc b/chrome/browser/tab_contents/navigation_entry.cc
new file mode 100644
index 0000000..bf0951c
--- /dev/null
+++ b/chrome/browser/tab_contents/navigation_entry.cc
@@ -0,0 +1,64 @@
+// 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 "chrome/browser/tab_contents/navigation_entry.h"
+
+#include "chrome/common/resource_bundle.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),
+ content_status_(NORMAL_CONTENT) {
+}
+
+NavigationEntry::FaviconStatus::FaviconStatus() : valid_(false) {
+ ResourceBundle &rb = ResourceBundle::GetSharedInstance();
+ bitmap_ = *rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
+}
+
+NavigationEntry::NavigationEntry(TabContentsType type)
+ : unique_id_(GetUniqueID()),
+ tab_type_(type),
+ site_instance_(NULL),
+ page_type_(NORMAL_PAGE),
+ page_id_(-1),
+ transition_type_(PageTransition::LINK),
+ has_post_data_(false),
+ restored_(false) {
+}
+
+NavigationEntry::NavigationEntry(TabContentsType type,
+ SiteInstance* instance,
+ int page_id,
+ const GURL& url,
+ const GURL& referrer,
+ const std::wstring& title,
+ PageTransition::Type transition_type)
+ : unique_id_(GetUniqueID()),
+ tab_type_(type),
+ site_instance_(instance),
+ page_type_(NORMAL_PAGE),
+ url_(url),
+ referrer_(referrer),
+ title_(title),
+ page_id_(page_id),
+ transition_type_(transition_type),
+ has_post_data_(false),
+ restored_(false) {
+}
+
+const std::wstring& NavigationEntry::GetTitleForDisplay() {
+ if (title_.empty())
+ return display_url_as_string_;
+ return title_;
+}
diff --git a/chrome/browser/tab_contents/navigation_entry.h b/chrome/browser/tab_contents/navigation_entry.h
new file mode 100644
index 0000000..3ffbfdb
--- /dev/null
+++ b/chrome/browser/tab_contents/navigation_entry.h
@@ -0,0 +1,399 @@
+// 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 CHROME_BROWSER_NAVIGATION_ENTRY_H_
+#define CHROME_BROWSER_NAVIGATION_ENTRY_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "chrome/app/theme/theme_resources.h"
+#include "chrome/browser/security_style.h"
+#include "chrome/browser/tab_contents/site_instance.h"
+#include "chrome/browser/tab_contents/tab_contents_type.h"
+#include "chrome/common/page_transition_types.h"
+#include "googleurl/src/gurl.h"
+#include "skia/include/SkBitmap.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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 {
+ NORMAL_CONTENT = 0, // Neither of the 2 cases below.
+ MIXED_CONTENT = 1 << 0, // https page containing http resources.
+ UNSAFE_CONTENT = 1 << 1 // https page containing broken https resources.
+ };
+
+ SSLStatus();
+
+ 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_;
+ }
+
+ // Mixed content means that this page which is served over https contains
+ // http sub-resources.
+ void set_has_mixed_content() {
+ content_status_ |= MIXED_CONTENT;
+ }
+ bool has_mixed_content() const {
+ return (content_status_ & MIXED_CONTENT) != 0;
+ }
+
+ // Unsafe content means that this page is served over https but contains
+ // https sub-resources with cert errors.
+ void set_has_unsafe_content() {
+ content_status_ |= UNSAFE_CONTENT;
+ }
+ bool has_unsafe_content() const {
+ return (content_status_ & UNSAFE_CONTENT) != 0;
+ }
+
+ // Raw accessors for all the content status flags. This contains a
+ // combination of any of the ContentStatusFlags defined above. It is used
+ // by the UI 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 content_status_;
+
+ // Copy and assignment is explicitly allowed for this class.
+ };
+
+ // The type of the page an entry corresponds to. Used by ui tests.
+ enum PageType {
+ NORMAL_PAGE = 0,
+ ERROR_PAGE,
+ INTERSTITIAL_PAGE
+ };
+
+ // 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.
+ };
+
+ // ---------------------------------------------------------------------------
+
+ explicit NavigationEntry(TabContentsType type);
+ NavigationEntry(TabContentsType type,
+ SiteInstance* instance,
+ int page_id,
+ const GURL& url,
+ const GURL& referrer,
+ const std::wstring& 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_;
+ }
+
+ // Return the TabContents type required to display this entry. Immutable
+ // because a tab can never change its type.
+ TabContentsType tab_type() const {
+ return tab_type_;
+ }
+
+ // 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) {
+ site_instance_ = 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 display_url() below for showing to
+ // the user.
+ void set_url(const GURL& url) {
+ url_ = url;
+ if (display_url_.is_empty()) {
+ // If there is no explicit display URL, then we'll display this URL.
+ display_url_as_string_ = UTF8ToWide(url_.spec());
+ }
+ }
+ 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 display 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.
+ //
+ // display_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_display_url(const GURL& url) {
+ display_url_ = (url == url_) ? GURL() : url;
+ display_url_as_string_ = UTF8ToWide(url.spec());
+ }
+ bool has_display_url() const {
+ return !display_url_.is_empty();
+ }
+ const GURL& display_url() const {
+ return display_url_.is_empty() ? url_ : display_url_;
+ }
+
+ // 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 std::wstring& title) {
+ title_ = title;
+ }
+ const std::wstring& 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 you write a custom TabContents and provide your own state make
+ // sure you have the ability to modify the format in the future while being
+ // 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_;
+ }
+
+ // 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_;
+ }
+
+ // Was this entry created from session/tab restore? If so this is true and
+ // gets set to false once we navigate to it.
+ // (See NavigationController::DidNavigateToEntry).
+ void set_restored(bool restored) {
+ restored_ = restored;
+ }
+ bool restored() const {
+ return restored_;
+ }
+
+ // Returns the title to be displayed on the tab. This could be the title of
+ // the page if it is available or the URL.
+ const std::wstring& GetTitleForDisplay();
+
+ 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_;
+ TabContentsType tab_type_;
+ scoped_refptr<SiteInstance> site_instance_;
+ PageType page_type_;
+ GURL url_;
+ GURL referrer_;
+
+ GURL display_url_;
+
+ // We cache a copy of the display URL as a string so we don't have to
+ // convert the display URL to a wide string every time we paint.
+ std::wstring display_url_as_string_;
+
+ std::wstring title_;
+ FaviconStatus favicon_;
+ std::string content_state_;
+ int32 page_id_;
+ SSLStatus ssl_;
+ PageTransition::Type transition_type_;
+ GURL user_typed_url_;
+ bool has_post_data_;
+ bool restored_;
+
+ // Copy and assignment is explicitly allowed for this class.
+};
+
+#endif // CHROME_BROWSER_NAVIGATION_ENTRY_H_
diff --git a/chrome/browser/tab_contents/network_status_view.cc b/chrome/browser/tab_contents/network_status_view.cc
new file mode 100644
index 0000000..2061ffe
--- /dev/null
+++ b/chrome/browser/tab_contents/network_status_view.cc
@@ -0,0 +1,320 @@
+// 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 "chrome/browser/tab_contents/network_status_view.h"
+
+#include <stdio.h>
+
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/tab_contents/tab_contents_delegate.h"
+#include "chrome/views/root_view.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace {
+const wchar_t kTitleMsg[] = L"Network Status";
+const wchar_t kStartTrackingMsg[] = L"Start I/O Tracking";
+const wchar_t kStopTrackingMsg[] = L"Stop I/O Tracking";
+
+const wchar_t kShowIOStatusMsg[] = L"Show Current I/O Status";
+const wchar_t kClearOutputMsg[] = L"Clear Output";
+
+// Returns a string representing the URL, handling the case where the spec
+// is invalid.
+std::wstring StringForURL(const GURL& url) {
+ if (url.is_valid())
+ return UTF8ToWide(url.spec());
+ return UTF8ToWide(url.possibly_invalid_spec()) + L" (invalid)";
+}
+
+std::wstring URLForJob(URLRequestJob* job) {
+ URLRequest* request = job->request();
+ if (request)
+ return StringForURL(request->url());
+ return std::wstring(L"(orphaned)");
+}
+
+} // namespace
+
+NetworkStatusView::NetworkStatusView()
+ : StatusView(TAB_CONTENTS_NETWORK_STATUS_VIEW) {
+ tracker_ = new JobTracker(this);
+}
+
+NetworkStatusView::~NetworkStatusView() {
+ if (monospaced_font_)
+ DeleteObject(monospaced_font_);
+
+ if (is_tracking_) {
+ tracker_->StopTracking();
+ is_tracking_ = false;
+ }
+
+ tracker_->DetachView();
+}
+
+const std::wstring NetworkStatusView::GetDefaultTitle() {
+ return kTitleMsg;
+}
+
+void NetworkStatusView::OnCreate(const CRect& rect) {
+ CreateButton(IDC_CONFIG_TRACKING_BUTTON, kStartTrackingMsg);
+ CreateButton(IDC_CURRENT_STATUS_BUTTON, kShowIOStatusMsg);
+ CreateButton(IDC_CLEAR, kClearOutputMsg);
+
+ is_tracking_ = false;
+
+ // Initialize the text box for network tracking
+ // Don't worry about the size, we'll resize when we get WM_SIZE
+ text_area_.Create(m_hWnd, const_cast<CRect&>(rect), NULL,
+ WS_CHILD | WS_HSCROLL | WS_VSCROLL |
+ ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL, 0);
+
+ // This raises the maximum number of chars from 32K to some large maximum,
+ // probably 2GB. 32K is not nearly enough for our use-case.
+ text_area_.SendMessageW(EM_SETLIMITTEXT, 0, 0);
+
+ // make a monospaced font for the edit control
+ LOGFONT lf = {0};
+ lf.lfHeight = 16;
+ wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Courier New");
+ monospaced_font_ = CreateFontIndirect(&lf);
+ text_area_.SetFont(monospaced_font_);
+}
+
+void NetworkStatusView::OnSize(const CRect& rect) {
+ // re-layout the edit control
+ text_area_.MoveWindow(rect);
+
+ // re-layout the performance view
+ CRect new_rect(rect);
+ int list_width = rect.Width();
+ int list_height = static_cast<int>(rect.Height() / 5);
+ int page_width = rect.Width() / 2;
+ int page_height = static_cast<int>(rect.Height() * 4 / 5);
+}
+
+void NetworkStatusView::OnConfigTrackingClicked(UINT code, int button_id,
+ HWND hwnd) {
+ if (is_tracking_) {
+ tracker_->StopTracking();
+ is_tracking_ = false;
+
+ SetButtonText(IDC_CONFIG_TRACKING_BUTTON, kStartTrackingMsg);
+ } else {
+ tracker_->StartTracking();
+ is_tracking_ = true;
+
+ ClearTrackingResults();
+ ShowTrackingResults();
+
+ SetButtonText(IDC_CONFIG_TRACKING_BUTTON, kStopTrackingMsg);
+ }
+}
+
+void NetworkStatusView::OnCurrentStatusClicked(UINT code, int button_id,
+ HWND hwnd) {
+ ShowTrackingResults();
+ if (is_tracking_) {
+ tracker_->ReportStatus();
+ }
+}
+
+void NetworkStatusView::OnClearClicked(UINT code, int button_id, HWND hwnd) {
+ ClearTrackingResults();
+}
+
+void NetworkStatusView::AppendText(const std::wstring& text) {
+ text_area_.AppendText(text.c_str());
+}
+
+void NetworkStatusView::HideTrackingResults() {
+ text_area_.ShowWindow(SW_HIDE);
+}
+
+void NetworkStatusView::ShowTrackingResults() {
+ text_area_.ShowWindow(SW_SHOW);
+}
+
+void NetworkStatusView::ClearTrackingResults() {
+ text_area_.SetSelAll();
+ text_area_.Clear();
+}
+
+//-----------------------------------------------------------------------------
+
+// main thread:
+NetworkStatusView::JobTracker::JobTracker(NetworkStatusView* view)
+ : view_(view),
+ view_message_loop_(MessageLoop::current()) {
+}
+
+// main thread:
+void NetworkStatusView::JobTracker::InvokeOnIOThread(void (JobTracker::*m)()) {
+ base::Thread* thread = g_browser_process->io_thread();
+ if (!thread)
+ return;
+ thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, m));
+}
+
+// main thread:
+void NetworkStatusView::JobTracker::StartTracking() {
+ DCHECK(MessageLoop::current() == view_message_loop_);
+ DCHECK(view_);
+ InvokeOnIOThread(&JobTracker::OnStartTracking);
+}
+
+// main thread:
+void NetworkStatusView::JobTracker::StopTracking() {
+ DCHECK(MessageLoop::current() == view_message_loop_);
+ // The tracker should not be deleted before it is removed from observer
+ // list.
+ AddRef();
+ InvokeOnIOThread(&JobTracker::OnStopTracking);
+}
+
+// main thread:
+void NetworkStatusView::JobTracker::ReportStatus() {
+ DCHECK(MessageLoop::current() == view_message_loop_);
+ InvokeOnIOThread(&JobTracker::OnReportStatus);
+}
+
+// main thread:
+void NetworkStatusView::JobTracker::OnAppendText(const std::wstring& text) {
+ DCHECK(MessageLoop::current() == view_message_loop_);
+ if (view_ && view_->is_tracking_)
+ view_->AppendText(text);
+}
+
+// IO thread:
+void NetworkStatusView::JobTracker::AppendText(const std::wstring& text) {
+ DCHECK(MessageLoop::current() != view_message_loop_);
+ view_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &JobTracker::OnAppendText, text));
+}
+
+// IO thread:
+void NetworkStatusView::JobTracker::OnStartTracking() {
+ DCHECK(MessageLoop::current() != view_message_loop_);
+ g_url_request_job_tracker.AddObserver(this);
+}
+
+// IO thread:
+void NetworkStatusView::JobTracker::OnStopTracking() {
+ DCHECK(MessageLoop::current() != view_message_loop_);
+ g_url_request_job_tracker.RemoveObserver(this);
+ // Balance the AddRef() in StopTracking() called in main thread.
+ Release();
+}
+
+// IO thread:
+void NetworkStatusView::JobTracker::OnReportStatus() {
+ DCHECK(MessageLoop::current() != view_message_loop_);
+
+ std::wstring text(L"\r\n===== Active Job Summary =====\r\n");
+
+ URLRequestJobTracker::JobIterator begin_job =
+ g_url_request_job_tracker.begin();
+ URLRequestJobTracker::JobIterator end_job = g_url_request_job_tracker.end();
+ int orphaned_count = 0;
+ int regular_count = 0;
+ for (URLRequestJobTracker::JobIterator cur = begin_job;
+ cur != end_job; ++cur) {
+ URLRequestJob* job = (*cur);
+ URLRequest* request = job->request();
+ if (!request) {
+ orphaned_count++;
+ continue;
+ }
+
+ regular_count++;
+
+ // active state
+ if (job->is_done())
+ text.append(L" Done: ");
+ else
+ text.append(L" Active: ");
+
+ // URL
+ text.append(StringForURL(request->url()));
+ text.append(L"\r\n");
+ }
+
+ if (regular_count == 0)
+ text.append(L" (No active jobs)\r\n");
+
+ if (orphaned_count) {
+ wchar_t buf[64];
+ swprintf(buf, arraysize(buf), L" %d orphaned jobs\r\n", orphaned_count);
+ text.append(buf);
+ }
+
+ text.append(L"=====\r\n\r\n");
+ AppendText(text);
+}
+
+// IO thread:
+void NetworkStatusView::JobTracker::OnJobAdded(URLRequestJob* job) {
+ DCHECK(MessageLoop::current() != view_message_loop_);
+
+ std::wstring text(L"+ New job : ");
+ text.append(URLForJob(job));
+ text.append(L"\r\n");
+ AppendText(text);
+}
+
+// IO thread:
+void NetworkStatusView::JobTracker::OnJobRemoved(URLRequestJob* job) {
+ DCHECK(MessageLoop::current() != view_message_loop_);
+}
+
+// IO thread:
+void NetworkStatusView::JobTracker::OnJobDone(URLRequestJob* job,
+ const URLRequestStatus& status) {
+ DCHECK(MessageLoop::current() != view_message_loop_);
+
+ std::wstring text;
+ if (status.is_success()) {
+ text.assign(L"- Complete: ");
+ } else if (status.status() == URLRequestStatus::CANCELED) {
+ text.assign(L"- Canceled: ");
+ } else if (status.status() == URLRequestStatus::HANDLED_EXTERNALLY) {
+ text.assign(L"- Handled externally: ");
+ } else {
+ wchar_t buf[32];
+ swprintf(buf, arraysize(buf), L"Failed with %d: ", status.os_error());
+ text.assign(buf);
+ }
+
+ text.append(URLForJob(job));
+ text.append(L"\r\n");
+ AppendText(text);
+}
+
+// IO thread:
+void NetworkStatusView::JobTracker::OnJobRedirect(URLRequestJob* job,
+ const GURL& location,
+ int status_code) {
+ DCHECK(MessageLoop::current() != view_message_loop_);
+
+ std::wstring text(L"- Redirect: ");
+ text.append(URLForJob(job));
+ text.append(L"\r\n ");
+
+ wchar_t buf[16];
+ swprintf(buf, arraysize(buf), L"(%d) to: ", status_code);
+ text.append(buf);
+
+ text.append(StringForURL(location));
+ text.append(L"\r\n");
+ AppendText(text);
+}
+
+void NetworkStatusView::JobTracker::OnBytesRead(URLRequestJob* job,
+ int byte_count) {
+}
+
diff --git a/chrome/browser/tab_contents/network_status_view.h b/chrome/browser/tab_contents/network_status_view.h
new file mode 100644
index 0000000..5d6f53a9
--- /dev/null
+++ b/chrome/browser/tab_contents/network_status_view.h
@@ -0,0 +1,117 @@
+// 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 CHROME_BROWSER_NETWORK_STATUS_VIEW_H__
+#define CHROME_BROWSER_NETWORK_STATUS_VIEW_H__
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/browser/tab_contents/status_view.h"
+#include "net/url_request/url_request_job_tracker.h"
+
+class MessageLoop;
+class RenderProcessHost;
+class NavigationPerformanceViewer;
+class PageLoadView;
+
+class NetworkStatusView : public StatusView {
+ public:
+ // button types
+ enum {
+ IDC_CONFIG_TRACKING_BUTTON = 101,
+ IDC_CURRENT_STATUS_BUTTON,
+ IDC_CLEAR,
+ };
+
+ NetworkStatusView();
+ virtual ~NetworkStatusView();
+
+ // TabContents overrides
+ virtual const std::wstring GetDefaultTitle();
+
+ // StatusView implementation
+ virtual void OnCreate(const CRect& rect);
+ virtual void OnSize(const CRect& rect);
+
+ BEGIN_MSG_MAP(NetworkStatusView)
+ COMMAND_HANDLER_EX(IDC_CONFIG_TRACKING_BUTTON, BN_CLICKED, OnConfigTrackingClicked)
+ COMMAND_HANDLER_EX(IDC_CURRENT_STATUS_BUTTON, BN_CLICKED, OnCurrentStatusClicked)
+ COMMAND_HANDLER_EX(IDC_CLEAR, BN_CLICKED, OnClearClicked)
+ CHAIN_MSG_MAP(StatusView);
+ END_MSG_MAP()
+
+ bool is_tracking() const { return is_tracking_; }
+
+ private:
+
+ // Event handlers
+ void OnConfigTrackingClicked(UINT code, int button_id, HWND hwnd);
+ void OnCurrentStatusClicked(UINT code, int button_id, HWND hwnd);
+ void OnClearClicked(UINT code, int button_id, HWND hwnd);
+
+ void AppendText(const std::wstring& text);
+
+ // Hide/Show tracking output window
+ void HideTrackingResults();
+ void ShowTrackingResults();
+
+ // Clear tracking output
+ void ClearTrackingResults();
+
+ // A JobTracker is allocated to monitor network jobs running on the IO
+ // thread. This allows the NetworkStatusView to remain single-threaded.
+ class JobTracker : public URLRequestJobTracker::JobObserver,
+ public base::RefCountedThreadSafe<JobTracker> {
+ public:
+ JobTracker(NetworkStatusView* view);
+
+ // Called by the NetworkStatusView on the main application thread.
+ void StartTracking();
+ void StopTracking();
+ void ReportStatus();
+
+ // URLRequestJobTracker::JobObserver methods (called on the IO thread):
+ virtual void OnJobAdded(URLRequestJob* job);
+ virtual void OnJobRemoved(URLRequestJob* job);
+ virtual void OnJobDone(URLRequestJob* job, const URLRequestStatus& status);
+ virtual void OnJobRedirect(URLRequestJob* job, const GURL& location,
+ int status_code);
+ virtual void OnBytesRead(URLRequestJob* job, int byte_count);
+
+ // The JobTracker may be deleted after NetworkStatusView is deleted.
+ void DetachView() { view_ = NULL; }
+
+ private:
+ void InvokeOnIOThread(void (JobTracker::*method)());
+
+ // Called on the IO thread
+ void OnStartTracking();
+ void OnStopTracking();
+ void OnReportStatus();
+ void AppendText(const std::wstring& text);
+
+ // Called on the main thread
+ void OnAppendText(const std::wstring& text);
+
+ NetworkStatusView* view_;
+ MessageLoop* view_message_loop_;
+ };
+ friend class JobTracker;
+
+ scoped_refptr<JobTracker> tracker_;
+
+ bool is_tracking_;
+
+ // Textual output of network tracking
+ CEdit text_area_;
+
+ HFONT monospaced_font_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkStatusView);
+};
+
+#endif // #ifndef CHROME_BROWSER_NETWORK_STATUS_VIEW_H__
+
diff --git a/chrome/browser/tab_contents/page_navigator.h b/chrome/browser/tab_contents/page_navigator.h
new file mode 100644
index 0000000..0152d39
--- /dev/null
+++ b/chrome/browser/tab_contents/page_navigator.h
@@ -0,0 +1,27 @@
+// 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 CHROME_BROWSER_PAGE_NAVIGATOR_H__
+#define CHROME_BROWSER_PAGE_NAVIGATOR_H__
+
+#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;
+};
+
+#endif // CHROME_BROWSER_PAGE_NAVIGATOR_H__
+
diff --git a/chrome/browser/tab_contents/provisional_load_details.cc b/chrome/browser/tab_contents/provisional_load_details.cc
new file mode 100644
index 0000000..1ec1206
--- /dev/null
+++ b/chrome/browser/tab_contents/provisional_load_details.cc
@@ -0,0 +1,25 @@
+// 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 "chrome/browser/tab_contents/provisional_load_details.h"
+
+#include "chrome/browser/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)
+ : error_code_(net::OK),
+ url_(url),
+ is_main_frame_(is_main_frame),
+ is_in_page_navigation_(is_in_page_navigation),
+ is_content_filtered_(is_content_filtered) {
+ SSLManager::DeserializeSecurityInfo(security_info,
+ &ssl_cert_id_,
+ &ssl_cert_status_,
+ &ssl_security_bits_);
+}
+
diff --git a/chrome/browser/tab_contents/provisional_load_details.h b/chrome/browser/tab_contents/provisional_load_details.h
new file mode 100644
index 0000000..5cb9501
--- /dev/null
+++ b/chrome/browser/tab_contents/provisional_load_details.h
@@ -0,0 +1,60 @@
+// 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 CHROME_BROWSER_PROVISIONAL_LOAD_DETAILS_H_
+#define CHROME_BROWSER_PROVISIONAL_LOAD_DETAILS_H_
+
+#include "base/basictypes.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);
+ virtual ~ProvisionalLoadDetails() { }
+
+ void set_error_code(int error_code) { error_code_ = error_code; };
+ int error_code() const { return error_code_; }
+
+ 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_; }
+
+ bool is_content_filtered() const { return is_content_filtered_; }
+
+ private:
+ int error_code_;
+ GURL url_;
+ bool is_main_frame_;
+ bool is_in_page_navigation_;
+ int ssl_cert_id_;
+ int ssl_cert_status_;
+ int ssl_security_bits_;
+ bool is_content_filtered_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProvisionalLoadDetails);
+};
+
+#endif // CHROME_BROWSER_PROVISIONAL_LOAD_DETAILS_H_
+
diff --git a/chrome/browser/tab_contents/site_instance.cc b/chrome/browser/tab_contents/site_instance.cc
new file mode 100644
index 0000000..a303f85
--- /dev/null
+++ b/chrome/browser/tab_contents/site_instance.cc
@@ -0,0 +1,145 @@
+// 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 "chrome/browser/tab_contents/site_instance.h"
+
+#include "net/base/registry_controlled_domain.h"
+
+SiteInstance::~SiteInstance() {
+ // Now that no one is referencing us, we can safely remove ourselves from
+ // the BrowsingInstance. Any future visits to a page from this site
+ // (within the same BrowsingInstance) can safely create a new SiteInstance.
+ if (has_site_)
+ browsing_instance_->UnregisterSiteInstance(this);
+}
+
+RenderProcessHost* SiteInstance::GetProcess() {
+ RenderProcessHost* process = NULL;
+ if (process_host_id_ != -1)
+ process = RenderProcessHost::FromID(process_host_id_);
+
+ // Create a new process if ours went away or was reused.
+ if (!process) {
+ // See if we should reuse an old process
+ if (RenderProcessHost::ShouldTryToUseExistingProcessHost())
+ process = RenderProcessHost::GetExistingProcessHost(
+ browsing_instance_->profile());
+
+ // Otherwise (or if that fails), create a new one.
+ if (!process)
+ process = new RenderProcessHost(browsing_instance_->profile());
+
+ // Update our host ID, so all pages in this SiteInstance will use
+ // the correct process.
+ process_host_id_ = process->host_id();
+
+ // Make sure the process starts at the right max_page_id
+ process->UpdateMaxPageID(max_page_id_);
+ }
+ DCHECK(process);
+
+ return process;
+}
+
+void SiteInstance::SetSite(const GURL& url) {
+ // A SiteInstance's site should not change.
+ // TODO(creis): When following links or script navigations, we can currently
+ // render pages from other sites in this SiteInstance. This will eventually
+ // be fixed, but until then, we should still not set the site of a
+ // SiteInstance more than once.
+ DCHECK(!has_site_);
+
+ // Remember that this SiteInstance has been used to load a URL, even if the
+ // URL is invalid.
+ has_site_ = true;
+ site_ = GetSiteForURL(url);
+
+ // Now that we have a site, register it with the BrowsingInstance. This
+ // ensures that we won't create another SiteInstance for this site within
+ // the same BrowsingInstance, because all same-site pages within a
+ // BrowsingInstance can script each other.
+ browsing_instance_->RegisterSiteInstance(this);
+}
+
+bool SiteInstance::HasRelatedSiteInstance(const GURL& url) {
+ return browsing_instance_->HasSiteInstance(url);
+}
+
+SiteInstance* SiteInstance::GetRelatedSiteInstance(const GURL& url) {
+ return browsing_instance_->GetSiteInstanceForURL(url);
+}
+
+/*static*/
+SiteInstance* SiteInstance::CreateSiteInstance(Profile* profile) {
+ return new SiteInstance(new BrowsingInstance(profile));
+}
+
+/*static*/
+GURL SiteInstance::GetSiteForURL(const GURL& url) {
+ // URLs with no host should have an empty site.
+ GURL site;
+
+ // TODO(creis): For many protocols, we should just treat the scheme as the
+ // site, since there is no host. e.g., file:, about:, chrome:
+
+ // If the url has a host, then determine the site.
+ if (url.has_host()) {
+ // Only keep the scheme and registered domain as given by GetOrigin. This
+ // may also include a port, which we need to drop.
+ site = url.GetOrigin();
+
+ // Remove port, if any.
+ if (site.has_port()) {
+ GURL::Replacements rep;
+ rep.ClearPort();
+ site = site.ReplaceComponents(rep);
+ }
+
+ // If this URL has a registered domain, we only want to remember that part.
+ std::string domain =
+ net::RegistryControlledDomainService::GetDomainAndRegistry(url);
+ if (!domain.empty()) {
+ GURL::Replacements rep;
+ rep.SetHostStr(domain);
+ site = site.ReplaceComponents(rep);
+ }
+ }
+ return site;
+}
+
+/*static*/
+bool SiteInstance::IsSameWebSite(const GURL& url1, const GURL& url2) {
+ // We infer web site boundaries based on the registered domain name of the
+ // top-level page and the scheme. We do not pay attention to the port if
+ // one is present, because pages served from different ports can still
+ // access each other if they change their document.domain variable.
+
+ // We must treat javascript: URLs as part of the same site, regardless of
+ // the site.
+ if (url1.SchemeIs("javascript") || url2.SchemeIs("javascript"))
+ return true;
+
+ // We treat about:crash, about:hang, and about:shorthang as the same site as
+ // any URL, since they are used as demos for crashing/hanging a process.
+ GURL about_crash = GURL("about:crash");
+ GURL about_hang = GURL("about:hang");
+ GURL about_shorthang = GURL("about:shorthang");
+ if (url1 == about_crash || url2 == about_crash ||
+ url1 == about_hang || url2 == about_hang ||
+ url1 == about_shorthang || url2 == about_shorthang)
+ return true;
+
+ // If either URL is invalid, they aren't part of the same site.
+ if (!url1.is_valid() || !url2.is_valid()) {
+ return false;
+ }
+
+ // If the schemes differ, they aren't part of the same site.
+ if (url1.scheme() != url2.scheme()) {
+ return false;
+ }
+
+ return net::RegistryControlledDomainService::SameDomainOrHost(url1, url2);
+}
+
diff --git a/chrome/browser/tab_contents/site_instance.h b/chrome/browser/tab_contents/site_instance.h
new file mode 100644
index 0000000..6759fe3
--- /dev/null
+++ b/chrome/browser/tab_contents/site_instance.h
@@ -0,0 +1,156 @@
+// 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 CHROME_BROWSER_SITE_INSTANCE_H__
+#define CHROME_BROWSER_SITE_INSTANCE_H__
+
+#include "chrome/browser/browsing_instance.h"
+#include "chrome/browser/render_process_host.h"
+#include "googleurl/src/gurl.h"
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// SiteInstance class
+//
+// A SiteInstance is a data structure that is associated with all pages in a
+// given instance of a web site. Here, a web site is identified by its
+// registered domain name and scheme. An instance includes all pages
+// that are connected (i.e., either a user or a script navigated from one
+// to the other). We represent instances using the BrowsingInstance class.
+//
+// In --process-per-tab, one SiteInstance is created for each tab (i.e., in the
+// WebContents constructor), unless the tab is created by script (i.e., in
+// WebContents::CreateNewView). This corresponds to one process per
+// BrowsingInstance.
+//
+// In process-per-site-instance (the current default process model),
+// SiteInstances are created (1) when the user manually creates a new tab
+// (which also creates a new BrowsingInstance), and (2) when the user navigates
+// across site boundaries (which uses the same BrowsingInstance). If the user
+// navigates within a site, or opens links in new tabs within a site, the same
+// SiteInstance is used.
+//
+// In --process-per-site, we consolidate all SiteInstances for a given site,
+// throughout the entire profile. This ensures that only one process will be
+// dedicated to each site.
+//
+// Each NavigationEntry for a WebContents points to the SiteInstance that
+// rendered it. Each RenderViewHost also points to the SiteInstance that it is
+// associated with. A SiteInstance keeps track of the number of these
+// references and deletes itself when the count goes to zero. This means that
+// a SiteInstance is only live as long as it is accessible, either from new
+// tabs with no NavigationEntries or in NavigationEntries in the history.
+//
+///////////////////////////////////////////////////////////////////////////////
+class SiteInstance : public base::RefCounted<SiteInstance> {
+ public:
+ // Virtual to allow tests to extend it.
+ virtual ~SiteInstance();
+
+ // Get the BrowsingInstance to which this SiteInstance belongs.
+ BrowsingInstance* browsing_instance() { return browsing_instance_; }
+
+ // Set / Get the host ID for this SiteInstance's current RenderProcessHost.
+ void set_process_host_id(int process_host_id) {
+ process_host_id_ = process_host_id;
+ }
+ int process_host_id() const { return process_host_id_; }
+
+ // Update / Get the max page ID for this SiteInstance.
+ void UpdateMaxPageID(int32 page_id) {
+ if (page_id > max_page_id_)
+ max_page_id_ = page_id;
+ }
+ int32 max_page_id() const { return max_page_id_; }
+
+ // Returns the current process being used to render pages in this
+ // SiteInstance. If the process has crashed or otherwise gone away, then
+ // this method will create a new process and update our host ID accordingly.
+ RenderProcessHost* GetProcess();
+
+ // Set / Get the web site that this SiteInstance is rendering pages for.
+ // This includes the scheme and registered domain, but not the port. If the
+ // URL does not have a valid registered domain, then the full hostname is
+ // stored.
+ void SetSite(const GURL& url);
+ const GURL& site() const { return site_; }
+ bool has_site() const { return has_site_; }
+
+ // Returns whether there is currently a related SiteInstance (registered with
+ // BrowsingInstance) for the site of the given url. If so, we should try to
+ // avoid dedicating an unused SiteInstance to it (e.g., in a new tab).
+ bool HasRelatedSiteInstance(const GURL& url);
+
+ // Gets a SiteInstance for the given URL that shares the current
+ // BrowsingInstance, creating a new SiteInstance if necessary. This ensures
+ // that a BrowsingInstance only has one SiteInstance per site, so that pages
+ // in a BrowsingInstance have the ability to script each other. Callers
+ // should ensure that this SiteInstance becomes ref counted, by storing it in
+ // a scoped_refptr. (By having this method, we can hide the BrowsingInstance
+ // class from the rest of the codebase.)
+ // TODO(creis): This may be an argument to build a pass_refptr<T> class, as
+ // Darin suggests.
+ SiteInstance* GetRelatedSiteInstance(const GURL& url);
+
+ // Factory method to create a new SiteInstance. This will create a new
+ // new BrowsingInstance, so it should only be used when creating a new tab
+ // from scratch (or similar circumstances). Callers should ensure that
+ // this SiteInstance becomes ref counted, by storing it in a scoped_refptr.
+ // TODO(creis): This may be an argument to build a pass_refptr<T> class, as
+ // Darin suggests.
+ static SiteInstance* CreateSiteInstance(Profile* profile);
+
+ // Returns the site for the given URL, which includes only the scheme and
+ // registered domain. Returns an empty GURL if the URL has no host.
+ static GURL GetSiteForURL(const GURL& url);
+
+ // Return whether both URLs are part of the same web site, for the purpose of
+ // assigning them to processes accordingly. The decision is currently based
+ // on the registered domain of the URLs (google.com, bbc.co.uk), as well as
+ // the scheme (https, http). This ensures that two pages will be in
+ // the same process if they can communicate with other via JavaScript.
+ // (e.g., docs.google.com and mail.google.com have DOM access to each other
+ // if they both set their document.domain properties to google.com.)
+ static bool IsSameWebSite(const GURL& url1, const GURL& url2);
+
+ protected:
+ friend class BrowsingInstance;
+
+ // Create a new SiteInstance. Protected to give access to BrowsingInstance
+ // and tests; most callers should use CreateSiteInstance or
+ // GetRelatedSiteInstance instead.
+ SiteInstance(BrowsingInstance* browsing_instance)
+ : browsing_instance_(browsing_instance),
+ process_host_id_(-1),
+ max_page_id_(-1),
+ has_site_(false) {
+ DCHECK(browsing_instance);
+ }
+
+ private:
+ // BrowsingInstance to which this SiteInstance belongs.
+ scoped_refptr<BrowsingInstance> browsing_instance_;
+
+ // Current host ID for the RenderProcessHost that is rendering pages for this
+ // SiteInstance. If the rendering process dies, this host ID can be
+ // replaced when a new process is created, without losing the association
+ // between all pages in this SiteInstance.
+ int process_host_id_;
+
+ // The current max_page_id in the SiteInstance's RenderProcessHost. If the
+ // rendering process dies, its replacement should start issuing page IDs that
+ // are larger than this value.
+ int32 max_page_id_;
+
+ // The web site that this SiteInstance is rendering pages for.
+ GURL site_;
+
+ // Whether SetSite has been called.
+ bool has_site_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SiteInstance);
+};
+
+#endif // CHROME_BROWSER_SITE_INSTANCE_H__
+
diff --git a/chrome/browser/tab_contents/status_view.cc b/chrome/browser/tab_contents/status_view.cc
new file mode 100644
index 0000000..349ac53
--- /dev/null
+++ b/chrome/browser/tab_contents/status_view.cc
@@ -0,0 +1,74 @@
+// 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 "chrome/browser/tab_contents/status_view.h"
+
+const int StatusView::kLayoutPadding = 5;
+const int StatusView::kButtonWidth = 200;
+const int StatusView::kButtonHeight = 30;
+
+StatusView::StatusView(TabContentsType type) : TabContents(type) {
+}
+
+StatusView::~StatusView() {
+ for (size_t i = 0; i < buttons_.size(); ++i)
+ delete buttons_[i].button;
+}
+
+void StatusView::CreateView() {
+ Create(GetDesktopWindow());
+}
+
+LRESULT StatusView::OnCreate(LPCREATESTRUCT create_struct) {
+ CRect rect(kLayoutPadding, kButtonHeight + kLayoutPadding * 2, 200, 200);
+ OnCreate(rect);
+ return 0;
+}
+
+void StatusView::OnSize(WPARAM wParam, const CSize& size) {
+ int start_x = kLayoutPadding;
+ int start_y = kButtonHeight + kLayoutPadding * 2;
+ int end_x = size.cx - kLayoutPadding;
+ int end_y = size.cy - kLayoutPadding;
+ CRect rect(start_x, start_y, end_x, end_y);
+ OnSize(rect);
+}
+
+LRESULT StatusView::OnEraseBkgnd(HDC hdc) {
+ HBRUSH brush = GetSysColorBrush(COLOR_3DFACE);
+ HGDIOBJ old_brush = SelectObject(hdc, brush);
+
+ RECT rc;
+ GetClientRect(&rc);
+ FillRect(hdc, &rc, brush);
+
+ SelectObject(hdc, old_brush);
+ return 1;
+}
+
+void StatusView::CreateButton(int id, const wchar_t* title) {
+ int button_count = static_cast<int>(buttons_.size());
+ int width_offset =
+ kLayoutPadding + button_count * (kButtonWidth + kLayoutPadding);
+ CRect rect(0, 0, kButtonWidth, kButtonHeight);
+ rect.OffsetRect(width_offset, kLayoutPadding);
+ ButtonInfo bi;
+ bi.button = new CButton();
+ bi.id = id;
+ bi.button->Create(m_hWnd, rect, NULL, WS_CHILD | WS_VISIBLE, 0, bi.id);
+ bi.button->SetWindowText(title);
+ buttons_.push_back(bi);
+}
+
+void StatusView::SetButtonText(int id, const wchar_t* title) {
+ for (size_t i = 0; i < buttons_.size(); ++i) {
+ if (buttons_[i].id == id) {
+ buttons_[i].button->SetWindowText(title);
+ return;
+ }
+ }
+
+ DLOG(INFO) << "No button with id " << id << " to set title " << title;
+}
+
diff --git a/chrome/browser/tab_contents/status_view.h b/chrome/browser/tab_contents/status_view.h
new file mode 100644
index 0000000..e1f0204
--- /dev/null
+++ b/chrome/browser/tab_contents/status_view.h
@@ -0,0 +1,84 @@
+// 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 CHROME_BROWSER_STATUS_VIEW_H__
+#define CHROME_BROWSER_STATUS_VIEW_H__
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlcrack.h>
+#include <atlctrls.h>
+#include <atlmisc.h>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+
+typedef CWinTraits<WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS>
+ StatusViewTraits;
+
+// A base class for about:network, about:ipc etc. It handles creating a row of
+// buttons at the top of the page. Derived classes get a rect of the remaining
+// area and can create their own controls there.
+class StatusView : public TabContents,
+ public CWindowImpl<StatusView, CWindow, StatusViewTraits> {
+ public:
+ StatusView(TabContentsType type);
+
+ BEGIN_MSG_MAP(StatusView)
+ MSG_WM_CREATE(OnCreate)
+ MSG_WM_ERASEBKGND(OnEraseBkgnd)
+ MSG_WM_SIZE(OnSize)
+ END_MSG_MAP()
+
+ virtual void CreateView();
+ virtual HWND GetContainerHWND() const { return m_hWnd; }
+
+ // Derived classes should implement the following functions
+ // TabContents override, to set the page title.
+ // virtual const std::wstring GetDefaultTitle() = 0;
+ // Gives a rect whose top left corner is after the buttons. The size of the
+ // controls that are added by derived classes will be set in the next OnSize,
+ // for now can use any height/width.
+ virtual void OnCreate(const CRect& rect) = 0;
+ virtual void OnSize(const CRect& rect) = 0;
+
+ protected:
+ // Should be deleted via CloseContents.
+ virtual ~StatusView();
+
+ // Creates and adds a button to the top row of the page. Button ids should
+ // be unique and start at 101.
+ void CreateButton(int id, const wchar_t* title);
+ void SetButtonText(int id, const wchar_t* title);
+
+ static const int kLayoutPadding;
+ static const int kButtonWidth;
+ static const int kButtonHeight;
+
+ private:
+ // FocusTraversal Implementation
+ // TODO (jcampan): make focus traversal work
+ views::View* FindNextFocusableView(views::View* starting_view, bool reverse,
+ bool dont_loop) {
+ return NULL;
+ }
+
+ // Event handlers
+ LRESULT OnCreate(LPCREATESTRUCT create_struct);
+ void OnSize(UINT size_command, const CSize& new_size);
+ LRESULT OnEraseBkgnd(HDC hdc);
+
+ struct ButtonInfo {
+ CButton* button;
+ int id;
+ };
+
+ std::vector<ButtonInfo> buttons_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(StatusView);
+};
+
+#endif // #ifndef CHROME_BROWSER_STATUS_VIEW_H__
+
diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc
new file mode 100644
index 0000000..70988e2
--- /dev/null
+++ b/chrome/browser/tab_contents/tab_contents.cc
@@ -0,0 +1,605 @@
+// 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 "chrome/browser/tab_contents/tab_contents.h"
+
+#include "chrome/browser/cert_store.h"
+#include "chrome/browser/views/download_shelf_view.h"
+#include "chrome/browser/views/download_started_animation.h"
+#include "chrome/browser/views/blocked_popup_container.h"
+#include "chrome/browser/tab_contents/infobar_delegate.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/tab_contents/tab_contents_delegate.h"
+#include "chrome/browser/tab_contents/web_contents.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "chrome/views/native_scroll_bar.h"
+#include "chrome/views/root_view.h"
+#include "chrome/views/view.h"
+#include "chrome/views/view_storage.h"
+#include "chrome/views/widget.h"
+
+#include "generated_resources.h"
+
+namespace {
+
+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;
+}
+
+} // namespace
+
+TabContents::TabContents(TabContentsType type)
+ : type_(type),
+ delegate_(NULL),
+ controller_(NULL),
+ is_loading_(false),
+ is_active_(true),
+ is_crashed_(false),
+ waiting_for_response_(false),
+ shelf_visible_(false),
+ max_page_id_(-1),
+ blocked_popups_(NULL),
+ capturing_contents_(false),
+ is_being_destroyed_(false) {
+ last_focused_view_storage_id_ =
+ views::ViewStorage::GetSharedInstance()->CreateStorageID();
+}
+
+TabContents::~TabContents() {
+ // Makes sure to remove any stored view we may still have in the ViewStorage.
+ //
+ // It is possible the view went away before us, so we only do this if the
+ // view is registered.
+ views::ViewStorage* view_storage = views::ViewStorage::GetSharedInstance();
+ if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL)
+ view_storage->RemoveView(last_focused_view_storage_id_);
+}
+
+// static
+void TabContents::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterBooleanPref(prefs::kBlockPopups, false);
+}
+
+void TabContents::CloseContents() {
+ // Destroy our NavigationController, which will Destroy all tabs it owns.
+ controller_->Destroy();
+ // Note that the controller may have deleted us at this point,
+ // so don't touch any member variables here.
+}
+
+void TabContents::Destroy() {
+ DCHECK(!is_being_destroyed_);
+ is_being_destroyed_ = true;
+
+ // 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.
+ int size = static_cast<int>(child_windows_.size());
+ for (int i = size - 1; i >= 0; --i) {
+ ConstrainedWindow* window = child_windows_[i];
+ if (window)
+ window->CloseConstrainedWindow();
+ }
+
+ // Notify any observer that have a reference on this tab contents.
+ NotificationService::current()->Notify(NOTIFY_TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(this),
+ NotificationService::NoDetails());
+
+ // If we still have a window handle, destroy it. GetContainerHWND can return
+ // NULL if this contents was part of a window that closed.
+ if (GetContainerHWND())
+ ::DestroyWindow(GetContainerHWND());
+
+ // Notify our NavigationController. Make sure we are deleted first, so
+ // that the controller is the last to die.
+ NavigationController* controller = controller_;
+ TabContentsType type = this->type();
+
+ delete this;
+
+ controller->TabContentsWasDestroyed(type);
+}
+
+void TabContents::SetupController(Profile* profile) {
+ DCHECK(!controller_);
+ controller_ = new NavigationController(this, profile);
+}
+
+bool TabContents::SupportsURL(GURL* url) {
+ GURL u(*url);
+ if (TabContents::TypeForURL(&u) == type()) {
+ *url = u;
+ return true;
+ }
+ return false;
+}
+
+const GURL& TabContents::GetURL() const {
+ // We may not have a navigation entry yet
+ NavigationEntry* entry = controller_->GetActiveEntry();
+ return entry ? entry->display_url() : GURL::EmptyGURL();
+}
+
+const std::wstring& TabContents::GetTitle() const {
+ // 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.
+ // The exception is with transient pages, for which we really want to use
+ // their title, as they are not committed.
+ NavigationEntry* entry = controller_->GetTransientEntry();
+ if (entry)
+ return entry->GetTitleForDisplay();
+
+ entry = controller_->GetLastCommittedEntry();
+ if (entry)
+ return entry->GetTitleForDisplay();
+ else if (controller_->LoadingURLLazily())
+ return controller_->GetLazyTitle();
+ return EmptyWString();
+}
+
+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 WebContents will also have site instances, except during
+ // testing.
+ if (GetSiteInstance())
+ GetSiteInstance()->UpdateMaxPageID(page_id);
+
+ if (AsWebContents())
+ AsWebContents()->process()->UpdateMaxPageID(page_id);
+ else
+ max_page_id_ = std::max(max_page_id_, page_id);
+}
+
+const std::wstring TabContents::GetDefaultTitle() const {
+ return l10n_util::GetString(IDS_DEFAULT_TAB_TITLE);
+}
+
+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();
+ else if (controller_->LoadingURLLazily())
+ return controller_->GetLazyFavIcon();
+ return SkBitmap();
+}
+
+SecurityStyle TabContents::GetSecurityStyle() const {
+ // We may not have a navigation entry yet.
+ NavigationEntry* entry = controller_->GetActiveEntry();
+ return entry ? entry->ssl().security_style() : SECURITY_STYLE_UNKNOWN;
+}
+
+bool TabContents::GetSSLEVText(std::wstring* ev_text,
+ std::wstring* ev_tooltip_text) const {
+ DCHECK(ev_text && ev_tooltip_text);
+ ev_text->clear();
+ ev_tooltip_text->clear();
+
+ NavigationEntry* entry = controller_->GetActiveEntry();
+ if (!entry ||
+ net::IsCertStatusError(entry->ssl().cert_status()) ||
+ ((entry->ssl().cert_status() & net::CERT_STATUS_IS_EV) == 0))
+ return false;
+
+ scoped_refptr<net::X509Certificate> cert;
+ CertStore::GetSharedInstance()->RetrieveCert(entry->ssl().cert_id(), &cert);
+ if (!cert.get()) {
+ NOTREACHED();
+ return false;
+ }
+
+ return SSLManager::GetEVCertNames(*cert, ev_text, ev_tooltip_text);
+}
+
+void TabContents::SetIsCrashed(bool state) {
+ if (state == is_crashed_)
+ return;
+
+ is_crashed_ = state;
+ if (delegate_)
+ delegate_->ContentsStateChanged(this);
+}
+
+void TabContents::NotifyNavigationStateChanged(unsigned changed_flags) {
+ if (delegate_)
+ delegate_->NavigationStateChanged(this, changed_flags);
+}
+
+void TabContents::DidBecomeSelected() {
+ if (controller_)
+ controller_->SetActive(true);
+
+ // Invalidate all descendants. (take care to exclude invalidating ourselves!)
+ EnumChildWindows(GetContainerHWND(), InvalidateWindow, 0);
+}
+
+void TabContents::WasHidden() {
+ NotificationService::current()->Notify(NOTIFY_TAB_CONTENTS_HIDDEN,
+ Source<TabContents>(this),
+ NotificationService::NoDetails());
+}
+
+void TabContents::Activate() {
+ if (delegate_)
+ delegate_->ActivateContents(this);
+}
+
+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(bool reload) {
+ // Our benavior is just to report that the entry was committed.
+ controller()->GetPendingEntry()->set_title(GetDefaultTitle());
+ controller()->CommitPendingEntry();
+ return true;
+}
+
+ConstrainedWindow* TabContents::CreateConstrainedDialog(
+ views::WindowDelegate* window_delegate,
+ views::View* contents_view) {
+ ConstrainedWindow* window =
+ ConstrainedWindow::CreateConstrainedDialog(
+ this, gfx::Rect(), contents_view, window_delegate);
+ child_windows_.push_back(window);
+ return window;
+}
+
+void TabContents::AddNewContents(TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ if (!delegate_)
+ return;
+
+ if ((disposition == NEW_POPUP) && !user_gesture) {
+ // Unrequested popups from normal pages are constrained.
+ TabContents* popup_owner = this;
+ TabContents* our_owner = delegate_->GetConstrainingContents(this);
+ if (our_owner)
+ popup_owner = our_owner;
+ popup_owner->AddConstrainedPopup(new_contents, initial_pos);
+ } else {
+ new_contents->DisassociateFromPopupCount();
+
+ delegate_->AddNewContents(this, new_contents, disposition, initial_pos,
+ user_gesture);
+
+ PopupNotificationVisibilityChanged(ShowingBlockedPopupNotification());
+ }
+}
+
+void TabContents::AddConstrainedPopup(TabContents* new_contents,
+ const gfx::Rect& initial_pos) {
+ if (!blocked_popups_) {
+ CRect client_rect;
+ GetClientRect(GetContainerHWND(), &client_rect);
+ gfx::Point anchor_position(
+ client_rect.Width() -
+ views::NativeScrollBar::GetVerticalScrollBarWidth(),
+ client_rect.Height());
+
+ blocked_popups_ = BlockedPopupContainer::Create(
+ this, profile(), anchor_position);
+ child_windows_.push_back(blocked_popups_);
+ }
+
+ blocked_popups_->AddTabContents(new_contents, initial_pos);
+ PopupNotificationVisibilityChanged(ShowingBlockedPopupNotification());
+}
+
+void TabContents::CloseAllSuppressedPopups() {
+ if (blocked_popups_)
+ blocked_popups_->CloseAllPopups();
+}
+
+void TabContents::Focus() {
+ HWND container_hwnd = GetContainerHWND();
+ if (!container_hwnd)
+ return;
+
+ views::FocusManager* focus_manager =
+ views::FocusManager::GetFocusManager(container_hwnd);
+ DCHECK(focus_manager);
+ views::View* v = focus_manager->GetViewForWindow(container_hwnd, true);
+ DCHECK(v);
+ if (v)
+ v->RequestFocus();
+}
+
+void TabContents::StoreFocus() {
+ views::ViewStorage* view_storage =
+ views::ViewStorage::GetSharedInstance();
+
+ if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL)
+ view_storage->RemoveView(last_focused_view_storage_id_);
+
+ views::FocusManager* focus_manager =
+ views::FocusManager::GetFocusManager(GetContainerHWND());
+ if (focus_manager) {
+ // |focus_manager| can be NULL if the tab has been detached but still
+ // exists.
+ views::View* focused_view = focus_manager->GetFocusedView();
+ if (focused_view)
+ view_storage->StoreView(last_focused_view_storage_id_, focused_view);
+
+ // If the focus was on the page, explicitly clear the focus so that we
+ // don't end up with the focused HWND not part of the window hierarchy.
+ // TODO(brettw) this should move to the view somehow.
+ HWND container_hwnd = GetContainerHWND();
+ if (container_hwnd) {
+ views::View* focused_view = focus_manager->GetFocusedView();
+ if (focused_view) {
+ HWND hwnd = focused_view->GetRootView()->GetWidget()->GetHWND();
+ if (container_hwnd == hwnd || ::IsChild(container_hwnd, hwnd))
+ focus_manager->ClearFocus();
+ }
+ }
+ }
+}
+
+void TabContents::RestoreFocus() {
+ views::ViewStorage* view_storage =
+ views::ViewStorage::GetSharedInstance();
+ views::View* last_focused_view =
+ view_storage->RetrieveView(last_focused_view_storage_id_);
+
+ if (!last_focused_view) {
+ SetInitialFocus();
+ } else {
+ views::FocusManager* focus_manager =
+ views::FocusManager::GetFocusManager(GetContainerHWND());
+
+ // If you hit this DCHECK, please report it to Jay (jcampan).
+ DCHECK(focus_manager != NULL) << "No focus manager when restoring focus.";
+
+ if (focus_manager && focus_manager->ContainsView(last_focused_view)) {
+ last_focused_view->RequestFocus();
+ } else {
+ // The focused view may not belong to the same window hierarchy (for
+ // example if the location bar was focused and the tab is dragged out).
+ // In that case we default to the default focus.
+ SetInitialFocus();
+ }
+ view_storage->RemoveView(last_focused_view_storage_id_);
+ }
+}
+
+void TabContents::SetInitialFocus() {
+ ::SetFocus(GetContainerHWND());
+}
+
+void TabContents::AddInfoBar(InfoBarDelegate* delegate) {
+ // 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 (int i = 0; i < infobar_delegate_count(); ++i) {
+ if (GetInfoBarDelegateAt(i)->EqualsDelegate(delegate))
+ return;
+ }
+
+ infobar_delegates_.push_back(delegate);
+ NotificationService::current()->Notify(NOTIFY_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) {
+ DCHECK(controller());
+ registrar_.Add(this, NOTIFY_NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(controller()));
+ }
+}
+
+void TabContents::RemoveInfoBar(InfoBarDelegate* delegate) {
+ std::vector<InfoBarDelegate*>::iterator it =
+ find(infobar_delegates_.begin(), infobar_delegates_.end(), delegate);
+ if (it != infobar_delegates_.end()) {
+ InfoBarDelegate* delegate = *it;
+ NotificationService::current()->Notify(NOTIFY_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, NOTIFY_NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(controller()));
+ }
+ }
+}
+
+void TabContents::SetDownloadShelfVisible(bool visible) {
+ if (shelf_visible_ != visible) {
+ if (visible) {
+ // Invoke GetDownloadShelfView to force the shelf to be created.
+ GetDownloadShelfView();
+ }
+ shelf_visible_ = visible;
+
+ if (delegate_)
+ delegate_->ContentsStateChanged(this);
+ }
+
+ // SetShelfVisible can force-close the shelf, so make sure we lay out
+ // everything correctly, as if the animation had finished. This doesn't
+ // matter for showing the shelf, as the show animation will do it.
+ ToolbarSizeChanged(false);
+}
+
+void TabContents::ToolbarSizeChanged(bool is_animating) {
+ TabContentsDelegate* d = delegate();
+ if (d)
+ d->ToolbarSizeChanged(this, is_animating);
+}
+
+void TabContents::OnStartDownload(DownloadItem* download) {
+ DCHECK(download);
+ TabContents* tab_contents = this;
+
+ // Download in a constrained popup is shown in the tab that opened it.
+ TabContents* constraining_tab = delegate()->GetConstrainingContents(this);
+ if (constraining_tab)
+ tab_contents = constraining_tab;
+
+ // GetDownloadShelfView creates the download shelf if it was not yet created.
+ tab_contents->GetDownloadShelfView()->AddDownload(download);
+ tab_contents->SetDownloadShelfVisible(true);
+
+ // This animation will delete itself when it finishes, or if we become hidden
+ // or destroyed.
+ if (IsWindowVisible(GetContainerHWND())) { // For minimized windows, unit
+ // tests, etc.
+ new DownloadStartedAnimation(tab_contents);
+ }
+}
+
+DownloadShelfView* TabContents::GetDownloadShelfView() {
+ if (!download_shelf_view_.get()) {
+ download_shelf_view_.reset(new DownloadShelfView(this));
+ // The TabContents owns the download-shelf.
+ download_shelf_view_->SetParentOwned(false);
+ }
+ return download_shelf_view_.get();
+}
+
+void TabContents::MigrateShelfViewFrom(TabContents* tab_contents) {
+ download_shelf_view_.reset(tab_contents->GetDownloadShelfView());
+ download_shelf_view_->ChangeTabContents(tab_contents, this);
+ tab_contents->ReleaseDownloadShelfView();
+}
+
+void TabContents::WillClose(ConstrainedWindow* window) {
+ ConstrainedWindowList::iterator it =
+ find(child_windows_.begin(), child_windows_.end(), window);
+ if (it != child_windows_.end())
+ child_windows_.erase(it);
+
+ if (window == blocked_popups_)
+ blocked_popups_ = NULL;
+
+ if (::IsWindow(GetContainerHWND())) {
+ CRect client_rect;
+ GetClientRect(GetContainerHWND(), &client_rect);
+ RepositionSupressedPopupsToFit(
+ gfx::Size(client_rect.Width(), client_rect.Height()));
+ }
+}
+
+void TabContents::DidMoveOrResize(ConstrainedWindow* window) {
+ UpdateWindow(GetContainerHWND());
+}
+
+void TabContents::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NOTIFY_NAV_ENTRY_COMMITTED);
+ DCHECK(controller() == Source<NavigationController>(source).ptr());
+
+ NavigationController::LoadCommittedDetails& committed_details =
+ *(Details<NavigationController::LoadCommittedDetails>(details).ptr());
+ ExpireInfoBars(committed_details);
+}
+
+// static
+void TabContents::MigrateShelfView(TabContents* from, TabContents* to) {
+ bool was_shelf_visible = from->IsDownloadShelfVisible();
+ if (was_shelf_visible)
+ to->MigrateShelfViewFrom(from);
+ to->SetDownloadShelfVisible(was_shelf_visible);
+}
+
+void TabContents::SetIsLoading(bool is_loading,
+ LoadNotificationDetails* details) {
+ if (is_loading == is_loading_)
+ return;
+
+ is_loading_ = is_loading;
+ waiting_for_response_ = is_loading;
+
+ // Suppress notifications for this TabContents if we are not active.
+ if (!is_active_)
+ return;
+
+ if (delegate_)
+ delegate_->LoadingStateChanged(this);
+
+ NotificationService::current()->
+ Notify((is_loading ? NOTIFY_LOAD_START : NOTIFY_LOAD_STOP),
+ Source<NavigationController>(this->controller()),
+ details ? Details<LoadNotificationDetails>(details) :
+ NotificationService::NoDetails());
+}
+
+// TODO(brettw) This should be on the WebContentsView.
+void TabContents::RepositionSupressedPopupsToFit(const gfx::Size& new_size) {
+ // TODO(erg): There's no way to detect whether scroll bars are
+ // visible, so for beta, we're just going to assume that the
+ // vertical scroll bar is visible, and not care about covering up
+ // the horizontal scroll bar. Fixing this is half of
+ // http://b/1118139.
+ gfx::Point anchor_position(
+ new_size.width() -
+ views::NativeScrollBar::GetVerticalScrollBarWidth(),
+ new_size.height());
+
+ if (blocked_popups_)
+ blocked_popups_->RepositionConstrainedWindowTo(anchor_position);
+}
+
+void TabContents::ReleaseDownloadShelfView() {
+ download_shelf_view_.release();
+}
+
+bool TabContents::ShowingBlockedPopupNotification() const {
+ return blocked_popups_ != NULL &&
+ blocked_popups_->GetTabContentsCount() != 0;
+}
+
+namespace {
+bool TransitionIsReload(PageTransition::Type transition) {
+ return PageTransition::StripQualifier(transition) == PageTransition::RELOAD;
+}
+}
+
+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;
+
+ for (int i = infobar_delegate_count() - 1; i >= 0; --i) {
+ InfoBarDelegate* delegate = GetInfoBarDelegateAt(i);
+ if (delegate->ShouldExpire(details))
+ RemoveInfoBar(delegate);
+ }
+}
diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h
new file mode 100644
index 0000000..41ef34a
--- /dev/null
+++ b/chrome/browser/tab_contents/tab_contents.h
@@ -0,0 +1,560 @@
+// 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 CHROME_BROWSER_TAB_CONTENTS_H_
+#define CHROME_BROWSER_TAB_CONTENTS_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/browser/autocomplete/autocomplete_edit.h"
+#include "chrome/browser/tab_contents/constrained_window.h"
+#include "chrome/browser/tab_contents/infobar_delegate.h"
+#include "chrome/browser/tab_contents/navigation_controller.h"
+#include "chrome/browser/tab_contents/page_navigator.h"
+#include "chrome/browser/tab_contents/tab_contents_type.h"
+#include "chrome/common/navigation_types.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/property_bag.h"
+
+namespace gfx {
+class Rect;
+class Size;
+}
+namespace views {
+class RootView;
+class WindowDelegate;
+}
+
+class BlockedPopupContainer;
+class DOMUIHost;
+class DownloadItem;
+class DownloadShelfView;
+class InfoBarView;
+class LoadNotificationDetails;
+class Profile;
+class TabContentsDelegate;
+class TabContentsFactory;
+class SkBitmap;
+class SiteInstance;
+class WebContents;
+
+// Describes what goes in the main content area of a tab. For example,
+// the WebContents is one such thing.
+//
+// When instantiating a new TabContents explicitly, the TabContents will not
+// have an associated NavigationController. To setup a NavigationController
+// for the TabContents, its SetupController method should be called.
+//
+// Once they reside within a NavigationController, TabContents objects are
+// owned by that NavigationController. When the active TabContents within that
+// NavigationController is closed, that TabContents destroys the
+// NavigationController, which then destroys all of the TabContentses in it.
+//
+// NOTE: When the NavigationController is navigated to an URL corresponding to
+// a different type of TabContents (see the TabContents::TypeForURL method),
+// the NavigationController makes the active TabContents inactive, notifies the
+// TabContentsDelegate that the TabContents is being replaced, and then
+// activates the new TabContents.
+class TabContents : public PageNavigator,
+ public NotificationObserver {
+ 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, // The URL has changed.
+ INVALIDATE_TITLE = 2, // The title has changed.
+ INVALIDATE_FAVICON = 4, // The favicon has changed.
+ INVALIDATE_LOAD = 8, // The loading state has changed
+
+ // Helper for forcing a refresh.
+ INVALIDATE_EVERYTHING = 0xFFFFFFFF
+ };
+
+ static void RegisterUserPrefs(PrefService* prefs);
+
+ // Factory -------------------------------------------------------------------
+ // (implemented in tab_contents_factory.cc)
+
+ // Creates a new TabContents of the given type. Will reuse the given
+ // instance's renderer, if it is not null.
+ static TabContents* CreateWithType(TabContentsType type,
+ Profile* profile,
+ SiteInstance* instance);
+
+ // Returns the type of TabContents needed to handle the URL. |url| may
+ // end up being modified to contain the _real_ url being loaded if the
+ // parameter was an alias (such as about: urls and chrome- urls).
+ static TabContentsType TypeForURL(GURL* url);
+
+ // This method can be used to register a new TabContents type dynamically,
+ // which can be very useful for unit testing. If factory is null, then the
+ // tab contents type is unregistered. Returns the previously registered
+ // factory for the given type or null if there was none.
+ static TabContentsFactory* RegisterFactory(TabContentsType type,
+ TabContentsFactory* factory);
+
+ // Creation & destruction ----------------------------------------------------
+
+ // Request this tab to shut down. This kills the tab's NavigationController,
+ // which then Destroy()s all tabs it controls.
+ void CloseContents();
+
+ // Unregister/shut down any pending tasks involving this tab.
+ // This is called as the tab is shutting down, before the
+ // NavigationController (and consequently profile) are gone.
+ //
+ // If you override this, be sure to call this implementation at the end
+ // of yours.
+ // See also Close().
+ virtual void Destroy();
+
+ // Intrinsic tab state -------------------------------------------------------
+
+ // Returns the type of tab this is. See also the As* functions following.
+ TabContentsType type() const { return type_; }
+
+ // 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_; }
+
+ // Returns this object as a WebContents if it is one, and NULL otherwise.
+ virtual WebContents* AsWebContents() { return NULL; }
+
+ // Const version of above for situations where const TabContents*'s are used.
+ WebContents* AsWebContents() const {
+ return const_cast<TabContents*>(this)->AsWebContents();
+ }
+
+ // Returns this object as a DOMUIHost if it is one, and NULL otherwise.
+ virtual DOMUIHost* AsDOMUIHost() { return NULL; }
+
+ TabContentsDelegate* delegate() const { return delegate_; }
+ void set_delegate(TabContentsDelegate* d) { delegate_ = d; }
+
+ // This can only be null if the TabContents has been created but
+ // SetupController has not been called. The controller should always outlive
+ // its TabContents.
+ NavigationController* controller() const { return controller_; }
+ void set_controller(NavigationController* c) { controller_ = c; }
+
+ // Sets up a new NavigationController for this TabContents.
+ // |profile| is the user profile that should be associated with
+ // the new controller.
+ //
+ // TODO(brettw) this seems bogus and I couldn't find any legitimate need for
+ // it. I think it should be passed in the constructor.
+ void SetupController(Profile* profile);
+
+ // Returns the user profile associated with this TabContents (via the
+ // NavigationController). This will return NULL if there isn't yet a
+ // NavigationController on this TabContents.
+ // TODO(darin): make it so that controller_ can never be null
+ Profile* profile() const {
+ return controller_ ? controller_->profile() : NULL;
+ }
+
+ // Returns whether this tab contents supports the provided URL. By default,
+ // this method matches the tab contents type with the result of TypeForURL().
+ // |url| points to the actual URL that will be used. It can be modified as
+ // needed.
+ // Override this method if your TabContents subclass supports various URL
+ // schemes but doesn't want to be the default handler for these schemes.
+ // For example, the NewTabUIContents overrides this method to support
+ // javascript: URLs.
+ virtual bool SupportsURL(GURL* url);
+
+ // 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).
+ const GURL& GetURL() const;
+ virtual const std::wstring& 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
+ // WebContents, 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. WebContents overrides this to provide proper
+ // access to its site instance.
+ virtual SiteInstance* GetSiteInstance() const { return NULL; }
+
+ // Initial title assigned to NavigationEntries from Navigate.
+ virtual const std::wstring GetDefaultTitle() 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() { return true; }
+
+ // 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.
+ virtual SkBitmap GetFavIcon() 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() { return true; }
+
+ // SSL related states.
+ SecurityStyle GetSecurityStyle() const;
+
+ // Sets |ev_text| to the text that should be displayed in the EV label of
+ // the location bar and |ev_tooltip_text| to the tooltip for that label.
+ // Returns false and sets these strings to empty if the current page is either
+ // not served over HTTPS or if HTTPS does not use an EV cert.
+ bool GetSSLEVText(std::wstring* ev_text, std::wstring* ev_tooltip_text) const;
+
+ // Returns a human-readable description the tab's loading state.
+ virtual std::wstring GetStatusText() const { return std::wstring(); }
+
+ // 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_; }
+
+ // 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 is_crashed_; }
+ void SetIsCrashed(bool state);
+
+ // Set whether this tab contents is active. A tab content is active for a
+ // given tab if it is currently being used to display some contents. Note that
+ // this is different from whether a tab is selected.
+ bool is_active() const { return is_active_; }
+ void set_is_active(bool active) { is_active_ = active; }
+
+ // 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();
+
+ // 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.
+ virtual void Activate();
+
+ // 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(bool reload);
+
+ // Stop any pending navigation.
+ virtual void Stop() {}
+
+ // TODO(erg): HACK ALERT! This was thrown together for beta and
+ // needs to be completely removed after we ship it. Right now, the
+ // cut/copy/paste menu items are always enabled and will send a
+ // cut/copy/paste command to the currently visible
+ // TabContents. Post-beta, this needs to be replaced with a unified
+ // interface for supporting cut/copy/paste, and managing who has
+ // cut/copy/paste focus. (http://b/1117225)
+ virtual void Cut() { }
+ virtual void Copy() { }
+ virtual void Paste() { }
+
+ // Called on a TabContents when it isn't a popup, but a new window.
+ virtual void DisassociateFromPopupCount() { }
+
+ // 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. The window
+ // is sized according to the preferred size of the content_view, and centered
+ // within the contents.
+ ConstrainedWindow* CreateConstrainedDialog(
+ views::WindowDelegate* window_delegate,
+ views::View* contents_view);
+
+ // 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);
+
+ // Builds a ConstrainedWindow* for the incoming |new_contents| and
+ // adds it to child_windows_.
+ void AddConstrainedPopup(TabContents* new_contents,
+ const gfx::Rect& initial_pos);
+
+ // When a tab is closed, this method is called for all the remaining tabs. If
+ // they all return false or if no tabs are left, the window is closed. The
+ // default is to return true
+ virtual bool ShouldPreventWindowClose() { return true; }
+
+ // Closes all constrained windows that represent web popups that have not yet
+ // been activated by the user and are as such auto-positioned in the bottom
+ // right of the screen. This is a quick way for users to "clean up" a flurry
+ // of unwanted popups.
+ void CloseAllSuppressedPopups();
+
+ // Called when the blocked popup notification is shown or hidden.
+ virtual void PopupNotificationVisibilityChanged(bool visible) { }
+
+ // Views and focus -----------------------------------------------------------
+
+ // Returns the actual window that is focused when this TabContents is shown.
+ virtual HWND GetContentHWND() {
+ return GetContainerHWND();
+ }
+
+ // Tell the subclass to set up the view (e.g. create the container HWND if
+ // applicable) and any other create-time setup.
+ virtual void CreateView() {}
+
+ // Returns the HWND associated with this TabContents. Outside of automation
+ // in the context of the UI, this is required to be implemented.
+ virtual HWND GetContainerHWND() const { return NULL; }
+
+ // Returns the bounds of this TabContents in the screen coordinate system.
+ virtual void GetContainerBounds(gfx::Rect *out) const {
+ out->SetRect(0, 0, 0, 0);
+ }
+
+ // Make the tab the focused window.
+ virtual void Focus();
+
+ // Stores the currently focused view.
+ virtual void StoreFocus();
+
+ // Restores focus to the last focus view. If StoreFocus has not yet been
+ // invoked, SetInitialFocus is invoked.
+ virtual void RestoreFocus();
+
+ // Invoked the first time this tab is getting the focus through TAB traversal.
+ // By default this does nothing, but is overridden to set the focus for the
+ // first element in the page.
+ //
+ // |reverse| indicates if the user is going forward or backward, so we know
+ // whether to set the first or last element focus.
+ //
+ // See also SetInitialFocus(no arg).
+ // FIXME(brettw) having two SetInitialFocus that do different things is silly.
+ virtual void SetInitialFocus(bool reverse) { }
+
+ // TabContents that contain View hierarchy (such as NativeUIContents) should
+ // return their RootView. Other TabContents (such as WebContents) should
+ // return NULL.
+ // This is used by the focus manager to figure out what to focus when the tab
+ // is focused (when a tab with no view hierarchy is focused, the
+ // TabContentsContainerView is focused) and how to process tab events. If
+ // this returns NULL, the TabContents is supposed to know how to process TAB
+ // key events and is just sent the key messages. If this returns a RootView,
+ // the focus is passed to the RootView.
+ virtual views::RootView* GetContentsRootView() { return NULL; }
+
+ // Infobars ------------------------------------------------------------------
+
+ // Adds an InfoBar for the specified |delegate|.
+ void AddInfoBar(InfoBarDelegate* delegate);
+
+ // Removes the InfoBar for the specified |delegate|.
+ void RemoveInfoBar(InfoBarDelegate* delegate);
+
+ // Enumeration and access functions.
+ int infobar_delegate_count() const { return infobar_delegates_.size(); }
+ InfoBarDelegate* GetInfoBarDelegateAt(int index) {
+ return infobar_delegates_.at(index);
+ }
+
+ // Toolbars and such ---------------------------------------------------------
+
+ // Returns whether the bookmark bar should be visible.
+ virtual bool IsBookmarkBarAlwaysVisible() { return false; }
+
+ // Whether or not the shelf view is visible.
+ virtual void SetDownloadShelfVisible(bool visible);
+ bool IsDownloadShelfVisible() { return shelf_visible_; }
+
+ // Notify our delegate that some of our content has animated.
+ void ToolbarSizeChanged(bool is_animating);
+
+ // Displays the download shelf and animation when a download occurs.
+ void OnStartDownload(DownloadItem* download);
+
+ // Returns the DownloadShelfView, creating it if necessary.
+ DownloadShelfView* GetDownloadShelfView();
+
+ // Transfer the shelf view from |tab_contents| to the receiving TabContents.
+ // |tab_contents| no longer owns the shelf after this call. The shelf is owned
+ // by the receiving TabContents.
+ void MigrateShelfViewFrom(TabContents* tab_contents);
+
+ // Migrate the shelf view between 2 TabContents. This helper function is
+ // currently called by NavigationController::DiscardPendingEntry. We may
+ // want to generalize this if we need to migrate some other state.
+ static void MigrateShelfView(TabContents* from, TabContents* to);
+
+ // Called when a ConstrainedWindow we own is about to be closed.
+ void WillClose(ConstrainedWindow* window);
+
+ // Called when a ConstrainedWindow we own is moved or resized.
+ void DidMoveOrResize(ConstrainedWindow* window);
+
+ protected:
+ // NotificationObserver implementation:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ friend class NavigationController;
+ // Used to access the child_windows_ (ConstrainedWindowList) for testing
+ // automation purposes.
+ friend class AutomationProvider;
+
+ explicit TabContents(TabContentsType type);
+
+ // Some tab contents types need to override the type.
+ void set_type(TabContentsType type) { type_ = type; }
+
+ // NOTE: the TabContents destructor can run after the NavigationController
+ // has gone away, so any complicated unregistering that expects the profile
+ // or other shared objects to still be around does not belong in a
+ // destructor.
+ // For those purposes, instead see Destroy().
+ // Protected so that others don't try to delete this directly.
+ virtual ~TabContents();
+
+ // Sets focus to the tab contents window, but doesn't actuall set focus to
+ // a particular element in it (see also SetInitialFocus(bool) which does
+ // that in different circumstances).
+ // FIXME(brettw) having two SetInitialFocus that do different things is silly.
+ virtual void SetInitialFocus();
+
+ // 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.
+ virtual void SetIsLoading(bool is_loading, LoadNotificationDetails* details);
+
+ // Called by a derived class when the TabContents is resized, causing
+ // suppressed constrained web popups to be repositioned to the new bounds
+ // if necessary.
+ void RepositionSupressedPopupsToFit(const gfx::Size& new_size);
+
+ // Releases the download shelf. This method is used by MigrateShelfViewFrom.
+ // Sub-classes should clear any pointer they might keep to the shelf view and
+ // invoke TabContents::ReleaseDownloadShelfView().
+ virtual void ReleaseDownloadShelfView();
+
+ // 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; }
+
+ typedef std::vector<ConstrainedWindow*> ConstrainedWindowList;
+ ConstrainedWindowList child_windows_;
+
+ // Whether we have a notification AND the notification owns popups windows.
+ // (We keep the notification object around even when it's not shown since it
+ // determines whether to show itself).
+ bool ShowingBlockedPopupNotification() const;
+
+ private:
+ // 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);
+
+ // Data ----------------------------------------------------------------------
+
+ TabContentsType type_;
+
+ TabContentsDelegate* delegate_;
+ NavigationController* controller_;
+
+ PropertyBag property_bag_;
+
+ NotificationRegistrar registrar_;
+
+ // Indicates whether we're currently loading a resource.
+ bool is_loading_;
+
+ // See is_active() getter above.
+ bool is_active_;
+
+ bool is_crashed_; // true if the tab is considered crashed.
+
+ // See waiting_for_response() above.
+ bool waiting_for_response_;
+
+ // The download shelf view (view at the bottom of the page).
+ scoped_ptr<DownloadShelfView> download_shelf_view_;
+
+ // Whether the shelf view is visible.
+ bool shelf_visible_;
+
+ // Indicates the largest PageID we've seen. This field is ignored if we are
+ // a WebContents, in which case the max page ID is stored separately with
+ // each SiteInstance.
+ int32 max_page_id_;
+
+ // The id used in the ViewStorage to store the last focused view.
+ int last_focused_view_storage_id_;
+
+ // See capturing_contents() above.
+ bool capturing_contents_;
+
+ // ConstrainedWindow with additional methods for managing blocked
+ // popups. This pointer alsog goes in |child_windows_| for ownership,
+ // repositioning, etc.
+ BlockedPopupContainer* blocked_popups_;
+
+ // Delegates for InfoBars associated with this TabContents.
+ std::vector<InfoBarDelegate*> infobar_delegates_;
+
+ // See getter above.
+ bool is_being_destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabContents);
+};
+
+#endif // CHROME_BROWSER_TAB_CONTENTS_H_
diff --git a/chrome/browser/tab_contents/tab_contents_delegate.h b/chrome/browser/tab_contents/tab_contents_delegate.h
new file mode 100644
index 0000000..773bb41
--- /dev/null
+++ b/chrome/browser/tab_contents/tab_contents_delegate.h
@@ -0,0 +1,150 @@
+// 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 CHROME_BROWSER_TAB_CONTENTS_DELEGATE_H_
+#define CHROME_BROWSER_TAB_CONTENTS_DELEGATE_H_
+
+// TODO(maruel): Remove once UINT and HWND are replaced / typedef.
+#include <windows.h>
+
+#include "chrome/browser/tab_contents/page_navigator.h"
+#include "chrome/common/navigation_types.h"
+
+namespace gfx {
+class Point;
+class Rect;
+}
+
+class TabContents;
+class HtmlDialogContentsDelegate;
+
+// Objects implement this interface to get notified about changes in the
+// TabContents and to provide necessary functionality.
+class TabContentsDelegate : public PageNavigator {
+ public:
+ // Opens a new URL inside the passed in TabContents, if source is 0 open
+ // in the current front-most tab.
+ virtual void OpenURLFromTab(TabContents* source,
+ const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition) = 0;
+
+ virtual void OpenURL(const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition)
+ {
+ OpenURLFromTab(NULL, url, referrer, disposition, transition);
+ }
+
+ // 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;
+
+ // Called to cause the delegate to replace the source contents with the new
+ // contents.
+ virtual void ReplaceContents(TabContents* source,
+ TabContents* new_contents) = 0;
+
+ // 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;
+
+ // 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;
+
+ // 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;
+
+ // Called to determine if the TabContents is contained in a popup window.
+ virtual bool IsPopup(TabContents* source) = 0;
+
+ // Returns the tab which contains the specified tab content if it is
+ // constrained, NULL otherwise.
+ virtual TabContents* GetConstrainingContents(TabContents* source) {
+ return NULL;
+ }
+
+ // 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 starredness of the current URL changed.
+ virtual void URLStarredChanged(TabContents* source, bool starred) = 0;
+
+ // Notification that the target URL has changed
+ virtual void UpdateTargetURL(TabContents* source, const GURL& url) = 0;
+
+ // Notification that the target URL has changed
+ virtual void ContentsMouseEvent(TabContents* source, UINT message) { }
+
+ // Request the delegate to change the zoom level of the current tab.
+ virtual void ContentsZoomChange(bool zoom_in) { }
+
+ // Check whether this contents is inside a window dedicated to running a web
+ // application.
+ virtual bool IsApplication() { return false; }
+
+ // Detach the given tab and convert it to a "webapp" view. The tab must be
+ // a WebContents with a valid WebApp set.
+ virtual void ConvertContentsToApplication(TabContents* source) { }
+
+ // Informs the TabContentsDelegate that some of our state has changed
+ // for this tab.
+ virtual void ContentsStateChanged(TabContents* source) {}
+
+ // Return whether this tab contents should have a URL bar. Only web contents
+ // opened with a minimal chrome and their popups can be displayed without a
+ // URL bar.
+ virtual bool ShouldDisplayURLField() { return true; }
+
+ // Whether this tab can be blurred through a javascript obj.blur()
+ // call. ConstrainedWindows shouldn't be able to be blurred.
+ virtual bool CanBlur() const { return true; }
+
+ // 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(HtmlDialogContentsDelegate* delegate,
+ void* parent_window) { }
+
+ // 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) {
+ *proceed_to_fire_unload = true;
+ }
+
+ // Send IPC to external host. Default implementation is do nothing.
+ virtual void ForwardMessageToExternalHost(const std::string& receiver,
+ const std::string& message) {};
+
+ // If the delegate is hosting tabs externally.
+ virtual bool IsExternalTabContainer() const { return false; }
+};
+
+#endif // CHROME_BROWSER_TAB_CONTENTS_DELEGATE_H_
diff --git a/chrome/browser/tab_contents/tab_contents_factory.cc b/chrome/browser/tab_contents/tab_contents_factory.cc
new file mode 100644
index 0000000..5151555
--- /dev/null
+++ b/chrome/browser/tab_contents/tab_contents_factory.cc
@@ -0,0 +1,161 @@
+// 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 "base/string_util.h"
+#include "chrome/browser/browser_about_handler.h"
+#include "chrome/browser/browser_url_handler.h"
+#include "chrome/browser/dom_ui/dom_ui_contents.h"
+#include "chrome/browser/dom_ui/html_dialog_contents.h"
+#include "chrome/browser/dom_ui/new_tab_ui.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/render_process_host.h"
+#include "chrome/browser/debugger/debugger_contents.h"
+#include "chrome/browser/tab_contents/about_internets_status_view.h"
+#include "chrome/browser/tab_contents/ipc_status_view.h"
+#include "chrome/browser/tab_contents/native_ui_contents.h"
+#include "chrome/browser/tab_contents/network_status_view.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/tab_contents/tab_contents_factory.h"
+#include "chrome/browser/tab_contents/view_source_contents.h"
+#include "chrome/browser/tab_contents/web_contents.h"
+#include "net/base/net_util.h"
+
+typedef std::map<TabContentsType, TabContentsFactory*> TabContentsFactoryMap;
+static TabContentsFactoryMap* g_extra_types; // Only allocated if needed.
+
+// static
+TabContentsType TabContentsFactory::NextUnusedType() {
+ int type = static_cast<int>(TAB_CONTENTS_NUM_TYPES);
+ if (g_extra_types) {
+ for (TabContentsFactoryMap::iterator i = g_extra_types->begin();
+ i != g_extra_types->end(); ++i) {
+ type = std::max(type, static_cast<int>(i->first));
+ }
+ }
+ return static_cast<TabContentsType>(type + 1);
+}
+
+/*static*/
+TabContents* TabContents::CreateWithType(TabContentsType type,
+ Profile* profile,
+ SiteInstance* instance) {
+ TabContents* contents;
+
+ switch (type) {
+ case TAB_CONTENTS_WEB:
+ contents = new WebContents(profile, instance, NULL, MSG_ROUTING_NONE, NULL);
+ break;
+ case TAB_CONTENTS_NETWORK_STATUS_VIEW:
+ contents = new NetworkStatusView();
+ break;
+#ifdef IPC_MESSAGE_LOG_ENABLED
+ case TAB_CONTENTS_IPC_STATUS_VIEW:
+ contents = new IPCStatusView();
+ break;
+#endif
+ case TAB_CONTENTS_NEW_TAB_UI:
+ contents = new NewTabUIContents(profile, instance, NULL);
+ break;
+ case TAB_CONTENTS_HTML_DIALOG:
+ contents = new HtmlDialogContents(profile, instance, NULL);
+ break;
+ case TAB_CONTENTS_NATIVE_UI:
+ contents = new NativeUIContents(profile);
+ break;
+ case TAB_CONTENTS_ABOUT_INTERNETS_STATUS_VIEW:
+ contents = new AboutInternetsStatusView();
+ break;
+ case TAB_CONTENTS_VIEW_SOURCE:
+ contents = new ViewSourceContents(profile, instance);
+ break;
+ case TAB_CONTENTS_ABOUT_UI:
+ contents = new BrowserAboutHandler(profile, instance, NULL);
+ break;
+ case TAB_CONTENTS_DEBUGGER:
+ contents = new DebuggerContents(profile, instance);
+ break;
+ case TAB_CONTENTS_DOM_UI:
+ contents = new DOMUIContents(profile, instance, NULL);
+ break;
+ default:
+ if (g_extra_types) {
+ TabContentsFactoryMap::const_iterator it = g_extra_types->find(type);
+ if (it != g_extra_types->end()) {
+ contents = it->second->CreateInstance();
+ break;
+ }
+ }
+ NOTREACHED() << "Don't know how to create tab contents of type " << type;
+ contents = NULL;
+ }
+
+ if (contents)
+ contents->CreateView();
+
+ return contents;
+}
+
+/*static*/
+TabContentsType TabContents::TypeForURL(GURL* url) {
+ DCHECK(url);
+ if (g_extra_types) {
+ TabContentsFactoryMap::const_iterator it = g_extra_types->begin();
+ for (; it != g_extra_types->end(); ++it) {
+ if (it->second->CanHandleURL(*url))
+ return it->first;
+ }
+ }
+
+ // Try to handle as a browser URL. If successful, |url| will end up
+ // containing the real url being loaded (browser url's are just an alias).
+ TabContentsType type(TAB_CONTENTS_UNKNOWN_TYPE);
+ if (BrowserURLHandler::HandleBrowserURL(url, &type))
+ return type;
+
+ if (url->SchemeIs(NativeUIContents::GetScheme().c_str()))
+ return TAB_CONTENTS_NATIVE_UI;
+
+ if (HtmlDialogContents::IsHtmlDialogUrl(*url))
+ return TAB_CONTENTS_HTML_DIALOG;
+
+ if (DebuggerContents::IsDebuggerUrl(*url))
+ return TAB_CONTENTS_DEBUGGER;
+
+ if (url->SchemeIs(DOMUIContents::GetScheme().c_str()))
+ return TAB_CONTENTS_DOM_UI;
+
+ if (url->SchemeIs("view-source")) {
+ // Load the inner URL instead, but render it using a ViewSourceContents.
+ *url = GURL(url->path());
+ return TAB_CONTENTS_VIEW_SOURCE;
+ }
+
+ // NOTE: Even the empty string can be loaded by a WebContents.
+ return TAB_CONTENTS_WEB;
+}
+
+/*static*/
+TabContentsFactory* TabContents::RegisterFactory(TabContentsType type,
+ TabContentsFactory* factory) {
+ if (!g_extra_types)
+ g_extra_types = new TabContentsFactoryMap;
+
+ TabContentsFactory* prev_factory = NULL;
+ TabContentsFactoryMap::const_iterator prev = g_extra_types->find(type);
+ if (prev != g_extra_types->end())
+ prev_factory = prev->second;
+
+ if (factory) {
+ g_extra_types->insert(TabContentsFactoryMap::value_type(type, factory));
+ } else {
+ g_extra_types->erase(type);
+ if (g_extra_types->empty()) {
+ delete g_extra_types;
+ g_extra_types = NULL;
+ }
+ }
+
+ return prev_factory;
+}
+
diff --git a/chrome/browser/tab_contents/tab_contents_factory.h b/chrome/browser/tab_contents/tab_contents_factory.h
new file mode 100644
index 0000000..615c01d
--- /dev/null
+++ b/chrome/browser/tab_contents/tab_contents_factory.h
@@ -0,0 +1,28 @@
+// 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 CHROME_BROWSER_TAB_CONTENTS_FACTORY_H_
+#define CHROME_BROWSER_TAB_CONTENTS_FACTORY_H_
+
+#include <string>
+#include "chrome/browser/tab_contents/tab_contents_type.h"
+
+class TabContents;
+
+// Extend from this class to implement a custom tab contents type. See
+// TabContents::RegisterFactory.
+class TabContentsFactory {
+ public:
+ // Returns the next unused TabContentsType after TAB_CONTENTS_NUM_TYPES.
+ static TabContentsType NextUnusedType();
+
+ // Returns a new TabContents instance of the associated type.
+ virtual TabContents* CreateInstance() = 0;
+
+ // Returns true if this factory can be used to create a TabContents instance
+ // capable of handling the given URL. NOTE: the given url can be empty.
+ virtual bool CanHandleURL(const GURL& url) = 0;
+};
+
+#endif // CHROME_BROWSER_TAB_CONTENTS_FACTORY_H_ \ No newline at end of file
diff --git a/chrome/browser/tab_contents/tab_contents_type.h b/chrome/browser/tab_contents/tab_contents_type.h
new file mode 100644
index 0000000..86f38cc
--- /dev/null
+++ b/chrome/browser/tab_contents/tab_contents_type.h
@@ -0,0 +1,31 @@
+// 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 CHROME_BROWSER_TAB_CONTENTS_TYPE_H__
+#define CHROME_BROWSER_TAB_CONTENTS_TYPE_H__
+
+// The different kinds of tab contents we support. This is declared outside of
+// TabContents to eliminate the circular dependency between NavigationEntry
+// (which requires a tab type) and TabContents (which requires a
+// NavigationEntry).
+enum TabContentsType {
+ TAB_CONTENTS_UNKNOWN_TYPE = 0,
+ TAB_CONTENTS_WEB,
+ TAB_CONTENTS_DOWNLOAD_VIEW,
+ TAB_CONTENTS_NETWORK_STATUS_VIEW,
+ TAB_CONTENTS_IPC_STATUS_VIEW,
+ TAB_CONTENTS_CHROME_VIEW_CONTENTS,
+ TAB_CONTENTS_NEW_TAB_UI,
+ TAB_CONTENTS_NATIVE_UI,
+ TAB_CONTENTS_ABOUT_INTERNETS_STATUS_VIEW,
+ TAB_CONTENTS_VIEW_SOURCE,
+ TAB_CONTENTS_HTML_DIALOG,
+ TAB_CONTENTS_ABOUT_UI,
+ TAB_CONTENTS_DEBUGGER,
+ TAB_CONTENTS_DOM_UI,
+ TAB_CONTENTS_NUM_TYPES
+};
+
+#endif // CHROME_BROWSER_TAB_CONTENTS_TYPE_H__
+
diff --git a/chrome/browser/tab_contents/tab_util.cc b/chrome/browser/tab_contents/tab_util.cc
new file mode 100644
index 0000000..a1a2f4e
--- /dev/null
+++ b/chrome/browser/tab_contents/tab_util.cc
@@ -0,0 +1,39 @@
+// 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 "chrome/browser/tab_contents/tab_util.h"
+
+#include "chrome/browser/render_view_host.h"
+#include "chrome/browser/render_process_host.h"
+#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
+#include "chrome/browser/tab_contents/web_contents.h"
+#include "net/url_request/url_request.h"
+
+bool tab_util::GetTabContentsID(URLRequest* request,
+ int* render_process_id,
+ int* render_view_id) {
+
+ if (!request || !render_process_id || !render_view_id)
+ return false;
+
+ ResourceDispatcherHost::ExtraRequestInfo* info =
+ ResourceDispatcherHost::ExtraInfoForRequest(request);
+ if (!info)
+ return false;
+
+ *render_process_id = info->render_process_host_id;
+ *render_view_id = info->render_view_id;
+ return true;
+}
+
+WebContents* tab_util::GetWebContentsByID(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 static_cast<WebContents*>(render_view_host->delegate());
+}
+
diff --git a/chrome/browser/tab_contents/tab_util.h b/chrome/browser/tab_contents/tab_util.h
new file mode 100644
index 0000000..6aa96f3
--- /dev/null
+++ b/chrome/browser/tab_contents/tab_util.h
@@ -0,0 +1,26 @@
+// 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 CHROME_BROWSER_TAB_UTIL_H__
+#define CHROME_BROWSER_TAB_UTIL_H__
+
+class URLRequest;
+class WebContents;
+
+namespace tab_util {
+
+// Helper to get the IDs necessary for looking up a TabContents.
+// Should only be called from the IO thread, since it accesses an URLRequest.
+bool GetTabContentsID(URLRequest* request, int* render_process_host_id,
+ int* routing_id);
+
+// Helper to find the WebContents that originated the given request. Can be
+// NULL if the tab has been closed or some other error occurs.
+// Should only be called from the UI thread, since it accesses TabContent.
+WebContents* GetWebContentsByID(int render_process_host_id, int routing_id);
+
+} // namespace tab_util
+
+#endif // CHROME_BROWSER_TAB_UTIL_H__
+
diff --git a/chrome/browser/tab_contents/view_source_contents.cc b/chrome/browser/tab_contents/view_source_contents.cc
new file mode 100644
index 0000000..849a005
--- /dev/null
+++ b/chrome/browser/tab_contents/view_source_contents.cc
@@ -0,0 +1,19 @@
+// 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 "chrome/browser/tab_contents/view_source_contents.h"
+
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/render_view_host.h"
+
+ViewSourceContents::ViewSourceContents(Profile* profile, SiteInstance* instance)
+ : WebContents(profile, instance, NULL, MSG_ROUTING_NONE, NULL) {
+ set_type(TAB_CONTENTS_VIEW_SOURCE);
+}
+
+void ViewSourceContents::RendererCreated(RenderViewHost* host) {
+ // Make sure the renderer is in view source mode.
+ host->Send(new ViewMsg_EnableViewSourceMode(host->routing_id()));
+}
+
diff --git a/chrome/browser/tab_contents/view_source_contents.h b/chrome/browser/tab_contents/view_source_contents.h
new file mode 100644
index 0000000..baeade6
--- /dev/null
+++ b/chrome/browser/tab_contents/view_source_contents.h
@@ -0,0 +1,27 @@
+// 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 CHROME_BROWSER_VIEW_SOURCE_CONTENTS_H__
+#define CHROME_BROWSER_VIEW_SOURCE_CONTENTS_H__
+
+#include "chrome/browser/tab_contents/web_contents.h"
+
+// We use this class to implement view-source: URLs.
+class ViewSourceContents : public WebContents {
+ public:
+ ViewSourceContents(Profile* profile, SiteInstance* instance);
+
+ protected:
+ // RenderViewHostDelegate overrides:
+ virtual void RendererCreated(RenderViewHost* host);
+
+ // WebContents overrides:
+ // We override updating history with a no-op so these pages
+ // are not saved to history.
+ virtual void UpdateHistoryForNavigation(const GURL& url,
+ const ViewHostMsg_FrameNavigate_Params& params) { }
+};
+
+#endif // CHROME_BROWSER_VIEW_SOURCE_CONTENTS_H__
+
diff --git a/chrome/browser/tab_contents/view_source_uitest.cc b/chrome/browser/tab_contents/view_source_uitest.cc
new file mode 100644
index 0000000..73cc875
--- /dev/null
+++ b/chrome/browser/tab_contents/view_source_uitest.cc
@@ -0,0 +1,110 @@
+// 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 "chrome/app/chrome_dll_resource.h"
+#include "chrome/test/automation/browser_proxy.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome/test/ui/ui_test.h"
+#include "net/url_request/url_request_unittest.h"
+
+namespace {
+
+const wchar_t kDocRoot[] = L"chrome/test/data";
+
+class ViewSourceTest : public UITest {
+ protected:
+ ViewSourceTest() : UITest() {
+ test_html_ = L"files/viewsource/test.html";
+ }
+
+ bool IsPageMenuCommandEnabled(int command) {
+ scoped_ptr<BrowserProxy> window_proxy(automation()->GetBrowserWindow(0));
+ if (!window_proxy.get())
+ return false;
+
+ bool timed_out;
+ return window_proxy->IsPageMenuCommandEnabledWithTimeout(
+ command, 5000, &timed_out) && !timed_out;
+ }
+
+ protected:
+ std::wstring test_html_;
+};
+
+} // namespace
+
+// This test renders a page in view-source and then checks to see if a cookie
+// set in the html was set successfully (it shouldn't because we rendered the
+// page in view source)
+TEST_F(ViewSourceTest, DoesBrowserRenderInViewSource) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(kDocRoot);
+ ASSERT_TRUE(NULL != server.get());
+ std::string cookie = "viewsource_cookie";
+ std::string cookie_data = "foo";
+
+ // First we navigate to our view-source test page
+ GURL url = server->TestServerPageW(test_html_);
+ url = GURL("view-source:" + url.spec());
+ scoped_ptr<TabProxy> tab(GetActiveTab());
+ tab->NavigateToURL(url);
+ Sleep(kWaitForActionMsec);
+
+ // Try to retrieve the cookie that the page sets
+ // It should not be there (because we are in view-source mode
+ std::string cookie_found;
+ tab->GetCookieByName(url, cookie, &cookie_found);
+ EXPECT_NE(cookie_data, cookie_found);
+}
+
+// This test renders a page normally and then renders the same page in
+// view-source mode. This is done since we had a problem at one point during
+// implementation of the view-source: prefix being consumed (removed from the
+// URL) if the URL was not changed (apart from adding the view-source prefix)
+TEST_F(ViewSourceTest, DoesBrowserConsumeViewSourcePrefix) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(kDocRoot);
+ ASSERT_TRUE(NULL != server.get());
+
+ // First we navigate to google.html
+ GURL url = server->TestServerPageW(test_html_);
+ NavigateToURL(url);
+
+ // Then we navigate to the SAME url but with the view-source: prefix
+ GURL url_viewsource = GURL("view-source:" + url.spec());
+ NavigateToURL(url_viewsource);
+
+ // The URL should still be prefixed with view-source:
+ EXPECT_EQ(url_viewsource.spec(), GetActiveTabURL().spec());
+}
+
+// Make sure that when looking at the actual page, we can select
+// "View Source" from the Page menu.
+TEST_F(ViewSourceTest, ViewSourceInPageMenuEnabledOnANormalPage) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(kDocRoot);
+ ASSERT_TRUE(NULL != server.get());
+
+ // First we navigate to google.html
+ GURL url = server->TestServerPageW(test_html_);
+ NavigateToURL(url);
+
+ EXPECT_TRUE(IsPageMenuCommandEnabled(IDC_VIEW_SOURCE));
+}
+
+// Make sure that when looking at the page source, we can't select
+// "View Source" from the Page menu.
+TEST_F(ViewSourceTest, ViewSourceInPageMenuDisabledWhileViewingSource) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(kDocRoot);
+ ASSERT_TRUE(NULL != server.get());
+
+ // First we navigate to google.html
+ GURL url = server->TestServerPageW(test_html_);
+ GURL url_viewsource = GURL("view-source:" + url.spec());
+ NavigateToURL(url_viewsource);
+
+ EXPECT_FALSE(IsPageMenuCommandEnabled(IDC_VIEW_SOURCE));
+}
+
diff --git a/chrome/browser/tab_contents/web_contents.cc b/chrome/browser/tab_contents/web_contents.cc
new file mode 100644
index 0000000..512c35a
--- /dev/null
+++ b/chrome/browser/tab_contents/web_contents.cc
@@ -0,0 +1,1796 @@
+// 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 "chrome/browser/tab_contents/web_contents.h"
+
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/file_version_info.h"
+#include "base/process_util.h"
+#include "chrome/app/locales/locale_settings.h"
+#include "chrome/browser/autofill_manager.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/cache_manager_host.h"
+#include "chrome/browser/character_encoding.h"
+#include "chrome/browser/dom_operation_notification_details.h"
+#include "chrome/browser/download/download_manager.h"
+#include "chrome/browser/download/download_request_manager.h"
+#include "chrome/browser/find_notification_details.h"
+#include "chrome/browser/google_util.h"
+#include "chrome/browser/js_before_unload_handler.h"
+#include "chrome/browser/jsmessage_box_handler.h"
+#include "chrome/browser/load_from_memory_cache_details.h"
+#include "chrome/browser/load_notification_details.h"
+#include "chrome/browser/modal_html_dialog_delegate.h"
+#include "chrome/browser/password_manager.h"
+#include "chrome/browser/plugin_installer.h"
+#include "chrome/browser/plugin_service.h"
+#include "chrome/browser/printing/print_job.h"
+#include "chrome/browser/render_view_host.h"
+#include "chrome/browser/render_widget_host_view_win.h" // TODO(brettw) delete me.
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/tab_contents/web_contents_view.h"
+#include "chrome/browser/tab_contents/web_contents_view_win.h"
+#include "chrome/browser/template_url_fetcher.h"
+#include "chrome/browser/template_url_model.h"
+#include "chrome/browser/views/hung_renderer_view.h" // TODO(brettw) delete me.
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "chrome/common/resource_bundle.h"
+#include "net/base/mime_util.h"
+#include "net/base/registry_controlled_domain.h"
+#include "webkit/glue/webkit_glue.h"
+
+#include "generated_resources.h"
+
+// Cross-Site Navigations
+//
+// If a WebContents 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 WebContents. 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 WebContents 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.
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+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;
+
+// Minimum amount of time in ms that has to elapse since the download shelf was
+// shown for us to hide it when navigating away from the current page.
+const int kDownloadShelfHideDelay = 5000;
+
+const wchar_t kLinkDoctorBaseURL[] =
+ L"http://linkhelp.clients.google.com/tbproxy/lh/fixurl";
+
+// The printer icon in shell32.dll. That's a standard icon user will quickly
+// recognize.
+const int kShell32PrinterIcon = 17;
+
+// The list of prefs we want to observe.
+const wchar_t* kPrefsToObserve[] = {
+ prefs::kAlternateErrorPagesEnabled,
+ prefs::kWebKitJavaEnabled,
+ prefs::kWebKitJavascriptEnabled,
+ prefs::kWebKitLoadsImagesAutomatically,
+ prefs::kWebKitPluginsEnabled,
+ prefs::kWebKitUsesUniversalDetector,
+ prefs::kWebKitSerifFontFamily,
+ prefs::kWebKitSansSerifFontFamily,
+ prefs::kWebKitFixedFontFamily,
+ prefs::kWebKitDefaultFontSize,
+ prefs::kWebKitDefaultFixedFontSize,
+ 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);
+
+// Limit on the number of suggestions to appear in the pop-up menu under an
+// text input element in a form.
+const int kMaxAutofillMenuItems = 6;
+
+void InitWebContentsClass() {
+ static bool web_contents_class_initialized = false;
+ if (!web_contents_class_initialized) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ web_contents_class_initialized = true;
+ }
+}
+
+// Returns true if the entry's transition type is FORM_SUBMIT.
+bool IsFormSubmit(const NavigationEntry* entry) {
+ return (PageTransition::StripQualifier(entry->transition_type()) ==
+ PageTransition::FORM_SUBMIT);
+}
+
+} // namespace
+
+class WebContents::GearsCreateShortcutCallbackFunctor {
+ public:
+ explicit GearsCreateShortcutCallbackFunctor(WebContents* contents)
+ : contents_(contents) {}
+
+ void Run(const GearsShortcutData& shortcut_data, bool success) {
+ if (contents_)
+ contents_->OnGearsCreateShortcutDone(shortcut_data, success);
+ delete this;
+ }
+ void Cancel() {
+ contents_ = NULL;
+ }
+
+ private:
+ WebContents* contents_;
+};
+
+WebContents::WebContents(Profile* profile,
+ SiteInstance* site_instance,
+ RenderViewHostFactory* render_view_factory,
+ int routing_id,
+ HANDLE modal_dialog_event)
+ : TabContents(TAB_CONTENTS_WEB),
+ view_(new WebContentsViewWin(this)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ render_manager_(render_view_factory, this, this)),
+ render_view_factory_(render_view_factory),
+ received_page_title_(false),
+ is_starred_(false),
+ printing_(*this),
+ notify_disconnection_(false),
+ message_box_active_(CreateEvent(NULL, TRUE, FALSE, NULL)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(fav_icon_helper_(this)),
+ suppress_javascript_messages_(false),
+ load_state_(net::LOAD_STATE_IDLE) {
+ InitWebContentsClass();
+
+ pending_install_.page_id = 0;
+ pending_install_.callback_functor = NULL;
+
+ render_manager_.Init(profile, site_instance, routing_id, modal_dialog_event);
+
+ // Register for notifications about all interested prefs change.
+ PrefService* prefs = profile->GetPrefs();
+ if (prefs) {
+ for (int i = 0; i < kPrefsToObserveLength; ++i)
+ prefs->AddPrefObserver(kPrefsToObserve[i], this);
+ }
+
+ // Register for notifications about URL starredness changing on any profile.
+ NotificationService::current()->
+ AddObserver(this, NOTIFY_URLS_STARRED, NotificationService::AllSources());
+ NotificationService::current()->
+ AddObserver(this, NOTIFY_BOOKMARK_MODEL_LOADED,
+ NotificationService::AllSources());
+ NotificationService::current()->
+ AddObserver(this, NOTIFY_RENDER_WIDGET_HOST_DESTROYED,
+ NotificationService::AllSources());
+}
+
+WebContents::~WebContents() {
+ if (web_app_.get())
+ web_app_->RemoveObserver(this);
+ if (pending_install_.callback_functor)
+ pending_install_.callback_functor->Cancel();
+ NotificationService::current()->
+ RemoveObserver(this, NOTIFY_RENDER_WIDGET_HOST_DESTROYED,
+ NotificationService::AllSources());
+}
+
+// static
+void WebContents::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterBooleanPref(prefs::kAlternateErrorPagesEnabled, true);
+
+ WebPreferences pref_defaults;
+ prefs->RegisterBooleanPref(prefs::kWebKitJavascriptEnabled,
+ pref_defaults.javascript_enabled);
+ prefs->RegisterBooleanPref(
+ prefs::kWebKitJavascriptCanOpenWindowsAutomatically, true);
+ prefs->RegisterBooleanPref(prefs::kWebKitLoadsImagesAutomatically,
+ pref_defaults.loads_images_automatically);
+ prefs->RegisterBooleanPref(prefs::kWebKitPluginsEnabled,
+ pref_defaults.plugins_enabled);
+ prefs->RegisterBooleanPref(prefs::kWebKitDomPasteEnabled,
+ pref_defaults.dom_paste_enabled);
+ prefs->RegisterBooleanPref(prefs::kWebKitShrinksStandaloneImagesToFit,
+ pref_defaults.shrinks_standalone_images_to_fit);
+ prefs->RegisterBooleanPref(prefs::kWebKitDeveloperExtrasEnabled,
+ true);
+ prefs->RegisterBooleanPref(prefs::kWebKitTextAreasAreResizable,
+ pref_defaults.text_areas_are_resizable);
+ prefs->RegisterBooleanPref(prefs::kWebKitJavaEnabled,
+ pref_defaults.java_enabled);
+
+ prefs->RegisterLocalizedStringPref(prefs::kAcceptLanguages,
+ IDS_ACCEPT_LANGUAGES);
+ prefs->RegisterLocalizedStringPref(prefs::kDefaultCharset,
+ IDS_DEFAULT_ENCODING);
+ prefs->RegisterLocalizedBooleanPref(prefs::kWebKitStandardFontIsSerif,
+ IDS_STANDARD_FONT_IS_SERIF);
+ prefs->RegisterLocalizedStringPref(prefs::kWebKitFixedFontFamily,
+ IDS_FIXED_FONT_FAMILY);
+ prefs->RegisterLocalizedStringPref(prefs::kWebKitSerifFontFamily,
+ IDS_SERIF_FONT_FAMILY);
+ prefs->RegisterLocalizedStringPref(prefs::kWebKitSansSerifFontFamily,
+ IDS_SANS_SERIF_FONT_FAMILY);
+ prefs->RegisterLocalizedStringPref(prefs::kWebKitCursiveFontFamily,
+ IDS_CURSIVE_FONT_FAMILY);
+ prefs->RegisterLocalizedStringPref(prefs::kWebKitFantasyFontFamily,
+ IDS_FANTASY_FONT_FAMILY);
+ prefs->RegisterLocalizedIntegerPref(prefs::kWebKitDefaultFontSize,
+ IDS_DEFAULT_FONT_SIZE);
+ prefs->RegisterLocalizedIntegerPref(prefs::kWebKitDefaultFixedFontSize,
+ IDS_DEFAULT_FIXED_FONT_SIZE);
+ prefs->RegisterLocalizedIntegerPref(prefs::kWebKitMinimumFontSize,
+ IDS_MINIMUM_FONT_SIZE);
+ prefs->RegisterLocalizedIntegerPref(prefs::kWebKitMinimumLogicalFontSize,
+ IDS_MINIMUM_LOGICAL_FONT_SIZE);
+ prefs->RegisterLocalizedBooleanPref(prefs::kWebKitUsesUniversalDetector,
+ IDS_USES_UNIVERSAL_DETECTOR);
+ prefs->RegisterLocalizedStringPref(prefs::kStaticEncodings,
+ IDS_STATIC_ENCODING_LIST);
+}
+
+AutofillManager* WebContents::GetAutofillManager() {
+ if (autofill_manager_.get() == NULL)
+ autofill_manager_.reset(new AutofillManager(this));
+ return autofill_manager_.get();
+}
+
+PasswordManager* WebContents::GetPasswordManager() {
+ if (password_manager_.get() == NULL)
+ password_manager_.reset(new PasswordManager(this));
+ return password_manager_.get();
+}
+
+PluginInstaller* WebContents::GetPluginInstaller() {
+ if (plugin_installer_.get() == NULL)
+ plugin_installer_.reset(new PluginInstaller(this));
+ return plugin_installer_.get();
+}
+
+void WebContents::Destroy() {
+ // Tell the notification service we no longer want notifications.
+ NotificationService::current()->
+ RemoveObserver(this, NOTIFY_URLS_STARRED,
+ NotificationService::AllSources());
+ NotificationService::current()->
+ RemoveObserver(this, NOTIFY_BOOKMARK_MODEL_LOADED,
+ NotificationService::AllSources());
+
+ // Destroy the print manager right now since a Print command may be pending.
+ printing_.Destroy();
+
+ // Unregister the notifications of all observed prefs change.
+ PrefService* prefs = profile()->GetPrefs();
+ if (prefs) {
+ for (int i = 0; i < kPrefsToObserveLength; ++i)
+ prefs->RemovePrefObserver(kPrefsToObserve[i], this);
+ }
+
+ cancelable_consumer_.CancelAllRequests();
+
+ // Clean up subwindows like plugins and the find in page bar.
+ view_->OnContentsDestroy();
+
+ NotifyDisconnected();
+ HungRendererWarning::HideForWebContents(this);
+ render_manager_.Shutdown();
+ TabContents::Destroy();
+}
+
+SiteInstance* WebContents::GetSiteInstance() const {
+ return render_manager_.current_host()->site_instance();
+}
+
+SkBitmap WebContents::GetFavIcon() {
+ if (web_app_.get() && IsWebApplicationActive()) {
+ SkBitmap app_icon = web_app_->GetFavIcon();
+ if (!app_icon.isNull())
+ return app_icon;
+ }
+ return TabContents::GetFavIcon();
+}
+
+std::wstring WebContents::GetStatusText() const {
+ if (!is_loading() || load_state_ == net::LOAD_STATE_IDLE)
+ return std::wstring();
+
+ switch (load_state_) {
+ case net::LOAD_STATE_WAITING_FOR_CACHE:
+ return l10n_util::GetString(IDS_LOAD_STATE_WAITING_FOR_CACHE);
+ case net::LOAD_STATE_RESOLVING_PROXY_FOR_URL:
+ return l10n_util::GetString(IDS_LOAD_STATE_RESOLVING_PROXY_FOR_URL);
+ case net::LOAD_STATE_RESOLVING_HOST:
+ return l10n_util::GetString(IDS_LOAD_STATE_RESOLVING_HOST);
+ case net::LOAD_STATE_CONNECTING:
+ return l10n_util::GetString(IDS_LOAD_STATE_CONNECTING);
+ case net::LOAD_STATE_SENDING_REQUEST:
+ return l10n_util::GetString(IDS_LOAD_STATE_SENDING_REQUEST);
+ case net::LOAD_STATE_WAITING_FOR_RESPONSE:
+ return l10n_util::GetStringF(IDS_LOAD_STATE_WAITING_FOR_RESPONSE,
+ load_state_host_);
+ // Ignore net::LOAD_STATE_READING_RESPONSE and net::LOAD_STATE_IDLE
+ }
+
+ return std::wstring();
+}
+
+bool WebContents::NavigateToPendingEntry(bool reload) {
+ NavigationEntry* entry = controller()->GetPendingEntry();
+ RenderViewHost* dest_render_view_host = render_manager_.Navigate(*entry);
+ if (!dest_render_view_host)
+ return false; // Unable to create the desired render view host.
+
+ // Used for page load time metrics.
+ current_load_start_ = TimeTicks::Now();
+
+ // Navigate in the desired RenderViewHost.
+ dest_render_view_host->NavigateToEntry(*entry, reload);
+
+ 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("javascript"))
+ return false;
+ }
+
+ // Clear any provisional password saves - this stops password infobars
+ // showing up on pages the user navigates to while the right page is
+ // loading.
+ GetPasswordManager()->ClearProvisionalSave();
+
+ if (reload && !profile()->IsOffTheRecord()) {
+ HistoryService* history =
+ profile()->GetHistoryService(Profile::IMPLICIT_ACCESS);
+ if (history)
+ history->SetFavIconOutOfDateForPage(entry->url());
+ }
+
+ return true;
+}
+
+void WebContents::Stop() {
+ render_manager_.Stop();
+ printing_.Stop();
+}
+
+void WebContents::Cut() {
+ render_view_host()->Cut();
+}
+
+void WebContents::Copy() {
+ render_view_host()->Copy();
+}
+
+void WebContents::Paste() {
+ render_view_host()->Paste();
+}
+
+void WebContents::DisassociateFromPopupCount() {
+ render_view_host()->DisassociateFromPopupCount();
+}
+
+void WebContents::DidBecomeSelected() {
+ TabContents::DidBecomeSelected();
+
+ if (render_widget_host_view())
+ render_widget_host_view()->DidBecomeSelected();
+
+ CacheManagerHost::GetInstance()->ObserveActivity(process()->host_id());
+}
+
+void WebContents::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 WebContents::Destroy(), which removes
+ // the |render_view_host()|; then when we actually destroy the window,
+ // OnWindowPosChanged() notices and calls HideContents() (which calls us).
+ if (render_widget_host_view())
+ render_widget_host_view()->WasHidden();
+
+ // Loop through children and send WasHidden to them, too.
+ int count = static_cast<int>(child_windows_.size());
+ for (int i = count - 1; i >= 0; --i) {
+ ConstrainedWindow* window = child_windows_.at(i);
+ window->WasHidden();
+ }
+ }
+
+ TabContents::WasHidden();
+}
+
+void WebContents::ShowContents() {
+ if (render_widget_host_view())
+ render_widget_host_view()->DidBecomeSelected();
+
+ // Loop through children and send DidBecomeSelected to them, too.
+ int count = static_cast<int>(child_windows_.size());
+ for (int i = count - 1; i >= 0; --i) {
+ ConstrainedWindow* window = child_windows_.at(i);
+ window->DidBecomeSelected();
+ }
+}
+
+void WebContents::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 WebContents.
+ WasHidden();
+}
+
+void WebContents::SetDownloadShelfVisible(bool visible) {
+ TabContents::SetDownloadShelfVisible(visible);
+ if (visible) {
+ // Always set this value as it reflects the last time the download shelf
+ // was made visible (even if it was already visible).
+ last_download_shelf_show_ = TimeTicks::Now();
+ }
+}
+
+void WebContents::PopupNotificationVisibilityChanged(bool visible) {
+ render_view_host()->PopupNotificationVisibilityChanged(visible);
+}
+
+// Stupid view pass-throughs
+void WebContents::CreateView() {
+ view_->CreateView();
+}
+HWND WebContents::GetContainerHWND() const {
+ return view_->GetContainerHWND();
+}
+HWND WebContents::GetContentHWND() {
+ return view_->GetContentHWND();
+}
+void WebContents::GetContainerBounds(gfx::Rect *out) const {
+ view_->GetContainerBounds(out);
+}
+
+void WebContents::SetWebApp(WebApp* web_app) {
+ if (web_app_.get()) {
+ web_app_->RemoveObserver(this);
+ web_app_->SetWebContents(NULL);
+ }
+
+ web_app_ = web_app;
+ if (web_app) {
+ web_app->AddObserver(this);
+ web_app_->SetWebContents(this);
+ }
+}
+
+bool WebContents::IsWebApplication() const {
+ return (web_app_.get() != NULL);
+}
+
+void WebContents::CreateShortcut() {
+ NavigationEntry* entry = controller()->GetLastCommittedEntry();
+ if (!entry)
+ return;
+
+ // We only allow one pending install request. By resetting the page id we
+ // effectively cancel the pending install request.
+ pending_install_.page_id = entry->page_id();
+ pending_install_.icon = GetFavIcon();
+ pending_install_.title = GetTitle();
+ pending_install_.url = GetURL();
+ if (pending_install_.callback_functor) {
+ pending_install_.callback_functor->Cancel();
+ pending_install_.callback_functor = NULL;
+ }
+ DCHECK(!pending_install_.icon.isNull()) << "Menu item should be disabled.";
+ if (pending_install_.title.empty())
+ pending_install_.title = UTF8ToWide(GetURL().spec());
+
+ // Request the application info. When done OnDidGetApplicationInfo is invoked
+ // and we'll create the shortcut.
+ render_view_host()->GetApplicationInfo(pending_install_.page_id);
+}
+
+void WebContents::OnJavaScriptMessageBoxClosed(IPC::Message* reply_msg,
+ bool success,
+ const std::wstring& prompt) {
+ last_javascript_message_dismissal_ = TimeTicks::Now();
+ render_manager_.OnJavaScriptMessageBoxClosed(reply_msg, success, prompt);
+}
+
+void WebContents::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;
+ }
+
+ // Get our user preference state.
+ PrefService* prefs = profile()->GetPrefs();
+ DCHECK(prefs);
+
+ std::wstring suggest_name =
+ SavePackage::GetSuggestNameForSaveAs(prefs, GetTitle());
+
+ SavePackage::SavePackageParam param(contents_mime_type());
+ param.prefs = prefs;
+
+ // TODO(rocking): Use new asynchronous dialog boxes to prevent the SaveAs
+ // dialog blocking the UI thread. See bug: http://b/issue?id=1129694.
+ if (SavePackage::GetSaveInfo(suggest_name, GetContainerHWND(), &param,
+ profile()->GetDownloadManager()))
+ SavePage(param.saved_main_file_path, param.dir, param.save_type);
+}
+
+void WebContents::SavePage(const std::wstring& main_file,
+ const std::wstring& dir_path,
+ SavePackage::SavePackageType save_type) {
+ // Stop the page from navigating.
+ Stop();
+
+ save_package_ = new SavePackage(this, save_type, main_file, dir_path);
+ save_package_->Init();
+}
+
+void WebContents::PrintPreview() {
+ // We can't print interstitial page for now.
+ if (showing_interstitial_page())
+ return;
+
+ // If we have a find bar it needs to hide as well.
+ view_->HideFindBar(false);
+
+ // We don't show the print preview for the beta, only the print dialog.
+ printing_.ShowPrintDialog();
+}
+
+bool WebContents::PrintNow() {
+ // We can't print interstitial page for now.
+ if (showing_interstitial_page())
+ return false;
+
+ // If we have a find bar it needs to hide as well.
+ view_->HideFindBar(false);
+
+ return printing_.PrintNow();
+}
+
+bool WebContents::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 WebContents::SetInitialFocus(bool reverse) {
+ render_view_host()->SetInitialFocus(reverse);
+}
+
+// Notifies the RenderWidgetHost instance about the fact that the page is
+// loading, or done loading and calls the base implementation.
+void WebContents::SetIsLoading(bool is_loading,
+ LoadNotificationDetails* details) {
+ if (!is_loading) {
+ load_state_ = net::LOAD_STATE_IDLE;
+ load_state_host_.clear();
+ }
+
+ TabContents::SetIsLoading(is_loading, details);
+ render_manager_.SetIsLoading(is_loading);
+}
+
+RenderViewHostDelegate::View* WebContents::GetViewDelegate() const {
+ return view_.get();
+}
+
+RenderViewHostDelegate::Save* WebContents::GetSaveDelegate() const {
+ return save_package_.get(); // May be NULL, but we can return NULL.
+}
+
+Profile* WebContents::GetProfile() const {
+ return profile();
+}
+
+void WebContents::RendererReady(RenderViewHost* rvh) {
+ if (rvh != render_view_host()) {
+ // Don't notify the world, since this came from a renderer in the
+ // background.
+ return;
+ }
+
+ NotifyConnected();
+ SetIsCrashed(false);
+}
+
+void WebContents::RendererGone(RenderViewHost* rvh) {
+ // Ask the print preview if this renderer was valuable.
+ if (!printing_.OnRendererGone(rvh))
+ return;
+ if (rvh != render_view_host()) {
+ // The pending page's RenderViewHost is gone.
+ return;
+ }
+
+ SetIsLoading(false, NULL);
+ NotifyDisconnected();
+ SetIsCrashed(true);
+
+ // Force an invalidation to render sad tab. The view will notice we crashed
+ // when it paints.
+ view_->Invalidate();
+
+ // Hide any visible hung renderer warning for this web contents' process.
+ HungRendererWarning::HideForWebContents(this);
+}
+
+void WebContents::DidNavigate(RenderViewHost* rvh,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ if (PageTransition::IsMainFrame(params.transition))
+ render_manager_.DidNavigateMainFrame(rvh);
+
+ // We can't do anything about navigations when we're inactive.
+ if (!controller() || !is_active())
+ return;
+
+ // 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;
+ if (!controller()->RendererDidNavigate(params, &details))
+ 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 WebContents::UpdateState(RenderViewHost* rvh,
+ int32 page_id,
+ const std::string& state) {
+ DCHECK(rvh == render_view_host());
+ if (!controller())
+ return;
+
+ // 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(
+ type(), 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 WebContents::UpdateTitle(RenderViewHost* rvh,
+ int32 page_id, const std::wstring& title) {
+ if (!controller())
+ return;
+
+ // 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(type(),
+ GetSiteInstance(),
+ 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 WebContents::UpdateEncoding(RenderViewHost* render_view_host,
+ const std::wstring& encoding) {
+ set_encoding(encoding);
+}
+
+void WebContents::UpdateTargetURL(int32 page_id, const GURL& url) {
+ if (delegate())
+ delegate()->UpdateTargetURL(this, url);
+}
+
+void WebContents::UpdateThumbnail(const GURL& url,
+ const SkBitmap& bitmap,
+ const ThumbnailScore& score) {
+ // Tell History about this thumbnail
+ HistoryService* hs;
+ if (!profile()->IsOffTheRecord() &&
+ (hs = profile()->GetHistoryService(Profile::IMPLICIT_ACCESS))) {
+ hs->SetPageThumbnail(url, bitmap, score);
+ }
+}
+
+void WebContents::Close(RenderViewHost* rvh) {
+ // Ignore this if it comes from a RenderViewHost that we aren't showing.
+ if (delegate() && rvh == render_view_host())
+ delegate()->CloseContents(this);
+}
+
+void WebContents::RequestMove(const gfx::Rect& new_bounds) {
+ if (delegate() && delegate()->IsPopup(this))
+ delegate()->MoveContents(this, new_bounds);
+}
+
+void WebContents::DidStartLoading(RenderViewHost* rvh, int32 page_id) {
+ SetIsLoading(true, NULL);
+}
+
+void WebContents::DidStopLoading(RenderViewHost* rvh, int32 page_id) {
+ scoped_ptr<LoadNotificationDetails> details;
+ if (controller()) {
+ 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) {
+ scoped_ptr<base::ProcessMetrics> metrics(
+ base::ProcessMetrics::CreateProcessMetrics(
+ process()->process().handle()));
+
+ TimeDelta elapsed = TimeTicks::Now() - current_load_start_;
+
+ details.reset(new LoadNotificationDetails(
+ entry->display_url(),
+ entry->transition_type(),
+ elapsed,
+ controller(),
+ controller()->GetCurrentEntryIndex()));
+ }
+ }
+
+ // Tell PasswordManager we've finished a page load, which serves as a
+ // green light to save pending passwords and reset itself.
+ GetPasswordManager()->DidStopLoading();
+
+ SetIsLoading(false, details.get());
+}
+
+void WebContents::DidStartProvisionalLoadForFrame(
+ RenderViewHost* render_view_host,
+ bool is_main_frame,
+ const GURL& url) {
+ ProvisionalLoadDetails details(is_main_frame,
+ controller()->IsURLInPageNavigation(url),
+ url, std::string(), false);
+ NotificationService::current()->
+ Notify(NOTIFY_FRAME_PROVISIONAL_LOAD_START,
+ Source<NavigationController>(controller()),
+ Details<ProvisionalLoadDetails>(&details));
+}
+
+void WebContents::DidRedirectProvisionalLoad(int32 page_id,
+ const GURL& source_url,
+ const GURL& target_url) {
+ NavigationEntry* entry;
+ if (page_id == -1) {
+ entry = controller()->GetPendingEntry();
+ } else {
+ entry = controller()->GetEntryWithPageID(type(), GetSiteInstance(),
+ page_id);
+ }
+ if (!entry || entry->tab_type() != type() || entry->url() != source_url)
+ return;
+ entry->set_url(target_url);
+}
+
+void WebContents::DidLoadResourceFromMemoryCache(
+ const GURL& url,
+ const std::string& security_info) {
+ if (!controller())
+ return;
+
+ // Send out a notification that we loaded a resource from our memory cache.
+ int cert_id, cert_status, security_bits;
+ SSLManager::DeserializeSecurityInfo(security_info,
+ &cert_id, &cert_status,
+ &security_bits);
+ LoadFromMemoryCacheDetails details(url, cert_id, cert_status);
+
+ NotificationService::current()->
+ Notify(NOTIFY_LOAD_FROM_MEMORY_CACHE,
+ Source<NavigationController>(controller()),
+ Details<LoadFromMemoryCacheDetails>(&details));
+}
+
+void WebContents::DidFailProvisionalLoadWithError(
+ RenderViewHost* render_view_host,
+ bool is_main_frame,
+ int error_code,
+ const GURL& url) {
+ if (!controller())
+ return;
+
+ 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()->GetPendingEntry();
+ if (pending_entry && pending_entry->url() == url)
+ controller()->DiscardNonCommittedEntries();
+
+ 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(url),
+ url, std::string(), false);
+ details.set_error_code(error_code);
+
+ NotificationService::current()->
+ Notify(NOTIFY_FAIL_PROVISIONAL_LOAD_WITH_ERROR,
+ Source<NavigationController>(controller()),
+ Details<ProvisionalLoadDetails>(&details));
+}
+
+void WebContents::UpdateFavIconURL(RenderViewHost* render_view_host,
+ int32 page_id,
+ const GURL& icon_url) {
+ fav_icon_helper_.SetFavIconURL(icon_url);
+}
+
+void WebContents::DidDownloadImage(
+ RenderViewHost* render_view_host,
+ int id,
+ const GURL& image_url,
+ bool errored,
+ const SkBitmap& image) {
+ // A notification for downloading would be more flexible, but for now I'm
+ // forwarding to the two places that could possibly have initiated the
+ // request. If we end up with another place invoking DownloadImage, probably
+ // best to refactor out into notification service, or something similar.
+ if (errored)
+ fav_icon_helper_.FavIconDownloadFailed(id);
+ else
+ fav_icon_helper_.SetFavIcon(id, image_url, image);
+ if (web_app_.get() && !errored)
+ web_app_->SetImage(image_url, image);
+}
+
+void WebContents::RequestOpenURL(const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition) {
+ OpenURL(url, referrer, disposition, PageTransition::LINK);
+}
+
+void WebContents::DomOperationResponse(const std::string& json_string,
+ int automation_id) {
+ DomOperationNotificationDetails details(json_string, automation_id);
+ NotificationService::current()->Notify(
+ NOTIFY_DOM_OPERATION_RESPONSE, Source<WebContents>(this),
+ Details<DomOperationNotificationDetails>(&details));
+}
+
+void WebContents::ProcessExternalHostMessage(const std::string& receiver,
+ const std::string& message) {
+ if (delegate())
+ delegate()->ForwardMessageToExternalHost(receiver, message);
+}
+
+void WebContents::GoToEntryAtOffset(int offset) {
+ if (!controller())
+ return;
+ controller()->GoToOffset(offset);
+}
+
+void WebContents::GetHistoryListCount(int* back_list_count,
+ int* forward_list_count) {
+ *back_list_count = 0;
+ *forward_list_count = 0;
+
+ if (controller()) {
+ int current_index = controller()->GetLastCommittedEntryIndex();
+ *back_list_count = current_index;
+ *forward_list_count = controller()->GetEntryCount() - current_index - 1;
+ }
+}
+
+void WebContents::RunFileChooser(bool multiple_files,
+ const std::wstring& title,
+ const std::wstring& default_file,
+ const std::wstring& filter) {
+ HWND toplevel_hwnd = GetAncestor(GetContainerHWND(), GA_ROOT);
+ if (!select_file_dialog_.get())
+ select_file_dialog_ = SelectFileDialog::Create(this);
+ SelectFileDialog::Type dialog_type =
+ multiple_files ? SelectFileDialog::SELECT_OPEN_MULTI_FILE :
+ SelectFileDialog::SELECT_OPEN_FILE;
+ select_file_dialog_->SelectFile(dialog_type, title, default_file, filter,
+ std::wstring(), toplevel_hwnd, NULL);
+}
+
+void WebContents::RunJavaScriptMessage(
+ const std::wstring& message,
+ const std::wstring& default_prompt,
+ 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).
+ bool suppress_this_message = suppress_javascript_messages_;
+ if (delegate())
+ suppress_this_message |=
+ (delegate()->GetConstrainingContents(this) != NULL);
+
+ *did_suppress_message = suppress_this_message;
+
+ if (!suppress_this_message) {
+ TimeDelta time_since_last_message(
+ 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 <
+ TimeDelta::FromMilliseconds(kJavascriptMessageExpectedDelay))
+ show_suppress_checkbox = true;
+
+ JavascriptMessageBoxHandler::RunJavascriptMessageBox(this,
+ 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".
+ OnJavaScriptMessageBoxClosed(reply_msg, false, L"");
+ }
+}
+
+void WebContents::RunBeforeUnloadConfirm(const std::wstring& message,
+ IPC::Message* reply_msg) {
+ JavascriptBeforeUnloadHandler::RunBeforeUnloadDialog(this, message,
+ reply_msg);
+}
+
+void WebContents::ShowModalHTMLDialog(const GURL& url, int width, int height,
+ const std::string& json_arguments,
+ IPC::Message* reply_msg) {
+ if (delegate()) {
+ ModalHtmlDialogDelegate* dialog_delegate =
+ new ModalHtmlDialogDelegate(url, width, height, json_arguments,
+ reply_msg, this);
+ delegate()->ShowHtmlDialog(dialog_delegate, NULL);
+ }
+}
+
+void WebContents::PasswordFormsSeen(
+ const std::vector<PasswordForm>& forms) {
+ GetPasswordManager()->PasswordFormsSeen(forms);
+}
+
+void WebContents::AutofillFormSubmitted(
+ const AutofillForm& form) {
+ GetAutofillManager()->AutofillFormSubmitted(form);
+}
+
+void WebContents::GetAutofillSuggestions(const std::wstring& field_name,
+ const std::wstring& user_text, int64 node_id, int request_id) {
+ GetAutofillManager()->FetchValuesForName(field_name, user_text,
+ kMaxAutofillMenuItems, node_id, request_id);
+}
+
+// Checks to see if we should generate a keyword based on the OSDD, and if
+// necessary uses TemplateURLFetcher to download the OSDD and create a keyword.
+void WebContents::PageHasOSDD(RenderViewHost* render_view_host,
+ int32 page_id, const GURL& url,
+ bool autodetected) {
+ // Make sure page_id is the current page, and the TemplateURLModel is loaded.
+ DCHECK(url.is_valid());
+ if (!controller() || !IsActiveEntry(page_id))
+ return;
+ TemplateURLModel* url_model = profile()->GetTemplateURLModel();
+ if (!url_model)
+ return;
+ if (!url_model->loaded()) {
+ url_model->Load();
+ return;
+ }
+ if (!profile()->GetTemplateURLFetcher())
+ return;
+
+ if (profile()->IsOffTheRecord())
+ return;
+
+ const NavigationEntry* entry = controller()->GetLastCommittedEntry();
+ DCHECK(entry);
+
+ const NavigationEntry* base_entry = entry;
+ if (IsFormSubmit(base_entry)) {
+ // If the current page is a form submit, find the last page that was not
+ // a form submit and use its url to generate the keyword from.
+ int index = controller()->GetLastCommittedEntryIndex() - 1;
+ while (index >= 0 && IsFormSubmit(controller()->GetEntryAtIndex(index)))
+ index--;
+ if (index >= 0)
+ base_entry = controller()->GetEntryAtIndex(index);
+ else
+ base_entry = NULL;
+ }
+
+ // We want to use the user typed URL if available since that represents what
+ // the user typed to get here, and fall back on the regular URL if not.
+ if (!base_entry)
+ return;
+ GURL keyword_url = base_entry->user_typed_url().is_valid() ?
+ base_entry->user_typed_url() : base_entry->url();
+ if (!keyword_url.is_valid())
+ return;
+ std::wstring keyword = TemplateURLModel::GenerateKeyword(keyword_url,
+ autodetected);
+ if (keyword.empty())
+ return;
+ const TemplateURL* template_url =
+ url_model->GetTemplateURLForKeyword(keyword);
+ if (template_url && (!template_url->safe_for_autoreplace() ||
+ template_url->originating_url() == url)) {
+ // Either there is a user created TemplateURL for this keyword, or the
+ // keyword has the same OSDD url and we've parsed it.
+ return;
+ }
+
+ // Download the OpenSearch description document. If this is successful a
+ // new keyword will be created when done.
+ profile()->GetTemplateURLFetcher()->ScheduleDownload(
+ keyword,
+ url,
+ base_entry->favicon().url(),
+ GetAncestor(view_->GetContainerHWND(), GA_ROOT),
+ autodetected);
+}
+
+void WebContents::InspectElementReply(int num_resources) {
+ // We have received reply from inspect element request. Notify the
+ // automation provider in case we need to notify automation client.
+ NotificationService::current()->
+ Notify(NOTIFY_DOM_INSPECT_ELEMENT_RESPONSE, Source<WebContents>(this),
+ Details<int>(&num_resources));
+}
+
+void WebContents::DidGetPrintedPagesCount(int cookie, int number_pages) {
+ printing_.DidGetPrintedPagesCount(cookie, number_pages);
+}
+
+void WebContents::DidPrintPage(const ViewHostMsg_DidPrintPage_Params& params) {
+ printing_.DidPrintPage(params);
+}
+
+GURL WebContents::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(kLinkDoctorBaseURL));
+ url = google_util::AppendGoogleTLDParam(url);
+ }
+ return url;
+}
+
+WebPreferences WebContents::GetWebkitPrefs() {
+ // Initialize web_preferences_ to chrome defaults.
+ WebPreferences web_prefs;
+ PrefService* prefs = profile()->GetPrefs();
+
+ web_prefs.fixed_font_family =
+ prefs->GetString(prefs::kWebKitFixedFontFamily);
+ web_prefs.serif_font_family =
+ prefs->GetString(prefs::kWebKitSerifFontFamily);
+ web_prefs.sans_serif_font_family =
+ prefs->GetString(prefs::kWebKitSansSerifFontFamily);
+ if (prefs->GetBoolean(prefs::kWebKitStandardFontIsSerif))
+ web_prefs.standard_font_family = web_prefs.serif_font_family;
+ else
+ web_prefs.standard_font_family = web_prefs.sans_serif_font_family;
+ web_prefs.cursive_font_family =
+ prefs->GetString(prefs::kWebKitCursiveFontFamily);
+ web_prefs.fantasy_font_family =
+ prefs->GetString(prefs::kWebKitFantasyFontFamily);
+
+ web_prefs.default_font_size =
+ prefs->GetInteger(prefs::kWebKitDefaultFontSize);
+ web_prefs.default_fixed_font_size =
+ prefs->GetInteger(prefs::kWebKitDefaultFixedFontSize);
+ web_prefs.minimum_font_size =
+ prefs->GetInteger(prefs::kWebKitMinimumFontSize);
+ web_prefs.minimum_logical_font_size =
+ prefs->GetInteger(prefs::kWebKitMinimumLogicalFontSize);
+
+ web_prefs.default_encoding = prefs->GetString(prefs::kDefaultCharset);
+
+ web_prefs.javascript_can_open_windows_automatically =
+ prefs->GetBoolean(prefs::kWebKitJavascriptCanOpenWindowsAutomatically);
+ web_prefs.dom_paste_enabled =
+ prefs->GetBoolean(prefs::kWebKitDomPasteEnabled);
+ web_prefs.shrinks_standalone_images_to_fit =
+ prefs->GetBoolean(prefs::kWebKitShrinksStandaloneImagesToFit);
+
+ { // Command line switches are used for preferences with no user interface.
+ CommandLine command_line;
+ web_prefs.developer_extras_enabled =
+ !command_line.HasSwitch(switches::kDisableDevTools) &&
+ prefs->GetBoolean(prefs::kWebKitDeveloperExtrasEnabled);
+ web_prefs.javascript_enabled =
+ !command_line.HasSwitch(switches::kDisableJavaScript) &&
+ prefs->GetBoolean(prefs::kWebKitJavascriptEnabled);
+ web_prefs.plugins_enabled =
+ !command_line.HasSwitch(switches::kDisablePlugins) &&
+ prefs->GetBoolean(prefs::kWebKitPluginsEnabled);
+ web_prefs.java_enabled =
+ !command_line.HasSwitch(switches::kDisableJava) &&
+ prefs->GetBoolean(prefs::kWebKitJavaEnabled);
+ web_prefs.loads_images_automatically =
+ !command_line.HasSwitch(switches::kDisableImages) &&
+ prefs->GetBoolean(prefs::kWebKitLoadsImagesAutomatically);
+ web_prefs.uses_page_cache =
+ command_line.HasSwitch(switches::kEnableFastback);
+ }
+
+ web_prefs.uses_universal_detector =
+ prefs->GetBoolean(prefs::kWebKitUsesUniversalDetector);
+ web_prefs.text_areas_are_resizable =
+ prefs->GetBoolean(prefs::kWebKitTextAreasAreResizable);
+
+ // User CSS is currently disabled because it crashes chrome. See
+ // webkit/glue/webpreferences.h for more details.
+
+ // Make sure we will set the default_encoding with canonical encoding name.
+ web_prefs.default_encoding =
+ CharacterEncoding::GetCanonicalEncodingNameByAliasName(
+ web_prefs.default_encoding);
+ if (web_prefs.default_encoding.empty()) {
+ prefs->ClearPref(prefs::kDefaultCharset);
+ web_prefs.default_encoding = prefs->GetString(
+ prefs::kDefaultCharset);
+ }
+ DCHECK(!web_prefs.default_encoding.empty());
+ return web_prefs;
+}
+
+void WebContents::OnMissingPluginStatus(int status) {
+ GetPluginInstaller()->OnMissingPluginStatus(status);
+}
+
+void WebContents::OnCrashedPlugin(const FilePath& plugin_path) {
+ DCHECK(!plugin_path.value().empty());
+
+ std::wstring plugin_name = plugin_path.ToWStringHack();
+ scoped_ptr<FileVersionInfo> version_info(
+ FileVersionInfo::CreateFileVersionInfo(plugin_path));
+ if (version_info.get()) {
+ const std::wstring& product_name = version_info->product_name();
+ if (!product_name.empty())
+ plugin_name = product_name;
+ }
+ AddInfoBar(new SimpleAlertInfoBarDelegate(
+ this, l10n_util::GetStringF(IDS_PLUGIN_CRASHED_PROMPT, plugin_name),
+ NULL));
+}
+
+void WebContents::OnJSOutOfMemory() {
+ AddInfoBar(new SimpleAlertInfoBarDelegate(
+ this, l10n_util::GetString(IDS_JS_OUT_OF_MEMORY_PROMPT), NULL));
+}
+
+bool WebContents::CanBlur() const {
+ return delegate() ? delegate()->CanBlur() : true;
+}
+
+void WebContents::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->UnloadListenerHasFired();
+
+ 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())
+ HungRendererWarning::ShowForWebContents(this);
+}
+
+void WebContents::RendererResponsive(RenderViewHost* render_view_host) {
+ HungRendererWarning::HideForWebContents(this);
+}
+
+void WebContents::LoadStateChanged(const GURL& url,
+ net::LoadState load_state) {
+ load_state_ = load_state;
+ load_state_host_ = UTF8ToWide(url.host());
+ if (load_state_ == net::LOAD_STATE_READING_RESPONSE)
+ SetNotWaitingForResponse();
+ if (is_loading())
+ NotifyNavigationStateChanged(INVALIDATE_LOAD);
+}
+
+void WebContents::OnDidGetApplicationInfo(
+ int32 page_id,
+ const webkit_glue::WebApplicationInfo& info) {
+ if (pending_install_.page_id != page_id)
+ return; // The user clicked create on a separate page. Ignore this.
+
+ pending_install_.callback_functor =
+ new GearsCreateShortcutCallbackFunctor(this);
+ GearsCreateShortcut(
+ info, pending_install_.title, pending_install_.url, pending_install_.icon,
+ NewCallback(pending_install_.callback_functor,
+ &GearsCreateShortcutCallbackFunctor::Run));
+}
+
+void WebContents::OnEnterOrSpace() {
+ // See comment in RenderViewHostDelegate::OnEnterOrSpace as to why we do this.
+ DownloadRequestManager* drm = g_browser_process->download_request_manager();
+ if (drm)
+ drm->OnUserGesture(this);
+}
+
+bool WebContents::CanTerminate() const {
+ if (!delegate())
+ return true;
+
+ return !delegate()->IsExternalTabContainer();
+}
+
+void WebContents::FileSelected(const std::wstring& path, void* params) {
+ render_view_host()->FileSelected(path);
+}
+
+void WebContents::MultiFilesSelected(const std::vector<std::wstring>& files,
+ void* params) {
+ render_view_host()->MultiFilesSelected(files);
+}
+
+
+void WebContents::FileSelectionCanceled(void* params) {
+ // If the user cancels choosing a file to upload we pass back an
+ // empty vector.
+ render_view_host()->MultiFilesSelected(std::vector<std::wstring>());
+}
+
+void WebContents::BeforeUnloadFiredFromRenderManager(
+ bool proceed,
+ bool* proceed_to_fire_unload) {
+ if (delegate())
+ delegate()->BeforeUnloadFired(this, proceed, proceed_to_fire_unload);
+}
+
+void WebContents::UpdateRenderViewSizeForRenderManager() {
+ // TODO(brettw) this is a hack. See WebContentsView::SizeContents.
+ view_->SizeContents(view_->GetContainerSize());
+}
+
+bool WebContents::CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host) {
+ RenderWidgetHostView* rvh_view = view_->CreateViewForWidget(render_view_host);
+
+ bool ok = render_view_host->CreateRenderView();
+ if (ok) {
+ // TODO(brettw) hack alert. Do this in some cross platform way, or move
+ // to the view?
+ RenderWidgetHostViewWin* rvh_view_win =
+ static_cast<RenderWidgetHostViewWin*>(rvh_view);
+ rvh_view->SetSize(view_->GetContainerSize());
+ UpdateMaxPageIDIfNecessary(render_view_host->site_instance(),
+ render_view_host);
+ }
+ return ok;
+}
+
+void WebContents::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFY_BOOKMARK_MODEL_LOADED: // BookmarkModel finished loading, fall
+ // through to update starred state.
+ case NOTIFY_URLS_STARRED: { // Somewhere, a URL has been starred.
+ // Ignore notifications for profiles other than our current one.
+ Profile* source_profile = Source<Profile>(source).ptr();
+ if (!source_profile->IsSameProfile(profile()))
+ return;
+
+ UpdateStarredStateForCurrentURL();
+ break;
+ }
+ case NOTIFY_PREF_CHANGED: {
+ std::wstring* pref_name_in = Details<std::wstring>(details).ptr();
+ DCHECK(Source<PrefService>(source).ptr() == profile()->GetPrefs());
+ if (*pref_name_in == prefs::kAlternateErrorPagesEnabled) {
+ UpdateAlternateErrorPageURL();
+ } else if (*pref_name_in == prefs::kDefaultCharset ||
+ StartsWithASCII(WideToUTF8(*pref_name_in), "webkit.webprefs.", true)
+ ) {
+ UpdateWebPreferences();
+ } else {
+ NOTREACHED() << "unexpected pref change notification" << *pref_name_in;
+ }
+ break;
+ }
+ case NOTIFY_RENDER_WIDGET_HOST_DESTROYED:
+ view_->RenderWidgetHostDestroyed(Source<RenderWidgetHost>(source).ptr());
+ break;
+ default: {
+ TabContents::Observe(type, source, details);
+ break;
+ }
+ }
+}
+
+void WebContents::DidNavigateMainFramePostCommit(
+ const NavigationController::LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // Hide the download shelf if all the following conditions are true:
+ // - there are no active downloads.
+ // - this is a navigation to a different TLD.
+ // - at least 5 seconds have elapsed since the download shelf was shown.
+ // TODO(jcampan): bug 1156075 when user gestures are reliable, they should
+ // be used to ensure we are hiding only on user initiated
+ // navigations.
+ DownloadManager* download_manager = profile()->GetDownloadManager();
+ // download_manager can be NULL in unit test context.
+ if (download_manager && download_manager->in_progress_count() == 0 &&
+ !details.previous_url.is_empty() &&
+ !net::RegistryControlledDomainService::SameDomainOrHost(
+ details.previous_url, details.entry->url())) {
+ TimeDelta time_delta(
+ TimeTicks::Now() - last_download_shelf_show_);
+ if (time_delta >
+ TimeDelta::FromMilliseconds(kDownloadShelfHideDelay)) {
+ SetDownloadShelfVisible(false);
+ }
+ }
+
+ 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());
+
+ // UpdateHelpersForDidNavigate will handle the case where the password_form
+ // origin is valid.
+ // TODO(brettw) bug 1343111: Password manager stuff in here needs to be
+ // cleaned up and covered by tests.
+ if (!params.password_form.origin.is_valid())
+ GetPasswordManager()->DidNavigate();
+ }
+
+ // The keyword generator uses the navigation entries, so must be called after
+ // the commit.
+ GenerateKeywordIfNecessary(params);
+
+ // 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());
+
+ // Close constrained popups if necessary.
+ MaybeCloseChildWindows(details.previous_url, details.entry->url());
+
+ // We hide the FindInPage window when the user navigates away, except on
+ // reload.
+ if (PageTransition::StripQualifier(params.transition) !=
+ PageTransition::RELOAD)
+ view_->HideFindBar(true);
+
+ // Update the starred state.
+ UpdateStarredStateForCurrentURL();
+}
+
+void WebContents::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;
+
+ // 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.
+ UpdateHistoryForNavigation(GetURL(), params);
+ }
+
+ // Notify the password manager of the navigation or form submit.
+ // TODO(brettw) bug 1343111: Password manager stuff in here needs to be
+ // cleaned up and covered by tests.
+ if (params.password_form.origin.is_valid())
+ GetPasswordManager()->ProvisionallySavePassword(params.password_form);
+}
+
+void WebContents::MaybeCloseChildWindows(const GURL& previous_url,
+ const GURL& current_url) {
+ if (net::RegistryControlledDomainService::SameDomainOrHost(
+ previous_url, current_url))
+ return;
+
+ // Clear out any child windows since we are leaving this page entirely.
+ // We use indices instead of iterators in case CloseWindow does something
+ // that may invalidate an iterator.
+ int size = static_cast<int>(child_windows_.size());
+ for (int i = size - 1; i >= 0; --i) {
+ ConstrainedWindow* window = child_windows_[i];
+ if (window)
+ window->CloseConstrainedWindow();
+ }
+}
+
+void WebContents::UpdateStarredStateForCurrentURL() {
+ BookmarkModel* model = profile()->GetBookmarkModel();
+ const bool old_state = is_starred_;
+ is_starred_ = (model && model->IsBookmarked(GetURL()));
+
+ if (is_starred_ != old_state && delegate())
+ delegate()->URLStarredChanged(this, is_starred_);
+}
+
+void WebContents::UpdateAlternateErrorPageURL() {
+ GURL url = GetAlternateErrorPageURL();
+ render_view_host()->SetAlternateErrorPageURL(url);
+}
+
+void WebContents::UpdateWebPreferences() {
+ render_view_host()->UpdateWebPreferences(GetWebkitPrefs());
+}
+
+bool WebContents::IsWebApplicationActive() const {
+ if (!web_app_.get())
+ return false;
+
+ // If we are inside an application, the application is always active. For
+ // example, this allows us to display the GMail icon even when we are bounced
+ // the login page.
+ if (delegate() && delegate()->IsApplication())
+ return true;
+
+ return (GetURL() == web_app_->url());
+}
+
+void WebContents::WebAppImagesChanged(WebApp* web_app) {
+ DCHECK(web_app == web_app_.get());
+ if (delegate() && IsWebApplicationActive())
+ delegate()->NavigationStateChanged(this, TabContents::INVALIDATE_FAVICON);
+}
+
+void WebContents::OnGearsCreateShortcutDone(
+ const GearsShortcutData& shortcut_data, bool success) {
+ NavigationEntry* current_entry = controller()->GetLastCommittedEntry();
+ bool same_page =
+ current_entry && pending_install_.page_id == current_entry->page_id();
+
+ if (success && same_page) {
+ // Only switch to app mode if the user chose to create a shortcut and
+ // we're still on the same page that it corresponded to.
+ SetWebApp(new WebApp(profile(), shortcut_data));
+ if (delegate())
+ delegate()->ConvertContentsToApplication(this);
+ }
+
+ // Reset the page id to indicate no requests are pending.
+ pending_install_.page_id = 0;
+ pending_install_.callback_functor = NULL;
+}
+
+void WebContents::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);
+ }
+ }
+}
+
+void WebContents::UpdateHistoryForNavigation(const GURL& display_url,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ if (profile()->IsOffTheRecord())
+ return;
+
+ // Add to history service.
+ HistoryService* hs = profile()->GetHistoryService(Profile::IMPLICIT_ACCESS);
+ if (hs) {
+ if (PageTransition::IsMainFrame(params.transition) &&
+ display_url != params.url) {
+ // Hack on the "display" 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 display URL as a redirect.
+ // This only applies to the main frame, as the display URL doesn't apply
+ // to sub-frames.
+ std::vector<GURL> redirects = params.redirects;
+ if (!redirects.empty())
+ redirects.back() = display_url;
+ hs->AddPage(display_url, this, params.page_id, params.referrer,
+ params.transition, redirects);
+ } else {
+ hs->AddPage(params.url, this, params.page_id, params.referrer,
+ params.transition, params.redirects);
+ }
+ }
+}
+
+bool WebContents::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."
+ std::wstring final_title;
+ bool explicit_set;
+ if (entry->url().SchemeIsFile() && title.empty()) {
+ final_title = UTF8ToWide(entry->url().ExtractFileName());
+ explicit_set = false; // Don't count synthetic titles toward the set limit.
+ } else {
+ TrimWhitespace(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);
+
+ // Update the history system for this page.
+ if (!profile()->IsOffTheRecord() && !received_page_title_) {
+ HistoryService* hs =
+ profile()->GetHistoryService(Profile::IMPLICIT_ACCESS);
+ if (hs)
+ hs->SetPageTitle(entry->display_url(), final_title);
+
+ // Don't allow the title to be saved again for explicitly set ones.
+ received_page_title_ = explicit_set;
+ }
+
+ // Lastly, set the title for the view.
+ view_->SetPageTitle(final_title);
+
+ return true;
+}
+
+void WebContents::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(NOTIFY_WEB_CONTENTS_SWAPPED,
+ Source<WebContents>(this),
+ NotificationService::NoDetails());
+}
+
+void WebContents::NotifyConnected() {
+ notify_disconnection_ = true;
+ NotificationService::current()->
+ Notify(NOTIFY_WEB_CONTENTS_CONNECTED,
+ Source<WebContents>(this),
+ NotificationService::NoDetails());
+}
+
+void WebContents::NotifyDisconnected() {
+ if (!notify_disconnection_)
+ return;
+
+ notify_disconnection_ = false;
+ NotificationService::current()->
+ Notify(NOTIFY_WEB_CONTENTS_DISCONNECTED,
+ Source<WebContents>(this),
+ NotificationService::NoDetails());
+}
+
+void WebContents::GenerateKeywordIfNecessary(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ DCHECK(controller());
+ if (!params.searchable_form_url.is_valid())
+ return;
+
+ if (profile()->IsOffTheRecord())
+ return;
+
+ const int last_index = controller()->GetLastCommittedEntryIndex();
+ // When there was no previous page, the last index will be 0. This is
+ // normally due to a form submit that opened in a new tab.
+ // TODO(brettw) bug 916126: we should support keywords when form submits
+ // happen in new tabs.
+ if (last_index <= 0)
+ return;
+ const NavigationEntry* previous_entry =
+ controller()->GetEntryAtIndex(last_index - 1);
+ if (IsFormSubmit(previous_entry)) {
+ // Only generate a keyword if the previous page wasn't itself a form
+ // submit.
+ return;
+ }
+
+ GURL keyword_url = previous_entry->user_typed_url().is_valid() ?
+ previous_entry->user_typed_url() : previous_entry->url();
+ std::wstring keyword =
+ TemplateURLModel::GenerateKeyword(keyword_url, true); // autodetected
+ if (keyword.empty())
+ return;
+
+ TemplateURLModel* url_model = profile()->GetTemplateURLModel();
+ if (!url_model)
+ return;
+
+ if (!url_model->loaded()) {
+ url_model->Load();
+ return;
+ }
+
+ const TemplateURL* current_url;
+ std::wstring url = UTF8ToWide(params.searchable_form_url.spec());
+ if (!url_model->CanReplaceKeyword(keyword, url, &current_url))
+ return;
+
+ if (current_url) {
+ if (current_url->originating_url().is_valid()) {
+ // The existing keyword was generated from an OpenSearch description
+ // document, don't regenerate.
+ return;
+ }
+ url_model->Remove(current_url);
+ }
+ TemplateURL* new_url = new TemplateURL();
+ new_url->set_keyword(keyword);
+ new_url->set_short_name(keyword);
+ new_url->SetURL(url, 0, 0);
+ new_url->add_input_encoding(params.searchable_form_encoding);
+ DCHECK(controller()->GetLastCommittedEntry());
+ const GURL& favicon_url =
+ controller()->GetLastCommittedEntry()->favicon().url();
+ if (favicon_url.is_valid()) {
+ new_url->SetFavIconURL(favicon_url);
+ } else {
+ // The favicon url isn't valid. This means there really isn't a favicon,
+ // or the favicon url wasn't obtained before the load started. This assumes
+ // the later.
+ // TODO(sky): Need a way to set the favicon that doesn't involve generating
+ // its url.
+ new_url->SetFavIconURL(TemplateURL::GenerateFaviconURL(params.referrer));
+ }
+ new_url->set_safe_for_autoreplace(true);
+ url_model->Add(new_url);
+}
diff --git a/chrome/browser/tab_contents/web_contents.h b/chrome/browser/tab_contents/web_contents.h
new file mode 100644
index 0000000..7b44e79
--- /dev/null
+++ b/chrome/browser/tab_contents/web_contents.h
@@ -0,0 +1,572 @@
+// 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 CHROME_BROWSER_WEB_CONTENTS_H_
+#define CHROME_BROWSER_WEB_CONTENTS_H_
+
+#include "base/hash_tables.h"
+#include "chrome/browser/download/save_package.h"
+#include "chrome/browser/fav_icon_helper.h"
+#include "chrome/browser/printing/print_view_manager.h"
+#include "chrome/browser/render_view_host_delegate.h"
+#include "chrome/browser/render_view_host_manager.h"
+#include "chrome/browser/shell_dialogs.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/web_app.h"
+
+class AutofillManager;
+class InterstitialPageDelegate;
+class PasswordManager;
+class PluginInstaller;
+class RenderViewHost;
+class RenderViewHostFactory;
+class RenderWidgetHost;
+class WebContentsView;
+
+// WebContents represents the contents of a tab that shows web pages. It embeds
+// a RenderViewHost (via RenderViewHostManager) to actually display the page.
+class WebContents : public TabContents,
+ public RenderViewHostDelegate,
+ public RenderViewHostManager::Delegate,
+ public SelectFileDialog::Listener,
+ public WebApp::Observer {
+ public:
+ // If instance is NULL, then creates a new process for this view. Otherwise
+ // initialize with a process already created for a different WebContents.
+ // This will share the process between views in the same instance. If
+ // render_view_factory is NULL, this will create RenderViewHost objects
+ // directly.
+ WebContents(Profile* profile,
+ SiteInstance* instance,
+ RenderViewHostFactory* render_view_factory,
+ int routing_id,
+ HANDLE modal_dialog_event);
+
+ static void RegisterUserPrefs(PrefService* prefs);
+
+ // Getters -------------------------------------------------------------------
+
+ // Returns the AutofillManager, creating it if necessary.
+ AutofillManager* GetAutofillManager();
+
+ // Returns the PasswordManager, creating it if necessary.
+ PasswordManager* GetPasswordManager();
+
+ // Returns the PluginInstaller, creating it if necessary.
+ PluginInstaller* GetPluginInstaller();
+
+ // 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* process() const {
+ return render_manager_.current_host()->process();
+ }
+ RenderViewHost* render_view_host() const {
+ return render_manager_.current_host();
+ }
+
+ // The WebContentsView will never change and is guaranteed non-NULL.
+ WebContentsView* view() const {
+ return view_.get();
+ }
+
+ bool is_starred() const { return is_starred_; }
+
+ const std::wstring& encoding() const { return encoding_; }
+ void set_encoding(const std::wstring& encoding) {
+ encoding_ = encoding;
+ }
+
+ // TabContents (public overrides) --------------------------------------------
+
+ virtual void Destroy();
+ virtual WebContents* AsWebContents() { return this; }
+ virtual SiteInstance* GetSiteInstance() const;
+ virtual SkBitmap GetFavIcon();
+ virtual std::wstring GetStatusText() const;
+ virtual bool NavigateToPendingEntry(bool reload);
+ virtual void Stop();
+ virtual void Cut();
+ virtual void Copy();
+ virtual void Paste();
+ virtual void DisassociateFromPopupCount();
+ virtual void DidBecomeSelected();
+ virtual void WasHidden();
+ virtual void ShowContents();
+ virtual void HideContents();
+ virtual void SetDownloadShelfVisible(bool visible);
+ virtual void PopupNotificationVisibilityChanged(bool visible);
+
+ // Retarded pass-throughs to the view.
+ // TODO(brettw) fix this, tab contents shouldn't have these methods, probably
+ // it should be killed altogether.
+ virtual void CreateView();
+ virtual HWND GetContainerHWND() const;
+ virtual HWND GetContentHWND();
+ virtual void GetContainerBounds(gfx::Rect *out) const;
+
+ // Web apps ------------------------------------------------------------------
+
+ // Sets the WebApp for this WebContents.
+ void SetWebApp(WebApp* web_app);
+ WebApp* web_app() { return web_app_.get(); }
+
+ // Return whether this tab contents was created to contain an application.
+ bool IsWebApplication() const;
+
+ // Tell Gears to create a shortcut for the current page.
+ void CreateShortcut();
+
+ // 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;
+ }
+
+ // JavascriptMessageBoxHandler calls this when the dialog is closed.
+ void OnJavaScriptMessageBoxClosed(IPC::Message* reply_msg,
+ bool success,
+ const std::wstring& prompt);
+
+ // Prepare for saving page.
+ 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.
+ void SavePage(const std::wstring& main_file, const std::wstring& dir_path,
+ SavePackage::SavePackageType save_type);
+
+ // 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();
+
+ // 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 WebContents 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 override_encoding(const std::wstring& encoding) {
+ set_encoding(encoding);
+ render_view_host()->SetPageEncoding(encoding);
+ }
+
+ void CrossSiteNavigationCanceled() {
+ render_manager_.CrossSiteNavigationCanceled();
+ }
+
+ protected:
+ // Should be deleted via CloseContents.
+ virtual ~WebContents();
+
+ RenderWidgetHostView* render_widget_host_view() const {
+ return render_manager_.current_view();
+ }
+
+ // TabContents (private overrides) -------------------------------------------
+
+ virtual void SetInitialFocus(bool reverse);
+ virtual void SetIsLoading(bool is_loading, LoadNotificationDetails* details);
+
+ // RenderViewHostDelegate ----------------------------------------------------
+
+ virtual RenderViewHostDelegate::View* GetViewDelegate() const;
+ virtual RenderViewHostDelegate::Save* GetSaveDelegate() const;
+ virtual Profile* GetProfile() const;
+ virtual void RendererReady(RenderViewHost* render_view_host);
+ virtual void RendererGone(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::wstring& encoding);
+ virtual void UpdateTargetURL(int32 page_id, const GURL& url);
+ virtual void UpdateThumbnail(const GURL& url,
+ const SkBitmap& bitmap,
+ const ThumbnailScore& score);
+ virtual void Close(RenderViewHost* render_view_host);
+ virtual void RequestMove(const gfx::Rect& new_bounds);
+ virtual void DidStartLoading(RenderViewHost* render_view_host, int32 page_id);
+ virtual void DidStopLoading(RenderViewHost* render_view_host, int32 page_id);
+ virtual void DidStartProvisionalLoadForFrame(RenderViewHost* render_view_host,
+ bool is_main_frame,
+ const GURL& url);
+ virtual void DidRedirectProvisionalLoad(int32 page_id,
+ const GURL& source_url,
+ const GURL& target_url);
+ virtual void DidLoadResourceFromMemoryCache(const GURL& url,
+ const std::string& security_info);
+ virtual void DidFailProvisionalLoadWithError(RenderViewHost* render_view_host,
+ bool is_main_frame,
+ int error_code,
+ const GURL& url);
+ virtual void UpdateFavIconURL(RenderViewHost* render_view_host,
+ int32 page_id, const GURL& icon_url);
+ virtual void DidDownloadImage(RenderViewHost* render_view_host,
+ int id,
+ const GURL& image_url,
+ bool errored,
+ const SkBitmap& image);
+ virtual void RequestOpenURL(const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition);
+ virtual void DomOperationResponse(const std::string& json_string,
+ int automation_id);
+ virtual void ProcessExternalHostMessage(const std::string& receiver,
+ const std::string& message);
+ virtual void GoToEntryAtOffset(int offset);
+ virtual void GetHistoryListCount(int* back_list_count,
+ int* forward_list_count);
+ virtual void RunFileChooser(bool multiple_files,
+ const std::wstring& title,
+ const std::wstring& default_file,
+ const std::wstring& filter);
+ virtual void RunJavaScriptMessage(const std::wstring& message,
+ const std::wstring& default_prompt,
+ 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 void PasswordFormsSeen(const std::vector<PasswordForm>& forms);
+ virtual void AutofillFormSubmitted(const AutofillForm& form);
+ virtual void GetAutofillSuggestions(const std::wstring& field_name,
+ const std::wstring& user_text, int64 node_id, int request_id);
+ virtual void PageHasOSDD(RenderViewHost* render_view_host,
+ int32 page_id, const GURL& url, bool autodetected);
+ virtual void InspectElementReply(int num_resources);
+ virtual void DidGetPrintedPagesCount(int cookie, int number_pages);
+ virtual void DidPrintPage(const ViewHostMsg_DidPrintPage_Params& params);
+ virtual GURL GetAlternateErrorPageURL() const;
+ virtual WebPreferences GetWebkitPrefs();
+ virtual void OnMissingPluginStatus(int status);
+ virtual void OnCrashedPlugin(const FilePath& plugin_path);
+ virtual void OnJSOutOfMemory();
+ virtual void ShouldClosePage(bool proceed) {
+ render_manager_.ShouldClosePage(proceed);
+ }
+ // Allows the WebContents 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.
+ void OnCrossSiteResponse(int new_render_process_host_id,
+ int new_request_id) {
+ render_manager_.OnCrossSiteResponse(new_render_process_host_id,
+ new_request_id);
+ }
+ virtual bool CanBlur() const;
+ 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);
+ virtual void OnDidGetApplicationInfo(
+ int32 page_id,
+ const webkit_glue::WebApplicationInfo& info);
+ virtual void OnEnterOrSpace();
+ virtual bool CanTerminate() const;
+
+
+ // SelectFileDialog::Listener ------------------------------------------------
+
+ virtual void FileSelected(const std::wstring& path, void* params);
+ virtual void MultiFilesSelected(const std::vector<std::wstring>& files,
+ void* params);
+ virtual void FileSelectionCanceled(void* params);
+
+ // RenderViewHostManager::Delegate -------------------------------------------
+
+ virtual void BeforeUnloadFiredFromRenderManager(
+ bool proceed,
+ bool* proceed_to_fire_unload);
+ virtual void DidStartLoadingFromRenderManager(
+ RenderViewHost* render_view_host, int32 page_id) {
+ DidStartLoading(render_view_host, page_id);
+ }
+ virtual void RendererGoneFromRenderManager(RenderViewHost* render_view_host) {
+ RendererGone(render_view_host);
+ }
+ virtual void UpdateRenderViewSizeForRenderManager();
+ virtual void NotifySwappedFromRenderManager() {
+ NotifySwapped();
+ }
+ virtual NavigationController* GetControllerForRenderManager() {
+ return controller();
+ }
+
+ // 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 WebContents 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.
+ //
+ // TODO(brettw) clean this up! This logic seems out of place. This is called
+ // by the RenderViewHostManager, but also overridden by the DOMUIHost. Any
+ // logic that has to be here should have a more clear name.
+ virtual bool CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host);
+
+ private:
+ FRIEND_TEST(WebContentsTest, UpdateTitle);
+ friend class TestWebContents;
+
+ // Temporary until the view/contents separation is complete.
+ friend class WebContentsViewWin;
+
+ // So InterstitialPage can access SetIsLoading.
+ friend class InterstitialPage;
+
+ // When CreateShortcut is invoked RenderViewHost::GetApplicationInfo is
+ // invoked. CreateShortcut caches the state of the page needed to create the
+ // shortcut in PendingInstall. When OnDidGetApplicationInfo is invoked, it
+ // uses the information from PendingInstall and the WebApplicationInfo
+ // to create the shortcut.
+ class GearsCreateShortcutCallbackFunctor;
+ struct PendingInstall {
+ int32 page_id;
+ SkBitmap icon;
+ std::wstring title;
+ GURL url;
+ // This object receives the GearsCreateShortcutCallback and routes the
+ // message back to the WebContents, if we haven't been deleted.
+ GearsCreateShortcutCallbackFunctor* callback_functor;
+ };
+
+
+ // NotificationObserver ------------------------------------------------------
+
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // 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 child windows (constrained popups) when the domain changes.
+ // Supply the new and old URLs, and this function will figure out when the
+ // domain changing conditions are met.
+ void MaybeCloseChildWindows(const GURL& previous_url,
+ const GURL& current_url);
+
+ // Updates the starred state from the bookmark bar model. If the state has
+ // changed, the delegate is notified.
+ void UpdateStarredStateForCurrentURL();
+
+ // 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();
+
+ // Return whether the optional web application is active for the current URL.
+ // Call this method to check if web app properties are in effect.
+ //
+ // Note: This method should be used for presentation but not security. The app
+ // is always active if the containing window is a web application.
+ bool IsWebApplicationActive() const;
+
+ // WebApp::Observer method. Invoked when the set of images contained in the
+ // web app changes. Notifies the delegate our favicon has changed.
+ virtual void WebAppImagesChanged(WebApp* web_app);
+
+ // Called when the user dismisses the shortcut creation dialog. 'success' is
+ // true if the shortcut was created.
+ void OnGearsCreateShortcutDone(const GearsShortcutData& shortcut_data,
+ bool success);
+
+ // 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);
+
+ // Called by OnMsgNavigate to update history state. Overridden by subclasses
+ // that don't want to be added to history.
+ virtual void UpdateHistoryForNavigation(const GURL& display_url,
+ 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);
+
+ // Misc non-view stuff -------------------------------------------------------
+
+ // Helper functions for sending notifications.
+ void NotifySwapped();
+ void NotifyConnected();
+ void NotifyDisconnected();
+
+ // If params has a searchable form, this tries to create a new keyword.
+ void GenerateKeywordIfNecessary(
+ const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Data ----------------------------------------------------------------------
+
+ // The corresponding view.
+ scoped_ptr<WebContentsView> view_;
+
+ // Manages creation and swapping of render views.
+ RenderViewHostManager render_manager_;
+
+ // For testing, passed to new RenderViewHost managers.
+ RenderViewHostFactory* render_view_factory_;
+
+ // Handles print preview and print job for this contents.
+ printing::PrintViewManager printing_;
+
+ // Indicates whether we should notify about disconnection of this
+ // WebContents. 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<HistoryService::Handle, int32> HistoryRequestMap;
+ HistoryRequestMap history_requests_;
+
+ // System time at which the current load was started.
+ base::TimeTicks current_load_start_;
+
+ // 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_;
+
+ // SavePackage, lazily created.
+ scoped_refptr<SavePackage> save_package_;
+
+ // Tracks our pending CancelableRequests. This maps pending requests to
+ // page IDs so that we know whether a given callback still applies. The
+ // page ID -1 means no page ID was set.
+ CancelableRequestConsumerT<int32, -1> cancelable_consumer_;
+
+ // Whether the current URL is starred
+ bool is_starred_;
+
+ // 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.
+ ScopedHandle message_box_active_;
+
+ // AutofillManager, lazily created.
+ scoped_ptr<AutofillManager> autofill_manager_;
+
+ // PasswordManager, lazily created.
+ scoped_ptr<PasswordManager> password_manager_;
+
+ // PluginInstaller, lazily created.
+ scoped_ptr<PluginInstaller> plugin_installer_;
+
+ // Handles downloading favicons.
+ FavIconHelper fav_icon_helper_;
+
+ // Dialog box used for choosing files to upload from file form fields.
+ scoped_refptr<SelectFileDialog> select_file_dialog_;
+
+ // 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_;
+
+ // 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. TODO(jungshik) : convert to std::string
+ std::wstring encoding_;
+
+ PendingInstall pending_install_;
+
+ // The last time that the download shelf was made visible.
+ base::TimeTicks last_download_shelf_show_;
+
+ // The current load state and the URL associated with it.
+ net::LoadState load_state_;
+ std::wstring load_state_host_;
+
+ // Non-null if we're displaying content for a web app.
+ scoped_refptr<WebApp> web_app_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContents);
+};
+
+#endif // CHROME_BROWSER_WEB_CONTENTS_H_
diff --git a/chrome/browser/tab_contents/web_contents_unittest.cc b/chrome/browser/tab_contents/web_contents_unittest.cc
new file mode 100644
index 0000000..df018c1
--- /dev/null
+++ b/chrome/browser/tab_contents/web_contents_unittest.cc
@@ -0,0 +1,1271 @@
+// 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 "base/logging.h"
+#include "chrome/browser/render_view_host.h"
+#include "chrome/browser/render_widget_host_view.h"
+#include "chrome/browser/tab_contents/interstitial_page.h"
+#include "chrome/browser/tab_contents/navigation_controller.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/tab_contents/web_contents.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/ipc_channel.h"
+#include "chrome/common/pref_service.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/test/testing_profile.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+static void InitNavigateParams(ViewHostMsg_FrameNavigate_Params* params,
+ int page_id,
+ const GURL& url) {
+ params->page_id = page_id;
+ params->url = url;
+ params->referrer = GURL::EmptyGURL();
+ params->transition = PageTransition::TYPED;
+ params->redirects = std::vector<GURL>();
+ params->should_update_history = false;
+ params->searchable_form_url = GURL::EmptyGURL();
+ params->searchable_form_element_name = std::wstring();
+ params->searchable_form_encoding = std::string();
+ params->password_form = PasswordForm();
+ params->security_info = std::string();
+ params->gesture = NavigationGestureUser;
+ params->is_post = false;
+}
+
+// Subclass the RenderViewHost's view so that we can call Show(), etc.,
+// without having side-effects.
+class TestRenderWidgetHostView : public RenderWidgetHostView {
+ public:
+ TestRenderWidgetHostView() : is_showing_(false) {}
+
+ virtual RenderWidgetHost* GetRenderWidgetHost() const { return NULL; }
+ virtual void DidBecomeSelected() {}
+ virtual void WasHidden() {}
+ virtual void SetSize(const gfx::Size& size) {}
+ virtual HWND GetPluginHWND() { return NULL; }
+ virtual HANDLE ModalDialogEvent() { return NULL; }
+ virtual void ForwardMouseEventToRenderer(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {}
+ virtual void Focus() {}
+ virtual void Blur() {}
+ virtual bool HasFocus() { return true; }
+ virtual void AdvanceFocus(bool reverse) {}
+ virtual void Show() { is_showing_ = true; }
+ virtual void Hide() { is_showing_ = false; }
+ virtual gfx::Rect GetViewBounds() const { return gfx::Rect(); }
+ virtual void UpdateCursor(const WebCursor& cursor) {}
+ virtual void UpdateCursorIfOverSelf() {}
+ // Indicates if the page has finished loading.
+ virtual void SetIsLoading(bool is_loading) {}
+ virtual void IMEUpdateStatus(ViewHostMsg_ImeControl control,
+ const gfx::Rect& caret_rect) {}
+ virtual void DidPaintRect(const gfx::Rect& rect) {}
+ virtual void DidScrollRect(const gfx::Rect& rect, int dx, int dy) {}
+ virtual void RendererGone() {}
+ virtual void Destroy() {}
+ virtual void PrepareToDestroy() {}
+ virtual void SetTooltipText(const std::wstring& tooltip_text) {}
+
+ bool is_showing() const { return is_showing_; }
+
+ private:
+ bool is_showing_;
+};
+
+// Subclass RenderViewHost so that it does not create a process.
+class TestRenderViewHost : public RenderViewHost {
+ public:
+ TestRenderViewHost(
+ SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ int routing_id,
+ HANDLE modal_dialog_event)
+ : RenderViewHost(instance, delegate, routing_id, modal_dialog_event),
+ is_loading(false),
+ is_created(false),
+ immediate_before_unload(true),
+ delete_counter_(NULL) {
+ set_view(new TestRenderWidgetHostView());
+ }
+ ~TestRenderViewHost() {
+ // Track the delete if we've been asked to.
+ if (delete_counter_)
+ ++*delete_counter_;
+
+ // Since this isn't a traditional view, we have to delete it.
+ delete view_;
+ }
+
+ // If set, *delete_counter is incremented when this object destructs.
+ void set_delete_counter(int* delete_counter) {
+ delete_counter_ = delete_counter;
+ }
+
+ bool CreateRenderView() {
+ is_created = true;
+ return true;
+ }
+
+ bool IsRenderViewLive() const { return is_created; }
+
+ bool IsNavigationSuspended() { return navigations_suspended_; }
+
+ void NavigateToEntry(const NavigationEntry& entry, bool is_reload) {
+ is_loading = true;
+ }
+
+ void LoadAlternateHTMLString(const std::string& html_text,
+ bool new_navigation,
+ const GURL& display_url,
+ const std::string& security_info) {
+ is_loading = true;
+ }
+
+ // Support for onbeforeunload, onunload
+ void FirePageBeforeUnload() {
+ is_waiting_for_unload_ack_ = true;
+ if (immediate_before_unload)
+ delegate()->ShouldClosePage(true);
+ }
+ void ClosePage(int new_render_process_host_id, int new_request_id) {
+ // Nothing to do here... This would cause a ClosePage_ACK to be sent to
+ // ResourceDispatcherHost, so we can simulate that manually.
+ }
+ void TestOnMsgShouldClose(bool proceed) {
+ OnMsgShouldCloseACK(proceed);
+ }
+
+ bool is_loading;
+ bool is_created;
+ bool immediate_before_unload;
+ int* delete_counter_;
+};
+
+// Factory to create TestRenderViewHosts.
+class TestRenderViewHostFactory : public RenderViewHostFactory {
+ public:
+ static TestRenderViewHostFactory* GetInstance() {
+ static TestRenderViewHostFactory instance;
+ return &instance;
+ }
+
+ virtual RenderViewHost* CreateRenderViewHost(
+ SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ int routing_id,
+ HANDLE modal_dialog_event) {
+ return new TestRenderViewHost(
+ instance, delegate, routing_id, modal_dialog_event);
+ }
+
+ private:
+ TestRenderViewHostFactory() {}
+};
+
+// Subclass the TestingProfile so that it can return certain services we need.
+class WebContentsTestingProfile : public TestingProfile {
+ public:
+ WebContentsTestingProfile() : TestingProfile() { }
+
+ virtual PrefService* GetPrefs() {
+ if (!prefs_.get()) {
+ std::wstring source_path;
+ PathService::Get(chrome::DIR_TEST_DATA, &source_path);
+ file_util::AppendToPath(&source_path, L"profiles");
+ file_util::AppendToPath(&source_path, L"chrome_prefs");
+ file_util::AppendToPath(&source_path, L"Preferences");
+
+ prefs_.reset(new PrefService(source_path));
+ Profile::RegisterUserPrefs(prefs_.get());
+ browser::RegisterAllPrefs(prefs_.get(), prefs_.get());
+ }
+ return prefs_.get();
+ }
+};
+
+// Subclass WebContents to ensure it creates TestRenderViewHosts and does
+// not do anything involving views.
+class TestWebContents : public WebContents {
+ public:
+ TestWebContents(Profile* profile, SiteInstance* instance)
+ : WebContents(profile,
+ instance,
+ TestRenderViewHostFactory::GetInstance(),
+ MSG_ROUTING_NONE,
+ NULL),
+ transition_cross_site(false) {}
+
+ // Accessors for interesting fields
+ TestRenderViewHost* rvh() {
+ return static_cast<TestRenderViewHost*>(
+ render_manager_.render_view_host_);
+ }
+ TestRenderViewHost* pending_rvh() {
+ return static_cast<TestRenderViewHost*>(
+ render_manager_.pending_render_view_host_);
+ }
+
+ // State accessor.
+ bool cross_navigation_pending() {
+ return render_manager_.cross_navigation_pending_;
+ }
+
+ // Ensure we create TestRenderViewHosts that don't spawn processes.
+ RenderViewHost* CreateRenderViewHost(SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ int routing_id,
+ HANDLE modal_dialog_event) {
+ return new TestRenderViewHost(
+ instance, delegate, routing_id, modal_dialog_event);
+ }
+
+ // Overrides WebContents::ShouldTransitionCrossSite so that we can test both
+ // alternatives without using command-line switches.
+ bool ShouldTransitionCrossSite() { return transition_cross_site; }
+
+ // Promote DidNavigate to public.
+ void TestDidNavigate(TestRenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ DidNavigate(render_view_host, params);
+ render_view_host->is_loading = false;
+ }
+
+ // Promote GetWebkitPrefs to public.
+ WebPreferences TestGetWebkitPrefs() {
+ return GetWebkitPrefs();
+ }
+
+ // Prevent interaction with views.
+ bool CreateRenderViewForRenderManager(RenderViewHost* render_view_host) {
+ // This will go to a TestRenderViewHost.
+ render_view_host->CreateRenderView();
+ return true;
+ }
+ void UpdateRenderViewSizeForRenderManager() {}
+
+ // Set by individual tests.
+ bool transition_cross_site;
+};
+
+class TestInterstitialPage : public InterstitialPage {
+ public:
+ enum InterstitialState {
+ UNDECIDED = 0, // No decision taken yet.
+ OKED, // Proceed was called.
+ CANCELED // DontProceed was called.
+ };
+
+ class Delegate {
+ public:
+ virtual void TestInterstitialPageDeleted(
+ TestInterstitialPage* interstitial) = 0;
+ };
+
+ // IMPORTANT NOTE: if you pass stack allocated values for |state| and
+ // |deleted| (like all interstitial related tests do at this point), make sure
+ // to create an instance of the TestInterstitialPageStateGuard class on the
+ // stack in your test. This will ensure that the TestInterstitialPage states
+ // are cleared when the test finishes.
+ // Not doing so will cause stack trashing if your test does not hide the
+ // interstitial, as in such a case it will be destroyed in the test TearDown
+ // method and will dereference the |deleted| local variable which by then is
+ // out of scope.
+ TestInterstitialPage(WebContents* tab,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialState* state,
+ bool* deleted)
+ : InterstitialPage(tab, new_navigation, url),
+ state_(state),
+ deleted_(deleted),
+ command_received_count_(0),
+ delegate_(NULL) {
+ *state_ = UNDECIDED;
+ *deleted_ = false;
+ }
+
+ virtual ~TestInterstitialPage() {
+ if (deleted_)
+ *deleted_ = true;
+ if (delegate_)
+ delegate_->TestInterstitialPageDeleted(this);
+ }
+
+ virtual void DontProceed() {
+ if (state_)
+ *state_ = CANCELED;
+ InterstitialPage::DontProceed();
+ }
+ virtual void Proceed() {
+ if (state_)
+ *state_ = OKED;
+ InterstitialPage::Proceed();
+ }
+
+ int command_received_count() const {
+ return command_received_count_;
+ }
+
+ void TestDomOperationResponse(const std::string& json_string) {
+ DomOperationResponse(json_string, 1);
+ }
+
+ void TestDidNavigate(int page_id, const GURL& url) {
+ ViewHostMsg_FrameNavigate_Params params;
+ InitNavigateParams(&params, page_id, url);
+ DidNavigate(render_view_host(), params);
+ }
+
+ void TestRendererGone() {
+ RendererGone(render_view_host());
+ }
+
+ bool is_showing() const {
+ return static_cast<TestRenderWidgetHostView*>(render_view_host()->view())->
+ is_showing();
+ }
+
+ void ClearStates() {
+ state_ = NULL;
+ deleted_ = NULL;
+ delegate_ = NULL;
+ }
+
+ void set_delegate(Delegate* delegate) {
+ delegate_ = delegate;
+ }
+
+ protected:
+ virtual RenderViewHost* CreateRenderViewHost() {
+ return new TestRenderViewHost(
+ SiteInstance::CreateSiteInstance(tab()->profile()),
+ this, MSG_ROUTING_NONE, NULL);
+ }
+
+ virtual void CommandReceived(const std::string& command) {
+ command_received_count_++;
+ }
+
+ private:
+ InterstitialState* state_;
+ bool* deleted_;
+ int command_received_count_;
+ Delegate* delegate_;
+};
+
+class TestInterstitialPageStateGuard : public TestInterstitialPage::Delegate {
+ public:
+ explicit TestInterstitialPageStateGuard(
+ TestInterstitialPage* interstitial_page)
+ : interstitial_page_(interstitial_page) {
+ DCHECK(interstitial_page_);
+ interstitial_page_->set_delegate(this);
+ }
+ ~TestInterstitialPageStateGuard() {
+ if (interstitial_page_)
+ interstitial_page_->ClearStates();
+ }
+
+ virtual void TestInterstitialPageDeleted(TestInterstitialPage* interstitial) {
+ DCHECK(interstitial_page_ == interstitial);
+ interstitial_page_ = NULL;
+ }
+
+ private:
+ TestInterstitialPage* interstitial_page_;
+};
+
+class WebContentsTest : public testing::Test {
+ public:
+ WebContentsTest() : contents(NULL) {}
+
+ // testing::Test methods:
+
+ virtual void SetUp() {
+ profile.reset(new WebContentsTestingProfile());
+
+ // This will be deleted when the WebContents goes away
+ SiteInstance* instance = SiteInstance::CreateSiteInstance(profile.get());
+
+ contents = new TestWebContents(profile.get(), instance);
+ contents->SetupController(profile.get());
+ }
+
+ virtual void TearDown() {
+ // This will delete the contents.
+ if (contents)
+ contents->CloseContents();
+
+ // Make sure that we flush any messages related to WebContents destruction
+ // before we destroy the profile.
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void Navigate(int page_id, const GURL& url) {
+ DCHECK(contents);
+ ViewHostMsg_FrameNavigate_Params params;
+ InitNavigateParams(&params, page_id, url);
+ contents->TestDidNavigate(contents->rvh(), params);
+ }
+
+ scoped_ptr<WebContentsTestingProfile> profile;
+ TestWebContents* contents;
+
+ private:
+ MessageLoopForUI message_loop_;
+};
+
+// Test to make sure that title updates get stripped of whitespace.
+TEST_F(WebContentsTest, UpdateTitle) {
+ ViewHostMsg_FrameNavigate_Params params;
+ InitNavigateParams(&params, 0, GURL("about:blank"));
+
+ NavigationController::LoadCommittedDetails details;
+ contents->controller()->RendererDidNavigate(params, &details);
+
+ contents->UpdateTitle(contents->rvh(), 0, L" Lots O' Whitespace\n");
+ EXPECT_EQ(std::wstring(L"Lots O' Whitespace"), contents->GetTitle());
+}
+
+// Test simple same-SiteInstance navigation.
+TEST_F(WebContentsTest, SimpleNavigation) {
+ TestRenderViewHost* orig_rvh = contents->rvh();
+ SiteInstance* instance1 = contents->GetSiteInstance();
+ EXPECT_TRUE(contents->pending_rvh() == NULL);
+ EXPECT_FALSE(orig_rvh->is_loading);
+
+ // Navigate to URL
+ const GURL url("http://www.google.com");
+ contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED);
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_TRUE(orig_rvh->is_loading);
+ EXPECT_EQ(instance1, orig_rvh->site_instance());
+ // Controller's pending entry will have a NULL site instance until we assign
+ // it in DidNavigate.
+ EXPECT_TRUE(
+ contents->controller()->GetActiveEntry()->site_instance() == NULL);
+
+ // DidNavigate from the page
+ ViewHostMsg_FrameNavigate_Params params;
+ InitNavigateParams(&params, 1, url);
+ contents->TestDidNavigate(orig_rvh, params);
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents->render_view_host());
+ EXPECT_EQ(instance1, orig_rvh->site_instance());
+ // Controller's entry should now have the SiteInstance, or else we won't be
+ // able to find it later.
+ EXPECT_EQ(instance1,
+ contents->controller()->GetActiveEntry()->site_instance());
+}
+
+// Test that navigating across a site boundary creates a new RenderViewHost
+// with a new SiteInstance. Going back should do the same.
+TEST_F(WebContentsTest, CrossSiteBoundaries) {
+ contents->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = contents->rvh();
+ int orig_rvh_delete_count = 0;
+ orig_rvh->set_delete_counter(&orig_rvh_delete_count);
+ SiteInstance* instance1 = contents->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED);
+ ViewHostMsg_FrameNavigate_Params params1;
+ InitNavigateParams(&params1, 1, url);
+ contents->TestDidNavigate(orig_rvh, params1);
+
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents->render_view_host());
+
+ // Navigate to new site
+ const GURL url2("http://www.yahoo.com");
+ contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED);
+ EXPECT_TRUE(contents->cross_navigation_pending());
+ TestRenderViewHost* pending_rvh = contents->pending_rvh();
+ int pending_rvh_delete_count = 0;
+ pending_rvh->set_delete_counter(&pending_rvh_delete_count);
+
+ // DidNavigate from the pending page
+ ViewHostMsg_FrameNavigate_Params params2;
+ InitNavigateParams(&params2, 1, url2);
+ contents->TestDidNavigate(pending_rvh, params2);
+ SiteInstance* instance2 = contents->GetSiteInstance();
+
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_EQ(pending_rvh, contents->render_view_host());
+ EXPECT_NE(instance1, instance2);
+ EXPECT_TRUE(contents->pending_rvh() == NULL);
+ EXPECT_EQ(orig_rvh_delete_count, 1);
+
+ // Going back should switch SiteInstances again. The first SiteInstance is
+ // stored in the NavigationEntry, so it should be the same as at the start.
+ contents->controller()->GoBack();
+ TestRenderViewHost* goback_rvh = contents->pending_rvh();
+ EXPECT_TRUE(contents->cross_navigation_pending());
+
+ // DidNavigate from the back action
+ contents->TestDidNavigate(goback_rvh, params1);
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_EQ(goback_rvh, contents->render_view_host());
+ EXPECT_EQ(pending_rvh_delete_count, 1);
+ EXPECT_EQ(instance1, contents->GetSiteInstance());
+}
+
+// Test that navigating across a site boundary after a crash creates a new
+// RVH without requiring a cross-site transition (i.e., PENDING state).
+TEST_F(WebContentsTest, CrossSiteBoundariesAfterCrash) {
+ contents->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = contents->rvh();
+ int orig_rvh_delete_count = 0;
+ orig_rvh->set_delete_counter(&orig_rvh_delete_count);
+ SiteInstance* instance1 = contents->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED);
+ ViewHostMsg_FrameNavigate_Params params1;
+ InitNavigateParams(&params1, 1, url);
+ contents->TestDidNavigate(orig_rvh, params1);
+
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents->render_view_host());
+
+ // Crash the renderer.
+ orig_rvh->is_created = false;
+
+ // Navigate to new site. We should not go into PENDING.
+ const GURL url2("http://www.yahoo.com");
+ contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED);
+ TestRenderViewHost* new_rvh = contents->rvh();
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_TRUE(contents->pending_rvh() == NULL);
+ EXPECT_NE(orig_rvh, new_rvh);
+ EXPECT_EQ(orig_rvh_delete_count, 1);
+
+ // DidNavigate from the new page
+ ViewHostMsg_FrameNavigate_Params params2;
+ InitNavigateParams(&params2, 1, url2);
+ contents->TestDidNavigate(new_rvh, params2);
+ SiteInstance* instance2 = contents->GetSiteInstance();
+
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_EQ(new_rvh, contents->render_view_host());
+ EXPECT_NE(instance1, instance2);
+ EXPECT_TRUE(contents->pending_rvh() == NULL);
+}
+
+// Test that opening a new tab in the same SiteInstance and then navigating
+// both tabs to a new site will place both tabs in a single SiteInstance.
+TEST_F(WebContentsTest, NavigateTwoTabsCrossSite) {
+ contents->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = contents->rvh();
+ SiteInstance* instance1 = contents->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED);
+ ViewHostMsg_FrameNavigate_Params params1;
+ InitNavigateParams(&params1, 1, url);
+ contents->TestDidNavigate(orig_rvh, params1);
+
+ // Open a new tab with the same SiteInstance, navigated to the same site.
+ TestWebContents* contents2 = new TestWebContents(profile.get(), instance1);
+ params1.page_id = 2; // Need this since the site instance is the same (which
+ // is the scope of page IDs) and we want to consider
+ // this a new page.
+ contents2->transition_cross_site = true;
+ contents2->SetupController(profile.get());
+ contents2->controller()->LoadURL(url, GURL(), PageTransition::TYPED);
+ contents2->TestDidNavigate(contents2->rvh(), params1);
+
+ // Navigate first tab to a new site
+ const GURL url2a("http://www.yahoo.com");
+ contents->controller()->LoadURL(url2a, GURL(), PageTransition::TYPED);
+ TestRenderViewHost* pending_rvh_a = contents->pending_rvh();
+ ViewHostMsg_FrameNavigate_Params params2a;
+ InitNavigateParams(&params2a, 1, url2a);
+ contents->TestDidNavigate(pending_rvh_a, params2a);
+ SiteInstance* instance2a = contents->GetSiteInstance();
+ EXPECT_NE(instance1, instance2a);
+
+ // Navigate second tab to the same site as the first tab
+ const GURL url2b("http://mail.yahoo.com");
+ contents2->controller()->LoadURL(url2b, GURL(), PageTransition::TYPED);
+ TestRenderViewHost* pending_rvh_b = contents2->pending_rvh();
+ EXPECT_TRUE(pending_rvh_b != NULL);
+ EXPECT_TRUE(contents2->cross_navigation_pending());
+
+ // NOTE(creis): We used to be in danger of showing a sad tab page here if the
+ // second tab hadn't navigated somewhere first (bug 1145430). That case is
+ // now covered by the CrossSiteBoundariesAfterCrash test.
+
+ ViewHostMsg_FrameNavigate_Params params2b;
+ InitNavigateParams(&params2b, 2, url2b);
+ contents2->TestDidNavigate(pending_rvh_b, params2b);
+ SiteInstance* instance2b = contents2->GetSiteInstance();
+ EXPECT_NE(instance1, instance2b);
+
+ // Both tabs should now be in the same SiteInstance.
+ EXPECT_EQ(instance2a, instance2b);
+
+ contents2->CloseContents();
+}
+
+// Tests that WebContents uses the current URL, not the SiteInstance's site, to
+// determine whether a navigation is cross-site.
+TEST_F(WebContentsTest, CrossSiteComparesAgainstCurrentPage) {
+ contents->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = contents->rvh();
+ SiteInstance* instance1 = contents->GetSiteInstance();
+
+ // Navigate to URL.
+ const GURL url("http://www.google.com");
+ contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED);
+ ViewHostMsg_FrameNavigate_Params params1;
+ InitNavigateParams(&params1, 1, url);
+ contents->TestDidNavigate(orig_rvh, params1);
+
+ // Open a related tab to a second site.
+ TestWebContents* contents2 = new TestWebContents(profile.get(), instance1);
+ contents2->transition_cross_site = true;
+ contents2->SetupController(profile.get());
+ const GURL url2("http://www.yahoo.com");
+ contents2->controller()->LoadURL(url2, GURL(), PageTransition::TYPED);
+ // The first RVH in contents2 isn't live yet, so we shortcut the cross site
+ // pending.
+ TestRenderViewHost* rvh2 = contents2->rvh();
+ EXPECT_FALSE(contents2->cross_navigation_pending());
+ ViewHostMsg_FrameNavigate_Params params2;
+ InitNavigateParams(&params2, 2, url2);
+ contents2->TestDidNavigate(rvh2, params2);
+ SiteInstance* instance2 = contents2->GetSiteInstance();
+ EXPECT_NE(instance1, instance2);
+ EXPECT_FALSE(contents2->cross_navigation_pending());
+
+ // Simulate a link click in first tab to second site. Doesn't switch
+ // SiteInstances, because we don't intercept WebKit navigations.
+ ViewHostMsg_FrameNavigate_Params params3;
+ InitNavigateParams(&params3, 2, url2);
+ contents->TestDidNavigate(orig_rvh, params3);
+ SiteInstance* instance3 = contents->GetSiteInstance();
+ EXPECT_EQ(instance1, instance3);
+ EXPECT_FALSE(contents->cross_navigation_pending());
+
+ // Navigate to the new site. Doesn't switch SiteInstancees, because we
+ // compare against the current URL, not the SiteInstance's site.
+ const GURL url3("http://mail.yahoo.com");
+ contents->controller()->LoadURL(url3, GURL(), PageTransition::TYPED);
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ ViewHostMsg_FrameNavigate_Params params4;
+ InitNavigateParams(&params4, 3, url3);
+ contents->TestDidNavigate(orig_rvh, params4);
+ SiteInstance* instance4 = contents->GetSiteInstance();
+ EXPECT_EQ(instance1, instance4);
+
+ contents2->CloseContents();
+}
+
+// Test that the onbeforeunload and onunload handlers run when navigating
+// across site boundaries.
+TEST_F(WebContentsTest, CrossSiteUnloadHandlers) {
+ contents->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = contents->rvh();
+ SiteInstance* instance1 = contents->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED);
+ ViewHostMsg_FrameNavigate_Params params1;
+ InitNavigateParams(&params1, 1, url);
+ contents->TestDidNavigate(orig_rvh, params1);
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents->render_view_host());
+
+ // Navigate to new site, but simulate an onbeforeunload denial.
+ const GURL url2("http://www.yahoo.com");
+ orig_rvh->immediate_before_unload = false;
+ contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED);
+ orig_rvh->TestOnMsgShouldClose(false);
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents->render_view_host());
+
+ // Navigate again, but simulate an onbeforeunload approval.
+ contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED);
+ orig_rvh->TestOnMsgShouldClose(true);
+ EXPECT_TRUE(contents->cross_navigation_pending());
+ TestRenderViewHost* pending_rvh = contents->pending_rvh();
+
+ // We won't hear DidNavigate until the onunload handler has finished running.
+ // (No way to simulate that here, but it involves a call from RDH to
+ // WebContents::OnCrossSiteResponse.)
+
+ // DidNavigate from the pending page
+ ViewHostMsg_FrameNavigate_Params params2;
+ InitNavigateParams(&params2, 1, url2);
+ contents->TestDidNavigate(pending_rvh, params2);
+ SiteInstance* instance2 = contents->GetSiteInstance();
+ EXPECT_FALSE(contents->cross_navigation_pending());
+ EXPECT_EQ(pending_rvh, contents->render_view_host());
+ EXPECT_NE(instance1, instance2);
+ EXPECT_TRUE(contents->pending_rvh() == NULL);
+}
+
+// Test that NavigationEntries have the correct content state after going
+// forward and back. Prevents regression for bug 1116137.
+TEST_F(WebContentsTest, NavigationEntryContentState) {
+ TestRenderViewHost* orig_rvh = contents->rvh();
+
+ // Navigate to URL. There should be no committed entry yet.
+ const GURL url("http://www.google.com");
+ contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED);
+ NavigationEntry* entry = contents->controller()->GetLastCommittedEntry();
+ EXPECT_TRUE(entry == NULL);
+
+ // Committed entry should have content state after DidNavigate.
+ ViewHostMsg_FrameNavigate_Params params1;
+ InitNavigateParams(&params1, 1, url);
+ contents->TestDidNavigate(orig_rvh, params1);
+ entry = contents->controller()->GetLastCommittedEntry();
+ EXPECT_FALSE(entry->content_state().empty());
+
+ // Navigate to same site.
+ const GURL url2("http://images.google.com");
+ contents->controller()->LoadURL(url2, GURL(), PageTransition::TYPED);
+ entry = contents->controller()->GetLastCommittedEntry();
+ EXPECT_FALSE(entry->content_state().empty());
+
+ // Committed entry should have content state after DidNavigate.
+ ViewHostMsg_FrameNavigate_Params params2;
+ InitNavigateParams(&params2, 2, url2);
+ contents->TestDidNavigate(orig_rvh, params2);
+ entry = contents->controller()->GetLastCommittedEntry();
+ EXPECT_FALSE(entry->content_state().empty());
+
+ // Now go back. Committed entry should still have content state.
+ contents->controller()->GoBack();
+ contents->TestDidNavigate(orig_rvh, params1);
+ entry = contents->controller()->GetLastCommittedEntry();
+ EXPECT_FALSE(entry->content_state().empty());
+}
+
+// Test that NavigationEntries have the correct content state after opening
+// a new window to about:blank. Prevents regression for bug 1116137.
+TEST_F(WebContentsTest, NavigationEntryContentStateNewWindow) {
+ TestRenderViewHost* orig_rvh = contents->rvh();
+
+ // When opening a new window, it is navigated to about:blank internally.
+ // Currently, this results in two DidNavigate events.
+ const GURL url("about:blank");
+ ViewHostMsg_FrameNavigate_Params params1;
+ InitNavigateParams(&params1, 1, url);
+ contents->TestDidNavigate(orig_rvh, params1);
+ contents->TestDidNavigate(orig_rvh, params1);
+
+ // Should have a content state here.
+ NavigationEntry* entry = contents->controller()->GetLastCommittedEntry();
+ EXPECT_FALSE(entry->content_state().empty());
+}
+
+// Tests to see that webkit preferences are properly loaded and copied over
+// to a WebPreferences object.
+TEST_F(WebContentsTest, WebKitPrefs) {
+ WebPreferences webkit_prefs = contents->TestGetWebkitPrefs();
+
+ // These values have been overridden by the profile preferences.
+ EXPECT_EQ(L"UTF-8", webkit_prefs.default_encoding);
+ EXPECT_EQ(20, webkit_prefs.default_font_size);
+ EXPECT_EQ(false, webkit_prefs.text_areas_are_resizable);
+ EXPECT_EQ(true, webkit_prefs.uses_universal_detector);
+
+ // These should still be the default values.
+ EXPECT_EQ(L"Times New Roman", webkit_prefs.standard_font_family);
+ EXPECT_EQ(true, webkit_prefs.javascript_enabled);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Interstitial Tests
+////////////////////////////////////////////////////////////////////////////////
+
+// Test navigating to a page (with the navigation initiated from the browser,
+// as when a URL is typed in the location bar) that shows an interstitial and
+// creates a new navigation entry, then hiding it without proceeding.
+TEST_F(WebContentsTest,
+ ShowInterstitialFromBrowserWithNewNavigationDontProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ Navigate(1, url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+
+ // Initiate a browser navigation that will trigger the interstitial
+ contents->controller()->LoadURL(GURL("http://www.evil.com"), GURL(),
+ PageTransition::TYPED);
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == interstitial);
+ NavigationEntry* entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url2);
+
+ // Now don't proceed.
+ interstitial->DontProceed();
+ EXPECT_TRUE(deleted);
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+}
+
+// Test navigating to a page (with the navigation initiated from the renderer,
+// as when clicking on a link in the page) that shows an interstitial and
+// creates a new navigation entry, then hiding it without proceeding.
+TEST_F(WebContentsTest,
+ ShowInterstitiaFromRendererlWithNewNavigationDontProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ Navigate(1, url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+
+ // Show an interstitial (no pending entry, the interstitial would have been
+ // triggered by clicking on a link).
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == interstitial);
+ NavigationEntry* entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url2);
+
+ // Now don't proceed.
+ interstitial->DontProceed();
+ EXPECT_TRUE(deleted);
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+}
+
+// Test navigating to a page that shows an interstitial without creating a new
+// navigation entry (this happens when the interstitial is triggered by a
+// sub-resource in the page), then hiding it without proceeding.
+TEST_F(WebContentsTest, ShowInterstitialNoNewNavigationDontProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ Navigate(1, url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, false, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == interstitial);
+ NavigationEntry* entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ // The URL specified to the interstitial should have been ignored.
+ EXPECT_TRUE(entry->url() == url1);
+
+ // Now don't proceed.
+ interstitial->DontProceed();
+ EXPECT_TRUE(deleted);
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+}
+
+// Test navigating to a page (with the navigation initiated from the browser,
+// as when a URL is typed in the location bar) that shows an interstitial and
+// creates a new navigation entry, then proceeding.
+TEST_F(WebContentsTest,
+ ShowInterstitialFromBrowserNewNavigationProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ Navigate(1, url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+
+ // Initiate a browser navigation that will trigger the interstitial
+ contents->controller()->LoadURL(GURL("http://www.evil.com"), GURL(),
+ PageTransition::TYPED);
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == interstitial);
+ NavigationEntry* entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url2);
+
+ // Then proceed.
+ interstitial->Proceed();
+ // The interstitial should show until the new navigation commits.
+ ASSERT_FALSE(deleted);
+ EXPECT_EQ(TestInterstitialPage::OKED, state);
+ EXPECT_TRUE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == interstitial);
+
+ // Simulate the navigation to the page, that's when the interstitial gets
+ // hidden.
+ GURL url3("http://www.thepage.com");
+ Navigate(2, url3);
+
+ EXPECT_TRUE(deleted);
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url3);
+
+ EXPECT_EQ(2, contents->controller()->GetEntryCount());
+}
+
+// Test navigating to a page (with the navigation initiated from the renderer,
+// as when clicking on a link in the page) that shows an interstitial and
+// creates a new navigation entry, then proceeding.
+TEST_F(WebContentsTest,
+ ShowInterstitialFromRendererNewNavigationProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ Navigate(1, url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == interstitial);
+ NavigationEntry* entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url2);
+
+ // Then proceed.
+ interstitial->Proceed();
+ // The interstitial should show until the new navigation commits.
+ ASSERT_FALSE(deleted);
+ EXPECT_EQ(TestInterstitialPage::OKED, state);
+ EXPECT_TRUE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == interstitial);
+
+ // Simulate the navigation to the page, that's when the interstitial gets
+ // hidden.
+ GURL url3("http://www.thepage.com");
+ Navigate(2, url3);
+
+ EXPECT_TRUE(deleted);
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url3);
+
+ EXPECT_EQ(2, contents->controller()->GetEntryCount());
+}
+
+// Test navigating to a page that shows an interstitial without creating a new
+// navigation entry (this happens when the interstitial is triggered by a
+// sub-resource in the page), then proceeding.
+TEST_F(WebContentsTest, ShowInterstitialNoNewNavigationProceed) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL url1("http://www.google.com");
+ Navigate(1, url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, false, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == interstitial);
+ NavigationEntry* entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ // The URL specified to the interstitial should have been ignored.
+ EXPECT_TRUE(entry->url() == url1);
+
+ // Then proceed.
+ interstitial->Proceed();
+ // Since this is not a new navigation, the previous page is dismissed right
+ // away and shows the original page.
+ EXPECT_TRUE(deleted);
+ EXPECT_EQ(TestInterstitialPage::OKED, state);
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == url1);
+
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+}
+
+// Test navigating to a page that shows an interstitial, then navigating away.
+TEST_F(WebContentsTest, ShowInterstitialThenNavigate) {
+ // Show interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, true, url, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url);
+
+ // While interstitial showing, navigate to a new URL.
+ const GURL url2("http://www.yahoo.com");
+ Navigate(1, url2);
+
+ EXPECT_TRUE(deleted);
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+}
+
+// Test navigating to a page that shows an interstitial, then close the tab.
+TEST_F(WebContentsTest, ShowInterstitialThenCloseTab) {
+ // Show interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, true, url, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url);
+
+ // Now close the tab.
+ contents->CloseContents();
+ contents = NULL; // So we don't detroy it again on TearDown.
+ EXPECT_TRUE(deleted);
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+}
+
+// Test that after Proceed is called and an interstitial is still shown, no more
+// commands get executed.
+TEST_F(WebContentsTest, ShowInterstitialProceedMultipleCommands) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL url1("http://www.google.com");
+ Navigate(1, url1);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url2);
+
+ // Run a command.
+ EXPECT_EQ(0, interstitial->command_received_count());
+ interstitial->TestDomOperationResponse("toto");
+ EXPECT_EQ(1, interstitial->command_received_count());
+
+ // Then proceed.
+ interstitial->Proceed();
+ ASSERT_FALSE(deleted);
+
+ // While the navigation to the new page is pending, send other commands, they
+ // should be ignored.
+ interstitial->TestDomOperationResponse("hello");
+ interstitial->TestDomOperationResponse("hi");
+ EXPECT_EQ(1, interstitial->command_received_count());
+}
+
+// Test showing an interstitial while another interstitial is already showing.
+TEST_F(WebContentsTest, ShowInterstitialOnInterstitial) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL start_url("http://www.google.com");
+ Navigate(1, start_url);
+ EXPECT_EQ(1, contents->controller()->GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state1 =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted1 = false;
+ GURL url1("http://interstitial1");
+ TestInterstitialPage* interstitial1 =
+ new TestInterstitialPage(contents, true, url1, &state1, &deleted1);
+ TestInterstitialPageStateGuard state_guard1(interstitial1);
+ interstitial1->Show();
+ interstitial1->TestDidNavigate(1, url1);
+
+ // Now show another interstitial.
+ TestInterstitialPage::InterstitialState state2 =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted2 = false;
+ GURL url2("http://interstitial2");
+ TestInterstitialPage* interstitial2 =
+ new TestInterstitialPage(contents, true, url2, &state2, &deleted2);
+ TestInterstitialPageStateGuard state_guard2(interstitial2);
+ interstitial2->Show();
+ interstitial2->TestDidNavigate(1, url2);
+
+ // Showing interstitial2 should have caused interstitial1 to go away.
+ EXPECT_TRUE(deleted1);
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state1);
+
+ // Let's make sure interstitial2 is working as intended.
+ ASSERT_FALSE(deleted2);
+ EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2);
+ interstitial2->Proceed();
+ GURL landing_url("http://www.thepage.com");
+ Navigate(2, landing_url);
+
+ EXPECT_TRUE(deleted2);
+ EXPECT_FALSE(contents->showing_interstitial_page());
+ EXPECT_TRUE(contents->interstitial_page() == NULL);
+ NavigationEntry* entry = contents->controller()->GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->url() == landing_url);
+ EXPECT_EQ(2, contents->controller()->GetEntryCount());
+}
+
+// Test that navigating away from an interstitial while it's loading cause it
+// not to show.
+TEST_F(WebContentsTest, NavigateBeforeInterstitialShows) {
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL interstitial_url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, true, interstitial_url,
+ &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+
+ // Let's simulate a navigation initiated from the browser before the
+ // interstitial finishes loading.
+ const GURL url("http://www.google.com");
+ contents->controller()->LoadURL(url, GURL(), PageTransition::TYPED);
+ ASSERT_FALSE(deleted);
+ EXPECT_FALSE(interstitial->is_showing());
+
+ // Now let's make the interstitial navigation commit.
+ interstitial->TestDidNavigate(1, interstitial_url);
+
+ // After it loaded the interstitial should be gone.
+ EXPECT_TRUE(deleted);
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+}
+
+// Test showing an interstitial and have its renderer crash.
+TEST_F(WebContentsTest, InterstitialCrasher) {
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::UNDECIDED;
+ bool deleted = false;
+ GURL url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents, true, url, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // Simulate a renderer crash before the interstitial is shown.
+ interstitial->TestRendererGone();
+ // The interstitial should have been dismissed.
+ EXPECT_TRUE(deleted);
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+
+ // Now try again but this time crash the intersitial after it was shown.
+ interstitial =
+ new TestInterstitialPage(contents, true, url, &state, &deleted);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url);
+ // Simulate a renderer crash.
+ interstitial->TestRendererGone();
+ // The interstitial should have been dismissed.
+ EXPECT_TRUE(deleted);
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+}
diff --git a/chrome/browser/tab_contents/web_contents_view.cc b/chrome/browser/tab_contents/web_contents_view.cc
new file mode 100644
index 0000000..d95fd78
--- /dev/null
+++ b/chrome/browser/tab_contents/web_contents_view.cc
@@ -0,0 +1,60 @@
+// 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 "chrome/browser/tab_contents/web_contents_view.h"
+
+#include "chrome/browser/render_widget_host.h"
+
+void WebContentsView::RenderWidgetHostDestroyed(RenderWidgetHost* host) {
+ for (PendingWidgetViews::iterator i = pending_widget_views_.begin();
+ i != pending_widget_views_.end(); ++i) {
+ if (host->view() == i->second) {
+ pending_widget_views_.erase(i);
+ return;
+ }
+ }
+}
+
+void WebContentsView::CreateNewWindow(int route_id, HANDLE modal_dialog_event) {
+ // Save the created window associated with the route so we can show it later.
+ pending_contents_[route_id] = CreateNewWindowInternal(route_id,
+ modal_dialog_event);
+}
+
+void WebContentsView::CreateNewWidget(int route_id, bool activatable) {
+ // Save the created widget associated with the route so we can show it later.
+ pending_widget_views_[route_id] = CreateNewWidgetInternal(route_id,
+ activatable);
+}
+
+void WebContentsView::ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ PendingContents::iterator iter = pending_contents_.find(route_id);
+ if (iter == pending_contents_.end()) {
+ DCHECK(false);
+ return;
+ }
+
+ WebContents* new_web_contents = iter->second;
+ pending_contents_.erase(route_id);
+
+ ShowCreatedWindowInternal(new_web_contents, disposition, initial_pos,
+ user_gesture);
+}
+
+void WebContentsView::ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) {
+ PendingWidgetViews::iterator iter = pending_widget_views_.find(route_id);
+ if (iter == pending_widget_views_.end()) {
+ DCHECK(false);
+ return;
+ }
+
+ RenderWidgetHostView* widget_host_view = iter->second;
+ pending_widget_views_.erase(route_id);
+
+ ShowCreatedWidgetInternal(widget_host_view, initial_pos);
+}
diff --git a/chrome/browser/tab_contents/web_contents_view.h b/chrome/browser/tab_contents/web_contents_view.h
new file mode 100644
index 0000000..232b449
--- /dev/null
+++ b/chrome/browser/tab_contents/web_contents_view.h
@@ -0,0 +1,191 @@
+// 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 CHROME_BROWSER_WEB_CONTENTS_VIEW_H_
+#define CHROME_BROWSER_WEB_CONTENTS_VIEW_H_
+
+#include <windows.h>
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gfx/rect.h"
+#include "base/gfx/size.h"
+#include "chrome/browser/render_view_host_delegate.h"
+
+class Browser;
+class RenderViewHost;
+class RenderWidgetHost;
+class RenderWidgetHostView;
+class RenderWidgetHostViewWin; // TODO(brettw) this should not be necessary.
+class WebContents;
+struct WebDropData;
+class WebKeyboardEvent;
+
+// The WebContentsView is an interface that is implemented by the platform-
+// dependent web contents views. The WebContents 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 WebContentsView : public RenderViewHostDelegate::View {
+ public:
+ virtual ~WebContentsView() {}
+
+ virtual WebContents* GetWebContents() = 0;
+
+ virtual void CreateView() = 0;
+
+ // Sets up the View that holds the rendered web page, receives messages for
+ // it and contains page plugins.
+ // TODO(brettw) make this so we don't need to return the Win version (see the
+ // caller in WebContents).
+ virtual RenderWidgetHostViewWin* CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) = 0;
+
+ // Returns the HWND that contains the contents of the tab.
+ // TODO(brettw) this should not be necessary in this cross-platform interface.
+ virtual HWND GetContainerHWND() const = 0;
+
+ // Returns the HWND 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 HWND).
+ // TODO(brettw) this should not be necessary in this cross-platform interface.
+ virtual HWND GetContentHWND() 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());
+ }
+
+ // Called when the WebContents is being destroyed. This should clean up child
+ // windows that are part of the view.
+ //
+ // TODO(brettw) It seems like this might be able to be done internally as the
+ // window is being torn down without input from the WebContents. Try to
+ // implement functions that way rather than adding stuff here.
+ virtual void OnContentsDestroy() = 0;
+
+ // 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;
+
+ // Schedules a complete repaint of the window. This is used for cases where
+ // the existing contents became invalid due to an external event, such as the
+ // renderer crashing.
+ virtual void Invalidate() = 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);
+
+ // Find in page --------------------------------------------------------------
+
+ // Opens the find in page window if it isn't already open. It will advance to
+ // the next match if |find_next| is set and there is a search string,
+ // otherwise, the find window will merely be opened. |forward_direction|
+ // indicates the direction to search when find_next is set, otherwise it is
+ // ignored.
+ virtual void FindInPage(const Browser& browser,
+ bool find_next, bool forward_direction) = 0;
+
+ // Hides the find bar if there is one shown. Does nothing otherwise. The find
+ // bar will not be deleted, merely hidden. This ensures that any search terms
+ // are preserved if the user subsequently opens the find bar.
+ //
+ // If |end_session| is true, then the find session will be ended, which
+ // indicates the user requested they no longer be in find mode for that tab.
+ // The find bar will not be restored when we switch back to the tab.
+ // Otherwise, we assume that the find bar is being hidden because the tab is
+ // being hidden, and all state like visibility and tickmarks will be restored
+ // when the tab comes back.
+ virtual void HideFindBar(bool end_session) = 0;
+
+ // Called when the tab is reparented to a new browser window. On MS Windows,
+ // we have to change the parent of our find bar to go with the new window.
+ //
+ // TODO(brettw) this seems like it could be improved. Possibly all doohickies
+ // around the tab like this, the download bar etc. should be managed by the
+ // BrowserView2 object.
+ virtual void ReparentFindWindow(Browser* new_browser) const = 0;
+
+ // Computes the location of the find bar and whether it is fully visible in
+ // its parent window. The return value indicates if the window is visible at
+ // all. Both out arguments are required.
+ //
+ // This is used for UI tests of the find bar. If the find bar is not currently
+ // shown (return value of false), the out params will be {(0, 0), false}.
+ virtual bool GetFindBarWindowInfo(gfx::Point* position,
+ bool* fully_visible) const = 0;
+
+ protected:
+ WebContentsView() {} // Abstract interface.
+
+ // Internal interface for some functions in the RenderViewHostDelegate::View
+ // interface. Subclasses should implement this rather than the corresponding
+ // ...::View functions directly, since the routing stuff will already be
+ // computed. They should implement the rest of the functions as normal.
+ //
+ // The only difference is that the Create functions return the newly
+ // created objects so that they can be associated with the given routes. When
+ // they are shown later, we'll look them up again and pass the objects to
+ // the Show functions rather than the route ID.
+ virtual WebContents* CreateNewWindowInternal(int route_id,
+ HANDLE modal_dialog_event) = 0;
+ virtual RenderWidgetHostView* CreateNewWidgetInternal(int route_id,
+ bool activatable) = 0;
+ virtual void ShowCreatedWindowInternal(WebContents* new_web_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) = 0;
+ virtual void ShowCreatedWidgetInternal(RenderWidgetHostView* widget_host_view,
+ const gfx::Rect& initial_pos) = 0;
+
+ 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, HANDLE modal_dialog_event);
+ virtual void CreateNewWidget(int route_id, bool activatable);
+ 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);
+
+ // Tracks created WebContents objects that have not been shown yet. They are
+ // identified by the route ID passed to CreateNewWindow.
+ typedef std::map<int, WebContents*> 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(WebContentsView);
+};
+
+#endif // CHROME_BROWSER_WEB_CONTENTS_VIEW_H_
diff --git a/chrome/browser/tab_contents/web_contents_view_win.cc b/chrome/browser/tab_contents/web_contents_view_win.cc
new file mode 100644
index 0000000..ab244cc
--- /dev/null
+++ b/chrome/browser/tab_contents/web_contents_view_win.cc
@@ -0,0 +1,646 @@
+// 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 "chrome/browser/tab_contents/web_contents_view_win.h"
+
+#include <windows.h>
+
+#include "chrome/browser/bookmarks/bookmark_drag_data.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/download/download_request_manager.h"
+#include "chrome/browser/render_view_context_menu.h"
+#include "chrome/browser/render_view_context_menu_controller.h"
+#include "chrome/browser/render_view_host.h"
+#include "chrome/browser/render_widget_host_view_win.h"
+#include "chrome/browser/tab_contents/interstitial_page.h"
+#include "chrome/browser/tab_contents/tab_contents_delegate.h"
+#include "chrome/browser/tab_contents/web_contents.h"
+#include "chrome/browser/tab_contents/web_drag_source.h"
+#include "chrome/browser/tab_contents/web_drop_target.h"
+#include "chrome/browser/views/find_bar_win.h"
+#include "chrome/browser/views/sad_tab_view.h"
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/common/os_exchange_data.h"
+#include "webkit/glue/plugins/webplugin_delegate_impl.h"
+
+namespace {
+
+// Windows callback for OnDestroy to detach the plugin windows.
+BOOL CALLBACK DetachPluginWindowsCallback(HWND window, LPARAM param) {
+ if (WebPluginDelegateImpl::IsPluginDelegateWindow(window)) {
+ ::ShowWindow(window, SW_HIDE);
+ SetParent(window, NULL);
+ }
+ return TRUE;
+}
+
+} // namespace
+
+WebContentsViewWin::WebContentsViewWin(WebContents* web_contents)
+ : web_contents_(web_contents),
+ ignore_next_char_event_(false) {
+}
+
+WebContentsViewWin::~WebContentsViewWin() {
+}
+
+WebContents* WebContentsViewWin::GetWebContents() {
+ return web_contents_;
+}
+
+void WebContentsViewWin::CreateView() {
+ set_delete_on_destroy(false);
+ // Since we create these windows parented to the desktop window initially, we
+ // don't want to create them initially visible.
+ set_window_style(WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
+ WidgetWin::Init(GetDesktopWindow(), gfx::Rect(), false);
+
+ // Remove the root view drop target so we can register our own.
+ RevokeDragDrop(GetHWND());
+ drop_target_ = new WebDropTarget(GetHWND(), web_contents_);
+}
+
+RenderWidgetHostViewWin* WebContentsViewWin::CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) {
+ DCHECK(!render_widget_host->view());
+ RenderWidgetHostViewWin* view =
+ new RenderWidgetHostViewWin(render_widget_host);
+ view->Create(GetHWND());
+ view->ShowWindow(SW_SHOW);
+ return view;
+}
+
+HWND WebContentsViewWin::GetContainerHWND() const {
+ return GetHWND();
+}
+
+HWND WebContentsViewWin::GetContentHWND() const {
+ if (!web_contents_->render_widget_host_view())
+ return NULL;
+ return web_contents_->render_widget_host_view()->GetPluginHWND();
+}
+
+void WebContentsViewWin::GetContainerBounds(gfx::Rect* out) const {
+ GetBounds(out, false);
+}
+
+void WebContentsViewWin::StartDragging(const WebDropData& drop_data) {
+ scoped_refptr<OSExchangeData> data(new OSExchangeData);
+
+ // TODO(tc): Generate an appropriate drag image.
+
+ // We set the file contents before the URL because the URL also sets file
+ // contents (to a .URL shortcut). We want to prefer file content data over a
+ // shortcut so we add it first.
+ if (!drop_data.file_contents.empty()) {
+ data->SetFileContents(drop_data.file_description_filename,
+ drop_data.file_contents);
+ }
+ if (!drop_data.text_html.empty())
+ data->SetHtml(drop_data.text_html, drop_data.html_base_url);
+ if (drop_data.url.is_valid()) {
+ if (drop_data.url.SchemeIs("javascript")) {
+ // We don't want to allow javascript URLs to be dragged to the desktop,
+ // but we do want to allow them to be added to the bookmarks bar
+ // (bookmarklets).
+ BookmarkDragData::Element bm_elt;
+ bm_elt.is_url = true;
+ bm_elt.url = drop_data.url;
+ bm_elt.title = drop_data.url_title;
+
+ BookmarkDragData bm_drag_data;
+ bm_drag_data.elements.push_back(bm_elt);
+
+ bm_drag_data.Write(web_contents_->profile(), data);
+ } else {
+ data->SetURL(drop_data.url, drop_data.url_title);
+ }
+ }
+ if (!drop_data.plain_text.empty())
+ data->SetString(drop_data.plain_text);
+
+ scoped_refptr<WebDragSource> drag_source(
+ new WebDragSource(GetHWND(), web_contents_->render_view_host()));
+
+ DWORD effects;
+
+ // We need to enable recursive tasks on the message loop so we can get
+ // updates while in the system DoDragDrop loop.
+ bool old_state = MessageLoop::current()->NestableTasksAllowed();
+ MessageLoop::current()->SetNestableTasksAllowed(true);
+ DoDragDrop(data, drag_source, DROPEFFECT_COPY | DROPEFFECT_LINK, &effects);
+ MessageLoop::current()->SetNestableTasksAllowed(old_state);
+
+ if (web_contents_->render_view_host())
+ web_contents_->render_view_host()->DragSourceSystemDragEnded();
+}
+
+void WebContentsViewWin::OnContentsDestroy() {
+ // TODO(brettw) this seems like maybe it can be moved into OnDestroy and this
+ // function can be deleted? If you're adding more here, consider whether it
+ // can be moved into OnDestroy which is a Windows message handler as the
+ // window is being torn down.
+
+ // When a tab is closed all its child plugin windows are destroyed
+ // automatically. This happens before plugins get any notification that its
+ // instances are tearing down.
+ //
+ // Plugins like Quicktime assume that their windows will remain valid as long
+ // as they have plugin instances active. Quicktime crashes in this case
+ // because its windowing code cleans up an internal data structure that the
+ // handler for NPP_DestroyStream relies on.
+ //
+ // The fix is to detach plugin windows from web contents when it is going
+ // away. This will prevent the plugin windows from getting destroyed
+ // automatically. The detached plugin windows will get cleaned up in proper
+ // sequence as part of the usual cleanup when the plugin instance goes away.
+ EnumChildWindows(GetHWND(), DetachPluginWindowsCallback, NULL);
+
+ // Close the find bar if any.
+ if (find_bar_.get())
+ find_bar_->Close();
+}
+
+void WebContentsViewWin::OnDestroy() {
+ if (drop_target_.get()) {
+ RevokeDragDrop(GetHWND());
+ drop_target_ = NULL;
+ }
+}
+
+void WebContentsViewWin::SetPageTitle(const std::wstring& title) {
+ if (GetContainerHWND()) {
+ // It's possible to get this after the hwnd has been destroyed.
+ ::SetWindowText(GetContainerHWND(), title.c_str());
+ // TODO(brettw) this call seems messy the way it reaches into the widget
+ // view, and I'm not sure it's necessary. Maybe we should just remove it.
+ ::SetWindowText(web_contents_->render_widget_host_view()->GetPluginHWND(),
+ title.c_str());
+ }
+}
+
+void WebContentsViewWin::Invalidate() {
+ // Note that it's possible to get this message after the window was destroyed.
+ if (::IsWindow(GetContainerHWND()))
+ InvalidateRect(GetContainerHWND(), NULL, FALSE);
+}
+
+void WebContentsViewWin::SizeContents(const gfx::Size& size) {
+ // TODO(brettw) this is a hack and should be removed. See web_contents_view.h.
+ WasSized(size);
+}
+
+void WebContentsViewWin::FindInPage(const Browser& browser,
+ bool find_next, bool forward_direction) {
+ if (!find_bar_.get()) {
+ // We want the Chrome top-level (Frame) window.
+ HWND hwnd = reinterpret_cast<HWND>(browser.window()->GetNativeHandle());
+ find_bar_.reset(new FindBarWin(this, hwnd));
+ } else {
+ find_bar_->Show();
+ }
+
+ if (find_next && !find_bar_->find_string().empty())
+ find_bar_->StartFinding(forward_direction);
+}
+
+void WebContentsViewWin::HideFindBar(bool end_session) {
+ if (find_bar_.get()) {
+ if (end_session)
+ find_bar_->EndFindSession();
+ else
+ find_bar_->DidBecomeUnselected();
+ }
+}
+
+void WebContentsViewWin::ReparentFindWindow(Browser* new_browser) const {
+ if (find_bar_.get()) {
+ find_bar_->SetParent(
+ reinterpret_cast<HWND>(new_browser->window()->GetNativeHandle()));
+ }
+}
+
+bool WebContentsViewWin::GetFindBarWindowInfo(gfx::Point* position,
+ bool* fully_visible) const {
+ CRect window_rect;
+ if (!find_bar_.get() ||
+ !::IsWindow(find_bar_->GetHWND()) ||
+ !::GetWindowRect(find_bar_->GetHWND(), &window_rect)) {
+ *position = gfx::Point(0, 0);
+ *fully_visible = false;
+ return false;
+ }
+
+ *position = gfx::Point(window_rect.TopLeft().x, window_rect.TopLeft().y);
+ *fully_visible = find_bar_->IsVisible() && !find_bar_->IsAnimating();
+ return true;
+}
+
+void WebContentsViewWin::UpdateDragCursor(bool is_drop_target) {
+ drop_target_->set_is_drop_target(is_drop_target);
+}
+
+void WebContentsViewWin::TakeFocus(bool reverse) {
+ views::FocusManager* focus_manager =
+ views::FocusManager::GetFocusManager(GetContainerHWND());
+
+ // We may not have a focus manager if the tab has been switched before this
+ // message arrived.
+ if (focus_manager)
+ focus_manager->AdvanceFocus(reverse);
+}
+
+void WebContentsViewWin::HandleKeyboardEvent(const WebKeyboardEvent& event) {
+ // Previous calls to TranslateMessage can generate CHAR events as well as
+ // KEY_DOWN events, even if the latter triggered an accelerator. In these
+ // cases, we discard the CHAR events.
+ if (event.type == WebInputEvent::CHAR && ignore_next_char_event_) {
+ ignore_next_char_event_ = false;
+ return;
+ }
+ ignore_next_char_event_ = false;
+
+ // The renderer returned a keyboard event it did not process. This may be
+ // a keyboard shortcut that we have to process.
+ if (event.type == WebInputEvent::KEY_DOWN) {
+ views::FocusManager* focus_manager =
+ views::FocusManager::GetFocusManager(GetHWND());
+ // We may not have a focus_manager at this point (if the tab has been
+ // switched by the time this message returned).
+ if (focus_manager) {
+ views::Accelerator accelerator(event.key_code,
+ (event.modifiers & WebInputEvent::SHIFT_KEY) ==
+ WebInputEvent::SHIFT_KEY,
+ (event.modifiers & WebInputEvent::CTRL_KEY) ==
+ WebInputEvent::CTRL_KEY,
+ (event.modifiers & WebInputEvent::ALT_KEY) ==
+ WebInputEvent::ALT_KEY);
+ if (focus_manager->ProcessAccelerator(accelerator, false)) {
+ ignore_next_char_event_ = true;
+ return;
+ }
+ }
+ }
+
+ // Any unhandled keyboard/character messages should be defproced.
+ // This allows stuff like Alt+F4, etc to work correctly.
+ DefWindowProc(event.actual_message.hwnd,
+ event.actual_message.message,
+ event.actual_message.wParam,
+ event.actual_message.lParam);
+}
+
+void WebContentsViewWin::OnFindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update) {
+ if (find_bar_.get()) {
+ find_bar_->OnFindReply(request_id, number_of_matches, selection_rect,
+ active_match_ordinal, final_update);
+ }
+}
+
+void WebContentsViewWin::ShowContextMenu(
+ const ViewHostMsg_ContextMenu_Params& params) {
+ RenderViewContextMenuController menu_controller(web_contents_, params);
+ RenderViewContextMenu menu(&menu_controller,
+ GetHWND(),
+ params.type,
+ params.misspelled_word,
+ params.dictionary_suggestions,
+ web_contents_->profile());
+
+ POINT screen_pt = { params.x, params.y };
+ MapWindowPoints(GetHWND(), HWND_DESKTOP, &screen_pt, 1);
+
+ // Enable recursive tasks on the message loop so we can get updates while
+ // the context menu is being displayed.
+ bool old_state = MessageLoop::current()->NestableTasksAllowed();
+ MessageLoop::current()->SetNestableTasksAllowed(true);
+ menu.RunMenuAt(screen_pt.x, screen_pt.y);
+ MessageLoop::current()->SetNestableTasksAllowed(old_state);
+}
+
+WebContents* WebContentsViewWin::CreateNewWindowInternal(
+ int route_id,
+ HANDLE modal_dialog_event) {
+ // Create the new web contents. This will automatically create the new
+ // WebContentsView. In the future, we may want to create the view separately.
+ WebContents* new_contents =
+ new WebContents(web_contents_->profile(),
+ web_contents_->GetSiteInstance(),
+ web_contents_->render_view_factory_,
+ route_id,
+ modal_dialog_event);
+ new_contents->SetupController(web_contents_->profile());
+ WebContentsView* new_view = new_contents->view();
+
+ new_view->CreateView();
+
+ // TODO(brettw) it seems bogus that we have to call this function on the
+ // newly created object and give it one of its own member variables.
+ new_view->CreateViewForWidget(new_contents->render_view_host());
+ return new_contents;
+}
+
+RenderWidgetHostView* WebContentsViewWin::CreateNewWidgetInternal(
+ int route_id,
+ bool activatable) {
+ // Create the widget and its associated view.
+ // TODO(brettw) can widget creation be cross-platform?
+ RenderWidgetHost* widget_host =
+ new RenderWidgetHost(web_contents_->process(), route_id);
+ RenderWidgetHostViewWin* widget_view =
+ new RenderWidgetHostViewWin(widget_host);
+
+ // We set the parent HWDN explicitly as pop-up HWNDs are parented and owned by
+ // the first non-child HWND of the HWND that was specified to the CreateWindow
+ // call.
+ // TODO(brettw) this should not need to get the current RVHView from the
+ // WebContents. We should have it somewhere ourselves.
+ widget_view->set_parent_hwnd(
+ web_contents_->render_widget_host_view()->GetPluginHWND());
+ widget_view->set_close_on_deactivate(true);
+ widget_view->set_activatable(activatable);
+
+ return widget_view;
+}
+
+void WebContentsViewWin::ShowCreatedWindowInternal(
+ WebContents* new_web_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ if (!new_web_contents->render_widget_host_view() ||
+ !new_web_contents->process()->channel()) {
+ // The view has gone away or the renderer crashed. Nothing to do.
+ return;
+ }
+
+ // TODO(brettw) this seems bogus to reach into here and initialize the host.
+ new_web_contents->render_view_host()->Init();
+ web_contents_->AddNewContents(new_web_contents, disposition, initial_pos,
+ user_gesture);
+}
+
+void WebContentsViewWin::ShowCreatedWidgetInternal(
+ RenderWidgetHostView* widget_host_view,
+ const gfx::Rect& initial_pos) {
+ // TODO(beng): (Cleanup) move all this windows-specific creation and showing
+ // code into RenderWidgetHostView behind some API that a
+ // ChromeView can also reasonably implement.
+ RenderWidgetHostViewWin* widget_host_view_win =
+ static_cast<RenderWidgetHostViewWin*>(widget_host_view);
+
+ RenderWidgetHost* widget_host = widget_host_view->GetRenderWidgetHost();
+ if (!widget_host->process()->channel()) {
+ // The view has gone away or the renderer crashed. Nothing to do.
+ return;
+ }
+
+ // This logic should be implemented by RenderWidgetHostHWND (as mentioned
+ // above) in the ::Init function, which should take a parent and some initial
+ // bounds.
+ widget_host_view_win->Create(GetContainerHWND(), NULL, NULL,
+ WS_POPUP, WS_EX_TOOLWINDOW);
+ widget_host_view_win->MoveWindow(initial_pos.x(), initial_pos.y(),
+ initial_pos.width(), initial_pos.height(),
+ TRUE);
+ widget_host_view_win->ShowWindow(widget_host_view_win->activatable() ?
+ SW_SHOW : SW_SHOWNA);
+ widget_host->Init();
+}
+
+void WebContentsViewWin::OnHScroll(int scroll_type, short position,
+ HWND scrollbar) {
+ ScrollCommon(WM_HSCROLL, scroll_type, position, scrollbar);
+}
+
+void WebContentsViewWin::OnMouseLeave() {
+ // Let our delegate know that the mouse moved (useful for resetting status
+ // bubble state).
+ if (web_contents_->delegate())
+ web_contents_->delegate()->ContentsMouseEvent(web_contents_, WM_MOUSELEAVE);
+ SetMsgHandled(FALSE);
+}
+
+LRESULT WebContentsViewWin::OnMouseRange(UINT msg,
+ WPARAM w_param, LPARAM l_param) {
+ switch (msg) {
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN: {
+ // Make sure this TabContents is activated when it is clicked on.
+ if (web_contents_->delegate())
+ web_contents_->delegate()->ActivateContents(web_contents_);
+ DownloadRequestManager* drm =
+ g_browser_process->download_request_manager();
+ if (drm)
+ drm->OnUserGesture(web_contents_);
+ break;
+ }
+ case WM_MOUSEMOVE:
+ // Let our delegate know that the mouse moved (useful for resetting status
+ // bubble state).
+ if (web_contents_->delegate()) {
+ web_contents_->delegate()->ContentsMouseEvent(web_contents_,
+ WM_MOUSEMOVE);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void WebContentsViewWin::OnPaint(HDC junk_dc) {
+ if (web_contents_->render_view_host() &&
+ !web_contents_->render_view_host()->IsRenderViewLive()) {
+ if (!sad_tab_.get())
+ sad_tab_.reset(new SadTabView);
+ CRect cr;
+ GetClientRect(&cr);
+ sad_tab_->SetBounds(gfx::Rect(cr));
+ ChromeCanvasPaint canvas(GetHWND(), true);
+ sad_tab_->ProcessPaint(&canvas);
+ return;
+ }
+
+ // We need to do this to validate the dirty area so we don't end up in a
+ // WM_PAINTstorm that causes other mysterious bugs (such as WM_TIMERs not
+ // firing etc). It doesn't matter that we don't have any non-clipped area.
+ CPaintDC dc(GetHWND());
+ SetMsgHandled(FALSE);
+}
+
+// A message is reflected here from view().
+// Return non-zero to indicate that it is handled here.
+// Return 0 to allow view() to further process it.
+LRESULT WebContentsViewWin::OnReflectedMessage(UINT msg, WPARAM w_param,
+ LPARAM l_param) {
+ MSG* message = reinterpret_cast<MSG*>(l_param);
+ switch (message->message) {
+ case WM_MOUSEWHEEL:
+ // This message is reflected from the view() to this window.
+ if (GET_KEYSTATE_WPARAM(message->wParam) & MK_CONTROL) {
+ WheelZoom(GET_WHEEL_DELTA_WPARAM(message->wParam));
+ return 1;
+ }
+ break;
+ case WM_HSCROLL:
+ case WM_VSCROLL:
+ if (ScrollZoom(LOWORD(message->wParam)))
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void WebContentsViewWin::OnSetFocus(HWND window) {
+ // TODO(jcampan): figure out why removing this prevents tabs opened in the
+ // background from properly taking focus.
+ // We NULL-check the render_view_host_ here because Windows can send us
+ // messages during the destruction process after it has been destroyed.
+ if (web_contents_->render_widget_host_view()) {
+ HWND inner_hwnd = web_contents_->render_widget_host_view()->GetPluginHWND();
+ if (::IsWindow(inner_hwnd))
+ ::SetFocus(inner_hwnd);
+ }
+}
+
+void WebContentsViewWin::OnVScroll(int scroll_type, short position,
+ HWND scrollbar) {
+ ScrollCommon(WM_VSCROLL, scroll_type, position, scrollbar);
+}
+
+void WebContentsViewWin::OnWindowPosChanged(WINDOWPOS* window_pos) {
+ if (window_pos->flags & SWP_HIDEWINDOW) {
+ WasHidden();
+ } else {
+ // The WebContents was shown by a means other than the user selecting a
+ // Tab, e.g. the window was minimized then restored.
+ if (window_pos->flags & SWP_SHOWWINDOW)
+ WasShown();
+
+ // Unless we were specifically told not to size, cause the renderer to be
+ // sized to the new bounds, which forces a repaint. Not required for the
+ // simple minimize-restore case described above, for example, since the
+ // size hasn't changed.
+ if (!(window_pos->flags & SWP_NOSIZE))
+ WasSized(gfx::Size(window_pos->cx, window_pos->cy));
+
+ // If we have a FindInPage dialog, notify it that the window changed.
+ if (find_bar_.get() && find_bar_->IsVisible())
+ find_bar_->MoveWindowIfNecessary(gfx::Rect());
+ }
+}
+
+void WebContentsViewWin::OnSize(UINT param, const CSize& size) {
+ WidgetWin::OnSize(param, size);
+
+ // Hack for thinkpad touchpad driver.
+ // Set fake scrollbars so that we can get scroll messages,
+ SCROLLINFO si = {0};
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_ALL;
+
+ si.nMin = 1;
+ si.nMax = 100;
+ si.nPage = 10;
+ si.nPos = 50;
+
+ ::SetScrollInfo(GetHWND(), SB_HORZ, &si, FALSE);
+ ::SetScrollInfo(GetHWND(), SB_VERT, &si, FALSE);
+}
+
+LRESULT WebContentsViewWin::OnNCCalcSize(BOOL w_param, LPARAM l_param) {
+ // Hack for thinkpad mouse wheel driver. We have set the fake scroll bars
+ // to receive scroll messages from thinkpad touchpad driver. Suppress
+ // painting of scrollbars by returning 0 size for them.
+ return 0;
+}
+
+void WebContentsViewWin::OnNCPaint(HRGN rgn) {
+ // Suppress default WM_NCPAINT handling. We don't need to do anything
+ // here since the view will draw everything correctly.
+}
+
+void WebContentsViewWin::ScrollCommon(UINT message, int scroll_type,
+ short position, HWND scrollbar) {
+ // This window can receive scroll events as a result of the ThinkPad's
+ // Trackpad scroll wheel emulation.
+ if (!ScrollZoom(scroll_type)) {
+ // Reflect scroll message to the view() to give it a chance
+ // to process scrolling.
+ SendMessage(GetContentHWND(), message, MAKELONG(scroll_type, position),
+ (LPARAM) scrollbar);
+ }
+}
+
+void WebContentsViewWin::WasHidden() {
+ web_contents_->HideContents();
+ if (find_bar_.get())
+ find_bar_->DidBecomeUnselected();
+}
+
+void WebContentsViewWin::WasShown() {
+ web_contents_->ShowContents();
+ if (find_bar_.get())
+ find_bar_->DidBecomeSelected();
+}
+
+void WebContentsViewWin::WasSized(const gfx::Size& size) {
+ if (web_contents_->interstitial_page())
+ web_contents_->interstitial_page()->SetSize(size);
+ if (web_contents_->render_widget_host_view())
+ web_contents_->render_widget_host_view()->SetSize(size);
+ if (find_bar_.get())
+ find_bar_->RespondToResize(size);
+
+ // TODO(brettw) this function can probably be moved to this class.
+ web_contents_->RepositionSupressedPopupsToFit(size);
+}
+
+bool WebContentsViewWin::ScrollZoom(int scroll_type) {
+ // If ctrl is held, zoom the UI. There are three issues with this:
+ // 1) Should the event be eaten or forwarded to content? We eat the event,
+ // which is like Firefox and unlike IE.
+ // 2) Should wheel up zoom in or out? We zoom in (increase font size), which
+ // is like IE and Google maps, but unlike Firefox.
+ // 3) Should the mouse have to be over the content area? We zoom as long as
+ // content has focus, although FF and IE require that the mouse is over
+ // content. This is because all events get forwarded when content has
+ // focus.
+ if (GetAsyncKeyState(VK_CONTROL) & 0x8000) {
+ int distance = 0;
+ switch (scroll_type) {
+ case SB_LINEUP:
+ distance = WHEEL_DELTA;
+ break;
+ case SB_LINEDOWN:
+ distance = -WHEEL_DELTA;
+ break;
+ // TODO(joshia): Handle SB_PAGEUP, SB_PAGEDOWN, SB_THUMBPOSITION,
+ // and SB_THUMBTRACK for completeness
+ default:
+ break;
+ }
+
+ WheelZoom(distance);
+ return true;
+ }
+ return false;
+}
+
+void WebContentsViewWin::WheelZoom(int distance) {
+ if (web_contents_->delegate()) {
+ bool zoom_in = distance > 0;
+ web_contents_->delegate()->ContentsZoomChange(zoom_in);
+ }
+}
diff --git a/chrome/browser/tab_contents/web_contents_view_win.h b/chrome/browser/tab_contents/web_contents_view_win.h
new file mode 100644
index 0000000..137674a
--- /dev/null
+++ b/chrome/browser/tab_contents/web_contents_view_win.h
@@ -0,0 +1,128 @@
+// 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 CHROME_BROWSER_WEB_CONTENTS_VIEW_WIN_H_
+#define CHROME_BROWSER_WEB_CONTENTS_VIEW_WIN_H_
+
+#include "base/gfx/size.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/tab_contents/web_contents_view.h"
+#include "chrome/views/widget_win.h"
+
+class FindBarWin;
+class SadTabView;
+struct WebDropData;
+class WebDropTarget;
+
+// Windows-specific implementation of the WebContentsView. It is a HWND that
+// contains all of the contents of the tab and associated child views.
+class WebContentsViewWin : public WebContentsView,
+ public views::WidgetWin {
+ public:
+ // The corresponding WebContents is passed in the constructor, and manages our
+ // lifetime. This doesn't need to be the case, but is this way currently
+ // because that's what was easiest when they were split.
+ explicit WebContentsViewWin(WebContents* web_contents);
+ virtual ~WebContentsViewWin();
+
+ // WebContentsView implementation --------------------------------------------
+
+ virtual WebContents* GetWebContents();
+ virtual void CreateView();
+ virtual RenderWidgetHostViewWin* CreateViewForWidget(
+ RenderWidgetHost* render_widget_host);
+ virtual HWND GetContainerHWND() const;
+ virtual HWND GetContentHWND() const;
+ virtual void GetContainerBounds(gfx::Rect* out) const;
+ virtual void OnContentsDestroy();
+ virtual void SetPageTitle(const std::wstring& title);
+ virtual void Invalidate();
+ virtual void SizeContents(const gfx::Size& size);
+ virtual void FindInPage(const Browser& browser,
+ bool find_next, bool forward_direction);
+ virtual void HideFindBar(bool end_session);
+ virtual void ReparentFindWindow(Browser* new_browser) const;
+ virtual bool GetFindBarWindowInfo(gfx::Point* position,
+ bool* fully_visible) const;
+
+ // Backend implementation of RenderViewHostDelegate::View.
+ virtual WebContents* CreateNewWindowInternal(
+ int route_id, HANDLE modal_dialog_event);
+ virtual RenderWidgetHostView* CreateNewWidgetInternal(int route_id,
+ bool activatable);
+ virtual void ShowCreatedWindowInternal(WebContents* new_web_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture);
+ virtual void ShowCreatedWidgetInternal(RenderWidgetHostView* widget_host_view,
+ const gfx::Rect& initial_pos);
+ virtual void ShowContextMenu(
+ const ViewHostMsg_ContextMenu_Params& params);
+ virtual void StartDragging(const WebDropData& drop_data);
+ virtual void UpdateDragCursor(bool is_drop_target);
+ virtual void TakeFocus(bool reverse);
+ virtual void HandleKeyboardEvent(const WebKeyboardEvent& event);
+ virtual void OnFindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+
+ private:
+ // Windows events ------------------------------------------------------------
+
+ // Overrides from WidgetWin.
+ virtual void OnDestroy();
+ virtual void OnHScroll(int scroll_type, short position, HWND scrollbar);
+ virtual void OnMouseLeave();
+ virtual LRESULT OnMouseRange(UINT msg, WPARAM w_param, LPARAM l_param);
+ virtual void OnPaint(HDC junk_dc);
+ virtual LRESULT OnReflectedMessage(UINT msg, WPARAM w_param, LPARAM l_param);
+ virtual void OnSetFocus(HWND window);
+ virtual void OnVScroll(int scroll_type, short position, HWND scrollbar);
+ virtual void OnWindowPosChanged(WINDOWPOS* window_pos);
+ virtual void OnSize(UINT param, const CSize& size);
+ virtual LRESULT OnNCCalcSize(BOOL w_param, LPARAM l_param);
+ virtual void OnNCPaint(HRGN rgn);
+
+ // Backend for all scroll messages, the |message| parameter indicates which
+ // one it is.
+ void ScrollCommon(UINT message, int scroll_type, short position,
+ HWND scrollbar);
+
+ // Handles notifying the WebContents and other operations when the window was
+ // shown or hidden.
+ void WasHidden();
+ void WasShown();
+
+ // Handles resizing of the contents. This will notify the RenderWidgetHostView
+ // of the change, reposition popups, and the find in page bar.
+ void WasSized(const gfx::Size& size);
+
+ // TODO(brettw) comment these. They're confusing.
+ bool ScrollZoom(int scroll_type);
+ void WheelZoom(int distance);
+
+ // ---------------------------------------------------------------------------
+
+ WebContents* web_contents_;
+
+ // For find in page. This may be NULL if there is no find bar, and if it is
+ // non-NULL, it may or may not be visible.
+ scoped_ptr<FindBarWin> find_bar_;
+
+ // A drop target object that handles drags over this WebContents.
+ scoped_refptr<WebDropTarget> drop_target_;
+
+ // Used to render the sad tab. This will be non-NULL only when the sad tab is
+ // visible.
+ scoped_ptr<SadTabView> sad_tab_;
+
+ // Whether to ignore the next CHAR keyboard event.
+ bool ignore_next_char_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsViewWin);
+};
+
+#endif // CHROME_BROWSER_WEB_CONTENTS_VIEW_WIN_H_
diff --git a/chrome/browser/tab_contents/web_drag_source.cc b/chrome/browser/tab_contents/web_drag_source.cc
new file mode 100644
index 0000000..b47913b
--- /dev/null
+++ b/chrome/browser/tab_contents/web_drag_source.cc
@@ -0,0 +1,45 @@
+// 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 <atlbase.h>
+#include <atlapp.h>
+#include <atlmisc.h>
+
+#include "chrome/browser/tab_contents/web_drag_source.h"
+
+#include "chrome/browser/render_view_host.h"
+
+namespace {
+
+static void GetCursorPositions(HWND hwnd, CPoint* client, CPoint* screen) {
+ GetCursorPos(screen);
+ *client = *screen;
+ ScreenToClient(hwnd, client);
+}
+
+} // namespace
+///////////////////////////////////////////////////////////////////////////////
+// WebDragSource, public:
+
+WebDragSource::WebDragSource(HWND source_hwnd,
+ RenderViewHost* render_view_host)
+ : BaseDragSource(),
+ source_hwnd_(source_hwnd),
+ render_view_host_(render_view_host) {
+}
+
+void WebDragSource::OnDragSourceDrop() {
+ CPoint client;
+ CPoint screen;
+ GetCursorPositions(source_hwnd_, &client, &screen);
+ render_view_host_->DragSourceEndedAt(client.x, client.y, screen.x, screen.y);
+}
+
+void WebDragSource::OnDragSourceMove() {
+ CPoint client;
+ CPoint screen;
+ GetCursorPositions(source_hwnd_, &client, &screen);
+ render_view_host_->DragSourceMovedTo(client.x, client.y, screen.x, screen.y);
+}
+
diff --git a/chrome/browser/tab_contents/web_drag_source.h b/chrome/browser/tab_contents/web_drag_source.h
new file mode 100644
index 0000000..f8aa628
--- /dev/null
+++ b/chrome/browser/tab_contents/web_drag_source.h
@@ -0,0 +1,49 @@
+// 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 CHROME_BROWSER_WEB_DRAG_SOURCE_H__
+#define CHROME_BROWSER_WEB_DRAG_SOURCE_H__
+
+#include "base/base_drag_source.h"
+#include "base/basictypes.h"
+
+class RenderViewHost;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// WebDragSource
+//
+// An IDropSource implementation for a WebContents. Handles notifications sent
+// by an active drag-drop operation as the user mouses over other drop targets
+// on their system. This object tells Windows whether or not the drag should
+// continue, and supplies the appropriate cursors.
+//
+class WebDragSource : public BaseDragSource {
+ public:
+ // Create a new DragSource for a given HWND and RenderViewHost.
+ WebDragSource(HWND source_hwnd, RenderViewHost* render_view_host);
+ virtual ~WebDragSource() { }
+
+ protected:
+ // BaseDragSource
+ virtual void OnDragSourceDrop();
+ virtual void OnDragSourceMove();
+
+ private:
+ // Cannot construct thusly.
+ WebDragSource();
+
+ // Keep a reference to the HWND so we can translate the cursor position.
+ HWND source_hwnd_;
+
+ // We use this as a channel to the renderer to tell it about various drag
+ // drop events that it needs to know about (such as when a drag operation it
+ // initiated terminates).
+ RenderViewHost* render_view_host_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(WebDragSource);
+};
+
+#endif // #ifndef CHROME_BROWSER_WEB_DRAG_SOURCE_H__
+
diff --git a/chrome/browser/tab_contents/web_drop_target.cc b/chrome/browser/tab_contents/web_drop_target.cc
new file mode 100644
index 0000000..c3220e5
--- /dev/null
+++ b/chrome/browser/tab_contents/web_drop_target.cc
@@ -0,0 +1,182 @@
+// 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 <windows.h>
+#include <shlobj.h>
+
+#include "chrome/browser/tab_contents/web_drop_target.h"
+
+#include "base/clipboard_util.h"
+#include "base/gfx/point.h"
+#include "chrome/browser/render_view_host.h"
+#include "chrome/browser/tab_contents/web_contents.h"
+#include "chrome/common/os_exchange_data.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_util.h"
+#include "webkit/glue/webdropdata.h"
+#include "webkit/glue/window_open_disposition.h"
+
+namespace {
+
+// A helper method for getting the preferred drop effect.
+DWORD GetPreferredDropEffect(DWORD effect) {
+ if (effect & DROPEFFECT_COPY)
+ return DROPEFFECT_COPY;
+ if (effect & DROPEFFECT_LINK)
+ return DROPEFFECT_LINK;
+ if (effect & DROPEFFECT_MOVE)
+ return DROPEFFECT_MOVE;
+ return DROPEFFECT_NONE;
+}
+
+} // anonymous namespace
+
+// InterstitialDropTarget is like a BaseDropTarget implementation that
+// WebDropTarget passes through to if an interstitial is showing. Rather than
+// passing messages on to the renderer, we just check to see if there's a link
+// in the drop data and handle links as navigations.
+class InterstitialDropTarget {
+ public:
+ explicit InterstitialDropTarget(WebContents* web_contents)
+ : web_contents_(web_contents) {}
+
+ DWORD OnDragEnter(IDataObject* data_object, DWORD effect) {
+ return ClipboardUtil::HasUrl(data_object) ? GetPreferredDropEffect(effect)
+ : DROPEFFECT_NONE;
+ }
+
+ DWORD OnDragOver(IDataObject* data_object, DWORD effect) {
+ return ClipboardUtil::HasUrl(data_object) ? GetPreferredDropEffect(effect)
+ : DROPEFFECT_NONE;
+ }
+
+ void OnDragLeave(IDataObject* data_object) {
+ }
+
+ DWORD OnDrop(IDataObject* data_object, DWORD effect) {
+ if (ClipboardUtil::HasUrl(data_object)) {
+ std::wstring url;
+ std::wstring title;
+ ClipboardUtil::GetUrl(data_object, &url, &title);
+ web_contents_->OpenURL(GURL(url), GURL(), CURRENT_TAB,
+ PageTransition::AUTO_BOOKMARK);
+ return GetPreferredDropEffect(effect);
+ }
+ return DROPEFFECT_NONE;
+ }
+
+ private:
+ WebContents* web_contents_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(InterstitialDropTarget);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// WebDropTarget, public:
+
+WebDropTarget::WebDropTarget(HWND source_hwnd, WebContents* web_contents)
+ : BaseDropTarget(source_hwnd),
+ web_contents_(web_contents),
+ current_rvh_(NULL),
+ is_drop_target_(false),
+ interstitial_drop_target_(new InterstitialDropTarget(web_contents)) {
+}
+
+WebDropTarget::~WebDropTarget() {
+}
+
+DWORD WebDropTarget::OnDragEnter(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect) {
+ current_rvh_ = web_contents_->render_view_host();
+
+ // Don't pass messages to the renderer if an interstitial page is showing
+ // because we don't want the interstitial page to navigate. Instead,
+ // pass the messages on to a separate interstitial DropTarget handler.
+ if (web_contents_->showing_interstitial_page())
+ return interstitial_drop_target_->OnDragEnter(data_object, effect);
+
+ // TODO(tc): PopulateWebDropData can be slow depending on what is in the
+ // IDataObject. Maybe we can do this in a background thread.
+ WebDropData drop_data;
+ WebDropData::PopulateWebDropData(data_object, &drop_data);
+
+ if (drop_data.url.is_empty())
+ OSExchangeData::GetPlainTextURL(data_object, &drop_data.url);
+
+ is_drop_target_ = true;
+
+ POINT client_pt = cursor_position;
+ ScreenToClient(GetHWND(), &client_pt);
+ web_contents_->render_view_host()->DragTargetDragEnter(drop_data,
+ gfx::Point(client_pt.x, client_pt.y),
+ gfx::Point(cursor_position.x, cursor_position.y));
+
+ // We lie here and always return a DROPEFFECT because we don't want to
+ // wait for the IPC call to return.
+ return GetPreferredDropEffect(effect);
+}
+
+DWORD WebDropTarget::OnDragOver(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect) {
+ DCHECK(current_rvh_);
+ if (current_rvh_ != web_contents_->render_view_host())
+ OnDragEnter(data_object, key_state, cursor_position, effect);
+
+ if (web_contents_->showing_interstitial_page())
+ return interstitial_drop_target_->OnDragOver(data_object, effect);
+
+ POINT client_pt = cursor_position;
+ ScreenToClient(GetHWND(), &client_pt);
+ web_contents_->render_view_host()->DragTargetDragOver(
+ gfx::Point(client_pt.x, client_pt.y),
+ gfx::Point(cursor_position.x, cursor_position.y));
+
+ if (!is_drop_target_)
+ return DROPEFFECT_NONE;
+
+ return GetPreferredDropEffect(effect);
+}
+
+void WebDropTarget::OnDragLeave(IDataObject* data_object) {
+ DCHECK(current_rvh_);
+ if (current_rvh_ != web_contents_->render_view_host())
+ return;
+
+ if (web_contents_->showing_interstitial_page()) {
+ interstitial_drop_target_->OnDragLeave(data_object);
+ } else {
+ web_contents_->render_view_host()->DragTargetDragLeave();
+ }
+}
+
+DWORD WebDropTarget::OnDrop(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect) {
+ DCHECK(current_rvh_);
+ if (current_rvh_ != web_contents_->render_view_host())
+ OnDragEnter(data_object, key_state, cursor_position, effect);
+
+ if (web_contents_->showing_interstitial_page())
+ interstitial_drop_target_->OnDragOver(data_object, effect);
+
+ if (web_contents_->showing_interstitial_page())
+ return interstitial_drop_target_->OnDrop(data_object, effect);
+
+ POINT client_pt = cursor_position;
+ ScreenToClient(GetHWND(), &client_pt);
+ web_contents_->render_view_host()->DragTargetDrop(
+ gfx::Point(client_pt.x, client_pt.y),
+ gfx::Point(cursor_position.x, cursor_position.y));
+
+ current_rvh_ = NULL;
+
+ // We lie and always claim that the drop operation didn't happen because we
+ // don't want to wait for the renderer to respond.
+ return DROPEFFECT_NONE;
+}
diff --git a/chrome/browser/tab_contents/web_drop_target.h b/chrome/browser/tab_contents/web_drop_target.h
new file mode 100644
index 0000000..28f4073
--- /dev/null
+++ b/chrome/browser/tab_contents/web_drop_target.h
@@ -0,0 +1,74 @@
+// 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 CHROME_BROWSER_WEB_DROP_TARGET_H__
+#define CHROME_BROWSER_WEB_DROP_TARGET_H__
+
+#include "base/base_drop_target.h"
+#include "base/scoped_ptr.h"
+
+class InterstitialDropTarget;
+class RenderViewHost;
+class WebContents;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// WebDropTarget
+//
+// A helper object that provides drop capabilities to a WebContents. The
+// DropTarget handles drags that enter the region of the WebContents by
+// passing on the events to the renderer.
+//
+class WebDropTarget : public BaseDropTarget {
+ public:
+ // Create a new WebDropTarget associating it with the given HWND and
+ // WebContents.
+ WebDropTarget(HWND source_hwnd, WebContents* contents);
+ virtual ~WebDropTarget();
+
+ void set_is_drop_target(bool is_drop_target) {
+ is_drop_target_ = is_drop_target;
+ }
+
+ protected:
+ virtual DWORD OnDragEnter(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect);
+
+ virtual DWORD OnDragOver(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect);
+
+ virtual void OnDragLeave(IDataObject* data_object);
+
+ virtual DWORD OnDrop(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect);
+
+ private:
+ // Our associated WebContents.
+ WebContents* web_contents_;
+
+ // We keep track of the render view host we're dragging over. If it changes
+ // during a drag, we need to re-send the DragEnter message. WARNING:
+ // this pointer should never be dereferenced. We only use it for comparing
+ // pointers.
+ RenderViewHost* current_rvh_;
+
+ // Used to determine what cursor we should display when dragging over web
+ // content area. This can be updated async during a drag operation.
+ bool is_drop_target_;
+
+ // A special drop target handler for when we try to d&d while an interstitial
+ // page is showing.
+ scoped_ptr<InterstitialDropTarget> interstitial_drop_target_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(WebDropTarget);
+};
+
+#endif // #ifndef CHROME_BROWSER_WEB_DROP_TARGET_H__
+