Home | History | Annotate | Download | only in autocomplete
      1 // Copyright 2014 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/autocomplete/keyword_extensions_delegate_impl.h"
      6 
      7 #include "base/strings/utf_string_conversions.h"
      8 #include "chrome/browser/chrome_notification_types.h"
      9 #include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
     10 #include "chrome/browser/extensions/extension_service.h"
     11 #include "chrome/browser/extensions/extension_util.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "content/public/browser/notification_details.h"
     14 #include "content/public/browser/notification_source.h"
     15 #include "extensions/browser/extension_system.h"
     16 
     17 namespace omnibox_api = extensions::api::omnibox;
     18 
     19 int KeywordExtensionsDelegateImpl::global_input_uid_ = 0;
     20 
     21 KeywordExtensionsDelegateImpl::KeywordExtensionsDelegateImpl(
     22     KeywordProvider* provider)
     23     : KeywordExtensionsDelegate(provider),
     24       provider_(provider) {
     25   DCHECK(provider_);
     26 
     27   current_input_id_ = 0;
     28   // Extension suggestions always come from the original profile, since that's
     29   // where extensions run. We use the input ID to distinguish whether the
     30   // suggestions are meant for us.
     31   registrar_.Add(
     32       this, chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY,
     33       content::Source<Profile>(profile()->GetOriginalProfile()));
     34   registrar_.Add(
     35       this, chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
     36       content::Source<Profile>(profile()->GetOriginalProfile()));
     37   registrar_.Add(
     38       this, chrome::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED,
     39       content::Source<Profile>(profile()));
     40 }
     41 
     42 KeywordExtensionsDelegateImpl::~KeywordExtensionsDelegateImpl() {
     43 }
     44 
     45 void  KeywordExtensionsDelegateImpl::IncrementInputId() {
     46   current_input_id_ = ++global_input_uid_;
     47 }
     48 
     49 bool KeywordExtensionsDelegateImpl::IsEnabledExtension(
     50     Profile* profile,
     51     const std::string& extension_id) {
     52   ExtensionService* extension_service =
     53       extensions::ExtensionSystem::Get(profile)->extension_service();
     54   const extensions::Extension* extension =
     55       extension_service->GetExtensionById(extension_id, false);
     56   return extension &&
     57       (!profile->IsOffTheRecord() ||
     58        !extensions::util::IsIncognitoEnabled(extension_id, profile));
     59 }
     60 
     61 bool KeywordExtensionsDelegateImpl::Start(
     62     const AutocompleteInput& input,
     63     bool minimal_changes,
     64     const TemplateURL* template_url,
     65     const base::string16& remaining_input) {
     66   DCHECK(template_url);
     67 
     68   if (input.want_asynchronous_matches()) {
     69     std::string extension_id = template_url->GetExtensionId();
     70     if (extension_id != current_keyword_extension_id_)
     71       MaybeEndExtensionKeywordMode();
     72     if (current_keyword_extension_id_.empty())
     73       EnterExtensionKeywordMode(extension_id);
     74   }
     75 
     76   extensions::ApplyDefaultSuggestionForExtensionKeyword(
     77       profile(), template_url, remaining_input, &matches()->front());
     78 
     79   if (minimal_changes) {
     80     // If the input hasn't significantly changed, we can just use the
     81     // suggestions from last time. We need to readjust the relevance to
     82     // ensure it is less than the main match's relevance.
     83     for (size_t i = 0; i < extension_suggest_matches_.size(); ++i) {
     84       matches()->push_back(extension_suggest_matches_[i]);
     85       matches()->back().relevance = matches()->front().relevance - (i + 1);
     86     }
     87   } else if (input.want_asynchronous_matches()) {
     88     extension_suggest_last_input_ = input;
     89     extension_suggest_matches_.clear();
     90 
     91     // We only have to wait for suggest results if there are actually
     92     // extensions listening for input changes.
     93     if (extensions::ExtensionOmniboxEventRouter::OnInputChanged(
     94             profile(), template_url->GetExtensionId(),
     95             base::UTF16ToUTF8(remaining_input), current_input_id_))
     96       set_done(false);
     97   }
     98   return input.want_asynchronous_matches();
     99 }
    100 
    101 void KeywordExtensionsDelegateImpl::EnterExtensionKeywordMode(
    102     const std::string& extension_id) {
    103   DCHECK(current_keyword_extension_id_.empty());
    104   current_keyword_extension_id_ = extension_id;
    105 
    106   extensions::ExtensionOmniboxEventRouter::OnInputStarted(
    107       profile(), current_keyword_extension_id_);
    108 }
    109 
    110 void KeywordExtensionsDelegateImpl::MaybeEndExtensionKeywordMode() {
    111   if (!current_keyword_extension_id_.empty()) {
    112     extensions::ExtensionOmniboxEventRouter::OnInputCancelled(
    113         profile(), current_keyword_extension_id_);
    114     current_keyword_extension_id_.clear();
    115   }
    116 }
    117 
    118 void KeywordExtensionsDelegateImpl::Observe(
    119     int type,
    120     const content::NotificationSource& source,
    121     const content::NotificationDetails& details) {
    122   TemplateURLService* model = provider_->GetTemplateURLService();
    123   const AutocompleteInput& input = extension_suggest_last_input_;
    124 
    125   switch (type) {
    126     case chrome::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED:
    127       // Input has been accepted, so we're done with this input session. Ensure
    128       // we don't send the OnInputCancelled event, or handle any more stray
    129       // suggestions_ready events.
    130       current_keyword_extension_id_.clear();
    131       current_input_id_ = 0;
    132       return;
    133 
    134     case chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED: {
    135       // It's possible to change the default suggestion while not in an editing
    136       // session.
    137       base::string16 keyword, remaining_input;
    138       if (matches()->empty() || current_keyword_extension_id_.empty() ||
    139           !KeywordProvider::ExtractKeywordFromInput(
    140               input, &keyword, &remaining_input))
    141         return;
    142 
    143       const TemplateURL* template_url(
    144           model->GetTemplateURLForKeyword(keyword));
    145       extensions::ApplyDefaultSuggestionForExtensionKeyword(
    146           profile(), template_url, remaining_input, &matches()->front());
    147       OnProviderUpdate(true);
    148       return;
    149     }
    150 
    151     case chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY: {
    152       const omnibox_api::SendSuggestions::Params& suggestions =
    153           *content::Details<
    154               omnibox_api::SendSuggestions::Params>(details).ptr();
    155       if (suggestions.request_id != current_input_id_)
    156         return;  // This is an old result. Just ignore.
    157 
    158       base::string16 keyword, remaining_input;
    159       bool result = KeywordProvider::ExtractKeywordFromInput(
    160           input, &keyword, &remaining_input);
    161       DCHECK(result);
    162       const TemplateURL* template_url =
    163           model->GetTemplateURLForKeyword(keyword);
    164 
    165       // TODO(mpcomplete): consider clamping the number of suggestions to
    166       // AutocompleteProvider::kMaxMatches.
    167       for (size_t i = 0; i < suggestions.suggest_results.size(); ++i) {
    168         const omnibox_api::SuggestResult& suggestion =
    169             *suggestions.suggest_results[i];
    170         // We want to order these suggestions in descending order, so start with
    171         // the relevance of the first result (added synchronously in Start()),
    172         // and subtract 1 for each subsequent suggestion from the extension.
    173         // We recompute the first match's relevance; we know that |complete|
    174         // is true, because we wouldn't get results from the extension unless
    175         // the full keyword had been typed.
    176         int first_relevance = KeywordProvider::CalculateRelevance(
    177             input.type(), true, true, input.prefer_keyword(),
    178             input.allow_exact_keyword_match());
    179         // Because these matches are async, we should never let them become the
    180         // default match, lest we introduce race conditions in the omnibox user
    181         // interaction.
    182         extension_suggest_matches_.push_back(
    183             provider_->CreateAutocompleteMatch(
    184                 template_url, input, keyword.length(),
    185                 base::UTF8ToUTF16(suggestion.content), false,
    186                 first_relevance - (i + 1)));
    187 
    188         AutocompleteMatch* match = &extension_suggest_matches_.back();
    189         match->contents.assign(base::UTF8ToUTF16(suggestion.description));
    190         match->contents_class =
    191             extensions::StyleTypesToACMatchClassifications(suggestion);
    192       }
    193 
    194       set_done(true);
    195       matches()->insert(matches()->end(),
    196                         extension_suggest_matches_.begin(),
    197                         extension_suggest_matches_.end());
    198       OnProviderUpdate(!extension_suggest_matches_.empty());
    199       return;
    200     }
    201 
    202     default:
    203       NOTREACHED();
    204       return;
    205   }
    206 }
    207 
    208 void KeywordExtensionsDelegateImpl::OnProviderUpdate(bool updated_matches) {
    209   provider_->listener_->OnProviderUpdate(updated_matches);
    210 }
    211