blob: aa3f768398763bce105da4383e9a85d3543d0d7d [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 "webkit/plugins/ppapi/ppb_flash_menu_impl.h"
#include "base/utf_string_conversions.h"
#include "ppapi/c/pp_completion_callback.h"
#include "ui/gfx/point.h"
#include "webkit/glue/webmenuitem.h"
#include "webkit/plugins/ppapi/common.h"
#include "webkit/plugins/ppapi/plugin_delegate.h"
#include "webkit/plugins/ppapi/plugin_module.h"
#include "webkit/plugins/ppapi/ppapi_plugin_instance.h"
#include "webkit/plugins/ppapi/resource_helper.h"
using ppapi::thunk::PPB_Flash_Menu_API;
using ppapi::TrackedCallback;
namespace webkit {
namespace ppapi {
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 |WebMenuItem| has, |id| is what a
// |PP_Flash_MenuItem| has).
bool ConvertMenuData(const PP_Flash_Menu* in_menu,
size_t depth,
PPB_Flash_Menu_Impl::MenuData* 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++) {
WebMenuItem item;
PP_Flash_MenuItem_Type type = in_menu->items[i].type;
switch (type) {
case PP_FLASH_MENUITEM_TYPE_NORMAL:
item.type = WebMenuItem::OPTION;
break;
case PP_FLASH_MENUITEM_TYPE_CHECKBOX:
item.type = WebMenuItem::CHECKABLE_OPTION;
break;
case PP_FLASH_MENUITEM_TYPE_SEPARATOR:
item.type = WebMenuItem::SEPARATOR;
break;
case PP_FLASH_MENUITEM_TYPE_SUBMENU:
item.type = WebMenuItem::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 = PPBoolToBool(in_menu->items[i].enabled);
item.checked = PPBoolToBool(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
PPB_Flash_Menu_Impl::PPB_Flash_Menu_Impl(PP_Instance instance)
: Resource(::ppapi::OBJECT_IS_IMPL, instance),
selected_id_out_(NULL) {
}
PPB_Flash_Menu_Impl::~PPB_Flash_Menu_Impl() {
}
// static
PP_Resource PPB_Flash_Menu_Impl::Create(PP_Instance instance,
const PP_Flash_Menu* menu_data) {
scoped_refptr<PPB_Flash_Menu_Impl> menu(new PPB_Flash_Menu_Impl(instance));
if (!menu->Init(menu_data))
return 0;
return menu->GetReference();
}
bool PPB_Flash_Menu_Impl::Init(const PP_Flash_Menu* menu_data) {
menu_id_map_.clear();
menu_id_map_.push_back(0); // Reserve |menu_id_map_[0]|.
if (!ConvertMenuData(menu_data, 0, &menu_data_, &menu_id_map_)) {
menu_id_map_.clear();
return false;
}
return true;
}
PPB_Flash_Menu_API* PPB_Flash_Menu_Impl::AsPPB_Flash_Menu_API() {
return this;
}
int32_t PPB_Flash_Menu_Impl::Show(const PP_Point* location,
int32_t* selected_id_out,
PP_CompletionCallback callback) {
// |location| is not (currently) optional.
// TODO(viettrungluu): Make it optional and default to the current mouse pos?
if (!location)
return PP_ERROR_BADARGUMENT;
if (!callback.func)
return PP_ERROR_BLOCKS_MAIN_THREAD;
if (TrackedCallback::IsPending(callback_))
return PP_ERROR_INPROGRESS;
PluginInstance* plugin_instance = ResourceHelper::GetPluginInstance(this);
if (!plugin_instance)
return false;
int32_t rv = plugin_instance->delegate()->ShowContextMenu(
plugin_instance, this, gfx::Point(location->x, location->y));
if (rv == PP_OK_COMPLETIONPENDING) {
// Record callback and output buffers.
callback_ = new TrackedCallback(this, callback);
selected_id_out_ = selected_id_out;
} else {
// This should never be completed synchronously successfully.
DCHECK_NE(rv, PP_OK);
}
return rv;
NOTIMPLEMENTED();
return PP_ERROR_FAILED;
}
void PPB_Flash_Menu_Impl::CompleteShow(int32_t result,
unsigned action) {
int32_t rv = PP_ERROR_ABORTED;
if (!callback_->aborted()) {
CHECK(!callback_->completed());
rv = result;
// Write output data.
if (selected_id_out_ && result == PP_OK) {
// We reserved action 0 to be invalid.
if (action == 0 || action >= menu_id_map_.size()) {
NOTREACHED() << "Invalid action received.";
rv = PP_ERROR_FAILED;
} else {
*selected_id_out_ = menu_id_map_[action];
}
}
}
selected_id_out_ = NULL;
TrackedCallback::ClearAndRun(&callback_, rv);
}
} // namespace ppapi
} // namespace webkit