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