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