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 399