| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/extension_context_menu_api.h" |
| |
| #include <string> |
| |
| #include "base/values.h" |
| #include "base/string_number_conversions.h" |
| #include "base/string_util.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/extensions/extension_error_utils.h" |
| |
| const char kCheckedKey[] = "checked"; |
| const char kContextsKey[] = "contexts"; |
| const char kDocumentUrlPatternsKey[] = "documentUrlPatterns"; |
| const char kGeneratedIdKey[] = "generatedId"; |
| const char kParentIdKey[] = "parentId"; |
| const char kTargetUrlPatternsKey[] = "targetUrlPatterns"; |
| const char kTitleKey[] = "title"; |
| const char kTypeKey[] = "type"; |
| |
| const char kCannotFindItemError[] = "Cannot find menu item with id *"; |
| const char kCheckedError[] = |
| "Only items with type \"radio\" or \"checkbox\" can be checked"; |
| const char kInvalidURLPatternError[] = "Invalid url pattern '*'"; |
| const char kInvalidValueError[] = "Invalid value for *"; |
| const char kInvalidTypeStringError[] = "Invalid type string '*'"; |
| const char kParentsMustBeNormalError[] = |
| "Parent items must have type \"normal\""; |
| const char kTitleNeededError[] = |
| "All menu items except for separators must have a title"; |
| |
| |
| bool ExtensionContextMenuFunction::ParseContexts( |
| const DictionaryValue& properties, |
| const char* key, |
| ExtensionMenuItem::ContextList* result) { |
| ListValue* list = NULL; |
| if (!properties.GetList(key, &list)) { |
| return true; |
| } |
| ExtensionMenuItem::ContextList tmp_result; |
| |
| std::string value; |
| for (size_t i = 0; i < list->GetSize(); i++) { |
| if (!list->GetString(i, &value)) |
| return false; |
| |
| if (value == "all") { |
| tmp_result.Add(ExtensionMenuItem::ALL); |
| } else if (value == "page") { |
| tmp_result.Add(ExtensionMenuItem::PAGE); |
| } else if (value == "selection") { |
| tmp_result.Add(ExtensionMenuItem::SELECTION); |
| } else if (value == "link") { |
| tmp_result.Add(ExtensionMenuItem::LINK); |
| } else if (value == "editable") { |
| tmp_result.Add(ExtensionMenuItem::EDITABLE); |
| } else if (value == "image") { |
| tmp_result.Add(ExtensionMenuItem::IMAGE); |
| } else if (value == "video") { |
| tmp_result.Add(ExtensionMenuItem::VIDEO); |
| } else if (value == "audio") { |
| tmp_result.Add(ExtensionMenuItem::AUDIO); |
| } else if (value == "frame") { |
| tmp_result.Add(ExtensionMenuItem::FRAME); |
| } else { |
| error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidValueError, key); |
| return false; |
| } |
| } |
| *result = tmp_result; |
| return true; |
| } |
| |
| bool ExtensionContextMenuFunction::ParseType( |
| const DictionaryValue& properties, |
| const ExtensionMenuItem::Type& default_value, |
| ExtensionMenuItem::Type* result) { |
| DCHECK(result); |
| if (!properties.HasKey(kTypeKey)) { |
| *result = default_value; |
| return true; |
| } |
| |
| std::string type_string; |
| if (!properties.GetString(kTypeKey, &type_string)) |
| return false; |
| |
| if (type_string == "normal") { |
| *result = ExtensionMenuItem::NORMAL; |
| } else if (type_string == "checkbox") { |
| *result = ExtensionMenuItem::CHECKBOX; |
| } else if (type_string == "radio") { |
| *result = ExtensionMenuItem::RADIO; |
| } else if (type_string == "separator") { |
| *result = ExtensionMenuItem::SEPARATOR; |
| } else { |
| error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidTypeStringError, |
| type_string); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ExtensionContextMenuFunction::ParseChecked( |
| ExtensionMenuItem::Type type, |
| const DictionaryValue& properties, |
| bool default_value, |
| bool* checked) { |
| if (!properties.HasKey(kCheckedKey)) { |
| *checked = default_value; |
| return true; |
| } |
| if (!properties.GetBoolean(kCheckedKey, checked)) |
| return false; |
| if (checked && type != ExtensionMenuItem::CHECKBOX && |
| type != ExtensionMenuItem::RADIO) { |
| error_ = kCheckedError; |
| return false; |
| } |
| return true; |
| } |
| |
| bool ExtensionContextMenuFunction::ParseURLPatterns( |
| const DictionaryValue& properties, |
| const char* key, |
| ExtensionExtent* result) { |
| if (!properties.HasKey(key)) |
| return true; |
| ListValue* list = NULL; |
| if (!properties.GetList(key, &list)) |
| return false; |
| for (ListValue::iterator i = list->begin(); i != list->end(); ++i) { |
| std::string tmp; |
| if (!(*i)->GetAsString(&tmp)) |
| return false; |
| |
| URLPattern pattern(ExtensionMenuManager::kAllowedSchemes); |
| // TODO(skerner): Consider enabling strict pattern parsing |
| // if this extension's location indicates that it is under development. |
| if (URLPattern::PARSE_SUCCESS != pattern.Parse(tmp, |
| URLPattern::PARSE_LENIENT)) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidURLPatternError, |
| tmp); |
| return false; |
| } |
| result->AddPattern(pattern); |
| } |
| return true; |
| } |
| |
| bool ExtensionContextMenuFunction::SetURLPatterns( |
| const DictionaryValue& properties, |
| ExtensionMenuItem* item) { |
| // Process the documentUrlPattern value. |
| ExtensionExtent document_url_patterns; |
| if (!ParseURLPatterns(properties, kDocumentUrlPatternsKey, |
| &document_url_patterns)) |
| return false; |
| |
| if (!document_url_patterns.is_empty()) { |
| item->set_document_url_patterns(document_url_patterns); |
| } |
| |
| // Process the targetUrlPattern value. |
| ExtensionExtent target_url_patterns; |
| if (!ParseURLPatterns(properties, kTargetUrlPatternsKey, |
| &target_url_patterns)) |
| return false; |
| |
| if (!target_url_patterns.is_empty()) { |
| item->set_target_url_patterns(target_url_patterns); |
| } |
| |
| return true; |
| } |
| |
| bool ExtensionContextMenuFunction::GetParent( |
| const DictionaryValue& properties, |
| const ExtensionMenuManager& manager, |
| ExtensionMenuItem** result) { |
| if (!properties.HasKey(kParentIdKey)) |
| return true; |
| ExtensionMenuItem::Id parent_id(profile(), extension_id(), 0); |
| if (properties.HasKey(kParentIdKey) && |
| !properties.GetInteger(kParentIdKey, &parent_id.uid)) |
| return false; |
| |
| ExtensionMenuItem* parent = manager.GetItemById(parent_id); |
| if (!parent) { |
| error_ = "Cannot find menu item with id " + |
| base::IntToString(parent_id.uid); |
| return false; |
| } |
| if (parent->type() != ExtensionMenuItem::NORMAL) { |
| error_ = kParentsMustBeNormalError; |
| return false; |
| } |
| *result = parent; |
| return true; |
| } |
| |
| bool CreateContextMenuFunction::RunImpl() { |
| DictionaryValue* properties; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &properties)); |
| EXTENSION_FUNCTION_VALIDATE(properties != NULL); |
| |
| ExtensionMenuItem::Id id(profile(), extension_id(), 0); |
| EXTENSION_FUNCTION_VALIDATE(properties->GetInteger(kGeneratedIdKey, |
| &id.uid)); |
| std::string title; |
| if (properties->HasKey(kTitleKey) && |
| !properties->GetString(kTitleKey, &title)) |
| return false; |
| |
| ExtensionMenuManager* menu_manager = |
| profile()->GetExtensionService()->menu_manager(); |
| |
| ExtensionMenuItem::ContextList contexts(ExtensionMenuItem::PAGE); |
| if (!ParseContexts(*properties, kContextsKey, &contexts)) |
| return false; |
| |
| ExtensionMenuItem::Type type; |
| if (!ParseType(*properties, ExtensionMenuItem::NORMAL, &type)) |
| return false; |
| |
| if (title.empty() && type != ExtensionMenuItem::SEPARATOR) { |
| error_ = kTitleNeededError; |
| return false; |
| } |
| |
| bool checked; |
| if (!ParseChecked(type, *properties, false, &checked)) |
| return false; |
| |
| scoped_ptr<ExtensionMenuItem> item( |
| new ExtensionMenuItem(id, title, checked, type, contexts)); |
| |
| if (!SetURLPatterns(*properties, item.get())) |
| return false; |
| |
| bool success = true; |
| if (properties->HasKey(kParentIdKey)) { |
| ExtensionMenuItem::Id parent_id(profile(), extension_id(), 0); |
| EXTENSION_FUNCTION_VALIDATE(properties->GetInteger(kParentIdKey, |
| &parent_id.uid)); |
| ExtensionMenuItem* parent = menu_manager->GetItemById(parent_id); |
| if (!parent) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| kCannotFindItemError, base::IntToString(parent_id.uid)); |
| return false; |
| } |
| if (parent->type() != ExtensionMenuItem::NORMAL) { |
| error_ = kParentsMustBeNormalError; |
| return false; |
| } |
| success = menu_manager->AddChildItem(parent_id, item.release()); |
| } else { |
| success = menu_manager->AddContextItem(GetExtension(), item.release()); |
| } |
| |
| if (!success) |
| return false; |
| |
| return true; |
| } |
| |
| bool UpdateContextMenuFunction::RunImpl() { |
| ExtensionMenuItem::Id item_id(profile(), extension_id(), 0); |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &item_id.uid)); |
| |
| ExtensionService* service = profile()->GetExtensionService(); |
| ExtensionMenuManager* manager = service->menu_manager(); |
| ExtensionMenuItem* item = manager->GetItemById(item_id); |
| if (!item || item->extension_id() != extension_id()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| kCannotFindItemError, base::IntToString(item_id.uid)); |
| return false; |
| } |
| |
| DictionaryValue *properties = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &properties)); |
| EXTENSION_FUNCTION_VALIDATE(properties != NULL); |
| |
| ExtensionMenuManager* menu_manager = |
| profile()->GetExtensionService()->menu_manager(); |
| |
| // Type. |
| ExtensionMenuItem::Type type; |
| if (!ParseType(*properties, item->type(), &type)) |
| return false; |
| if (type != item->type()) |
| item->set_type(type); |
| |
| // Title. |
| if (properties->HasKey(kTitleKey)) { |
| std::string title; |
| EXTENSION_FUNCTION_VALIDATE(properties->GetString(kTitleKey, &title)); |
| if (title.empty() && type != ExtensionMenuItem::SEPARATOR) { |
| error_ = kTitleNeededError; |
| return false; |
| } |
| item->set_title(title); |
| } |
| |
| // Checked state. |
| bool checked; |
| if (!ParseChecked(item->type(), *properties, item->checked(), &checked)) |
| return false; |
| if (checked != item->checked()) { |
| if (!item->SetChecked(checked)) |
| return false; |
| } |
| |
| // Contexts. |
| ExtensionMenuItem::ContextList contexts(item->contexts()); |
| if (!ParseContexts(*properties, kContextsKey, &contexts)) |
| return false; |
| if (contexts != item->contexts()) |
| item->set_contexts(contexts); |
| |
| // Parent id. |
| ExtensionMenuItem* parent = NULL; |
| if (!GetParent(*properties, *menu_manager, &parent)) |
| return false; |
| if (parent && !menu_manager->ChangeParent(item->id(), &parent->id())) |
| return false; |
| |
| if (!SetURLPatterns(*properties, item)) |
| return false; |
| |
| return true; |
| } |
| |
| bool RemoveContextMenuFunction::RunImpl() { |
| ExtensionMenuItem::Id id(profile(), extension_id(), 0); |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &id.uid)); |
| ExtensionService* service = profile()->GetExtensionService(); |
| ExtensionMenuManager* manager = service->menu_manager(); |
| |
| ExtensionMenuItem* item = manager->GetItemById(id); |
| // Ensure one extension can't remove another's menu items. |
| if (!item || item->extension_id() != extension_id()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| kCannotFindItemError, base::IntToString(id.uid)); |
| return false; |
| } |
| |
| return manager->RemoveContextMenuItem(id); |
| } |
| |
| bool RemoveAllContextMenusFunction::RunImpl() { |
| ExtensionService* service = profile()->GetExtensionService(); |
| ExtensionMenuManager* manager = service->menu_manager(); |
| manager->RemoveAllContextItems(extension_id()); |
| return true; |
| } |