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/extensions/api/omnibox/omnibox_api.h"
7 #include "base/lazy_instance.h"
8 #include "base/strings/string16.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/extensions/tab_helper.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/search_engines/template_url_service_factory.h"
13 #include "chrome/common/extensions/api/omnibox.h"
14 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
15 #include "components/search_engines/template_url.h"
16 #include "components/search_engines/template_url_service.h"
17 #include "content/public/browser/notification_details.h"
18 #include "content/public/browser/notification_service.h"
19 #include "extensions/browser/event_router.h"
20 #include "extensions/browser/extension_prefs.h"
21 #include "extensions/browser/extension_prefs_factory.h"
22 #include "extensions/browser/extension_registry.h"
23 #include "extensions/browser/notification_types.h"
24 #include "ui/gfx/image/image.h"
26 namespace extensions
{
28 namespace omnibox
= api::omnibox
;
29 namespace SendSuggestions
= omnibox::SendSuggestions
;
30 namespace SetDefaultSuggestion
= omnibox::SetDefaultSuggestion
;
34 const char kSuggestionContent
[] = "content";
35 const char kCurrentTabDisposition
[] = "currentTab";
36 const char kForegroundTabDisposition
[] = "newForegroundTab";
37 const char kBackgroundTabDisposition
[] = "newBackgroundTab";
39 // Pref key for omnibox.setDefaultSuggestion.
40 const char kOmniboxDefaultSuggestion
[] = "omnibox_default_suggestion";
43 static const int kOmniboxIconPaddingLeft
= 2;
44 static const int kOmniboxIconPaddingRight
= 2;
45 #elif defined(OS_MACOSX)
46 static const int kOmniboxIconPaddingLeft
= 0;
47 static const int kOmniboxIconPaddingRight
= 2;
49 static const int kOmniboxIconPaddingLeft
= 0;
50 static const int kOmniboxIconPaddingRight
= 0;
53 scoped_ptr
<omnibox::SuggestResult
> GetOmniboxDefaultSuggestion(
55 const std::string
& extension_id
) {
56 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile
);
58 scoped_ptr
<omnibox::SuggestResult
> suggestion
;
59 const base::DictionaryValue
* dict
= NULL
;
60 if (prefs
&& prefs
->ReadPrefAsDictionary(extension_id
,
61 kOmniboxDefaultSuggestion
,
63 suggestion
.reset(new omnibox::SuggestResult
);
64 omnibox::SuggestResult::Populate(*dict
, suggestion
.get());
66 return suggestion
.Pass();
69 // Tries to set the omnibox default suggestion; returns true on success or
71 bool SetOmniboxDefaultSuggestion(
73 const std::string
& extension_id
,
74 const omnibox::DefaultSuggestResult
& suggestion
) {
75 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile
);
79 scoped_ptr
<base::DictionaryValue
> dict
= suggestion
.ToValue();
80 // Add the content field so that the dictionary can be used to populate an
81 // omnibox::SuggestResult.
82 dict
->SetWithoutPathExpansion(kSuggestionContent
, new base::StringValue(""));
83 prefs
->UpdateExtensionPref(extension_id
,
84 kOmniboxDefaultSuggestion
,
90 // Returns a string used as a template URL string of the extension.
91 std::string
GetTemplateURLStringForExtension(const std::string
& extension_id
) {
92 // This URL is not actually used for navigation. It holds the extension's ID.
93 return std::string(extensions::kExtensionScheme
) + "://" +
94 extension_id
+ "/?q={searchTerms}";
100 void ExtensionOmniboxEventRouter::OnInputStarted(
101 Profile
* profile
, const std::string
& extension_id
) {
102 scoped_ptr
<Event
> event(new Event(events::OMNIBOX_ON_INPUT_STARTED
,
103 omnibox::OnInputStarted::kEventName
,
104 make_scoped_ptr(new base::ListValue())));
105 event
->restrict_to_browser_context
= profile
;
106 EventRouter::Get(profile
)
107 ->DispatchEventToExtension(extension_id
, event
.Pass());
111 bool ExtensionOmniboxEventRouter::OnInputChanged(
112 Profile
* profile
, const std::string
& extension_id
,
113 const std::string
& input
, int suggest_id
) {
114 EventRouter
* event_router
= EventRouter::Get(profile
);
115 if (!event_router
->ExtensionHasEventListener(
116 extension_id
, omnibox::OnInputChanged::kEventName
))
119 scoped_ptr
<base::ListValue
> args(new base::ListValue());
120 args
->Set(0, new base::StringValue(input
));
121 args
->Set(1, new base::FundamentalValue(suggest_id
));
123 scoped_ptr
<Event
> event(new Event(events::OMNIBOX_ON_INPUT_CHANGED
,
124 omnibox::OnInputChanged::kEventName
,
126 event
->restrict_to_browser_context
= profile
;
127 event_router
->DispatchEventToExtension(extension_id
, event
.Pass());
132 void ExtensionOmniboxEventRouter::OnInputEntered(
133 content::WebContents
* web_contents
,
134 const std::string
& extension_id
,
135 const std::string
& input
,
136 WindowOpenDisposition disposition
) {
138 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
140 const Extension
* extension
=
141 ExtensionRegistry::Get(profile
)->enabled_extensions().GetByID(
144 extensions::TabHelper::FromWebContents(web_contents
)->
145 active_tab_permission_granter()->GrantIfRequested(extension
);
147 scoped_ptr
<base::ListValue
> args(new base::ListValue());
148 args
->Set(0, new base::StringValue(input
));
149 if (disposition
== NEW_FOREGROUND_TAB
)
150 args
->Set(1, new base::StringValue(kForegroundTabDisposition
));
151 else if (disposition
== NEW_BACKGROUND_TAB
)
152 args
->Set(1, new base::StringValue(kBackgroundTabDisposition
));
154 args
->Set(1, new base::StringValue(kCurrentTabDisposition
));
156 scoped_ptr
<Event
> event(new Event(events::OMNIBOX_ON_INPUT_ENTERED
,
157 omnibox::OnInputEntered::kEventName
,
159 event
->restrict_to_browser_context
= profile
;
160 EventRouter::Get(profile
)
161 ->DispatchEventToExtension(extension_id
, event
.Pass());
163 content::NotificationService::current()->Notify(
164 extensions::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED
,
165 content::Source
<Profile
>(profile
),
166 content::NotificationService::NoDetails());
170 void ExtensionOmniboxEventRouter::OnInputCancelled(
171 Profile
* profile
, const std::string
& extension_id
) {
172 scoped_ptr
<Event
> event(new Event(events::OMNIBOX_ON_INPUT_CANCELLED
,
173 omnibox::OnInputCancelled::kEventName
,
174 make_scoped_ptr(new base::ListValue())));
175 event
->restrict_to_browser_context
= profile
;
176 EventRouter::Get(profile
)
177 ->DispatchEventToExtension(extension_id
, event
.Pass());
180 OmniboxAPI::OmniboxAPI(content::BrowserContext
* context
)
181 : profile_(Profile::FromBrowserContext(context
)),
182 url_service_(TemplateURLServiceFactory::GetForProfile(profile_
)),
183 extension_registry_observer_(this) {
184 extension_registry_observer_
.Add(ExtensionRegistry::Get(profile_
));
186 template_url_sub_
= url_service_
->RegisterOnLoadedCallback(
187 base::Bind(&OmniboxAPI::OnTemplateURLsLoaded
,
188 base::Unretained(this)));
191 // Use monochrome icons for Omnibox icons.
192 omnibox_popup_icon_manager_
.set_monochrome(true);
193 omnibox_icon_manager_
.set_monochrome(true);
194 omnibox_icon_manager_
.set_padding(gfx::Insets(0, kOmniboxIconPaddingLeft
,
195 0, kOmniboxIconPaddingRight
));
198 void OmniboxAPI::Shutdown() {
199 template_url_sub_
.reset();
202 OmniboxAPI::~OmniboxAPI() {
205 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<OmniboxAPI
> >
206 g_factory
= LAZY_INSTANCE_INITIALIZER
;
209 BrowserContextKeyedAPIFactory
<OmniboxAPI
>* OmniboxAPI::GetFactoryInstance() {
210 return g_factory
.Pointer();
214 OmniboxAPI
* OmniboxAPI::Get(content::BrowserContext
* context
) {
215 return BrowserContextKeyedAPIFactory
<OmniboxAPI
>::Get(context
);
218 void OmniboxAPI::OnExtensionLoaded(content::BrowserContext
* browser_context
,
219 const Extension
* extension
) {
220 const std::string
& keyword
= OmniboxInfo::GetKeyword(extension
);
221 if (!keyword
.empty()) {
222 // Load the omnibox icon so it will be ready to display in the URL bar.
223 omnibox_popup_icon_manager_
.LoadIcon(profile_
, extension
);
224 omnibox_icon_manager_
.LoadIcon(profile_
, extension
);
227 url_service_
->Load();
228 if (url_service_
->loaded()) {
229 url_service_
->RegisterOmniboxKeyword(
230 extension
->id(), extension
->name(), keyword
,
231 GetTemplateURLStringForExtension(extension
->id()));
233 pending_extensions_
.insert(extension
);
239 void OmniboxAPI::OnExtensionUnloaded(content::BrowserContext
* browser_context
,
240 const Extension
* extension
,
241 UnloadedExtensionInfo::Reason reason
) {
242 if (!OmniboxInfo::GetKeyword(extension
).empty() && url_service_
) {
243 if (url_service_
->loaded()) {
244 url_service_
->RemoveExtensionControlledTURL(
245 extension
->id(), TemplateURL::OMNIBOX_API_EXTENSION
);
247 pending_extensions_
.erase(extension
);
252 gfx::Image
OmniboxAPI::GetOmniboxIcon(const std::string
& extension_id
) {
253 return gfx::Image::CreateFrom1xBitmap(
254 omnibox_icon_manager_
.GetIcon(extension_id
));
257 gfx::Image
OmniboxAPI::GetOmniboxPopupIcon(const std::string
& extension_id
) {
258 return gfx::Image::CreateFrom1xBitmap(
259 omnibox_popup_icon_manager_
.GetIcon(extension_id
));
262 void OmniboxAPI::OnTemplateURLsLoaded() {
263 // Register keywords for pending extensions.
264 template_url_sub_
.reset();
265 for (PendingExtensions::const_iterator
i(pending_extensions_
.begin());
266 i
!= pending_extensions_
.end(); ++i
) {
267 url_service_
->RegisterOmniboxKeyword(
268 (*i
)->id(), (*i
)->name(), OmniboxInfo::GetKeyword(*i
),
269 GetTemplateURLStringForExtension((*i
)->id()));
271 pending_extensions_
.clear();
275 void BrowserContextKeyedAPIFactory
<OmniboxAPI
>::DeclareFactoryDependencies() {
276 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
277 DependsOn(ExtensionPrefsFactory::GetInstance());
278 DependsOn(TemplateURLServiceFactory::GetInstance());
281 bool OmniboxSendSuggestionsFunction::RunSync() {
282 scoped_ptr
<SendSuggestions::Params
> params(
283 SendSuggestions::Params::Create(*args_
));
284 EXTENSION_FUNCTION_VALIDATE(params
);
286 content::NotificationService::current()->Notify(
287 extensions::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY
,
288 content::Source
<Profile
>(GetProfile()->GetOriginalProfile()),
289 content::Details
<SendSuggestions::Params
>(params
.get()));
294 bool OmniboxSetDefaultSuggestionFunction::RunSync() {
295 scoped_ptr
<SetDefaultSuggestion::Params
> params(
296 SetDefaultSuggestion::Params::Create(*args_
));
297 EXTENSION_FUNCTION_VALIDATE(params
);
299 if (SetOmniboxDefaultSuggestion(
300 GetProfile(), extension_id(), params
->suggestion
)) {
301 content::NotificationService::current()->Notify(
302 extensions::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED
,
303 content::Source
<Profile
>(GetProfile()->GetOriginalProfile()),
304 content::NotificationService::NoDetails());
310 // This function converts style information populated by the JSON schema
311 // compiler into an ACMatchClassifications object.
312 ACMatchClassifications
StyleTypesToACMatchClassifications(
313 const omnibox::SuggestResult
&suggestion
) {
314 ACMatchClassifications match_classifications
;
315 if (suggestion
.description_styles
) {
316 base::string16 description
= base::UTF8ToUTF16(suggestion
.description
);
317 std::vector
<int> styles(description
.length(), 0);
319 for (std::vector
<linked_ptr
<omnibox::SuggestResult::DescriptionStylesType
> >
320 ::iterator i
= suggestion
.description_styles
->begin();
321 i
!= suggestion
.description_styles
->end(); ++i
) {
322 omnibox::SuggestResult::DescriptionStylesType
* style
= i
->get();
324 int length
= description
.length();
326 length
= *style
->length
;
328 size_t offset
= style
->offset
>= 0 ? style
->offset
:
329 std::max(0, static_cast<int>(description
.length()) + style
->offset
);
332 switch (style
->type
) {
333 case omnibox::DESCRIPTION_STYLE_TYPE_URL
:
334 type_class
= AutocompleteMatch::ACMatchClassification::URL
;
336 case omnibox::DESCRIPTION_STYLE_TYPE_MATCH
:
337 type_class
= AutocompleteMatch::ACMatchClassification::MATCH
;
339 case omnibox::DESCRIPTION_STYLE_TYPE_DIM
:
340 type_class
= AutocompleteMatch::ACMatchClassification::DIM
;
343 type_class
= AutocompleteMatch::ACMatchClassification::NONE
;
344 return match_classifications
;
347 for (size_t j
= offset
; j
< offset
+ length
&& j
< styles
.size(); ++j
)
348 styles
[j
] |= type_class
;
351 for (size_t i
= 0; i
< styles
.size(); ++i
) {
352 if (i
== 0 || styles
[i
] != styles
[i
-1])
353 match_classifications
.push_back(
354 ACMatchClassification(i
, styles
[i
]));
357 match_classifications
.push_back(
358 ACMatchClassification(0, ACMatchClassification::NONE
));
361 return match_classifications
;
364 void ApplyDefaultSuggestionForExtensionKeyword(
366 const TemplateURL
* keyword
,
367 const base::string16
& remaining_input
,
368 AutocompleteMatch
* match
) {
369 DCHECK(keyword
->GetType() == TemplateURL::OMNIBOX_API_EXTENSION
);
371 scoped_ptr
<omnibox::SuggestResult
> suggestion(
372 GetOmniboxDefaultSuggestion(profile
, keyword
->GetExtensionId()));
373 if (!suggestion
|| suggestion
->description
.empty())
374 return; // fall back to the universal default
376 const base::string16
kPlaceholderText(base::ASCIIToUTF16("%s"));
377 const base::string16
kReplacementText(base::ASCIIToUTF16("<input>"));
379 base::string16 description
= base::UTF8ToUTF16(suggestion
->description
);
380 ACMatchClassifications
& description_styles
= match
->contents_class
;
381 description_styles
= StyleTypesToACMatchClassifications(*suggestion
);
383 // Replace "%s" with the user's input and adjust the style offsets to the
384 // new length of the description.
385 size_t placeholder(description
.find(kPlaceholderText
, 0));
386 if (placeholder
!= base::string16::npos
) {
387 base::string16 replacement
=
388 remaining_input
.empty() ? kReplacementText
: remaining_input
;
389 description
.replace(placeholder
, kPlaceholderText
.length(), replacement
);
391 for (size_t i
= 0; i
< description_styles
.size(); ++i
) {
392 if (description_styles
[i
].offset
> placeholder
)
393 description_styles
[i
].offset
+= replacement
.length() - 2;
397 match
->contents
.assign(description
);
400 } // namespace extensions