Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / context_menu_matcher.cc
1 // Copyright (c) 2012 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 "base/strings/utf_string_conversions.h"
6 #include "chrome/app/chrome_command_ids.h"
7 #include "chrome/browser/extensions/context_menu_matcher.h"
8 #include "chrome/browser/extensions/extension_service.h"
9 #include "chrome/browser/extensions/extension_util.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "content/public/common/context_menu_params.h"
12 #include "extensions/browser/extension_system.h"
13 #include "ui/gfx/favicon_size.h"
14 #include "ui/gfx/image/image.h"
15
16 namespace extensions {
17
18 // static
19 const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength = 75;
20
21 ContextMenuMatcher::ContextMenuMatcher(
22     Profile* profile,
23     ui::SimpleMenuModel::Delegate* delegate,
24     ui::SimpleMenuModel* menu_model,
25     const base::Callback<bool(const MenuItem*)>& filter)
26     : profile_(profile), menu_model_(menu_model), delegate_(delegate),
27       filter_(filter) {
28 }
29
30 void ContextMenuMatcher::AppendExtensionItems(
31     const std::string& extension_id,
32     const base::string16& selection_text,
33     int* index) {
34   DCHECK_GE(*index, 0);
35   int max_index =
36       IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST - IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST;
37   if (*index >= max_index)
38     return;
39
40   const Extension* extension = NULL;
41   MenuItem::List items;
42   bool can_cross_incognito;
43   if (!GetRelevantExtensionTopLevelItems(extension_id, &extension,
44                                          &can_cross_incognito, items))
45     return;
46
47   if (items.empty())
48     return;
49
50   // If this is the first extension-provided menu item, and there are other
51   // items in the menu, and the last item is not a separator add a separator.
52   if (*index == 0 && menu_model_->GetItemCount())
53     menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
54
55   // Extensions (other than platform apps) are only allowed one top-level slot
56   // (and it can't be a radio or checkbox item because we are going to put the
57   // extension icon next to it).
58   // If they have more than that, we automatically push them into a submenu.
59   if (extension->is_platform_app()) {
60     RecursivelyAppendExtensionItems(items, can_cross_incognito, selection_text,
61                                     menu_model_, index);
62   } else {
63     int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
64     base::string16 title;
65     MenuItem::List submenu_items;
66
67     if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) {
68       title = base::UTF8ToUTF16(extension->name());
69       submenu_items = items;
70     } else {
71       MenuItem* item = items[0];
72       extension_item_map_[menu_id] = item->id();
73       title = item->TitleWithReplacement(selection_text,
74                                        kMaxExtensionItemTitleLength);
75       submenu_items = GetRelevantExtensionItems(item->children(),
76                                                 can_cross_incognito);
77     }
78
79     // Now add our item(s) to the menu_model_.
80     if (submenu_items.empty()) {
81       menu_model_->AddItem(menu_id, title);
82     } else {
83       ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
84       extension_menu_models_.push_back(submenu);
85       menu_model_->AddSubMenu(menu_id, title, submenu);
86       RecursivelyAppendExtensionItems(submenu_items, can_cross_incognito,
87                                       selection_text, submenu, index);
88     }
89     SetExtensionIcon(extension_id);
90   }
91 }
92
93 void ContextMenuMatcher::Clear() {
94   extension_item_map_.clear();
95   extension_menu_models_.clear();
96 }
97
98 base::string16 ContextMenuMatcher::GetTopLevelContextMenuTitle(
99     const std::string& extension_id,
100     const base::string16& selection_text) {
101   const Extension* extension = NULL;
102   MenuItem::List items;
103   bool can_cross_incognito;
104   GetRelevantExtensionTopLevelItems(extension_id, &extension,
105       &can_cross_incognito, items);
106
107   base::string16 title;
108
109   if (items.empty() ||
110       items.size() > 1 ||
111       items[0]->type() != MenuItem::NORMAL) {
112     title = base::UTF8ToUTF16(extension->name());
113   } else {
114     MenuItem* item = items[0];
115     title = item->TitleWithReplacement(
116         selection_text, kMaxExtensionItemTitleLength);
117   }
118   return title;
119 }
120
121 bool ContextMenuMatcher::IsCommandIdChecked(int command_id) const {
122   MenuItem* item = GetExtensionMenuItem(command_id);
123   if (!item)
124     return false;
125   return item->checked();
126 }
127
128 bool ContextMenuMatcher::IsCommandIdEnabled(int command_id) const {
129   MenuItem* item = GetExtensionMenuItem(command_id);
130   if (!item)
131     return true;
132   return item->enabled();
133 }
134
135 void ContextMenuMatcher::ExecuteCommand(int command_id,
136     content::WebContents* web_contents,
137     const content::ContextMenuParams& params) {
138   MenuItem* item = GetExtensionMenuItem(command_id);
139   if (!item)
140     return;
141
142   MenuManager* manager = MenuManager::Get(profile_);
143   manager->ExecuteCommand(profile_, web_contents, params, item->id());
144 }
145
146 bool ContextMenuMatcher::GetRelevantExtensionTopLevelItems(
147     const std::string& extension_id,
148     const Extension** extension,
149     bool* can_cross_incognito,
150     MenuItem::List& items) {
151   ExtensionService* service =
152       extensions::ExtensionSystem::Get(profile_)->extension_service();
153   *extension = service->GetExtensionById(extension_id, false);
154
155   if (!*extension)
156     return false;
157
158   // Find matching items.
159   MenuManager* manager = MenuManager::Get(profile_);
160   const MenuItem::List* all_items = manager->MenuItems(extension_id);
161   if (!all_items || all_items->empty())
162     return false;
163
164   *can_cross_incognito = util::CanCrossIncognito(*extension, profile_);
165   items = GetRelevantExtensionItems(*all_items,
166                                     *can_cross_incognito);
167
168   return true;
169 }
170
171 MenuItem::List ContextMenuMatcher::GetRelevantExtensionItems(
172     const MenuItem::List& items,
173     bool can_cross_incognito) {
174   MenuItem::List result;
175   for (MenuItem::List::const_iterator i = items.begin();
176        i != items.end(); ++i) {
177     const MenuItem* item = *i;
178
179     if (!filter_.Run(item))
180       continue;
181
182     if (item->id().incognito == profile_->IsOffTheRecord() ||
183         can_cross_incognito)
184       result.push_back(*i);
185   }
186   return result;
187 }
188
189 void ContextMenuMatcher::RecursivelyAppendExtensionItems(
190     const MenuItem::List& items,
191     bool can_cross_incognito,
192     const base::string16& selection_text,
193     ui::SimpleMenuModel* menu_model,
194     int* index)
195 {
196   MenuItem::Type last_type = MenuItem::NORMAL;
197   int radio_group_id = 1;
198
199   for (MenuItem::List::const_iterator i = items.begin();
200        i != items.end(); ++i) {
201     MenuItem* item = *i;
202
203     // If last item was of type radio but the current one isn't, auto-insert
204     // a separator.  The converse case is handled below.
205     if (last_type == MenuItem::RADIO &&
206         item->type() != MenuItem::RADIO) {
207       menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
208       last_type = MenuItem::SEPARATOR;
209     }
210
211     int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
212     if (menu_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
213       return;
214     extension_item_map_[menu_id] = item->id();
215     base::string16 title = item->TitleWithReplacement(selection_text,
216                                                 kMaxExtensionItemTitleLength);
217     if (item->type() == MenuItem::NORMAL) {
218       MenuItem::List children =
219           GetRelevantExtensionItems(item->children(), can_cross_incognito);
220       if (children.empty()) {
221         menu_model->AddItem(menu_id, title);
222       } else {
223         ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
224         extension_menu_models_.push_back(submenu);
225         menu_model->AddSubMenu(menu_id, title, submenu);
226         RecursivelyAppendExtensionItems(children, can_cross_incognito,
227                                         selection_text, submenu, index);
228       }
229     } else if (item->type() == MenuItem::CHECKBOX) {
230       menu_model->AddCheckItem(menu_id, title);
231     } else if (item->type() == MenuItem::RADIO) {
232       if (i != items.begin() &&
233           last_type != MenuItem::RADIO) {
234         radio_group_id++;
235
236         // Auto-append a separator if needed.
237         menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
238       }
239
240       menu_model->AddRadioItem(menu_id, title, radio_group_id);
241     } else if (item->type() == MenuItem::SEPARATOR) {
242       menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
243     }
244     last_type = item->type();
245   }
246 }
247
248 MenuItem* ContextMenuMatcher::GetExtensionMenuItem(int id) const {
249   MenuManager* manager = MenuManager::Get(profile_);
250   std::map<int, MenuItem::Id>::const_iterator i =
251       extension_item_map_.find(id);
252   if (i != extension_item_map_.end()) {
253     MenuItem* item = manager->GetItemById(i->second);
254     if (item)
255       return item;
256   }
257   return NULL;
258 }
259
260 void ContextMenuMatcher::SetExtensionIcon(const std::string& extension_id) {
261   MenuManager* menu_manager = MenuManager::Get(profile_);
262
263   int index = menu_model_->GetItemCount() - 1;
264   DCHECK_GE(index, 0);
265
266   const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id);
267   DCHECK(icon.width() == gfx::kFaviconSize);
268   DCHECK(icon.height() == gfx::kFaviconSize);
269
270   menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon));
271 }
272
273 }  // namespace extensions