extension_omnibox_api.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2010 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 "chrome/common/notification_service.h"
17
18namespace events {
19const char kOnInputStarted[] = "omnibox.onInputStarted";
20const char kOnInputChanged[] = "omnibox.onInputChanged";
21const char kOnInputEntered[] = "omnibox.onInputEntered";
22const char kOnInputCancelled[] = "omnibox.onInputCancelled";
23};  // namespace events
24
25namespace {
26const char kDescriptionStylesOrderError[] =
27    "Suggestion descriptionStyles must be in increasing non-overlapping order.";
28const char kDescriptionStylesLengthError[] =
29    "Suggestion descriptionStyles contains an offset longer than the"
30    " description text";
31
32const char kSuggestionContent[] = "content";
33const char kSuggestionDescription[] = "description";
34const char kSuggestionDescriptionStyles[] = "descriptionStyles";
35const char kDescriptionStylesType[] = "type";
36const char kDescriptionStylesOffset[] = "offset";
37const char kDescriptionStylesLength[] = "length";
38
39static base::LazyInstance<PropertyAccessor<ExtensionOmniboxSuggestion> >
40    g_extension_omnibox_suggestion_property_accessor(base::LINKER_INITIALIZED);
41
42PropertyAccessor<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.
48const 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
61void 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
68bool 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
87void 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
104void ExtensionOmniboxEventRouter::OnInputCancelled(
105    Profile* profile, const std::string& extension_id) {
106  profile->GetExtensionEventRouter()->DispatchEventToExtension(
107      extension_id, events::kOnInputCancelled, "[]", profile, GURL());
108}
109
110bool 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
147bool 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
178ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {}
179
180ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {}
181
182bool 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
228ExtensionOmniboxSuggestions::ExtensionOmniboxSuggestions() : request_id(0) {}
229
230ExtensionOmniboxSuggestions::~ExtensionOmniboxSuggestions() {}
231
232void 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