// 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/bind.h" #include "base/bind_helpers.h" #include "base/command_line.h" #include "base/stl_util.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/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/common/chrome_switches.h" #include "content/browser/tab_contents/navigation_details.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/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" #include "content/public/common/frame_navigate_params.h" #include "content/public/common/page_transition_types.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 DidNavigateAnyFrame( const content::LoadCommittedDetails& details, const content::FrameNavigateParams& 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::DidNavigateAnyFrame( const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) { finder_->DidNavigateAnyFrame(tab_contents(), details, params); } void TabFinder::TabContentsObserverImpl::TabContentsDestroyed( TabContents* tab) { finder_->TabDestroyed(this); delete this; } // static TabFinder* TabFinder::GetInstance() { return IsEnabled() ? Singleton::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() && (*i)->profile()->IsSameProfile(browser->profile())) { tab_in_browser = FindTabInBrowser(*i, url); if (tab_in_browser) { *existing_browser = *i; return tab_in_browser; } } } return NULL; } void TabFinder::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(type, content::NOTIFICATION_TAB_PARENTED); // The tab was added to a browser. Query for its state now. TabContentsWrapper* tab = content::Source(source).ptr(); TrackTab(tab->tab_contents()); } TabFinder::TabFinder() { registrar_.Add(this, content::NOTIFICATION_TAB_PARENTED, content::NotificationService::AllSources()); } TabFinder::~TabFinder() { STLDeleteElements(&tab_contents_observers_); } void TabFinder::DidNavigateAnyFrame( TabContents* source, const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) { CancelRequestsFor(source); if (content::PageTransitionIsRedirect(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->is_type_tabbed()) 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::TabDestroyed(TabContentsObserverImpl* observer) { DCHECK_GT(tab_contents_observers_.count(observer), 0u); tab_contents_observers_.erase(observer); } void TabFinder::CancelRequestsFor(TabContents* tab_contents) { Profile* profile = Profile::FromBrowserContext(tab_contents->browser_context()); if (profile->IsOffTheRecord()) return; tab_contents_to_url_.erase(tab_contents); HistoryService* history = 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) { Profile* profile = Profile::FromBrowserContext(tab->browser_context()); if (profile->IsOffTheRecord()) return; NavigationEntry* committed_entry = tab->controller().GetLastCommittedEntry(); if (!committed_entry || committed_entry->url().is_empty()) return; HistoryService* history = profile->GetHistoryService( Profile::EXPLICIT_ACCESS); if (history) { CancelableRequestProvider::Handle request_handle = history->QueryRedirectsTo( committed_entry->url(), &callback_consumer_, base::Bind(&TabFinder::QueryRedirectsToComplete, base::Unretained(this))); 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(); } }