• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetPropertyAccessor()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.
GetDefaultSuggestionForExtension(Profile * profile,const std::string & extension_id)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
OnInputStarted(Profile * profile,const std::string & extension_id)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
OnInputChanged(Profile * profile,const std::string & extension_id,const std::string & input,int suggest_id)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
OnInputEntered(Profile * profile,const std::string & extension_id,const std::string & input)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
OnInputCancelled(Profile * profile,const std::string & extension_id)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 
RunImpl()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 
RunImpl()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 
ExtensionOmniboxSuggestion()178 ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {}
179 
~ExtensionOmniboxSuggestion()180 ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {}
181 
ReadStylesFromValue(const ListValue & styles_value)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 
ExtensionOmniboxSuggestions()228 ExtensionOmniboxSuggestions::ExtensionOmniboxSuggestions() : request_id(0) {}
229 
~ExtensionOmniboxSuggestions()230 ExtensionOmniboxSuggestions::~ExtensionOmniboxSuggestions() {}
231 
ApplyDefaultSuggestionForExtensionKeyword(Profile * profile,const TemplateURL * keyword,const string16 & remaining_input,AutocompleteMatch * match)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