| // 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::AddExtensionAppForTesting( |
| const std::string& app_name, |
| const std::string url) { |
| extension_apps_.push_back(std::make_pair(app_name, url)); |
| } |
| |
| 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::EXTENSION_APP); |
| 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)->GetFullLaunchURL().is_valid()) { |
| extension_apps_.push_back( |
| std::make_pair((*app)->name(), |
| (*app)->GetFullLaunchURL().spec())); |
| } |
| } |
| } |
| |
| 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; |
| } |