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