Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/api/omnibox/omnibox_api.h" | 5 #include "chrome/browser/extensions/api/omnibox/omnibox_api.h" |
| 6 | 6 |
| 7 #include "base/json/json_writer.h" | 7 #include "base/json/json_writer.h" |
| 8 #include "base/lazy_instance.h" | 8 #include "base/lazy_instance.h" |
| 9 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
| 10 #include "base/string16.h" | 10 #include "base/string16.h" |
| 11 #include "base/utf_string_conversions.h" | 11 #include "base/utf_string_conversions.h" |
| 12 #include "base/values.h" | 12 #include "base/values.h" |
| 13 #include "chrome/browser/extensions/event_router.h" | 13 #include "chrome/browser/extensions/event_router.h" |
| 14 #include "chrome/browser/extensions/extension_prefs.h" | 14 #include "chrome/browser/extensions/extension_prefs.h" |
| 15 #include "chrome/browser/extensions/extension_service.h" | 15 #include "chrome/browser/extensions/extension_service.h" |
| 16 #include "chrome/browser/extensions/extension_system.h" | 16 #include "chrome/browser/extensions/extension_system.h" |
| 17 #include "chrome/browser/extensions/tab_helper.h" | 17 #include "chrome/browser/extensions/tab_helper.h" |
| 18 #include "chrome/browser/profiles/profile.h" | 18 #include "chrome/browser/profiles/profile.h" |
| 19 #include "chrome/browser/search_engines/template_url.h" | 19 #include "chrome/browser/search_engines/template_url.h" |
| 20 #include "chrome/browser/search_engines/template_url_service.h" | 20 #include "chrome/browser/search_engines/template_url_service.h" |
| 21 #include "chrome/browser/search_engines/template_url_service_factory.h" | 21 #include "chrome/browser/search_engines/template_url_service_factory.h" |
| 22 #include "chrome/common/chrome_notification_types.h" | 22 #include "chrome/common/chrome_notification_types.h" |
| 23 #include "chrome/common/extensions/api/omnibox.h" | |
| 23 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h" | 24 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h" |
| 24 #include "chrome/common/extensions/extension.h" | 25 #include "chrome/common/extensions/extension.h" |
| 25 #include "content/public/browser/notification_details.h" | 26 #include "content/public/browser/notification_details.h" |
| 26 #include "content/public/browser/notification_service.h" | 27 #include "content/public/browser/notification_service.h" |
| 27 #include "ui/gfx/image/image.h" | 28 #include "ui/gfx/image/image.h" |
| 28 | 29 |
| 29 namespace events { | 30 namespace events { |
| 30 const char kOnInputStarted[] = "omnibox.onInputStarted"; | 31 const char kOnInputStarted[] = "omnibox.onInputStarted"; |
| 31 const char kOnInputChanged[] = "omnibox.onInputChanged"; | 32 const char kOnInputChanged[] = "omnibox.onInputChanged"; |
| 32 const char kOnInputEntered[] = "omnibox.onInputEntered"; | 33 const char kOnInputEntered[] = "omnibox.onInputEntered"; |
| 33 const char kOnInputCancelled[] = "omnibox.onInputCancelled"; | 34 const char kOnInputCancelled[] = "omnibox.onInputCancelled"; |
| 34 } // namespace events | 35 } // namespace events |
| 35 | 36 |
| 36 namespace extensions { | 37 namespace extensions { |
| 37 | 38 |
| 39 namespace omnibox = api::omnibox; | |
| 40 namespace SendSuggestions = omnibox::SendSuggestions; | |
| 41 namespace SetDefaultSuggestion = omnibox::SetDefaultSuggestion; | |
| 42 | |
| 38 namespace { | 43 namespace { |
| 39 | 44 |
| 40 const char kSuggestionContent[] = "content"; | 45 const char kSuggestionContent[] = "content"; |
| 41 const char kSuggestionDescription[] = "description"; | 46 const char kSuggestionDescription[] = "description"; |
| 42 const char kSuggestionDescriptionStyles[] = "descriptionStyles"; | 47 const char kSuggestionDescriptionStyles[] = "descriptionStyles"; |
| 43 const char kSuggestionDescriptionStylesRaw[] = "descriptionStylesRaw"; | 48 const char kSuggestionDescriptionStylesRaw[] = "descriptionStylesRaw"; |
| 44 const char kDescriptionStylesType[] = "type"; | 49 const char kDescriptionStylesType[] = "type"; |
| 45 const char kDescriptionStylesOffset[] = "offset"; | 50 const char kDescriptionStylesOffset[] = "offset"; |
| 46 const char kDescriptionStylesLength[] = "length"; | 51 const char kDescriptionStylesLength[] = "length"; |
| 47 | 52 |
| (...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 219 omnibox_popup_icon_manager_.GetIcon(extension_id)); | 224 omnibox_popup_icon_manager_.GetIcon(extension_id)); |
| 220 } | 225 } |
| 221 | 226 |
| 222 template <> | 227 template <> |
| 223 void ProfileKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() { | 228 void ProfileKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() { |
| 224 DependsOn(ExtensionSystemFactory::GetInstance()); | 229 DependsOn(ExtensionSystemFactory::GetInstance()); |
| 225 DependsOn(TemplateURLServiceFactory::GetInstance()); | 230 DependsOn(TemplateURLServiceFactory::GetInstance()); |
| 226 } | 231 } |
| 227 | 232 |
| 228 bool OmniboxSendSuggestionsFunction::RunImpl() { | 233 bool OmniboxSendSuggestionsFunction::RunImpl() { |
| 229 ExtensionOmniboxSuggestions suggestions; | 234 scoped_ptr<SendSuggestions::Params> params( |
| 230 ListValue* suggestions_value; | 235 SendSuggestions::Params::Create(*args_)); |
| 231 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &suggestions.request_id)); | 236 EXTENSION_FUNCTION_VALIDATE(params); |
| 232 EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &suggestions_value)); | |
| 233 | |
| 234 suggestions.suggestions.resize(suggestions_value->GetSize()); | |
| 235 for (size_t i = 0; i < suggestions_value->GetSize(); ++i) { | |
| 236 ExtensionOmniboxSuggestion& suggestion = suggestions.suggestions[i]; | |
| 237 DictionaryValue* suggestion_value; | |
| 238 EXTENSION_FUNCTION_VALIDATE(suggestions_value->GetDictionary( | |
| 239 i, &suggestion_value)); | |
| 240 EXTENSION_FUNCTION_VALIDATE(suggestion.Populate(*suggestion_value, true)); | |
| 241 } | |
| 242 | 237 |
| 243 content::NotificationService::current()->Notify( | 238 content::NotificationService::current()->Notify( |
| 244 chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY, | 239 chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY, |
| 245 content::Source<Profile>(profile_->GetOriginalProfile()), | 240 content::Source<Profile>(profile_->GetOriginalProfile()), |
| 246 content::Details<ExtensionOmniboxSuggestions>(&suggestions)); | 241 content::Details<SendSuggestions::Params>(params.get())); |
| 247 | 242 |
| 248 return true; | 243 return true; |
| 249 } | 244 } |
| 250 | 245 |
| 251 bool OmniboxSetDefaultSuggestionFunction::RunImpl() { | 246 bool OmniboxSetDefaultSuggestionFunction::RunImpl() { |
| 252 ExtensionOmniboxSuggestion suggestion; | 247 scoped_ptr<SetDefaultSuggestion::Params> params( |
| 253 DictionaryValue* suggestion_value; | 248 SetDefaultSuggestion::Params::Create(*args_)); |
| 254 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &suggestion_value)); | 249 EXTENSION_FUNCTION_VALIDATE(params); |
| 255 EXTENSION_FUNCTION_VALIDATE(suggestion.Populate(*suggestion_value, false)); | |
| 256 | 250 |
| 257 ExtensionPrefs* prefs = | 251 ExtensionPrefs* prefs = |
| 258 ExtensionSystem::Get(profile())->extension_service()->extension_prefs(); | 252 ExtensionSystem::Get(profile())->extension_service()->extension_prefs(); |
| 259 if (prefs) | 253 if (prefs) |
| 260 prefs->SetOmniboxDefaultSuggestion(extension_id(), suggestion); | 254 prefs->SetOmniboxDefaultSuggestion(extension_id(), params->suggestion); |
| 261 | 255 |
| 262 content::NotificationService::current()->Notify( | 256 content::NotificationService::current()->Notify( |
| 263 chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED, | 257 chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED, |
| 264 content::Source<Profile>(profile_->GetOriginalProfile()), | 258 content::Source<Profile>(profile_->GetOriginalProfile()), |
| 265 content::NotificationService::NoDetails()); | 259 content::NotificationService::NoDetails()); |
| 266 | 260 |
| 267 return true; | 261 return true; |
| 268 } | 262 } |
| 269 | 263 |
| 270 ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {} | 264 // This function converts style information populated by the JSON schema |
| 265 // compiler into an ACMatchClassifications object. | |
| 266 ACMatchClassifications StyleTypesToACMatchClassifications( | |
| 267 const omnibox::SuggestResult &suggestion) { | |
| 268 ACMatchClassifications match_classifications; | |
| 269 if (suggestion.description_styles) { | |
| 270 string16 description = UTF8ToUTF16(suggestion.description); | |
| 271 std::vector<int> styles(description.length(), 0); | |
| 271 | 272 |
| 272 ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {} | 273 for (std::vector<linked_ptr<omnibox::SuggestResult::DescriptionStylesType> > |
| 274 ::iterator i = suggestion.description_styles->begin(); | |
| 275 i != suggestion.description_styles->end(); ++i) { | |
| 276 omnibox::SuggestResult::DescriptionStylesType* itr = i->get(); | |
|
not at google - send to devlin
2013/03/22 23:30:32
"itr" could have a nicer name. How about just "sty
Aaron Jacobs
2013/03/22 23:56:40
Done.
| |
| 273 | 277 |
| 274 bool ExtensionOmniboxSuggestion::Populate(const base::DictionaryValue& value, | 278 int length; |
| 275 bool require_content) { | 279 if (itr && itr->length) |
|
not at google - send to devlin
2013/03/22 23:30:32
doesn't seem possible for itr to be null here.
Aaron Jacobs
2013/03/22 23:56:40
Done.
| |
| 276 if (!value.GetString(kSuggestionContent, &content) && require_content) | 280 length = *itr->length; |
| 277 return false; | 281 if (length < 0) |
|
not at google - send to devlin
2013/03/22 23:30:32
this seems buggy, since length may not have been i
Aaron Jacobs
2013/03/22 23:56:40
Done.
| |
| 282 length = description.length(); | |
| 278 | 283 |
| 279 if (!value.GetString(kSuggestionDescription, &description)) | 284 size_t offset = itr->offset >= 0 ? itr->offset : |
| 280 return false; | 285 std::max(0, static_cast<int>(description.length()) + itr->offset); |
|
not at google - send to devlin
2013/03/22 23:30:32
i'm surprised that you need the static_cast<int> h
| |
| 281 | 286 |
| 282 description_styles.clear(); | 287 int type_class; |
| 283 if (value.HasKey(kSuggestionDescriptionStyles)) { | 288 switch (itr->type) { |
| 284 // This version comes from the extension. | 289 case omnibox::SuggestResult::DescriptionStylesType::TYPE_URL: |
| 285 const ListValue* styles = NULL; | 290 type_class = AutocompleteMatch::ACMatchClassification::URL; |
| 286 if (!value.GetList(kSuggestionDescriptionStyles, &styles) || | 291 break; |
| 287 !ReadStylesFromValue(*styles)) { | 292 case omnibox::SuggestResult::DescriptionStylesType::TYPE_MATCH: |
| 288 return false; | 293 type_class = AutocompleteMatch::ACMatchClassification::MATCH; |
| 294 break; | |
| 295 case omnibox::SuggestResult::DescriptionStylesType::TYPE_DIM: | |
| 296 type_class = AutocompleteMatch::ACMatchClassification::DIM; | |
| 297 break; | |
| 298 default: | |
| 299 type_class = AutocompleteMatch::ACMatchClassification::NONE; | |
| 300 return match_classifications; | |
| 301 } | |
| 302 | |
| 303 for (size_t j = offset; j < offset + length && j < styles.size(); ++j) | |
| 304 styles[j] |= type_class; | |
| 289 } | 305 } |
| 290 } else if (value.HasKey(kSuggestionDescriptionStylesRaw)) { | 306 |
| 291 // This version comes from ToValue(), which we use to persist to disk. | 307 for (size_t i = 0; i < styles.size(); ++i) { |
| 292 const ListValue* styles = NULL; | 308 if (i == 0 || styles[i] != styles[i-1]) |
| 293 if (!value.GetList(kSuggestionDescriptionStylesRaw, &styles) || | 309 match_classifications.push_back( |
| 294 styles->empty()) { | 310 ACMatchClassification(i, styles[i])); |
| 295 return false; | |
| 296 } | |
| 297 for (size_t i = 0; i < styles->GetSize(); ++i) { | |
| 298 const base::DictionaryValue* style = NULL; | |
| 299 int offset, type; | |
| 300 if (!styles->GetDictionary(i, &style)) | |
| 301 return false; | |
| 302 if (!style->GetInteger(kDescriptionStylesType, &type)) | |
| 303 return false; | |
| 304 if (!style->GetInteger(kDescriptionStylesOffset, &offset)) | |
| 305 return false; | |
| 306 description_styles.push_back(ACMatchClassification(offset, type)); | |
| 307 } | 311 } |
| 308 } else { | 312 } else { |
| 309 description_styles.push_back( | 313 match_classifications.push_back( |
| 310 ACMatchClassification(0, ACMatchClassification::NONE)); | 314 ACMatchClassification(0, ACMatchClassification::NONE)); |
| 311 } | 315 } |
| 312 | 316 |
| 313 return true; | 317 return match_classifications; |
| 314 } | 318 } |
| 315 | 319 |
| 316 bool ExtensionOmniboxSuggestion::ReadStylesFromValue( | |
| 317 const ListValue& styles_value) { | |
| 318 description_styles.clear(); | |
| 319 | |
| 320 // Step 1: Build a vector of styles, 1 per character of description text. | |
| 321 std::vector<int> styles; | |
| 322 styles.resize(description.length()); // sets all styles to 0 | |
| 323 | |
| 324 for (size_t i = 0; i < styles_value.GetSize(); ++i) { | |
| 325 const DictionaryValue* style; | |
| 326 std::string type; | |
| 327 int offset; | |
| 328 int length; | |
| 329 if (!styles_value.GetDictionary(i, &style)) | |
| 330 return false; | |
| 331 if (!style->GetString(kDescriptionStylesType, &type)) | |
| 332 return false; | |
| 333 if (!style->GetInteger(kDescriptionStylesOffset, &offset)) | |
| 334 return false; | |
| 335 if (!style->GetInteger(kDescriptionStylesLength, &length) || length < 0) | |
| 336 length = description.length(); | |
| 337 | |
| 338 if (offset < 0) | |
| 339 offset = std::max(0, static_cast<int>(description.length()) + offset); | |
| 340 | |
| 341 int type_class = | |
| 342 (type == "url") ? ACMatchClassification::URL : | |
| 343 (type == "match") ? ACMatchClassification::MATCH : | |
| 344 (type == "dim") ? ACMatchClassification::DIM : -1; | |
| 345 if (type_class == -1) | |
| 346 return false; | |
| 347 | |
| 348 for (int j = offset; | |
| 349 j < offset + length && j < static_cast<int>(styles.size()); ++j) | |
| 350 styles[j] |= type_class; | |
| 351 } | |
| 352 | |
| 353 // Step 2: Convert the vector into continuous runs of common styles. | |
| 354 for (size_t i = 0; i < styles.size(); ++i) { | |
| 355 if (i == 0 || styles[i] != styles[i-1]) | |
| 356 description_styles.push_back(ACMatchClassification(i, styles[i])); | |
| 357 } | |
| 358 | |
| 359 return true; | |
| 360 } | |
| 361 | |
| 362 scoped_ptr<base::DictionaryValue> ExtensionOmniboxSuggestion::ToValue() const { | |
| 363 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue()); | |
| 364 | |
| 365 value->SetString(kSuggestionContent, content); | |
| 366 value->SetString(kSuggestionDescription, description); | |
| 367 | |
| 368 if (description_styles.size() > 0) { | |
| 369 base::ListValue* styles_value = new base::ListValue(); | |
| 370 for (size_t i = 0; i < description_styles.size(); ++i) { | |
| 371 base::DictionaryValue* style = new base::DictionaryValue(); | |
| 372 style->SetInteger(kDescriptionStylesOffset, description_styles[i].offset); | |
| 373 style->SetInteger(kDescriptionStylesType, description_styles[i].style); | |
| 374 styles_value->Append(style); | |
| 375 } | |
| 376 | |
| 377 value->Set(kSuggestionDescriptionStylesRaw, styles_value); | |
| 378 } | |
| 379 | |
| 380 return value.Pass(); | |
| 381 } | |
| 382 | |
| 383 ExtensionOmniboxSuggestions::ExtensionOmniboxSuggestions() : request_id(0) {} | |
| 384 | |
| 385 ExtensionOmniboxSuggestions::~ExtensionOmniboxSuggestions() {} | |
| 386 | |
| 387 void ApplyDefaultSuggestionForExtensionKeyword( | 320 void ApplyDefaultSuggestionForExtensionKeyword( |
| 388 Profile* profile, | 321 Profile* profile, |
| 389 const TemplateURL* keyword, | 322 const TemplateURL* keyword, |
| 390 const string16& remaining_input, | 323 const string16& remaining_input, |
| 391 AutocompleteMatch* match) { | 324 AutocompleteMatch* match) { |
| 392 DCHECK(keyword->IsExtensionKeyword()); | 325 DCHECK(keyword->IsExtensionKeyword()); |
| 393 | 326 |
| 394 ExtensionPrefs* prefs = | 327 ExtensionPrefs* prefs = |
| 395 ExtensionSystem::Get(profile)->extension_service()->extension_prefs(); | 328 ExtensionSystem::Get(profile)->extension_service()->extension_prefs(); |
| 396 if (!prefs) | 329 if (!prefs) |
| 397 return; | 330 return; |
| 398 | 331 |
| 399 ExtensionOmniboxSuggestion suggestion = | 332 scoped_ptr<omnibox::SuggestResult> suggestion( |
| 400 prefs->GetOmniboxDefaultSuggestion(keyword->GetExtensionId()); | 333 prefs->GetOmniboxDefaultSuggestion(keyword->GetExtensionId())); |
| 401 if (suggestion.description.empty()) | 334 if (!suggestion || suggestion->description.empty()) |
| 402 return; // fall back to the universal default | 335 return; // fall back to the universal default |
| 403 | 336 |
| 404 const string16 kPlaceholderText(ASCIIToUTF16("%s")); | 337 const string16 kPlaceholderText(ASCIIToUTF16("%s")); |
| 405 const string16 kReplacementText(ASCIIToUTF16("<input>")); | 338 const string16 kReplacementText(ASCIIToUTF16("<input>")); |
| 406 | 339 |
| 407 string16 description = suggestion.description; | 340 string16 description = UTF8ToUTF16(suggestion->description); |
| 408 ACMatchClassifications& description_styles = match->contents_class; | 341 ACMatchClassifications& description_styles = match->contents_class; |
| 409 description_styles = suggestion.description_styles; | 342 description_styles = StyleTypesToACMatchClassifications(*suggestion); |
| 410 | 343 |
| 411 // Replace "%s" with the user's input and adjust the style offsets to the | 344 // Replace "%s" with the user's input and adjust the style offsets to the |
| 412 // new length of the description. | 345 // new length of the description. |
| 413 size_t placeholder(suggestion.description.find(kPlaceholderText, 0)); | 346 size_t placeholder(description.find(kPlaceholderText, 0)); |
| 414 if (placeholder != string16::npos) { | 347 if (placeholder != string16::npos) { |
| 415 string16 replacement = | 348 string16 replacement = |
| 416 remaining_input.empty() ? kReplacementText : remaining_input; | 349 remaining_input.empty() ? kReplacementText : remaining_input; |
| 417 description.replace(placeholder, kPlaceholderText.length(), replacement); | 350 description.replace(placeholder, kPlaceholderText.length(), replacement); |
| 418 | 351 |
| 419 for (size_t i = 0; i < description_styles.size(); ++i) { | 352 for (size_t i = 0; i < description_styles.size(); ++i) { |
| 420 if (description_styles[i].offset > placeholder) | 353 if (description_styles[i].offset > placeholder) |
| 421 description_styles[i].offset += replacement.length() - 2; | 354 description_styles[i].offset += replacement.length() - 2; |
| 422 } | 355 } |
| 423 } | 356 } |
| 424 | 357 |
| 425 match->contents.assign(description); | 358 match->contents.assign(description); |
| 426 } | 359 } |
| 427 | 360 |
| 428 } // namespace extensions | 361 } // namespace extensions |
| OLD | NEW |