Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / components / renderer_context_menu / render_view_context_menu_base.cc
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
5 #include "components/renderer_context_menu/render_view_context_menu_base.h"
6
7 #include <algorithm>
8 #include <utility>
9
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 #include "third_party/WebKit/public/web/WebContextMenuData.h"
19
20 using blink::WebContextMenuData;
21 using blink::WebString;
22 using blink::WebURL;
23 using content::BrowserContext;
24 using content::OpenURLParams;
25 using content::RenderFrameHost;
26 using content::RenderViewHost;
27 using content::WebContents;
28
29 namespace {
30
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
35 bool IsCustomItemEnabledInternal(const std::vector<content::MenuItem>& items,
36                                  int id) {
37   DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
38   for (size_t i = 0; i < items.size(); ++i) {
39     int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
40         items[i].action);
41     if (action_id == id)
42       return items[i].enabled;
43     if (items[i].type == content::MenuItem::SUBMENU) {
44       if (IsCustomItemEnabledInternal(items[i].submenu, id))
45         return true;
46     }
47   }
48   return false;
49 }
50
51 bool IsCustomItemCheckedInternal(const std::vector<content::MenuItem>& items,
52                                  int id) {
53   DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id));
54   for (size_t i = 0; i < items.size(); ++i) {
55     int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
56         items[i].action);
57     if (action_id == id)
58       return items[i].checked;
59     if (items[i].type == content::MenuItem::SUBMENU) {
60       if (IsCustomItemCheckedInternal(items[i].submenu, id))
61         return true;
62     }
63   }
64   return false;
65 }
66
67 const size_t kMaxCustomMenuDepth = 5;
68 const size_t kMaxCustomMenuTotalItems = 1000;
69
70 void AddCustomItemsToMenu(const std::vector<content::MenuItem>& items,
71                           size_t depth,
72                           size_t* total_items,
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 (size_t i = 0; i < items.size(); ++i) {
80     int command_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId(
81         items[i].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 (items[i].type) {
92       case content::MenuItem::OPTION:
93         menu_model->AddItem(
94             RenderViewContextMenuBase::ConvertToContentCustomCommandId(
95                 items[i].action),
96             items[i].label);
97         break;
98       case content::MenuItem::CHECKABLE_OPTION:
99         menu_model->AddCheckItem(
100             RenderViewContextMenuBase::ConvertToContentCustomCommandId(
101                 items[i].action),
102             items[i].label);
103         break;
104       case content::MenuItem::GROUP:
105         // TODO(viettrungluu): I don't know what this is supposed to do.
106         NOTREACHED();
107         break;
108       case content::MenuItem::SEPARATOR:
109         menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
110         break;
111       case content::MenuItem::SUBMENU: {
112         ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate);
113         AddCustomItemsToMenu(items[i].submenu, depth + 1, total_items, delegate,
114                              submenu);
115         menu_model->AddSubMenu(
116             RenderViewContextMenuBase::ConvertToContentCustomCommandId(
117                 items[i].action),
118             items[i].label,
119             submenu);
120         break;
121       }
122       default:
123         NOTREACHED();
124         break;
125     }
126   }
127 }
128
129 }  // namespace
130
131 // static
132 void RenderViewContextMenuBase::SetContentCustomCommandIdRange(
133     int first, int last) {
134   // The range is inclusive.
135   content_context_custom_first = first;
136   content_context_custom_last = last;
137 }
138
139 // static
140 const size_t RenderViewContextMenuBase::kMaxSelectionTextLength = 50;
141
142 // static
143 int RenderViewContextMenuBase::ConvertToContentCustomCommandId(int id) {
144   return content_context_custom_first + id;
145 }
146
147 // static
148 bool RenderViewContextMenuBase::IsContentCustomCommandId(int id) {
149   return id >= content_context_custom_first &&
150          id <= content_context_custom_last;
151 }
152
153 RenderViewContextMenuBase::RenderViewContextMenuBase(
154     content::RenderFrameHost* render_frame_host,
155     const content::ContextMenuParams& params)
156     : params_(params),
157       source_web_contents_(WebContents::FromRenderFrameHost(render_frame_host)),
158       browser_context_(source_web_contents_->GetBrowserContext()),
159       menu_model_(this),
160       render_frame_id_(render_frame_host->GetRoutingID()),
161       command_executed_(false),
162       render_process_id_(render_frame_host->GetProcess()->GetID()) {
163 }
164
165 RenderViewContextMenuBase::~RenderViewContextMenuBase() {
166 }
167
168 // Menu construction functions -------------------------------------------------
169
170 void RenderViewContextMenuBase::Init() {
171   // Command id range must have been already initializerd.
172   DCHECK_NE(-1, content_context_custom_first);
173   DCHECK_NE(-1, content_context_custom_last);
174
175   InitMenu();
176   if (toolkit_delegate_)
177     toolkit_delegate_->Init(&menu_model_);
178 }
179
180 void RenderViewContextMenuBase::Cancel() {
181   if (toolkit_delegate_)
182     toolkit_delegate_->Cancel();
183 }
184
185 void RenderViewContextMenuBase::InitMenu() {
186   if (content_type_->SupportsGroup(ContextMenuContentType::ITEM_GROUP_CUSTOM)) {
187     AppendCustomItems();
188
189     const bool has_selection = !params_.selection_text.empty();
190     if (has_selection) {
191       // We will add more items if there's a selection, so add a separator.
192       // TODO(lazyboy): Clean up separator logic.
193       menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
194     }
195   }
196 }
197
198 void RenderViewContextMenuBase::AddMenuItem(int command_id,
199                                             const base::string16& title) {
200   menu_model_.AddItem(command_id, title);
201 }
202
203 void RenderViewContextMenuBase::AddCheckItem(int command_id,
204                                          const base::string16& title) {
205   menu_model_.AddCheckItem(command_id, title);
206 }
207
208 void RenderViewContextMenuBase::AddSeparator() {
209   menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
210 }
211
212 void RenderViewContextMenuBase::AddSubMenu(int command_id,
213                                        const base::string16& label,
214                                        ui::MenuModel* model) {
215   menu_model_.AddSubMenu(command_id, label, model);
216 }
217
218 void RenderViewContextMenuBase::UpdateMenuItem(int command_id,
219                                            bool enabled,
220                                            bool hidden,
221                                            const base::string16& label) {
222   if (toolkit_delegate_) {
223     toolkit_delegate_->UpdateMenuItem(command_id,
224                                       enabled,
225                                       hidden,
226                                       label);
227   }
228 }
229
230 RenderViewHost* RenderViewContextMenuBase::GetRenderViewHost() const {
231   return source_web_contents_->GetRenderViewHost();
232 }
233
234 WebContents* RenderViewContextMenuBase::GetWebContents() const {
235   return source_web_contents_;
236 }
237
238 BrowserContext* RenderViewContextMenuBase::GetBrowserContext() const {
239   return browser_context_;
240 }
241
242 bool RenderViewContextMenuBase::AppendCustomItems() {
243   size_t total_items = 0;
244   AddCustomItemsToMenu(params_.custom_items, 0, &total_items, this,
245                        &menu_model_);
246   return total_items > 0;
247 }
248
249 bool RenderViewContextMenuBase::IsCommandIdKnown(
250     int id,
251     bool* enabled) const {
252   // If this command is is added by one of our observers, we dispatch
253   // it to the observer.
254   ObserverListBase<RenderViewContextMenuObserver>::Iterator it(observers_);
255   RenderViewContextMenuObserver* observer;
256   while ((observer = it.GetNext()) != NULL) {
257     if (observer->IsCommandIdSupported(id)) {
258       *enabled = observer->IsCommandIdEnabled(id);
259       return true;
260     }
261   }
262
263   // Custom items.
264   if (IsContentCustomCommandId(id)) {
265     *enabled = IsCustomItemEnabled(id);
266     return true;
267   }
268
269   return false;
270 }
271
272 // Menu delegate functions -----------------------------------------------------
273
274 bool RenderViewContextMenuBase::IsCommandIdChecked(int id) const {
275   // If this command is is added by one of our observers, we dispatch it to the
276   // observer.
277   ObserverListBase<RenderViewContextMenuObserver>::Iterator it(observers_);
278   RenderViewContextMenuObserver* observer;
279   while ((observer = it.GetNext()) != NULL) {
280     if (observer->IsCommandIdSupported(id))
281       return observer->IsCommandIdChecked(id);
282   }
283
284   // Custom items.
285   if (IsContentCustomCommandId(id))
286     return IsCustomItemChecked(id);
287
288   return false;
289 }
290
291 void RenderViewContextMenuBase::ExecuteCommand(int id, int event_flags) {
292   command_executed_ = true;
293   RecordUsedItem(id);
294
295   // If this command is is added by one of our observers, we dispatch
296   // it to the observer.
297   ObserverListBase<RenderViewContextMenuObserver>::Iterator it(observers_);
298   RenderViewContextMenuObserver* observer;
299   while ((observer = it.GetNext()) != NULL) {
300     if (observer->IsCommandIdSupported(id))
301       return observer->ExecuteCommand(id);
302   }
303
304   // Process custom actions range.
305   if (IsContentCustomCommandId(id)) {
306     unsigned action = id - content_context_custom_first;
307     const content::CustomContextMenuContext& context = params_.custom_context;
308 #if defined(ENABLE_PLUGINS)
309     if (context.request_id && !context.is_pepper_menu)
310       HandleAuthorizeAllPlugins();
311 #endif
312     source_web_contents_->ExecuteCustomContextMenuCommand(action, context);
313     return;
314   }
315   command_executed_ = false;
316 }
317
318 void RenderViewContextMenuBase::MenuWillShow(ui::SimpleMenuModel* source) {
319   for (int i = 0; i < source->GetItemCount(); ++i) {
320     if (source->IsVisibleAt(i) &&
321         source->GetTypeAt(i) != ui::MenuModel::TYPE_SEPARATOR) {
322       RecordShownItem(source->GetCommandIdAt(i));
323     }
324   }
325
326   // Ignore notifications from submenus.
327   if (source != &menu_model_)
328     return;
329
330   content::RenderWidgetHostView* view =
331       source_web_contents_->GetRenderWidgetHostView();
332   if (view)
333     view->SetShowingContextMenu(true);
334
335   NotifyMenuShown();
336 }
337
338 void RenderViewContextMenuBase::MenuClosed(ui::SimpleMenuModel* source) {
339   // Ignore notifications from submenus.
340   if (source != &menu_model_)
341     return;
342
343   content::RenderWidgetHostView* view =
344       source_web_contents_->GetRenderWidgetHostView();
345   if (view)
346     view->SetShowingContextMenu(false);
347   source_web_contents_->NotifyContextMenuClosed(params_.custom_context);
348
349   if (!command_executed_) {
350     FOR_EACH_OBSERVER(RenderViewContextMenuObserver,
351                       observers_,
352                       OnMenuCancel());
353   }
354 }
355
356 RenderFrameHost* RenderViewContextMenuBase::GetRenderFrameHost() {
357   return RenderFrameHost::FromID(render_process_id_, render_frame_id_);
358 }
359
360 // Controller functions --------------------------------------------------------
361
362 void RenderViewContextMenuBase::OpenURL(
363     const GURL& url, const GURL& referring_url,
364     WindowOpenDisposition disposition,
365     ui::PageTransition transition) {
366   content::Referrer referrer = content::Referrer::SanitizeForRequest(
367       url,
368       content::Referrer(referring_url.GetAsReferrer(),
369                         params_.referrer_policy));
370
371   if (params_.link_url == url && disposition != OFF_THE_RECORD)
372     params_.custom_context.link_followed = url;
373
374   WebContents* new_contents = source_web_contents_->OpenURL(OpenURLParams(
375       url, referrer, disposition, transition, false));
376   if (!new_contents)
377     return;
378
379   NotifyURLOpened(url, new_contents);
380 }
381
382 bool RenderViewContextMenuBase::IsCustomItemChecked(int id) const {
383   return IsCustomItemCheckedInternal(params_.custom_items, id);
384 }
385
386 bool RenderViewContextMenuBase::IsCustomItemEnabled(int id) const {
387   return IsCustomItemEnabledInternal(params_.custom_items, id);
388 }
389