render_view_context_menu_base.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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. #include "components/renderer_context_menu/render_view_context_menu_base.h"
  5. #include <algorithm>
  6. #include <utility>
  7. #include <vector>
  8. #include "base/command_line.h"
  9. #include "base/logging.h"
  10. #include "base/memory/ptr_util.h"
  11. #include "base/observer_list.h"
  12. #include "build/build_config.h"
  13. #include "build/chromeos_buildflags.h"
  14. #include "content/public/browser/global_routing_id.h"
  15. #include "content/public/browser/render_frame_host.h"
  16. #include "content/public/browser/render_process_host.h"
  17. #include "content/public/browser/render_view_host.h"
  18. #include "content/public/browser/render_widget_host_view.h"
  19. #include "content/public/browser/web_contents.h"
  20. #include "ppapi/buildflags/buildflags.h"
  21. #include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
  22. #include "ui/base/models/image_model.h"
  23. #include "url/origin.h"
  24. using content::BrowserContext;
  25. using content::GlobalRenderFrameHostId;
  26. using content::OpenURLParams;
  27. using content::RenderFrameHost;
  28. using content::RenderViewHost;
  29. using content::WebContents;
  30. namespace {
  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. bool IsCustomItemEnabledInternal(
  35. const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
  36. int id) {
  37. DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
  38. for (const auto& item : items) {
  39. int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
  40. item->action);
  41. if (action_id == id)
  42. return item->enabled;
  43. if (item->type == blink::mojom::CustomContextMenuItemType::kSubMenu) {
  44. if (IsCustomItemEnabledInternal(item->submenu, id))
  45. return true;
  46. }
  47. }
  48. return false;
  49. }
  50. bool IsCustomItemCheckedInternal(
  51. const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
  52. int id) {
  53. DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
  54. for (const auto& item : items) {
  55. int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
  56. item->action);
  57. if (action_id == id)
  58. return item->checked;
  59. if (item->type == blink::mojom::CustomContextMenuItemType::kSubMenu) {
  60. if (IsCustomItemCheckedInternal(item->submenu, id))
  61. return true;
  62. }
  63. }
  64. return false;
  65. }
  66. const size_t kMaxCustomMenuDepth = 5;
  67. const size_t kMaxCustomMenuTotalItems = 1000;
  68. void AddCustomItemsToMenu(
  69. const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
  70. size_t depth,
  71. size_t* total_items,
  72. std::vector<std::unique_ptr<ui::SimpleMenuModel>>* submenus,
  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 (const auto& item : items) {
  80. int command_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
  81. item->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 (item->type) {
  92. case blink::mojom::CustomContextMenuItemType::kOption:
  93. menu_model->AddItem(
  94. RenderViewContextMenuBase::ConvertToContentCustomCommandId(
  95. item->action),
  96. item->label);
  97. break;
  98. case blink::mojom::CustomContextMenuItemType::kCheckableOption:
  99. menu_model->AddCheckItem(
  100. RenderViewContextMenuBase::ConvertToContentCustomCommandId(
  101. item->action),
  102. item->label);
  103. break;
  104. case blink::mojom::CustomContextMenuItemType::kGroup:
  105. // TODO(viettrungluu): I don't know what this is supposed to do.
  106. NOTREACHED();
  107. break;
  108. case blink::mojom::CustomContextMenuItemType::kSeparator:
  109. menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
  110. break;
  111. case blink::mojom::CustomContextMenuItemType::kSubMenu: {
  112. ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate);
  113. submenus->push_back(base::WrapUnique(submenu));
  114. AddCustomItemsToMenu(item->submenu, depth + 1, total_items, submenus,
  115. delegate, submenu);
  116. menu_model->AddSubMenu(
  117. RenderViewContextMenuBase::ConvertToContentCustomCommandId(
  118. item->action),
  119. item->label, submenu);
  120. break;
  121. }
  122. default:
  123. NOTREACHED();
  124. break;
  125. }
  126. }
  127. }
  128. } // namespace
  129. // static
  130. void RenderViewContextMenuBase::SetContentCustomCommandIdRange(
  131. int first, int last) {
  132. // The range is inclusive.
  133. content_context_custom_first = first;
  134. content_context_custom_last = last;
  135. }
  136. // static
  137. const size_t RenderViewContextMenuBase::kMaxSelectionTextLength = 50;
  138. // static
  139. int RenderViewContextMenuBase::ConvertToContentCustomCommandId(int id) {
  140. return content_context_custom_first + id;
  141. }
  142. // static
  143. bool RenderViewContextMenuBase::IsContentCustomCommandId(int id) {
  144. return id >= content_context_custom_first &&
  145. id <= content_context_custom_last;
  146. }
  147. RenderViewContextMenuBase::RenderViewContextMenuBase(
  148. content::RenderFrameHost& render_frame_host,
  149. const content::ContextMenuParams& params)
  150. : params_(params),
  151. source_web_contents_(
  152. WebContents::FromRenderFrameHost(&render_frame_host)),
  153. browser_context_(source_web_contents_->GetBrowserContext()),
  154. menu_model_(this),
  155. render_frame_id_(render_frame_host.GetRoutingID()),
  156. render_frame_token_(render_frame_host.GetFrameToken()),
  157. render_process_id_(render_frame_host.GetProcess()->GetID()),
  158. site_instance_(render_frame_host.GetSiteInstance()),
  159. command_executed_(false) {}
  160. RenderViewContextMenuBase::~RenderViewContextMenuBase() {
  161. }
  162. // Menu construction functions -------------------------------------------------
  163. void RenderViewContextMenuBase::Init() {
  164. // Command id range must have been already initializerd.
  165. DCHECK_NE(-1, content_context_custom_first);
  166. DCHECK_NE(-1, content_context_custom_last);
  167. InitMenu();
  168. if (toolkit_delegate_)
  169. toolkit_delegate_->Init(&menu_model_);
  170. }
  171. void RenderViewContextMenuBase::Cancel() {
  172. if (toolkit_delegate_)
  173. toolkit_delegate_->Cancel();
  174. }
  175. void RenderViewContextMenuBase::InitMenu() {
  176. if (content_type_->SupportsGroup(ContextMenuContentType::ITEM_GROUP_CUSTOM)) {
  177. AppendCustomItems();
  178. const bool has_selection = !params_.selection_text.empty();
  179. if (has_selection) {
  180. // We will add more items if there's a selection, so add a separator.
  181. // TODO(lazyboy): Clean up separator logic.
  182. menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
  183. }
  184. }
  185. }
  186. void RenderViewContextMenuBase::AddMenuItem(int command_id,
  187. const std::u16string& title) {
  188. menu_model_.AddItem(command_id, title);
  189. }
  190. void RenderViewContextMenuBase::AddMenuItemWithIcon(
  191. int command_id,
  192. const std::u16string& title,
  193. const ui::ImageModel& icon) {
  194. menu_model_.AddItemWithIcon(command_id, title, icon);
  195. }
  196. void RenderViewContextMenuBase::AddCheckItem(int command_id,
  197. const std::u16string& title) {
  198. menu_model_.AddCheckItem(command_id, title);
  199. }
  200. void RenderViewContextMenuBase::AddSeparator() {
  201. menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
  202. }
  203. void RenderViewContextMenuBase::AddSubMenu(int command_id,
  204. const std::u16string& label,
  205. ui::MenuModel* model) {
  206. menu_model_.AddSubMenu(command_id, label, model);
  207. }
  208. void RenderViewContextMenuBase::AddSubMenuWithStringIdAndIcon(
  209. int command_id,
  210. int message_id,
  211. ui::MenuModel* model,
  212. const ui::ImageModel& icon) {
  213. menu_model_.AddSubMenuWithStringIdAndIcon(command_id, message_id, model,
  214. icon);
  215. }
  216. void RenderViewContextMenuBase::UpdateMenuItem(int command_id,
  217. bool enabled,
  218. bool hidden,
  219. const std::u16string& label) {
  220. absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
  221. if (!index.has_value())
  222. return;
  223. menu_model_.SetLabel(index.value(), label);
  224. menu_model_.SetEnabledAt(index.value(), enabled);
  225. menu_model_.SetVisibleAt(index.value(), !hidden);
  226. if (toolkit_delegate_) {
  227. #if BUILDFLAG(IS_MAC)
  228. toolkit_delegate_->UpdateMenuItem(command_id, enabled, hidden, label);
  229. #else
  230. toolkit_delegate_->RebuildMenu();
  231. #endif
  232. }
  233. }
  234. void RenderViewContextMenuBase::UpdateMenuIcon(int command_id,
  235. const ui::ImageModel& icon) {
  236. absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
  237. if (!index.has_value())
  238. return;
  239. menu_model_.SetIcon(index.value(), icon);
  240. #if BUILDFLAG(IS_CHROMEOS_ASH)
  241. if (toolkit_delegate_)
  242. toolkit_delegate_->RebuildMenu();
  243. #endif
  244. }
  245. void RenderViewContextMenuBase::RemoveMenuItem(int command_id) {
  246. absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
  247. if (!index.has_value())
  248. return;
  249. menu_model_.RemoveItemAt(index.value());
  250. if (toolkit_delegate_)
  251. toolkit_delegate_->RebuildMenu();
  252. }
  253. // Removes separators so that if there are two separators next to each other,
  254. // only one of them remains.
  255. void RenderViewContextMenuBase::RemoveAdjacentSeparators() {
  256. size_t num_items = menu_model_.GetItemCount();
  257. for (size_t index = num_items; index > 1; --index) {
  258. ui::MenuModel::ItemType curr_type = menu_model_.GetTypeAt(index - 1);
  259. ui::MenuModel::ItemType prev_type = menu_model_.GetTypeAt(index - 2);
  260. if (curr_type == ui::MenuModel::ItemType::TYPE_SEPARATOR &&
  261. prev_type == ui::MenuModel::ItemType::TYPE_SEPARATOR) {
  262. // We found adjacent separators, remove the one at the bottom.
  263. menu_model_.RemoveItemAt(index - 1);
  264. }
  265. }
  266. if (toolkit_delegate_)
  267. toolkit_delegate_->RebuildMenu();
  268. }
  269. void RenderViewContextMenuBase::RemoveSeparatorBeforeMenuItem(int command_id) {
  270. absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
  271. // Ignore if command not found or if it's the first menu item.
  272. if (!index.has_value() || index == size_t{0})
  273. return;
  274. ui::MenuModel::ItemType prev_type = menu_model_.GetTypeAt(index.value() - 1);
  275. if (prev_type != ui::MenuModel::ItemType::TYPE_SEPARATOR)
  276. return;
  277. menu_model_.RemoveItemAt(index.value() - 1);
  278. if (toolkit_delegate_)
  279. toolkit_delegate_->RebuildMenu();
  280. }
  281. RenderViewHost* RenderViewContextMenuBase::GetRenderViewHost() const {
  282. return source_web_contents_->GetPrimaryMainFrame()->GetRenderViewHost();
  283. }
  284. WebContents* RenderViewContextMenuBase::GetWebContents() const {
  285. return source_web_contents_;
  286. }
  287. BrowserContext* RenderViewContextMenuBase::GetBrowserContext() const {
  288. return browser_context_;
  289. }
  290. bool RenderViewContextMenuBase::AppendCustomItems() {
  291. size_t total_items = 0;
  292. AddCustomItemsToMenu(params_.custom_items, 0, &total_items, &custom_submenus_,
  293. this, &menu_model_);
  294. return total_items > 0;
  295. }
  296. bool RenderViewContextMenuBase::IsCommandIdKnown(
  297. int id,
  298. bool* enabled) const {
  299. // If this command is added by one of our observers, we dispatch
  300. // it to the observer.
  301. for (auto& observer : observers_) {
  302. if (observer.IsCommandIdSupported(id)) {
  303. *enabled = observer.IsCommandIdEnabled(id);
  304. return true;
  305. }
  306. }
  307. // Custom items.
  308. if (IsContentCustomCommandId(id)) {
  309. *enabled = IsCustomItemEnabled(id);
  310. return true;
  311. }
  312. return false;
  313. }
  314. // Menu delegate functions -----------------------------------------------------
  315. bool RenderViewContextMenuBase::IsCommandIdChecked(int id) const {
  316. // If this command is is added by one of our observers, we dispatch it to the
  317. // observer.
  318. for (auto& observer : observers_) {
  319. if (observer.IsCommandIdSupported(id))
  320. return observer.IsCommandIdChecked(id);
  321. }
  322. // Custom items.
  323. if (IsContentCustomCommandId(id))
  324. return IsCustomItemChecked(id);
  325. return false;
  326. }
  327. void RenderViewContextMenuBase::ExecuteCommand(int id, int event_flags) {
  328. command_executed_ = true;
  329. RecordUsedItem(id);
  330. // Notify all observers the command to be executed.
  331. for (auto& observer : observers_)
  332. observer.CommandWillBeExecuted(id);
  333. // If this command is is added by one of our observers, we dispatch
  334. // it to the observer.
  335. for (auto& observer : observers_) {
  336. if (observer.IsCommandIdSupported(id))
  337. return observer.ExecuteCommand(id);
  338. }
  339. // Process custom actions range.
  340. if (IsContentCustomCommandId(id)) {
  341. unsigned action = id - content_context_custom_first;
  342. const GURL& link_followed = params_.link_followed;
  343. #if BUILDFLAG(ENABLE_PLUGINS)
  344. HandleAuthorizeAllPlugins();
  345. #endif
  346. source_web_contents_->ExecuteCustomContextMenuCommand(action,
  347. link_followed);
  348. return;
  349. }
  350. command_executed_ = false;
  351. }
  352. void RenderViewContextMenuBase::OnMenuWillShow(ui::SimpleMenuModel* source) {
  353. for (size_t i = 0; i < source->GetItemCount(); ++i) {
  354. if (source->IsVisibleAt(i) &&
  355. source->GetTypeAt(i) != ui::MenuModel::TYPE_SEPARATOR) {
  356. RecordShownItem(source->GetCommandIdAt(i),
  357. source->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU);
  358. }
  359. }
  360. // Ignore notifications from submenus.
  361. if (source != &menu_model_)
  362. return;
  363. source_web_contents_->SetShowingContextMenu(true);
  364. NotifyMenuShown();
  365. }
  366. void RenderViewContextMenuBase::MenuClosed(ui::SimpleMenuModel* source) {
  367. // Ignore notifications from submenus.
  368. if (source != &menu_model_)
  369. return;
  370. source_web_contents_->SetShowingContextMenu(false);
  371. source_web_contents_->NotifyContextMenuClosed(params_.link_followed);
  372. for (auto& observer : observers_) {
  373. observer.OnMenuClosed();
  374. }
  375. }
  376. RenderFrameHost* RenderViewContextMenuBase::GetRenderFrameHost() const {
  377. return RenderFrameHost::FromID(render_process_id_, render_frame_id_);
  378. }
  379. // Controller functions --------------------------------------------------------
  380. void RenderViewContextMenuBase::OpenURL(const GURL& url,
  381. const GURL& referring_url,
  382. WindowOpenDisposition disposition,
  383. ui::PageTransition transition) {
  384. OpenURLWithExtraHeaders(url, referring_url, disposition, transition,
  385. "" /* extra_headers */,
  386. false /* started_from_context_menu */);
  387. }
  388. void RenderViewContextMenuBase::OpenURLWithExtraHeaders(
  389. const GURL& url,
  390. const GURL& referring_url,
  391. WindowOpenDisposition disposition,
  392. ui::PageTransition transition,
  393. const std::string& extra_headers,
  394. bool started_from_context_menu) {
  395. // Do not send the referrer url to OTR windows. We still need the
  396. // |referring_url| to populate the |initiator_origin| below for browser UI.
  397. GURL referrer_url;
  398. if (disposition != WindowOpenDisposition::OFF_THE_RECORD)
  399. referrer_url = referring_url.GetAsReferrer();
  400. content::Referrer referrer = content::Referrer::SanitizeForRequest(
  401. url, content::Referrer(referrer_url, params_.referrer_policy));
  402. if (params_.link_url == url &&
  403. disposition != WindowOpenDisposition::OFF_THE_RECORD)
  404. params_.link_followed = url;
  405. OpenURLParams open_url_params(url, referrer, disposition, transition, false,
  406. started_from_context_menu);
  407. if (!extra_headers.empty())
  408. open_url_params.extra_headers = extra_headers;
  409. open_url_params.source_render_process_id = render_process_id_;
  410. open_url_params.source_render_frame_id = render_frame_id_;
  411. open_url_params.initiator_frame_token = render_frame_token_;
  412. open_url_params.initiator_process_id = render_process_id_;
  413. open_url_params.initiator_origin = url::Origin::Create(referring_url);
  414. open_url_params.source_site_instance = site_instance_;
  415. if (disposition != WindowOpenDisposition::OFF_THE_RECORD)
  416. open_url_params.impression = params_.impression;
  417. source_web_contents_->OpenURL(open_url_params);
  418. }
  419. bool RenderViewContextMenuBase::IsCustomItemChecked(int id) const {
  420. return IsCustomItemCheckedInternal(params_.custom_items, id);
  421. }
  422. bool RenderViewContextMenuBase::IsCustomItemEnabled(int id) const {
  423. return IsCustomItemEnabledInternal(params_.custom_items, id);
  424. }