OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/extensions/context_menu_matcher.h" | 5 #include "chrome/browser/extensions/context_menu_matcher.h" |
6 | 6 |
7 #include "base/strings/utf_string_conversions.h" | 7 #include "base/strings/utf_string_conversions.h" |
8 #include "chrome/app/chrome_command_ids.h" | 8 #include "chrome/app/chrome_command_ids.h" |
9 #include "chrome/browser/extensions/extension_service.h" | 9 #include "chrome/browser/extensions/extension_service.h" |
10 #include "chrome/browser/extensions/extension_util.h" | 10 #include "chrome/browser/extensions/extension_util.h" |
11 #include "content/public/browser/browser_context.h" | 11 #include "content/public/browser/browser_context.h" |
12 #include "content/public/common/context_menu_params.h" | 12 #include "content/public/common/context_menu_params.h" |
13 #include "extensions/browser/extension_system.h" | 13 #include "extensions/browser/extension_system.h" |
14 #include "ui/gfx/favicon_size.h" | 14 #include "ui/gfx/favicon_size.h" |
15 #include "ui/gfx/image/image.h" | 15 #include "ui/gfx/image/image.h" |
16 | 16 |
17 #if defined(ENABLE_EXTENSIONS) | |
18 #include "chrome/common/extensions/api/context_menus.h" | |
19 #endif | |
20 | |
21 namespace extensions { | 17 namespace extensions { |
22 | 18 |
23 namespace { | 19 namespace { |
24 | 20 |
25 #if defined(ENABLE_EXTENSIONS) | |
26 int action_menu_top_level_limit = | |
27 api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT; | |
28 #else | |
29 int action_menu_top_level_limit = 0; | |
30 #endif | |
31 | |
32 // The range of command IDs reserved for extension's custom menus. | 21 // The range of command IDs reserved for extension's custom menus. |
33 // TODO(oshima): These values will be injected by embedders. | 22 // TODO(oshima): These values will be injected by embedders. |
34 int extensions_context_custom_first = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST; | 23 int extensions_context_custom_first = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST; |
35 int extensions_context_custom_last = IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST; | 24 int extensions_context_custom_last = IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST; |
36 | 25 |
37 } // namespace | 26 } // namespace |
38 | 27 |
39 // static | 28 // static |
40 const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength = 75; | 29 const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength = 75; |
41 | 30 |
(...skipping 15 matching lines...) Expand all Loading... |
57 const base::Callback<bool(const MenuItem*)>& filter) | 46 const base::Callback<bool(const MenuItem*)>& filter) |
58 : browser_context_(browser_context), | 47 : browser_context_(browser_context), |
59 menu_model_(menu_model), | 48 menu_model_(menu_model), |
60 delegate_(delegate), | 49 delegate_(delegate), |
61 filter_(filter) { | 50 filter_(filter) { |
62 } | 51 } |
63 | 52 |
64 void ContextMenuMatcher::AppendExtensionItems( | 53 void ContextMenuMatcher::AppendExtensionItems( |
65 const MenuItem::ExtensionKey& extension_key, | 54 const MenuItem::ExtensionKey& extension_key, |
66 const base::string16& selection_text, | 55 const base::string16& selection_text, |
67 int* index, | 56 int* index) { |
68 bool is_action_menu) { | |
69 DCHECK_GE(*index, 0); | 57 DCHECK_GE(*index, 0); |
70 int max_index = | 58 int max_index = |
71 extensions_context_custom_last - extensions_context_custom_first; | 59 extensions_context_custom_last - extensions_context_custom_first; |
72 if (*index >= max_index) | 60 if (*index >= max_index) |
73 return; | 61 return; |
74 | 62 |
75 const Extension* extension = NULL; | 63 const Extension* extension = NULL; |
76 MenuItem::List items; | 64 MenuItem::List items; |
77 bool can_cross_incognito; | 65 bool can_cross_incognito; |
78 if (!GetRelevantExtensionTopLevelItems( | 66 if (!GetRelevantExtensionTopLevelItems( |
79 extension_key, &extension, &can_cross_incognito, items)) | 67 extension_key, &extension, &can_cross_incognito, items)) |
80 return; | 68 return; |
81 | 69 |
82 if (items.empty()) | 70 if (items.empty()) |
83 return; | 71 return; |
84 | 72 |
85 // If this is the first extension-provided menu item, and there are other | 73 // If this is the first extension-provided menu item, and there are other |
86 // items in the menu, and the last item is not a separator add a separator. | 74 // items in the menu, and the last item is not a separator add a separator. |
87 if (*index == 0 && menu_model_->GetItemCount()) | 75 if (*index == 0 && menu_model_->GetItemCount()) |
88 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); | 76 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); |
89 | 77 |
90 // Extensions (other than platform apps) are only allowed one top-level slot | 78 // Extensions (other than platform apps) are only allowed one top-level slot |
91 // (and it can't be a radio or checkbox item because we are going to put the | 79 // (and it can't be a radio or checkbox item because we are going to put the |
92 // extension icon next to it), unless the context menu is an an action menu. | 80 // extension icon next to it). |
93 // Action menus do not include the extension action, and they only include | 81 // If they have more than that, we automatically push them into a submenu. |
94 // items from one extension, so they are not placed within a submenu. | 82 if (extension->is_platform_app()) { |
95 // Otherwise, we automatically push them into a submenu if there is more than | 83 RecursivelyAppendExtensionItems(items, can_cross_incognito, selection_text, |
96 // one top-level item. | 84 menu_model_, index); |
97 if (extension->is_platform_app() || is_action_menu) { | |
98 RecursivelyAppendExtensionItems(items, | |
99 can_cross_incognito, | |
100 selection_text, | |
101 menu_model_, | |
102 index, | |
103 is_action_menu); | |
104 } else { | 85 } else { |
105 int menu_id = ConvertToExtensionsCustomCommandId(*index); | 86 int menu_id = ConvertToExtensionsCustomCommandId(*index); |
106 (*index)++; | 87 (*index)++; |
107 base::string16 title; | 88 base::string16 title; |
108 MenuItem::List submenu_items; | 89 MenuItem::List submenu_items; |
109 | 90 |
110 if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) { | 91 if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) { |
111 title = base::UTF8ToUTF16(extension->name()); | 92 title = base::UTF8ToUTF16(extension->name()); |
112 submenu_items = items; | 93 submenu_items = items; |
113 } else { | 94 } else { |
114 MenuItem* item = items[0]; | 95 MenuItem* item = items[0]; |
115 extension_item_map_[menu_id] = item->id(); | 96 extension_item_map_[menu_id] = item->id(); |
116 title = item->TitleWithReplacement(selection_text, | 97 title = item->TitleWithReplacement(selection_text, |
117 kMaxExtensionItemTitleLength); | 98 kMaxExtensionItemTitleLength); |
118 submenu_items = GetRelevantExtensionItems(item->children(), | 99 submenu_items = GetRelevantExtensionItems(item->children(), |
119 can_cross_incognito); | 100 can_cross_incognito); |
120 } | 101 } |
121 | 102 |
122 // Now add our item(s) to the menu_model_. | 103 // Now add our item(s) to the menu_model_. |
123 if (submenu_items.empty()) { | 104 if (submenu_items.empty()) { |
124 menu_model_->AddItem(menu_id, title); | 105 menu_model_->AddItem(menu_id, title); |
125 } else { | 106 } else { |
126 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_); | 107 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_); |
127 extension_menu_models_.push_back(submenu); | 108 extension_menu_models_.push_back(submenu); |
128 menu_model_->AddSubMenu(menu_id, title, submenu); | 109 menu_model_->AddSubMenu(menu_id, title, submenu); |
129 RecursivelyAppendExtensionItems(submenu_items, | 110 RecursivelyAppendExtensionItems(submenu_items, can_cross_incognito, |
130 can_cross_incognito, | 111 selection_text, submenu, index); |
131 selection_text, | |
132 submenu, | |
133 index, | |
134 false); // is_action_menu_top_level | |
135 } | 112 } |
136 if (!is_action_menu) | 113 SetExtensionIcon(extension_key.extension_id); |
137 SetExtensionIcon(extension_key.extension_id); | |
138 } | 114 } |
139 } | 115 } |
140 | 116 |
141 void ContextMenuMatcher::Clear() { | 117 void ContextMenuMatcher::Clear() { |
142 extension_item_map_.clear(); | 118 extension_item_map_.clear(); |
143 extension_menu_models_.clear(); | 119 extension_menu_models_.clear(); |
144 } | 120 } |
145 | 121 |
146 base::string16 ContextMenuMatcher::GetTopLevelContextMenuTitle( | 122 base::string16 ContextMenuMatcher::GetTopLevelContextMenuTitle( |
147 const MenuItem::ExtensionKey& extension_key, | 123 const MenuItem::ExtensionKey& extension_key, |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
232 result.push_back(*i); | 208 result.push_back(*i); |
233 } | 209 } |
234 return result; | 210 return result; |
235 } | 211 } |
236 | 212 |
237 void ContextMenuMatcher::RecursivelyAppendExtensionItems( | 213 void ContextMenuMatcher::RecursivelyAppendExtensionItems( |
238 const MenuItem::List& items, | 214 const MenuItem::List& items, |
239 bool can_cross_incognito, | 215 bool can_cross_incognito, |
240 const base::string16& selection_text, | 216 const base::string16& selection_text, |
241 ui::SimpleMenuModel* menu_model, | 217 ui::SimpleMenuModel* menu_model, |
242 int* index, | 218 int* index) |
243 bool is_action_menu_top_level) { | 219 { |
244 MenuItem::Type last_type = MenuItem::NORMAL; | 220 MenuItem::Type last_type = MenuItem::NORMAL; |
245 int radio_group_id = 1; | 221 int radio_group_id = 1; |
246 int num_items = 0; | |
247 | 222 |
248 for (MenuItem::List::const_iterator i = items.begin(); | 223 for (MenuItem::List::const_iterator i = items.begin(); |
249 i != items.end(); ++i) { | 224 i != items.end(); ++i) { |
250 MenuItem* item = *i; | 225 MenuItem* item = *i; |
251 | 226 |
252 // If last item was of type radio but the current one isn't, auto-insert | 227 // If last item was of type radio but the current one isn't, auto-insert |
253 // a separator. The converse case is handled below. | 228 // a separator. The converse case is handled below. |
254 if (last_type == MenuItem::RADIO && | 229 if (last_type == MenuItem::RADIO && |
255 item->type() != MenuItem::RADIO) { | 230 item->type() != MenuItem::RADIO) { |
256 menu_model->AddSeparator(ui::NORMAL_SEPARATOR); | 231 menu_model->AddSeparator(ui::NORMAL_SEPARATOR); |
257 last_type = MenuItem::SEPARATOR; | 232 last_type = MenuItem::SEPARATOR; |
258 } | 233 } |
259 | 234 |
260 int menu_id = ConvertToExtensionsCustomCommandId(*index); | 235 int menu_id = ConvertToExtensionsCustomCommandId(*index); |
261 ++(*index); | 236 (*index)++; |
262 ++num_items; | 237 if (menu_id >= extensions_context_custom_last) |
263 // Action context menus have a limit for top level extension items to | |
264 // prevent control items from being pushed off the screen, since extension | |
265 // items will not be placed in a submenu. | |
266 if (menu_id >= extensions_context_custom_last || | |
267 (is_action_menu_top_level && num_items >= action_menu_top_level_limit)) | |
268 return; | 238 return; |
269 | |
270 extension_item_map_[menu_id] = item->id(); | 239 extension_item_map_[menu_id] = item->id(); |
271 base::string16 title = item->TitleWithReplacement(selection_text, | 240 base::string16 title = item->TitleWithReplacement(selection_text, |
272 kMaxExtensionItemTitleLength); | 241 kMaxExtensionItemTitleLength); |
273 if (item->type() == MenuItem::NORMAL) { | 242 if (item->type() == MenuItem::NORMAL) { |
274 MenuItem::List children = | 243 MenuItem::List children = |
275 GetRelevantExtensionItems(item->children(), can_cross_incognito); | 244 GetRelevantExtensionItems(item->children(), can_cross_incognito); |
276 if (children.empty()) { | 245 if (children.empty()) { |
277 menu_model->AddItem(menu_id, title); | 246 menu_model->AddItem(menu_id, title); |
278 } else { | 247 } else { |
279 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_); | 248 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_); |
280 extension_menu_models_.push_back(submenu); | 249 extension_menu_models_.push_back(submenu); |
281 menu_model->AddSubMenu(menu_id, title, submenu); | 250 menu_model->AddSubMenu(menu_id, title, submenu); |
282 RecursivelyAppendExtensionItems(children, | 251 RecursivelyAppendExtensionItems(children, can_cross_incognito, |
283 can_cross_incognito, | 252 selection_text, submenu, index); |
284 selection_text, | |
285 submenu, | |
286 index, | |
287 false); // is_action_menu_top_level | |
288 } | 253 } |
289 } else if (item->type() == MenuItem::CHECKBOX) { | 254 } else if (item->type() == MenuItem::CHECKBOX) { |
290 menu_model->AddCheckItem(menu_id, title); | 255 menu_model->AddCheckItem(menu_id, title); |
291 } else if (item->type() == MenuItem::RADIO) { | 256 } else if (item->type() == MenuItem::RADIO) { |
292 if (i != items.begin() && | 257 if (i != items.begin() && |
293 last_type != MenuItem::RADIO) { | 258 last_type != MenuItem::RADIO) { |
294 radio_group_id++; | 259 radio_group_id++; |
295 | 260 |
296 // Auto-append a separator if needed. | 261 // Auto-append a separator if needed. |
297 menu_model->AddSeparator(ui::NORMAL_SEPARATOR); | 262 menu_model->AddSeparator(ui::NORMAL_SEPARATOR); |
(...skipping 26 matching lines...) Expand all Loading... |
324 DCHECK_GE(index, 0); | 289 DCHECK_GE(index, 0); |
325 | 290 |
326 const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id); | 291 const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id); |
327 DCHECK(icon.width() == gfx::kFaviconSize); | 292 DCHECK(icon.width() == gfx::kFaviconSize); |
328 DCHECK(icon.height() == gfx::kFaviconSize); | 293 DCHECK(icon.height() == gfx::kFaviconSize); |
329 | 294 |
330 menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon)); | 295 menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon)); |
331 } | 296 } |
332 | 297 |
333 } // namespace extensions | 298 } // namespace extensions |
OLD | NEW |