| 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/string_util.h" | 10 #include "base/string_util.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/extension_event_router.h" | 13 #include "chrome/browser/extensions/extension_event_router.h" |
| 14 #include "chrome/browser/extensions/extension_prefs.h" |
| 14 #include "chrome/browser/extensions/extension_service.h" | 15 #include "chrome/browser/extensions/extension_service.h" |
| 16 #include "chrome/browser/extensions/extension_system.h" |
| 15 #include "chrome/browser/profiles/profile.h" | 17 #include "chrome/browser/profiles/profile.h" |
| 16 #include "chrome/browser/search_engines/template_url.h" | 18 #include "chrome/browser/search_engines/template_url.h" |
| 17 #include "chrome/browser/ui/browser.h" | 19 #include "chrome/browser/ui/browser.h" |
| 18 #include "chrome/browser/ui/webui/ntp/app_launcher_handler.h" | 20 #include "chrome/browser/ui/webui/ntp/app_launcher_handler.h" |
| 19 #include "chrome/common/chrome_notification_types.h" | 21 #include "chrome/common/chrome_notification_types.h" |
| 20 #include "chrome/common/extensions/extension_constants.h" | 22 #include "chrome/common/extensions/extension_constants.h" |
| 21 #include "content/public/browser/notification_service.h" | 23 #include "content/public/browser/notification_service.h" |
| 22 | 24 |
| 23 namespace events { | 25 namespace events { |
| 24 const char kOnInputStarted[] = "omnibox.onInputStarted"; | 26 const char kOnInputStarted[] = "omnibox.onInputStarted"; |
| 25 const char kOnInputChanged[] = "omnibox.onInputChanged"; | 27 const char kOnInputChanged[] = "omnibox.onInputChanged"; |
| 26 const char kOnInputEntered[] = "omnibox.onInputEntered"; | 28 const char kOnInputEntered[] = "omnibox.onInputEntered"; |
| 27 const char kOnInputCancelled[] = "omnibox.onInputCancelled"; | 29 const char kOnInputCancelled[] = "omnibox.onInputCancelled"; |
| 28 } // namespace events | 30 } // namespace events |
| 29 | 31 |
| 30 namespace extensions { | 32 namespace extensions { |
| 31 | 33 |
| 32 namespace { | 34 namespace { |
| 33 const char kDescriptionStylesOrderError[] = | 35 const char kDescriptionStylesOrderError[] = |
| 34 "Suggestion descriptionStyles must be in increasing non-overlapping order."; | 36 "Suggestion descriptionStyles must be in increasing non-overlapping order."; |
| 35 const char kDescriptionStylesLengthError[] = | 37 const char kDescriptionStylesLengthError[] = |
| 36 "Suggestion descriptionStyles contains an offset longer than the" | 38 "Suggestion descriptionStyles contains an offset longer than the" |
| 37 " description text"; | 39 " description text"; |
| 38 | 40 |
| 39 const char kSuggestionContent[] = "content"; | 41 const char kSuggestionContent[] = "content"; |
| 40 const char kSuggestionDescription[] = "description"; | 42 const char kSuggestionDescription[] = "description"; |
| 41 const char kSuggestionDescriptionStyles[] = "descriptionStyles"; | 43 const char kSuggestionDescriptionStyles[] = "descriptionStyles"; |
| 44 const char kSuggestionDescriptionStylesRaw[] = "descriptionStylesRaw"; |
| 42 const char kDescriptionStylesType[] = "type"; | 45 const char kDescriptionStylesType[] = "type"; |
| 43 const char kDescriptionStylesOffset[] = "offset"; | 46 const char kDescriptionStylesOffset[] = "offset"; |
| 44 const char kDescriptionStylesLength[] = "length"; | 47 const char kDescriptionStylesLength[] = "length"; |
| 45 | 48 |
| 46 static base::LazyInstance<base::PropertyAccessor<ExtensionOmniboxSuggestion> > | |
| 47 g_extension_omnibox_suggestion_property_accessor = | |
| 48 LAZY_INSTANCE_INITIALIZER; | |
| 49 | |
| 50 base::PropertyAccessor<ExtensionOmniboxSuggestion>& GetPropertyAccessor() { | |
| 51 return g_extension_omnibox_suggestion_property_accessor.Get(); | |
| 52 } | |
| 53 | |
| 54 // Returns the suggestion object set by the extension via the | |
| 55 // omnibox.setDefaultSuggestion call, or NULL if it was never set. | |
| 56 const ExtensionOmniboxSuggestion* GetDefaultSuggestionForExtension( | |
| 57 Profile* profile, const std::string& extension_id) { | |
| 58 const Extension* extension = | |
| 59 profile->GetExtensionService()->GetExtensionById(extension_id, false); | |
| 60 if (!extension) | |
| 61 return NULL; | |
| 62 return GetPropertyAccessor().GetProperty( | |
| 63 profile->GetExtensionService()->GetPropertyBag(extension)); | |
| 64 } | |
| 65 | |
| 66 } // namespace | 49 } // namespace |
| 67 | 50 |
| 68 // static | 51 // static |
| 69 void ExtensionOmniboxEventRouter::OnInputStarted( | 52 void ExtensionOmniboxEventRouter::OnInputStarted( |
| 70 Profile* profile, const std::string& extension_id) { | 53 Profile* profile, const std::string& extension_id) { |
| 71 profile->GetExtensionEventRouter()->DispatchEventToExtension( | 54 profile->GetExtensionEventRouter()->DispatchEventToExtension( |
| 72 extension_id, events::kOnInputStarted, "[]", profile, GURL()); | 55 extension_id, events::kOnInputStarted, "[]", profile, GURL()); |
| 73 } | 56 } |
| 74 | 57 |
| 75 // static | 58 // static |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 121 ListValue* suggestions_value; | 104 ListValue* suggestions_value; |
| 122 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &suggestions.request_id)); | 105 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &suggestions.request_id)); |
| 123 EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &suggestions_value)); | 106 EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &suggestions_value)); |
| 124 | 107 |
| 125 suggestions.suggestions.resize(suggestions_value->GetSize()); | 108 suggestions.suggestions.resize(suggestions_value->GetSize()); |
| 126 for (size_t i = 0; i < suggestions_value->GetSize(); ++i) { | 109 for (size_t i = 0; i < suggestions_value->GetSize(); ++i) { |
| 127 ExtensionOmniboxSuggestion& suggestion = suggestions.suggestions[i]; | 110 ExtensionOmniboxSuggestion& suggestion = suggestions.suggestions[i]; |
| 128 DictionaryValue* suggestion_value; | 111 DictionaryValue* suggestion_value; |
| 129 EXTENSION_FUNCTION_VALIDATE(suggestions_value->GetDictionary( | 112 EXTENSION_FUNCTION_VALIDATE(suggestions_value->GetDictionary( |
| 130 i, &suggestion_value)); | 113 i, &suggestion_value)); |
| 131 EXTENSION_FUNCTION_VALIDATE(suggestion_value->GetString( | 114 EXTENSION_FUNCTION_VALIDATE(suggestion.Populate(*suggestion_value, true)); |
| 132 kSuggestionContent, &suggestion.content)); | |
| 133 EXTENSION_FUNCTION_VALIDATE(suggestion_value->GetString( | |
| 134 kSuggestionDescription, &suggestion.description)); | |
| 135 | |
| 136 if (suggestion_value->HasKey(kSuggestionDescriptionStyles)) { | |
| 137 ListValue* styles; | |
| 138 EXTENSION_FUNCTION_VALIDATE( | |
| 139 suggestion_value->GetList(kSuggestionDescriptionStyles, &styles)); | |
| 140 EXTENSION_FUNCTION_VALIDATE(suggestion.ReadStylesFromValue(*styles)); | |
| 141 } else { | |
| 142 suggestion.description_styles.clear(); | |
| 143 suggestion.description_styles.push_back( | |
| 144 ACMatchClassification(0, ACMatchClassification::NONE)); | |
| 145 } | |
| 146 } | 115 } |
| 147 | 116 |
| 148 content::NotificationService::current()->Notify( | 117 content::NotificationService::current()->Notify( |
| 149 chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY, | 118 chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY, |
| 150 content::Source<Profile>(profile_->GetOriginalProfile()), | 119 content::Source<Profile>(profile_->GetOriginalProfile()), |
| 151 content::Details<ExtensionOmniboxSuggestions>(&suggestions)); | 120 content::Details<ExtensionOmniboxSuggestions>(&suggestions)); |
| 152 | 121 |
| 153 return true; | 122 return true; |
| 154 } | 123 } |
| 155 | 124 |
| 156 bool OmniboxSetDefaultSuggestionFunction::RunImpl() { | 125 bool OmniboxSetDefaultSuggestionFunction::RunImpl() { |
| 157 ExtensionOmniboxSuggestion suggestion; | 126 ExtensionOmniboxSuggestion suggestion; |
| 158 DictionaryValue* suggestion_value; | 127 DictionaryValue* suggestion_value; |
| 159 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &suggestion_value)); | 128 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &suggestion_value)); |
| 160 EXTENSION_FUNCTION_VALIDATE(suggestion_value->GetString( | 129 EXTENSION_FUNCTION_VALIDATE(suggestion.Populate(*suggestion_value, false)); |
| 161 kSuggestionDescription, &suggestion.description)); | |
| 162 | 130 |
| 163 if (suggestion_value->HasKey(kSuggestionDescriptionStyles)) { | 131 ExtensionPrefs* prefs = |
| 164 ListValue* styles; | 132 ExtensionSystem::Get(profile())->extension_service()->extension_prefs(); |
| 165 EXTENSION_FUNCTION_VALIDATE( | 133 if (prefs) |
| 166 suggestion_value->GetList(kSuggestionDescriptionStyles, &styles)); | 134 prefs->SetOmniboxDefaultSuggestion(extension_id(), suggestion); |
| 167 EXTENSION_FUNCTION_VALIDATE(suggestion.ReadStylesFromValue(*styles)); | |
| 168 } else { | |
| 169 suggestion.description_styles.clear(); | |
| 170 suggestion.description_styles.push_back( | |
| 171 ACMatchClassification(0, ACMatchClassification::NONE)); | |
| 172 } | |
| 173 | |
| 174 // Store the suggestion in the extension's runtime data. | |
| 175 GetPropertyAccessor().SetProperty( | |
| 176 profile_->GetExtensionService()->GetPropertyBag(GetExtension()), | |
| 177 suggestion); | |
| 178 | 135 |
| 179 content::NotificationService::current()->Notify( | 136 content::NotificationService::current()->Notify( |
| 180 chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED, | 137 chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED, |
| 181 content::Source<Profile>(profile_->GetOriginalProfile()), | 138 content::Source<Profile>(profile_->GetOriginalProfile()), |
| 182 content::NotificationService::NoDetails()); | 139 content::NotificationService::NoDetails()); |
| 183 | 140 |
| 184 return true; | 141 return true; |
| 185 } | 142 } |
| 186 | 143 |
| 187 ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {} | 144 ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {} |
| 188 | 145 |
| 189 ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {} | 146 ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {} |
| 190 | 147 |
| 148 bool ExtensionOmniboxSuggestion::Populate(const base::DictionaryValue& value, |
| 149 bool require_content) { |
| 150 if (!value.GetString(kSuggestionContent, &content) && require_content) |
| 151 return false; |
| 152 |
| 153 if (!value.GetString(kSuggestionDescription, &description)) |
| 154 return false; |
| 155 |
| 156 description_styles.clear(); |
| 157 if (value.HasKey(kSuggestionDescriptionStyles)) { |
| 158 // This version comes from the extension. |
| 159 ListValue* styles = NULL; |
| 160 if (!value.GetList(kSuggestionDescriptionStyles, &styles) || |
| 161 !ReadStylesFromValue(*styles)) { |
| 162 return false; |
| 163 } |
| 164 } else if (value.HasKey(kSuggestionDescriptionStylesRaw)) { |
| 165 // This version comes from ToValue(), which we use to persist to disk. |
| 166 ListValue* styles = NULL; |
| 167 if (!value.GetList(kSuggestionDescriptionStylesRaw, &styles) || |
| 168 styles->empty()) { |
| 169 return false; |
| 170 } |
| 171 for (size_t i = 0; i < styles->GetSize(); ++i) { |
| 172 base::DictionaryValue* style = NULL; |
| 173 int offset, type; |
| 174 if (!styles->GetDictionary(i, &style)) |
| 175 return false; |
| 176 if (!style->GetInteger(kDescriptionStylesType, &type)) |
| 177 return false; |
| 178 if (!style->GetInteger(kDescriptionStylesOffset, &offset)) |
| 179 return false; |
| 180 description_styles.push_back(ACMatchClassification(offset, type)); |
| 181 } |
| 182 } else { |
| 183 description_styles.push_back( |
| 184 ACMatchClassification(0, ACMatchClassification::NONE)); |
| 185 } |
| 186 |
| 187 return true; |
| 188 } |
| 189 |
| 191 bool ExtensionOmniboxSuggestion::ReadStylesFromValue( | 190 bool ExtensionOmniboxSuggestion::ReadStylesFromValue( |
| 192 const ListValue& styles_value) { | 191 const ListValue& styles_value) { |
| 193 description_styles.clear(); | 192 description_styles.clear(); |
| 194 | 193 |
| 195 // Step 1: Build a vector of styles, 1 per character of description text. | 194 // Step 1: Build a vector of styles, 1 per character of description text. |
| 196 std::vector<int> styles; | 195 std::vector<int> styles; |
| 197 styles.resize(description.length()); // sets all styles to 0 | 196 styles.resize(description.length()); // sets all styles to 0 |
| 198 | 197 |
| 199 for (size_t i = 0; i < styles_value.GetSize(); ++i) { | 198 for (size_t i = 0; i < styles_value.GetSize(); ++i) { |
| 200 DictionaryValue* style; | 199 DictionaryValue* style; |
| (...skipping 26 matching lines...) Expand all Loading... |
| 227 | 226 |
| 228 // Step 2: Convert the vector into continuous runs of common styles. | 227 // Step 2: Convert the vector into continuous runs of common styles. |
| 229 for (size_t i = 0; i < styles.size(); ++i) { | 228 for (size_t i = 0; i < styles.size(); ++i) { |
| 230 if (i == 0 || styles[i] != styles[i-1]) | 229 if (i == 0 || styles[i] != styles[i-1]) |
| 231 description_styles.push_back(ACMatchClassification(i, styles[i])); | 230 description_styles.push_back(ACMatchClassification(i, styles[i])); |
| 232 } | 231 } |
| 233 | 232 |
| 234 return true; | 233 return true; |
| 235 } | 234 } |
| 236 | 235 |
| 236 scoped_ptr<base::DictionaryValue> ExtensionOmniboxSuggestion::ToValue() const { |
| 237 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue()); |
| 238 |
| 239 value->SetString(kSuggestionContent, content); |
| 240 value->SetString(kSuggestionDescription, description); |
| 241 |
| 242 if (description_styles.size() > 0) { |
| 243 base::ListValue* styles_value = new base::ListValue(); |
| 244 for (size_t i = 0; i < description_styles.size(); ++i) { |
| 245 base::DictionaryValue* style = new base::DictionaryValue(); |
| 246 style->SetInteger(kDescriptionStylesOffset, description_styles[i].offset); |
| 247 style->SetInteger(kDescriptionStylesType, description_styles[i].style); |
| 248 styles_value->Append(style); |
| 249 } |
| 250 |
| 251 value->Set(kSuggestionDescriptionStylesRaw, styles_value); |
| 252 } |
| 253 |
| 254 return value.Pass(); |
| 255 } |
| 256 |
| 237 ExtensionOmniboxSuggestions::ExtensionOmniboxSuggestions() : request_id(0) {} | 257 ExtensionOmniboxSuggestions::ExtensionOmniboxSuggestions() : request_id(0) {} |
| 238 | 258 |
| 239 ExtensionOmniboxSuggestions::~ExtensionOmniboxSuggestions() {} | 259 ExtensionOmniboxSuggestions::~ExtensionOmniboxSuggestions() {} |
| 240 | 260 |
| 241 void ApplyDefaultSuggestionForExtensionKeyword( | 261 void ApplyDefaultSuggestionForExtensionKeyword( |
| 242 Profile* profile, | 262 Profile* profile, |
| 243 const TemplateURL* keyword, | 263 const TemplateURL* keyword, |
| 244 const string16& remaining_input, | 264 const string16& remaining_input, |
| 245 AutocompleteMatch* match) { | 265 AutocompleteMatch* match) { |
| 246 DCHECK(keyword->IsExtensionKeyword()); | 266 DCHECK(keyword->IsExtensionKeyword()); |
| 247 const ExtensionOmniboxSuggestion* suggestion = | 267 |
| 248 GetDefaultSuggestionForExtension(profile, keyword->GetExtensionId()); | 268 ExtensionPrefs* prefs = |
| 249 if (!suggestion) | 269 ExtensionSystem::Get(profile)->extension_service()->extension_prefs(); |
| 270 if (!prefs) |
| 271 return; |
| 272 |
| 273 ExtensionOmniboxSuggestion suggestion = |
| 274 prefs->GetOmniboxDefaultSuggestion(keyword->GetExtensionId()); |
| 275 if (suggestion.description.empty()) |
| 250 return; // fall back to the universal default | 276 return; // fall back to the universal default |
| 251 | 277 |
| 252 const string16 kPlaceholderText(ASCIIToUTF16("%s")); | 278 const string16 kPlaceholderText(ASCIIToUTF16("%s")); |
| 253 const string16 kReplacementText(ASCIIToUTF16("<input>")); | 279 const string16 kReplacementText(ASCIIToUTF16("<input>")); |
| 254 | 280 |
| 255 string16 description = suggestion->description; | 281 string16 description = suggestion.description; |
| 256 ACMatchClassifications& description_styles = match->contents_class; | 282 ACMatchClassifications& description_styles = match->contents_class; |
| 257 description_styles = suggestion->description_styles; | 283 description_styles = suggestion.description_styles; |
| 258 | 284 |
| 259 // Replace "%s" with the user's input and adjust the style offsets to the | 285 // Replace "%s" with the user's input and adjust the style offsets to the |
| 260 // new length of the description. | 286 // new length of the description. |
| 261 size_t placeholder(suggestion->description.find(kPlaceholderText, 0)); | 287 size_t placeholder(suggestion.description.find(kPlaceholderText, 0)); |
| 262 if (placeholder != string16::npos) { | 288 if (placeholder != string16::npos) { |
| 263 string16 replacement = | 289 string16 replacement = |
| 264 remaining_input.empty() ? kReplacementText : remaining_input; | 290 remaining_input.empty() ? kReplacementText : remaining_input; |
| 265 description.replace(placeholder, kPlaceholderText.length(), replacement); | 291 description.replace(placeholder, kPlaceholderText.length(), replacement); |
| 266 | 292 |
| 267 for (size_t i = 0; i < description_styles.size(); ++i) { | 293 for (size_t i = 0; i < description_styles.size(); ++i) { |
| 268 if (description_styles[i].offset > placeholder) | 294 if (description_styles[i].offset > placeholder) |
| 269 description_styles[i].offset += replacement.length() - 2; | 295 description_styles[i].offset += replacement.length() - 2; |
| 270 } | 296 } |
| 271 } | 297 } |
| (...skipping 20 matching lines...) Expand all Loading... |
| 292 // preference is set, launch as a regular tab. | 318 // preference is set, launch as a regular tab. |
| 293 extension_misc::LaunchContainer launch_container = | 319 extension_misc::LaunchContainer launch_container = |
| 294 service->extension_prefs()->GetLaunchContainer( | 320 service->extension_prefs()->GetLaunchContainer( |
| 295 extension, ExtensionPrefs::LAUNCH_REGULAR); | 321 extension, ExtensionPrefs::LAUNCH_REGULAR); |
| 296 | 322 |
| 297 Browser::OpenApplication(profile, extension, launch_container, GURL(), | 323 Browser::OpenApplication(profile, extension, launch_container, GURL(), |
| 298 disposition); | 324 disposition); |
| 299 } | 325 } |
| 300 | 326 |
| 301 } // namespace extensions | 327 } // namespace extensions |
| OLD | NEW |