Home | History | Annotate | Download | only in search_engines
      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, &current_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