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( 31 const std::string& extension_id, 32 const base::string16& selection_text, 33 int* index) { 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 base::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 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); 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 MenuItem* item = GetExtensionMenuItem(command_id); 139 if (!item) 140 return; 141 142 MenuManager* manager = MenuManager::Get(profile_); 143 manager->ExecuteCommand(profile_, web_contents, params, item->id()); 144 } 145 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); 154 155 if (!*extension) 156 return false; 157 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()) 162 return false; 163 164 *can_cross_incognito = extension_util::CanCrossIncognito(*extension, service); 165 items = GetRelevantExtensionItems(*all_items, 166 *can_cross_incognito); 167 168 return true; 169 } 170 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; 178 179 if (!filter_.Run(item)) 180 continue; 181 182 if (item->id().incognito == profile_->IsOffTheRecord() || 183 can_cross_incognito) 184 result.push_back(*i); 185 } 186 return result; 187 } 188 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, 194 int* index) 195 { 196 MenuItem::Type last_type = MenuItem::NORMAL; 197 int radio_group_id = 1; 198 199 for (MenuItem::List::const_iterator i = items.begin(); 200 i != items.end(); ++i) { 201 MenuItem* item = *i; 202 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; 209 } 210 211 int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++; 212 if (menu_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) 213 return; 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); 222 } else { 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); 228 } 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) { 234 radio_group_id++; 235 236 // Auto-append a separator if needed. 237 menu_model->AddSeparator(ui::NORMAL_SEPARATOR); 238 } 239 240 menu_model->AddRadioItem(menu_id, title, radio_group_id); 241 } else if (item->type() == MenuItem::SEPARATOR) { 242 menu_model->AddSeparator(ui::NORMAL_SEPARATOR); 243 } 244 last_type = item->type(); 245 } 246 } 247 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); 254 if (item) 255 return item; 256 } 257 return NULL; 258 } 259 260 void ContextMenuMatcher::SetExtensionIcon(const std::string& extension_id) { 261 MenuManager* menu_manager = MenuManager::Get(profile_); 262 263 int index = menu_model_->GetItemCount() - 1; 264 DCHECK_GE(index, 0); 265 266 const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id); 267 DCHECK(icon.width() == gfx::kFaviconSize); 268 DCHECK(icon.height() == gfx::kFaviconSize); 269 270 menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon)); 271 } 272 273 } // namespace extensions 274