Index: chrome/browser/autocomplete/keyword_provider.cc |
diff --git a/chrome/browser/autocomplete/keyword_provider.cc b/chrome/browser/autocomplete/keyword_provider.cc |
index 9f841ccea1338ab19188ce9924a4a22d5b11a921..a0130b739fe252d2d24db1807b20f2747d1c1de5 100644 |
--- a/chrome/browser/autocomplete/keyword_provider.cc |
+++ b/chrome/browser/autocomplete/keyword_provider.cc |
@@ -8,10 +8,13 @@ |
#include <vector> |
#include "app/l10n_util.h" |
+#include "base/string16.h" |
#include "base/utf_string_conversions.h" |
+#include "chrome/browser/extensions/extension_omnibox_api.h" |
#include "chrome/browser/profile.h" |
#include "chrome/browser/search_engines/template_url.h" |
#include "chrome/browser/search_engines/template_url_model.h" |
+#include "chrome/common/notification_service.h" |
#include "grit/generated_resources.h" |
#include "net/base/escape.h" |
#include "net/base/net_util.h" |
@@ -31,13 +34,17 @@ std::wstring KeywordProvider::SplitReplacementStringFromInput( |
KeywordProvider::KeywordProvider(ACProviderListener* listener, Profile* profile) |
: AutocompleteProvider(listener, profile, "Keyword"), |
- model_(NULL) { |
+ model_(NULL), |
+ current_input_id_(0) { |
+ registrar_.Add(this, NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY, |
+ Source<Profile>(profile)); |
} |
KeywordProvider::KeywordProvider(ACProviderListener* listener, |
TemplateURLModel* model) |
: AutocompleteProvider(listener, NULL, "Keyword"), |
- model_(model) { |
+ model_(model), |
+ current_input_id_(0) { |
} |
@@ -84,6 +91,14 @@ void KeywordProvider::Start(const AutocompleteInput& input, |
bool minimal_changes) { |
matches_.clear(); |
+ if (!minimal_changes) { |
+ done_ = true; |
+ |
+ // Input has changed. Increment the input ID so that we can discard any |
+ // stale extension suggestions that may be incoming. |
+ ++current_input_id_; |
+ } |
+ |
// Split user input into a keyword and some query input. |
// |
// We want to suggest keywords even when users have started typing URLs, on |
@@ -131,7 +146,33 @@ void KeywordProvider::Start(const AutocompleteInput& input, |
if (keyword_matches.front() == keyword) { |
matches_.push_back(CreateAutocompleteMatch(model, keyword, input, |
keyword.length(), |
- remaining_input)); |
+ remaining_input, -1)); |
+ |
+ const TemplateURL* template_url(model->GetTemplateURLForKeyword(keyword)); |
+ if (profile_ && |
+ !input.synchronous_only() && template_url->IsExtensionKeyword()) { |
+ if (minimal_changes) { |
+ // If the input hasn't significantly changed, we can just use the |
+ // suggestions from last time. We need to readjust the relevance to |
+ // ensure it is less than the main match's relevance. |
+ for (size_t i = 0; i < extension_suggest_matches_.size(); ++i) { |
+ matches_.push_back(extension_suggest_matches_[i]); |
+ matches_.back().relevance = matches_[0].relevance - (i + 1); |
+ } |
+ } else { |
+ extension_suggest_last_input_ = input; |
+ extension_suggest_matches_.clear(); |
+ |
+ bool have_listeners = ExtensionOmniboxEventRouter::OnInputChanged( |
+ profile_, template_url->GetExtensionId(), |
+ WideToUTF8(remaining_input), current_input_id_); |
+ |
+ // We only have to wait for suggest results if there are actually |
+ // extensions listening for input changes. |
+ if (have_listeners) |
+ done_ = false; |
+ } |
+ } |
} else { |
if (keyword_matches.size() > kMaxMatches) { |
keyword_matches.erase(keyword_matches.begin() + kMaxMatches, |
@@ -141,7 +182,7 @@ void KeywordProvider::Start(const AutocompleteInput& input, |
i != keyword_matches.end(); ++i) { |
matches_.push_back(CreateAutocompleteMatch(model, *i, input, |
keyword.length(), |
- remaining_input)); |
+ remaining_input, -1)); |
} |
} |
} |
@@ -189,10 +230,12 @@ void KeywordProvider::FillInURLAndContents( |
DCHECK(!element->short_name().empty()); |
DCHECK(element->url()); |
DCHECK(element->url()->IsValid()); |
+ int message_id = element->IsExtensionKeyword() ? |
+ IDS_EXTENSION_KEYWORD_COMMAND : IDS_KEYWORD_SEARCH; |
if (remaining_input.empty()) { |
if (element->url()->SupportsReplacement()) { |
// No query input; return a generic, no-destination placeholder. |
- match->contents.assign(l10n_util::GetStringF(IDS_KEYWORD_SEARCH, |
+ match->contents.assign(l10n_util::GetStringF(message_id, |
element->AdjustedShortNameForLocaleDirection(), |
l10n_util::GetString(IDS_EMPTY_KEYWORD_VALUE))); |
match->contents_class.push_back( |
@@ -215,7 +258,7 @@ void KeywordProvider::FillInURLAndContents( |
*element, remaining_input, TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, |
std::wstring()))); |
std::vector<size_t> content_param_offsets; |
- match->contents.assign(l10n_util::GetStringF(IDS_KEYWORD_SEARCH, |
+ match->contents.assign(l10n_util::GetStringF(message_id, |
element->short_name(), |
remaining_input, |
&content_param_offsets)); |
@@ -246,7 +289,8 @@ AutocompleteMatch KeywordProvider::CreateAutocompleteMatch( |
const std::wstring keyword, |
const AutocompleteInput& input, |
size_t prefix_length, |
- const std::wstring& remaining_input) { |
+ const std::wstring& remaining_input, |
+ int relevance) { |
DCHECK(model); |
// Get keyword data from data store. |
const TemplateURL* element(model->GetTemplateURLForKeyword(keyword)); |
@@ -257,14 +301,17 @@ AutocompleteMatch KeywordProvider::CreateAutocompleteMatch( |
// even when [remaining input] is empty, as the user can select the popup |
// choice and immediately begin typing in query input. |
const bool keyword_complete = (prefix_length == keyword.length()); |
- AutocompleteMatch result(this, |
- CalculateRelevance(input.type(), keyword_complete, |
- // When the user wants keyword matches to take |
- // preference, score them highly regardless of whether |
- // the input provides query text. |
- input.prefer_keyword() || !supports_replacement), |
- false, supports_replacement ? AutocompleteMatch::SEARCH_OTHER_ENGINE : |
- AutocompleteMatch::HISTORY_KEYWORD); |
+ if (relevance < 0) { |
+ relevance = |
+ CalculateRelevance(input.type(), keyword_complete, |
+ // When the user wants keyword matches to take |
+ // preference, score them highly regardless of |
+ // whether the input provides query text. |
+ input.prefer_keyword() || !supports_replacement); |
+ } |
+ AutocompleteMatch result(this, relevance, false, |
+ supports_replacement ? AutocompleteMatch::SEARCH_OTHER_ENGINE : |
+ AutocompleteMatch::HISTORY_KEYWORD); |
result.fill_into_edit.assign(keyword); |
if (!remaining_input.empty() || !keyword_complete || supports_replacement) |
result.fill_into_edit.push_back(L' '); |
@@ -278,12 +325,13 @@ AutocompleteMatch KeywordProvider::CreateAutocompleteMatch( |
FillInURLAndContents(remaining_input, element, &result); |
// Create popup entry description based on the keyword name. |
- result.description.assign(l10n_util::GetStringF( |
- IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION, keyword)); |
+ int message_id = element->IsExtensionKeyword() ? |
+ IDS_AUTOCOMPLETE_EXTENSION_KEYWORD_DESCRIPTION : |
+ IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION; |
+ result.description.assign(l10n_util::GetStringF(message_id, keyword)); |
if (supports_replacement) |
result.template_url = element; |
- static const std::wstring kKeywordDesc(l10n_util::GetString( |
- IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION)); |
+ static const std::wstring kKeywordDesc(l10n_util::GetString(message_id)); |
AutocompleteMatch::ClassifyLocationInString(kKeywordDesc.find(L"%s"), |
prefix_length, |
result.description.length(), |
@@ -294,3 +342,64 @@ AutocompleteMatch KeywordProvider::CreateAutocompleteMatch( |
return result; |
} |
+ |
+void KeywordProvider::Observe(NotificationType type, |
+ const NotificationSource& source, |
+ const NotificationDetails& details) { |
+ // TODO(mpcomplete): consider clamping the number of suggestions to |
+ // AutocompleteProvider::kMaxMatches. |
+ DCHECK(type == NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY); |
+ |
+ int suggest_id = Details<ExtensionOmniboxSuggestions>(details).ptr()->first; |
+ if (suggest_id != current_input_id_) |
+ return; // This is an old result. Just ignore. |
+ |
+ const AutocompleteInput& input = extension_suggest_last_input_; |
+ std::wstring keyword, remaining_input; |
+ if (!ExtractKeywordFromInput(input, &keyword, &remaining_input)) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ TemplateURLModel* model = |
+ profile_ ? profile_->GetTemplateURLModel() : model_; |
+ |
+ ListValue* suggestions = |
+ Details<ExtensionOmniboxSuggestions>(details).ptr()->second; |
+ for (size_t i = 0; i < suggestions->GetSize(); ++i) { |
+ DictionaryValue* suggestion; |
+ string16 content, description; |
+ if (!suggestions->GetDictionary(i, &suggestion) || |
+ !suggestion->GetString("content", &content) || |
+ !suggestion->GetString("description", &description)) |
+ break; |
+ |
+ // We want to order these suggestions in descending order, so start with |
+ // the relevance of the first result (added synchronously in Start()), |
+ // and subtract 1 for each subsequent suggestion from the extension. |
+ // We know that |complete| is true, because we wouldn't get results from |
+ // the extension unless the full keyword had been typed. |
+ int first_relevance = |
+ CalculateRelevance(input.type(), true, input.prefer_keyword()); |
+ extension_suggest_matches_.push_back(CreateAutocompleteMatch( |
+ model, keyword, input, keyword.length(), UTF16ToWide(content), |
+ first_relevance - (i + 1))); |
+ |
+ if (!description.empty()) { |
+ AutocompleteMatch* match = &extension_suggest_matches_.back(); |
+ std::vector<size_t> offsets; |
+ match->contents.assign(l10n_util::GetStringF( |
+ IDS_AUTOCOMPLETE_EXTENSION_KEYWORD_CONTENT, |
+ match->contents, UTF16ToWide(description), &offsets)); |
+ CHECK_EQ(2U, offsets.size()) << |
+ "Expected 2 params for IDS_AUTOCOMPLETE_EXTENSION_KEYWORD_CONTENT"; |
+ match->contents_class.push_back( |
+ ACMatchClassification(offsets[1], ACMatchClassification::NONE)); |
+ } |
+ } |
+ |
+ done_ = true; |
+ matches_.insert(matches_.end(), extension_suggest_matches_.begin(), |
+ extension_suggest_matches_.end()); |
+ listener_->OnProviderUpdate(!extension_suggest_matches_.empty()); |
+} |