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_context_menu_api.h" 6 7 #include <string> 8 9 #include "base/values.h" 10 #include "base/string_number_conversions.h" 11 #include "base/string_util.h" 12 #include "chrome/browser/extensions/extension_service.h" 13 #include "chrome/browser/profiles/profile.h" 14 #include "chrome/common/extensions/extension_error_utils.h" 15 16 const char kCheckedKey[] = "checked"; 17 const char kContextsKey[] = "contexts"; 18 const char kDocumentUrlPatternsKey[] = "documentUrlPatterns"; 19 const char kGeneratedIdKey[] = "generatedId"; 20 const char kParentIdKey[] = "parentId"; 21 const char kTargetUrlPatternsKey[] = "targetUrlPatterns"; 22 const char kTitleKey[] = "title"; 23 const char kTypeKey[] = "type"; 24 25 const char kCannotFindItemError[] = "Cannot find menu item with id *"; 26 const char kCheckedError[] = 27 "Only items with type \"radio\" or \"checkbox\" can be checked"; 28 const char kInvalidURLPatternError[] = "Invalid url pattern '*'"; 29 const char kInvalidValueError[] = "Invalid value for *"; 30 const char kInvalidTypeStringError[] = "Invalid type string '*'"; 31 const char kParentsMustBeNormalError[] = 32 "Parent items must have type \"normal\""; 33 const char kTitleNeededError[] = 34 "All menu items except for separators must have a title"; 35 36 37 bool ExtensionContextMenuFunction::ParseContexts( 38 const DictionaryValue& properties, 39 const char* key, 40 ExtensionMenuItem::ContextList* result) { 41 ListValue* list = NULL; 42 if (!properties.GetList(key, &list)) { 43 return true; 44 } 45 ExtensionMenuItem::ContextList tmp_result; 46 47 std::string value; 48 for (size_t i = 0; i < list->GetSize(); i++) { 49 if (!list->GetString(i, &value)) 50 return false; 51 52 if (value == "all") { 53 tmp_result.Add(ExtensionMenuItem::ALL); 54 } else if (value == "page") { 55 tmp_result.Add(ExtensionMenuItem::PAGE); 56 } else if (value == "selection") { 57 tmp_result.Add(ExtensionMenuItem::SELECTION); 58 } else if (value == "link") { 59 tmp_result.Add(ExtensionMenuItem::LINK); 60 } else if (value == "editable") { 61 tmp_result.Add(ExtensionMenuItem::EDITABLE); 62 } else if (value == "image") { 63 tmp_result.Add(ExtensionMenuItem::IMAGE); 64 } else if (value == "video") { 65 tmp_result.Add(ExtensionMenuItem::VIDEO); 66 } else if (value == "audio") { 67 tmp_result.Add(ExtensionMenuItem::AUDIO); 68 } else if (value == "frame") { 69 tmp_result.Add(ExtensionMenuItem::FRAME); 70 } else { 71 error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidValueError, key); 72 return false; 73 } 74 } 75 *result = tmp_result; 76 return true; 77 } 78 79 bool ExtensionContextMenuFunction::ParseType( 80 const DictionaryValue& properties, 81 const ExtensionMenuItem::Type& default_value, 82 ExtensionMenuItem::Type* result) { 83 DCHECK(result); 84 if (!properties.HasKey(kTypeKey)) { 85 *result = default_value; 86 return true; 87 } 88 89 std::string type_string; 90 if (!properties.GetString(kTypeKey, &type_string)) 91 return false; 92 93 if (type_string == "normal") { 94 *result = ExtensionMenuItem::NORMAL; 95 } else if (type_string == "checkbox") { 96 *result = ExtensionMenuItem::CHECKBOX; 97 } else if (type_string == "radio") { 98 *result = ExtensionMenuItem::RADIO; 99 } else if (type_string == "separator") { 100 *result = ExtensionMenuItem::SEPARATOR; 101 } else { 102 error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidTypeStringError, 103 type_string); 104 return false; 105 } 106 return true; 107 } 108 109 bool ExtensionContextMenuFunction::ParseChecked( 110 ExtensionMenuItem::Type type, 111 const DictionaryValue& properties, 112 bool default_value, 113 bool* checked) { 114 if (!properties.HasKey(kCheckedKey)) { 115 *checked = default_value; 116 return true; 117 } 118 if (!properties.GetBoolean(kCheckedKey, checked)) 119 return false; 120 if (checked && type != ExtensionMenuItem::CHECKBOX && 121 type != ExtensionMenuItem::RADIO) { 122 error_ = kCheckedError; 123 return false; 124 } 125 return true; 126 } 127 128 bool ExtensionContextMenuFunction::ParseURLPatterns( 129 const DictionaryValue& properties, 130 const char* key, 131 ExtensionExtent* result) { 132 if (!properties.HasKey(key)) 133 return true; 134 ListValue* list = NULL; 135 if (!properties.GetList(key, &list)) 136 return false; 137 for (ListValue::iterator i = list->begin(); i != list->end(); ++i) { 138 std::string tmp; 139 if (!(*i)->GetAsString(&tmp)) 140 return false; 141 142 URLPattern pattern(ExtensionMenuManager::kAllowedSchemes); 143 // TODO(skerner): Consider enabling strict pattern parsing 144 // if this extension's location indicates that it is under development. 145 if (URLPattern::PARSE_SUCCESS != pattern.Parse(tmp, 146 URLPattern::PARSE_LENIENT)) { 147 error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidURLPatternError, 148 tmp); 149 return false; 150 } 151 result->AddPattern(pattern); 152 } 153 return true; 154 } 155 156 bool ExtensionContextMenuFunction::SetURLPatterns( 157 const DictionaryValue& properties, 158 ExtensionMenuItem* item) { 159 // Process the documentUrlPattern value. 160 ExtensionExtent document_url_patterns; 161 if (!ParseURLPatterns(properties, kDocumentUrlPatternsKey, 162 &document_url_patterns)) 163 return false; 164 165 if (!document_url_patterns.is_empty()) { 166 item->set_document_url_patterns(document_url_patterns); 167 } 168 169 // Process the targetUrlPattern value. 170 ExtensionExtent target_url_patterns; 171 if (!ParseURLPatterns(properties, kTargetUrlPatternsKey, 172 &target_url_patterns)) 173 return false; 174 175 if (!target_url_patterns.is_empty()) { 176 item->set_target_url_patterns(target_url_patterns); 177 } 178 179 return true; 180 } 181 182 bool ExtensionContextMenuFunction::GetParent( 183 const DictionaryValue& properties, 184 const ExtensionMenuManager& manager, 185 ExtensionMenuItem** result) { 186 if (!properties.HasKey(kParentIdKey)) 187 return true; 188 ExtensionMenuItem::Id parent_id(profile(), extension_id(), 0); 189 if (properties.HasKey(kParentIdKey) && 190 !properties.GetInteger(kParentIdKey, &parent_id.uid)) 191 return false; 192 193 ExtensionMenuItem* parent = manager.GetItemById(parent_id); 194 if (!parent) { 195 error_ = "Cannot find menu item with id " + 196 base::IntToString(parent_id.uid); 197 return false; 198 } 199 if (parent->type() != ExtensionMenuItem::NORMAL) { 200 error_ = kParentsMustBeNormalError; 201 return false; 202 } 203 *result = parent; 204 return true; 205 } 206 207 bool CreateContextMenuFunction::RunImpl() { 208 DictionaryValue* properties; 209 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &properties)); 210 EXTENSION_FUNCTION_VALIDATE(properties != NULL); 211 212 ExtensionMenuItem::Id id(profile(), extension_id(), 0); 213 EXTENSION_FUNCTION_VALIDATE(properties->GetInteger(kGeneratedIdKey, 214 &id.uid)); 215 std::string title; 216 if (properties->HasKey(kTitleKey) && 217 !properties->GetString(kTitleKey, &title)) 218 return false; 219 220 ExtensionMenuManager* menu_manager = 221 profile()->GetExtensionService()->menu_manager(); 222 223 ExtensionMenuItem::ContextList contexts(ExtensionMenuItem::PAGE); 224 if (!ParseContexts(*properties, kContextsKey, &contexts)) 225 return false; 226 227 ExtensionMenuItem::Type type; 228 if (!ParseType(*properties, ExtensionMenuItem::NORMAL, &type)) 229 return false; 230 231 if (title.empty() && type != ExtensionMenuItem::SEPARATOR) { 232 error_ = kTitleNeededError; 233 return false; 234 } 235 236 bool checked; 237 if (!ParseChecked(type, *properties, false, &checked)) 238 return false; 239 240 scoped_ptr<ExtensionMenuItem> item( 241 new ExtensionMenuItem(id, title, checked, type, contexts)); 242 243 if (!SetURLPatterns(*properties, item.get())) 244 return false; 245 246 bool success = true; 247 if (properties->HasKey(kParentIdKey)) { 248 ExtensionMenuItem::Id parent_id(profile(), extension_id(), 0); 249 EXTENSION_FUNCTION_VALIDATE(properties->GetInteger(kParentIdKey, 250 &parent_id.uid)); 251 ExtensionMenuItem* parent = menu_manager->GetItemById(parent_id); 252 if (!parent) { 253 error_ = ExtensionErrorUtils::FormatErrorMessage( 254 kCannotFindItemError, base::IntToString(parent_id.uid)); 255 return false; 256 } 257 if (parent->type() != ExtensionMenuItem::NORMAL) { 258 error_ = kParentsMustBeNormalError; 259 return false; 260 } 261 success = menu_manager->AddChildItem(parent_id, item.release()); 262 } else { 263 success = menu_manager->AddContextItem(GetExtension(), item.release()); 264 } 265 266 if (!success) 267 return false; 268 269 return true; 270 } 271 272 bool UpdateContextMenuFunction::RunImpl() { 273 ExtensionMenuItem::Id item_id(profile(), extension_id(), 0); 274 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &item_id.uid)); 275 276 ExtensionService* service = profile()->GetExtensionService(); 277 ExtensionMenuManager* manager = service->menu_manager(); 278 ExtensionMenuItem* item = manager->GetItemById(item_id); 279 if (!item || item->extension_id() != extension_id()) { 280 error_ = ExtensionErrorUtils::FormatErrorMessage( 281 kCannotFindItemError, base::IntToString(item_id.uid)); 282 return false; 283 } 284 285 DictionaryValue *properties = NULL; 286 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &properties)); 287 EXTENSION_FUNCTION_VALIDATE(properties != NULL); 288 289 ExtensionMenuManager* menu_manager = 290 profile()->GetExtensionService()->menu_manager(); 291 292 // Type. 293 ExtensionMenuItem::Type type; 294 if (!ParseType(*properties, item->type(), &type)) 295 return false; 296 if (type != item->type()) 297 item->set_type(type); 298 299 // Title. 300 if (properties->HasKey(kTitleKey)) { 301 std::string title; 302 EXTENSION_FUNCTION_VALIDATE(properties->GetString(kTitleKey, &title)); 303 if (title.empty() && type != ExtensionMenuItem::SEPARATOR) { 304 error_ = kTitleNeededError; 305 return false; 306 } 307 item->set_title(title); 308 } 309 310 // Checked state. 311 bool checked; 312 if (!ParseChecked(item->type(), *properties, item->checked(), &checked)) 313 return false; 314 if (checked != item->checked()) { 315 if (!item->SetChecked(checked)) 316 return false; 317 } 318 319 // Contexts. 320 ExtensionMenuItem::ContextList contexts(item->contexts()); 321 if (!ParseContexts(*properties, kContextsKey, &contexts)) 322 return false; 323 if (contexts != item->contexts()) 324 item->set_contexts(contexts); 325 326 // Parent id. 327 ExtensionMenuItem* parent = NULL; 328 if (!GetParent(*properties, *menu_manager, &parent)) 329 return false; 330 if (parent && !menu_manager->ChangeParent(item->id(), &parent->id())) 331 return false; 332 333 if (!SetURLPatterns(*properties, item)) 334 return false; 335 336 return true; 337 } 338 339 bool RemoveContextMenuFunction::RunImpl() { 340 ExtensionMenuItem::Id id(profile(), extension_id(), 0); 341 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &id.uid)); 342 ExtensionService* service = profile()->GetExtensionService(); 343 ExtensionMenuManager* manager = service->menu_manager(); 344 345 ExtensionMenuItem* item = manager->GetItemById(id); 346 // Ensure one extension can't remove another's menu items. 347 if (!item || item->extension_id() != extension_id()) { 348 error_ = ExtensionErrorUtils::FormatErrorMessage( 349 kCannotFindItemError, base::IntToString(id.uid)); 350 return false; 351 } 352 353 return manager->RemoveContextMenuItem(id); 354 } 355 356 bool RemoveAllContextMenusFunction::RunImpl() { 357 ExtensionService* service = profile()->GetExtensionService(); 358 ExtensionMenuManager* manager = service->menu_manager(); 359 manager->RemoveAllContextItems(extension_id()); 360 return true; 361 } 362