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 = extensions::api::omnibox; | |
|
not at google - send to devlin
2013/03/16 00:04:25
fwiw extensions:: is unnecessary because we're alr
Aaron Jacobs
2013/03/21 21:59:55
Done.
| |
| 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)); | 250 |
| 251 if (!params->suggestion.description_styles) { | |
|
not at google - send to devlin
2013/03/16 00:04:25
I think it would be better to just do params->sugg
Aaron Jacobs
2013/03/21 21:59:55
This section of code was not actually needed, as i
| |
| 252 params->suggestion.description_styles.reset( | |
| 253 new std::vector<linked_ptr< | |
| 254 omnibox::SuggestDefaultResult::DescriptionStylesType> >); | |
|
not at google - send to devlin
2013/03/16 00:04:25
I can't remember what SuggestDefaultResult is, but
Aaron Jacobs
2013/03/21 21:59:55
Done.
| |
| 255 } | |
| 256 params->suggestion.description_styles->clear(); | |
| 256 | 257 |
| 257 ExtensionPrefs* prefs = | 258 ExtensionPrefs* prefs = |
| 258 ExtensionSystem::Get(profile())->extension_service()->extension_prefs(); | 259 ExtensionSystem::Get(profile())->extension_service()->extension_prefs(); |
| 259 if (prefs) | 260 if (prefs) |
| 260 prefs->SetOmniboxDefaultSuggestion(extension_id(), suggestion); | 261 prefs->SetOmniboxDefaultSuggestion(extension_id(), params->suggestion); |
| 261 | 262 |
| 262 content::NotificationService::current()->Notify( | 263 content::NotificationService::current()->Notify( |
| 263 chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED, | 264 chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED, |
| 264 content::Source<Profile>(profile_->GetOriginalProfile()), | 265 content::Source<Profile>(profile_->GetOriginalProfile()), |
| 265 content::NotificationService::NoDetails()); | 266 content::NotificationService::NoDetails()); |
| 266 | 267 |
| 267 return true; | 268 return true; |
| 268 } | 269 } |
| 269 | 270 |
| 270 ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {} | 271 // This function converts style information populated by the JSON schema |
| 272 // compiler into an ACMatchClassifications object. | |
| 273 ACMatchClassifications StyleTypesToACMatchClassifications( | |
| 274 const omnibox::SuggestResult &suggestion) { | |
| 275 ACMatchClassifications match_classifications; | |
| 276 if (suggestion.description_styles) { | |
| 277 const std::vector<linked_ptr< | |
| 278 omnibox::SuggestResult::DescriptionStylesType> > &style_types = | |
| 279 *suggestion.description_styles; | |
|
not at google - send to devlin
2013/03/16 00:04:25
This is pretty unwieldy. Just use an iterator belo
Aaron Jacobs
2013/03/21 21:59:55
Done.
| |
| 280 string16 description = UTF8ToUTF16(suggestion.description); | |
| 281 std::vector<int> styles; | |
| 282 styles.resize(description.length()); // sets all styles to 0 | |
| 271 | 283 |
| 272 ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {} | 284 for (size_t i = 0; i < style_types.size(); ++i) { |
| 285 int type_class, offset, length; | |
| 273 | 286 |
| 274 bool ExtensionOmniboxSuggestion::Populate(const base::DictionaryValue& value, | 287 if (style_types[i].get() && style_types[i]->length) |
|
not at google - send to devlin
2013/03/16 00:04:25
don't need .get(), use !empty()
Aaron Jacobs
2013/03/21 21:59:55
I can't remove .get(), as it is operating on a lin
| |
| 275 bool require_content) { | 288 length = *style_types[i]->length; |
| 276 if (!value.GetString(kSuggestionContent, &content) && require_content) | 289 if (length < 0) |
| 277 return false; | 290 length = description.length(); |
| 278 | 291 |
| 279 if (!value.GetString(kSuggestionDescription, &description)) | 292 offset = style_types[i]->offset; |
| 280 return false; | 293 if (offset < 0) |
| 294 offset = std::max(0, static_cast<int>(description.length()) + offset); | |
| 281 | 295 |
| 282 description_styles.clear(); | 296 if (style_types[i]->type == |
|
not at google - send to devlin
2013/03/16 00:04:25
use switch
Aaron Jacobs
2013/03/21 21:59:55
Done.
| |
| 283 if (value.HasKey(kSuggestionDescriptionStyles)) { | 297 omnibox::SuggestResult::DescriptionStylesType::TYPE_URL) { |
| 284 // This version comes from the extension. | 298 type_class = AutocompleteMatch::ACMatchClassification::URL; |
| 285 const ListValue* styles = NULL; | 299 } else if (style_types[i]->type == |
| 286 if (!value.GetList(kSuggestionDescriptionStyles, &styles) || | 300 omnibox::SuggestResult::DescriptionStylesType::TYPE_MATCH) { |
| 287 !ReadStylesFromValue(*styles)) { | 301 type_class = AutocompleteMatch::ACMatchClassification::MATCH; |
| 288 return false; | 302 } else if (style_types[i]->type == |
| 303 omnibox::SuggestResult::DescriptionStylesType::TYPE_DIM) { | |
| 304 type_class = AutocompleteMatch::ACMatchClassification::DIM; | |
| 305 } else { | |
| 306 type_class = AutocompleteMatch::ACMatchClassification::NONE; | |
| 307 return match_classifications; | |
| 308 } | |
| 309 | |
| 310 for (int j = offset; | |
| 311 j < offset + length && j < static_cast<int>(styles.size()); ++j) | |
| 312 styles[j] |= type_class; | |
| 289 } | 313 } |
| 290 } else if (value.HasKey(kSuggestionDescriptionStylesRaw)) { | 314 |
| 291 // This version comes from ToValue(), which we use to persist to disk. | 315 for (size_t i = 0; i < styles.size(); ++i) { |
| 292 const ListValue* styles = NULL; | 316 if (i ==0 || styles[i] != styles[i-1]) |
| 293 if (!value.GetList(kSuggestionDescriptionStylesRaw, &styles) || | 317 match_classifications.push_back( |
| 294 styles->empty()) { | 318 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 } | 319 } |
| 308 } else { | 320 } else { |
| 309 description_styles.push_back( | 321 match_classifications.push_back( |
| 310 ACMatchClassification(0, ACMatchClassification::NONE)); | 322 ACMatchClassification(0, ACMatchClassification::NONE)); |
| 311 } | 323 } |
| 312 | 324 |
| 313 return true; | 325 return match_classifications; |
| 314 } | 326 } |
| 315 | 327 |
| 316 bool ExtensionOmniboxSuggestion::ReadStylesFromValue( | 328 // This function converts style information populated by the JSON schema |
| 317 const ListValue& styles_value) { | 329 // compiler into an ACMatchClassifications object. |
| 318 description_styles.clear(); | 330 ACMatchClassifications DefaultStyleTypesToACMatchClassifications( |
|
not at google - send to devlin
2013/03/16 00:04:25
what is the difference between this function and t
Aaron Jacobs
2013/03/21 21:59:55
They served pretty much the same purpose, though w
| |
| 331 const omnibox::SuggestDefaultResult &suggestion) { | |
| 332 ACMatchClassifications match_classifications; | |
| 333 if (suggestion.description_styles) { | |
| 334 const std::vector<linked_ptr< | |
| 335 omnibox::SuggestDefaultResult::DescriptionStylesType> > &style_types = | |
| 336 *suggestion.description_styles; | |
| 337 string16 description = UTF8ToUTF16(suggestion.description); | |
| 338 std::vector<int> styles; | |
| 339 styles.resize(description.length()); // sets all styles to 0 | |
| 319 | 340 |
| 320 // Step 1: Build a vector of styles, 1 per character of description text. | 341 for (size_t i = 0; i < style_types.size(); ++i) { |
| 321 std::vector<int> styles; | 342 int type_class, offset, length; |
| 322 styles.resize(description.length()); // sets all styles to 0 | |
| 323 | 343 |
| 324 for (size_t i = 0; i < styles_value.GetSize(); ++i) { | 344 if (style_types[i].get() && style_types[i]->length) |
| 325 const DictionaryValue* style; | 345 length = *style_types[i]->length; |
| 326 std::string type; | 346 if (length < 0) |
| 327 int offset; | 347 length = description.length(); |
| 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 | 348 |
| 338 if (offset < 0) | 349 offset = style_types[i]->offset; |
| 339 offset = std::max(0, static_cast<int>(description.length()) + offset); | 350 if (offset < 0) |
| 351 offset = std::max(0, static_cast<int>(description.length()) + offset); | |
| 340 | 352 |
| 341 int type_class = | 353 if (style_types[i]->type == |
| 342 (type == "url") ? ACMatchClassification::URL : | 354 omnibox::SuggestDefaultResult::DescriptionStylesType::TYPE_URL) { |
| 343 (type == "match") ? ACMatchClassification::MATCH : | 355 type_class = AutocompleteMatch::ACMatchClassification::URL; |
| 344 (type == "dim") ? ACMatchClassification::DIM : -1; | 356 } else if (style_types[i]->type == |
| 345 if (type_class == -1) | 357 omnibox::SuggestDefaultResult::DescriptionStylesType::TYPE_MATCH) { |
| 346 return false; | 358 type_class = AutocompleteMatch::ACMatchClassification::MATCH; |
| 359 } else if (style_types[i]->type == | |
| 360 omnibox::SuggestDefaultResult::DescriptionStylesType::TYPE_DIM) { | |
| 361 type_class = AutocompleteMatch::ACMatchClassification::DIM; | |
| 362 } else { | |
| 363 type_class = AutocompleteMatch::ACMatchClassification::NONE; | |
| 364 return match_classifications; | |
| 365 } | |
| 347 | 366 |
| 348 for (int j = offset; | 367 for (int j = offset; |
| 349 j < offset + length && j < static_cast<int>(styles.size()); ++j) | 368 j < offset + length && j < static_cast<int>(styles.size()); ++j) |
| 350 styles[j] |= type_class; | 369 styles[j] |= type_class; |
| 370 } | |
| 371 | |
| 372 for (size_t i = 0; i < styles.size(); ++i) { | |
| 373 if (i ==0 || styles[i] != styles[i-1]) | |
| 374 match_classifications.push_back( | |
| 375 ACMatchClassification(i, styles[i])); | |
| 376 } | |
| 377 } else { | |
| 378 match_classifications.push_back( | |
| 379 ACMatchClassification(0, ACMatchClassification::NONE)); | |
| 351 } | 380 } |
| 352 | 381 |
| 353 // Step 2: Convert the vector into continuous runs of common styles. | 382 return match_classifications; |
| 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 } | 383 } |
| 361 | 384 |
| 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( | 385 void ApplyDefaultSuggestionForExtensionKeyword( |
| 388 Profile* profile, | 386 Profile* profile, |
| 389 const TemplateURL* keyword, | 387 const TemplateURL* keyword, |
| 390 const string16& remaining_input, | 388 const string16& remaining_input, |
| 391 AutocompleteMatch* match) { | 389 AutocompleteMatch* match) { |
| 392 DCHECK(keyword->IsExtensionKeyword()); | 390 DCHECK(keyword->IsExtensionKeyword()); |
| 393 | 391 |
| 394 ExtensionPrefs* prefs = | 392 ExtensionPrefs* prefs = |
| 395 ExtensionSystem::Get(profile)->extension_service()->extension_prefs(); | 393 ExtensionSystem::Get(profile)->extension_service()->extension_prefs(); |
| 396 if (!prefs) | 394 if (!prefs) |
| 397 return; | 395 return; |
| 398 | 396 |
| 399 ExtensionOmniboxSuggestion suggestion = | 397 scoped_ptr<api::omnibox::SuggestDefaultResult> suggestion( |
| 400 prefs->GetOmniboxDefaultSuggestion(keyword->GetExtensionId()); | 398 prefs->GetOmniboxDefaultSuggestion(keyword->GetExtensionId())); |
| 401 if (suggestion.description.empty()) | 399 if (!suggestion || suggestion->description.empty()) |
| 402 return; // fall back to the universal default | 400 return; // fall back to the universal default |
| 403 | 401 |
| 404 const string16 kPlaceholderText(ASCIIToUTF16("%s")); | 402 const string16 kPlaceholderText(ASCIIToUTF16("%s")); |
| 405 const string16 kReplacementText(ASCIIToUTF16("<input>")); | 403 const string16 kReplacementText(ASCIIToUTF16("<input>")); |
| 406 | 404 |
| 407 string16 description = suggestion.description; | 405 string16 description = UTF8ToUTF16(suggestion->description); |
| 408 ACMatchClassifications& description_styles = match->contents_class; | 406 ACMatchClassifications& description_styles = match->contents_class; |
| 409 description_styles = suggestion.description_styles; | 407 description_styles = DefaultStyleTypesToACMatchClassifications(*suggestion); |
| 410 | 408 |
| 411 // Replace "%s" with the user's input and adjust the style offsets to the | 409 // Replace "%s" with the user's input and adjust the style offsets to the |
| 412 // new length of the description. | 410 // new length of the description. |
| 413 size_t placeholder(suggestion.description.find(kPlaceholderText, 0)); | 411 size_t placeholder(description.find(kPlaceholderText, 0)); |
| 414 if (placeholder != string16::npos) { | 412 if (placeholder != string16::npos) { |
| 415 string16 replacement = | 413 string16 replacement = |
| 416 remaining_input.empty() ? kReplacementText : remaining_input; | 414 remaining_input.empty() ? kReplacementText : remaining_input; |
| 417 description.replace(placeholder, kPlaceholderText.length(), replacement); | 415 description.replace(placeholder, kPlaceholderText.length(), replacement); |
| 418 | 416 |
| 419 for (size_t i = 0; i < description_styles.size(); ++i) { | 417 for (size_t i = 0; i < description_styles.size(); ++i) { |
| 420 if (description_styles[i].offset > placeholder) | 418 if (description_styles[i].offset > placeholder) |
| 421 description_styles[i].offset += replacement.length() - 2; | 419 description_styles[i].offset += replacement.length() - 2; |
| 422 } | 420 } |
| 423 } | 421 } |
| 424 | 422 |
| 425 match->contents.assign(description); | 423 match->contents.assign(description); |
| 426 } | 424 } |
| 427 | 425 |
| 428 } // namespace extensions | 426 } // namespace extensions |
| OLD | NEW |