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