• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/chrome_notification_types.h"
11 #include "chrome/browser/extensions/tab_helper.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/search_engines/template_url.h"
14 #include "chrome/browser/search_engines/template_url_service.h"
15 #include "chrome/browser/search_engines/template_url_service_factory.h"
16 #include "chrome/common/extensions/api/omnibox.h"
17 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
18 #include "content/public/browser/notification_details.h"
19 #include "content/public/browser/notification_service.h"
20 #include "extensions/browser/event_router.h"
21 #include "extensions/browser/extension_prefs.h"
22 #include "extensions/browser/extension_prefs_factory.h"
23 #include "extensions/browser/extension_registry.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 
GetOmniboxDefaultSuggestion(Profile * profile,const std::string & extension_id)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.
SetOmniboxDefaultSuggestion(Profile * profile,const std::string & extension_id,const omnibox::DefaultSuggestResult & suggestion)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 }  // namespace
91 
92 // static
OnInputStarted(Profile * profile,const std::string & extension_id)93 void ExtensionOmniboxEventRouter::OnInputStarted(
94     Profile* profile, const std::string& extension_id) {
95   scoped_ptr<Event> event(new Event(
96       omnibox::OnInputStarted::kEventName,
97       make_scoped_ptr(new base::ListValue())));
98   event->restrict_to_browser_context = profile;
99   EventRouter::Get(profile)
100       ->DispatchEventToExtension(extension_id, event.Pass());
101 }
102 
103 // static
OnInputChanged(Profile * profile,const std::string & extension_id,const std::string & input,int suggest_id)104 bool ExtensionOmniboxEventRouter::OnInputChanged(
105     Profile* profile, const std::string& extension_id,
106     const std::string& input, int suggest_id) {
107   EventRouter* event_router = EventRouter::Get(profile);
108   if (!event_router->ExtensionHasEventListener(
109           extension_id, omnibox::OnInputChanged::kEventName))
110     return false;
111 
112   scoped_ptr<base::ListValue> args(new base::ListValue());
113   args->Set(0, new base::StringValue(input));
114   args->Set(1, new base::FundamentalValue(suggest_id));
115 
116   scoped_ptr<Event> event(new Event(omnibox::OnInputChanged::kEventName,
117                                     args.Pass()));
118   event->restrict_to_browser_context = profile;
119   event_router->DispatchEventToExtension(extension_id, event.Pass());
120   return true;
121 }
122 
123 // static
OnInputEntered(content::WebContents * web_contents,const std::string & extension_id,const std::string & input,WindowOpenDisposition disposition)124 void ExtensionOmniboxEventRouter::OnInputEntered(
125     content::WebContents* web_contents,
126     const std::string& extension_id,
127     const std::string& input,
128     WindowOpenDisposition disposition) {
129   Profile* profile =
130       Profile::FromBrowserContext(web_contents->GetBrowserContext());
131 
132   const Extension* extension =
133       ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
134           extension_id);
135   CHECK(extension);
136   extensions::TabHelper::FromWebContents(web_contents)->
137       active_tab_permission_granter()->GrantIfRequested(extension);
138 
139   scoped_ptr<base::ListValue> args(new base::ListValue());
140   args->Set(0, new base::StringValue(input));
141   if (disposition == NEW_FOREGROUND_TAB)
142     args->Set(1, new base::StringValue(kForegroundTabDisposition));
143   else if (disposition == NEW_BACKGROUND_TAB)
144     args->Set(1, new base::StringValue(kBackgroundTabDisposition));
145   else
146     args->Set(1, new base::StringValue(kCurrentTabDisposition));
147 
148   scoped_ptr<Event> event(new Event(omnibox::OnInputEntered::kEventName,
149                                     args.Pass()));
150   event->restrict_to_browser_context = profile;
151   EventRouter::Get(profile)
152       ->DispatchEventToExtension(extension_id, event.Pass());
153 
154   content::NotificationService::current()->Notify(
155       chrome::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED,
156       content::Source<Profile>(profile),
157       content::NotificationService::NoDetails());
158 }
159 
160 // static
OnInputCancelled(Profile * profile,const std::string & extension_id)161 void ExtensionOmniboxEventRouter::OnInputCancelled(
162     Profile* profile, const std::string& extension_id) {
163   scoped_ptr<Event> event(new Event(
164       omnibox::OnInputCancelled::kEventName,
165       make_scoped_ptr(new base::ListValue())));
166   event->restrict_to_browser_context = profile;
167   EventRouter::Get(profile)
168       ->DispatchEventToExtension(extension_id, event.Pass());
169 }
170 
OmniboxAPI(content::BrowserContext * context)171 OmniboxAPI::OmniboxAPI(content::BrowserContext* context)
172     : profile_(Profile::FromBrowserContext(context)),
173       url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
174       extension_registry_observer_(this) {
175   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
176   if (url_service_) {
177     template_url_sub_ = url_service_->RegisterOnLoadedCallback(
178         base::Bind(&OmniboxAPI::OnTemplateURLsLoaded,
179                    base::Unretained(this)));
180   }
181 
182   // Use monochrome icons for Omnibox icons.
183   omnibox_popup_icon_manager_.set_monochrome(true);
184   omnibox_icon_manager_.set_monochrome(true);
185   omnibox_icon_manager_.set_padding(gfx::Insets(0, kOmniboxIconPaddingLeft,
186                                                 0, kOmniboxIconPaddingRight));
187 }
188 
Shutdown()189 void OmniboxAPI::Shutdown() {
190   template_url_sub_.reset();
191 }
192 
~OmniboxAPI()193 OmniboxAPI::~OmniboxAPI() {
194 }
195 
196 static base::LazyInstance<BrowserContextKeyedAPIFactory<OmniboxAPI> >
197     g_factory = LAZY_INSTANCE_INITIALIZER;
198 
199 // static
GetFactoryInstance()200 BrowserContextKeyedAPIFactory<OmniboxAPI>* OmniboxAPI::GetFactoryInstance() {
201   return g_factory.Pointer();
202 }
203 
204 // static
Get(content::BrowserContext * context)205 OmniboxAPI* OmniboxAPI::Get(content::BrowserContext* context) {
206   return BrowserContextKeyedAPIFactory<OmniboxAPI>::Get(context);
207 }
208 
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)209 void OmniboxAPI::OnExtensionLoaded(content::BrowserContext* browser_context,
210                                    const Extension* extension) {
211   const std::string& keyword = OmniboxInfo::GetKeyword(extension);
212   if (!keyword.empty()) {
213     // Load the omnibox icon so it will be ready to display in the URL bar.
214     omnibox_popup_icon_manager_.LoadIcon(profile_, extension);
215     omnibox_icon_manager_.LoadIcon(profile_, extension);
216 
217     if (url_service_) {
218       url_service_->Load();
219       if (url_service_->loaded()) {
220         url_service_->RegisterOmniboxKeyword(
221             extension->id(), extension->name(), keyword);
222       } else {
223         pending_extensions_.insert(extension);
224       }
225     }
226   }
227 }
228 
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionInfo::Reason reason)229 void OmniboxAPI::OnExtensionUnloaded(content::BrowserContext* browser_context,
230                                      const Extension* extension,
231                                      UnloadedExtensionInfo::Reason reason) {
232   if (!OmniboxInfo::GetKeyword(extension).empty() && url_service_) {
233     if (url_service_->loaded())
234       url_service_->UnregisterOmniboxKeyword(extension->id());
235     else
236       pending_extensions_.erase(extension);
237   }
238 }
239 
GetOmniboxIcon(const std::string & extension_id)240 gfx::Image OmniboxAPI::GetOmniboxIcon(const std::string& extension_id) {
241   return gfx::Image::CreateFrom1xBitmap(
242       omnibox_icon_manager_.GetIcon(extension_id));
243 }
244 
GetOmniboxPopupIcon(const std::string & extension_id)245 gfx::Image OmniboxAPI::GetOmniboxPopupIcon(const std::string& extension_id) {
246   return gfx::Image::CreateFrom1xBitmap(
247       omnibox_popup_icon_manager_.GetIcon(extension_id));
248 }
249 
OnTemplateURLsLoaded()250 void OmniboxAPI::OnTemplateURLsLoaded() {
251   // Register keywords for pending extensions.
252   template_url_sub_.reset();
253   for (PendingExtensions::const_iterator i(pending_extensions_.begin());
254        i != pending_extensions_.end(); ++i) {
255     url_service_->RegisterOmniboxKeyword((*i)->id(),
256                                          (*i)->name(),
257                                          OmniboxInfo::GetKeyword(*i));
258   }
259   pending_extensions_.clear();
260 }
261 
262 template <>
DeclareFactoryDependencies()263 void BrowserContextKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() {
264   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
265   DependsOn(ExtensionPrefsFactory::GetInstance());
266   DependsOn(TemplateURLServiceFactory::GetInstance());
267 }
268 
RunSync()269 bool OmniboxSendSuggestionsFunction::RunSync() {
270   scoped_ptr<SendSuggestions::Params> params(
271       SendSuggestions::Params::Create(*args_));
272   EXTENSION_FUNCTION_VALIDATE(params);
273 
274   content::NotificationService::current()->Notify(
275       chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY,
276       content::Source<Profile>(GetProfile()->GetOriginalProfile()),
277       content::Details<SendSuggestions::Params>(params.get()));
278 
279   return true;
280 }
281 
RunSync()282 bool OmniboxSetDefaultSuggestionFunction::RunSync() {
283   scoped_ptr<SetDefaultSuggestion::Params> params(
284       SetDefaultSuggestion::Params::Create(*args_));
285   EXTENSION_FUNCTION_VALIDATE(params);
286 
287   if (SetOmniboxDefaultSuggestion(
288           GetProfile(), extension_id(), params->suggestion)) {
289     content::NotificationService::current()->Notify(
290         chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
291         content::Source<Profile>(GetProfile()->GetOriginalProfile()),
292         content::NotificationService::NoDetails());
293   }
294 
295   return true;
296 }
297 
298 // This function converts style information populated by the JSON schema
299 // compiler into an ACMatchClassifications object.
StyleTypesToACMatchClassifications(const omnibox::SuggestResult & suggestion)300 ACMatchClassifications StyleTypesToACMatchClassifications(
301     const omnibox::SuggestResult &suggestion) {
302   ACMatchClassifications match_classifications;
303   if (suggestion.description_styles) {
304     base::string16 description = base::UTF8ToUTF16(suggestion.description);
305     std::vector<int> styles(description.length(), 0);
306 
307     for (std::vector<linked_ptr<omnibox::SuggestResult::DescriptionStylesType> >
308          ::iterator i = suggestion.description_styles->begin();
309          i != suggestion.description_styles->end(); ++i) {
310       omnibox::SuggestResult::DescriptionStylesType* style = i->get();
311 
312       int length = description.length();
313       if (style->length)
314         length = *style->length;
315 
316       size_t offset = style->offset >= 0 ? style->offset :
317           std::max(0, static_cast<int>(description.length()) + style->offset);
318 
319       int type_class;
320       switch (style->type) {
321         case omnibox::SuggestResult::DescriptionStylesType::TYPE_URL:
322           type_class = AutocompleteMatch::ACMatchClassification::URL;
323           break;
324         case omnibox::SuggestResult::DescriptionStylesType::TYPE_MATCH:
325           type_class = AutocompleteMatch::ACMatchClassification::MATCH;
326           break;
327         case omnibox::SuggestResult::DescriptionStylesType::TYPE_DIM:
328           type_class = AutocompleteMatch::ACMatchClassification::DIM;
329           break;
330         default:
331           type_class = AutocompleteMatch::ACMatchClassification::NONE;
332           return match_classifications;
333       }
334 
335       for (size_t j = offset; j < offset + length && j < styles.size(); ++j)
336         styles[j] |= type_class;
337     }
338 
339     for (size_t i = 0; i < styles.size(); ++i) {
340       if (i == 0 || styles[i] != styles[i-1])
341         match_classifications.push_back(
342             ACMatchClassification(i, styles[i]));
343     }
344   } else {
345     match_classifications.push_back(
346         ACMatchClassification(0, ACMatchClassification::NONE));
347   }
348 
349   return match_classifications;
350 }
351 
ApplyDefaultSuggestionForExtensionKeyword(Profile * profile,const TemplateURL * keyword,const base::string16 & remaining_input,AutocompleteMatch * match)352 void ApplyDefaultSuggestionForExtensionKeyword(
353     Profile* profile,
354     const TemplateURL* keyword,
355     const base::string16& remaining_input,
356     AutocompleteMatch* match) {
357   DCHECK(keyword->GetType() == TemplateURL::OMNIBOX_API_EXTENSION);
358 
359   scoped_ptr<omnibox::SuggestResult> suggestion(
360       GetOmniboxDefaultSuggestion(profile, keyword->GetExtensionId()));
361   if (!suggestion || suggestion->description.empty())
362     return;  // fall back to the universal default
363 
364   const base::string16 kPlaceholderText(base::ASCIIToUTF16("%s"));
365   const base::string16 kReplacementText(base::ASCIIToUTF16("<input>"));
366 
367   base::string16 description = base::UTF8ToUTF16(suggestion->description);
368   ACMatchClassifications& description_styles = match->contents_class;
369   description_styles = StyleTypesToACMatchClassifications(*suggestion);
370 
371   // Replace "%s" with the user's input and adjust the style offsets to the
372   // new length of the description.
373   size_t placeholder(description.find(kPlaceholderText, 0));
374   if (placeholder != base::string16::npos) {
375     base::string16 replacement =
376         remaining_input.empty() ? kReplacementText : remaining_input;
377     description.replace(placeholder, kPlaceholderText.length(), replacement);
378 
379     for (size_t i = 0; i < description_styles.size(); ++i) {
380       if (description_styles[i].offset > placeholder)
381         description_styles[i].offset += replacement.length() - 2;
382     }
383   }
384 
385   match->contents.assign(description);
386 }
387 
388 }  // namespace extensions
389