Home | History | Annotate | Download | only in pepper
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/renderer/pepper/pepper_flash_menu_host.h"
      6 
      7 #include "base/strings/utf_string_conversions.h"
      8 #include "content/public/common/context_menu_params.h"
      9 #include "content/public/renderer/render_view.h"
     10 #include "content/public/renderer/renderer_ppapi_host.h"
     11 #include "ipc/ipc_message.h"
     12 #include "ppapi/c/private/ppb_flash_menu.h"
     13 #include "ppapi/host/dispatch_host_message.h"
     14 #include "ppapi/host/ppapi_host.h"
     15 #include "ppapi/proxy/ppapi_messages.h"
     16 #include "ppapi/proxy/serialized_flash_menu.h"
     17 #include "ui/gfx/point.h"
     18 
     19 namespace chrome {
     20 
     21 namespace {
     22 
     23 // Maximum depth of submenus allowed (e.g., 1 indicates that submenus are
     24 // allowed, but not sub-submenus).
     25 const size_t kMaxMenuDepth = 2;
     26 
     27 // Maximum number of entries in any single menu (including separators).
     28 const size_t kMaxMenuEntries = 50;
     29 
     30 // Maximum total number of entries in the |menu_id_map| (see below).
     31 // (Limit to 500 real entries; reserve the 0 action as an invalid entry.)
     32 const size_t kMaxMenuIdMapEntries = 501;
     33 
     34 // Converts menu data from one form to another.
     35 //  - |depth| is the current nested depth (call it starting with 0).
     36 //  - |menu_id_map| is such that |menu_id_map[output_item.action] ==
     37 //    input_item.id| (where |action| is what a |MenuItem| has, |id| is what a
     38 //    |PP_Flash_MenuItem| has).
     39 bool ConvertMenuData(const PP_Flash_Menu* in_menu,
     40                      size_t depth,
     41                      std::vector<content::MenuItem>* out_menu,
     42                      std::vector<int32_t>* menu_id_map) {
     43   if (depth > kMaxMenuDepth || !in_menu)
     44     return false;
     45 
     46   // Clear the output, just in case.
     47   out_menu->clear();
     48 
     49   if (!in_menu->count)
     50     return true;  // Nothing else to do.
     51 
     52   if (!in_menu->items || in_menu->count > kMaxMenuEntries)
     53     return false;
     54   for (uint32_t i = 0; i < in_menu->count; i++) {
     55     content::MenuItem item;
     56 
     57     PP_Flash_MenuItem_Type type = in_menu->items[i].type;
     58     switch (type) {
     59       case PP_FLASH_MENUITEM_TYPE_NORMAL:
     60         item.type = content::MenuItem::OPTION;
     61         break;
     62       case PP_FLASH_MENUITEM_TYPE_CHECKBOX:
     63         item.type = content::MenuItem::CHECKABLE_OPTION;
     64         break;
     65       case PP_FLASH_MENUITEM_TYPE_SEPARATOR:
     66         item.type = content::MenuItem::SEPARATOR;
     67         break;
     68       case PP_FLASH_MENUITEM_TYPE_SUBMENU:
     69         item.type = content::MenuItem::SUBMENU;
     70         break;
     71       default:
     72         return false;
     73     }
     74     if (in_menu->items[i].name)
     75       item.label = UTF8ToUTF16(in_menu->items[i].name);
     76     if (menu_id_map->size() >= kMaxMenuIdMapEntries)
     77       return false;
     78     item.action = static_cast<unsigned>(menu_id_map->size());
     79     // This sets |(*menu_id_map)[item.action] = in_menu->items[i].id|.
     80     menu_id_map->push_back(in_menu->items[i].id);
     81     item.enabled = PP_ToBool(in_menu->items[i].enabled);
     82     item.checked = PP_ToBool(in_menu->items[i].checked);
     83     if (type == PP_FLASH_MENUITEM_TYPE_SUBMENU) {
     84       if (!ConvertMenuData(in_menu->items[i].submenu, depth + 1, &item.submenu,
     85                            menu_id_map))
     86         return false;
     87     }
     88 
     89     out_menu->push_back(item);
     90   }
     91 
     92   return true;
     93 }
     94 
     95 }  // namespace
     96 
     97 PepperFlashMenuHost::PepperFlashMenuHost(
     98     content::RendererPpapiHost* host,
     99     PP_Instance instance,
    100     PP_Resource resource,
    101     const ppapi::proxy::SerializedFlashMenu& serial_menu)
    102     : ppapi::host::ResourceHost(host->GetPpapiHost(), instance, resource),
    103       renderer_ppapi_host_(host),
    104       showing_context_menu_(false),
    105       context_menu_request_id_(0),
    106       has_saved_context_menu_action_(false),
    107       saved_context_menu_action_(0) {
    108   menu_id_map_.push_back(0);  // Reserve |menu_id_map_[0]|.
    109   if (!ConvertMenuData(serial_menu.pp_menu(), 0, &menu_data_, &menu_id_map_)) {
    110     menu_data_.clear();
    111     menu_id_map_.clear();
    112   }
    113 }
    114 
    115 PepperFlashMenuHost::~PepperFlashMenuHost() {
    116   if (showing_context_menu_) {
    117     content::RenderView* render_view =
    118         renderer_ppapi_host_->GetRenderViewForInstance(pp_instance());
    119     if (render_view)
    120       render_view->CancelContextMenu(context_menu_request_id_);
    121   }
    122 }
    123 
    124 int32_t PepperFlashMenuHost::OnResourceMessageReceived(
    125     const IPC::Message& msg,
    126     ppapi::host::HostMessageContext* context) {
    127   IPC_BEGIN_MESSAGE_MAP(PepperFlashMenuHost, msg)
    128     PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashMenu_Show,
    129                                       OnHostMsgShow)
    130   IPC_END_MESSAGE_MAP()
    131   return PP_ERROR_FAILED;
    132 }
    133 
    134 int32_t PepperFlashMenuHost::OnHostMsgShow(
    135     ppapi::host::HostMessageContext* context,
    136     const PP_Point& location) {
    137   // Note that all early returns must do a SendMenuReply. The sync result for
    138   // this message isn't used, so to forward the error to the plugin, we need to
    139   // additionally call SendMenuReply explicitly.
    140   if (menu_data_.empty()) {
    141     SendMenuReply(PP_ERROR_FAILED, -1);
    142     return PP_ERROR_FAILED;
    143   }
    144   if (showing_context_menu_) {
    145     SendMenuReply(PP_ERROR_INPROGRESS, -1);
    146     return PP_ERROR_INPROGRESS;
    147   }
    148 
    149   content::RenderView* render_view =
    150       renderer_ppapi_host_->GetRenderViewForInstance(pp_instance());
    151 
    152   content::ContextMenuParams params;
    153   params.x = location.x;
    154   params.y = location.y;
    155   params.custom_context.is_pepper_menu = true;
    156   params.custom_context.render_widget_id =
    157       renderer_ppapi_host_->GetRoutingIDForWidget(pp_instance());
    158   params.custom_items = menu_data_;
    159 
    160   // Transform the position to be in render view's coordinates.
    161   gfx::Point render_view_pt = renderer_ppapi_host_->PluginPointToRenderView(
    162       pp_instance(), gfx::Point(location.x, location.y));
    163   params.x = render_view_pt.x();
    164   params.y = render_view_pt.y();
    165 
    166   showing_context_menu_ = true;
    167   context_menu_request_id_ = render_view->ShowContextMenu(this, params);
    168 
    169   // Note: the show message is sync so this OK is for the sync reply which we
    170   // don't actually use (see the comment in the resource file for this). The
    171   // async message containing the context menu action will be sent in the
    172   // future.
    173   return PP_OK;
    174 }
    175 
    176 void PepperFlashMenuHost::OnMenuAction(int request_id, unsigned action) {
    177   // Just save the action.
    178   DCHECK(!has_saved_context_menu_action_);
    179   has_saved_context_menu_action_ = true;
    180   saved_context_menu_action_ = action;
    181 }
    182 
    183 void PepperFlashMenuHost::OnMenuClosed(int request_id) {
    184   if (has_saved_context_menu_action_ &&
    185       saved_context_menu_action_ < menu_id_map_.size()) {
    186     SendMenuReply(PP_OK, menu_id_map_[saved_context_menu_action_]);
    187     has_saved_context_menu_action_ = false;
    188     saved_context_menu_action_ = 0;
    189   } else {
    190     SendMenuReply(PP_ERROR_USERCANCEL, -1);
    191   }
    192 
    193   showing_context_menu_ = false;
    194   context_menu_request_id_ = 0;
    195 }
    196 
    197 void PepperFlashMenuHost::SendMenuReply(int32_t result, int action) {
    198   ppapi::host::ReplyMessageContext reply_context(
    199       ppapi::proxy::ResourceMessageReplyParams(pp_resource(), 0),
    200       NULL, MSG_ROUTING_NONE);
    201   reply_context.params.set_result(result);
    202   host()->SendReply(reply_context,
    203                     PpapiPluginMsg_FlashMenu_ShowReply(action));
    204 
    205 }
    206 
    207 }  // namespace chrome
    208