Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / omnibox / omnibox_api.cc
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.
4
5 #include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
6
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"
31
32 namespace extensions {
33
34 namespace omnibox = api::omnibox;
35 namespace SendSuggestions = omnibox::SendSuggestions;
36 namespace SetDefaultSuggestion = omnibox::SetDefaultSuggestion;
37
38 namespace {
39
40 const char kSuggestionContent[] = "content";
41 const char kCurrentTabDisposition[] = "currentTab";
42 const char kForegroundTabDisposition[] = "newForegroundTab";
43 const char kBackgroundTabDisposition[] = "newBackgroundTab";
44
45 // Pref key for omnibox.setDefaultSuggestion.
46 const char kOmniboxDefaultSuggestion[] = "omnibox_default_suggestion";
47
48 #if defined(OS_LINUX)
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;
54 #else
55 static const int kOmniboxIconPaddingLeft = 0;
56 static const int kOmniboxIconPaddingRight = 0;
57 #endif
58
59 scoped_ptr<omnibox::SuggestResult> GetOmniboxDefaultSuggestion(
60     Profile* profile,
61     const std::string& extension_id) {
62   ExtensionPrefs* prefs =
63       ExtensionSystem::Get(profile)->extension_service()->extension_prefs();
64
65   scoped_ptr<omnibox::SuggestResult> suggestion;
66   const base::DictionaryValue* dict = NULL;
67   if (prefs && prefs->ReadPrefAsDictionary(extension_id,
68                                            kOmniboxDefaultSuggestion,
69                                            &dict)) {
70     suggestion.reset(new omnibox::SuggestResult);
71     omnibox::SuggestResult::Populate(*dict, suggestion.get());
72   }
73   return suggestion.Pass();
74 }
75
76 // Tries to set the omnibox default suggestion; returns true on success or
77 // false on failure.
78 bool SetOmniboxDefaultSuggestion(
79     Profile* profile,
80     const std::string& extension_id,
81     const omnibox::DefaultSuggestResult& suggestion) {
82   ExtensionPrefs* prefs =
83       ExtensionSystem::Get(profile)->extension_service()->extension_prefs();
84   if (!prefs)
85     return false;
86
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,
93                              dict.release());
94
95   return true;
96 }
97
98 }  // namespace
99
100 // static
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());
109 }
110
111 // static
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))
118     return false;
119
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));
123
124   scoped_ptr<Event> event(new Event(omnibox::OnInputChanged::kEventName,
125                                     args.Pass()));
126   event->restrict_to_browser_context = profile;
127   ExtensionSystem::Get(profile)->event_router()->
128       DispatchEventToExtension(extension_id, event.Pass());
129   return true;
130 }
131
132 // static
133 void ExtensionOmniboxEventRouter::OnInputEntered(
134     content::WebContents* web_contents,
135     const std::string& extension_id,
136     const std::string& input,
137     WindowOpenDisposition disposition) {
138   Profile* profile =
139       Profile::FromBrowserContext(web_contents->GetBrowserContext());
140
141   const Extension* extension =
142       ExtensionSystem::Get(profile)->extension_service()->extensions()->
143           GetByID(extension_id);
144   CHECK(extension);
145   extensions::TabHelper::FromWebContents(web_contents)->
146       active_tab_permission_granter()->GrantIfRequested(extension);
147
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));
154   else
155     args->Set(1, new base::StringValue(kCurrentTabDisposition));
156
157   scoped_ptr<Event> event(new Event(omnibox::OnInputEntered::kEventName,
158                                     args.Pass()));
159   event->restrict_to_browser_context = profile;
160   ExtensionSystem::Get(profile)->event_router()->
161       DispatchEventToExtension(extension_id, event.Pass());
162
163   content::NotificationService::current()->Notify(
164       chrome::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED,
165       content::Source<Profile>(profile),
166       content::NotificationService::NoDetails());
167 }
168
169 // static
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());
178 }
179
180 OmniboxAPI::OmniboxAPI(Profile* profile)
181     : 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));
187   if (url_service_) {
188     template_url_sub_ = url_service_->RegisterOnLoadedCallback(
189         base::Bind(&OmniboxAPI::OnTemplateURLsLoaded,
190                    base::Unretained(this)));
191   }
192
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));
198 }
199
200 void OmniboxAPI::Shutdown() {
201   template_url_sub_.reset();
202 }
203
204 OmniboxAPI::~OmniboxAPI() {
205 }
206
207 static base::LazyInstance<ProfileKeyedAPIFactory<OmniboxAPI> >
208     g_factory = LAZY_INSTANCE_INITIALIZER;
209
210 // static
211 ProfileKeyedAPIFactory<OmniboxAPI>* OmniboxAPI::GetFactoryInstance() {
212   return g_factory.Pointer();
213 }
214
215 // static
216 OmniboxAPI* OmniboxAPI::Get(Profile* profile) {
217   return ProfileKeyedAPIFactory<OmniboxAPI>::GetForProfile(profile);
218 }
219
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);
231
232       if (url_service_) {
233         url_service_->Load();
234         if (url_service_->loaded()) {
235           url_service_->RegisterOmniboxKeyword(extension->id(),
236                                                extension->name(),
237                                                keyword);
238         } else {
239           pending_extensions_.insert(extension);
240         }
241       }
242     }
243   } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
244     const Extension* extension =
245         content::Details<UnloadedExtensionInfo>(details)->extension;
246     if (!OmniboxInfo::GetKeyword(extension).empty()) {
247       if (url_service_) {
248         if (url_service_->loaded())
249           url_service_->UnregisterOmniboxKeyword(extension->id());
250         else
251           pending_extensions_.erase(extension);
252       }
253     }
254   } else {
255     NOTREACHED();
256   }
257 }
258
259 gfx::Image OmniboxAPI::GetOmniboxIcon(const std::string& extension_id) {
260   return gfx::Image::CreateFrom1xBitmap(
261       omnibox_icon_manager_.GetIcon(extension_id));
262 }
263
264 gfx::Image OmniboxAPI::GetOmniboxPopupIcon(const std::string& extension_id) {
265   return gfx::Image::CreateFrom1xBitmap(
266       omnibox_popup_icon_manager_.GetIcon(extension_id));
267 }
268
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(),
275                                          (*i)->name(),
276                                          OmniboxInfo::GetKeyword(*i));
277   }
278   pending_extensions_.clear();
279 }
280
281 template <>
282 void ProfileKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() {
283   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
284   DependsOn(TemplateURLServiceFactory::GetInstance());
285 }
286
287 bool OmniboxSendSuggestionsFunction::RunImpl() {
288   scoped_ptr<SendSuggestions::Params> params(
289       SendSuggestions::Params::Create(*args_));
290   EXTENSION_FUNCTION_VALIDATE(params);
291
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()));
296
297   return true;
298 }
299
300 bool OmniboxSetDefaultSuggestionFunction::RunImpl() {
301   scoped_ptr<SetDefaultSuggestion::Params> params(
302       SetDefaultSuggestion::Params::Create(*args_));
303   EXTENSION_FUNCTION_VALIDATE(params);
304
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());
311   }
312
313   return true;
314 }
315
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);
324
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();
329
330       int length = description.length();
331       if (style->length)
332         length = *style->length;
333
334       size_t offset = style->offset >= 0 ? style->offset :
335           std::max(0, static_cast<int>(description.length()) + style->offset);
336
337       int type_class;
338       switch (style->type) {
339         case omnibox::SuggestResult::DescriptionStylesType::TYPE_URL:
340           type_class = AutocompleteMatch::ACMatchClassification::URL;
341           break;
342         case omnibox::SuggestResult::DescriptionStylesType::TYPE_MATCH:
343           type_class = AutocompleteMatch::ACMatchClassification::MATCH;
344           break;
345         case omnibox::SuggestResult::DescriptionStylesType::TYPE_DIM:
346           type_class = AutocompleteMatch::ACMatchClassification::DIM;
347           break;
348         default:
349           type_class = AutocompleteMatch::ACMatchClassification::NONE;
350           return match_classifications;
351       }
352
353       for (size_t j = offset; j < offset + length && j < styles.size(); ++j)
354         styles[j] |= type_class;
355     }
356
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]));
361     }
362   } else {
363     match_classifications.push_back(
364         ACMatchClassification(0, ACMatchClassification::NONE));
365   }
366
367   return match_classifications;
368 }
369
370 void ApplyDefaultSuggestionForExtensionKeyword(
371     Profile* profile,
372     const TemplateURL* keyword,
373     const base::string16& remaining_input,
374     AutocompleteMatch* match) {
375   DCHECK(keyword->GetType() == TemplateURL::OMNIBOX_API_EXTENSION);
376
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
381
382   const base::string16 kPlaceholderText(base::ASCIIToUTF16("%s"));
383   const base::string16 kReplacementText(base::ASCIIToUTF16("<input>"));
384
385   base::string16 description = base::UTF8ToUTF16(suggestion->description);
386   ACMatchClassifications& description_styles = match->contents_class;
387   description_styles = StyleTypesToACMatchClassifications(*suggestion);
388
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);
396
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;
400     }
401   }
402
403   match->contents.assign(description);
404 }
405
406 }  // namespace extensions