| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/extensions/api/context_menu/context_menu_api.h" | |
| 6 | |
| 7 #include <string> | |
| 8 | |
| 9 #include "base/string_number_conversions.h" | |
| 10 #include "base/string_util.h" | |
| 11 #include "base/values.h" | |
| 12 #include "chrome/browser/extensions/extension_service.h" | |
| 13 #include "chrome/browser/extensions/menu_manager.h" | |
| 14 #include "chrome/browser/profiles/profile.h" | |
| 15 #include "chrome/common/extensions/api/context_menus.h" | |
| 16 #include "extensions/common/error_utils.h" | |
| 17 #include "extensions/common/url_pattern_set.h" | |
| 18 | |
| 19 using extensions::ErrorUtils; | |
| 20 | |
| 21 namespace { | |
| 22 | |
| 23 const char kGeneratedIdKey[] = "generatedId"; | |
| 24 | |
| 25 const char kCannotFindItemError[] = "Cannot find menu item with id *"; | |
| 26 const char kOnclickDisallowedError[] = "Extensions using event pages cannot " | |
| 27 "pass an onclick parameter to chrome.contextMenus.create. Instead, use " | |
| 28 "the chrome.contextMenus.onClicked event."; | |
| 29 const char kCheckedError[] = | |
| 30 "Only items with type \"radio\" or \"checkbox\" can be checked"; | |
| 31 const char kDuplicateIDError[] = | |
| 32 "Cannot create item with duplicate id *"; | |
| 33 const char kIdRequiredError[] = "Extensions using event pages must pass an " | |
| 34 "id parameter to chrome.contextMenus.create"; | |
| 35 const char kParentsMustBeNormalError[] = | |
| 36 "Parent items must have type \"normal\""; | |
| 37 const char kTitleNeededError[] = | |
| 38 "All menu items except for separators must have a title"; | |
| 39 const char kLauncherNotAllowedError[] = | |
| 40 "Only packaged apps are allowed to use 'launcher' context"; | |
| 41 | |
| 42 std::string GetIDString(const extensions::MenuItem::Id& id) { | |
| 43 if (id.uid == 0) | |
| 44 return id.string_uid; | |
| 45 else | |
| 46 return base::IntToString(id.uid); | |
| 47 } | |
| 48 | |
| 49 template<typename PropertyWithEnumT> | |
| 50 extensions::MenuItem::ContextList GetContexts( | |
| 51 const PropertyWithEnumT& property) { | |
| 52 extensions::MenuItem::ContextList contexts; | |
| 53 for (size_t i = 0; i < property.contexts->size(); ++i) { | |
| 54 switch (property.contexts->at(i)) { | |
| 55 case PropertyWithEnumT::CONTEXTS_ELEMENT_ALL: | |
| 56 contexts.Add(extensions::MenuItem::ALL); | |
| 57 break; | |
| 58 case PropertyWithEnumT::CONTEXTS_ELEMENT_PAGE: | |
| 59 contexts.Add(extensions::MenuItem::PAGE); | |
| 60 break; | |
| 61 case PropertyWithEnumT::CONTEXTS_ELEMENT_SELECTION: | |
| 62 contexts.Add(extensions::MenuItem::SELECTION); | |
| 63 break; | |
| 64 case PropertyWithEnumT::CONTEXTS_ELEMENT_LINK: | |
| 65 contexts.Add(extensions::MenuItem::LINK); | |
| 66 break; | |
| 67 case PropertyWithEnumT::CONTEXTS_ELEMENT_EDITABLE: | |
| 68 contexts.Add(extensions::MenuItem::EDITABLE); | |
| 69 break; | |
| 70 case PropertyWithEnumT::CONTEXTS_ELEMENT_IMAGE: | |
| 71 contexts.Add(extensions::MenuItem::IMAGE); | |
| 72 break; | |
| 73 case PropertyWithEnumT::CONTEXTS_ELEMENT_VIDEO: | |
| 74 contexts.Add(extensions::MenuItem::VIDEO); | |
| 75 break; | |
| 76 case PropertyWithEnumT::CONTEXTS_ELEMENT_AUDIO: | |
| 77 contexts.Add(extensions::MenuItem::AUDIO); | |
| 78 break; | |
| 79 case PropertyWithEnumT::CONTEXTS_ELEMENT_FRAME: | |
| 80 contexts.Add(extensions::MenuItem::FRAME); | |
| 81 break; | |
| 82 case PropertyWithEnumT::CONTEXTS_ELEMENT_LAUNCHER: | |
| 83 contexts.Add(extensions::MenuItem::LAUNCHER); | |
| 84 break; | |
| 85 case PropertyWithEnumT::CONTEXTS_ELEMENT_NONE: | |
| 86 NOTREACHED(); | |
| 87 } | |
| 88 } | |
| 89 return contexts; | |
| 90 } | |
| 91 | |
| 92 template<typename PropertyWithEnumT> | |
| 93 extensions::MenuItem::Type GetType(const PropertyWithEnumT& property) { | |
| 94 switch (property.type) { | |
| 95 case PropertyWithEnumT::TYPE_NONE: | |
| 96 case PropertyWithEnumT::TYPE_NORMAL: | |
| 97 return extensions::MenuItem::NORMAL; | |
| 98 case PropertyWithEnumT::TYPE_CHECKBOX: | |
| 99 return extensions::MenuItem::CHECKBOX; | |
| 100 case PropertyWithEnumT::TYPE_RADIO: | |
| 101 return extensions::MenuItem::RADIO; | |
| 102 case PropertyWithEnumT::TYPE_SEPARATOR: | |
| 103 return extensions::MenuItem::SEPARATOR; | |
| 104 } | |
| 105 return extensions::MenuItem::NORMAL; | |
| 106 } | |
| 107 | |
| 108 template<typename PropertyWithEnumT> | |
| 109 scoped_ptr<extensions::MenuItem::Id> GetParentId( | |
| 110 const PropertyWithEnumT& property, | |
| 111 bool is_off_the_record, | |
| 112 std::string extension_id) { | |
| 113 scoped_ptr<extensions::MenuItem::Id> parent_id( | |
| 114 new extensions::MenuItem::Id(is_off_the_record, extension_id)); | |
| 115 switch (property.parent_id_type) { | |
| 116 case PropertyWithEnumT::PARENT_ID_NONE: | |
| 117 return scoped_ptr<extensions::MenuItem::Id>().Pass(); | |
| 118 case PropertyWithEnumT::PARENT_ID_INTEGER: | |
| 119 parent_id->uid = *property.parent_id_integer; | |
| 120 break; | |
| 121 case PropertyWithEnumT::PARENT_ID_STRING: | |
| 122 parent_id->string_uid = *property.parent_id_string; | |
| 123 break; | |
| 124 } | |
| 125 return parent_id.Pass(); | |
| 126 } | |
| 127 | |
| 128 extensions::MenuItem* GetParent(extensions::MenuItem::Id parent_id, | |
| 129 const extensions::MenuManager* menu_manager, | |
| 130 std::string* error) { | |
| 131 extensions::MenuItem* parent = menu_manager->GetItemById(parent_id); | |
| 132 if (!parent) { | |
| 133 *error = ErrorUtils::FormatErrorMessage( | |
| 134 kCannotFindItemError, GetIDString(parent_id)); | |
| 135 return NULL; | |
| 136 } | |
| 137 if (parent->type() != extensions::MenuItem::NORMAL) { | |
| 138 *error = kParentsMustBeNormalError; | |
| 139 return NULL; | |
| 140 } | |
| 141 | |
| 142 return parent; | |
| 143 } | |
| 144 | |
| 145 } // namespace | |
| 146 | |
| 147 namespace extensions { | |
| 148 | |
| 149 namespace Create = api::context_menus::Create; | |
| 150 namespace Remove = api::context_menus::Remove; | |
| 151 namespace Update = api::context_menus::Update; | |
| 152 | |
| 153 bool CreateContextMenuFunction::RunImpl() { | |
| 154 MenuItem::Id id(profile()->IsOffTheRecord(), extension_id()); | |
| 155 scoped_ptr<Create::Params> params(Create::Params::Create(*args_)); | |
| 156 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
| 157 | |
| 158 if (params->create_properties.id.get()) { | |
| 159 id.string_uid = *params->create_properties.id; | |
| 160 } else { | |
| 161 if (GetExtension()->has_lazy_background_page()) { | |
| 162 error_ = kIdRequiredError; | |
| 163 return false; | |
| 164 } | |
| 165 | |
| 166 // The Generated Id is added by context_menus_custom_bindings.js. | |
| 167 DictionaryValue* properties = NULL; | |
| 168 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &properties)); | |
| 169 EXTENSION_FUNCTION_VALIDATE(properties->GetInteger(kGeneratedIdKey, | |
| 170 &id.uid)); | |
| 171 } | |
| 172 | |
| 173 std::string title; | |
| 174 if (params->create_properties.title.get()) | |
| 175 title = *params->create_properties.title; | |
| 176 | |
| 177 MenuManager* menu_manager = profile()->GetExtensionService()->menu_manager(); | |
| 178 | |
| 179 if (menu_manager->GetItemById(id)) { | |
| 180 error_ = ErrorUtils::FormatErrorMessage(kDuplicateIDError, | |
| 181 GetIDString(id)); | |
| 182 return false; | |
| 183 } | |
| 184 | |
| 185 if (GetExtension()->has_lazy_background_page() && | |
| 186 params->create_properties.onclick.get()) { | |
| 187 error_ = kOnclickDisallowedError; | |
| 188 return false; | |
| 189 } | |
| 190 | |
| 191 MenuItem::ContextList contexts; | |
| 192 if (params->create_properties.contexts.get()) | |
| 193 contexts = GetContexts(params->create_properties); | |
| 194 else | |
| 195 contexts.Add(MenuItem::PAGE); | |
| 196 | |
| 197 if (contexts.Contains(MenuItem::LAUNCHER) && | |
| 198 !GetExtension()->is_platform_app()) { | |
| 199 error_ = kLauncherNotAllowedError; | |
| 200 return false; | |
| 201 } | |
| 202 | |
| 203 MenuItem::Type type = GetType(params->create_properties); | |
| 204 | |
| 205 if (title.empty() && type != MenuItem::SEPARATOR) { | |
| 206 error_ = kTitleNeededError; | |
| 207 return false; | |
| 208 } | |
| 209 | |
| 210 bool checked = false; | |
| 211 if (params->create_properties.checked.get()) | |
| 212 checked = *params->create_properties.checked; | |
| 213 | |
| 214 bool enabled = true; | |
| 215 if (params->create_properties.enabled.get()) | |
| 216 enabled = *params->create_properties.enabled; | |
| 217 | |
| 218 scoped_ptr<MenuItem> item( | |
| 219 new MenuItem(id, title, checked, enabled, type, contexts)); | |
| 220 | |
| 221 if (!item->PopulateURLPatterns( | |
| 222 params->create_properties.document_url_patterns.get(), | |
| 223 params->create_properties.target_url_patterns.get(), | |
| 224 &error_)) { | |
| 225 return false; | |
| 226 } | |
| 227 | |
| 228 bool success = true; | |
| 229 scoped_ptr<MenuItem::Id> parent_id(GetParentId(params->create_properties, | |
| 230 profile()->IsOffTheRecord(), | |
| 231 extension_id())); | |
| 232 if (parent_id.get()) { | |
| 233 MenuItem* parent = GetParent(*parent_id, menu_manager, &error_); | |
| 234 if (!parent) | |
| 235 return false; | |
| 236 success = menu_manager->AddChildItem(parent->id(), item.release()); | |
| 237 } else { | |
| 238 success = menu_manager->AddContextItem(GetExtension(), item.release()); | |
| 239 } | |
| 240 | |
| 241 if (!success) | |
| 242 return false; | |
| 243 | |
| 244 menu_manager->WriteToStorage(GetExtension()); | |
| 245 return true; | |
| 246 } | |
| 247 | |
| 248 bool UpdateContextMenuFunction::RunImpl() { | |
| 249 bool radio_item_updated = false; | |
| 250 MenuItem::Id item_id(profile()->IsOffTheRecord(), extension_id()); | |
| 251 scoped_ptr<Update::Params> params(Update::Params::Create(*args_)); | |
| 252 | |
| 253 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
| 254 switch (params->id_type) { | |
| 255 case Update::Params::ID_STRING: | |
| 256 item_id.string_uid = *params->id_string; | |
| 257 break; | |
| 258 case Update::Params::ID_INTEGER: | |
| 259 item_id.uid = *params->id_integer; | |
| 260 break; | |
| 261 case Update::Params::ID_NONE: | |
| 262 NOTREACHED(); | |
| 263 } | |
| 264 | |
| 265 ExtensionService* service = profile()->GetExtensionService(); | |
| 266 MenuManager* manager = service->menu_manager(); | |
| 267 MenuItem* item = manager->GetItemById(item_id); | |
| 268 if (!item || item->extension_id() != extension_id()) { | |
| 269 error_ = ErrorUtils::FormatErrorMessage( | |
| 270 kCannotFindItemError, GetIDString(item_id)); | |
| 271 return false; | |
| 272 } | |
| 273 | |
| 274 // Type. | |
| 275 MenuItem::Type type = GetType(params->update_properties); | |
| 276 | |
| 277 if (type != item->type()) { | |
| 278 if (type == MenuItem::RADIO || item->type() == MenuItem::RADIO) | |
| 279 radio_item_updated = true; | |
| 280 item->set_type(type); | |
| 281 } | |
| 282 | |
| 283 // Title. | |
| 284 if (params->update_properties.title.get()) { | |
| 285 std::string title(*params->update_properties.title); | |
| 286 if (title.empty() && item->type() != MenuItem::SEPARATOR) { | |
| 287 error_ = kTitleNeededError; | |
| 288 return false; | |
| 289 } | |
| 290 item->set_title(title); | |
| 291 } | |
| 292 | |
| 293 // Checked state. | |
| 294 if (params->update_properties.checked.get()) { | |
| 295 bool checked = *params->update_properties.checked; | |
| 296 if (checked && | |
| 297 item->type() != MenuItem::CHECKBOX && | |
| 298 item->type() != MenuItem::RADIO) { | |
| 299 error_ = kCheckedError; | |
| 300 return false; | |
| 301 } | |
| 302 if (checked != item->checked()) { | |
| 303 if (!item->SetChecked(checked)) { | |
| 304 error_ = kCheckedError; | |
| 305 return false; | |
| 306 } | |
| 307 radio_item_updated = true; | |
| 308 } | |
| 309 } | |
| 310 | |
| 311 // Enabled. | |
| 312 if (params->update_properties.enabled.get()) | |
| 313 item->set_enabled(*params->update_properties.enabled); | |
| 314 | |
| 315 // Contexts. | |
| 316 MenuItem::ContextList contexts; | |
| 317 if (params->update_properties.contexts.get()) { | |
| 318 contexts = GetContexts(params->update_properties); | |
| 319 | |
| 320 if (contexts.Contains(MenuItem::LAUNCHER) && | |
| 321 !GetExtension()->is_platform_app()) { | |
| 322 error_ = kLauncherNotAllowedError; | |
| 323 return false; | |
| 324 } | |
| 325 | |
| 326 if (contexts != item->contexts()) | |
| 327 item->set_contexts(contexts); | |
| 328 } | |
| 329 | |
| 330 // Parent id. | |
| 331 MenuItem* parent = NULL; | |
| 332 scoped_ptr<MenuItem::Id> parent_id(GetParentId(params->update_properties, | |
| 333 profile()->IsOffTheRecord(), | |
| 334 extension_id())); | |
| 335 if (parent_id.get()) { | |
| 336 MenuItem* parent = GetParent(*parent_id, manager, &error_); | |
| 337 if (!parent || !manager->ChangeParent(item->id(), &parent->id())) | |
| 338 return false; | |
| 339 } | |
| 340 | |
| 341 // URL Patterns. | |
| 342 if (!item->PopulateURLPatterns( | |
| 343 params->update_properties.document_url_patterns.get(), | |
| 344 params->update_properties.target_url_patterns.get(), &error_)) { | |
| 345 return false; | |
| 346 } | |
| 347 | |
| 348 // There is no need to call ItemUpdated if ChangeParent is called because | |
| 349 // all sanitation is taken care of in ChangeParent. | |
| 350 if (!parent && radio_item_updated && !manager->ItemUpdated(item->id())) | |
| 351 return false; | |
| 352 | |
| 353 manager->WriteToStorage(GetExtension()); | |
| 354 return true; | |
| 355 } | |
| 356 | |
| 357 bool RemoveContextMenuFunction::RunImpl() { | |
| 358 scoped_ptr<Remove::Params> params(Remove::Params::Create(*args_)); | |
| 359 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
| 360 | |
| 361 ExtensionService* service = profile()->GetExtensionService(); | |
| 362 MenuManager* manager = service->menu_manager(); | |
| 363 | |
| 364 MenuItem::Id id(profile()->IsOffTheRecord(), extension_id()); | |
| 365 switch (params->menu_item_id_type) { | |
| 366 case Remove::Params::MENU_ITEM_ID_STRING: | |
| 367 id.string_uid = *params->menu_item_id_string; | |
| 368 break; | |
| 369 case Remove::Params::MENU_ITEM_ID_INTEGER: | |
| 370 id.uid = *params->menu_item_id_integer; | |
| 371 break; | |
| 372 case Remove::Params::MENU_ITEM_ID_NONE: | |
| 373 NOTREACHED(); | |
| 374 } | |
| 375 | |
| 376 MenuItem* item = manager->GetItemById(id); | |
| 377 // Ensure one extension can't remove another's menu items. | |
| 378 if (!item || item->extension_id() != extension_id()) { | |
| 379 error_ = ErrorUtils::FormatErrorMessage( | |
| 380 kCannotFindItemError, GetIDString(id)); | |
| 381 return false; | |
| 382 } | |
| 383 | |
| 384 if (!manager->RemoveContextMenuItem(id)) | |
| 385 return false; | |
| 386 manager->WriteToStorage(GetExtension()); | |
| 387 return true; | |
| 388 } | |
| 389 | |
| 390 bool RemoveAllContextMenusFunction::RunImpl() { | |
| 391 ExtensionService* service = profile()->GetExtensionService(); | |
| 392 MenuManager* manager = service->menu_manager(); | |
| 393 manager->RemoveAllContextItems(GetExtension()->id()); | |
| 394 manager->WriteToStorage(GetExtension()); | |
| 395 return true; | |
| 396 } | |
| 397 | |
| 398 } // namespace extensions | |
| OLD | NEW |