[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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 | |
[email protected] | 112158af | 2013-06-07 23:46:18 | [diff] [blame] | 5 | #include "base/strings/utf_string_conversions.h" |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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" |
[email protected] | a7ff4b7 | 2013-10-17 20:56:02 | [diff] [blame] | 9 | #include "chrome/browser/extensions/extension_util.h" |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 10 | #include "chrome/browser/profiles/profile.h" |
| 11 | #include "content/public/common/context_menu_params.h" |
[email protected] | 59b0e60 | 2014-01-30 00:41:24 | [diff] [blame] | 12 | #include "extensions/browser/extension_system.h" |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 13 | #include "ui/gfx/favicon_size.h" |
[email protected] | f34efa2 | 2013-03-05 19:14:23 | [diff] [blame] | 14 | #include "ui/gfx/image/image.h" |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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 | |
[email protected] | 439f1e3 | 2013-12-09 20:09:09 | [diff] [blame] | 30 | void ContextMenuMatcher::AppendExtensionItems( |
[email protected] | 6f9d2c6 | 2014-03-10 12:12:05 | [diff] [blame] | 31 | const MenuItem::ExtensionKey& extension_key, |
[email protected] | 439f1e3 | 2013-12-09 20:09:09 | [diff] [blame] | 32 | const base::string16& selection_text, |
| 33 | int* index) { |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 34 | DCHECK_GE(*index, 0); |
| 35 | int max_index = |
| 36 | IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST - IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST; |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 37 | if (*index >= max_index) |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 38 | return; |
| 39 | |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 40 | const Extension* extension = NULL; |
| 41 | MenuItem::List items; |
| 42 | bool can_cross_incognito; |
[email protected] | 6f9d2c6 | 2014-03-10 12:12:05 | [diff] [blame] | 43 | if (!GetRelevantExtensionTopLevelItems( |
| 44 | extension_key, &extension, &can_cross_incognito, items)) |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 45 | return; |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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. |
[email protected] | 00491c05 | 2013-02-08 10:53:25 | [diff] [blame] | 52 | if (*index == 0 && menu_model_->GetItemCount()) |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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)++; |
[email protected] | 439f1e3 | 2013-12-09 20:09:09 | [diff] [blame] | 64 | base::string16 title; |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 65 | MenuItem::List submenu_items; |
| 66 | |
| 67 | if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) { |
[email protected] | 0433872 | 2013-12-24 23:18:05 | [diff] [blame] | 68 | title = base::UTF8ToUTF16(extension->name()); |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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 | } |
[email protected] | 6f9d2c6 | 2014-03-10 12:12:05 | [diff] [blame] | 89 | SetExtensionIcon(extension_key.extension_id); |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 90 | } |
| 91 | } |
| 92 | |
| 93 | void ContextMenuMatcher::Clear() { |
| 94 | extension_item_map_.clear(); |
| 95 | extension_menu_models_.clear(); |
| 96 | } |
| 97 | |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 98 | base::string16 ContextMenuMatcher::GetTopLevelContextMenuTitle( |
[email protected] | 6f9d2c6 | 2014-03-10 12:12:05 | [diff] [blame] | 99 | const MenuItem::ExtensionKey& extension_key, |
[email protected] | 439f1e3 | 2013-12-09 20:09:09 | [diff] [blame] | 100 | const base::string16& selection_text) { |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 101 | const Extension* extension = NULL; |
| 102 | MenuItem::List items; |
| 103 | bool can_cross_incognito; |
[email protected] | 6f9d2c6 | 2014-03-10 12:12:05 | [diff] [blame] | 104 | GetRelevantExtensionTopLevelItems( |
| 105 | extension_key, &extension, &can_cross_incognito, items); |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 106 | |
| 107 | base::string16 title; |
| 108 | |
| 109 | if (items.empty() || |
| 110 | items.size() > 1 || |
| 111 | items[0]->type() != MenuItem::NORMAL) { |
[email protected] | 0433872 | 2013-12-24 23:18:05 | [diff] [blame] | 112 | title = base::UTF8ToUTF16(extension->name()); |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 113 | } else { |
| 114 | MenuItem* item = items[0]; |
| 115 | title = item->TitleWithReplacement( |
| 116 | selection_text, kMaxExtensionItemTitleLength); |
| 117 | } |
| 118 | return title; |
| 119 | } |
| 120 | |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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) { |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 138 | MenuItem* item = GetExtensionMenuItem(command_id); |
| 139 | if (!item) |
| 140 | return; |
| 141 | |
[email protected] | b673a594 | 2013-11-14 11:14:19 | [diff] [blame] | 142 | MenuManager* manager = MenuManager::Get(profile_); |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 143 | manager->ExecuteCommand(profile_, web_contents, params, item->id()); |
| 144 | } |
| 145 | |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 146 | bool ContextMenuMatcher::GetRelevantExtensionTopLevelItems( |
[email protected] | 6f9d2c6 | 2014-03-10 12:12:05 | [diff] [blame] | 147 | const MenuItem::ExtensionKey& extension_key, |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 148 | const Extension** extension, |
| 149 | bool* can_cross_incognito, |
| 150 | MenuItem::List& items) { |
| 151 | ExtensionService* service = |
| 152 | extensions::ExtensionSystem::Get(profile_)->extension_service(); |
[email protected] | 6f9d2c6 | 2014-03-10 12:12:05 | [diff] [blame] | 153 | *extension = service->GetExtensionById(extension_key.extension_id, false); |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 154 | |
| 155 | if (!*extension) |
| 156 | return false; |
| 157 | |
| 158 | // Find matching items. |
[email protected] | b673a594 | 2013-11-14 11:14:19 | [diff] [blame] | 159 | MenuManager* manager = MenuManager::Get(profile_); |
[email protected] | 6f9d2c6 | 2014-03-10 12:12:05 | [diff] [blame] | 160 | const MenuItem::List* all_items = manager->MenuItems(extension_key); |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 161 | if (!all_items || all_items->empty()) |
| 162 | return false; |
| 163 | |
[email protected] | 1d5cf414 | 2014-01-24 18:25:22 | [diff] [blame] | 164 | *can_cross_incognito = util::CanCrossIncognito(*extension, profile_); |
[email protected] | 0ea8fac | 2013-06-12 15:31:35 | [diff] [blame] | 165 | items = GetRelevantExtensionItems(*all_items, |
| 166 | *can_cross_incognito); |
| 167 | |
| 168 | return true; |
| 169 | } |
| 170 | |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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, |
[email protected] | 439f1e3 | 2013-12-09 20:09:09 | [diff] [blame] | 192 | const base::string16& selection_text, |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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(); |
[email protected] | 439f1e3 | 2013-12-09 20:09:09 | [diff] [blame] | 215 | base::string16 title = item->TitleWithReplacement(selection_text, |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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. |
[email protected] | 00491c05 | 2013-02-08 10:53:25 | [diff] [blame] | 237 | menu_model->AddSeparator(ui::NORMAL_SEPARATOR); |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 238 | } |
| 239 | |
| 240 | menu_model->AddRadioItem(menu_id, title, radio_group_id); |
| 241 | } else if (item->type() == MenuItem::SEPARATOR) { |
[email protected] | 00491c05 | 2013-02-08 10:53:25 | [diff] [blame] | 242 | menu_model->AddSeparator(ui::NORMAL_SEPARATOR); |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 243 | } |
| 244 | last_type = item->type(); |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | MenuItem* ContextMenuMatcher::GetExtensionMenuItem(int id) const { |
[email protected] | b673a594 | 2013-11-14 11:14:19 | [diff] [blame] | 249 | MenuManager* manager = MenuManager::Get(profile_); |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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) { |
[email protected] | b673a594 | 2013-11-14 11:14:19 | [diff] [blame] | 261 | MenuManager* menu_manager = MenuManager::Get(profile_); |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 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 | |
[email protected] | 32e7a9b | 2013-01-23 23:00:19 | [diff] [blame] | 270 | menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon)); |
[email protected] | 4f8a4d1 | 2012-09-28 19:23:09 | [diff] [blame] | 271 | } |
| 272 | |
| 273 | } // namespace extensions |