Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2011 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/extension_omnibox_api.h"
      6 
      7 #include "base/json/json_writer.h"
      8 #include "base/lazy_instance.h"
      9 #include "base/string_util.h"
     10 #include "base/utf_string_conversions.h"
     11 #include "base/values.h"
     12 #include "chrome/browser/extensions/extension_event_router.h"
     13 #include "chrome/browser/extensions/extension_service.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/browser/search_engines/template_url.h"
     16 #include "content/common/notification_service.h"
     17 
     18 namespace events {
     19 const char kOnInputStarted[] = "omnibox.onInputStarted";
     20 const char kOnInputChanged[] = "omnibox.onInputChanged";
     21 const char kOnInputEntered[] = "omnibox.onInputEntered";
     22 const char kOnInputCancelled[] = "omnibox.onInputCancelled";
     23 };  // namespace events
     24 
     25 namespace {
     26 const char kDescriptionStylesOrderError[] =
     27     "Suggestion descriptionStyles must be in increasing non-overlapping order.";
     28 const char kDescriptionStylesLengthError[] =
     29     "Suggestion descriptionStyles contains an offset longer than the"
     30     " description text";
     31 
     32 const char kSuggestionContent[] = "content";
     33 const char kSuggestionDescription[] = "description";
     34 const char kSuggestionDescriptionStyles[] = "descriptionStyles";
     35 const char kDescriptionStylesType[] = "type";
     36 const char kDescriptionStylesOffset[] = "offset";
     37 const char kDescriptionStylesLength[] = "length";
     38 
     39 static base::LazyInstance<PropertyAccessor<ExtensionOmniboxSuggestion> >
     40     g_extension_omnibox_suggestion_property_accessor(base::LINKER_INITIALIZED);
     41 
     42 PropertyAccessor<ExtensionOmniboxSuggestion>& GetPropertyAccessor() {
     43   return g_extension_omnibox_suggestion_property_accessor.Get();
     44 }
     45 
     46 // Returns the suggestion object set by the extension via the
     47 // omnibox.setDefaultSuggestion call, or NULL if it was never set.
     48 const ExtensionOmniboxSuggestion* GetDefaultSuggestionForExtension(
     49     Profile* profile, const std::string& extension_id) {
     50   const Extension* extension =
     51       profile->GetExtensionService()->GetExtensionById(extension_id, false);
     52   if (!extension)
     53     return NULL;
     54   return GetPropertyAccessor().GetProperty(
     55       profile->GetExtensionService()->GetPropertyBag(extension));
     56 }
     57 
     58 };  // namespace
     59 
     60 // static
     61 void ExtensionOmniboxEventRouter::OnInputStarted(
     62     Profile* profile, const std::string& extension_id) {
     63   profile->GetExtensionEventRouter()->DispatchEventToExtension(
     64       extension_id, events::kOnInputStarted, "[]", profile, GURL());
     65 }
     66 
     67 // static
     68 bool ExtensionOmniboxEventRouter::OnInputChanged(
     69     Profile* profile, const std::string& extension_id,
     70     const std::string& input, int suggest_id) {
     71   if (!profile->GetExtensionEventRouter()->ExtensionHasEventListener(
     72         extension_id, events::kOnInputChanged))
     73     return false;
     74 
     75   ListValue args;
     76   args.Set(0, Value::CreateStringValue(input));
     77   args.Set(1, Value::CreateIntegerValue(suggest_id));
     78   std::string json_args;
     79   base::JSONWriter::Write(&args, false, &json_args);
     80 
     81   profile->GetExtensionEventRouter()->DispatchEventToExtension(
     82       extension_id, events::kOnInputChanged, json_args, profile, GURL());
     83   return true;
     84 }
     85 
     86 // static
     87 void ExtensionOmniboxEventRouter::OnInputEntered(
     88     Profile* profile, const std::string& extension_id,
     89     const std::string& input) {
     90   ListValue args;
     91   args.Set(0, Value::CreateStringValue(input));
     92   std::string json_args;
     93   base::JSONWriter::Write(&args, false, &json_args);
     94 
     95   profile->GetExtensionEventRouter()->DispatchEventToExtension(
     96       extension_id, events::kOnInputEntered, json_args, profile, GURL());
     97 
     98   NotificationService::current()->Notify(
     99       NotificationType::EXTENSION_OMNIBOX_INPUT_ENTERED,
    100       Source<Profile>(profile), NotificationService::NoDetails());
    101 }
    102 
    103 // static
    104 void ExtensionOmniboxEventRouter::OnInputCancelled(
    105     Profile* profile, const std::string& extension_id) {
    106   profile->GetExtensionEventRouter()->DispatchEventToExtension(
    107       extension_id, events::kOnInputCancelled, "[]", profile, GURL());
    108 }
    109 
    110 bool OmniboxSendSuggestionsFunction::RunImpl() {
    111   ExtensionOmniboxSuggestions suggestions;
    112   ListValue* suggestions_value;
    113   EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &suggestions.request_id));
    114   EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &suggestions_value));
    115 
    116   suggestions.suggestions.resize(suggestions_value->GetSize());
    117   for (size_t i = 0; i < suggestions_value->GetSize(); ++i) {
    118     ExtensionOmniboxSuggestion& suggestion = suggestions.suggestions[i];
    119     DictionaryValue* suggestion_value;
    120     EXTENSION_FUNCTION_VALIDATE(suggestions_value->GetDictionary(
    121         i, &suggestion_value));
    122     EXTENSION_FUNCTION_VALIDATE(suggestion_value->GetString(
    123         kSuggestionContent, &suggestion.content));
    124     EXTENSION_FUNCTION_VALIDATE(suggestion_value->GetString(
    125         kSuggestionDescription, &suggestion.description));
    126 
    127     if (suggestion_value->HasKey(kSuggestionDescriptionStyles)) {
    128       ListValue* styles;
    129       EXTENSION_FUNCTION_VALIDATE(
    130           suggestion_value->GetList(kSuggestionDescriptionStyles, &styles));
    131       EXTENSION_FUNCTION_VALIDATE(suggestion.ReadStylesFromValue(*styles));
    132     } else {
    133       suggestion.description_styles.clear();
    134       suggestion.description_styles.push_back(
    135           ACMatchClassification(0, ACMatchClassification::NONE));
    136     }
    137   }
    138 
    139   NotificationService::current()->Notify(
    140       NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY,
    141       Source<Profile>(profile_),
    142       Details<ExtensionOmniboxSuggestions>(&suggestions));
    143 
    144   return true;
    145 }
    146 
    147 bool OmniboxSetDefaultSuggestionFunction::RunImpl() {
    148   ExtensionOmniboxSuggestion suggestion;
    149   DictionaryValue* suggestion_value;
    150   EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &suggestion_value));
    151   EXTENSION_FUNCTION_VALIDATE(suggestion_value->GetString(
    152       kSuggestionDescription, &suggestion.description));
    153 
    154   if (suggestion_value->HasKey(kSuggestionDescriptionStyles)) {
    155     ListValue* styles;
    156     EXTENSION_FUNCTION_VALIDATE(
    157         suggestion_value->GetList(kSuggestionDescriptionStyles, &styles));
    158     EXTENSION_FUNCTION_VALIDATE(suggestion.ReadStylesFromValue(*styles));
    159   } else {
    160     suggestion.description_styles.clear();
    161     suggestion.description_styles.push_back(
    162         ACMatchClassification(0, ACMatchClassification::NONE));
    163   }
    164 
    165   // Store the suggestion in the extension's runtime data.
    166   GetPropertyAccessor().SetProperty(
    167       profile_->GetExtensionService()->GetPropertyBag(GetExtension()),
    168       suggestion);
    169 
    170   NotificationService::current()->Notify(
    171       NotificationType::EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
    172       Source<Profile>(profile_),
    173       NotificationService::NoDetails());
    174 
    175   return true;
    176 }
    177 
    178 ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {}
    179 
    180 ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {}
    181 
    182 bool ExtensionOmniboxSuggestion::ReadStylesFromValue(
    183     const ListValue& styles_value) {
    184   description_styles.clear();
    185 
    186   // Step 1: Build a vector of styles, 1 per character of description text.
    187   std::vector<int> styles;
    188   styles.resize(description.length());  // sets all styles to 0
    189 
    190   for (size_t i = 0; i < styles_value.GetSize(); ++i) {
    191     DictionaryValue* style;
    192     std::string type;
    193     int offset;
    194     int length;
    195     if (!styles_value.GetDictionary(i, &style))
    196       return false;
    197     if (!style->GetString(kDescriptionStylesType, &type))
    198       return false;
    199     if (!style->GetInteger(kDescriptionStylesOffset, &offset))
    200       return false;
    201     if (!style->GetInteger(kDescriptionStylesLength, &length) || length < 0)
    202       length = description.length();
    203 
    204     if (offset < 0)
    205       offset = std::max(0, static_cast<int>(description.length()) + offset);
    206 
    207     int type_class =
    208         (type == "url") ? ACMatchClassification::URL :
    209         (type == "match") ? ACMatchClassification::MATCH :
    210         (type == "dim") ? ACMatchClassification::DIM : -1;
    211     if (type_class == -1)
    212       return false;
    213 
    214     for (int j = offset;
    215          j < offset + length && j < static_cast<int>(styles.size()); ++j)
    216       styles[j] |= type_class;
    217   }
    218 
    219   // Step 2: Convert the vector into continuous runs of common styles.
    220   for (size_t i = 0; i < styles.size(); ++i) {
    221     if (i == 0 || styles[i] != styles[i-1])
    222       description_styles.push_back(ACMatchClassification(i, styles[i]));
    223   }
    224 
    225   return true;
    226 }
    227 
    228 ExtensionOmniboxSuggestions::ExtensionOmniboxSuggestions() : request_id(0) {}
    229 
    230 ExtensionOmniboxSuggestions::~ExtensionOmniboxSuggestions() {}
    231 
    232 void ApplyDefaultSuggestionForExtensionKeyword(
    233     Profile* profile,
    234     const TemplateURL* keyword,
    235     const string16& remaining_input,
    236     AutocompleteMatch* match) {
    237   DCHECK(keyword->IsExtensionKeyword());
    238   const ExtensionOmniboxSuggestion* suggestion =
    239       GetDefaultSuggestionForExtension(profile, keyword->GetExtensionId());
    240   if (!suggestion)
    241     return;  // fall back to the universal default
    242 
    243   const string16 kPlaceholderText(ASCIIToUTF16("%s"));
    244   const string16 kReplacementText(ASCIIToUTF16("<input>"));
    245 
    246   string16 description = suggestion->description;
    247   ACMatchClassifications& description_styles = match->contents_class;
    248   description_styles = suggestion->description_styles;
    249 
    250   // Replace "%s" with the user's input and adjust the style offsets to the
    251   // new length of the description.
    252   size_t placeholder(suggestion->description.find(kPlaceholderText, 0));
    253   if (placeholder != string16::npos) {
    254     string16 replacement =
    255         remaining_input.empty() ? kReplacementText : remaining_input;
    256     description.replace(placeholder, kPlaceholderText.length(), replacement);
    257 
    258     for (size_t i = 0; i < description_styles.size(); ++i) {
    259       if (description_styles[i].offset > placeholder)
    260         description_styles[i].offset += replacement.length() - 2;
    261     }
    262   }
    263 
    264   match->contents.assign(description);
    265 }
    266