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