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
tab_contents()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
TabContentsObserverImpl(TabContents * tab,TabFinder * finder)42 TabFinder::TabContentsObserverImpl::TabContentsObserverImpl(
43 TabContents* tab,
44 TabFinder* finder)
45 : TabContentsObserver(tab),
46 finder_(finder) {
47 }
48
~TabContentsObserverImpl()49 TabFinder::TabContentsObserverImpl::~TabContentsObserverImpl() {
50 }
51
DidNavigateAnyFramePostCommit(const NavigationController::LoadCommittedDetails & details,const ViewHostMsg_FrameNavigate_Params & params)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
TabContentsDestroyed(TabContents * tab)58 void TabFinder::TabContentsObserverImpl::TabContentsDestroyed(
59 TabContents* tab) {
60 finder_->TabDestroyed(this);
61 delete this;
62 }
63
64 // static
GetInstance()65 TabFinder* TabFinder::GetInstance() {
66 return IsEnabled() ? Singleton<TabFinder>::get() : NULL;
67 }
68
69 // static
IsEnabled()70 bool TabFinder::IsEnabled() {
71 return CommandLine::ForCurrentProcess()->HasSwitch(
72 switches::kFocusExistingTabOnOpen);
73 }
74
FindTab(Browser * browser,const GURL & url,Browser ** existing_browser)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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
TabFinder()120 TabFinder::TabFinder() {
121 registrar_.Add(this, NotificationType::TAB_PARENTED,
122 NotificationService::AllSources());
123 }
124
~TabFinder()125 TabFinder::~TabFinder() {
126 STLDeleteElements(&tab_contents_observers_);
127 }
128
Init()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
DidNavigateAnyFramePostCommit(TabContents * source,const NavigationController::LoadCommittedDetails & details,const ViewHostMsg_FrameNavigate_Params & params)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
TabMatchesURL(TabContents * tab_contents,const GURL & url)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
FindTabInBrowser(Browser * browser,const GURL & url)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
TrackTab(TabContents * tab)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
TrackBrowser(Browser * browser)185 void TabFinder::TrackBrowser(Browser* browser) {
186 for (int i = 0; i < browser->tab_count(); ++i)
187 FetchRedirectStart(browser->GetTabContentsAt(i));
188 }
189
TabDestroyed(TabContentsObserverImpl * observer)190 void TabFinder::TabDestroyed(TabContentsObserverImpl* observer) {
191 DCHECK_GT(tab_contents_observers_.count(observer), 0u);
192 tab_contents_observers_.erase(observer);
193 }
194
CancelRequestsFor(TabContents * tab_contents)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
FetchRedirectStart(TabContents * tab)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
QueryRedirectsToComplete(HistoryService::Handle handle,GURL url,bool success,history::RedirectList * redirects)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