text: // 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/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) {
    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::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));
  
  }
  