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.
5 #include "chrome/browser/autocomplete/keyword_extensions_delegate_impl.h"
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"
17 namespace omnibox_api
= extensions::api::omnibox
;
19 int KeywordExtensionsDelegateImpl::global_input_uid_
= 0;
21 KeywordExtensionsDelegateImpl::KeywordExtensionsDelegateImpl(
23 KeywordProvider
* provider
)
24 : KeywordExtensionsDelegate(provider
),
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.
34 extensions::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY
,
35 content::Source
<Profile
>(profile_
->GetOriginalProfile()));
38 extensions::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED
,
39 content::Source
<Profile
>(profile_
->GetOriginalProfile()));
41 extensions::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED
,
42 content::Source
<Profile
>(profile_
));
45 KeywordExtensionsDelegateImpl::~KeywordExtensionsDelegateImpl() {
48 void KeywordExtensionsDelegateImpl::IncrementInputId() {
49 current_input_id_
= ++global_input_uid_
;
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
);
58 (!profile_
->IsOffTheRecord() ||
59 extensions::util::IsIncognitoEnabled(extension_id
, profile_
));
62 bool KeywordExtensionsDelegateImpl::Start(
63 const AutocompleteInput
& input
,
65 const TemplateURL
* template_url
,
66 const base::string16
& remaining_input
) {
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
);
77 extensions::ApplyDefaultSuggestionForExtensionKeyword(
78 profile_
, template_url
, remaining_input
, &matches()->front());
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);
88 } else if (input
.want_asynchronous_matches()) {
89 extension_suggest_last_input_
= input
;
90 extension_suggest_matches_
.clear();
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_
))
99 return input
.want_asynchronous_matches();
102 void KeywordExtensionsDelegateImpl::EnterExtensionKeywordMode(
103 const std::string
& extension_id
) {
104 DCHECK(current_keyword_extension_id_
.empty());
105 current_keyword_extension_id_
= extension_id
;
107 extensions::ExtensionOmniboxEventRouter::OnInputStarted(
108 profile_
, current_keyword_extension_id_
);
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 // Ignore stray suggestions_ready events that arrive after
117 // OnInputCancelled().
122 void KeywordExtensionsDelegateImpl::Observe(
124 const content::NotificationSource
& source
,
125 const content::NotificationDetails
& details
) {
126 TemplateURLService
* model
= provider_
->GetTemplateURLService();
127 const AutocompleteInput
& input
= extension_suggest_last_input_
;
130 case extensions::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED
:
131 // Input has been accepted, so we're done with this input session. Ensure
132 // we don't send the OnInputCancelled event, or handle any more stray
133 // suggestions_ready events.
134 current_keyword_extension_id_
.clear();
138 case extensions::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED
140 // It's possible to change the default suggestion while not in an editing
142 base::string16 keyword
, remaining_input
;
143 if (matches()->empty() || current_keyword_extension_id_
.empty() ||
144 !KeywordProvider::ExtractKeywordFromInput(
145 input
, &keyword
, &remaining_input
))
148 const TemplateURL
* template_url(
149 model
->GetTemplateURLForKeyword(keyword
));
150 extensions::ApplyDefaultSuggestionForExtensionKeyword(
151 profile_
, template_url
, remaining_input
, &matches()->front());
152 OnProviderUpdate(true);
156 case extensions::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY
: {
157 const omnibox_api::SendSuggestions::Params
& suggestions
=
159 omnibox_api::SendSuggestions::Params
>(details
).ptr();
160 if (suggestions
.request_id
!= current_input_id_
)
161 return; // This is an old result. Just ignore.
163 base::string16 keyword
, remaining_input
;
164 bool result
= KeywordProvider::ExtractKeywordFromInput(
165 input
, &keyword
, &remaining_input
);
167 const TemplateURL
* template_url
=
168 model
->GetTemplateURLForKeyword(keyword
);
170 // TODO(mpcomplete): consider clamping the number of suggestions to
171 // AutocompleteProvider::kMaxMatches.
172 for (size_t i
= 0; i
< suggestions
.suggest_results
.size(); ++i
) {
173 const omnibox_api::SuggestResult
& suggestion
=
174 *suggestions
.suggest_results
[i
];
175 // We want to order these suggestions in descending order, so start with
176 // the relevance of the first result (added synchronously in Start()),
177 // and subtract 1 for each subsequent suggestion from the extension.
178 // We recompute the first match's relevance; we know that |complete|
179 // is true, because we wouldn't get results from the extension unless
180 // the full keyword had been typed.
181 int first_relevance
= KeywordProvider::CalculateRelevance(
182 input
.type(), true, true, input
.prefer_keyword(),
183 input
.allow_exact_keyword_match());
184 // Because these matches are async, we should never let them become the
185 // default match, lest we introduce race conditions in the omnibox user
187 extension_suggest_matches_
.push_back(
188 provider_
->CreateAutocompleteMatch(
189 template_url
, input
, keyword
.length(),
190 base::UTF8ToUTF16(suggestion
.content
), false,
191 first_relevance
- (i
+ 1)));
193 AutocompleteMatch
* match
= &extension_suggest_matches_
.back();
194 match
->contents
.assign(base::UTF8ToUTF16(suggestion
.description
));
195 match
->contents_class
=
196 extensions::StyleTypesToACMatchClassifications(suggestion
);
200 matches()->insert(matches()->end(),
201 extension_suggest_matches_
.begin(),
202 extension_suggest_matches_
.end());
203 OnProviderUpdate(!extension_suggest_matches_
.empty());
213 void KeywordExtensionsDelegateImpl::OnProviderUpdate(bool updated_matches
) {
214 provider_
->listener_
->OnProviderUpdate(updated_matches
);