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.
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"
16 namespace extensions
{
19 const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength
= 75;
21 ContextMenuMatcher::ContextMenuMatcher(
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
),
30 void ContextMenuMatcher::AppendExtensionItems(
31 const std::string
& extension_id
,
32 const base::string16
& selection_text
,
36 IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST
- IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST
;
37 if (*index
>= max_index
)
40 const Extension
* extension
= NULL
;
42 bool can_cross_incognito
;
43 if (!GetRelevantExtensionTopLevelItems(extension_id
, &extension
,
44 &can_cross_incognito
, items
))
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
);
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
,
63 int menu_id
= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST
+ (*index
)++;
65 MenuItem::List submenu_items
;
67 if (items
.size() > 1 || items
[0]->type() != MenuItem::NORMAL
) {
68 title
= base::UTF8ToUTF16(extension
->name());
69 submenu_items
= items
;
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(),
79 // Now add our item(s) to the menu_model_.
80 if (submenu_items
.empty()) {
81 menu_model_
->AddItem(menu_id
, title
);
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
);
89 SetExtensionIcon(extension_id
);
93 void ContextMenuMatcher::Clear() {
94 extension_item_map_
.clear();
95 extension_menu_models_
.clear();
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
);
107 base::string16 title
;
111 items
[0]->type() != MenuItem::NORMAL
) {
112 title
= base::UTF8ToUTF16(extension
->name());
114 MenuItem
* item
= items
[0];
115 title
= item
->TitleWithReplacement(
116 selection_text
, kMaxExtensionItemTitleLength
);
121 bool ContextMenuMatcher::IsCommandIdChecked(int command_id
) const {
122 MenuItem
* item
= GetExtensionMenuItem(command_id
);
125 return item
->checked();
128 bool ContextMenuMatcher::IsCommandIdEnabled(int command_id
) const {
129 MenuItem
* item
= GetExtensionMenuItem(command_id
);
132 return item
->enabled();
135 void ContextMenuMatcher::ExecuteCommand(int command_id
,
136 content::WebContents
* web_contents
,
137 const content::ContextMenuParams
& params
) {
138 MenuItem
* item
= GetExtensionMenuItem(command_id
);
142 MenuManager
* manager
= MenuManager::Get(profile_
);
143 manager
->ExecuteCommand(profile_
, web_contents
, params
, item
->id());
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);
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())
164 *can_cross_incognito
= extension_util::CanCrossIncognito(*extension
, service
);
165 items
= GetRelevantExtensionItems(*all_items
,
166 *can_cross_incognito
);
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
;
179 if (!filter_
.Run(item
))
182 if (item
->id().incognito
== profile_
->IsOffTheRecord() ||
184 result
.push_back(*i
);
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
,
196 MenuItem::Type last_type
= MenuItem::NORMAL
;
197 int radio_group_id
= 1;
199 for (MenuItem::List::const_iterator i
= items
.begin();
200 i
!= items
.end(); ++i
) {
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
;
211 int menu_id
= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST
+ (*index
)++;
212 if (menu_id
>= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST
)
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
);
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
);
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
) {
236 // Auto-append a separator if needed.
237 menu_model
->AddSeparator(ui::NORMAL_SEPARATOR
);
240 menu_model
->AddRadioItem(menu_id
, title
, radio_group_id
);
241 } else if (item
->type() == MenuItem::SEPARATOR
) {
242 menu_model
->AddSeparator(ui::NORMAL_SEPARATOR
);
244 last_type
= item
->type();
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
);
260 void ContextMenuMatcher::SetExtensionIcon(const std::string
& extension_id
) {
261 MenuManager
* menu_manager
= MenuManager::Get(profile_
);
263 int index
= menu_model_
->GetItemCount() - 1;
266 const SkBitmap
& icon
= menu_manager
->GetIconForExtension(extension_id
);
267 DCHECK(icon
.width() == gfx::kFaviconSize
);
268 DCHECK(icon
.height() == gfx::kFaviconSize
);
270 menu_model_
->SetIcon(index
, gfx::Image::CreateFrom1xBitmap(icon
));
273 } // namespace extensions