// 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;
}