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