| 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());
|
| +}
|
|
|