Home | History | Annotate | Download | only in context_menus
      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