1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/autocomplete/extension_app_provider.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/extension_system_factory.h"
15 #include "chrome/browser/extensions/extension_util.h"
16 #include "chrome/browser/history/history_service.h"
17 #include "chrome/browser/history/history_service_factory.h"
18 #include "chrome/browser/history/url_database.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/extensions/application_launch.h"
21 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
22 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
23 #include "content/public/browser/notification_source.h"
24 #include "extensions/common/extension.h"
25 #include "extensions/common/extension_set.h"
26 #include "ui/base/l10n/l10n_util.h"
28 ExtensionAppProvider::ExtensionAppProvider(
29 AutocompleteProviderListener
* listener
,
31 : AutocompleteProvider(listener
, profile
,
32 AutocompleteProvider::TYPE_EXTENSION_APP
) {
33 // Notifications of extensions loading and unloading always come from the
34 // non-incognito profile, but we need to see them regardless, as the incognito
35 // windows can be affected.
36 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED
,
37 content::Source
<Profile
>(profile_
->GetOriginalProfile()));
38 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED
,
39 content::Source
<Profile
>(profile_
->GetOriginalProfile()));
44 void ExtensionAppProvider::LaunchAppFromOmnibox(
45 const AutocompleteMatch
& match
,
47 WindowOpenDisposition disposition
) {
48 ExtensionService
* service
=
49 extensions::ExtensionSystemFactory::GetForProfile(profile
)->
51 const extensions::Extension
* extension
=
52 service
->GetInstalledApp(match
.destination_url
);
53 // While the Omnibox popup is open, the extension can be updated, changing
54 // its URL and leaving us with no extension being found. In this case, we
55 // ignore the request.
59 CoreAppLauncherHandler::RecordAppLaunchType(
60 extension_misc::APP_LAUNCH_OMNIBOX_APP
,
61 extension
->GetType());
63 OpenApplication(AppLaunchParams(profile
, extension
, disposition
));
66 void ExtensionAppProvider::AddExtensionAppForTesting(
67 const ExtensionApp
& extension_app
) {
68 extension_apps_
.push_back(extension_app
);
71 AutocompleteMatch
ExtensionAppProvider::CreateAutocompleteMatch(
72 const AutocompleteInput
& input
,
73 const ExtensionApp
& app
,
74 size_t name_match_index
,
75 size_t url_match_index
) {
76 // TODO(finnur): Figure out what type to return here, might want to have
77 // the extension icon/a generic icon show up in the Omnibox.
78 AutocompleteMatch
match(this, 0, false,
79 AutocompleteMatchType::EXTENSION_APP
);
80 match
.fill_into_edit
=
81 app
.should_match_against_launch_url
? app
.launch_url
: input
.text();
82 match
.destination_url
= GURL(app
.launch_url
);
83 match
.allowed_to_be_default_match
= true;
84 match
.contents
= AutocompleteMatch::SanitizeString(app
.name
);
85 AutocompleteMatch::ClassifyLocationInString(name_match_index
,
86 input
.text().length(), app
.name
.length(), ACMatchClassification::NONE
,
87 &match
.contents_class
);
88 if (app
.should_match_against_launch_url
) {
89 match
.description
= app
.launch_url
;
90 AutocompleteMatch::ClassifyLocationInString(url_match_index
,
91 input
.text().length(), app
.launch_url
.length(),
92 ACMatchClassification::URL
, &match
.description_class
);
94 match
.relevance
= CalculateRelevance(
96 input
.text().length(),
97 name_match_index
!= base::string16::npos
?
98 app
.name
.length() : app
.launch_url
.length(),
99 match
.destination_url
);
103 void ExtensionAppProvider::Start(const AutocompleteInput
& input
,
104 bool minimal_changes
) {
107 if ((input
.type() == AutocompleteInput::INVALID
) ||
108 (input
.type() == AutocompleteInput::FORCED_QUERY
))
111 if (input
.text().empty())
114 for (ExtensionApps::const_iterator app
= extension_apps_
.begin();
115 app
!= extension_apps_
.end(); ++app
) {
116 // See if the input matches this extension application.
117 const base::string16
& name
= app
->name
;
118 base::string16::const_iterator name_iter
=
119 std::search(name
.begin(), name
.end(),
120 input
.text().begin(), input
.text().end(),
121 base::CaseInsensitiveCompare
<base::char16
>());
122 bool matches_name
= name_iter
!= name
.end();
123 size_t name_match_index
= matches_name
?
124 static_cast<size_t>(name_iter
- name
.begin()) : base::string16::npos
;
126 bool matches_url
= false;
127 size_t url_match_index
= base::string16::npos
;
128 if (app
->should_match_against_launch_url
) {
129 const base::string16
& url
= app
->launch_url
;
130 base::string16::const_iterator url_iter
=
131 std::search(url
.begin(), url
.end(),
132 input
.text().begin(), input
.text().end(),
133 base::CaseInsensitiveCompare
<base::char16
>());
134 matches_url
= url_iter
!= url
.end() &&
135 input
.type() != AutocompleteInput::FORCED_QUERY
;
136 url_match_index
= matches_url
?
137 static_cast<size_t>(url_iter
- url
.begin()) : base::string16::npos
;
140 if (matches_name
|| matches_url
) {
141 // We have a match, might be a partial match.
142 matches_
.push_back(CreateAutocompleteMatch(
143 input
, *app
, name_match_index
, url_match_index
));
148 ExtensionAppProvider::~ExtensionAppProvider() {
151 void ExtensionAppProvider::RefreshAppList() {
152 ExtensionService
* extension_service
=
153 extensions::ExtensionSystemFactory::GetForProfile(profile_
)->
155 if (!extension_service
)
156 return; // During testing, there is no extension service.
157 const extensions::ExtensionSet
* extensions
= extension_service
->extensions();
158 extension_apps_
.clear();
159 for (extensions::ExtensionSet::const_iterator iter
= extensions
->begin();
160 iter
!= extensions
->end(); ++iter
) {
161 const extensions::Extension
* app
= iter
->get();
162 if (!app
->ShouldDisplayInAppLauncher())
164 // Note: Apps that appear in the NTP only are not added here since this
165 // provider is currently only used in the app launcher.
167 if (profile_
->IsOffTheRecord() &&
168 !extension_util::CanLoadInIncognito(app
, extension_service
))
171 GURL launch_url
= app
->is_platform_app() ?
172 app
->url() : extensions::AppLaunchInfo::GetFullLaunchURL(app
);
173 DCHECK(launch_url
.is_valid());
175 ExtensionApp extension_app
= {
176 base::UTF8ToUTF16(app
->name()),
177 base::UTF8ToUTF16(launch_url
.spec()),
178 // Only hosted apps have recognizable URLs that users might type in,
179 // packaged apps and hosted apps use chrome-extension:// URLs that are
180 // normally not shown to users.
183 extension_apps_
.push_back(extension_app
);
187 void ExtensionAppProvider::Observe(int type
,
188 const content::NotificationSource
& source
,
189 const content::NotificationDetails
& details
) {
193 int ExtensionAppProvider::CalculateRelevance(AutocompleteInput::Type type
,
197 // If you update the algorithm here, please remember to update the tables in
198 // autocomplete.h also.
199 const int kMaxRelevance
= 1425;
201 if (input_length
== target_length
)
202 return kMaxRelevance
;
204 // We give a boost proportionally based on how much of the input matches the
205 // app name, up to a maximum close to 200 (we can be close to, but we'll never
206 // reach 200 because the 100% match is taken care of above).
207 double fraction_boost
= static_cast<double>(200) *
208 input_length
/ target_length
;
210 // We also give a boost relative to how often the user has previously typed
211 // the Extension App URL/selected the Extension App suggestion from this
212 // provider (boost is between 200-400).
213 double type_count_boost
= 0;
214 HistoryService
* const history_service
=
215 HistoryServiceFactory::GetForProfile(profile_
, Profile::EXPLICIT_ACCESS
);
216 history::URLDatabase
* url_db
= history_service
?
217 history_service
->InMemoryDatabase() : NULL
;
219 history::URLRow info
;
220 url_db
->GetRowForURL(url
, &info
);
222 400 * (1.0 - (std::pow(static_cast<double>(2), -info
.typed_count())));
224 int relevance
= 575 + static_cast<int>(type_count_boost
) +
225 static_cast<int>(fraction_boost
);
226 DCHECK_LE(relevance
, kMaxRelevance
);