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