Upload upstream chromium 114.0.5735.31
[platform/framework/web/chromium-efl.git] / components / renderer_context_menu / render_view_context_menu_base.cc
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.
4
5 #include "components/renderer_context_menu/render_view_context_menu_base.h"
6
7 #include <algorithm>
8 #include <utility>
9 #include <vector>
10
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"
27
28 using content::BrowserContext;
29 using content::GlobalRenderFrameHostId;
30 using content::OpenURLParams;
31 using content::RenderFrameHost;
32 using content::RenderViewHost;
33 using content::WebContents;
34
35 namespace {
36
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;
40
41 bool IsCustomItemEnabledInternal(
42     const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
43     int id) {
44   DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
45   for (const auto& item : items) {
46     int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
47         item->action);
48     if (action_id == id)
49       return item->enabled;
50     if (item->type == blink::mojom::CustomContextMenuItemType::kSubMenu) {
51       if (IsCustomItemEnabledInternal(item->submenu, id))
52         return true;
53     }
54   }
55   return false;
56 }
57
58 bool IsCustomItemCheckedInternal(
59     const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
60     int id) {
61   DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
62   for (const auto& item : items) {
63     int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
64         item->action);
65     if (action_id == id)
66       return item->checked;
67     if (item->type == blink::mojom::CustomContextMenuItemType::kSubMenu) {
68       if (IsCustomItemCheckedInternal(item->submenu, id))
69         return true;
70     }
71   }
72   return false;
73 }
74
75 const size_t kMaxCustomMenuDepth = 5;
76 const size_t kMaxCustomMenuTotalItems = 1000;
77
78 void AddCustomItemsToMenu(
79     const std::vector<blink::mojom::CustomContextMenuItemPtr>& items,
80     size_t depth,
81     size_t* total_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.";
87     return;
88   }
89   for (const auto& item : items) {
90     int command_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
91         item->action);
92     if (!RenderViewContextMenuBase::IsContentCustomCommandId(command_id)) {
93       LOG(ERROR) << "Custom menu action value out of range.";
94       return;
95     }
96     if (*total_items >= kMaxCustomMenuTotalItems) {
97       LOG(ERROR) << "Custom menu too large (too many items).";
98       return;
99     }
100     (*total_items)++;
101     switch (item->type) {
102       case blink::mojom::CustomContextMenuItemType::kOption:
103         menu_model->AddItem(
104             RenderViewContextMenuBase::ConvertToContentCustomCommandId(
105                 item->action),
106             item->label);
107         break;
108       case blink::mojom::CustomContextMenuItemType::kCheckableOption:
109         menu_model->AddCheckItem(
110             RenderViewContextMenuBase::ConvertToContentCustomCommandId(
111                 item->action),
112             item->label);
113         break;
114       case blink::mojom::CustomContextMenuItemType::kGroup:
115         // TODO(viettrungluu): I don't know what this is supposed to do.
116         NOTREACHED();
117         break;
118       case blink::mojom::CustomContextMenuItemType::kSeparator:
119         menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
120         break;
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,
125                              delegate, submenu);
126         menu_model->AddSubMenu(
127             RenderViewContextMenuBase::ConvertToContentCustomCommandId(
128                 item->action),
129             item->label, submenu);
130         break;
131       }
132       default:
133         NOTREACHED();
134         break;
135     }
136   }
137 }
138
139 }  // namespace
140
141 // static
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;
147 }
148
149 // static
150 const size_t RenderViewContextMenuBase::kMaxSelectionTextLength = 50;
151
152 // static
153 int RenderViewContextMenuBase::ConvertToContentCustomCommandId(int id) {
154   return content_context_custom_first + id;
155 }
156
157 // static
158 bool RenderViewContextMenuBase::IsContentCustomCommandId(int id) {
159   return id >= content_context_custom_first &&
160          id <= content_context_custom_last;
161 }
162
163 RenderViewContextMenuBase::RenderViewContextMenuBase(
164     content::RenderFrameHost& render_frame_host,
165     const content::ContextMenuParams& params)
166     : params_(params),
167       source_web_contents_(
168           WebContents::FromRenderFrameHost(&render_frame_host)),
169       browser_context_(source_web_contents_->GetBrowserContext()),
170       menu_model_(this),
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) {}
176
177 RenderViewContextMenuBase::~RenderViewContextMenuBase() {
178 }
179
180 // Menu construction functions -------------------------------------------------
181
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);
186
187   InitMenu();
188   if (toolkit_delegate_)
189     toolkit_delegate_->Init(&menu_model_);
190 }
191
192 void RenderViewContextMenuBase::Cancel() {
193   if (toolkit_delegate_)
194     toolkit_delegate_->Cancel();
195 }
196
197 void RenderViewContextMenuBase::InitMenu() {
198   if (content_type_->SupportsGroup(ContextMenuContentType::ITEM_GROUP_CUSTOM)) {
199     AppendCustomItems();
200
201     const bool has_selection = !params_.selection_text.empty();
202     if (has_selection) {
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);
206     }
207   }
208 }
209
210 void RenderViewContextMenuBase::AddMenuItem(int command_id,
211                                             const std::u16string& title) {
212   menu_model_.AddItem(command_id, title);
213 }
214
215 void RenderViewContextMenuBase::AddMenuItemWithIcon(
216     int command_id,
217     const std::u16string& title,
218     const ui::ImageModel& icon) {
219   menu_model_.AddItemWithIcon(command_id, title, icon);
220 }
221
222 void RenderViewContextMenuBase::AddCheckItem(int command_id,
223                                              const std::u16string& title) {
224   menu_model_.AddCheckItem(command_id, title);
225 }
226
227 void RenderViewContextMenuBase::AddSeparator() {
228   menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
229 }
230
231 void RenderViewContextMenuBase::AddSubMenu(int command_id,
232                                            const std::u16string& label,
233                                            ui::MenuModel* model) {
234   menu_model_.AddSubMenu(command_id, label, model);
235 }
236
237 void RenderViewContextMenuBase::AddSubMenuWithStringIdAndIcon(
238     int command_id,
239     int message_id,
240     ui::MenuModel* model,
241     const ui::ImageModel& icon) {
242   menu_model_.AddSubMenuWithStringIdAndIcon(command_id, message_id, model,
243                                             icon);
244 }
245
246 void RenderViewContextMenuBase::UpdateMenuItem(int command_id,
247                                                bool enabled,
248                                                bool hidden,
249                                                const std::u16string& label) {
250   absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
251   if (!index.has_value())
252     return;
253
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);
260 #else
261     toolkit_delegate_->RebuildMenu();
262 #endif
263   }
264 }
265
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())
270     return;
271
272   menu_model_.SetIcon(index.value(), icon);
273 #if BUILDFLAG(IS_CHROMEOS_ASH)
274   if (toolkit_delegate_)
275     toolkit_delegate_->RebuildMenu();
276 #endif
277 }
278
279 void RenderViewContextMenuBase::RemoveMenuItem(int command_id) {
280   absl::optional<size_t> index = menu_model_.GetIndexOfCommandId(command_id);
281   if (!index.has_value())
282     return;
283
284   menu_model_.RemoveItemAt(index.value());
285   if (toolkit_delegate_)
286     toolkit_delegate_->RebuildMenu();
287 }
288
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);
296
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);
301     }
302   }
303
304   if (toolkit_delegate_)
305     toolkit_delegate_->RebuildMenu();
306 }
307
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})
312     return;
313
314   ui::MenuModel::ItemType prev_type = menu_model_.GetTypeAt(index.value() - 1);
315   if (prev_type != ui::MenuModel::ItemType::TYPE_SEPARATOR)
316     return;
317
318   menu_model_.RemoveItemAt(index.value() - 1);
319
320   if (toolkit_delegate_)
321     toolkit_delegate_->RebuildMenu();
322 }
323
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();
330 }
331
332 WebContents* RenderViewContextMenuBase::GetWebContents() const {
333   return source_web_contents_;
334 }
335
336 BrowserContext* RenderViewContextMenuBase::GetBrowserContext() const {
337   return browser_context_;
338 }
339
340 bool RenderViewContextMenuBase::AppendCustomItems() {
341   size_t total_items = 0;
342   AddCustomItemsToMenu(params_.custom_items, 0, &total_items, &custom_submenus_,
343                        this, &menu_model_);
344   return total_items > 0;
345 }
346
347 bool RenderViewContextMenuBase::IsCommandIdKnown(
348     int id,
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);
355       return true;
356     }
357   }
358
359   // Custom items.
360   if (IsContentCustomCommandId(id)) {
361     *enabled = IsCustomItemEnabled(id);
362     return true;
363   }
364
365   return false;
366 }
367
368 // Menu delegate functions -----------------------------------------------------
369
370 bool RenderViewContextMenuBase::IsCommandIdChecked(int id) const {
371   // If this command is is added by one of our observers, we dispatch it to the
372   // observer.
373   for (auto& observer : observers_) {
374     if (observer.IsCommandIdSupported(id))
375       return observer.IsCommandIdChecked(id);
376   }
377
378   // Custom items.
379   if (IsContentCustomCommandId(id))
380     return IsCustomItemChecked(id);
381
382   return false;
383 }
384
385 void RenderViewContextMenuBase::ExecuteCommand(int id, int event_flags) {
386   command_executed_ = true;
387   RecordUsedItem(id);
388
389   // Notify all observers the command to be executed.
390   for (auto& observer : observers_)
391     observer.CommandWillBeExecuted(id);
392
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);
398   }
399
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();
406 #endif
407     source_web_contents_->ExecuteCustomContextMenuCommand(action,
408                                                           link_followed);
409     return;
410   }
411   command_executed_ = false;
412 }
413
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);
420     }
421   }
422
423   // Ignore notifications from submenus.
424   if (source != &menu_model_)
425     return;
426
427   source_web_contents_->SetShowingContextMenu(true);
428
429   NotifyMenuShown();
430 }
431
432 void RenderViewContextMenuBase::MenuClosed(ui::SimpleMenuModel* source) {
433   // Ignore notifications from submenus.
434   if (source != &menu_model_)
435     return;
436
437   source_web_contents_->SetShowingContextMenu(false);
438   source_web_contents_->NotifyContextMenuClosed(params_.link_followed);
439   for (auto& observer : observers_) {
440     observer.OnMenuClosed();
441   }
442 }
443
444 RenderFrameHost* RenderViewContextMenuBase::GetRenderFrameHost() const {
445   return RenderFrameHost::FromID(render_process_id_, render_frame_id_);
446 }
447
448 // Controller functions --------------------------------------------------------
449
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 */);
457 }
458
459 void RenderViewContextMenuBase::OpenURLWithExtraHeaders(
460     const GURL& url,
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);
469
470   source_web_contents_->OpenURL(open_url_params);
471 }
472
473 content::OpenURLParams
474 RenderViewContextMenuBase::GetOpenURLParamsWithExtraHeaders(
475     const GURL& url,
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.
483   GURL referrer_url;
484   if (disposition != WindowOpenDisposition::OFF_THE_RECORD) {
485     referrer_url = referring_url.GetAsReferrer();
486   }
487
488   content::Referrer referrer = content::Referrer::SanitizeForRequest(
489       url, content::Referrer(referrer_url, params_.referrer_policy));
490
491   if (params_.link_url == url &&
492       disposition != WindowOpenDisposition::OFF_THE_RECORD) {
493     params_.link_followed = url;
494   }
495
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;
500
501   open_url_params.source_render_process_id = render_process_id_;
502   open_url_params.source_render_frame_id = render_frame_id_;
503
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);
507
508   open_url_params.source_site_instance = site_instance_;
509
510   if (disposition != WindowOpenDisposition::OFF_THE_RECORD)
511     open_url_params.impression = params_.impression;
512
513   return open_url_params;
514 }
515
516 bool RenderViewContextMenuBase::IsCustomItemChecked(int id) const {
517   return IsCustomItemCheckedInternal(params_.custom_items, id);
518 }
519
520 bool RenderViewContextMenuBase::IsCustomItemEnabled(int id) const {
521   return IsCustomItemEnabledInternal(params_.custom_items, id);
522 }