1 // Copyright 2014 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 // Definition of helper functions for the ContextMenus API. 6 7 #ifndef CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_HELPERS_H_ 8 #define CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_HELPERS_H_ 9 10 #include "chrome/browser/extensions/menu_manager.h" 11 #include "chrome/browser/profiles/profile.h" 12 #include "extensions/common/error_utils.h" 13 #include "extensions/common/manifest_handlers/background_info.h" 14 15 namespace extensions { 16 namespace context_menus_api_helpers { 17 18 namespace { 19 20 template <typename PropertyWithEnumT> 21 scoped_ptr<extensions::MenuItem::Id> GetParentId( 22 const PropertyWithEnumT& property, 23 bool is_off_the_record, 24 const MenuItem::ExtensionKey& key) { 25 if (!property.parent_id) 26 return scoped_ptr<extensions::MenuItem::Id>(); 27 28 scoped_ptr<extensions::MenuItem::Id> parent_id( 29 new extensions::MenuItem::Id(is_off_the_record, key)); 30 if (property.parent_id->as_integer) 31 parent_id->uid = *property.parent_id->as_integer; 32 else if (property.parent_id->as_string) 33 parent_id->string_uid = *property.parent_id->as_string; 34 else 35 NOTREACHED(); 36 return parent_id.Pass(); 37 } 38 39 } // namespace 40 41 extern const char kActionNotAllowedError[]; 42 extern const char kCannotFindItemError[]; 43 extern const char kCheckedError[]; 44 extern const char kDuplicateIDError[]; 45 extern const char kGeneratedIdKey[]; 46 extern const char kLauncherNotAllowedError[]; 47 extern const char kOnclickDisallowedError[]; 48 extern const char kParentsMustBeNormalError[]; 49 extern const char kTitleNeededError[]; 50 51 std::string GetIDString(const MenuItem::Id& id); 52 53 MenuItem* GetParent(MenuItem::Id parent_id, 54 const MenuManager* menu_manager, 55 std::string* error); 56 57 template<typename PropertyWithEnumT> 58 MenuItem::ContextList GetContexts(const PropertyWithEnumT& property) { 59 MenuItem::ContextList contexts; 60 for (size_t i = 0; i < property.contexts->size(); ++i) { 61 switch (property.contexts->at(i)) { 62 case PropertyWithEnumT::CONTEXTS_TYPE_ALL: 63 contexts.Add(extensions::MenuItem::ALL); 64 break; 65 case PropertyWithEnumT::CONTEXTS_TYPE_PAGE: 66 contexts.Add(extensions::MenuItem::PAGE); 67 break; 68 case PropertyWithEnumT::CONTEXTS_TYPE_SELECTION: 69 contexts.Add(extensions::MenuItem::SELECTION); 70 break; 71 case PropertyWithEnumT::CONTEXTS_TYPE_LINK: 72 contexts.Add(extensions::MenuItem::LINK); 73 break; 74 case PropertyWithEnumT::CONTEXTS_TYPE_EDITABLE: 75 contexts.Add(extensions::MenuItem::EDITABLE); 76 break; 77 case PropertyWithEnumT::CONTEXTS_TYPE_IMAGE: 78 contexts.Add(extensions::MenuItem::IMAGE); 79 break; 80 case PropertyWithEnumT::CONTEXTS_TYPE_VIDEO: 81 contexts.Add(extensions::MenuItem::VIDEO); 82 break; 83 case PropertyWithEnumT::CONTEXTS_TYPE_AUDIO: 84 contexts.Add(extensions::MenuItem::AUDIO); 85 break; 86 case PropertyWithEnumT::CONTEXTS_TYPE_FRAME: 87 contexts.Add(extensions::MenuItem::FRAME); 88 break; 89 case PropertyWithEnumT::CONTEXTS_TYPE_LAUNCHER: 90 // Not available for <webview>. 91 contexts.Add(extensions::MenuItem::LAUNCHER); 92 break; 93 case PropertyWithEnumT::CONTEXTS_TYPE_BROWSER_ACTION: 94 // Not available for <webview>. 95 contexts.Add(extensions::MenuItem::BROWSER_ACTION); 96 break; 97 case PropertyWithEnumT::CONTEXTS_TYPE_PAGE_ACTION: 98 // Not available for <webview>. 99 contexts.Add(extensions::MenuItem::PAGE_ACTION); 100 break; 101 case PropertyWithEnumT::CONTEXTS_TYPE_NONE: 102 NOTREACHED(); 103 } 104 } 105 return contexts; 106 } 107 108 template<typename PropertyWithEnumT> 109 MenuItem::Type GetType(const PropertyWithEnumT& property, 110 MenuItem::Type default_type) { 111 switch (property.type) { 112 case PropertyWithEnumT::TYPE_NONE: 113 return default_type; 114 case PropertyWithEnumT::TYPE_NORMAL: 115 return extensions::MenuItem::NORMAL; 116 case PropertyWithEnumT::TYPE_CHECKBOX: 117 return extensions::MenuItem::CHECKBOX; 118 case PropertyWithEnumT::TYPE_RADIO: 119 return extensions::MenuItem::RADIO; 120 case PropertyWithEnumT::TYPE_SEPARATOR: 121 return extensions::MenuItem::SEPARATOR; 122 } 123 return extensions::MenuItem::NORMAL; 124 } 125 126 // Creates and adds a menu item from |create_properties|. 127 template<typename PropertyWithEnumT> 128 bool CreateMenuItem(const PropertyWithEnumT& create_properties, 129 Profile* profile, 130 const Extension* extension, 131 const MenuItem::Id& item_id, 132 std::string* error) { 133 bool is_webview = item_id.extension_key.webview_instance_id != 0; 134 MenuManager* menu_manager = MenuManager::Get(profile); 135 136 if (menu_manager->GetItemById(item_id)) { 137 *error = ErrorUtils::FormatErrorMessage(kDuplicateIDError, 138 GetIDString(item_id)); 139 return false; 140 } 141 142 if (!is_webview && BackgroundInfo::HasLazyBackgroundPage(extension) && 143 create_properties.onclick.get()) { 144 *error = kOnclickDisallowedError; 145 return false; 146 } 147 148 // Contexts. 149 MenuItem::ContextList contexts; 150 if (create_properties.contexts.get()) 151 contexts = GetContexts(create_properties); 152 else 153 contexts.Add(MenuItem::PAGE); 154 155 if (contexts.Contains(MenuItem::LAUNCHER)) { 156 // Launcher item is not allowed for <webview>. 157 if (!extension->is_platform_app() || is_webview) { 158 *error = kLauncherNotAllowedError; 159 return false; 160 } 161 } 162 163 if (contexts.Contains(MenuItem::BROWSER_ACTION) || 164 contexts.Contains(MenuItem::PAGE_ACTION)) { 165 // Action items are not allowed for <webview>. 166 if (!extension->is_extension() || is_webview) { 167 *error = kActionNotAllowedError; 168 return false; 169 } 170 } 171 172 // Title. 173 std::string title; 174 if (create_properties.title.get()) 175 title = *create_properties.title; 176 177 MenuItem::Type type = GetType(create_properties, MenuItem::NORMAL); 178 if (title.empty() && type != MenuItem::SEPARATOR) { 179 *error = kTitleNeededError; 180 return false; 181 } 182 183 // Checked state. 184 bool checked = false; 185 if (create_properties.checked.get()) 186 checked = *create_properties.checked; 187 188 // Enabled. 189 bool enabled = true; 190 if (create_properties.enabled.get()) 191 enabled = *create_properties.enabled; 192 193 scoped_ptr<MenuItem> item( 194 new MenuItem(item_id, title, checked, enabled, type, contexts)); 195 196 // URL Patterns. 197 if (!item->PopulateURLPatterns( 198 create_properties.document_url_patterns.get(), 199 create_properties.target_url_patterns.get(), 200 error)) { 201 return false; 202 } 203 204 // Parent id. 205 bool success = true; 206 scoped_ptr<MenuItem::Id> parent_id(GetParentId( 207 create_properties, profile->IsOffTheRecord(), item_id.extension_key)); 208 if (parent_id.get()) { 209 MenuItem* parent = GetParent(*parent_id, menu_manager, error); 210 if (!parent) 211 return false; 212 success = menu_manager->AddChildItem(parent->id(), item.release()); 213 } else { 214 success = menu_manager->AddContextItem(extension, item.release()); 215 } 216 217 if (!success) 218 return false; 219 220 menu_manager->WriteToStorage(extension, item_id.extension_key); 221 return true; 222 } 223 224 // Updates a menu item from |update_properties|. 225 template<typename PropertyWithEnumT> 226 bool UpdateMenuItem(const PropertyWithEnumT& update_properties, 227 Profile* profile, 228 const Extension* extension, 229 const MenuItem::Id& item_id, 230 std::string* error) { 231 bool radio_item_updated = false; 232 bool is_webview = item_id.extension_key.webview_instance_id != 0; 233 MenuManager* menu_manager = MenuManager::Get(profile); 234 235 MenuItem* item = menu_manager->GetItemById(item_id); 236 if (!item || item->extension_id() != extension->id()){ 237 *error = ErrorUtils::FormatErrorMessage( 238 kCannotFindItemError, GetIDString(item_id)); 239 return false; 240 } 241 242 // Type. 243 MenuItem::Type type = GetType(update_properties, item->type()); 244 245 if (type != item->type()) { 246 if (type == MenuItem::RADIO || item->type() == MenuItem::RADIO) 247 radio_item_updated = true; 248 item->set_type(type); 249 } 250 251 // Title. 252 if (update_properties.title.get()) { 253 std::string title(*update_properties.title); 254 if (title.empty() && item->type() != MenuItem::SEPARATOR) { 255 *error = kTitleNeededError; 256 return false; 257 } 258 item->set_title(title); 259 } 260 261 // Checked state. 262 if (update_properties.checked.get()) { 263 bool checked = *update_properties.checked; 264 if (checked && 265 item->type() != MenuItem::CHECKBOX && 266 item->type() != MenuItem::RADIO) { 267 *error = kCheckedError; 268 return false; 269 } 270 if (checked != item->checked()) { 271 if (!item->SetChecked(checked)) { 272 *error = kCheckedError; 273 return false; 274 } 275 radio_item_updated = true; 276 } 277 } 278 279 // Enabled. 280 if (update_properties.enabled.get()) 281 item->set_enabled(*update_properties.enabled); 282 283 // Contexts. 284 MenuItem::ContextList contexts; 285 if (update_properties.contexts.get()) { 286 contexts = GetContexts(update_properties); 287 288 if (contexts.Contains(MenuItem::LAUNCHER)) { 289 // Launcher item is not allowed for <webview>. 290 if (!extension->is_platform_app() || is_webview) { 291 *error = kLauncherNotAllowedError; 292 return false; 293 } 294 } 295 296 if (contexts != item->contexts()) 297 item->set_contexts(contexts); 298 } 299 300 // Parent id. 301 MenuItem* parent = NULL; 302 scoped_ptr<MenuItem::Id> parent_id(GetParentId( 303 update_properties, profile->IsOffTheRecord(), item_id.extension_key)); 304 if (parent_id.get()) { 305 MenuItem* parent = GetParent(*parent_id, menu_manager, error); 306 if (!parent || !menu_manager->ChangeParent(item->id(), &parent->id())) 307 return false; 308 } 309 310 // URL Patterns. 311 if (!item->PopulateURLPatterns( 312 update_properties.document_url_patterns.get(), 313 update_properties.target_url_patterns.get(), error)) { 314 return false; 315 } 316 317 // There is no need to call ItemUpdated if ChangeParent is called because 318 // all sanitation is taken care of in ChangeParent. 319 if (!parent && radio_item_updated && !menu_manager->ItemUpdated(item->id())) 320 return false; 321 322 menu_manager->WriteToStorage(extension, item_id.extension_key); 323 return true; 324 } 325 326 } // namespace context_menus_api_helpers 327 } // namespace extensions 328 329 #endif // CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_HELPERS_H_ 330