search_engine_tab_helper.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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/ui/search_engines/search_engine_tab_helper.h"
6
7#include "chrome/browser/profiles/profile.h"
8#include "chrome/browser/search_engines/template_url.h"
9#include "chrome/browser/search_engines/template_url_fetcher.h"
10#include "chrome/browser/search_engines/template_url_model.h"
11#include "chrome/browser/ui/search_engines/template_url_fetcher_ui_callbacks.h"
12#include "chrome/common/render_messages.h"
13#include "content/common/view_messages.h"
14#include "content/browser/tab_contents/tab_contents.h"
15
16namespace {
17
18// Returns true if the entry's transition type is FORM_SUBMIT.
19bool IsFormSubmit(const NavigationEntry* entry) {
20  return (PageTransition::StripQualifier(entry->transition_type()) ==
21          PageTransition::FORM_SUBMIT);
22}
23
24}  // namespace
25
26SearchEngineTabHelper::SearchEngineTabHelper(TabContents* tab_contents)
27    : TabContentsObserver(tab_contents) {
28  DCHECK(tab_contents);
29}
30
31SearchEngineTabHelper::~SearchEngineTabHelper() {
32}
33
34void SearchEngineTabHelper::DidNavigateMainFramePostCommit(
35    const NavigationController::LoadCommittedDetails& /*details*/,
36    const ViewHostMsg_FrameNavigate_Params& params) {
37  GenerateKeywordIfNecessary(params);
38}
39
40bool SearchEngineTabHelper::OnMessageReceived(const IPC::Message& message) {
41  bool handled = true;
42  IPC_BEGIN_MESSAGE_MAP(SearchEngineTabHelper, message)
43    IPC_MESSAGE_HANDLER(ViewHostMsg_PageHasOSDD, OnPageHasOSDD)
44    IPC_MESSAGE_UNHANDLED(handled = false)
45  IPC_END_MESSAGE_MAP()
46
47  return handled;
48}
49
50void SearchEngineTabHelper::OnPageHasOSDD(
51    int32 page_id,
52    const GURL& doc_url,
53    const search_provider::OSDDType& msg_provider_type) {
54  // Checks to see if we should generate a keyword based on the OSDD, and if
55  // necessary uses TemplateURLFetcher to download the OSDD and create a
56  // keyword.
57
58  // Make sure page_id is the current page and other basic checks.
59  DCHECK(doc_url.is_valid());
60  if (!tab_contents()->IsActiveEntry(page_id))
61    return;
62  if (!tab_contents()->profile()->GetTemplateURLFetcher())
63    return;
64  if (tab_contents()->profile()->IsOffTheRecord())
65    return;
66
67  TemplateURLFetcher::ProviderType provider_type;
68  switch (msg_provider_type) {
69    case search_provider::AUTODETECTED_PROVIDER:
70      provider_type = TemplateURLFetcher::AUTODETECTED_PROVIDER;
71      break;
72
73    case search_provider::EXPLICIT_DEFAULT_PROVIDER:
74      provider_type = TemplateURLFetcher::EXPLICIT_DEFAULT_PROVIDER;
75      break;
76
77    case search_provider::EXPLICIT_PROVIDER:
78      provider_type = TemplateURLFetcher::EXPLICIT_PROVIDER;
79      break;
80
81    default:
82      NOTREACHED();
83      return;
84  }
85
86  const NavigationController& controller = tab_contents()->controller();
87  const NavigationEntry* entry = controller.GetLastCommittedEntry();
88  DCHECK(entry);
89
90  const NavigationEntry* base_entry = entry;
91  if (IsFormSubmit(base_entry)) {
92    // If the current page is a form submit, find the last page that was not
93    // a form submit and use its url to generate the keyword from.
94    int index = controller.last_committed_entry_index() - 1;
95    while (index >= 0 && IsFormSubmit(controller.GetEntryAtIndex(index)))
96      index--;
97    if (index >= 0)
98      base_entry = controller.GetEntryAtIndex(index);
99    else
100      base_entry = NULL;
101  }
102
103  // We want to use the user typed URL if available since that represents what
104  // the user typed to get here, and fall back on the regular URL if not.
105  if (!base_entry)
106    return;
107  GURL keyword_url = base_entry->user_typed_url().is_valid() ?
108          base_entry->user_typed_url() : base_entry->url();
109  if (!keyword_url.is_valid())
110    return;
111
112  string16 keyword = TemplateURLModel::GenerateKeyword(
113      keyword_url,
114      provider_type == TemplateURLFetcher::AUTODETECTED_PROVIDER);
115
116  // Download the OpenSearch description document. If this is successful, a
117  // new keyword will be created when done.
118  tab_contents()->profile()->GetTemplateURLFetcher()->ScheduleDownload(
119      keyword,
120      doc_url,
121      base_entry->favicon().url(),
122      new TemplateURLFetcherUICallbacks(this, tab_contents()),
123      provider_type);
124}
125
126void SearchEngineTabHelper::GenerateKeywordIfNecessary(
127    const ViewHostMsg_FrameNavigate_Params& params) {
128  if (!params.searchable_form_url.is_valid())
129    return;
130
131  if (tab_contents()->profile()->IsOffTheRecord())
132    return;
133
134  const NavigationController& controller = tab_contents()->controller();
135  int last_index = controller.last_committed_entry_index();
136  // When there was no previous page, the last index will be 0. This is
137  // normally due to a form submit that opened in a new tab.
138  // TODO(brettw) bug 916126: we should support keywords when form submits
139  //              happen in new tabs.
140  if (last_index <= 0)
141    return;
142  const NavigationEntry* previous_entry =
143      controller.GetEntryAtIndex(last_index - 1);
144  if (IsFormSubmit(previous_entry)) {
145    // Only generate a keyword if the previous page wasn't itself a form
146    // submit.
147    return;
148  }
149
150  GURL keyword_url = previous_entry->user_typed_url().is_valid() ?
151          previous_entry->user_typed_url() : previous_entry->url();
152  string16 keyword =
153      TemplateURLModel::GenerateKeyword(keyword_url, true);  // autodetected
154  if (keyword.empty())
155    return;
156
157  TemplateURLModel* url_model =
158      tab_contents()->profile()->GetTemplateURLModel();
159  if (!url_model)
160    return;
161
162  if (!url_model->loaded()) {
163    url_model->Load();
164    return;
165  }
166
167  const TemplateURL* current_url;
168  GURL url = params.searchable_form_url;
169  if (!url_model->CanReplaceKeyword(keyword, url, &current_url))
170    return;
171
172  if (current_url) {
173    if (current_url->originating_url().is_valid()) {
174      // The existing keyword was generated from an OpenSearch description
175      // document, don't regenerate.
176      return;
177    }
178    url_model->Remove(current_url);
179  }
180  TemplateURL* new_url = new TemplateURL();
181  new_url->set_keyword(keyword);
182  new_url->set_short_name(keyword);
183  new_url->SetURL(url.spec(), 0, 0);
184  new_url->add_input_encoding(params.searchable_form_encoding);
185  DCHECK(controller.GetLastCommittedEntry());
186  const GURL& favicon_url =
187      controller.GetLastCommittedEntry()->favicon().url();
188  if (favicon_url.is_valid()) {
189    new_url->SetFaviconURL(favicon_url);
190  } else {
191    // The favicon url isn't valid. This means there really isn't a favicon,
192    // or the favicon url wasn't obtained before the load started. This assumes
193    // the later.
194    // TODO(sky): Need a way to set the favicon that doesn't involve generating
195    // its url.
196    new_url->SetFaviconURL(TemplateURL::GenerateFaviconURL(params.referrer));
197  }
198  new_url->set_safe_for_autoreplace(true);
199  url_model->Add(new_url);
200}
201