1 // Copyright (c) 2012 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_fetcher_factory.h"
9 #include "chrome/browser/search_engines/template_url_service_factory.h"
10 #include "chrome/browser/ui/search_engines/search_engine_tab_helper_delegate.h"
11 #include "chrome/common/render_messages.h"
12 #include "chrome/common/url_constants.h"
13 #include "components/search_engines/template_url.h"
14 #include "components/search_engines/template_url_fetcher.h"
15 #include "components/search_engines/template_url_service.h"
16 #include "content/public/browser/favicon_status.h"
17 #include "content/public/browser/navigation_controller.h"
18 #include "content/public/browser/navigation_entry.h"
19 #include "content/public/browser/render_frame_host.h"
20 #include "content/public/browser/render_process_host.h"
21 #include "content/public/browser/web_contents.h"
22 #include "content/public/common/frame_navigate_params.h"
23 #include "content/public/common/url_fetcher.h"
24
25 using content::NavigationController;
26 using content::NavigationEntry;
27 using content::WebContents;
28
29 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SearchEngineTabHelper);
30
31 namespace {
32
33 // Returns true if the entry's transition type is FORM_SUBMIT.
IsFormSubmit(const NavigationEntry * entry)34 bool IsFormSubmit(const NavigationEntry* entry) {
35 return (ui::PageTransitionStripQualifier(entry->GetTransitionType()) ==
36 ui::PAGE_TRANSITION_FORM_SUBMIT);
37 }
38
GenerateKeywordFromNavigationEntry(const NavigationEntry * entry)39 base::string16 GenerateKeywordFromNavigationEntry(
40 const NavigationEntry* entry) {
41 // Don't autogenerate keywords for pages that are the result of form
42 // submissions.
43 if (IsFormSubmit(entry))
44 return base::string16();
45
46 // We want to use the user typed URL if available since that represents what
47 // the user typed to get here, and fall back on the regular URL if not.
48 GURL url = entry->GetUserTypedURL();
49 if (!url.is_valid()) {
50 url = entry->GetURL();
51 if (!url.is_valid())
52 return base::string16();
53 }
54
55 // Don't autogenerate keywords for referrers that are anything other than HTTP
56 // or have a path.
57 //
58 // If we relax the path constraint, we need to be sure to sanitize the path
59 // elements and update AutocompletePopup to look for keywords using the path.
60 // See http://b/issue?id=863583.
61 if (!url.SchemeIs(url::kHttpScheme) || (url.path().length() > 1))
62 return base::string16();
63
64 return TemplateURL::GenerateKeyword(url);
65 }
66
AssociateURLFetcherWithWebContents(content::WebContents * web_contents,net::URLFetcher * url_fetcher)67 void AssociateURLFetcherWithWebContents(content::WebContents* web_contents,
68 net::URLFetcher* url_fetcher) {
69 content::AssociateURLFetcherWithRenderFrame(
70 url_fetcher,
71 web_contents->GetURL(),
72 web_contents->GetRenderProcessHost()->GetID(),
73 web_contents->GetMainFrame()->GetRoutingID());
74 }
75
76 } // namespace
77
~SearchEngineTabHelper()78 SearchEngineTabHelper::~SearchEngineTabHelper() {
79 }
80
DidNavigateMainFrame(const content::LoadCommittedDetails &,const content::FrameNavigateParams & params)81 void SearchEngineTabHelper::DidNavigateMainFrame(
82 const content::LoadCommittedDetails& /*details*/,
83 const content::FrameNavigateParams& params) {
84 GenerateKeywordIfNecessary(params);
85 }
86
OnMessageReceived(const IPC::Message & message)87 bool SearchEngineTabHelper::OnMessageReceived(const IPC::Message& message) {
88 bool handled = true;
89 IPC_BEGIN_MESSAGE_MAP(SearchEngineTabHelper, message)
90 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_PageHasOSDD, OnPageHasOSDD)
91 IPC_MESSAGE_UNHANDLED(handled = false)
92 IPC_END_MESSAGE_MAP()
93
94 return handled;
95 }
96
SearchEngineTabHelper(WebContents * web_contents)97 SearchEngineTabHelper::SearchEngineTabHelper(WebContents* web_contents)
98 : content::WebContentsObserver(web_contents),
99 weak_ptr_factory_(this) {
100 DCHECK(web_contents);
101 }
102
OnPageHasOSDD(const GURL & page_url,const GURL & osdd_url,const search_provider::OSDDType & msg_provider_type)103 void SearchEngineTabHelper::OnPageHasOSDD(
104 const GURL& page_url,
105 const GURL& osdd_url,
106 const search_provider::OSDDType& msg_provider_type) {
107 // Checks to see if we should generate a keyword based on the OSDD, and if
108 // necessary uses TemplateURLFetcher to download the OSDD and create a
109 // keyword.
110
111 // Make sure that the page is the current page and other basic checks.
112 if (!osdd_url.is_valid())
113 return;
114 Profile* profile =
115 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
116 if (page_url != web_contents()->GetLastCommittedURL() ||
117 !TemplateURLFetcherFactory::GetForProfile(profile) ||
118 profile->IsOffTheRecord())
119 return;
120
121 TemplateURLFetcher::ProviderType provider_type =
122 (msg_provider_type == search_provider::AUTODETECTED_PROVIDER) ?
123 TemplateURLFetcher::AUTODETECTED_PROVIDER :
124 TemplateURLFetcher::EXPLICIT_PROVIDER;
125
126 // If the current page is a form submit, find the last page that was not a
127 // form submit and use its url to generate the keyword from.
128 const NavigationController& controller = web_contents()->GetController();
129 const NavigationEntry* entry = controller.GetLastCommittedEntry();
130 for (int index = controller.GetLastCommittedEntryIndex();
131 (index > 0) && IsFormSubmit(entry);
132 entry = controller.GetEntryAtIndex(index))
133 --index;
134 if (IsFormSubmit(entry))
135 return;
136
137 // Autogenerate a keyword for the autodetected case; in the other cases we'll
138 // generate a keyword later after fetching the OSDD.
139 base::string16 keyword;
140 if (provider_type == TemplateURLFetcher::AUTODETECTED_PROVIDER) {
141 keyword = GenerateKeywordFromNavigationEntry(entry);
142 if (keyword.empty())
143 return;
144 }
145
146 // Download the OpenSearch description document. If this is successful, a
147 // new keyword will be created when done.
148 TemplateURLFetcherFactory::GetForProfile(profile)->ScheduleDownload(
149 keyword, osdd_url, entry->GetFavicon().url,
150 base::Bind(&AssociateURLFetcherWithWebContents, web_contents()),
151 base::Bind(&SearchEngineTabHelper::OnDownloadedOSDD,
152 weak_ptr_factory_.GetWeakPtr()),
153 provider_type);
154 }
155
OnDownloadedOSDD(scoped_ptr<TemplateURL> template_url)156 void SearchEngineTabHelper::OnDownloadedOSDD(
157 scoped_ptr<TemplateURL> template_url) {
158 Profile* profile =
159 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
160 delegate_->ConfirmAddSearchProvider(template_url.release(), profile);
161 }
162
GenerateKeywordIfNecessary(const content::FrameNavigateParams & params)163 void SearchEngineTabHelper::GenerateKeywordIfNecessary(
164 const content::FrameNavigateParams& params) {
165 if (!params.searchable_form_url.is_valid())
166 return;
167
168 Profile* profile =
169 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
170 if (profile->IsOffTheRecord())
171 return;
172
173 const NavigationController& controller = web_contents()->GetController();
174 int last_index = controller.GetLastCommittedEntryIndex();
175 // When there was no previous page, the last index will be 0. This is
176 // normally due to a form submit that opened in a new tab.
177 // TODO(brettw) bug 916126: we should support keywords when form submits
178 // happen in new tabs.
179 if (last_index <= 0)
180 return;
181
182 base::string16 keyword(GenerateKeywordFromNavigationEntry(
183 controller.GetEntryAtIndex(last_index - 1)));
184 if (keyword.empty())
185 return;
186
187 TemplateURLService* url_service =
188 TemplateURLServiceFactory::GetForProfile(profile);
189 if (!url_service)
190 return;
191
192 if (!url_service->loaded()) {
193 url_service->Load();
194 return;
195 }
196
197 TemplateURL* current_url;
198 GURL url = params.searchable_form_url;
199 if (!url_service->CanReplaceKeyword(keyword, url, ¤t_url))
200 return;
201
202 if (current_url) {
203 if (current_url->originating_url().is_valid()) {
204 // The existing keyword was generated from an OpenSearch description
205 // document, don't regenerate.
206 return;
207 }
208 url_service->Remove(current_url);
209 }
210
211 TemplateURLData data;
212 data.short_name = keyword;
213 data.SetKeyword(keyword);
214 data.SetURL(url.spec());
215 DCHECK(controller.GetLastCommittedEntry());
216 const GURL& current_favicon =
217 controller.GetLastCommittedEntry()->GetFavicon().url;
218 // If the favicon url isn't valid, it means there really isn't a favicon, or
219 // the favicon url wasn't obtained before the load started. This assumes the
220 // latter.
221 // TODO(sky): Need a way to set the favicon that doesn't involve generating
222 // its url.
223 data.favicon_url = current_favicon.is_valid() ?
224 current_favicon : TemplateURL::GenerateFaviconURL(params.referrer.url);
225 data.safe_for_autoreplace = true;
226 data.input_encodings.push_back(params.searchable_form_encoding);
227 url_service->Add(new TemplateURL(data));
228 }
229