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/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