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