Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / api / omnibox / omnibox_api.cc
blobd232f63fb37895b99318de23084a262001be8490
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;
32 namespace {
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";
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
53 scoped_ptr<omnibox::SuggestResult> GetOmniboxDefaultSuggestion(
54 Profile* profile,
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,
62 &dict)) {
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
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;
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());
87 return true;
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}";
97 } // namespace
99 // static
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());
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;
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,
125 args.Pass()));
126 event->restrict_to_browser_context = profile;
127 event_router->DispatchEventToExtension(extension_id, event.Pass());
128 return true;
131 // static
132 void ExtensionOmniboxEventRouter::OnInputEntered(
133 content::WebContents* web_contents,
134 const std::string& extension_id,
135 const std::string& input,
136 WindowOpenDisposition disposition) {
137 Profile* profile =
138 Profile::FromBrowserContext(web_contents->GetBrowserContext());
140 const Extension* extension =
141 ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
142 extension_id);
143 CHECK(extension);
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));
153 else
154 args->Set(1, new base::StringValue(kCurrentTabDisposition));
156 scoped_ptr<Event> event(new Event(events::OMNIBOX_ON_INPUT_ENTERED,
157 omnibox::OnInputEntered::kEventName,
158 args.Pass()));
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());
169 // static
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_));
185 if (url_service_) {
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;
208 // static
209 BrowserContextKeyedAPIFactory<OmniboxAPI>* OmniboxAPI::GetFactoryInstance() {
210 return g_factory.Pointer();
213 // static
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);
226 if (url_service_) {
227 url_service_->Load();
228 if (url_service_->loaded()) {
229 url_service_->RegisterOmniboxKeyword(
230 extension->id(), extension->name(), keyword,
231 GetTemplateURLStringForExtension(extension->id()));
232 } else {
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);
246 } else {
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();
274 template <>
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()));
291 return true;
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());
307 return true;
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();
325 if (style->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);
331 int type_class;
332 switch (style->type) {
333 case omnibox::DESCRIPTION_STYLE_TYPE_URL:
334 type_class = AutocompleteMatch::ACMatchClassification::URL;
335 break;
336 case omnibox::DESCRIPTION_STYLE_TYPE_MATCH:
337 type_class = AutocompleteMatch::ACMatchClassification::MATCH;
338 break;
339 case omnibox::DESCRIPTION_STYLE_TYPE_DIM:
340 type_class = AutocompleteMatch::ACMatchClassification::DIM;
341 break;
342 default:
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]));
356 } else {
357 match_classifications.push_back(
358 ACMatchClassification(0, ACMatchClassification::NONE));
361 return match_classifications;
364 void ApplyDefaultSuggestionForExtensionKeyword(
365 Profile* profile,
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