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/json/json_writer.h"
8 #include "base/lazy_instance.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/values.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/tab_helper.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/search_engines/template_url.h"
18 #include "chrome/browser/search_engines/template_url_service.h"
19 #include "chrome/browser/search_engines/template_url_service_factory.h"
20 #include "chrome/common/extensions/api/omnibox.h"
21 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_service.h"
24 #include "extensions/browser/event_router.h"
25 #include "extensions/browser/extension_prefs.h"
26 #include "extensions/browser/extension_system.h"
27 #include "extensions/browser/extension_system_provider.h"
28 #include "extensions/browser/extensions_browser_client.h"
29 #include "extensions/common/extension.h"
30 #include "ui/gfx/image/image.h"
32 namespace extensions {
34 namespace omnibox = api::omnibox;
35 namespace SendSuggestions = omnibox::SendSuggestions;
36 namespace SetDefaultSuggestion = omnibox::SetDefaultSuggestion;
40 const char kSuggestionContent[] = "content";
41 const char kCurrentTabDisposition[] = "currentTab";
42 const char kForegroundTabDisposition[] = "newForegroundTab";
43 const char kBackgroundTabDisposition[] = "newBackgroundTab";
45 // Pref key for omnibox.setDefaultSuggestion.
46 const char kOmniboxDefaultSuggestion[] = "omnibox_default_suggestion";
49 static const int kOmniboxIconPaddingLeft = 2;
50 static const int kOmniboxIconPaddingRight = 2;
51 #elif defined(OS_MACOSX)
52 static const int kOmniboxIconPaddingLeft = 0;
53 static const int kOmniboxIconPaddingRight = 2;
55 static const int kOmniboxIconPaddingLeft = 0;
56 static const int kOmniboxIconPaddingRight = 0;
59 scoped_ptr<omnibox::SuggestResult> GetOmniboxDefaultSuggestion(
61 const std::string& extension_id) {
62 ExtensionPrefs* prefs =
63 ExtensionSystem::Get(profile)->extension_service()->extension_prefs();
65 scoped_ptr<omnibox::SuggestResult> suggestion;
66 const base::DictionaryValue* dict = NULL;
67 if (prefs && prefs->ReadPrefAsDictionary(extension_id,
68 kOmniboxDefaultSuggestion,
70 suggestion.reset(new omnibox::SuggestResult);
71 omnibox::SuggestResult::Populate(*dict, suggestion.get());
73 return suggestion.Pass();
76 // Tries to set the omnibox default suggestion; returns true on success or
78 bool SetOmniboxDefaultSuggestion(
80 const std::string& extension_id,
81 const omnibox::DefaultSuggestResult& suggestion) {
82 ExtensionPrefs* prefs =
83 ExtensionSystem::Get(profile)->extension_service()->extension_prefs();
87 scoped_ptr<base::DictionaryValue> dict = suggestion.ToValue();
88 // Add the content field so that the dictionary can be used to populate an
89 // omnibox::SuggestResult.
90 dict->SetWithoutPathExpansion(kSuggestionContent, new base::StringValue(""));
91 prefs->UpdateExtensionPref(extension_id,
92 kOmniboxDefaultSuggestion,
101 void ExtensionOmniboxEventRouter::OnInputStarted(
102 Profile* profile, const std::string& extension_id) {
103 scoped_ptr<Event> event(new Event(
104 omnibox::OnInputStarted::kEventName,
105 make_scoped_ptr(new base::ListValue())));
106 event->restrict_to_browser_context = profile;
107 ExtensionSystem::Get(profile)->event_router()->
108 DispatchEventToExtension(extension_id, event.Pass());
112 bool ExtensionOmniboxEventRouter::OnInputChanged(
113 Profile* profile, const std::string& extension_id,
114 const std::string& input, int suggest_id) {
115 if (!extensions::ExtensionSystem::Get(profile)->event_router()->
116 ExtensionHasEventListener(extension_id,
117 omnibox::OnInputChanged::kEventName))
120 scoped_ptr<base::ListValue> args(new base::ListValue());
121 args->Set(0, new base::StringValue(input));
122 args->Set(1, new base::FundamentalValue(suggest_id));
124 scoped_ptr<Event> event(new Event(omnibox::OnInputChanged::kEventName,
126 event->restrict_to_browser_context = profile;
127 ExtensionSystem::Get(profile)->event_router()->
128 DispatchEventToExtension(extension_id, event.Pass());
133 void ExtensionOmniboxEventRouter::OnInputEntered(
134 content::WebContents* web_contents,
135 const std::string& extension_id,
136 const std::string& input,
137 WindowOpenDisposition disposition) {
139 Profile::FromBrowserContext(web_contents->GetBrowserContext());
141 const Extension* extension =
142 ExtensionSystem::Get(profile)->extension_service()->extensions()->
143 GetByID(extension_id);
145 extensions::TabHelper::FromWebContents(web_contents)->
146 active_tab_permission_granter()->GrantIfRequested(extension);
148 scoped_ptr<base::ListValue> args(new base::ListValue());
149 args->Set(0, new base::StringValue(input));
150 if (disposition == NEW_FOREGROUND_TAB)
151 args->Set(1, new base::StringValue(kForegroundTabDisposition));
152 else if (disposition == NEW_BACKGROUND_TAB)
153 args->Set(1, new base::StringValue(kBackgroundTabDisposition));
155 args->Set(1, new base::StringValue(kCurrentTabDisposition));
157 scoped_ptr<Event> event(new Event(omnibox::OnInputEntered::kEventName,
159 event->restrict_to_browser_context = profile;
160 ExtensionSystem::Get(profile)->event_router()->
161 DispatchEventToExtension(extension_id, event.Pass());
163 content::NotificationService::current()->Notify(
164 chrome::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(
173 omnibox::OnInputCancelled::kEventName,
174 make_scoped_ptr(new base::ListValue())));
175 event->restrict_to_browser_context = profile;
176 ExtensionSystem::Get(profile)->event_router()->
177 DispatchEventToExtension(extension_id, event.Pass());
180 OmniboxAPI::OmniboxAPI(Profile* profile)
182 url_service_(TemplateURLServiceFactory::GetForProfile(profile)) {
183 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
184 content::Source<Profile>(profile));
185 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
186 content::Source<Profile>(profile));
188 template_url_sub_ = url_service_->RegisterOnLoadedCallback(
189 base::Bind(&OmniboxAPI::OnTemplateURLsLoaded,
190 base::Unretained(this)));
193 // Use monochrome icons for Omnibox icons.
194 omnibox_popup_icon_manager_.set_monochrome(true);
195 omnibox_icon_manager_.set_monochrome(true);
196 omnibox_icon_manager_.set_padding(gfx::Insets(0, kOmniboxIconPaddingLeft,
197 0, kOmniboxIconPaddingRight));
200 void OmniboxAPI::Shutdown() {
201 template_url_sub_.reset();
204 OmniboxAPI::~OmniboxAPI() {
207 static base::LazyInstance<ProfileKeyedAPIFactory<OmniboxAPI> >
208 g_factory = LAZY_INSTANCE_INITIALIZER;
211 ProfileKeyedAPIFactory<OmniboxAPI>* OmniboxAPI::GetFactoryInstance() {
212 return g_factory.Pointer();
216 OmniboxAPI* OmniboxAPI::Get(Profile* profile) {
217 return ProfileKeyedAPIFactory<OmniboxAPI>::GetForProfile(profile);
220 void OmniboxAPI::Observe(int type,
221 const content::NotificationSource& source,
222 const content::NotificationDetails& details) {
223 if (type == chrome::NOTIFICATION_EXTENSION_LOADED) {
224 const Extension* extension =
225 content::Details<const Extension>(details).ptr();
226 const std::string& keyword = OmniboxInfo::GetKeyword(extension);
227 if (!keyword.empty()) {
228 // Load the omnibox icon so it will be ready to display in the URL bar.
229 omnibox_popup_icon_manager_.LoadIcon(profile_, extension);
230 omnibox_icon_manager_.LoadIcon(profile_, extension);
233 url_service_->Load();
234 if (url_service_->loaded()) {
235 url_service_->RegisterOmniboxKeyword(extension->id(),
239 pending_extensions_.insert(extension);
243 } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
244 const Extension* extension =
245 content::Details<UnloadedExtensionInfo>(details)->extension;
246 if (!OmniboxInfo::GetKeyword(extension).empty()) {
248 if (url_service_->loaded())
249 url_service_->UnregisterOmniboxKeyword(extension->id());
251 pending_extensions_.erase(extension);
259 gfx::Image OmniboxAPI::GetOmniboxIcon(const std::string& extension_id) {
260 return gfx::Image::CreateFrom1xBitmap(
261 omnibox_icon_manager_.GetIcon(extension_id));
264 gfx::Image OmniboxAPI::GetOmniboxPopupIcon(const std::string& extension_id) {
265 return gfx::Image::CreateFrom1xBitmap(
266 omnibox_popup_icon_manager_.GetIcon(extension_id));
269 void OmniboxAPI::OnTemplateURLsLoaded() {
270 // Register keywords for pending extensions.
271 template_url_sub_.reset();
272 for (PendingExtensions::const_iterator i(pending_extensions_.begin());
273 i != pending_extensions_.end(); ++i) {
274 url_service_->RegisterOmniboxKeyword((*i)->id(),
276 OmniboxInfo::GetKeyword(*i));
278 pending_extensions_.clear();
282 void ProfileKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() {
283 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
284 DependsOn(TemplateURLServiceFactory::GetInstance());
287 bool OmniboxSendSuggestionsFunction::RunImpl() {
288 scoped_ptr<SendSuggestions::Params> params(
289 SendSuggestions::Params::Create(*args_));
290 EXTENSION_FUNCTION_VALIDATE(params);
292 content::NotificationService::current()->Notify(
293 chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY,
294 content::Source<Profile>(GetProfile()->GetOriginalProfile()),
295 content::Details<SendSuggestions::Params>(params.get()));
300 bool OmniboxSetDefaultSuggestionFunction::RunImpl() {
301 scoped_ptr<SetDefaultSuggestion::Params> params(
302 SetDefaultSuggestion::Params::Create(*args_));
303 EXTENSION_FUNCTION_VALIDATE(params);
305 if (SetOmniboxDefaultSuggestion(
306 GetProfile(), extension_id(), params->suggestion)) {
307 content::NotificationService::current()->Notify(
308 chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
309 content::Source<Profile>(GetProfile()->GetOriginalProfile()),
310 content::NotificationService::NoDetails());
316 // This function converts style information populated by the JSON schema
317 // compiler into an ACMatchClassifications object.
318 ACMatchClassifications StyleTypesToACMatchClassifications(
319 const omnibox::SuggestResult &suggestion) {
320 ACMatchClassifications match_classifications;
321 if (suggestion.description_styles) {
322 base::string16 description = base::UTF8ToUTF16(suggestion.description);
323 std::vector<int> styles(description.length(), 0);
325 for (std::vector<linked_ptr<omnibox::SuggestResult::DescriptionStylesType> >
326 ::iterator i = suggestion.description_styles->begin();
327 i != suggestion.description_styles->end(); ++i) {
328 omnibox::SuggestResult::DescriptionStylesType* style = i->get();
330 int length = description.length();
332 length = *style->length;
334 size_t offset = style->offset >= 0 ? style->offset :
335 std::max(0, static_cast<int>(description.length()) + style->offset);
338 switch (style->type) {
339 case omnibox::SuggestResult::DescriptionStylesType::TYPE_URL:
340 type_class = AutocompleteMatch::ACMatchClassification::URL;
342 case omnibox::SuggestResult::DescriptionStylesType::TYPE_MATCH:
343 type_class = AutocompleteMatch::ACMatchClassification::MATCH;
345 case omnibox::SuggestResult::DescriptionStylesType::TYPE_DIM:
346 type_class = AutocompleteMatch::ACMatchClassification::DIM;
349 type_class = AutocompleteMatch::ACMatchClassification::NONE;
350 return match_classifications;
353 for (size_t j = offset; j < offset + length && j < styles.size(); ++j)
354 styles[j] |= type_class;
357 for (size_t i = 0; i < styles.size(); ++i) {
358 if (i == 0 || styles[i] != styles[i-1])
359 match_classifications.push_back(
360 ACMatchClassification(i, styles[i]));
363 match_classifications.push_back(
364 ACMatchClassification(0, ACMatchClassification::NONE));
367 return match_classifications;
370 void ApplyDefaultSuggestionForExtensionKeyword(
372 const TemplateURL* keyword,
373 const base::string16& remaining_input,
374 AutocompleteMatch* match) {
375 DCHECK(keyword->GetType() == TemplateURL::OMNIBOX_API_EXTENSION);
377 scoped_ptr<omnibox::SuggestResult> suggestion(
378 GetOmniboxDefaultSuggestion(profile, keyword->GetExtensionId()));
379 if (!suggestion || suggestion->description.empty())
380 return; // fall back to the universal default
382 const base::string16 kPlaceholderText(base::ASCIIToUTF16("%s"));
383 const base::string16 kReplacementText(base::ASCIIToUTF16("<input>"));
385 base::string16 description = base::UTF8ToUTF16(suggestion->description);
386 ACMatchClassifications& description_styles = match->contents_class;
387 description_styles = StyleTypesToACMatchClassifications(*suggestion);
389 // Replace "%s" with the user's input and adjust the style offsets to the
390 // new length of the description.
391 size_t placeholder(description.find(kPlaceholderText, 0));
392 if (placeholder != base::string16::npos) {
393 base::string16 replacement =
394 remaining_input.empty() ? kReplacementText : remaining_input;
395 description.replace(placeholder, kPlaceholderText.length(), replacement);
397 for (size_t i = 0; i < description_styles.size(); ++i) {
398 if (description_styles[i].offset > placeholder)
399 description_styles[i].offset += replacement.length() - 2;
403 match->contents.assign(description);
406 } // namespace extensions