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