- add sources.
[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_system.h"
10 #include "chrome/browser/extensions/extension_util.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "content/public/common/context_menu_params.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(const std::string& extension_id,
31                                               const string16& selection_text,
32                                               int* index)
33 {
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     string16 title;
65     MenuItem::List submenu_items;
66
67     if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) {
68       title = 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 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 = 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   MenuManager* manager = extensions::ExtensionSystem::Get(profile_)->
139       extension_service()->menu_manager();
140   MenuItem* item = GetExtensionMenuItem(command_id);
141   if (!item)
142     return;
143
144   manager->ExecuteCommand(profile_, web_contents, params, item->id());
145 }
146
147 bool ContextMenuMatcher::GetRelevantExtensionTopLevelItems(
148     const std::string& extension_id,
149     const Extension** extension,
150     bool* can_cross_incognito,
151     MenuItem::List& items) {
152   ExtensionService* service =
153       extensions::ExtensionSystem::Get(profile_)->extension_service();
154   MenuManager* manager = service->menu_manager();
155   *extension = service->GetExtensionById(extension_id, false);
156
157   if (!*extension)
158     return false;
159
160   // Find matching items.
161   const MenuItem::List* all_items = manager->MenuItems(extension_id);
162   if (!all_items || all_items->empty())
163     return false;
164
165   *can_cross_incognito = extension_util::CanCrossIncognito(*extension, service);
166   items = GetRelevantExtensionItems(*all_items,
167                                     *can_cross_incognito);
168
169   return true;
170 }
171
172 MenuItem::List ContextMenuMatcher::GetRelevantExtensionItems(
173     const MenuItem::List& items,
174     bool can_cross_incognito) {
175   MenuItem::List result;
176   for (MenuItem::List::const_iterator i = items.begin();
177        i != items.end(); ++i) {
178     const MenuItem* item = *i;
179
180     if (!filter_.Run(item))
181       continue;
182
183     if (item->id().incognito == profile_->IsOffTheRecord() ||
184         can_cross_incognito)
185       result.push_back(*i);
186   }
187   return result;
188 }
189
190 void ContextMenuMatcher::RecursivelyAppendExtensionItems(
191     const MenuItem::List& items,
192     bool can_cross_incognito,
193     const string16& selection_text,
194     ui::SimpleMenuModel* menu_model,
195     int* index)
196 {
197   MenuItem::Type last_type = MenuItem::NORMAL;
198   int radio_group_id = 1;
199
200   for (MenuItem::List::const_iterator i = items.begin();
201        i != items.end(); ++i) {
202     MenuItem* item = *i;
203
204     // If last item was of type radio but the current one isn't, auto-insert
205     // a separator.  The converse case is handled below.
206     if (last_type == MenuItem::RADIO &&
207         item->type() != MenuItem::RADIO) {
208       menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
209       last_type = MenuItem::SEPARATOR;
210     }
211
212     int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
213     if (menu_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
214       return;
215     extension_item_map_[menu_id] = item->id();
216     string16 title = item->TitleWithReplacement(selection_text,
217                                                 kMaxExtensionItemTitleLength);
218     if (item->type() == MenuItem::NORMAL) {
219       MenuItem::List children =
220           GetRelevantExtensionItems(item->children(), can_cross_incognito);
221       if (children.empty()) {
222         menu_model->AddItem(menu_id, title);
223       } else {
224         ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
225         extension_menu_models_.push_back(submenu);
226         menu_model->AddSubMenu(menu_id, title, submenu);
227         RecursivelyAppendExtensionItems(children, can_cross_incognito,
228                                         selection_text, submenu, index);
229       }
230     } else if (item->type() == MenuItem::CHECKBOX) {
231       menu_model->AddCheckItem(menu_id, title);
232     } else if (item->type() == MenuItem::RADIO) {
233       if (i != items.begin() &&
234           last_type != MenuItem::RADIO) {
235         radio_group_id++;
236
237         // Auto-append a separator if needed.
238         menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
239       }
240
241       menu_model->AddRadioItem(menu_id, title, radio_group_id);
242     } else if (item->type() == MenuItem::SEPARATOR) {
243       menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
244     }
245     last_type = item->type();
246   }
247 }
248
249 MenuItem* ContextMenuMatcher::GetExtensionMenuItem(int id) const {
250   MenuManager* manager = extensions::ExtensionSystem::Get(profile_)->
251       extension_service()->menu_manager();
252   std::map<int, MenuItem::Id>::const_iterator i =
253       extension_item_map_.find(id);
254   if (i != extension_item_map_.end()) {
255     MenuItem* item = manager->GetItemById(i->second);
256     if (item)
257       return item;
258   }
259   return NULL;
260 }
261
262 void ContextMenuMatcher::SetExtensionIcon(const std::string& extension_id) {
263   ExtensionService* service =
264       extensions::ExtensionSystem::Get(profile_)->extension_service();
265   MenuManager* menu_manager = service->menu_manager();
266
267   int index = menu_model_->GetItemCount() - 1;
268   DCHECK_GE(index, 0);
269
270   const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id);
271   DCHECK(icon.width() == gfx::kFaviconSize);
272   DCHECK(icon.height() == gfx::kFaviconSize);
273
274   menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon));
275 }
276
277 }  // namespace extensions