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 = 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(in_menu->items[i].submenu, depth + 1, &item.submenu, 83 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 IPC_BEGIN_MESSAGE_MAP(PepperFlashMenuHost, msg) 126 PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashMenu_Show, 127 OnHostMsgShow) 128 IPC_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, MSG_ROUTING_NONE); 199 reply_context.params.set_result(result); 200 host()->SendReply(reply_context, 201 PpapiPluginMsg_FlashMenu_ShowReply(action)); 202 203 } 204