extension_app_provider.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Use of this source code is governed by a BSD-style license that can be
3ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// found in the LICENSE file.
4ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
5ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "chrome/browser/autocomplete/extension_app_provider.h"
6ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
7ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include <algorithm>
8ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include <cmath>
9ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
10ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "base/string16.h"
11ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "base/utf_string_conversions.h"
12ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "chrome/browser/extensions/extension_service.h"
13ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "chrome/browser/history/history.h"
14ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "chrome/browser/history/url_database.h"
15ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "chrome/browser/profiles/profile.h"
16ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "content/common/notification_service.h"
17ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "ui/base/l10n/l10n_util.h"
18ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
19ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian MonsenExtensionAppProvider::ExtensionAppProvider(ACProviderListener* listener,
20ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                           Profile* profile)
21ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    : AutocompleteProvider(listener, profile, "ExtensionApps") {
22ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  RegisterForNotifications();
23ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  RefreshAppList();
24ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen}
25ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
26ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvoid ExtensionAppProvider::AddExtensionAppForTesting(
27ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    const std::string& app_name,
28ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    const std::string url) {
29ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  extension_apps_.push_back(std::make_pair(app_name, url));
30ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen}
31ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
32ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvoid ExtensionAppProvider::Start(const AutocompleteInput& input,
33ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                 bool minimal_changes) {
34ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  matches_.clear();
35ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
36ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  if (input.type() == AutocompleteInput::INVALID)
37ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    return;
38ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
39ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  if (!input.text().empty()) {
40ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    std::string input_utf8 = UTF16ToUTF8(input.text());
41ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    for (ExtensionApps::const_iterator app = extension_apps_.begin();
42ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen         app != extension_apps_.end(); ++app) {
43ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // See if the input matches this extension application.
44ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      const std::string& name = app->first;
45ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      const std::string& url = app->second;
46ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      std::string::const_iterator name_iter =
47ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          std::search(name.begin(),
48ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                      name.end(),
49ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                      input_utf8.begin(),
50ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                      input_utf8.end(),
51ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                      base::CaseInsensitiveCompare<char>());
52ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      std::string::const_iterator url_iter =
53ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          std::search(url.begin(),
54ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                      url.end(),
55ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                      input_utf8.begin(),
56ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                      input_utf8.end(),
57ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                      base::CaseInsensitiveCompare<char>());
58ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
59ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      bool matches_name = name_iter != name.end();
60ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      bool matches_url = url_iter != url.end() &&
61ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                         input.type() != AutocompleteInput::FORCED_QUERY;
62ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (matches_name || matches_url) {
63ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // We have a match, might be a partial match.
64ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // TODO(finnur): Figure out what type to return here, might want to have
65ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // the extension icon/a generic icon show up in the Omnibox.
66ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        AutocompleteMatch match(this, 0, false,
67ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                AutocompleteMatch::EXTENSION_APP);
68ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        match.fill_into_edit = UTF8ToUTF16(url);
69ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        match.destination_url = GURL(url);
70ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        match.inline_autocomplete_offset = string16::npos;
71ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        match.contents = UTF8ToUTF16(name);
72ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        HighlightMatch(input, &match.contents_class, name_iter, name);
73ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        match.description = UTF8ToUTF16(url);
74ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        HighlightMatch(input, &match.description_class, url_iter, url);
75ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        match.relevance = CalculateRelevance(input.type(),
76ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                             input.text().length(),
77ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                             matches_name ?
78ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                                 name.length() : url.length(),
79ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                             GURL(url));
80ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        matches_.push_back(match);
81ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
82ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
83ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
84ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen}
85ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
86ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian MonsenExtensionAppProvider::~ExtensionAppProvider() {
87ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen}
88ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
89ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvoid ExtensionAppProvider::RefreshAppList() {
90ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  ExtensionService* extension_service = profile_->GetExtensionService();
91ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  if (!extension_service)
92ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    return;  // During testing, there is no extension service.
93ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  const ExtensionList* extensions = extension_service->extensions();
94ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  extension_apps_.clear();
95ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  for (ExtensionList::const_iterator app = extensions->begin();
96ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen       app != extensions->end(); ++app) {
97ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if ((*app)->is_app() && (*app)->GetFullLaunchURL().is_valid()) {
98ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      extension_apps_.push_back(
99ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          std::make_pair((*app)->name(),
100ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                         (*app)->GetFullLaunchURL().spec()));
101ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
102ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
103ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen}
104ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
105ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvoid ExtensionAppProvider::RegisterForNotifications() {
106ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  registrar_.Add(this, NotificationType::EXTENSION_LOADED,
107ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                 NotificationService::AllSources());
108ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  registrar_.Add(this, NotificationType::EXTENSION_UNINSTALLED,
109ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                 NotificationService::AllSources());
110ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen}
111ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
112ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvoid ExtensionAppProvider::Observe(NotificationType type,
113ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                   const NotificationSource& source,
114ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                   const NotificationDetails& details) {
115ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  RefreshAppList();
116ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen}
117ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
118ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvoid ExtensionAppProvider::HighlightMatch(const AutocompleteInput& input,
119ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                          ACMatchClassifications* match_class,
120ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                          std::string::const_iterator iter,
121ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                          const std::string& match_string) {
122ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  size_t pos = iter - match_string.begin();
123ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  bool match_found = iter != match_string.end();
124ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  if (!match_found || pos > 0) {
125ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    match_class->push_back(
126ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        ACMatchClassification(0, ACMatchClassification::DIM));
127ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
128ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  if (match_found) {
129ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    match_class->push_back(
130ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        ACMatchClassification(pos, ACMatchClassification::MATCH));
131ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (pos + input.text().length() < match_string.length()) {
132ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      match_class->push_back(ACMatchClassification(pos + input.text().length(),
133ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                ACMatchClassification::DIM));
134ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
135ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
136ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen}
137ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
138ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenint ExtensionAppProvider::CalculateRelevance(AutocompleteInput::Type type,
139ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                             int input_length,
140ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                             int target_length,
141ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                             const GURL& url) {
142ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // If you update the algorithm here, please remember to update the tables in
143ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // autocomplete.h also.
144ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  const int kMaxRelevance = 1425;
145ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
146ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  if (input_length == target_length)
147ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    return kMaxRelevance;
148ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
149ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // We give a boost proportionally based on how much of the input matches the
150ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // app name, up to a maximum close to 200 (we can be close to, but we'll never
151ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // reach 200 because the 100% match is taken care of above).
152ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  double fraction_boost = static_cast<double>(200) *
153ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                          input_length / target_length;
154ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
155ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // We also give a boost relative to how often the user has previously typed
156ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // the Extension App URL/selected the Extension App suggestion from this
157ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // provider (boost is between 200-400).
158ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  double type_count_boost = 0;
159ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  HistoryService* const history_service =
160ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
161ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  history::URLDatabase* url_db = history_service ?
162ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      history_service->InMemoryDatabase() : NULL;
163ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  if (url_db) {
164ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    history::URLRow info;
165ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    url_db->GetRowForURL(url, &info);
166ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    type_count_boost =
167ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        400 * (1.0 - (std::pow(static_cast<double>(2), -info.typed_count())));
168ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
169ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  int relevance = 575 + static_cast<int>(type_count_boost) +
170ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                        static_cast<int>(fraction_boost);
171ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  DCHECK_LE(relevance, kMaxRelevance);
172ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  return relevance;
173ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen}
174