blob: 5f258a83fa753c81167951fe2542f8b0b55c08f4 [file] [log] [blame]
// Copyright (c) 2012 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/renderer/pepper/pepper_flash_menu_host.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/common/context_menu_params.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/renderer_ppapi_host.h"
#include "ipc/ipc_message.h"
#include "ppapi/c/private/ppb_flash_menu.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/serialized_flash_menu.h"
#include "ui/gfx/point.h"
namespace {
// Maximum depth of submenus allowed (e.g., 1 indicates that submenus are
// allowed, but not sub-submenus).
const size_t kMaxMenuDepth = 2;
// Maximum number of entries in any single menu (including separators).
const size_t kMaxMenuEntries = 50;
// Maximum total number of entries in the |menu_id_map| (see below).
// (Limit to 500 real entries; reserve the 0 action as an invalid entry.)
const size_t kMaxMenuIdMapEntries = 501;
// Converts menu data from one form to another.
// - |depth| is the current nested depth (call it starting with 0).
// - |menu_id_map| is such that |menu_id_map[output_item.action] ==
// input_item.id| (where |action| is what a |MenuItem| has, |id| is what a
// |PP_Flash_MenuItem| has).
bool ConvertMenuData(const PP_Flash_Menu* in_menu,
size_t depth,
std::vector<content::MenuItem>* out_menu,
std::vector<int32_t>* menu_id_map) {
if (depth > kMaxMenuDepth || !in_menu)
return false;
// Clear the output, just in case.
out_menu->clear();
if (!in_menu->count)
return true; // Nothing else to do.
if (!in_menu->items || in_menu->count > kMaxMenuEntries)
return false;
for (uint32_t i = 0; i < in_menu->count; i++) {
content::MenuItem item;
PP_Flash_MenuItem_Type type = in_menu->items[i].type;
switch (type) {
case PP_FLASH_MENUITEM_TYPE_NORMAL:
item.type = content::MenuItem::OPTION;
break;
case PP_FLASH_MENUITEM_TYPE_CHECKBOX:
item.type = content::MenuItem::CHECKABLE_OPTION;
break;
case PP_FLASH_MENUITEM_TYPE_SEPARATOR:
item.type = content::MenuItem::SEPARATOR;
break;
case PP_FLASH_MENUITEM_TYPE_SUBMENU:
item.type = content::MenuItem::SUBMENU;
break;
default:
return false;
}
if (in_menu->items[i].name)
item.label = UTF8ToUTF16(in_menu->items[i].name);
if (menu_id_map->size() >= kMaxMenuIdMapEntries)
return false;
item.action = static_cast<unsigned>(menu_id_map->size());
// This sets |(*menu_id_map)[item.action] = in_menu->items[i].id|.
menu_id_map->push_back(in_menu->items[i].id);
item.enabled = PP_ToBool(in_menu->items[i].enabled);
item.checked = PP_ToBool(in_menu->items[i].checked);
if (type == PP_FLASH_MENUITEM_TYPE_SUBMENU) {
if (!ConvertMenuData(in_menu->items[i].submenu, depth + 1, &item.submenu,
menu_id_map))
return false;
}
out_menu->push_back(item);
}
return true;
}
} // namespace
PepperFlashMenuHost::PepperFlashMenuHost(
content::RendererPpapiHost* host,
PP_Instance instance,
PP_Resource resource,
const ppapi::proxy::SerializedFlashMenu& serial_menu)
: ppapi::host::ResourceHost(host->GetPpapiHost(), instance, resource),
renderer_ppapi_host_(host),
showing_context_menu_(false),
context_menu_request_id_(0),
has_saved_context_menu_action_(false),
saved_context_menu_action_(0) {
menu_id_map_.push_back(0); // Reserve |menu_id_map_[0]|.
if (!ConvertMenuData(serial_menu.pp_menu(), 0, &menu_data_, &menu_id_map_)) {
menu_data_.clear();
menu_id_map_.clear();
}
}
PepperFlashMenuHost::~PepperFlashMenuHost() {
if (showing_context_menu_) {
content::RenderView* render_view =
renderer_ppapi_host_->GetRenderViewForInstance(pp_instance());
if (render_view)
render_view->CancelContextMenu(context_menu_request_id_);
}
}
int32_t PepperFlashMenuHost::OnResourceMessageReceived(
const IPC::Message& msg,
ppapi::host::HostMessageContext* context) {
IPC_BEGIN_MESSAGE_MAP(PepperFlashMenuHost, msg)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashMenu_Show,
OnHostMsgShow)
IPC_END_MESSAGE_MAP()
return PP_ERROR_FAILED;
}
int32_t PepperFlashMenuHost::OnHostMsgShow(
ppapi::host::HostMessageContext* context,
const PP_Point& location) {
// Note that all early returns must do a SendMenuReply. The sync result for
// this message isn't used, so to forward the error to the plugin, we need to
// additionally call SendMenuReply explicitly.
if (menu_data_.empty()) {
SendMenuReply(PP_ERROR_FAILED, -1);
return PP_ERROR_FAILED;
}
if (showing_context_menu_) {
SendMenuReply(PP_ERROR_INPROGRESS, -1);
return PP_ERROR_INPROGRESS;
}
content::RenderView* render_view =
renderer_ppapi_host_->GetRenderViewForInstance(pp_instance());
content::ContextMenuParams params;
params.x = location.x;
params.y = location.y;
params.custom_context.is_pepper_menu = true;
params.custom_context.render_widget_id =
renderer_ppapi_host_->GetRoutingIDForWidget(pp_instance());
params.custom_items = menu_data_;
// Transform the position to be in render view's coordinates.
gfx::Point render_view_pt = renderer_ppapi_host_->PluginPointToRenderView(
pp_instance(), gfx::Point(location.x, location.y));
params.x = render_view_pt.x();
params.y = render_view_pt.y();
showing_context_menu_ = true;
context_menu_request_id_ = render_view->ShowContextMenu(this, params);
// Note: the show message is sync so this OK is for the sync reply which we
// don't actually use (see the comment in the resource file for this). The
// async message containing the context menu action will be sent in the
// future.
return PP_OK;
}
void PepperFlashMenuHost::OnMenuAction(int request_id, unsigned action) {
// Just save the action.
DCHECK(!has_saved_context_menu_action_);
has_saved_context_menu_action_ = true;
saved_context_menu_action_ = action;
}
void PepperFlashMenuHost::OnMenuClosed(int request_id) {
if (has_saved_context_menu_action_ &&
saved_context_menu_action_ < menu_id_map_.size()) {
SendMenuReply(PP_OK, menu_id_map_[saved_context_menu_action_]);
has_saved_context_menu_action_ = false;
saved_context_menu_action_ = 0;
} else {
SendMenuReply(PP_ERROR_USERCANCEL, -1);
}
showing_context_menu_ = false;
context_menu_request_id_ = 0;
}
void PepperFlashMenuHost::SendMenuReply(int32_t result, int action) {
ppapi::host::ReplyMessageContext reply_context(
ppapi::proxy::ResourceMessageReplyParams(pp_resource(), 0),
NULL, MSG_ROUTING_NONE);
reply_context.params.set_result(result);
host()->SendReply(reply_context,
PpapiPluginMsg_FlashMenu_ShowReply(action));
}