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_util.h"
15 #include "chrome/browser/history/history_service.h"
16 #include "chrome/browser/history/history_service_factory.h"
17 #include "chrome/browser/history/url_database.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/extensions/application_launch.h"
20 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
21 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
22 #include "content/public/browser/notification_source.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/browser/extension_system.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/extension_set.h"
27 #include "ui/base/l10n/l10n_util.h"
29 ExtensionAppProvider::ExtensionAppProvider(
30 AutocompleteProviderListener
* listener
,
32 : AutocompleteProvider(listener
, profile
,
33 AutocompleteProvider::TYPE_EXTENSION_APP
) {
34 // Notifications of extensions loading and unloading always come from the
35 // non-incognito profile, but we need to see them regardless, as the incognito
36 // windows can be affected.
38 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
39 content::Source
<Profile
>(profile_
->GetOriginalProfile()));
40 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED
,
41 content::Source
<Profile
>(profile_
->GetOriginalProfile()));
46 void ExtensionAppProvider::LaunchAppFromOmnibox(
47 const AutocompleteMatch
& match
,
49 WindowOpenDisposition disposition
) {
50 const extensions::Extension
* extension
=
51 extensions::ExtensionRegistry::Get(profile
)
52 ->enabled_extensions().GetAppByURL(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::ExtensionSystem::Get(profile_
)->extension_service();
154 if (!extension_service
)
155 return; // During testing, there is no extension service.
156 const extensions::ExtensionSet
* extensions
= extension_service
->extensions();
157 extension_apps_
.clear();
158 for (extensions::ExtensionSet::const_iterator iter
= extensions
->begin();
159 iter
!= extensions
->end(); ++iter
) {
160 const extensions::Extension
* app
= iter
->get();
161 if (!app
->ShouldDisplayInAppLauncher())
163 // Note: Apps that appear in the NTP only are not added here since this
164 // provider is currently only used in the app launcher.
166 if (profile_
->IsOffTheRecord() &&
167 !extensions::util::CanLoadInIncognito(app
, profile_
))
170 GURL launch_url
= app
->is_platform_app() ?
171 app
->url() : extensions::AppLaunchInfo::GetFullLaunchURL(app
);
172 DCHECK(launch_url
.is_valid());
174 ExtensionApp extension_app
= {
175 base::UTF8ToUTF16(app
->name()),
176 base::UTF8ToUTF16(launch_url
.spec()),
177 // Only hosted apps have recognizable URLs that users might type in,
178 // packaged apps and hosted apps use chrome-extension:// URLs that are
179 // normally not shown to users.
182 extension_apps_
.push_back(extension_app
);
186 void ExtensionAppProvider::Observe(int type
,
187 const content::NotificationSource
& source
,
188 const content::NotificationDetails
& details
) {
192 int ExtensionAppProvider::CalculateRelevance(AutocompleteInput::Type type
,
196 // If you update the algorithm here, please remember to update the tables in
197 // autocomplete.h also.
198 const int kMaxRelevance
= 1425;
200 if (input_length
== target_length
)
201 return kMaxRelevance
;
203 // We give a boost proportionally based on how much of the input matches the
204 // app name, up to a maximum close to 200 (we can be close to, but we'll never
205 // reach 200 because the 100% match is taken care of above).
206 double fraction_boost
= static_cast<double>(200) *
207 input_length
/ target_length
;
209 // We also give a boost relative to how often the user has previously typed
210 // the Extension App URL/selected the Extension App suggestion from this
211 // provider (boost is between 200-400).
212 double type_count_boost
= 0;
213 HistoryService
* const history_service
=
214 HistoryServiceFactory::GetForProfile(profile_
, Profile::EXPLICIT_ACCESS
);
215 history::URLDatabase
* url_db
= history_service
?
216 history_service
->InMemoryDatabase() : NULL
;
218 history::URLRow info
;
219 url_db
->GetRowForURL(url
, &info
);
221 400 * (1.0 - (std::pow(static_cast<double>(2), -info
.typed_count())));
223 int relevance
= 575 + static_cast<int>(type_count_boost
) +
224 static_cast<int>(fraction_boost
);
225 DCHECK_LE(relevance
, kMaxRelevance
);