1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/tabs/tab_finder.h" 6 7 #include "base/command_line.h" 8 #include "base/stl_util-inl.h" 9 #include "chrome/browser/history/history.h" 10 #include "chrome/browser/profiles/profile.h" 11 #include "chrome/browser/ui/browser.h" 12 #include "chrome/browser/ui/browser_list.h" 13 #include "chrome/common/chrome_switches.h" 14 #include "content/browser/tab_contents/navigation_entry.h" 15 #include "content/browser/tab_contents/tab_contents.h" 16 #include "content/browser/tab_contents/tab_contents_observer.h" 17 #include "content/common/notification_service.h" 18 #include "content/common/notification_source.h" 19 #include "content/common/notification_type.h" 20 #include "content/common/page_transition_types.h" 21 #include "content/common/view_messages.h" 22 23 class TabFinder::TabContentsObserverImpl : public TabContentsObserver { 24 public: 25 TabContentsObserverImpl(TabContents* tab, TabFinder* finder); 26 virtual ~TabContentsObserverImpl(); 27 28 TabContents* tab_contents() { return TabContentsObserver::tab_contents(); } 29 30 // TabContentsObserver overrides: 31 virtual void DidNavigateAnyFramePostCommit( 32 const NavigationController::LoadCommittedDetails& details, 33 const ViewHostMsg_FrameNavigate_Params& params) OVERRIDE; 34 virtual void TabContentsDestroyed(TabContents* tab) OVERRIDE; 35 36 private: 37 TabFinder* finder_; 38 39 DISALLOW_COPY_AND_ASSIGN(TabContentsObserverImpl); 40 }; 41 42 TabFinder::TabContentsObserverImpl::TabContentsObserverImpl( 43 TabContents* tab, 44 TabFinder* finder) 45 : TabContentsObserver(tab), 46 finder_(finder) { 47 } 48 49 TabFinder::TabContentsObserverImpl::~TabContentsObserverImpl() { 50 } 51 52 void TabFinder::TabContentsObserverImpl::DidNavigateAnyFramePostCommit( 53 const NavigationController::LoadCommittedDetails& details, 54 const ViewHostMsg_FrameNavigate_Params& params) { 55 finder_->DidNavigateAnyFramePostCommit(tab_contents(), details, params); 56 } 57 58 void TabFinder::TabContentsObserverImpl::TabContentsDestroyed( 59 TabContents* tab) { 60 finder_->TabDestroyed(this); 61 delete this; 62 } 63 64 // static 65 TabFinder* TabFinder::GetInstance() { 66 return IsEnabled() ? Singleton<TabFinder>::get() : NULL; 67 } 68 69 // static 70 bool TabFinder::IsEnabled() { 71 return CommandLine::ForCurrentProcess()->HasSwitch( 72 switches::kFocusExistingTabOnOpen); 73 } 74 75 TabContents* TabFinder::FindTab(Browser* browser, 76 const GURL& url, 77 Browser** existing_browser) { 78 if (browser->profile()->IsOffTheRecord()) 79 return NULL; 80 81 // If the current tab matches the url, ignore it and let the user reload the 82 // existing tab. 83 TabContents* selected_tab = browser->GetSelectedTabContents(); 84 if (TabMatchesURL(selected_tab, url)) 85 return NULL; 86 87 // See if the current browser has a tab matching the specified url. 88 TabContents* tab_in_browser = FindTabInBrowser(browser, url); 89 if (tab_in_browser) { 90 *existing_browser = browser; 91 return tab_in_browser; 92 } 93 94 // Then check other browsers. 95 for (BrowserList::const_iterator i = BrowserList::begin(); 96 i != BrowserList::end(); ++i) { 97 if (!(*i)->profile()->IsOffTheRecord()) { 98 tab_in_browser = FindTabInBrowser(*i, url); 99 if (tab_in_browser) { 100 *existing_browser = *i; 101 return tab_in_browser; 102 } 103 } 104 } 105 106 return NULL; 107 } 108 109 void TabFinder::Observe(NotificationType type, 110 const NotificationSource& source, 111 const NotificationDetails& details) { 112 DCHECK_EQ(type.value, NotificationType::TAB_PARENTED); 113 114 // The tab was added to a browser. Query for its state now. 115 NavigationController* controller = 116 Source<NavigationController>(source).ptr(); 117 TrackTab(controller->tab_contents()); 118 } 119 120 TabFinder::TabFinder() { 121 registrar_.Add(this, NotificationType::TAB_PARENTED, 122 NotificationService::AllSources()); 123 } 124 125 TabFinder::~TabFinder() { 126 STLDeleteElements(&tab_contents_observers_); 127 } 128 129 void TabFinder::Init() { 130 for (BrowserList::const_iterator i = BrowserList::begin(); 131 i != BrowserList::end(); ++i) { 132 if (!(*i)->profile()->IsOffTheRecord()) 133 TrackBrowser(*i); 134 } 135 } 136 137 void TabFinder::DidNavigateAnyFramePostCommit( 138 TabContents* source, 139 const NavigationController::LoadCommittedDetails& details, 140 const ViewHostMsg_FrameNavigate_Params& params) { 141 CancelRequestsFor(source); 142 143 if (PageTransition::IsRedirect(params.transition)) { 144 // If this is a redirect, we need to go to the db to get the start. 145 FetchRedirectStart(source); 146 } else if (params.redirects.size() > 1 || 147 params.redirects[0] != details.entry->url()) { 148 tab_contents_to_url_[source] = params.redirects[0]; 149 } 150 } 151 152 bool TabFinder::TabMatchesURL(TabContents* tab_contents, const GURL& url) { 153 if (tab_contents->GetURL() == url) 154 return true; 155 156 TabContentsToURLMap::const_iterator i = 157 tab_contents_to_url_.find(tab_contents); 158 return i != tab_contents_to_url_.end() && i->second == url; 159 } 160 161 TabContents* TabFinder::FindTabInBrowser(Browser* browser, const GURL& url) { 162 if (browser->type() != Browser::TYPE_NORMAL) 163 return NULL; 164 165 for (int i = 0; i < browser->tab_count(); ++i) { 166 if (TabMatchesURL(browser->GetTabContentsAt(i), url)) 167 return browser->GetTabContentsAt(i); 168 } 169 return NULL; 170 } 171 172 void TabFinder::TrackTab(TabContents* tab) { 173 for (TabContentsObservers::const_iterator i = tab_contents_observers_.begin(); 174 i != tab_contents_observers_.end(); ++i) { 175 if ((*i)->tab_contents() == tab) { 176 // Already tracking the tab. 177 return; 178 } 179 } 180 TabContentsObserverImpl* observer = new TabContentsObserverImpl(tab, this); 181 tab_contents_observers_.insert(observer); 182 FetchRedirectStart(tab); 183 } 184 185 void TabFinder::TrackBrowser(Browser* browser) { 186 for (int i = 0; i < browser->tab_count(); ++i) 187 FetchRedirectStart(browser->GetTabContentsAt(i)); 188 } 189 190 void TabFinder::TabDestroyed(TabContentsObserverImpl* observer) { 191 DCHECK_GT(tab_contents_observers_.count(observer), 0u); 192 tab_contents_observers_.erase(observer); 193 } 194 195 void TabFinder::CancelRequestsFor(TabContents* tab_contents) { 196 if (tab_contents->profile()->IsOffTheRecord()) 197 return; 198 199 tab_contents_to_url_.erase(tab_contents); 200 201 HistoryService* history = tab_contents->profile()->GetHistoryService( 202 Profile::EXPLICIT_ACCESS); 203 if (history) { 204 CancelableRequestProvider::Handle request_handle; 205 if (callback_consumer_.GetFirstHandleForClientData(tab_contents, 206 &request_handle)) { 207 history->CancelRequest(request_handle); 208 } 209 } 210 } 211 212 void TabFinder::FetchRedirectStart(TabContents* tab) { 213 if (tab->profile()->IsOffTheRecord()) 214 return; 215 216 NavigationEntry* committed_entry = tab->controller().GetLastCommittedEntry(); 217 if (!committed_entry || committed_entry->url().is_empty()) 218 return; 219 220 HistoryService* history =tab->profile()->GetHistoryService( 221 Profile::EXPLICIT_ACCESS); 222 if (history) { 223 CancelableRequestProvider::Handle request_handle = 224 history->QueryRedirectsTo( 225 committed_entry->url(), 226 &callback_consumer_, 227 NewCallback(this, &TabFinder::QueryRedirectsToComplete)); 228 callback_consumer_.SetClientData(history, request_handle, tab); 229 } 230 } 231 232 void TabFinder::QueryRedirectsToComplete(HistoryService::Handle handle, 233 GURL url, 234 bool success, 235 history::RedirectList* redirects) { 236 if (success && !redirects->empty()) { 237 TabContents* tab_contents = 238 callback_consumer_.GetClientDataForCurrentRequest(); 239 DCHECK(tab_contents); 240 tab_contents_to_url_[tab_contents] = redirects->back(); 241 } 242 } 243