// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/tabs/tab_finder.h"
#include "base/command_line.h"
#include "base/stl_util-inl.h"
#include "chrome/browser/history/history.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/chrome_switches.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_observer.h"
#include "content/common/notification_service.h"
#include "content/common/notification_source.h"
#include "content/common/notification_type.h"
#include "content/common/page_transition_types.h"
#include "content/common/view_messages.h"
class TabFinder::TabContentsObserverImpl : public TabContentsObserver {
public:
TabContentsObserverImpl(TabContents* tab, TabFinder* finder);
virtual ~TabContentsObserverImpl();
TabContents* tab_contents() { return TabContentsObserver::tab_contents(); }
// TabContentsObserver overrides:
virtual void DidNavigateAnyFramePostCommit(
const NavigationController::LoadCommittedDetails& details,
const ViewHostMsg_FrameNavigate_Params& params) OVERRIDE;
virtual void TabContentsDestroyed(TabContents* tab) OVERRIDE;
private:
TabFinder* finder_;
DISALLOW_COPY_AND_ASSIGN(TabContentsObserverImpl);
};
TabFinder::TabContentsObserverImpl::TabContentsObserverImpl(
TabContents* tab,
TabFinder* finder)
: TabContentsObserver(tab),
finder_(finder) {
}
TabFinder::TabContentsObserverImpl::~TabContentsObserverImpl() {
}
void TabFinder::TabContentsObserverImpl::DidNavigateAnyFramePostCommit(
const NavigationController::LoadCommittedDetails& details,
const ViewHostMsg_FrameNavigate_Params& params) {
finder_->DidNavigateAnyFramePostCommit(tab_contents(), details, params);
}
void TabFinder::TabContentsObserverImpl::TabContentsDestroyed(
TabContents* tab) {
finder_->TabDestroyed(this);
delete this;
}
// static
TabFinder* TabFinder::GetInstance() {
return IsEnabled() ? Singleton<TabFinder>::get() : NULL;
}
// static
bool TabFinder::IsEnabled() {
return CommandLine::ForCurrentProcess()->HasSwitch(
switches::kFocusExistingTabOnOpen);
}
TabContents* TabFinder::FindTab(Browser* browser,
const GURL& url,
Browser** existing_browser) {
if (browser->profile()->IsOffTheRecord())
return NULL;
// If the current tab matches the url, ignore it and let the user reload the
// existing tab.
TabContents* selected_tab = browser->GetSelectedTabContents();
if (TabMatchesURL(selected_tab, url))
return NULL;
// See if the current browser has a tab matching the specified url.
TabContents* tab_in_browser = FindTabInBrowser(browser, url);
if (tab_in_browser) {
*existing_browser = browser;
return tab_in_browser;
}
// Then check other browsers.
for (BrowserList::const_iterator i = BrowserList::begin();
i != BrowserList::end(); ++i) {
if (!(*i)->profile()->IsOffTheRecord()) {
tab_in_browser = FindTabInBrowser(*i, url);
if (tab_in_browser) {
*existing_browser = *i;
return tab_in_browser;
}
}
}
return NULL;
}
void TabFinder::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
DCHECK_EQ(type.value, NotificationType::TAB_PARENTED);
// The tab was added to a browser. Query for its state now.
NavigationController* controller =
Source<NavigationController>(source).ptr();
TrackTab(controller->tab_contents());
}
TabFinder::TabFinder() {
registrar_.Add(this, NotificationType::TAB_PARENTED,
NotificationService::AllSources());
}
TabFinder::~TabFinder() {
STLDeleteElements(&tab_contents_observers_);
}
void TabFinder::Init() {
for (BrowserList::const_iterator i = BrowserList::begin();
i != BrowserList::end(); ++i) {
if (!(*i)->profile()->IsOffTheRecord())
TrackBrowser(*i);
}
}
void TabFinder::DidNavigateAnyFramePostCommit(
TabContents* source,
const NavigationController::LoadCommittedDetails& details,
const ViewHostMsg_FrameNavigate_Params& params) {
CancelRequestsFor(source);
if (PageTransition::IsRedirect(params.transition)) {
// If this is a redirect, we need to go to the db to get the start.
FetchRedirectStart(source);
} else if (params.redirects.size() > 1 ||
params.redirects[0] != details.entry->url()) {
tab_contents_to_url_[source] = params.redirects[0];
}
}
bool TabFinder::TabMatchesURL(TabContents* tab_contents, const GURL& url) {
if (tab_contents->GetURL() == url)
return true;
TabContentsToURLMap::const_iterator i =
tab_contents_to_url_.find(tab_contents);
return i != tab_contents_to_url_.end() && i->second == url;
}
TabContents* TabFinder::FindTabInBrowser(Browser* browser, const GURL& url) {
if (browser->type() != Browser::TYPE_NORMAL)
return NULL;
for (int i = 0; i < browser->tab_count(); ++i) {
if (TabMatchesURL(browser->GetTabContentsAt(i), url))
return browser->GetTabContentsAt(i);
}
return NULL;
}
void TabFinder::TrackTab(TabContents* tab) {
for (TabContentsObservers::const_iterator i = tab_contents_observers_.begin();
i != tab_contents_observers_.end(); ++i) {
if ((*i)->tab_contents() == tab) {
// Already tracking the tab.
return;
}
}
TabContentsObserverImpl* observer = new TabContentsObserverImpl(tab, this);
tab_contents_observers_.insert(observer);
FetchRedirectStart(tab);
}
void TabFinder::TrackBrowser(Browser* browser) {
for (int i = 0; i < browser->tab_count(); ++i)
FetchRedirectStart(browser->GetTabContentsAt(i));
}
void TabFinder::TabDestroyed(TabContentsObserverImpl* observer) {
DCHECK_GT(tab_contents_observers_.count(observer), 0u);
tab_contents_observers_.erase(observer);
}
void TabFinder::CancelRequestsFor(TabContents* tab_contents) {
if (tab_contents->profile()->IsOffTheRecord())
return;
tab_contents_to_url_.erase(tab_contents);
HistoryService* history = tab_contents->profile()->GetHistoryService(
Profile::EXPLICIT_ACCESS);
if (history) {
CancelableRequestProvider::Handle request_handle;
if (callback_consumer_.GetFirstHandleForClientData(tab_contents,
&request_handle)) {
history->CancelRequest(request_handle);
}
}
}
void TabFinder::FetchRedirectStart(TabContents* tab) {
if (tab->profile()->IsOffTheRecord())
return;
NavigationEntry* committed_entry = tab->controller().GetLastCommittedEntry();
if (!committed_entry || committed_entry->url().is_empty())
return;
HistoryService* history =tab->profile()->GetHistoryService(
Profile::EXPLICIT_ACCESS);
if (history) {
CancelableRequestProvider::Handle request_handle =
history->QueryRedirectsTo(
committed_entry->url(),
&callback_consumer_,
NewCallback(this, &TabFinder::QueryRedirectsToComplete));
callback_consumer_.SetClientData(history, request_handle, tab);
}
}
void TabFinder::QueryRedirectsToComplete(HistoryService::Handle handle,
GURL url,
bool success,
history::RedirectList* redirects) {
if (success && !redirects->empty()) {
TabContents* tab_contents =
callback_consumer_.GetClientDataForCurrentRequest();
DCHECK(tab_contents);
tab_contents_to_url_[tab_contents] = redirects->back();
}
}