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