1 // Copyright 2014 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 "components/renderer_context_menu/render_view_context_menu_base.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "content/public/browser/render_frame_host.h"
13 #include "content/public/browser/render_process_host.h"
14 #include "content/public/browser/render_view_host.h"
15 #include "content/public/browser/render_widget_host_view.h"
16 #include "content/public/browser/web_contents.h"
17 #include "content/public/common/menu_item.h"
18 #include "third_party/WebKit/public/web/WebContextMenuData.h"
19
20 using blink::WebContextMenuData;
21 using blink::WebString;
22 using blink::WebURL;
23 using content::BrowserContext;
24 using content::OpenURLParams;
25 using content::RenderFrameHost;
26 using content::RenderViewHost;
27 using content::WebContents;
28
29 namespace {
30
31 // The (inclusive) range of command IDs reserved for content's custom menus.
32 int content_context_custom_first = -1;
33 int content_context_custom_last = -1;
34
IsCustomItemEnabledInternal(const std::vector<content::MenuItem> & items,int id)35 bool IsCustomItemEnabledInternal(const std::vector<content::MenuItem>& items,
36 int id) {
37 DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
38 for (size_t i = 0; i < items.size(); ++i) {
39 int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
40 items[i].action);
41 if (action_id == id)
42 return items[i].enabled;
43 if (items[i].type == content::MenuItem::SUBMENU) {
44 if (IsCustomItemEnabledInternal(items[i].submenu, id))
45 return true;
46 }
47 }
48 return false;
49 }
50
IsCustomItemCheckedInternal(const std::vector<content::MenuItem> & items,int id)51 bool IsCustomItemCheckedInternal(const std::vector<content::MenuItem>& items,
52 int id) {
53 DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
54 for (size_t i = 0; i < items.size(); ++i) {
55 int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
56 items[i].action);
57 if (action_id == id)
58 return items[i].checked;
59 if (items[i].type == content::MenuItem::SUBMENU) {
60 if (IsCustomItemCheckedInternal(items[i].submenu, id))
61 return true;
62 }
63 }
64 return false;
65 }
66
67 const size_t kMaxCustomMenuDepth = 5;
68 const size_t kMaxCustomMenuTotalItems = 1000;
69
AddCustomItemsToMenu(const std::vector<content::MenuItem> & items,size_t depth,size_t * total_items,ui::SimpleMenuModel::Delegate * delegate,ui::SimpleMenuModel * menu_model)70 void AddCustomItemsToMenu(const std::vector<content::MenuItem>& items,
71 size_t depth,
72 size_t* total_items,
73 ui::SimpleMenuModel::Delegate* delegate,
74 ui::SimpleMenuModel* menu_model) {
75 if (depth > kMaxCustomMenuDepth) {
76 LOG(ERROR) << "Custom menu too deeply nested.";
77 return;
78 }
79 for (size_t i = 0; i < items.size(); ++i) {
80 int command_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
81 items[i].action);
82 if (!RenderViewContextMenuBase::IsContentCustomCommandId(command_id)) {
83 LOG(ERROR) << "Custom menu action value out of range.";
84 return;
85 }
86 if (*total_items >= kMaxCustomMenuTotalItems) {
87 LOG(ERROR) << "Custom menu too large (too many items).";
88 return;
89 }
90 (*total_items)++;
91 switch (items[i].type) {
92 case content::MenuItem::OPTION:
93 menu_model->AddItem(
94 RenderViewContextMenuBase::ConvertToContentCustomCommandId(
95 items[i].action),
96 items[i].label);
97 break;
98 case content::MenuItem::CHECKABLE_OPTION:
99 menu_model->AddCheckItem(
100 RenderViewContextMenuBase::ConvertToContentCustomCommandId(
101 items[i].action),
102 items[i].label);
103 break;
104 case content::MenuItem::GROUP:
105 // TODO(viettrungluu): I don't know what this is supposed to do.
106 NOTREACHED();
107 break;
108 case content::MenuItem::SEPARATOR:
109 menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
110 break;
111 case content::MenuItem::SUBMENU: {
112 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate);
113 AddCustomItemsToMenu(items[i].submenu, depth + 1, total_items, delegate,
114 submenu);
115 menu_model->AddSubMenu(
116 RenderViewContextMenuBase::ConvertToContentCustomCommandId(
117 items[i].action),
118 items[i].label,
119 submenu);
120 break;
121 }
122 default:
123 NOTREACHED();
124 break;
125 }
126 }
127 }
128
129 } // namespace
130
131 // static
SetContentCustomCommandIdRange(int first,int last)132 void RenderViewContextMenuBase::SetContentCustomCommandIdRange(
133 int first, int last) {
134 // The range is inclusive.
135 content_context_custom_first = first;
136 content_context_custom_last = last;
137 }
138
139 // static
140 const size_t RenderViewContextMenuBase::kMaxSelectionTextLength = 50;
141
142 // static
ConvertToContentCustomCommandId(int id)143 int RenderViewContextMenuBase::ConvertToContentCustomCommandId(int id) {
144 return content_context_custom_first + id;
145 }
146
147 // static
IsContentCustomCommandId(int id)148 bool RenderViewContextMenuBase::IsContentCustomCommandId(int id) {
149 return id >= content_context_custom_first &&
150 id <= content_context_custom_last;
151 }
152
RenderViewContextMenuBase(content::RenderFrameHost * render_frame_host,const content::ContextMenuParams & params)153 RenderViewContextMenuBase::RenderViewContextMenuBase(
154 content::RenderFrameHost* render_frame_host,
155 const content::ContextMenuParams& params)
156 : params_(params),
157 source_web_contents_(WebContents::FromRenderFrameHost(render_frame_host)),
158 browser_context_(source_web_contents_->GetBrowserContext()),
159 menu_model_(this),
160 render_frame_id_(render_frame_host->GetRoutingID()),
161 command_executed_(false),
162 render_process_id_(render_frame_host->GetProcess()->GetID()) {
163 }
164
~RenderViewContextMenuBase()165 RenderViewContextMenuBase::~RenderViewContextMenuBase() {
166 }
167
168 // Menu construction functions -------------------------------------------------
169
Init()170 void RenderViewContextMenuBase::Init() {
171 // Command id range must have been already initializerd.
172 DCHECK_NE(-1, content_context_custom_first);
173 DCHECK_NE(-1, content_context_custom_last);
174
175 InitMenu();
176 if (toolkit_delegate_)
177 toolkit_delegate_->Init(&menu_model_);
178 }
179
Cancel()180 void RenderViewContextMenuBase::Cancel() {
181 if (toolkit_delegate_)
182 toolkit_delegate_->Cancel();
183 }
184
InitMenu()185 void RenderViewContextMenuBase::InitMenu() {
186 if (content_type_->SupportsGroup(ContextMenuContentType::ITEM_GROUP_CUSTOM)) {
187 AppendCustomItems();
188
189 const bool has_selection = !params_.selection_text.empty();
190 if (has_selection) {
191 // We will add more items if there's a selection, so add a separator.
192 // TODO(lazyboy): Clean up separator logic.
193 menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
194 }
195 }
196 }
197
AddMenuItem(int command_id,const base::string16 & title)198 void RenderViewContextMenuBase::AddMenuItem(int command_id,
199 const base::string16& title) {
200 menu_model_.AddItem(command_id, title);
201 }
202
AddCheckItem(int command_id,const base::string16 & title)203 void RenderViewContextMenuBase::AddCheckItem(int command_id,
204 const base::string16& title) {
205 menu_model_.AddCheckItem(command_id, title);
206 }
207
AddSeparator()208 void RenderViewContextMenuBase::AddSeparator() {
209 menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
210 }
211
AddSubMenu(int command_id,const base::string16 & label,ui::MenuModel * model)212 void RenderViewContextMenuBase::AddSubMenu(int command_id,
213 const base::string16& label,
214 ui::MenuModel* model) {
215 menu_model_.AddSubMenu(command_id, label, model);
216 }
217
UpdateMenuItem(int command_id,bool enabled,bool hidden,const base::string16 & label)218 void RenderViewContextMenuBase::UpdateMenuItem(int command_id,
219 bool enabled,
220 bool hidden,
221 const base::string16& label) {
222 if (toolkit_delegate_) {
223 toolkit_delegate_->UpdateMenuItem(command_id,
224 enabled,
225 hidden,
226 label);
227 }
228 }
229
GetRenderViewHost() const230 RenderViewHost* RenderViewContextMenuBase::GetRenderViewHost() const {
231 return source_web_contents_->GetRenderViewHost();
232 }
233
GetWebContents() const234 WebContents* RenderViewContextMenuBase::GetWebContents() const {
235 return source_web_contents_;
236 }
237
GetBrowserContext() const238 BrowserContext* RenderViewContextMenuBase::GetBrowserContext() const {
239 return browser_context_;
240 }
241
AppendCustomItems()242 bool RenderViewContextMenuBase::AppendCustomItems() {
243 size_t total_items = 0;
244 AddCustomItemsToMenu(params_.custom_items, 0, &total_items, this,
245 &menu_model_);
246 return total_items > 0;
247 }
248
IsCommandIdKnown(int id,bool * enabled) const249 bool RenderViewContextMenuBase::IsCommandIdKnown(
250 int id,
251 bool* enabled) const {
252 // If this command is is added by one of our observers, we dispatch
253 // it to the observer.
254 ObserverListBase<RenderViewContextMenuObserver>::Iterator it(observers_);
255 RenderViewContextMenuObserver* observer;
256 while ((observer = it.GetNext()) != NULL) {
257 if (observer->IsCommandIdSupported(id)) {
258 *enabled = observer->IsCommandIdEnabled(id);
259 return true;
260 }
261 }
262
263 // Custom items.
264 if (IsContentCustomCommandId(id)) {
265 *enabled = IsCustomItemEnabled(id);
266 return true;
267 }
268
269 return false;
270 }
271
272 // Menu delegate functions -----------------------------------------------------
273
IsCommandIdChecked(int id) const274 bool RenderViewContextMenuBase::IsCommandIdChecked(int id) const {
275 // If this command is is added by one of our observers, we dispatch it to the
276 // observer.
277 ObserverListBase<RenderViewContextMenuObserver>::Iterator it(observers_);
278 RenderViewContextMenuObserver* observer;
279 while ((observer = it.GetNext()) != NULL) {
280 if (observer->IsCommandIdSupported(id))
281 return observer->IsCommandIdChecked(id);
282 }
283
284 // Custom items.
285 if (IsContentCustomCommandId(id))
286 return IsCustomItemChecked(id);
287
288 return false;
289 }
290
ExecuteCommand(int id,int event_flags)291 void RenderViewContextMenuBase::ExecuteCommand(int id, int event_flags) {
292 command_executed_ = true;
293 RecordUsedItem(id);
294
295 // If this command is is added by one of our observers, we dispatch
296 // it to the observer.
297 ObserverListBase<RenderViewContextMenuObserver>::Iterator it(observers_);
298 RenderViewContextMenuObserver* observer;
299 while ((observer = it.GetNext()) != NULL) {
300 if (observer->IsCommandIdSupported(id))
301 return observer->ExecuteCommand(id);
302 }
303
304 // Process custom actions range.
305 if (IsContentCustomCommandId(id)) {
306 unsigned action = id - content_context_custom_first;
307 const content::CustomContextMenuContext& context = params_.custom_context;
308 #if defined(ENABLE_PLUGINS)
309 if (context.request_id && !context.is_pepper_menu)
310 HandleAuthorizeAllPlugins();
311 #endif
312 source_web_contents_->ExecuteCustomContextMenuCommand(action, context);
313 return;
314 }
315 command_executed_ = false;
316 }
317
MenuWillShow(ui::SimpleMenuModel * source)318 void RenderViewContextMenuBase::MenuWillShow(ui::SimpleMenuModel* source) {
319 for (int i = 0; i < source->GetItemCount(); ++i) {
320 if (source->IsVisibleAt(i) &&
321 source->GetTypeAt(i) != ui::MenuModel::TYPE_SEPARATOR) {
322 RecordShownItem(source->GetCommandIdAt(i));
323 }
324 }
325
326 // Ignore notifications from submenus.
327 if (source != &menu_model_)
328 return;
329
330 content::RenderWidgetHostView* view =
331 source_web_contents_->GetRenderWidgetHostView();
332 if (view)
333 view->SetShowingContextMenu(true);
334
335 NotifyMenuShown();
336 }
337
MenuClosed(ui::SimpleMenuModel * source)338 void RenderViewContextMenuBase::MenuClosed(ui::SimpleMenuModel* source) {
339 // Ignore notifications from submenus.
340 if (source != &menu_model_)
341 return;
342
343 content::RenderWidgetHostView* view =
344 source_web_contents_->GetRenderWidgetHostView();
345 if (view)
346 view->SetShowingContextMenu(false);
347 source_web_contents_->NotifyContextMenuClosed(params_.custom_context);
348
349 if (!command_executed_) {
350 FOR_EACH_OBSERVER(RenderViewContextMenuObserver,
351 observers_,
352 OnMenuCancel());
353 }
354 }
355
GetRenderFrameHost()356 RenderFrameHost* RenderViewContextMenuBase::GetRenderFrameHost() {
357 return RenderFrameHost::FromID(render_process_id_, render_frame_id_);
358 }
359
360 // Controller functions --------------------------------------------------------
361
OpenURL(const GURL & url,const GURL & referring_url,WindowOpenDisposition disposition,ui::PageTransition transition)362 void RenderViewContextMenuBase::OpenURL(
363 const GURL& url, const GURL& referring_url,
364 WindowOpenDisposition disposition,
365 ui::PageTransition transition) {
366 content::Referrer referrer = content::Referrer::SanitizeForRequest(
367 url,
368 content::Referrer(referring_url.GetAsReferrer(),
369 params_.referrer_policy));
370
371 if (params_.link_url == url && disposition != OFF_THE_RECORD)
372 params_.custom_context.link_followed = url;
373
374 WebContents* new_contents = source_web_contents_->OpenURL(OpenURLParams(
375 url, referrer, disposition, transition, false));
376 if (!new_contents)
377 return;
378
379 NotifyURLOpened(url, new_contents);
380 }
381
IsCustomItemChecked(int id) const382 bool RenderViewContextMenuBase::IsCustomItemChecked(int id) const {
383 return IsCustomItemCheckedInternal(params_.custom_items, id);
384 }
385
IsCustomItemEnabled(int id) const386 bool RenderViewContextMenuBase::IsCustomItemEnabled(int id) const {
387 return IsCustomItemEnabledInternal(params_.custom_items, id);
388 }
389
390