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