203 строки
7.0 KiB
C++
203 строки
7.0 KiB
C++
// 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_frame.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/geometry/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 = base::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::RenderFrame* render_frame =
|
|
renderer_ppapi_host_->GetRenderFrameForInstance(pp_instance());
|
|
if (render_frame)
|
|
render_frame->CancelContextMenu(context_menu_request_id_);
|
|
}
|
|
}
|
|
|
|
int32_t PepperFlashMenuHost::OnResourceMessageReceived(
|
|
const IPC::Message& msg,
|
|
ppapi::host::HostMessageContext* context) {
|
|
PPAPI_BEGIN_MESSAGE_MAP(PepperFlashMenuHost, msg)
|
|
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashMenu_Show,
|
|
OnHostMsgShow)
|
|
PPAPI_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::RenderFrame* render_frame =
|
|
renderer_ppapi_host_->GetRenderFrameForInstance(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 frame's coordinates.
|
|
gfx::Point render_frame_pt = renderer_ppapi_host_->PluginPointToRenderFrame(
|
|
pp_instance(), gfx::Point(location.x, location.y));
|
|
params.x = render_frame_pt.x();
|
|
params.y = render_frame_pt.y();
|
|
|
|
showing_context_menu_ = true;
|
|
context_menu_request_id_ = render_frame->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));
|
|
}
|