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
23class 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
42TabFinder::TabContentsObserverImpl::TabContentsObserverImpl(
43    TabContents* tab,
44    TabFinder* finder)
45    : TabContentsObserver(tab),
46      finder_(finder) {
47}
48
49TabFinder::TabContentsObserverImpl::~TabContentsObserverImpl() {
50}
51
52void TabFinder::TabContentsObserverImpl::DidNavigateAnyFramePostCommit(
53    const NavigationController::LoadCommittedDetails& details,
54    const ViewHostMsg_FrameNavigate_Params& params) {
55  finder_->DidNavigateAnyFramePostCommit(tab_contents(), details, params);
56}
57
58void TabFinder::TabContentsObserverImpl::TabContentsDestroyed(
59    TabContents* tab) {
60  finder_->TabDestroyed(this);
61  delete this;
62}
63
64// static
65TabFinder* TabFinder::GetInstance() {
66  return IsEnabled() ? Singleton<TabFinder>::get() : NULL;
67}
68
69// static
70bool TabFinder::IsEnabled() {
71  return CommandLine::ForCurrentProcess()->HasSwitch(
72      switches::kFocusExistingTabOnOpen);
73}
74
75TabContents* 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
109void 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
120TabFinder::TabFinder() {
121  registrar_.Add(this, NotificationType::TAB_PARENTED,
122                 NotificationService::AllSources());
123}
124
125TabFinder::~TabFinder() {
126  STLDeleteElements(&tab_contents_observers_);
127}
128
129void 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
137void 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
152bool 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
161TabContents* 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
172void 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
185void TabFinder::TrackBrowser(Browser* browser) {
186  for (int i = 0; i < browser->tab_count(); ++i)
187    FetchRedirectStart(browser->GetTabContentsAt(i));
188}
189
190void TabFinder::TabDestroyed(TabContentsObserverImpl* observer) {
191  DCHECK_GT(tab_contents_observers_.count(observer), 0u);
192  tab_contents_observers_.erase(observer);
193}
194
195void 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
212void 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
232void 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