diff options
-rw-r--r-- | chrome/browser/autocomplete/autocomplete.cc | 2 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete.h | 15 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_browsertest.cc | 5 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_match.h | 10 | ||||
-rw-r--r-- | chrome/browser/autocomplete/extension_app_provider.cc | 165 | ||||
-rw-r--r-- | chrome/browser/autocomplete/extension_app_provider.h | 83 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 |
7 files changed, 275 insertions, 7 deletions
diff --git a/chrome/browser/autocomplete/autocomplete.cc b/chrome/browser/autocomplete/autocomplete.cc index b076dc0..c022f96 100644 --- a/chrome/browser/autocomplete/autocomplete.cc +++ b/chrome/browser/autocomplete/autocomplete.cc @@ -16,6 +16,7 @@ #include "chrome/browser/autocomplete/autocomplete_controller_delegate.h" #include "chrome/browser/autocomplete/autocomplete_match.h" #include "chrome/browser/autocomplete/builtin_provider.h" +#include "chrome/browser/autocomplete/extension_app_provider.h" #include "chrome/browser/autocomplete/history_contents_provider.h" #include "chrome/browser/autocomplete/history_quick_provider.h" #include "chrome/browser/autocomplete/history_url_provider.h" @@ -796,6 +797,7 @@ AutocompleteController::AutocompleteController( providers_.push_back(new KeywordProvider(this, profile)); providers_.push_back(new HistoryContentsProvider(this, profile)); providers_.push_back(new BuiltinProvider(this, profile)); + providers_.push_back(new ExtensionAppProvider(this, profile)); for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i) (*i)->AddRef(); } diff --git a/chrome/browser/autocomplete/autocomplete.h b/chrome/browser/autocomplete/autocomplete.h index d1809d3..95b1ee35 100644 --- a/chrome/browser/autocomplete/autocomplete.h +++ b/chrome/browser/autocomplete/autocomplete.h @@ -43,10 +43,12 @@ // UNKNOWN input type: // --------------------------------------------------------------------|----- // Keyword (non-substituting or in keyword UI mode, exact match) | 1500 +// Extension App (exact match) | 1425 // HistoryURL (exact or inline autocomplete match) | 1400 // Search Primary Provider (past query in history within 2 days) | 1399** // Search Primary Provider (what you typed) | 1300 // HistoryURL (what you typed) | 1200 +// Extension App (inexact match) | 1175*~ // Keyword (substituting, exact match) | 1100 // Search Primary Provider (past query in history older than 2 days) | 1050-- // HistoryContents (any match in title of starred page) | 1000++ @@ -66,9 +68,11 @@ // REQUESTED_URL input type: // --------------------------------------------------------------------|----- // Keyword (non-substituting or in keyword UI mode, exact match) | 1500 +// Extension App (exact match) | 1425 // HistoryURL (exact or inline autocomplete match) | 1400 // Search Primary Provider (past query in history within 2 days) | 1399** // HistoryURL (what you typed) | 1200 +// Extension App (inexact match) | 1175*~ // Search Primary Provider (what you typed) | 1150 // Keyword (substituting, exact match) | 1100 // Search Primary Provider (past query in history older than 2 days) | 1050-- @@ -89,8 +93,10 @@ // URL input type: // --------------------------------------------------------------------|----- // Keyword (non-substituting or in keyword UI mode, exact match) | 1500 +// Extension App (exact match) | 1425 // HistoryURL (exact or inline autocomplete match) | 1400 // HistoryURL (what you typed) | 1200 +// Extension App (inexact match) | 1175*~ // Keyword (substituting, exact match) | 1100 // HistoryURL (inexact match) | 900++ // Search Primary Provider (what you typed) | 850 @@ -108,9 +114,11 @@ // --------------------------------------------------------------------|----- // Keyword (non-substituting or in keyword UI mode, exact match) | 1500 // Keyword (substituting, exact match) | 1450 +// Extension App (exact match) | 1425 // HistoryURL (exact or inline autocomplete match) | 1400 // Search Primary Provider (past query in history within 2 days) | 1399** // Search Primary Provider (what you typed) | 1300 +// Extension App (inexact match) | 1175*~ // Search Primary Provider (past query in history older than 2 days) | 1050-- // HistoryContents (any match in title of starred page) | 1000++ // HistoryURL (inexact match) | 900++ @@ -127,8 +135,10 @@ // // FORCED_QUERY input type: // --------------------------------------------------------------------|----- +// Extension App (exact match on title only, not url) | 1425 // Search Primary Provider (past query in history within 2 days) | 1399** // Search Primary Provider (what you typed) | 1300 +// Extension App (inexact match on title only, not url) | 1175*~ // Search Primary Provider (past query in history older than 2 days) | 1050-- // HistoryContents (any match in title of starred page) | 1000++ // Search Primary Provider (navigational suggestion) | 800++ @@ -149,8 +159,11 @@ // ++: a series of matches with relevance from n up to (n + max_matches). // --: relevance score falls off over time (discounted 50 points @ 15 minutes, // 450 points @ two weeks) -// --: relevance score falls off over two days (discounted 99 points after two +// **: relevance score falls off over two days (discounted 99 points after two // days). +// *~: Partial matches get a score on a sliding scale from about 575-1125 based +// on how many times the URL for the Extension App has been typed and how +// many of the letters match. class AutocompleteController; class AutocompleteControllerDelegate; diff --git a/chrome/browser/autocomplete/autocomplete_browsertest.cc b/chrome/browser/autocomplete/autocomplete_browsertest.cc index b115277..edb02d4 100644 --- a/chrome/browser/autocomplete/autocomplete_browsertest.cc +++ b/chrome/browser/autocomplete/autocomplete_browsertest.cc @@ -115,7 +115,10 @@ IN_PROC_BROWSER_TEST_F(AutocompleteBrowserTest, MAYBE_Autocomplete) { EXPECT_TRUE(location_bar->location_entry()->GetText().empty()); EXPECT_TRUE(location_bar->location_entry()->IsSelectAll()); const AutocompleteResult& result = autocomplete_controller->result(); - ASSERT_EQ(1U, result.size()) << AutocompleteResultAsString(result); + // We get two matches because we have a provider for extension apps and the + // Chrome Web Store is a built-in Extension app. For this test, we only care + // about the other match existing. + ASSERT_GE(result.size(), 1U) << AutocompleteResultAsString(result); AutocompleteMatch match = result.match_at(0); EXPECT_EQ(AutocompleteMatch::SEARCH_WHAT_YOU_TYPED, match.type); EXPECT_FALSE(match.deletable); diff --git a/chrome/browser/autocomplete/autocomplete_match.h b/chrome/browser/autocomplete/autocomplete_match.h index a376062..3067715 100644 --- a/chrome/browser/autocomplete/autocomplete_match.h +++ b/chrome/browser/autocomplete/autocomplete_match.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -128,10 +128,10 @@ struct AutocompleteMatch { // no provider (or memory of the user's selection). AutocompleteProvider* provider; - // The relevance of this match. See table above for scores returned by - // various providers. This is used to rank matches among all responding - // providers, so different providers must be carefully tuned to supply - // matches with appropriate relevance. + // The relevance of this match. See table in autocomplete.h for scores + // returned by various providers. This is used to rank matches among all + // responding providers, so different providers must be carefully tuned to + // supply matches with appropriate relevance. // // TODO(pkasting): http://b/1111299 This should be calculated algorithmically, // rather than being a fairly fixed value defined by the table above. diff --git a/chrome/browser/autocomplete/extension_app_provider.cc b/chrome/browser/autocomplete/extension_app_provider.cc new file mode 100644 index 0000000..eff2bb1 --- /dev/null +++ b/chrome/browser/autocomplete/extension_app_provider.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2011 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. + +#include "chrome/browser/autocomplete/extension_app_provider.h" + +#include <algorithm> +#include <cmath> + +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/history/url_database.h" +#include "chrome/browser/profiles/profile.h" +#include "content/common/notification_service.h" +#include "ui/base/l10n/l10n_util.h" + +ExtensionAppProvider::ExtensionAppProvider(ACProviderListener* listener, + Profile* profile) + : AutocompleteProvider(listener, profile, "ExtensionApps") { + RegisterForNotifications(); + RefreshAppList(); +} + +void ExtensionAppProvider::Start(const AutocompleteInput& input, + bool minimal_changes) { + matches_.clear(); + + if (input.type() == AutocompleteInput::INVALID) + return; + + if (!input.text().empty()) { + std::string input_utf8 = UTF16ToUTF8(input.text()); + for (ExtensionApps::const_iterator app = extension_apps_.begin(); + app != extension_apps_.end(); ++app) { + // See if the input matches this extension application. + const std::string& name = app->first; + const std::string& url = app->second; + std::string::const_iterator name_iter = + std::search(name.begin(), + name.end(), + input_utf8.begin(), + input_utf8.end(), + base::CaseInsensitiveCompare<char>()); + std::string::const_iterator url_iter = + std::search(url.begin(), + url.end(), + input_utf8.begin(), + input_utf8.end(), + base::CaseInsensitiveCompare<char>()); + + bool matches_name = name_iter != name.end(); + bool matches_url = url_iter != url.end() && + input.type() != AutocompleteInput::FORCED_QUERY; + if (matches_name || matches_url) { + // We have a match, might be a partial match. + // TODO(finnur): Figure out what type to return here, might want to have + // the extension icon/a generic icon show up in the Omnibox. + AutocompleteMatch match(this, 0, false, AutocompleteMatch::HISTORY_URL); + match.fill_into_edit = UTF8ToUTF16(url); + match.destination_url = GURL(url); + match.inline_autocomplete_offset = string16::npos; + match.contents = UTF8ToUTF16(name); + HighlightMatch(input, &match.contents_class, name_iter, name); + match.description = UTF8ToUTF16(url); + HighlightMatch(input, &match.description_class, url_iter, url); + match.relevance = CalculateRelevance(input.type(), + input.text().length(), + matches_name ? + name.length() : url.length(), + GURL(url)); + matches_.push_back(match); + } + } + } +} + +ExtensionAppProvider::~ExtensionAppProvider() { +} + +void ExtensionAppProvider::RefreshAppList() { + ExtensionService* extension_service = profile_->GetExtensionService(); + if (!extension_service) + return; // During testing, there is no extension service. + const ExtensionList* extensions = extension_service->extensions(); + extension_apps_.clear(); + for (ExtensionList::const_iterator app = extensions->begin(); + app != extensions->end(); ++app) { + if ((*app)->is_app() && !(*app)->launch_web_url().empty()) { + extension_apps_.push_back(std::make_pair((*app)->name(), + (*app)->launch_web_url())); + } + } +} + +void ExtensionAppProvider::RegisterForNotifications() { + registrar_.Add(this, NotificationType::EXTENSION_LOADED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_UNINSTALLED, + NotificationService::AllSources()); +} + +void ExtensionAppProvider::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + RefreshAppList(); +} + +void ExtensionAppProvider::HighlightMatch(const AutocompleteInput& input, + ACMatchClassifications* match_class, + std::string::const_iterator iter, + const std::string& match_string) { + size_t pos = iter - match_string.begin(); + bool match_found = iter != match_string.end(); + if (!match_found || pos > 0) { + match_class->push_back( + ACMatchClassification(0, ACMatchClassification::DIM)); + } + if (match_found) { + match_class->push_back( + ACMatchClassification(pos, ACMatchClassification::MATCH)); + if (pos + input.text().length() < match_string.length()) { + match_class->push_back(ACMatchClassification(pos + input.text().length(), + ACMatchClassification::DIM)); + } + } +} + +int ExtensionAppProvider::CalculateRelevance(AutocompleteInput::Type type, + int input_length, + int target_length, + const GURL& url) { + // If you update the algorithm here, please remember to update the tables in + // autocomplete.h also. + const int kMaxRelevance = 1425; + + if (input_length == target_length) + return kMaxRelevance; + + // We give a boost proportionally based on how much of the input matches the + // app name, up to a maximum close to 200 (we can be close to, but we'll never + // reach 200 because the 100% match is taken care of above). + double fraction_boost = static_cast<double>(200) * + input_length / target_length; + + // We also give a boost relative to how often the user has previously typed + // the Extension App URL/selected the Extension App suggestion from this + // provider (boost is between 200-400). + double type_count_boost = 0; + HistoryService* const history_service = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + history::URLDatabase* url_db = history_service ? + history_service->InMemoryDatabase() : NULL; + if (url_db) { + history::URLRow info; + url_db->GetRowForURL(url, &info); + type_count_boost = + 400 * (1.0 - (std::pow(static_cast<double>(2), -info.typed_count()))); + } + int relevance = 575 + static_cast<int>(type_count_boost) + + static_cast<int>(fraction_boost); + DCHECK_LE(relevance, kMaxRelevance); + return relevance; +} diff --git a/chrome/browser/autocomplete/extension_app_provider.h b/chrome/browser/autocomplete/extension_app_provider.h new file mode 100644 index 0000000..a162419 --- /dev/null +++ b/chrome/browser/autocomplete/extension_app_provider.h @@ -0,0 +1,83 @@ +// Copyright (c) 2011 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. +// +// +// This file contains the Extension App autocomplete provider. The provider +// is responsible for keeping track of which Extension Apps are installed and +// their URLs. An instance of it gets created and managed by the autocomplete +// controller. +// +// For more information on the autocomplete system in general, including how +// the autocomplete controller and autocomplete providers work, see +// chrome/browser/autocomplete.h. + +#ifndef CHROME_BROWSER_AUTOCOMPLETE_EXTENSION_APP_PROVIDER_H_ +#define CHROME_BROWSER_AUTOCOMPLETE_EXTENSION_APP_PROVIDER_H_ +#pragma once + +#include <string> +#include <utility> +#include <vector> + +#include "base/compiler_specific.h" +#include "chrome/browser/autocomplete/autocomplete.h" +#include "chrome/browser/autocomplete/autocomplete_match.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" + +class TemplateURLModel; + +class ExtensionAppProvider : public AutocompleteProvider, + public NotificationObserver { + public: + ExtensionAppProvider(ACProviderListener* listener, Profile* profile); + + // AutocompleteProvider implementation: + virtual void Start(const AutocompleteInput& input, + bool minimal_changes) OVERRIDE; + + private: + // An ExtensionApp is a pair of Extension Name and the Launch URL. + typedef std::pair<std::string, std::string> ExtensionApp; + typedef std::vector<ExtensionApp> ExtensionApps; + + virtual ~ExtensionAppProvider(); + + // Fetch the current app list and cache it locally. + void RefreshAppList(); + + // Register for install/uninstall notification so we can update our cache. + void RegisterForNotifications(); + + // Highlights a certain part of a match string within a certain match class. + // |input| is the input we got from the user, |match_class| is the + // AutoComplete match classification that keeps track of the highlighting + // values, and |iter| is the location of the user input found within + // |match_string|. + void HighlightMatch(const AutocompleteInput& input, + ACMatchClassifications* match_class, + std::string::const_iterator iter, + const std::string& match_string); + + // Calculate the relevance of the match. + int CalculateRelevance(AutocompleteInput::Type type, + int input_length, + int target_length, + const GURL& url); + + // NotificationObserver implementation: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + NotificationRegistrar registrar_; + + // Our cache of ExtensionApp objects (name + url) representing the extension + // apps we know about. + ExtensionApps extension_apps_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionAppProvider); +}; + +#endif // CHROME_BROWSER_AUTOCOMPLETE_EXTENSION_APP_PROVIDER_H_ diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 7e3187d..5d8a49e 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -126,6 +126,8 @@ 'browser/autocomplete/autocomplete_popup_view_mac.mm', 'browser/autocomplete/builtin_provider.cc', 'browser/autocomplete/builtin_provider.h', + 'browser/autocomplete/extension_app_provider.cc', + 'browser/autocomplete/extension_app_provider.h', 'browser/autocomplete/history_contents_provider.cc', 'browser/autocomplete/history_contents_provider.h', 'browser/autocomplete/history_provider.cc', |