blob: 7dc82e0fd1a524cc96e070db92876e0dd80d8bb1 [file] [log] [blame]
// Copyright 2014 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.
// Definition of helper functions for the ContextMenus API.
#ifndef CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_HELPERS_H_
#define CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_HELPERS_H_
#include "chrome/browser/extensions/menu_manager.h"
#include "chrome/common/extensions/api/context_menus.h"
#include "content/public/browser/browser_context.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/manifest_handlers/background_info.h"
namespace extensions {
namespace context_menus_api_helpers {
namespace {
template <typename PropertyWithEnumT>
std::unique_ptr<extensions::MenuItem::Id> GetParentId(
const PropertyWithEnumT& property,
bool is_off_the_record,
const MenuItem::ExtensionKey& key) {
if (!property.parent_id)
return std::unique_ptr<extensions::MenuItem::Id>();
std::unique_ptr<extensions::MenuItem::Id> parent_id(
new extensions::MenuItem::Id(is_off_the_record, key));
if (property.parent_id->as_integer)
parent_id->uid = *property.parent_id->as_integer;
else if (property.parent_id->as_string)
parent_id->string_uid = *property.parent_id->as_string;
else
NOTREACHED();
return parent_id;
}
} // namespace
extern const char kActionNotAllowedError[];
extern const char kCannotFindItemError[];
extern const char kCheckedError[];
extern const char kDuplicateIDError[];
extern const char kGeneratedIdKey[];
extern const char kLauncherNotAllowedError[];
extern const char kOnclickDisallowedError[];
extern const char kParentsMustBeNormalError[];
extern const char kTitleNeededError[];
std::string GetIDString(const MenuItem::Id& id);
MenuItem* GetParent(MenuItem::Id parent_id,
const MenuManager* menu_manager,
std::string* error);
MenuItem::ContextList GetContexts(const std::vector<
extensions::api::context_menus::ContextType>& in_contexts);
MenuItem::Type GetType(extensions::api::context_menus::ItemType type,
MenuItem::Type default_type);
// Creates and adds a menu item from |create_properties|.
template <typename PropertyWithEnumT>
bool CreateMenuItem(const PropertyWithEnumT& create_properties,
content::BrowserContext* browser_context,
const Extension* extension,
const MenuItem::Id& item_id,
std::string* error) {
bool is_webview = item_id.extension_key.webview_instance_id != 0;
MenuManager* menu_manager = MenuManager::Get(browser_context);
if (menu_manager->GetItemById(item_id)) {
*error = ErrorUtils::FormatErrorMessage(kDuplicateIDError,
GetIDString(item_id));
return false;
}
if (!is_webview && BackgroundInfo::HasLazyBackgroundPage(extension) &&
create_properties.onclick.get()) {
*error = kOnclickDisallowedError;
return false;
}
// Contexts.
MenuItem::ContextList contexts;
if (create_properties.contexts.get())
contexts = GetContexts(*create_properties.contexts);
else
contexts.Add(MenuItem::PAGE);
if (contexts.Contains(MenuItem::LAUNCHER)) {
// Launcher item is not allowed for <webview>.
if (!extension->is_platform_app() || is_webview) {
*error = kLauncherNotAllowedError;
return false;
}
}
if (contexts.Contains(MenuItem::BROWSER_ACTION) ||
contexts.Contains(MenuItem::PAGE_ACTION)) {
// Action items are not allowed for <webview>.
if (!extension->is_extension() || is_webview) {
*error = kActionNotAllowedError;
return false;
}
}
// Title.
std::string title;
if (create_properties.title.get())
title = *create_properties.title;
MenuItem::Type type = GetType(create_properties.type, MenuItem::NORMAL);
if (title.empty() && type != MenuItem::SEPARATOR) {
*error = kTitleNeededError;
return false;
}
// Visibility state.
bool visible = true;
if (create_properties.visible)
visible = *create_properties.visible;
// Checked state.
bool checked = false;
if (create_properties.checked.get())
checked = *create_properties.checked;
// Enabled.
bool enabled = true;
if (create_properties.enabled.get())
enabled = *create_properties.enabled;
std::unique_ptr<MenuItem> item(
new MenuItem(item_id, title, checked, visible, enabled, type, contexts));
// URL Patterns.
if (!item->PopulateURLPatterns(
create_properties.document_url_patterns.get(),
create_properties.target_url_patterns.get(),
error)) {
return false;
}
// Parent id.
bool success = true;
std::unique_ptr<MenuItem::Id> parent_id(
GetParentId(create_properties, browser_context->IsOffTheRecord(),
item_id.extension_key));
if (parent_id.get()) {
MenuItem* parent = GetParent(*parent_id, menu_manager, error);
if (!parent)
return false;
success = menu_manager->AddChildItem(parent->id(), std::move(item));
} else {
success = menu_manager->AddContextItem(extension, std::move(item));
}
if (!success)
return false;
menu_manager->WriteToStorage(extension, item_id.extension_key);
return true;
}
// Updates a menu item from |update_properties|.
template <typename PropertyWithEnumT>
bool UpdateMenuItem(const PropertyWithEnumT& update_properties,
content::BrowserContext* browser_context,
const Extension* extension,
const MenuItem::Id& item_id,
std::string* error) {
bool radio_item_updated = false;
bool is_webview = item_id.extension_key.webview_instance_id != 0;
MenuManager* menu_manager = MenuManager::Get(browser_context);
MenuItem* item = menu_manager->GetItemById(item_id);
if (!item || item->extension_id() != extension->id()){
*error = ErrorUtils::FormatErrorMessage(
kCannotFindItemError, GetIDString(item_id));
return false;
}
// Type.
MenuItem::Type type = GetType(update_properties.type, item->type());
if (type != item->type()) {
if (type == MenuItem::RADIO || item->type() == MenuItem::RADIO)
radio_item_updated = true;
item->set_type(type);
}
// Title.
if (update_properties.title.get()) {
std::string title(*update_properties.title);
if (title.empty() && item->type() != MenuItem::SEPARATOR) {
*error = kTitleNeededError;
return false;
}
item->set_title(title);
}
// Checked state.
if (update_properties.checked.get()) {
bool checked = *update_properties.checked;
if (checked &&
item->type() != MenuItem::CHECKBOX &&
item->type() != MenuItem::RADIO) {
*error = kCheckedError;
return false;
}
const bool should_toggle_checked =
// If radio item was unchecked nothing should happen. The radio item
// should remain checked because there should always be one item checked
// in the radio list.
(item->type() == MenuItem::RADIO && checked) ||
// Checkboxes are always updated.
item->type() == MenuItem::CHECKBOX;
if (should_toggle_checked) {
if (!item->SetChecked(checked)) {
*error = kCheckedError;
return false;
}
radio_item_updated = true;
}
}
// Visibility state.
if (update_properties.visible)
item->set_visible(*update_properties.visible);
// Enabled.
if (update_properties.enabled.get())
item->set_enabled(*update_properties.enabled);
// Contexts.
MenuItem::ContextList contexts;
if (update_properties.contexts.get()) {
contexts = GetContexts(*update_properties.contexts);
if (contexts.Contains(MenuItem::LAUNCHER)) {
// Launcher item is not allowed for <webview>.
if (!extension->is_platform_app() || is_webview) {
*error = kLauncherNotAllowedError;
return false;
}
}
if (contexts != item->contexts())
item->set_contexts(contexts);
}
// Parent id.
MenuItem* parent = NULL;
std::unique_ptr<MenuItem::Id> parent_id(
GetParentId(update_properties, browser_context->IsOffTheRecord(),
item_id.extension_key));
if (parent_id.get()) {
MenuItem* parent = GetParent(*parent_id, menu_manager, error);
if (!parent || !menu_manager->ChangeParent(item->id(), &parent->id()))
return false;
}
// URL Patterns.
if (!item->PopulateURLPatterns(
update_properties.document_url_patterns.get(),
update_properties.target_url_patterns.get(), error)) {
return false;
}
// There is no need to call ItemUpdated if ChangeParent is called because
// all sanitation is taken care of in ChangeParent.
if (!parent && radio_item_updated && !menu_manager->ItemUpdated(item->id()))
return false;
menu_manager->WriteToStorage(extension, item_id.extension_key);
return true;
}
} // namespace context_menus_api_helpers
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_HELPERS_H_