Home | History | Annotate | Download | only in omnibox
      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_prefs.h"
     15 #include "chrome/browser/extensions/extension_service.h"
     16 #include "chrome/browser/extensions/extension_system.h"
     17 #include "chrome/browser/extensions/tab_helper.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/browser/search_engines/template_url.h"
     20 #include "chrome/browser/search_engines/template_url_service.h"
     21 #include "chrome/browser/search_engines/template_url_service_factory.h"
     22 #include "chrome/common/extensions/api/omnibox.h"
     23 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
     24 #include "content/public/browser/notification_details.h"
     25 #include "content/public/browser/notification_service.h"
     26 #include "extensions/browser/event_router.h"
     27 #include "extensions/common/extension.h"
     28 #include "ui/gfx/image/image.h"
     29 
     30 namespace extensions {
     31 
     32 namespace omnibox = api::omnibox;
     33 namespace SendSuggestions = omnibox::SendSuggestions;
     34 namespace SetDefaultSuggestion = omnibox::SetDefaultSuggestion;
     35 
     36 namespace {
     37 
     38 const char kSuggestionContent[] = "content";
     39 const char kCurrentTabDisposition[] = "currentTab";
     40 const char kForegroundTabDisposition[] = "newForegroundTab";
     41 const char kBackgroundTabDisposition[] = "newBackgroundTab";
     42 
     43 // Pref key for omnibox.setDefaultSuggestion.
     44 const char kOmniboxDefaultSuggestion[] = "omnibox_default_suggestion";
     45 
     46 #if defined(OS_LINUX)
     47 static const int kOmniboxIconPaddingLeft = 2;
     48 static const int kOmniboxIconPaddingRight = 2;
     49 #elif defined(OS_MACOSX)
     50 static const int kOmniboxIconPaddingLeft = 0;
     51 static const int kOmniboxIconPaddingRight = 2;
     52 #else
     53 static const int kOmniboxIconPaddingLeft = 0;
     54 static const int kOmniboxIconPaddingRight = 0;
     55 #endif
     56 
     57 scoped_ptr<omnibox::SuggestResult> GetOmniboxDefaultSuggestion(
     58     Profile* profile,
     59     const std::string& extension_id) {
     60   ExtensionPrefs* prefs =
     61       ExtensionSystem::Get(profile)->extension_service()->extension_prefs();
     62 
     63   scoped_ptr<omnibox::SuggestResult> suggestion;
     64   const base::DictionaryValue* dict = NULL;
     65   if (prefs && prefs->ReadPrefAsDictionary(extension_id,
     66                                            kOmniboxDefaultSuggestion,
     67                                            &dict)) {
     68     suggestion.reset(new omnibox::SuggestResult);
     69     omnibox::SuggestResult::Populate(*dict, suggestion.get());
     70   }
     71   return suggestion.Pass();
     72 }
     73 
     74 // Tries to set the omnibox default suggestion; returns true on success or
     75 // false on failure.
     76 bool SetOmniboxDefaultSuggestion(
     77     Profile* profile,
     78     const std::string& extension_id,
     79     const omnibox::DefaultSuggestResult& suggestion) {
     80   ExtensionPrefs* prefs =
     81       ExtensionSystem::Get(profile)->extension_service()->extension_prefs();
     82   if (!prefs)
     83     return false;
     84 
     85   scoped_ptr<base::DictionaryValue> dict = suggestion.ToValue();
     86   // Add the content field so that the dictionary can be used to populate an
     87   // omnibox::SuggestResult.
     88   dict->SetWithoutPathExpansion(kSuggestionContent, new base::StringValue(""));
     89   prefs->UpdateExtensionPref(extension_id,
     90                              kOmniboxDefaultSuggestion,
     91                              dict.release());
     92 
     93   return true;
     94 }
     95 
     96 }  // namespace
     97 
     98 // static
     99 void ExtensionOmniboxEventRouter::OnInputStarted(
    100     Profile* profile, const std::string& extension_id) {
    101   scoped_ptr<Event> event(new Event(
    102       omnibox::OnInputStarted::kEventName,
    103       make_scoped_ptr(new base::ListValue())));
    104   event->restrict_to_browser_context = profile;
    105   ExtensionSystem::Get(profile)->event_router()->
    106       DispatchEventToExtension(extension_id, event.Pass());
    107 }
    108 
    109 // static
    110 bool ExtensionOmniboxEventRouter::OnInputChanged(
    111     Profile* profile, const std::string& extension_id,
    112     const std::string& input, int suggest_id) {
    113   if (!extensions::ExtensionSystem::Get(profile)->event_router()->
    114           ExtensionHasEventListener(extension_id,
    115                                     omnibox::OnInputChanged::kEventName))
    116     return false;
    117 
    118   scoped_ptr<base::ListValue> args(new base::ListValue());
    119   args->Set(0, new base::StringValue(input));
    120   args->Set(1, new base::FundamentalValue(suggest_id));
    121 
    122   scoped_ptr<Event> event(new Event(omnibox::OnInputChanged::kEventName,
    123                                     args.Pass()));
    124   event->restrict_to_browser_context = profile;
    125   ExtensionSystem::Get(profile)->event_router()->
    126       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       ExtensionSystem::Get(profile)->extension_service()->extensions()->
    141           GetByID(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   ExtensionSystem::Get(profile)->event_router()->
    159       DispatchEventToExtension(extension_id, event.Pass());
    160 
    161   content::NotificationService::current()->Notify(
    162       chrome::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   ExtensionSystem::Get(profile)->event_router()->
    175       DispatchEventToExtension(extension_id, event.Pass());
    176 }
    177 
    178 OmniboxAPI::OmniboxAPI(Profile* profile)
    179     : profile_(profile),
    180       url_service_(TemplateURLServiceFactory::GetForProfile(profile)) {
    181   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
    182                  content::Source<Profile>(profile));
    183   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
    184                  content::Source<Profile>(profile));
    185   if (url_service_) {
    186     template_url_sub_ = url_service_->RegisterOnLoadedCallback(
    187         base::Bind(&OmniboxAPI::OnTemplateURLsLoaded,
    188                    base::Unretained(this)));
    189   }
    190 
    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));
    196 }
    197 
    198 void OmniboxAPI::Shutdown() {
    199   template_url_sub_.reset();
    200 }
    201 
    202 OmniboxAPI::~OmniboxAPI() {
    203 }
    204 
    205 static base::LazyInstance<ProfileKeyedAPIFactory<OmniboxAPI> >
    206     g_factory = LAZY_INSTANCE_INITIALIZER;
    207 
    208 // static
    209 ProfileKeyedAPIFactory<OmniboxAPI>* OmniboxAPI::GetFactoryInstance() {
    210   return &g_factory.Get();
    211 }
    212 
    213 // static
    214 OmniboxAPI* OmniboxAPI::Get(Profile* profile) {
    215   return ProfileKeyedAPIFactory<OmniboxAPI>::GetForProfile(profile);
    216 }
    217 
    218 void OmniboxAPI::Observe(int type,
    219                          const content::NotificationSource& source,
    220                          const content::NotificationDetails& details) {
    221   if (type == chrome::NOTIFICATION_EXTENSION_LOADED) {
    222     const Extension* extension =
    223         content::Details<const Extension>(details).ptr();
    224     const std::string& keyword = OmniboxInfo::GetKeyword(extension);
    225     if (!keyword.empty()) {
    226       // Load the omnibox icon so it will be ready to display in the URL bar.
    227       omnibox_popup_icon_manager_.LoadIcon(profile_, extension);
    228       omnibox_icon_manager_.LoadIcon(profile_, extension);
    229 
    230       if (url_service_) {
    231         url_service_->Load();
    232         if (url_service_->loaded()) {
    233           url_service_->RegisterOmniboxKeyword(extension->id(),
    234                                                extension->name(),
    235                                                keyword);
    236         } else {
    237           pending_extensions_.insert(extension);
    238         }
    239       }
    240     }
    241   } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
    242     const Extension* extension =
    243         content::Details<UnloadedExtensionInfo>(details)->extension;
    244     if (!OmniboxInfo::GetKeyword(extension).empty()) {
    245       if (url_service_) {
    246         if (url_service_->loaded())
    247           url_service_->UnregisterOmniboxKeyword(extension->id());
    248         else
    249           pending_extensions_.erase(extension);
    250       }
    251     }
    252   } else {
    253     NOTREACHED();
    254   }
    255 }
    256 
    257 gfx::Image OmniboxAPI::GetOmniboxIcon(const std::string& extension_id) {
    258   return gfx::Image::CreateFrom1xBitmap(
    259       omnibox_icon_manager_.GetIcon(extension_id));
    260 }
    261 
    262 gfx::Image OmniboxAPI::GetOmniboxPopupIcon(const std::string& extension_id) {
    263   return gfx::Image::CreateFrom1xBitmap(
    264       omnibox_popup_icon_manager_.GetIcon(extension_id));
    265 }
    266 
    267 void OmniboxAPI::OnTemplateURLsLoaded() {
    268   // Register keywords for pending extensions.
    269   template_url_sub_.reset();
    270   for (PendingExtensions::const_iterator i(pending_extensions_.begin());
    271        i != pending_extensions_.end(); ++i) {
    272     url_service_->RegisterOmniboxKeyword((*i)->id(),
    273                                          (*i)->name(),
    274                                          OmniboxInfo::GetKeyword(*i));
    275   }
    276   pending_extensions_.clear();
    277 }
    278 
    279 template <>
    280 void ProfileKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() {
    281   DependsOn(ExtensionSystemFactory::GetInstance());
    282   DependsOn(TemplateURLServiceFactory::GetInstance());
    283 }
    284 
    285 bool OmniboxSendSuggestionsFunction::RunImpl() {
    286   scoped_ptr<SendSuggestions::Params> params(
    287       SendSuggestions::Params::Create(*args_));
    288   EXTENSION_FUNCTION_VALIDATE(params);
    289 
    290   content::NotificationService::current()->Notify(
    291       chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY,
    292       content::Source<Profile>(GetProfile()->GetOriginalProfile()),
    293       content::Details<SendSuggestions::Params>(params.get()));
    294 
    295   return true;
    296 }
    297 
    298 bool OmniboxSetDefaultSuggestionFunction::RunImpl() {
    299   scoped_ptr<SetDefaultSuggestion::Params> params(
    300       SetDefaultSuggestion::Params::Create(*args_));
    301   EXTENSION_FUNCTION_VALIDATE(params);
    302 
    303   if (SetOmniboxDefaultSuggestion(
    304           GetProfile(), extension_id(), params->suggestion)) {
    305     content::NotificationService::current()->Notify(
    306         chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
    307         content::Source<Profile>(GetProfile()->GetOriginalProfile()),
    308         content::NotificationService::NoDetails());
    309   }
    310 
    311   return true;
    312 }
    313 
    314 // This function converts style information populated by the JSON schema
    315 // compiler into an ACMatchClassifications object.
    316 ACMatchClassifications StyleTypesToACMatchClassifications(
    317     const omnibox::SuggestResult &suggestion) {
    318   ACMatchClassifications match_classifications;
    319   if (suggestion.description_styles) {
    320     base::string16 description = UTF8ToUTF16(suggestion.description);
    321     std::vector<int> styles(description.length(), 0);
    322 
    323     for (std::vector<linked_ptr<omnibox::SuggestResult::DescriptionStylesType> >
    324          ::iterator i = suggestion.description_styles->begin();
    325          i != suggestion.description_styles->end(); ++i) {
    326       omnibox::SuggestResult::DescriptionStylesType* style = i->get();
    327 
    328       int length = description.length();
    329       if (style->length)
    330         length = *style->length;
    331 
    332       size_t offset = style->offset >= 0 ? style->offset :
    333           std::max(0, static_cast<int>(description.length()) + style->offset);
    334 
    335       int type_class;
    336       switch (style->type) {
    337         case omnibox::SuggestResult::DescriptionStylesType::TYPE_URL:
    338           type_class = AutocompleteMatch::ACMatchClassification::URL;
    339           break;
    340         case omnibox::SuggestResult::DescriptionStylesType::TYPE_MATCH:
    341           type_class = AutocompleteMatch::ACMatchClassification::MATCH;
    342           break;
    343         case omnibox::SuggestResult::DescriptionStylesType::TYPE_DIM:
    344           type_class = AutocompleteMatch::ACMatchClassification::DIM;
    345           break;
    346         default:
    347           type_class = AutocompleteMatch::ACMatchClassification::NONE;
    348           return match_classifications;
    349       }
    350 
    351       for (size_t j = offset; j < offset + length && j < styles.size(); ++j)
    352         styles[j] |= type_class;
    353     }
    354 
    355     for (size_t i = 0; i < styles.size(); ++i) {
    356       if (i == 0 || styles[i] != styles[i-1])
    357         match_classifications.push_back(
    358             ACMatchClassification(i, styles[i]));
    359     }
    360   } else {
    361     match_classifications.push_back(
    362         ACMatchClassification(0, ACMatchClassification::NONE));
    363   }
    364 
    365   return match_classifications;
    366 }
    367 
    368 void ApplyDefaultSuggestionForExtensionKeyword(
    369     Profile* profile,
    370     const TemplateURL* keyword,
    371     const base::string16& remaining_input,
    372     AutocompleteMatch* match) {
    373   DCHECK(keyword->GetType() == TemplateURL::OMNIBOX_API_EXTENSION);
    374 
    375   scoped_ptr<omnibox::SuggestResult> suggestion(
    376       GetOmniboxDefaultSuggestion(profile, keyword->GetExtensionId()));
    377   if (!suggestion || suggestion->description.empty())
    378     return;  // fall back to the universal default
    379 
    380   const base::string16 kPlaceholderText(ASCIIToUTF16("%s"));
    381   const base::string16 kReplacementText(ASCIIToUTF16("<input>"));
    382 
    383   base::string16 description = UTF8ToUTF16(suggestion->description);
    384   ACMatchClassifications& description_styles = match->contents_class;
    385   description_styles = StyleTypesToACMatchClassifications(*suggestion);
    386 
    387   // Replace "%s" with the user's input and adjust the style offsets to the
    388   // new length of the description.
    389   size_t placeholder(description.find(kPlaceholderText, 0));
    390   if (placeholder != base::string16::npos) {
    391     base::string16 replacement =
    392         remaining_input.empty() ? kReplacementText : remaining_input;
    393     description.replace(placeholder, kPlaceholderText.length(), replacement);
    394 
    395     for (size_t i = 0; i < description_styles.size(); ++i) {
    396       if (description_styles[i].offset > placeholder)
    397         description_styles[i].offset += replacement.length() - 2;
    398     }
    399   }
    400 
    401   match->contents.assign(description);
    402 }
    403 
    404 }  // namespace extensions
    405