1 // Copyright 2014 The Chromium Authors
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"
11 #include "base/command_line.h"
12 #include "base/logging.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/observer_list.h"
15 #include "build/build_config.h"
16 #include "build/chromeos_buildflags.h"
17 #include "content/public/browser/global_routing_id.h"
18 #include "content/public/browser/render_frame_host.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "content/public/browser/render_view_host.h"
21 #include "content/public/browser/render_widget_host_view.h"
22 #include "content/public/browser/web_contents.h"
23 #include "ppapi/buildflags/buildflags.h"
24 #include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
25 #include "ui/base/models/image_model.h"
26 #include "url/origin.h"
28 using content::BrowserContext;
29 using content::GlobalRenderFrameHostId;
30 using content::OpenURLParams;
31 using content::RenderFrameHost;
32 using content::RenderViewHost;
33 using content::WebContents;
37 // The (inclusive) range of command IDs reserved for content's custom menus.
38 int content_context_custom_first = -1;
39 int content_context_custom_last = -1;
41 bool IsCustomItemEnabledInternal(
42 const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
44 DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
45 for (const auto& item : items) {
46 int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
50 if (item->type == blink::mojom::CustomContextMenuItemType::kSubMenu) {
51 if (IsCustomItemEnabledInternal(item->submenu, id))
58 bool IsCustomItemCheckedInternal(
59 const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
61 DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
62 for (const auto& item : items) {
63 int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
67 if (item->type == blink::mojom::CustomContextMenuItemType::kSubMenu) {
68 if (IsCustomItemCheckedInternal(item->submenu, id))
75 const size_t kMaxCustomMenuDepth = 5;
76 const size_t kMaxCustomMenuTotalItems = 1000;
78 void AddCustomItemsToMenu(
79 const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
82 std::vector<std::unique_ptr<ui::SimpleMenuModel>>* submenus,
83 ui::SimpleMenuModel::Delegate* delegate,
84 ui::SimpleMenuModel* menu_model) {
85 if (depth > kMaxCustomMenuDepth) {
86 LOG(ERROR) << "Custom menu too deeply nested.";
89 for (const auto& item : items) {
90 int command_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
92 if (!RenderViewContextMenuBase::IsContentCustomCommandId(command_id)) {
93 LOG(ERROR) << "Custom menu action value out of range.";
96 if (*total_items >= kMaxCustomMenuTotalItems) {
97 LOG(ERROR) << "Custom menu too large (too many items).";
101 switch (item->type) {
102 case blink::mojom::CustomContextMenuItemType::kOption:
104 RenderViewContextMenuBase::ConvertToContentCustomCommandId(
108 case blink::mojom::CustomContextMenuItemType::kCheckableOption:
109 menu_model->AddCheckItem(
110 RenderViewContextMenuBase::ConvertToContentCustomCommandId(
114 case blink::mojom::CustomContextMenuItemType::kGroup:
115 // TODO(viettrungluu): I don't know what this is supposed to do.
118 case blink::mojom::CustomContextMenuItemType::kSeparator:
119 menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
121 case blink::mojom::CustomContextMenuItemType::kSubMenu: {
122 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate);
123 submenus->push_back(base::WrapUnique(submenu));
124 AddCustomItemsToMenu(item->submenu, depth + 1, total_items, submenus,
126 menu_model->AddSubMenu(
127 RenderViewContextMenuBase::ConvertToContentCustomCommandId(
129 item->label, submenu);
142 void RenderViewContextMenuBase::SetContentCustomCommandIdRange(
143 int first, int last) {
144 // The range is inclusive.
145 content_context_custom_first = first;
146 content_context_custom_last = last;
150 const size_t RenderViewContextMenuBase::kMaxSelectionTextLength = 50;
153 int RenderViewContextMenuBase::ConvertToContentCustomCommandId(int id) {
154 return content_context_custom_first + id;
158 bool RenderViewContextMenuBase::IsContentCustomCommandId(int id) {
159 return id >= content_context_custom_first &&
160 id <= content_context_custom_last;
163 RenderViewContextMenuBase::RenderViewContextMenuBase(
164 content::RenderFrameHost& render_frame_host,
165 const content::ContextMenuParams& params)
167 source_web_contents_(
168 WebContents::FromRenderFrameHost(&render_frame_host)),
169 browser_context_(source_web_contents_->GetBrowserContext()),
171 render_frame_id_(render_frame_host.GetRoutingID()),
172 render_frame_token_(render_frame_host.GetFrameToken()),
173 render_process_id_(render_frame_host.GetProcess()->GetID()),
174 site_instance_(render_frame_host.GetSiteInstance()),
175 command_executed_(false) {}
177 RenderViewContextMenuBase::~RenderViewContextMenuBase() {
180 // Menu construction functions -------------------------------------------------
182 void RenderViewContextMenuBase::Init() {
183 // Command id range must have been already initializerd.
184 DCHECK_NE(-1, content_context_custom_first);
185 DCHECK_NE(-1, content_context_custom_last);
188 if (toolkit_delegate_)
189 toolkit_delegate_->Init(&menu_model_);
192 void RenderViewContextMenuBase::Cancel() {
193 if (toolkit_delegate_)
194 toolkit_delegate_->Cancel();
197 void RenderViewContextMenuBase::InitMenu() {
198 if (content_type_->SupportsGroup(ContextMenuContentType::ITEM_GROUP_CUSTOM)) {
201 const bool has_selection = !params_.selection_text.empty();
203 // We will add more items if there's a selection, so add a separator.
204 // TODO(lazyboy): Clean up separator logic.
205 menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
210 void RenderViewContextMenuBase::AddMenuItem(int command_id,
211 const std::u16string& title) {
212 menu_model_.AddItem(command_id, title);
215 void RenderViewContextMenuBase::AddMenuItemWithIcon(
217 const std::u16string& title,
218 const ui::ImageModel& icon) {
219 menu_model_.AddItemWithIcon(command_id, title, icon);
222 void RenderViewContextMenuBase::AddCheckItem(int command_id,
223 const std::u16string& title) {
224 menu_model_.AddCheckItem(command_id, title);
227 void RenderViewContextMenuBase::AddSeparator() {
228 menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
231 void RenderViewContextMenuBase::AddSubMenu(int command_id,
232 const std::u16string& label,
233 ui::MenuModel* model) {
234 menu_model_.AddSubMenu(command_id, label, model);
237 void RenderViewContextMenuBase::AddSubMenuWithStringIdAndIcon(
240 ui::MenuModel* model,
241 const ui::ImageModel& icon) {
242 menu_model_.AddSubMenuWithStringIdAndIcon(command_id, message_id, model,
246 void RenderViewContextMenuBase::UpdateMenuItem(int command_id,
249 const std::u16string& label) {
250 absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
251 if (!index.has_value())
254 menu_model_.SetLabel(index.value(), label);
255 menu_model_.SetEnabledAt(index.value(), enabled);
256 menu_model_.SetVisibleAt(index.value(), !hidden);
257 if (toolkit_delegate_) {
258 #if BUILDFLAG(IS_MAC)
259 toolkit_delegate_->UpdateMenuItem(command_id, enabled, hidden, label);
261 toolkit_delegate_->RebuildMenu();
266 void RenderViewContextMenuBase::UpdateMenuIcon(int command_id,
267 const ui::ImageModel& icon) {
268 absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
269 if (!index.has_value())
272 menu_model_.SetIcon(index.value(), icon);
273 #if BUILDFLAG(IS_CHROMEOS_ASH)
274 if (toolkit_delegate_)
275 toolkit_delegate_->RebuildMenu();
279 void RenderViewContextMenuBase::RemoveMenuItem(int command_id) {
280 absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
281 if (!index.has_value())
284 menu_model_.RemoveItemAt(index.value());
285 if (toolkit_delegate_)
286 toolkit_delegate_->RebuildMenu();
289 // Removes separators so that if there are two separators next to each other,
290 // only one of them remains.
291 void RenderViewContextMenuBase::RemoveAdjacentSeparators() {
292 size_t num_items = menu_model_.GetItemCount();
293 for (size_t index = num_items; index > 1; --index) {
294 ui::MenuModel::ItemType curr_type = menu_model_.GetTypeAt(index - 1);
295 ui::MenuModel::ItemType prev_type = menu_model_.GetTypeAt(index - 2);
297 if (curr_type == ui::MenuModel::ItemType::TYPE_SEPARATOR &&
298 prev_type == ui::MenuModel::ItemType::TYPE_SEPARATOR) {
299 // We found adjacent separators, remove the one at the bottom.
300 menu_model_.RemoveItemAt(index - 1);
304 if (toolkit_delegate_)
305 toolkit_delegate_->RebuildMenu();
308 void RenderViewContextMenuBase::RemoveSeparatorBeforeMenuItem(int command_id) {
309 absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
310 // Ignore if command not found or if it's the first menu item.
311 if (!index.has_value() || index == size_t{0})
314 ui::MenuModel::ItemType prev_type = menu_model_.GetTypeAt(index.value() - 1);
315 if (prev_type != ui::MenuModel::ItemType::TYPE_SEPARATOR)
318 menu_model_.RemoveItemAt(index.value() - 1);
320 if (toolkit_delegate_)
321 toolkit_delegate_->RebuildMenu();
324 // TODO(crbug.com/1393234): This method returns the RenderViewHost associated
325 // with the primary main frame. Using this in the presence of out of process
326 // iframes is generally incorrect, and the use of RenderViewHost itself is
327 // deprecated. Callers should use GetRenderFrameHost() instead.
328 RenderViewHost* RenderViewContextMenuBase::GetRenderViewHost() const {
329 return source_web_contents_->GetPrimaryMainFrame()->GetRenderViewHost();
332 WebContents* RenderViewContextMenuBase::GetWebContents() const {
333 return source_web_contents_;
336 BrowserContext* RenderViewContextMenuBase::GetBrowserContext() const {
337 return browser_context_;
340 bool RenderViewContextMenuBase::AppendCustomItems() {
341 size_t total_items = 0;
342 AddCustomItemsToMenu(params_.custom_items, 0, &total_items, &custom_submenus_,
344 return total_items > 0;
347 bool RenderViewContextMenuBase::IsCommandIdKnown(
349 bool* enabled) const {
350 // If this command is added by one of our observers, we dispatch
351 // it to the observer.
352 for (auto& observer : observers_) {
353 if (observer.IsCommandIdSupported(id)) {
354 *enabled = observer.IsCommandIdEnabled(id);
360 if (IsContentCustomCommandId(id)) {
361 *enabled = IsCustomItemEnabled(id);
368 // Menu delegate functions -----------------------------------------------------
370 bool RenderViewContextMenuBase::IsCommandIdChecked(int id) const {
371 // If this command is is added by one of our observers, we dispatch it to the
373 for (auto& observer : observers_) {
374 if (observer.IsCommandIdSupported(id))
375 return observer.IsCommandIdChecked(id);
379 if (IsContentCustomCommandId(id))
380 return IsCustomItemChecked(id);
385 void RenderViewContextMenuBase::ExecuteCommand(int id, int event_flags) {
386 command_executed_ = true;
389 // Notify all observers the command to be executed.
390 for (auto& observer : observers_)
391 observer.CommandWillBeExecuted(id);
393 // If this command is is added by one of our observers, we dispatch
394 // it to the observer.
395 for (auto& observer : observers_) {
396 if (observer.IsCommandIdSupported(id))
397 return observer.ExecuteCommand(id);
400 // Process custom actions range.
401 if (IsContentCustomCommandId(id)) {
402 unsigned action = id - content_context_custom_first;
403 const GURL& link_followed = params_.link_followed;
404 #if BUILDFLAG(ENABLE_PLUGINS)
405 HandleAuthorizeAllPlugins();
407 source_web_contents_->ExecuteCustomContextMenuCommand(action,
411 command_executed_ = false;
414 void RenderViewContextMenuBase::OnMenuWillShow(ui::SimpleMenuModel* source) {
415 for (size_t i = 0; i < source->GetItemCount(); ++i) {
416 if (source->IsVisibleAt(i) &&
417 source->GetTypeAt(i) != ui::MenuModel::TYPE_SEPARATOR) {
418 RecordShownItem(source->GetCommandIdAt(i),
419 source->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU);
423 // Ignore notifications from submenus.
424 if (source != &menu_model_)
427 source_web_contents_->SetShowingContextMenu(true);
432 void RenderViewContextMenuBase::MenuClosed(ui::SimpleMenuModel* source) {
433 // Ignore notifications from submenus.
434 if (source != &menu_model_)
437 source_web_contents_->SetShowingContextMenu(false);
438 source_web_contents_->NotifyContextMenuClosed(params_.link_followed);
439 for (auto& observer : observers_) {
440 observer.OnMenuClosed();
444 RenderFrameHost* RenderViewContextMenuBase::GetRenderFrameHost() const {
445 return RenderFrameHost::FromID(render_process_id_, render_frame_id_);
448 // Controller functions --------------------------------------------------------
450 void RenderViewContextMenuBase::OpenURL(const GURL& url,
451 const GURL& referring_url,
452 WindowOpenDisposition disposition,
453 ui::PageTransition transition) {
454 OpenURLWithExtraHeaders(url, referring_url, disposition, transition,
455 "" /* extra_headers */,
456 true /* started_from_context_menu */);
459 void RenderViewContextMenuBase::OpenURLWithExtraHeaders(
461 const GURL& referring_url,
462 WindowOpenDisposition disposition,
463 ui::PageTransition transition,
464 const std::string& extra_headers,
465 bool started_from_context_menu) {
466 content::OpenURLParams open_url_params = GetOpenURLParamsWithExtraHeaders(
467 url, referring_url, disposition, transition, extra_headers,
468 started_from_context_menu);
470 source_web_contents_->OpenURL(open_url_params);
473 content::OpenURLParams
474 RenderViewContextMenuBase::GetOpenURLParamsWithExtraHeaders(
476 const GURL& referring_url,
477 WindowOpenDisposition disposition,
478 ui::PageTransition transition,
479 const std::string& extra_headers,
480 bool started_from_context_menu) {
481 // Do not send the referrer url to OTR windows. We still need the
482 // |referring_url| to populate the |initiator_origin| below for browser UI.
484 if (disposition != WindowOpenDisposition::OFF_THE_RECORD) {
485 referrer_url = referring_url.GetAsReferrer();
488 content::Referrer referrer = content::Referrer::SanitizeForRequest(
489 url, content::Referrer(referrer_url, params_.referrer_policy));
491 if (params_.link_url == url &&
492 disposition != WindowOpenDisposition::OFF_THE_RECORD) {
493 params_.link_followed = url;
496 OpenURLParams open_url_params(url, referrer, disposition, transition, false,
497 started_from_context_menu);
498 if (!extra_headers.empty())
499 open_url_params.extra_headers = extra_headers;
501 open_url_params.source_render_process_id = render_process_id_;
502 open_url_params.source_render_frame_id = render_frame_id_;
504 open_url_params.initiator_frame_token = render_frame_token_;
505 open_url_params.initiator_process_id = render_process_id_;
506 open_url_params.initiator_origin = url::Origin::Create(referring_url);
508 open_url_params.source_site_instance = site_instance_;
510 if (disposition != WindowOpenDisposition::OFF_THE_RECORD)
511 open_url_params.impression = params_.impression;
513 return open_url_params;
516 bool RenderViewContextMenuBase::IsCustomItemChecked(int id) const {
517 return IsCustomItemCheckedInternal(params_.custom_items, id);
520 bool RenderViewContextMenuBase::IsCustomItemEnabled(int id) const {
521 return IsCustomItemEnabledInternal(params_.custom_items, id);