summaryrefslogtreecommitdiffstats
path: root/chrome/browser/autocomplete
diff options
context:
space:
mode:
authormpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-28 17:45:33 +0000
committermpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-28 17:45:33 +0000
commit56ad3797dc8ea7d757dc2b12d606c14cedd564e1 (patch)
tree26fab7c6d4cbb0358b404ca3eb488aae75ebe1d3 /chrome/browser/autocomplete
parent79b663c6e6ddf89e85cdc566b5d4f368465bb858 (diff)
downloadchromium_src-56ad3797dc8ea7d757dc2b12d606c14cedd564e1.zip
chromium_src-56ad3797dc8ea7d757dc2b12d606c14cedd564e1.tar.gz
chromium_src-56ad3797dc8ea7d757dc2b12d606c14cedd564e1.tar.bz2
First pass at experimental omnibox API. There are plenty of rough edges and features to work on, but it's in a usable state.
When an extension is installed that specifies an omnibox keyword in its manifest, we add that keyword to the user's list of Search Engines. The user can then edit this keyword later. I'm leveraging most of the original search engine keyword code. An extension keyword has a special URL that identifies it as an extension keyword. There is some special case code to treat these keywords slightly differently throughout the omnibox code. BUG=38884 Review URL: http://codereview.chromium.org/2078021 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@48503 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/autocomplete')
-rw-r--r--chrome/browser/autocomplete/autocomplete_edit.cc22
-rw-r--r--chrome/browser/autocomplete/keyword_provider.cc147
-rw-r--r--chrome/browser/autocomplete/keyword_provider.h31
3 files changed, 175 insertions, 25 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_edit.cc b/chrome/browser/autocomplete/autocomplete_edit.cc
index a650e1d..de74891 100644
--- a/chrome/browser/autocomplete/autocomplete_edit.cc
+++ b/chrome/browser/autocomplete/autocomplete_edit.cc
@@ -15,6 +15,7 @@
#include "chrome/browser/autocomplete/autocomplete_popup_model.h"
#include "chrome/browser/autocomplete/keyword_provider.h"
#include "chrome/browser/command_updater.h"
+#include "chrome/browser/extensions/extension_omnibox_api.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/net/dns_global.h"
#include "chrome/browser/net/url_fixer_upper.h"
@@ -265,6 +266,17 @@ void AutocompleteEditModel::AcceptInput(WindowOpenDisposition disposition,
AutocompleteMatch match;
GURL alternate_nav_url;
GetInfoForCurrentText(&match, &alternate_nav_url);
+
+ if (match.template_url && match.template_url->IsExtensionKeyword()) {
+ // Strip the keyword + leading space off the input.
+ size_t prefix_length = match.template_url->keyword().size() + 1;
+ ExtensionOmniboxEventRouter::OnInputEntered(
+ profile_, match.template_url->GetExtensionId(),
+ WideToUTF8(match.fill_into_edit.substr(prefix_length)));
+ view_->RevertAll();
+ return;
+ }
+
if (!match.destination_url.is_valid())
return;
@@ -606,9 +618,13 @@ void AutocompleteEditModel::Observe(NotificationType type,
inline_autocomplete_text =
match->fill_into_edit.substr(match->inline_autocomplete_offset);
}
- // Warm up DNS Prefetch Cache.
- chrome_browser_net::DnsPrefetchUrl(match->destination_url,
- IsPreconnectable(match->type));
+
+ if (!match->destination_url.SchemeIs(chrome::kExtensionScheme)) {
+ // Warm up DNS Prefetch Cache.
+ chrome_browser_net::DnsPrefetchUrl(match->destination_url,
+ IsPreconnectable(match->type));
+ }
+
// We could prefetch the alternate nav URL, if any, but because there
// can be many of these as a user types an initial series of characters,
// the OS DNS cache could suffer eviction problems for minimal gain.
diff --git a/chrome/browser/autocomplete/keyword_provider.cc b/chrome/browser/autocomplete/keyword_provider.cc
index 9f841cc..a0130b7 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());
+}
diff --git a/chrome/browser/autocomplete/keyword_provider.h b/chrome/browser/autocomplete/keyword_provider.h
index adf81d8..6fb744b 100644
--- a/chrome/browser/autocomplete/keyword_provider.h
+++ b/chrome/browser/autocomplete/keyword_provider.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
@@ -18,6 +18,7 @@
#include <string>
#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/common/notification_registrar.h"
class Profile;
class TemplateURL;
@@ -45,7 +46,9 @@ class TemplateURLModel;
// action "[keyword] %s". If the user has typed a (possibly partial) keyword
// but no search terms, the suggested result is shown greyed out, with
// "<enter term(s)>" as the substituted input, and does nothing when selected.
-class KeywordProvider : public AutocompleteProvider {
+class KeywordProvider :
+ public AutocompleteProvider,
+ public NotificationObserver {
public:
KeywordProvider(ACProviderListener* listener, Profile* profile);
// For testing.
@@ -103,17 +106,39 @@ class KeywordProvider : public AutocompleteProvider {
bool no_query_text_needed);
// Creates a fully marked-up AutocompleteMatch from the user's input.
+ // If |relevance| is negative, calculate a relevance based on heuristics.
AutocompleteMatch CreateAutocompleteMatch(
TemplateURLModel* model,
const std::wstring keyword,
const AutocompleteInput& input,
size_t prefix_length,
- const std::wstring& remaining_input);
+ const std::wstring& remaining_input,
+ int relevance);
+
+ // NotificationObserver interface.
+ void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
// Model for the keywords. This is only non-null when testing, otherwise the
// TemplateURLModel from the Profile is used.
TemplateURLModel* model_;
+ // Identifies the current input state. This is incremented each time the
+ // autocomplete edit's input changes in any way. It is used to tell whether
+ // suggest results from the extension are current.
+ int current_input_id_;
+
+ // The input state at the time we last asked the extension for suggest
+ // results.
+ AutocompleteInput extension_suggest_last_input_;
+
+ // We remember the last suggestions we've received from the extension in case
+ // we need to reset our matches without asking the extension again.
+ std::vector<AutocompleteMatch> extension_suggest_matches_;
+
+ NotificationRegistrar registrar_;
+
DISALLOW_EVIL_CONSTRUCTORS(KeywordProvider);
};