| 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 const char kCurrentTabDisposition[] = "currentTab"; | 52 const char kCurrentTabDisposition[] = "currentTab"; |
| (...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 229 omnibox_popup_icon_manager_.GetIcon(extension_id)); | 234 omnibox_popup_icon_manager_.GetIcon(extension_id)); |
| 230 } | 235 } |
| 231 | 236 |
| 232 template <> | 237 template <> |
| 233 void ProfileKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() { | 238 void ProfileKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() { |
| 234 DependsOn(ExtensionSystemFactory::GetInstance()); | 239 DependsOn(ExtensionSystemFactory::GetInstance()); |
| 235 DependsOn(TemplateURLServiceFactory::GetInstance()); | 240 DependsOn(TemplateURLServiceFactory::GetInstance()); |
| 236 } | 241 } |
| 237 | 242 |
| 238 bool OmniboxSendSuggestionsFunction::RunImpl() { | 243 bool OmniboxSendSuggestionsFunction::RunImpl() { |
| 239 ExtensionOmniboxSuggestions suggestions; | 244 scoped_ptr<SendSuggestions::Params> params( |
| 240 ListValue* suggestions_value; | 245 SendSuggestions::Params::Create(*args_)); |
| 241 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &suggestions.request_id)); | 246 EXTENSION_FUNCTION_VALIDATE(params); |
| 242 EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &suggestions_value)); | |
| 243 | |
| 244 suggestions.suggestions.resize(suggestions_value->GetSize()); | |
| 245 for (size_t i = 0; i < suggestions_value->GetSize(); ++i) { | |
| 246 ExtensionOmniboxSuggestion& suggestion = suggestions.suggestions[i]; | |
| 247 DictionaryValue* suggestion_value; | |
| 248 EXTENSION_FUNCTION_VALIDATE(suggestions_value->GetDictionary( | |
| 249 i, &suggestion_value)); | |
| 250 EXTENSION_FUNCTION_VALIDATE(suggestion.Populate(*suggestion_value, true)); | |
| 251 } | |
| 252 | 247 |
| 253 content::NotificationService::current()->Notify( | 248 content::NotificationService::current()->Notify( |
| 254 chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY, | 249 chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY, |
| 255 content::Source<Profile>(profile_->GetOriginalProfile()), | 250 content::Source<Profile>(profile_->GetOriginalProfile()), |
| 256 content::Details<ExtensionOmniboxSuggestions>(&suggestions)); | 251 content::Details<SendSuggestions::Params>(params.get())); |
| 257 | 252 |
| 258 return true; | 253 return true; |
| 259 } | 254 } |
| 260 | 255 |
| 261 bool OmniboxSetDefaultSuggestionFunction::RunImpl() { | 256 bool OmniboxSetDefaultSuggestionFunction::RunImpl() { |
| 262 ExtensionOmniboxSuggestion suggestion; | 257 scoped_ptr<SetDefaultSuggestion::Params> params( |
| 263 DictionaryValue* suggestion_value; | 258 SetDefaultSuggestion::Params::Create(*args_)); |
| 264 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &suggestion_value)); | 259 EXTENSION_FUNCTION_VALIDATE(params); |
| 265 EXTENSION_FUNCTION_VALIDATE(suggestion.Populate(*suggestion_value, false)); | |
| 266 | 260 |
| 267 ExtensionPrefs* prefs = | 261 ExtensionPrefs* prefs = |
| 268 ExtensionSystem::Get(profile())->extension_service()->extension_prefs(); | 262 ExtensionSystem::Get(profile())->extension_service()->extension_prefs(); |
| 269 if (prefs) | 263 if (prefs) |
| 270 prefs->SetOmniboxDefaultSuggestion(extension_id(), suggestion); | 264 prefs->SetOmniboxDefaultSuggestion(extension_id(), params->suggestion); |
| 271 | 265 |
| 272 content::NotificationService::current()->Notify( | 266 content::NotificationService::current()->Notify( |
| 273 chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED, | 267 chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED, |
| 274 content::Source<Profile>(profile_->GetOriginalProfile()), | 268 content::Source<Profile>(profile_->GetOriginalProfile()), |
| 275 content::NotificationService::NoDetails()); | 269 content::NotificationService::NoDetails()); |
| 276 | 270 |
| 277 return true; | 271 return true; |
| 278 } | 272 } |
| 279 | 273 |
| 280 ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {} | 274 // This function converts style information populated by the JSON schema |
| 275 // compiler into an ACMatchClassifications object. |
| 276 ACMatchClassifications StyleTypesToACMatchClassifications( |
| 277 const omnibox::SuggestResult &suggestion) { |
| 278 ACMatchClassifications match_classifications; |
| 279 if (suggestion.description_styles) { |
| 280 string16 description = UTF8ToUTF16(suggestion.description); |
| 281 std::vector<int> styles(description.length(), 0); |
| 281 | 282 |
| 282 ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {} | 283 for (std::vector<linked_ptr<omnibox::SuggestResult::DescriptionStylesType> > |
| 284 ::iterator i = suggestion.description_styles->begin(); |
| 285 i != suggestion.description_styles->end(); ++i) { |
| 286 omnibox::SuggestResult::DescriptionStylesType* style = i->get(); |
| 283 | 287 |
| 284 bool ExtensionOmniboxSuggestion::Populate(const base::DictionaryValue& value, | 288 int length = description.length(); |
| 285 bool require_content) { | 289 if (style->length) |
| 286 if (!value.GetString(kSuggestionContent, &content) && require_content) | 290 length = *style->length; |
| 287 return false; | |
| 288 | 291 |
| 289 if (!value.GetString(kSuggestionDescription, &description)) | 292 size_t offset = style->offset >= 0 ? style->offset : |
| 290 return false; | 293 std::max(0, static_cast<int>(description.length()) + style->offset); |
| 291 | 294 |
| 292 description_styles.clear(); | 295 int type_class; |
| 293 if (value.HasKey(kSuggestionDescriptionStyles)) { | 296 switch (style->type) { |
| 294 // This version comes from the extension. | 297 case omnibox::SuggestResult::DescriptionStylesType::TYPE_URL: |
| 295 const ListValue* styles = NULL; | 298 type_class = AutocompleteMatch::ACMatchClassification::URL; |
| 296 if (!value.GetList(kSuggestionDescriptionStyles, &styles) || | 299 break; |
| 297 !ReadStylesFromValue(*styles)) { | 300 case omnibox::SuggestResult::DescriptionStylesType::TYPE_MATCH: |
| 298 return false; | 301 type_class = AutocompleteMatch::ACMatchClassification::MATCH; |
| 302 break; |
| 303 case omnibox::SuggestResult::DescriptionStylesType::TYPE_DIM: |
| 304 type_class = AutocompleteMatch::ACMatchClassification::DIM; |
| 305 break; |
| 306 default: |
| 307 type_class = AutocompleteMatch::ACMatchClassification::NONE; |
| 308 return match_classifications; |
| 309 } |
| 310 |
| 311 for (size_t j = offset; j < offset + length && j < styles.size(); ++j) |
| 312 styles[j] |= type_class; |
| 299 } | 313 } |
| 300 } else if (value.HasKey(kSuggestionDescriptionStylesRaw)) { | 314 |
| 301 // This version comes from ToValue(), which we use to persist to disk. | 315 for (size_t i = 0; i < styles.size(); ++i) { |
| 302 const ListValue* styles = NULL; | 316 if (i == 0 || styles[i] != styles[i-1]) |
| 303 if (!value.GetList(kSuggestionDescriptionStylesRaw, &styles) || | 317 match_classifications.push_back( |
| 304 styles->empty()) { | 318 ACMatchClassification(i, styles[i])); |
| 305 return false; | |
| 306 } | |
| 307 for (size_t i = 0; i < styles->GetSize(); ++i) { | |
| 308 const base::DictionaryValue* style = NULL; | |
| 309 int offset, type; | |
| 310 if (!styles->GetDictionary(i, &style)) | |
| 311 return false; | |
| 312 if (!style->GetInteger(kDescriptionStylesType, &type)) | |
| 313 return false; | |
| 314 if (!style->GetInteger(kDescriptionStylesOffset, &offset)) | |
| 315 return false; | |
| 316 description_styles.push_back(ACMatchClassification(offset, type)); | |
| 317 } | 319 } |
| 318 } else { | 320 } else { |
| 319 description_styles.push_back( | 321 match_classifications.push_back( |
| 320 ACMatchClassification(0, ACMatchClassification::NONE)); | 322 ACMatchClassification(0, ACMatchClassification::NONE)); |
| 321 } | 323 } |
| 322 | 324 |
| 323 return true; | 325 return match_classifications; |
| 324 } | 326 } |
| 325 | 327 |
| 326 bool ExtensionOmniboxSuggestion::ReadStylesFromValue( | |
| 327 const ListValue& styles_value) { | |
| 328 description_styles.clear(); | |
| 329 | |
| 330 // Step 1: Build a vector of styles, 1 per character of description text. | |
| 331 std::vector<int> styles; | |
| 332 styles.resize(description.length()); // sets all styles to 0 | |
| 333 | |
| 334 for (size_t i = 0; i < styles_value.GetSize(); ++i) { | |
| 335 const DictionaryValue* style; | |
| 336 std::string type; | |
| 337 int offset; | |
| 338 int length; | |
| 339 if (!styles_value.GetDictionary(i, &style)) | |
| 340 return false; | |
| 341 if (!style->GetString(kDescriptionStylesType, &type)) | |
| 342 return false; | |
| 343 if (!style->GetInteger(kDescriptionStylesOffset, &offset)) | |
| 344 return false; | |
| 345 if (!style->GetInteger(kDescriptionStylesLength, &length) || length < 0) | |
| 346 length = description.length(); | |
| 347 | |
| 348 if (offset < 0) | |
| 349 offset = std::max(0, static_cast<int>(description.length()) + offset); | |
| 350 | |
| 351 int type_class = | |
| 352 (type == "url") ? ACMatchClassification::URL : | |
| 353 (type == "match") ? ACMatchClassification::MATCH : | |
| 354 (type == "dim") ? ACMatchClassification::DIM : -1; | |
| 355 if (type_class == -1) | |
| 356 return false; | |
| 357 | |
| 358 for (int j = offset; | |
| 359 j < offset + length && j < static_cast<int>(styles.size()); ++j) | |
| 360 styles[j] |= type_class; | |
| 361 } | |
| 362 | |
| 363 // Step 2: Convert the vector into continuous runs of common styles. | |
| 364 for (size_t i = 0; i < styles.size(); ++i) { | |
| 365 if (i == 0 || styles[i] != styles[i-1]) | |
| 366 description_styles.push_back(ACMatchClassification(i, styles[i])); | |
| 367 } | |
| 368 | |
| 369 return true; | |
| 370 } | |
| 371 | |
| 372 scoped_ptr<base::DictionaryValue> ExtensionOmniboxSuggestion::ToValue() const { | |
| 373 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue()); | |
| 374 | |
| 375 value->SetString(kSuggestionContent, content); | |
| 376 value->SetString(kSuggestionDescription, description); | |
| 377 | |
| 378 if (description_styles.size() > 0) { | |
| 379 base::ListValue* styles_value = new base::ListValue(); | |
| 380 for (size_t i = 0; i < description_styles.size(); ++i) { | |
| 381 base::DictionaryValue* style = new base::DictionaryValue(); | |
| 382 style->SetInteger(kDescriptionStylesOffset, description_styles[i].offset); | |
| 383 style->SetInteger(kDescriptionStylesType, description_styles[i].style); | |
| 384 styles_value->Append(style); | |
| 385 } | |
| 386 | |
| 387 value->Set(kSuggestionDescriptionStylesRaw, styles_value); | |
| 388 } | |
| 389 | |
| 390 return value.Pass(); | |
| 391 } | |
| 392 | |
| 393 ExtensionOmniboxSuggestions::ExtensionOmniboxSuggestions() : request_id(0) {} | |
| 394 | |
| 395 ExtensionOmniboxSuggestions::~ExtensionOmniboxSuggestions() {} | |
| 396 | |
| 397 void ApplyDefaultSuggestionForExtensionKeyword( | 328 void ApplyDefaultSuggestionForExtensionKeyword( |
| 398 Profile* profile, | 329 Profile* profile, |
| 399 const TemplateURL* keyword, | 330 const TemplateURL* keyword, |
| 400 const string16& remaining_input, | 331 const string16& remaining_input, |
| 401 AutocompleteMatch* match) { | 332 AutocompleteMatch* match) { |
| 402 DCHECK(keyword->IsExtensionKeyword()); | 333 DCHECK(keyword->IsExtensionKeyword()); |
| 403 | 334 |
| 404 ExtensionPrefs* prefs = | 335 ExtensionPrefs* prefs = |
| 405 ExtensionSystem::Get(profile)->extension_service()->extension_prefs(); | 336 ExtensionSystem::Get(profile)->extension_service()->extension_prefs(); |
| 406 if (!prefs) | 337 if (!prefs) |
| 407 return; | 338 return; |
| 408 | 339 |
| 409 ExtensionOmniboxSuggestion suggestion = | 340 scoped_ptr<omnibox::SuggestResult> suggestion( |
| 410 prefs->GetOmniboxDefaultSuggestion(keyword->GetExtensionId()); | 341 prefs->GetOmniboxDefaultSuggestion(keyword->GetExtensionId())); |
| 411 if (suggestion.description.empty()) | 342 if (!suggestion || suggestion->description.empty()) |
| 412 return; // fall back to the universal default | 343 return; // fall back to the universal default |
| 413 | 344 |
| 414 const string16 kPlaceholderText(ASCIIToUTF16("%s")); | 345 const string16 kPlaceholderText(ASCIIToUTF16("%s")); |
| 415 const string16 kReplacementText(ASCIIToUTF16("<input>")); | 346 const string16 kReplacementText(ASCIIToUTF16("<input>")); |
| 416 | 347 |
| 417 string16 description = suggestion.description; | 348 string16 description = UTF8ToUTF16(suggestion->description); |
| 418 ACMatchClassifications& description_styles = match->contents_class; | 349 ACMatchClassifications& description_styles = match->contents_class; |
| 419 description_styles = suggestion.description_styles; | 350 description_styles = StyleTypesToACMatchClassifications(*suggestion); |
| 420 | 351 |
| 421 // Replace "%s" with the user's input and adjust the style offsets to the | 352 // Replace "%s" with the user's input and adjust the style offsets to the |
| 422 // new length of the description. | 353 // new length of the description. |
| 423 size_t placeholder(suggestion.description.find(kPlaceholderText, 0)); | 354 size_t placeholder(description.find(kPlaceholderText, 0)); |
| 424 if (placeholder != string16::npos) { | 355 if (placeholder != string16::npos) { |
| 425 string16 replacement = | 356 string16 replacement = |
| 426 remaining_input.empty() ? kReplacementText : remaining_input; | 357 remaining_input.empty() ? kReplacementText : remaining_input; |
| 427 description.replace(placeholder, kPlaceholderText.length(), replacement); | 358 description.replace(placeholder, kPlaceholderText.length(), replacement); |
| 428 | 359 |
| 429 for (size_t i = 0; i < description_styles.size(); ++i) { | 360 for (size_t i = 0; i < description_styles.size(); ++i) { |
| 430 if (description_styles[i].offset > placeholder) | 361 if (description_styles[i].offset > placeholder) |
| 431 description_styles[i].offset += replacement.length() - 2; | 362 description_styles[i].offset += replacement.length() - 2; |
| 432 } | 363 } |
| 433 } | 364 } |
| 434 | 365 |
| 435 match->contents.assign(description); | 366 match->contents.assign(description); |
| 436 } | 367 } |
| 437 | 368 |
| 438 } // namespace extensions | 369 } // namespace extensions |
| OLD | NEW |