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 19namespace chrome { 20 21namespace { 22 23// Maximum depth of submenus allowed (e.g., 1 indicates that submenus are 24// allowed, but not sub-submenus). 25const size_t kMaxMenuDepth = 2; 26 27// Maximum number of entries in any single menu (including separators). 28const 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.) 32const 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). 39bool 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 97PepperFlashMenuHost::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 115PepperFlashMenuHost::~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 124int32_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 134int32_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 176void 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 183void 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 197void 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