keyword_extensions_delegate_impl.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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 17namespace omnibox_api = extensions::api::omnibox; 18 19int KeywordExtensionsDelegateImpl::global_input_uid_ = 0; 20 21KeywordExtensionsDelegateImpl::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 42KeywordExtensionsDelegateImpl::~KeywordExtensionsDelegateImpl() { 43} 44 45void KeywordExtensionsDelegateImpl::IncrementInputId() { 46 current_input_id_ = ++global_input_uid_; 47} 48 49bool 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 61bool 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 101void 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 110void 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 118void 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 208void KeywordExtensionsDelegateImpl::OnProviderUpdate(bool updated_matches) { 209 provider_->listener_->OnProviderUpdate(updated_matches); 210} 211