diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-15 00:59:16 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-15 00:59:16 +0000 |
commit | f3ec774a2c177d3c6845553d3bf9735a7b8a5907 (patch) | |
tree | ed09ccf69300383bd4b2e8ac7c8b9d1bdeead07b /chrome/browser/tab_contents | |
parent | bb515eda39129537a089a062c3db3152e63f24d9 (diff) | |
download | chromium_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')
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 = ¤t()->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(), ¶m, + 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, ¤t_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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms1, 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(¶ms2, 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(¶ms1, 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(¶ms2, 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(¶ms1, 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(¶ms2a, 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(¶ms2b, 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(¶ms1, 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(¶ms2, 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(¶ms3, 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(¶ms4, 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(¶ms1, 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(¶ms2, 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(¶ms1, 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(¶ms2, 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(¶ms1, 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__ + |