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