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.
5 #include "components/renderer_context_menu/render_view_context_menu_base.h"
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 #if defined(ENABLE_EXTENSIONS)
19 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
21 #include "third_party/WebKit/public/web/WebContextMenuData.h"
23 using blink::WebContextMenuData
;
24 using blink::WebString
;
26 using content::BrowserContext
;
27 using content::OpenURLParams
;
28 using content::RenderFrameHost
;
29 using content::RenderViewHost
;
30 using content::WebContents
;
34 // The (inclusive) range of command IDs reserved for content's custom menus.
35 int content_context_custom_first
= -1;
36 int content_context_custom_last
= -1;
38 bool IsCustomItemEnabledInternal(const std::vector
<content::MenuItem
>& items
,
40 DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id
));
41 for (size_t i
= 0; i
< items
.size(); ++i
) {
42 int action_id
= RenderViewContextMenuBase::ConvertToContentCustomCommandId(
45 return items
[i
].enabled
;
46 if (items
[i
].type
== content::MenuItem::SUBMENU
) {
47 if (IsCustomItemEnabledInternal(items
[i
].submenu
, id
))
54 bool IsCustomItemCheckedInternal(const std::vector
<content::MenuItem
>& items
,
56 DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id
));
57 for (size_t i
= 0; i
< items
.size(); ++i
) {
58 int action_id
= RenderViewContextMenuBase::ConvertToContentCustomCommandId(
61 return items
[i
].checked
;
62 if (items
[i
].type
== content::MenuItem::SUBMENU
) {
63 if (IsCustomItemCheckedInternal(items
[i
].submenu
, id
))
70 const size_t kMaxCustomMenuDepth
= 5;
71 const size_t kMaxCustomMenuTotalItems
= 1000;
73 void AddCustomItemsToMenu(const std::vector
<content::MenuItem
>& items
,
76 ui::SimpleMenuModel::Delegate
* delegate
,
77 ui::SimpleMenuModel
* menu_model
) {
78 if (depth
> kMaxCustomMenuDepth
) {
79 LOG(ERROR
) << "Custom menu too deeply nested.";
82 for (size_t i
= 0; i
< items
.size(); ++i
) {
83 int command_id
= RenderViewContextMenuBase::ConvertToContentCustomCommandId(
85 if (!RenderViewContextMenuBase::IsContentCustomCommandId(command_id
)) {
86 LOG(ERROR
) << "Custom menu action value out of range.";
89 if (*total_items
>= kMaxCustomMenuTotalItems
) {
90 LOG(ERROR
) << "Custom menu too large (too many items).";
94 switch (items
[i
].type
) {
95 case content::MenuItem::OPTION
:
97 RenderViewContextMenuBase::ConvertToContentCustomCommandId(
101 case content::MenuItem::CHECKABLE_OPTION
:
102 menu_model
->AddCheckItem(
103 RenderViewContextMenuBase::ConvertToContentCustomCommandId(
107 case content::MenuItem::GROUP
:
108 // TODO(viettrungluu): I don't know what this is supposed to do.
111 case content::MenuItem::SEPARATOR
:
112 menu_model
->AddSeparator(ui::NORMAL_SEPARATOR
);
114 case content::MenuItem::SUBMENU
: {
115 ui::SimpleMenuModel
* submenu
= new ui::SimpleMenuModel(delegate
);
116 AddCustomItemsToMenu(items
[i
].submenu
, depth
+ 1, total_items
, delegate
,
118 menu_model
->AddSubMenu(
119 RenderViewContextMenuBase::ConvertToContentCustomCommandId(
132 content::WebContents
* GetWebContentsToUse(content::WebContents
* web_contents
) {
133 // If we're viewing in a MimeHandlerViewGuest, use its embedder WebContents.
134 #if defined(ENABLE_EXTENSIONS)
136 extensions::MimeHandlerViewGuest::FromWebContents(web_contents
);
138 return guest_view
->embedder_web_contents();
146 void RenderViewContextMenuBase::SetContentCustomCommandIdRange(
147 int first
, int last
) {
148 // The range is inclusive.
149 content_context_custom_first
= first
;
150 content_context_custom_last
= last
;
154 const size_t RenderViewContextMenuBase::kMaxSelectionTextLength
= 50;
157 int RenderViewContextMenuBase::ConvertToContentCustomCommandId(int id
) {
158 return content_context_custom_first
+ id
;
162 bool RenderViewContextMenuBase::IsContentCustomCommandId(int id
) {
163 return id
>= content_context_custom_first
&&
164 id
<= content_context_custom_last
;
167 RenderViewContextMenuBase::RenderViewContextMenuBase(
168 content::RenderFrameHost
* render_frame_host
,
169 const content::ContextMenuParams
& params
)
171 source_web_contents_(WebContents::FromRenderFrameHost(render_frame_host
)),
172 embedder_web_contents_(GetWebContentsToUse(source_web_contents_
)),
173 browser_context_(source_web_contents_
->GetBrowserContext()),
175 render_frame_id_(render_frame_host
->GetRoutingID()),
176 command_executed_(false),
177 render_process_id_(render_frame_host
->GetProcess()->GetID()) {
180 RenderViewContextMenuBase::~RenderViewContextMenuBase() {
183 // Menu construction functions -------------------------------------------------
185 void RenderViewContextMenuBase::Init() {
186 // Command id range must have been already initializerd.
187 DCHECK_NE(-1, content_context_custom_first
);
188 DCHECK_NE(-1, content_context_custom_last
);
191 if (toolkit_delegate_
)
192 toolkit_delegate_
->Init(&menu_model_
);
195 void RenderViewContextMenuBase::Cancel() {
196 if (toolkit_delegate_
)
197 toolkit_delegate_
->Cancel();
200 void RenderViewContextMenuBase::InitMenu() {
201 if (content_type_
->SupportsGroup(ContextMenuContentType::ITEM_GROUP_CUSTOM
)) {
204 const bool has_selection
= !params_
.selection_text
.empty();
206 // We will add more items if there's a selection, so add a separator.
207 // TODO(lazyboy): Clean up separator logic.
208 menu_model_
.AddSeparator(ui::NORMAL_SEPARATOR
);
213 void RenderViewContextMenuBase::AddMenuItem(int command_id
,
214 const base::string16
& title
) {
215 menu_model_
.AddItem(command_id
, title
);
218 void RenderViewContextMenuBase::AddCheckItem(int command_id
,
219 const base::string16
& title
) {
220 menu_model_
.AddCheckItem(command_id
, title
);
223 void RenderViewContextMenuBase::AddSeparator() {
224 menu_model_
.AddSeparator(ui::NORMAL_SEPARATOR
);
227 void RenderViewContextMenuBase::AddSubMenu(int command_id
,
228 const base::string16
& label
,
229 ui::MenuModel
* model
) {
230 menu_model_
.AddSubMenu(command_id
, label
, model
);
233 void RenderViewContextMenuBase::UpdateMenuItem(int command_id
,
236 const base::string16
& label
) {
237 if (toolkit_delegate_
) {
238 toolkit_delegate_
->UpdateMenuItem(command_id
,
245 RenderViewHost
* RenderViewContextMenuBase::GetRenderViewHost() const {
246 return source_web_contents_
->GetRenderViewHost();
249 WebContents
* RenderViewContextMenuBase::GetWebContents() const {
250 return source_web_contents_
;
253 BrowserContext
* RenderViewContextMenuBase::GetBrowserContext() const {
254 return browser_context_
;
257 bool RenderViewContextMenuBase::AppendCustomItems() {
258 size_t total_items
= 0;
259 AddCustomItemsToMenu(params_
.custom_items
, 0, &total_items
, this,
261 return total_items
> 0;
264 bool RenderViewContextMenuBase::IsCommandIdKnown(
266 bool* enabled
) const {
267 // If this command is is added by one of our observers, we dispatch
268 // it to the observer.
269 ObserverListBase
<RenderViewContextMenuObserver
>::Iterator
it(&observers_
);
270 RenderViewContextMenuObserver
* observer
;
271 while ((observer
= it
.GetNext()) != NULL
) {
272 if (observer
->IsCommandIdSupported(id
)) {
273 *enabled
= observer
->IsCommandIdEnabled(id
);
279 if (IsContentCustomCommandId(id
)) {
280 *enabled
= IsCustomItemEnabled(id
);
287 // Menu delegate functions -----------------------------------------------------
289 bool RenderViewContextMenuBase::IsCommandIdChecked(int id
) const {
290 // If this command is is added by one of our observers, we dispatch it to the
292 ObserverListBase
<RenderViewContextMenuObserver
>::Iterator
it(&observers_
);
293 RenderViewContextMenuObserver
* observer
;
294 while ((observer
= it
.GetNext()) != NULL
) {
295 if (observer
->IsCommandIdSupported(id
))
296 return observer
->IsCommandIdChecked(id
);
300 if (IsContentCustomCommandId(id
))
301 return IsCustomItemChecked(id
);
306 void RenderViewContextMenuBase::ExecuteCommand(int id
, int event_flags
) {
307 command_executed_
= true;
310 // If this command is is added by one of our observers, we dispatch
311 // it to the observer.
312 ObserverListBase
<RenderViewContextMenuObserver
>::Iterator
it(&observers_
);
313 RenderViewContextMenuObserver
* observer
;
314 while ((observer
= it
.GetNext()) != NULL
) {
315 if (observer
->IsCommandIdSupported(id
))
316 return observer
->ExecuteCommand(id
);
319 // Process custom actions range.
320 if (IsContentCustomCommandId(id
)) {
321 unsigned action
= id
- content_context_custom_first
;
322 const content::CustomContextMenuContext
& context
= params_
.custom_context
;
323 #if defined(ENABLE_PLUGINS)
324 if (context
.request_id
&& !context
.is_pepper_menu
)
325 HandleAuthorizeAllPlugins();
327 source_web_contents_
->ExecuteCustomContextMenuCommand(action
, context
);
330 command_executed_
= false;
333 void RenderViewContextMenuBase::MenuWillShow(ui::SimpleMenuModel
* source
) {
334 for (int i
= 0; i
< source
->GetItemCount(); ++i
) {
335 if (source
->IsVisibleAt(i
) &&
336 source
->GetTypeAt(i
) != ui::MenuModel::TYPE_SEPARATOR
) {
337 RecordShownItem(source
->GetCommandIdAt(i
));
341 // Ignore notifications from submenus.
342 if (source
!= &menu_model_
)
345 content::RenderWidgetHostView
* view
=
346 source_web_contents_
->GetRenderWidgetHostView();
348 view
->SetShowingContextMenu(true);
353 void RenderViewContextMenuBase::MenuClosed(ui::SimpleMenuModel
* source
) {
354 // Ignore notifications from submenus.
355 if (source
!= &menu_model_
)
358 content::RenderWidgetHostView
* view
=
359 source_web_contents_
->GetRenderWidgetHostView();
361 view
->SetShowingContextMenu(false);
362 source_web_contents_
->NotifyContextMenuClosed(params_
.custom_context
);
364 if (!command_executed_
) {
365 FOR_EACH_OBSERVER(RenderViewContextMenuObserver
,
371 RenderFrameHost
* RenderViewContextMenuBase::GetRenderFrameHost() {
372 return RenderFrameHost::FromID(render_process_id_
, render_frame_id_
);
375 // Controller functions --------------------------------------------------------
377 void RenderViewContextMenuBase::OpenURL(
378 const GURL
& url
, const GURL
& referring_url
,
379 WindowOpenDisposition disposition
,
380 ui::PageTransition transition
) {
381 OpenURLWithExtraHeaders(url
, referring_url
, disposition
, transition
, "");
384 void RenderViewContextMenuBase::OpenURLWithExtraHeaders(
386 const GURL
& referring_url
,
387 WindowOpenDisposition disposition
,
388 ui::PageTransition transition
,
389 const std::string
& extra_headers
) {
390 content::Referrer referrer
= content::Referrer::SanitizeForRequest(
392 content::Referrer(referring_url
.GetAsReferrer(),
393 params_
.referrer_policy
));
395 if (params_
.link_url
== url
&& disposition
!= OFF_THE_RECORD
)
396 params_
.custom_context
.link_followed
= url
;
398 OpenURLParams
open_url_params(url
, referrer
, disposition
, transition
, false);
399 if (!extra_headers
.empty())
400 open_url_params
.extra_headers
= extra_headers
;
402 WebContents
* new_contents
= source_web_contents_
->OpenURL(open_url_params
);
406 NotifyURLOpened(url
, new_contents
);
409 bool RenderViewContextMenuBase::IsCustomItemChecked(int id
) const {
410 return IsCustomItemCheckedInternal(params_
.custom_items
, id
);
413 bool RenderViewContextMenuBase::IsCustomItemEnabled(int id
) const {
414 return IsCustomItemEnabledInternal(params_
.custom_items
, id
);