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