1// Copyright 2013 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/search/search_terms_tracker.h"
6
7#include "chrome/browser/profiles/profile.h"
8#include "chrome/browser/search_engines/template_url_service_factory.h"
9#include "components/search_engines/template_url.h"
10#include "components/search_engines/template_url_service.h"
11#include "content/public/browser/navigation_controller.h"
12#include "content/public/browser/navigation_entry.h"
13#include "content/public/browser/notification_details.h"
14#include "content/public/browser/notification_service.h"
15#include "content/public/browser/notification_source.h"
16#include "content/public/browser/notification_types.h"
17#include "content/public/browser/web_contents.h"
18
19namespace chrome {
20
21SearchTermsTracker::SearchTermsTracker() {
22  registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
23                 content::NotificationService::AllSources());
24  registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
25                 content::NotificationService::AllSources());
26  registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
27                 content::NotificationService::AllSources());
28}
29
30SearchTermsTracker::~SearchTermsTracker() {
31}
32
33bool SearchTermsTracker::GetSearchTerms(
34    const content::WebContents* contents,
35    base::string16* search_terms,
36    int* navigation_index) const {
37  if (contents) {
38    TabState::const_iterator it = tabs_.find(contents);
39    if (it != tabs_.end() && !it->second.search_terms.empty()) {
40      if (search_terms)
41        *search_terms = it->second.search_terms;
42      if (navigation_index)
43        *navigation_index = it->second.srp_navigation_index;
44      return true;
45    }
46  }
47  return false;
48}
49
50void SearchTermsTracker::Observe(
51    int type,
52    const content::NotificationSource& source,
53    const content::NotificationDetails& details) {
54  switch (type) {
55    case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
56    case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
57      content::NavigationController* controller =
58          content::Source<content::NavigationController>(source).ptr();
59      TabData search;
60      if (FindMostRecentSearch(controller, &search))
61        tabs_[controller->GetWebContents()] = search;
62      else
63        RemoveTabData(controller->GetWebContents());
64      break;
65    }
66
67    case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
68      RemoveTabData(content::Source<content::WebContents>(source).ptr());
69      break;
70    }
71
72    default:
73      NOTREACHED();
74      return;
75  }
76}
77
78bool SearchTermsTracker::FindMostRecentSearch(
79    const content::NavigationController* controller,
80    SearchTermsTracker::TabData* tab_data) {
81  Profile* profile =
82      Profile::FromBrowserContext(controller->GetBrowserContext());
83  DCHECK(profile);
84  if (!profile)
85    return false;
86
87  TemplateURLService* service =
88      TemplateURLServiceFactory::GetForProfile(profile);
89  TemplateURL* template_url = service->GetDefaultSearchProvider();
90
91  for (int i = controller->GetCurrentEntryIndex(); i >= 0; --i) {
92    content::NavigationEntry* entry = controller->GetEntryAtIndex(i);
93    if (entry->GetPageType() == content::PAGE_TYPE_NORMAL) {
94      if (template_url->IsSearchURL(entry->GetURL(),
95                                    service->search_terms_data())) {
96        // This entry is a search results page. Extract the terms only if this
97        // isn't the last (i.e. most recent/current) entry as we don't want to
98        // record the search terms when we're on an SRP.
99        if (i != controller->GetCurrentEntryIndex()) {
100          tab_data->srp_navigation_index = i;
101          template_url->ExtractSearchTermsFromURL(entry->GetURL(),
102                                                  service->search_terms_data(),
103                                                  &tab_data->search_terms);
104          return true;
105        }
106
107        // We've found an SRP - stop searching (even if we did not record the
108        // search terms, as anything before this entry will be unrelated).
109        break;
110      }
111    }
112
113    // Do not consider any navigations that precede a non-web-triggerable
114    // navigation as they are not related to those terms.
115    if (!ui::PageTransitionIsWebTriggerable(
116            entry->GetTransitionType())) {
117      break;
118    }
119  }
120
121  return false;
122}
123
124void SearchTermsTracker::RemoveTabData(
125    const content::WebContents* contents) {
126  TabState::iterator it = tabs_.find(contents);
127  if (it != tabs_.end()) {
128    tabs_.erase(it);
129  }
130}
131
132}  // namespace chrome
133