Upstream version 9.38.198.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/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"
25
26 namespace extensions {
27
28 namespace omnibox = api::omnibox;
29 namespace SendSuggestions = omnibox::SendSuggestions;
30 namespace SetDefaultSuggestion = omnibox::SetDefaultSuggestion;
31
32 namespace {
33
34 const char kSuggestionContent[] = "content";
35 const char kCurrentTabDisposition[] = "currentTab";
36 const char kForegroundTabDisposition[] = "newForegroundTab";
37 const char kBackgroundTabDisposition[] = "newBackgroundTab";
38
39 // Pref key for omnibox.setDefaultSuggestion.
40 const char kOmniboxDefaultSuggestion[] = "omnibox_default_suggestion";
41
42 #if defined(OS_LINUX)
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;
48 #else
49 static const int kOmniboxIconPaddingLeft = 0;
50 static const int kOmniboxIconPaddingRight = 0;
51 #endif
52
53 scoped_ptr<omnibox::SuggestResult> GetOmniboxDefaultSuggestion(
54     Profile* profile,
55     const std::string& extension_id) {
56   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile);
57
58   scoped_ptr<omnibox::SuggestResult> suggestion;
59   const base::DictionaryValue* dict = NULL;
60   if (prefs && prefs->ReadPrefAsDictionary(extension_id,
61                                            kOmniboxDefaultSuggestion,
62                                            &dict)) {
63     suggestion.reset(new omnibox::SuggestResult);
64     omnibox::SuggestResult::Populate(*dict, suggestion.get());
65   }
66   return suggestion.Pass();
67 }
68
69 // Tries to set the omnibox default suggestion; returns true on success or
70 // false on failure.
71 bool SetOmniboxDefaultSuggestion(
72     Profile* profile,
73     const std::string& extension_id,
74     const omnibox::DefaultSuggestResult& suggestion) {
75   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile);
76   if (!prefs)
77     return false;
78
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,
85                              dict.release());
86
87   return true;
88 }
89
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}";
95 }
96
97 }  // namespace
98
99 // static
100 void ExtensionOmniboxEventRouter::OnInputStarted(
101     Profile* profile, const std::string& extension_id) {
102   scoped_ptr<Event> event(new Event(
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());
108 }
109
110 // static
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))
117     return false;
118
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));
122
123   scoped_ptr<Event> event(new Event(omnibox::OnInputChanged::kEventName,
124                                     args.Pass()));
125   event->restrict_to_browser_context = profile;
126   event_router->DispatchEventToExtension(extension_id, event.Pass());
127   return true;
128 }
129
130 // static
131 void ExtensionOmniboxEventRouter::OnInputEntered(
132     content::WebContents* web_contents,
133     const std::string& extension_id,
134     const std::string& input,
135     WindowOpenDisposition disposition) {
136   Profile* profile =
137       Profile::FromBrowserContext(web_contents->GetBrowserContext());
138
139   const Extension* extension =
140       ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
141           extension_id);
142   CHECK(extension);
143   extensions::TabHelper::FromWebContents(web_contents)->
144       active_tab_permission_granter()->GrantIfRequested(extension);
145
146   scoped_ptr<base::ListValue> args(new base::ListValue());
147   args->Set(0, new base::StringValue(input));
148   if (disposition == NEW_FOREGROUND_TAB)
149     args->Set(1, new base::StringValue(kForegroundTabDisposition));
150   else if (disposition == NEW_BACKGROUND_TAB)
151     args->Set(1, new base::StringValue(kBackgroundTabDisposition));
152   else
153     args->Set(1, new base::StringValue(kCurrentTabDisposition));
154
155   scoped_ptr<Event> event(new Event(omnibox::OnInputEntered::kEventName,
156                                     args.Pass()));
157   event->restrict_to_browser_context = profile;
158   EventRouter::Get(profile)
159       ->DispatchEventToExtension(extension_id, event.Pass());
160
161   content::NotificationService::current()->Notify(
162       extensions::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED,
163       content::Source<Profile>(profile),
164       content::NotificationService::NoDetails());
165 }
166
167 // static
168 void ExtensionOmniboxEventRouter::OnInputCancelled(
169     Profile* profile, const std::string& extension_id) {
170   scoped_ptr<Event> event(new Event(
171       omnibox::OnInputCancelled::kEventName,
172       make_scoped_ptr(new base::ListValue())));
173   event->restrict_to_browser_context = profile;
174   EventRouter::Get(profile)
175       ->DispatchEventToExtension(extension_id, event.Pass());
176 }
177
178 OmniboxAPI::OmniboxAPI(content::BrowserContext* context)
179     : profile_(Profile::FromBrowserContext(context)),
180       url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
181       extension_registry_observer_(this) {
182   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
183   if (url_service_) {
184     template_url_sub_ = url_service_->RegisterOnLoadedCallback(
185         base::Bind(&OmniboxAPI::OnTemplateURLsLoaded,
186                    base::Unretained(this)));
187   }
188
189   // Use monochrome icons for Omnibox icons.
190   omnibox_popup_icon_manager_.set_monochrome(true);
191   omnibox_icon_manager_.set_monochrome(true);
192   omnibox_icon_manager_.set_padding(gfx::Insets(0, kOmniboxIconPaddingLeft,
193                                                 0, kOmniboxIconPaddingRight));
194 }
195
196 void OmniboxAPI::Shutdown() {
197   template_url_sub_.reset();
198 }
199
200 OmniboxAPI::~OmniboxAPI() {
201 }
202
203 static base::LazyInstance<BrowserContextKeyedAPIFactory<OmniboxAPI> >
204     g_factory = LAZY_INSTANCE_INITIALIZER;
205
206 // static
207 BrowserContextKeyedAPIFactory<OmniboxAPI>* OmniboxAPI::GetFactoryInstance() {
208   return g_factory.Pointer();
209 }
210
211 // static
212 OmniboxAPI* OmniboxAPI::Get(content::BrowserContext* context) {
213   return BrowserContextKeyedAPIFactory<OmniboxAPI>::Get(context);
214 }
215
216 void OmniboxAPI::OnExtensionLoaded(content::BrowserContext* browser_context,
217                                    const Extension* extension) {
218   const std::string& keyword = OmniboxInfo::GetKeyword(extension);
219   if (!keyword.empty()) {
220     // Load the omnibox icon so it will be ready to display in the URL bar.
221     omnibox_popup_icon_manager_.LoadIcon(profile_, extension);
222     omnibox_icon_manager_.LoadIcon(profile_, extension);
223
224     if (url_service_) {
225       url_service_->Load();
226       if (url_service_->loaded()) {
227         url_service_->RegisterOmniboxKeyword(
228             extension->id(), extension->name(), keyword,
229             GetTemplateURLStringForExtension(extension->id()));
230       } else {
231         pending_extensions_.insert(extension);
232       }
233     }
234   }
235 }
236
237 void OmniboxAPI::OnExtensionUnloaded(content::BrowserContext* browser_context,
238                                      const Extension* extension,
239                                      UnloadedExtensionInfo::Reason reason) {
240   if (!OmniboxInfo::GetKeyword(extension).empty() && url_service_) {
241     if (url_service_->loaded()) {
242       url_service_->RemoveExtensionControlledTURL(
243           extension->id(), TemplateURL::OMNIBOX_API_EXTENSION);
244     } else {
245       pending_extensions_.erase(extension);
246     }
247   }
248 }
249
250 gfx::Image OmniboxAPI::GetOmniboxIcon(const std::string& extension_id) {
251   return gfx::Image::CreateFrom1xBitmap(
252       omnibox_icon_manager_.GetIcon(extension_id));
253 }
254
255 gfx::Image OmniboxAPI::GetOmniboxPopupIcon(const std::string& extension_id) {
256   return gfx::Image::CreateFrom1xBitmap(
257       omnibox_popup_icon_manager_.GetIcon(extension_id));
258 }
259
260 void OmniboxAPI::OnTemplateURLsLoaded() {
261   // Register keywords for pending extensions.
262   template_url_sub_.reset();
263   for (PendingExtensions::const_iterator i(pending_extensions_.begin());
264        i != pending_extensions_.end(); ++i) {
265     url_service_->RegisterOmniboxKeyword(
266         (*i)->id(), (*i)->name(), OmniboxInfo::GetKeyword(*i),
267         GetTemplateURLStringForExtension((*i)->id()));
268   }
269   pending_extensions_.clear();
270 }
271
272 template <>
273 void BrowserContextKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() {
274   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
275   DependsOn(ExtensionPrefsFactory::GetInstance());
276   DependsOn(TemplateURLServiceFactory::GetInstance());
277 }
278
279 bool OmniboxSendSuggestionsFunction::RunSync() {
280   scoped_ptr<SendSuggestions::Params> params(
281       SendSuggestions::Params::Create(*args_));
282   EXTENSION_FUNCTION_VALIDATE(params);
283
284   content::NotificationService::current()->Notify(
285       extensions::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY,
286       content::Source<Profile>(GetProfile()->GetOriginalProfile()),
287       content::Details<SendSuggestions::Params>(params.get()));
288
289   return true;
290 }
291
292 bool OmniboxSetDefaultSuggestionFunction::RunSync() {
293   scoped_ptr<SetDefaultSuggestion::Params> params(
294       SetDefaultSuggestion::Params::Create(*args_));
295   EXTENSION_FUNCTION_VALIDATE(params);
296
297   if (SetOmniboxDefaultSuggestion(
298           GetProfile(), extension_id(), params->suggestion)) {
299     content::NotificationService::current()->Notify(
300         extensions::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
301         content::Source<Profile>(GetProfile()->GetOriginalProfile()),
302         content::NotificationService::NoDetails());
303   }
304
305   return true;
306 }
307
308 // This function converts style information populated by the JSON schema
309 // compiler into an ACMatchClassifications object.
310 ACMatchClassifications StyleTypesToACMatchClassifications(
311     const omnibox::SuggestResult &suggestion) {
312   ACMatchClassifications match_classifications;
313   if (suggestion.description_styles) {
314     base::string16 description = base::UTF8ToUTF16(suggestion.description);
315     std::vector<int> styles(description.length(), 0);
316
317     for (std::vector<linked_ptr<omnibox::SuggestResult::DescriptionStylesType> >
318          ::iterator i = suggestion.description_styles->begin();
319          i != suggestion.description_styles->end(); ++i) {
320       omnibox::SuggestResult::DescriptionStylesType* style = i->get();
321
322       int length = description.length();
323       if (style->length)
324         length = *style->length;
325
326       size_t offset = style->offset >= 0 ? style->offset :
327           std::max(0, static_cast<int>(description.length()) + style->offset);
328
329       int type_class;
330       switch (style->type) {
331         case omnibox::SuggestResult::DescriptionStylesType::TYPE_URL:
332           type_class = AutocompleteMatch::ACMatchClassification::URL;
333           break;
334         case omnibox::SuggestResult::DescriptionStylesType::TYPE_MATCH:
335           type_class = AutocompleteMatch::ACMatchClassification::MATCH;
336           break;
337         case omnibox::SuggestResult::DescriptionStylesType::TYPE_DIM:
338           type_class = AutocompleteMatch::ACMatchClassification::DIM;
339           break;
340         default:
341           type_class = AutocompleteMatch::ACMatchClassification::NONE;
342           return match_classifications;
343       }
344
345       for (size_t j = offset; j < offset + length && j < styles.size(); ++j)
346         styles[j] |= type_class;
347     }
348
349     for (size_t i = 0; i < styles.size(); ++i) {
350       if (i == 0 || styles[i] != styles[i-1])
351         match_classifications.push_back(
352             ACMatchClassification(i, styles[i]));
353     }
354   } else {
355     match_classifications.push_back(
356         ACMatchClassification(0, ACMatchClassification::NONE));
357   }
358
359   return match_classifications;
360 }
361
362 void ApplyDefaultSuggestionForExtensionKeyword(
363     Profile* profile,
364     const TemplateURL* keyword,
365     const base::string16& remaining_input,
366     AutocompleteMatch* match) {
367   DCHECK(keyword->GetType() == TemplateURL::OMNIBOX_API_EXTENSION);
368
369   scoped_ptr<omnibox::SuggestResult> suggestion(
370       GetOmniboxDefaultSuggestion(profile, keyword->GetExtensionId()));
371   if (!suggestion || suggestion->description.empty())
372     return;  // fall back to the universal default
373
374   const base::string16 kPlaceholderText(base::ASCIIToUTF16("%s"));
375   const base::string16 kReplacementText(base::ASCIIToUTF16("<input>"));
376
377   base::string16 description = base::UTF8ToUTF16(suggestion->description);
378   ACMatchClassifications& description_styles = match->contents_class;
379   description_styles = StyleTypesToACMatchClassifications(*suggestion);
380
381   // Replace "%s" with the user's input and adjust the style offsets to the
382   // new length of the description.
383   size_t placeholder(description.find(kPlaceholderText, 0));
384   if (placeholder != base::string16::npos) {
385     base::string16 replacement =
386         remaining_input.empty() ? kReplacementText : remaining_input;
387     description.replace(placeholder, kPlaceholderText.length(), replacement);
388
389     for (size_t i = 0; i < description_styles.size(); ++i) {
390       if (description_styles[i].offset > placeholder)
391         description_styles[i].offset += replacement.length() - 2;
392     }
393   }
394
395   match->contents.assign(description);
396 }
397
398 }  // namespace extensions