blob: 70988e2de365c71a757e66386f9bf57a81fbcd0c [file] [log] [blame]
// 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);
}
}