Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / autocomplete / keyword_extensions_delegate_impl.cc
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
17 namespace omnibox_api = extensions::api::omnibox;
18
19 int KeywordExtensionsDelegateImpl::global_input_uid_ = 0;
20
21 KeywordExtensionsDelegateImpl::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
45 KeywordExtensionsDelegateImpl::~KeywordExtensionsDelegateImpl() {
46 }
47
48 void  KeywordExtensionsDelegateImpl::IncrementInputId() {
49   current_input_id_ = ++global_input_uid_;
50 }
51
52 bool 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
62 bool 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
102 void 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
111 void 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
119 void 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
210 void KeywordExtensionsDelegateImpl::OnProviderUpdate(bool updated_matches) {
211   provider_->listener_->OnProviderUpdate(updated_matches);
212 }