123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- // Copyright 2014 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/renderer_context_menu/render_view_context_menu_base.h"
- #include <algorithm>
- #include <utility>
- #include <vector>
- #include "base/command_line.h"
- #include "base/logging.h"
- #include "base/memory/ptr_util.h"
- #include "base/observer_list.h"
- #include "build/build_config.h"
- #include "build/chromeos_buildflags.h"
- #include "content/public/browser/global_routing_id.h"
- #include "content/public/browser/render_frame_host.h"
- #include "content/public/browser/render_process_host.h"
- #include "content/public/browser/render_view_host.h"
- #include "content/public/browser/render_widget_host_view.h"
- #include "content/public/browser/web_contents.h"
- #include "ppapi/buildflags/buildflags.h"
- #include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
- #include "ui/base/models/image_model.h"
- #include "url/origin.h"
- using content::BrowserContext;
- using content::GlobalRenderFrameHostId;
- using content::OpenURLParams;
- using content::RenderFrameHost;
- using content::RenderViewHost;
- using content::WebContents;
- namespace {
- // The (inclusive) range of command IDs reserved for content's custom menus.
- int content_context_custom_first = -1;
- int content_context_custom_last = -1;
- bool IsCustomItemEnabledInternal(
- const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
- int id) {
- DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
- for (const auto& item : items) {
- int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
- item->action);
- if (action_id == id)
- return item->enabled;
- if (item->type == blink::mojom::CustomContextMenuItemType::kSubMenu) {
- if (IsCustomItemEnabledInternal(item->submenu, id))
- return true;
- }
- }
- return false;
- }
- bool IsCustomItemCheckedInternal(
- const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
- int id) {
- DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
- for (const auto& item : items) {
- int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
- item->action);
- if (action_id == id)
- return item->checked;
- if (item->type == blink::mojom::CustomContextMenuItemType::kSubMenu) {
- if (IsCustomItemCheckedInternal(item->submenu, id))
- return true;
- }
- }
- return false;
- }
- const size_t kMaxCustomMenuDepth = 5;
- const size_t kMaxCustomMenuTotalItems = 1000;
- void AddCustomItemsToMenu(
- const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
- size_t depth,
- size_t* total_items,
- std::vector<std::unique_ptr<ui::SimpleMenuModel>>* submenus,
- ui::SimpleMenuModel::Delegate* delegate,
- ui::SimpleMenuModel* menu_model) {
- if (depth > kMaxCustomMenuDepth) {
- LOG(ERROR) << "Custom menu too deeply nested.";
- return;
- }
- for (const auto& item : items) {
- int command_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
- item->action);
- if (!RenderViewContextMenuBase::IsContentCustomCommandId(command_id)) {
- LOG(ERROR) << "Custom menu action value out of range.";
- return;
- }
- if (*total_items >= kMaxCustomMenuTotalItems) {
- LOG(ERROR) << "Custom menu too large (too many items).";
- return;
- }
- (*total_items)++;
- switch (item->type) {
- case blink::mojom::CustomContextMenuItemType::kOption:
- menu_model->AddItem(
- RenderViewContextMenuBase::ConvertToContentCustomCommandId(
- item->action),
- item->label);
- break;
- case blink::mojom::CustomContextMenuItemType::kCheckableOption:
- menu_model->AddCheckItem(
- RenderViewContextMenuBase::ConvertToContentCustomCommandId(
- item->action),
- item->label);
- break;
- case blink::mojom::CustomContextMenuItemType::kGroup:
- // TODO(viettrungluu): I don't know what this is supposed to do.
- NOTREACHED();
- break;
- case blink::mojom::CustomContextMenuItemType::kSeparator:
- menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
- break;
- case blink::mojom::CustomContextMenuItemType::kSubMenu: {
- ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate);
- submenus->push_back(base::WrapUnique(submenu));
- AddCustomItemsToMenu(item->submenu, depth + 1, total_items, submenus,
- delegate, submenu);
- menu_model->AddSubMenu(
- RenderViewContextMenuBase::ConvertToContentCustomCommandId(
- item->action),
- item->label, submenu);
- break;
- }
- default:
- NOTREACHED();
- break;
- }
- }
- }
- } // namespace
- // static
- void RenderViewContextMenuBase::SetContentCustomCommandIdRange(
- int first, int last) {
- // The range is inclusive.
- content_context_custom_first = first;
- content_context_custom_last = last;
- }
- // static
- const size_t RenderViewContextMenuBase::kMaxSelectionTextLength = 50;
- // static
- int RenderViewContextMenuBase::ConvertToContentCustomCommandId(int id) {
- return content_context_custom_first + id;
- }
- // static
- bool RenderViewContextMenuBase::IsContentCustomCommandId(int id) {
- return id >= content_context_custom_first &&
- id <= content_context_custom_last;
- }
- RenderViewContextMenuBase::RenderViewContextMenuBase(
- content::RenderFrameHost& render_frame_host,
- const content::ContextMenuParams& params)
- : params_(params),
- source_web_contents_(
- WebContents::FromRenderFrameHost(&render_frame_host)),
- browser_context_(source_web_contents_->GetBrowserContext()),
- menu_model_(this),
- render_frame_id_(render_frame_host.GetRoutingID()),
- render_frame_token_(render_frame_host.GetFrameToken()),
- render_process_id_(render_frame_host.GetProcess()->GetID()),
- site_instance_(render_frame_host.GetSiteInstance()),
- command_executed_(false) {}
- RenderViewContextMenuBase::~RenderViewContextMenuBase() {
- }
- // Menu construction functions -------------------------------------------------
- void RenderViewContextMenuBase::Init() {
- // Command id range must have been already initializerd.
- DCHECK_NE(-1, content_context_custom_first);
- DCHECK_NE(-1, content_context_custom_last);
- InitMenu();
- if (toolkit_delegate_)
- toolkit_delegate_->Init(&menu_model_);
- }
- void RenderViewContextMenuBase::Cancel() {
- if (toolkit_delegate_)
- toolkit_delegate_->Cancel();
- }
- void RenderViewContextMenuBase::InitMenu() {
- if (content_type_->SupportsGroup(ContextMenuContentType::ITEM_GROUP_CUSTOM)) {
- AppendCustomItems();
- const bool has_selection = !params_.selection_text.empty();
- if (has_selection) {
- // We will add more items if there's a selection, so add a separator.
- // TODO(lazyboy): Clean up separator logic.
- menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
- }
- }
- }
- void RenderViewContextMenuBase::AddMenuItem(int command_id,
- const std::u16string& title) {
- menu_model_.AddItem(command_id, title);
- }
- void RenderViewContextMenuBase::AddMenuItemWithIcon(
- int command_id,
- const std::u16string& title,
- const ui::ImageModel& icon) {
- menu_model_.AddItemWithIcon(command_id, title, icon);
- }
- void RenderViewContextMenuBase::AddCheckItem(int command_id,
- const std::u16string& title) {
- menu_model_.AddCheckItem(command_id, title);
- }
- void RenderViewContextMenuBase::AddSeparator() {
- menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
- }
- void RenderViewContextMenuBase::AddSubMenu(int command_id,
- const std::u16string& label,
- ui::MenuModel* model) {
- menu_model_.AddSubMenu(command_id, label, model);
- }
- void RenderViewContextMenuBase::AddSubMenuWithStringIdAndIcon(
- int command_id,
- int message_id,
- ui::MenuModel* model,
- const ui::ImageModel& icon) {
- menu_model_.AddSubMenuWithStringIdAndIcon(command_id, message_id, model,
- icon);
- }
- void RenderViewContextMenuBase::UpdateMenuItem(int command_id,
- bool enabled,
- bool hidden,
- const std::u16string& label) {
- absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
- if (!index.has_value())
- return;
- menu_model_.SetLabel(index.value(), label);
- menu_model_.SetEnabledAt(index.value(), enabled);
- menu_model_.SetVisibleAt(index.value(), !hidden);
- if (toolkit_delegate_) {
- #if BUILDFLAG(IS_MAC)
- toolkit_delegate_->UpdateMenuItem(command_id, enabled, hidden, label);
- #else
- toolkit_delegate_->RebuildMenu();
- #endif
- }
- }
- void RenderViewContextMenuBase::UpdateMenuIcon(int command_id,
- const ui::ImageModel& icon) {
- absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
- if (!index.has_value())
- return;
- menu_model_.SetIcon(index.value(), icon);
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- if (toolkit_delegate_)
- toolkit_delegate_->RebuildMenu();
- #endif
- }
- void RenderViewContextMenuBase::RemoveMenuItem(int command_id) {
- absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
- if (!index.has_value())
- return;
- menu_model_.RemoveItemAt(index.value());
- if (toolkit_delegate_)
- toolkit_delegate_->RebuildMenu();
- }
- // Removes separators so that if there are two separators next to each other,
- // only one of them remains.
- void RenderViewContextMenuBase::RemoveAdjacentSeparators() {
- size_t num_items = menu_model_.GetItemCount();
- for (size_t index = num_items; index > 1; --index) {
- ui::MenuModel::ItemType curr_type = menu_model_.GetTypeAt(index - 1);
- ui::MenuModel::ItemType prev_type = menu_model_.GetTypeAt(index - 2);
- if (curr_type == ui::MenuModel::ItemType::TYPE_SEPARATOR &&
- prev_type == ui::MenuModel::ItemType::TYPE_SEPARATOR) {
- // We found adjacent separators, remove the one at the bottom.
- menu_model_.RemoveItemAt(index - 1);
- }
- }
- if (toolkit_delegate_)
- toolkit_delegate_->RebuildMenu();
- }
- void RenderViewContextMenuBase::RemoveSeparatorBeforeMenuItem(int command_id) {
- absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
- // Ignore if command not found or if it's the first menu item.
- if (!index.has_value() || index == size_t{0})
- return;
- ui::MenuModel::ItemType prev_type = menu_model_.GetTypeAt(index.value() - 1);
- if (prev_type != ui::MenuModel::ItemType::TYPE_SEPARATOR)
- return;
- menu_model_.RemoveItemAt(index.value() - 1);
- if (toolkit_delegate_)
- toolkit_delegate_->RebuildMenu();
- }
- RenderViewHost* RenderViewContextMenuBase::GetRenderViewHost() const {
- return source_web_contents_->GetPrimaryMainFrame()->GetRenderViewHost();
- }
- WebContents* RenderViewContextMenuBase::GetWebContents() const {
- return source_web_contents_;
- }
- BrowserContext* RenderViewContextMenuBase::GetBrowserContext() const {
- return browser_context_;
- }
- bool RenderViewContextMenuBase::AppendCustomItems() {
- size_t total_items = 0;
- AddCustomItemsToMenu(params_.custom_items, 0, &total_items, &custom_submenus_,
- this, &menu_model_);
- return total_items > 0;
- }
- bool RenderViewContextMenuBase::IsCommandIdKnown(
- int id,
- bool* enabled) const {
- // If this command is added by one of our observers, we dispatch
- // it to the observer.
- for (auto& observer : observers_) {
- if (observer.IsCommandIdSupported(id)) {
- *enabled = observer.IsCommandIdEnabled(id);
- return true;
- }
- }
- // Custom items.
- if (IsContentCustomCommandId(id)) {
- *enabled = IsCustomItemEnabled(id);
- return true;
- }
- return false;
- }
- // Menu delegate functions -----------------------------------------------------
- bool RenderViewContextMenuBase::IsCommandIdChecked(int id) const {
- // If this command is is added by one of our observers, we dispatch it to the
- // observer.
- for (auto& observer : observers_) {
- if (observer.IsCommandIdSupported(id))
- return observer.IsCommandIdChecked(id);
- }
- // Custom items.
- if (IsContentCustomCommandId(id))
- return IsCustomItemChecked(id);
- return false;
- }
- void RenderViewContextMenuBase::ExecuteCommand(int id, int event_flags) {
- command_executed_ = true;
- RecordUsedItem(id);
- // Notify all observers the command to be executed.
- for (auto& observer : observers_)
- observer.CommandWillBeExecuted(id);
- // If this command is is added by one of our observers, we dispatch
- // it to the observer.
- for (auto& observer : observers_) {
- if (observer.IsCommandIdSupported(id))
- return observer.ExecuteCommand(id);
- }
- // Process custom actions range.
- if (IsContentCustomCommandId(id)) {
- unsigned action = id - content_context_custom_first;
- const GURL& link_followed = params_.link_followed;
- #if BUILDFLAG(ENABLE_PLUGINS)
- HandleAuthorizeAllPlugins();
- #endif
- source_web_contents_->ExecuteCustomContextMenuCommand(action,
- link_followed);
- return;
- }
- command_executed_ = false;
- }
- void RenderViewContextMenuBase::OnMenuWillShow(ui::SimpleMenuModel* source) {
- for (size_t i = 0; i < source->GetItemCount(); ++i) {
- if (source->IsVisibleAt(i) &&
- source->GetTypeAt(i) != ui::MenuModel::TYPE_SEPARATOR) {
- RecordShownItem(source->GetCommandIdAt(i),
- source->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU);
- }
- }
- // Ignore notifications from submenus.
- if (source != &menu_model_)
- return;
- source_web_contents_->SetShowingContextMenu(true);
- NotifyMenuShown();
- }
- void RenderViewContextMenuBase::MenuClosed(ui::SimpleMenuModel* source) {
- // Ignore notifications from submenus.
- if (source != &menu_model_)
- return;
- source_web_contents_->SetShowingContextMenu(false);
- source_web_contents_->NotifyContextMenuClosed(params_.link_followed);
- for (auto& observer : observers_) {
- observer.OnMenuClosed();
- }
- }
- RenderFrameHost* RenderViewContextMenuBase::GetRenderFrameHost() const {
- return RenderFrameHost::FromID(render_process_id_, render_frame_id_);
- }
- // Controller functions --------------------------------------------------------
- void RenderViewContextMenuBase::OpenURL(const GURL& url,
- const GURL& referring_url,
- WindowOpenDisposition disposition,
- ui::PageTransition transition) {
- OpenURLWithExtraHeaders(url, referring_url, disposition, transition,
- "" /* extra_headers */,
- false /* started_from_context_menu */);
- }
- void RenderViewContextMenuBase::OpenURLWithExtraHeaders(
- const GURL& url,
- const GURL& referring_url,
- WindowOpenDisposition disposition,
- ui::PageTransition transition,
- const std::string& extra_headers,
- bool started_from_context_menu) {
- // Do not send the referrer url to OTR windows. We still need the
- // |referring_url| to populate the |initiator_origin| below for browser UI.
- GURL referrer_url;
- if (disposition != WindowOpenDisposition::OFF_THE_RECORD)
- referrer_url = referring_url.GetAsReferrer();
- content::Referrer referrer = content::Referrer::SanitizeForRequest(
- url, content::Referrer(referrer_url, params_.referrer_policy));
- if (params_.link_url == url &&
- disposition != WindowOpenDisposition::OFF_THE_RECORD)
- params_.link_followed = url;
- OpenURLParams open_url_params(url, referrer, disposition, transition, false,
- started_from_context_menu);
- if (!extra_headers.empty())
- open_url_params.extra_headers = extra_headers;
- open_url_params.source_render_process_id = render_process_id_;
- open_url_params.source_render_frame_id = render_frame_id_;
- open_url_params.initiator_frame_token = render_frame_token_;
- open_url_params.initiator_process_id = render_process_id_;
- open_url_params.initiator_origin = url::Origin::Create(referring_url);
- open_url_params.source_site_instance = site_instance_;
- if (disposition != WindowOpenDisposition::OFF_THE_RECORD)
- open_url_params.impression = params_.impression;
- source_web_contents_->OpenURL(open_url_params);
- }
- bool RenderViewContextMenuBase::IsCustomItemChecked(int id) const {
- return IsCustomItemCheckedInternal(params_.custom_items, id);
- }
- bool RenderViewContextMenuBase::IsCustomItemEnabled(int id) const {
- return IsCustomItemEnabledInternal(params_.custom_items, id);
- }
|