| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/extensions/context_menu_matcher.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_system.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "content/public/common/context_menu_params.h" |
| #include "ui/gfx/favicon_size.h" |
| #include "ui/gfx/image/image.h" |
| |
| namespace extensions { |
| |
| // static |
| const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength = 75; |
| |
| ContextMenuMatcher::ContextMenuMatcher( |
| Profile* profile, |
| ui::SimpleMenuModel::Delegate* delegate, |
| ui::SimpleMenuModel* menu_model, |
| const base::Callback<bool(const MenuItem*)>& filter) |
| : profile_(profile), menu_model_(menu_model), delegate_(delegate), |
| filter_(filter) { |
| } |
| |
| void ContextMenuMatcher::AppendExtensionItems( |
| const std::string& extension_id, |
| const base::string16& selection_text, |
| int* index) { |
| DCHECK_GE(*index, 0); |
| int max_index = |
| IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST - IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST; |
| if (*index >= max_index) |
| return; |
| |
| const Extension* extension = NULL; |
| MenuItem::List items; |
| bool can_cross_incognito; |
| if (!GetRelevantExtensionTopLevelItems(extension_id, &extension, |
| &can_cross_incognito, items)) |
| return; |
| |
| if (items.empty()) |
| return; |
| |
| // If this is the first extension-provided menu item, and there are other |
| // items in the menu, and the last item is not a separator add a separator. |
| if (*index == 0 && menu_model_->GetItemCount()) |
| menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); |
| |
| // Extensions (other than platform apps) are only allowed one top-level slot |
| // (and it can't be a radio or checkbox item because we are going to put the |
| // extension icon next to it). |
| // If they have more than that, we automatically push them into a submenu. |
| if (extension->is_platform_app()) { |
| RecursivelyAppendExtensionItems(items, can_cross_incognito, selection_text, |
| menu_model_, index); |
| } else { |
| int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++; |
| base::string16 title; |
| MenuItem::List submenu_items; |
| |
| if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) { |
| title = UTF8ToUTF16(extension->name()); |
| submenu_items = items; |
| } else { |
| MenuItem* item = items[0]; |
| extension_item_map_[menu_id] = item->id(); |
| title = item->TitleWithReplacement(selection_text, |
| kMaxExtensionItemTitleLength); |
| submenu_items = GetRelevantExtensionItems(item->children(), |
| can_cross_incognito); |
| } |
| |
| // Now add our item(s) to the menu_model_. |
| if (submenu_items.empty()) { |
| menu_model_->AddItem(menu_id, title); |
| } else { |
| ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_); |
| extension_menu_models_.push_back(submenu); |
| menu_model_->AddSubMenu(menu_id, title, submenu); |
| RecursivelyAppendExtensionItems(submenu_items, can_cross_incognito, |
| selection_text, submenu, index); |
| } |
| SetExtensionIcon(extension_id); |
| } |
| } |
| |
| void ContextMenuMatcher::Clear() { |
| extension_item_map_.clear(); |
| extension_menu_models_.clear(); |
| } |
| |
| base::string16 ContextMenuMatcher::GetTopLevelContextMenuTitle( |
| const std::string& extension_id, |
| const base::string16& selection_text) { |
| const Extension* extension = NULL; |
| MenuItem::List items; |
| bool can_cross_incognito; |
| GetRelevantExtensionTopLevelItems(extension_id, &extension, |
| &can_cross_incognito, items); |
| |
| base::string16 title; |
| |
| if (items.empty() || |
| items.size() > 1 || |
| items[0]->type() != MenuItem::NORMAL) { |
| title = UTF8ToUTF16(extension->name()); |
| } else { |
| MenuItem* item = items[0]; |
| title = item->TitleWithReplacement( |
| selection_text, kMaxExtensionItemTitleLength); |
| } |
| return title; |
| } |
| |
| bool ContextMenuMatcher::IsCommandIdChecked(int command_id) const { |
| MenuItem* item = GetExtensionMenuItem(command_id); |
| if (!item) |
| return false; |
| return item->checked(); |
| } |
| |
| bool ContextMenuMatcher::IsCommandIdEnabled(int command_id) const { |
| MenuItem* item = GetExtensionMenuItem(command_id); |
| if (!item) |
| return true; |
| return item->enabled(); |
| } |
| |
| void ContextMenuMatcher::ExecuteCommand(int command_id, |
| content::WebContents* web_contents, |
| const content::ContextMenuParams& params) { |
| MenuItem* item = GetExtensionMenuItem(command_id); |
| if (!item) |
| return; |
| |
| MenuManager* manager = MenuManager::Get(profile_); |
| manager->ExecuteCommand(profile_, web_contents, params, item->id()); |
| } |
| |
| bool ContextMenuMatcher::GetRelevantExtensionTopLevelItems( |
| const std::string& extension_id, |
| const Extension** extension, |
| bool* can_cross_incognito, |
| MenuItem::List& items) { |
| ExtensionService* service = |
| extensions::ExtensionSystem::Get(profile_)->extension_service(); |
| *extension = service->GetExtensionById(extension_id, false); |
| |
| if (!*extension) |
| return false; |
| |
| // Find matching items. |
| MenuManager* manager = MenuManager::Get(profile_); |
| const MenuItem::List* all_items = manager->MenuItems(extension_id); |
| if (!all_items || all_items->empty()) |
| return false; |
| |
| *can_cross_incognito = extension_util::CanCrossIncognito(*extension, service); |
| items = GetRelevantExtensionItems(*all_items, |
| *can_cross_incognito); |
| |
| return true; |
| } |
| |
| MenuItem::List ContextMenuMatcher::GetRelevantExtensionItems( |
| const MenuItem::List& items, |
| bool can_cross_incognito) { |
| MenuItem::List result; |
| for (MenuItem::List::const_iterator i = items.begin(); |
| i != items.end(); ++i) { |
| const MenuItem* item = *i; |
| |
| if (!filter_.Run(item)) |
| continue; |
| |
| if (item->id().incognito == profile_->IsOffTheRecord() || |
| can_cross_incognito) |
| result.push_back(*i); |
| } |
| return result; |
| } |
| |
| void ContextMenuMatcher::RecursivelyAppendExtensionItems( |
| const MenuItem::List& items, |
| bool can_cross_incognito, |
| const base::string16& selection_text, |
| ui::SimpleMenuModel* menu_model, |
| int* index) |
| { |
| MenuItem::Type last_type = MenuItem::NORMAL; |
| int radio_group_id = 1; |
| |
| for (MenuItem::List::const_iterator i = items.begin(); |
| i != items.end(); ++i) { |
| MenuItem* item = *i; |
| |
| // If last item was of type radio but the current one isn't, auto-insert |
| // a separator. The converse case is handled below. |
| if (last_type == MenuItem::RADIO && |
| item->type() != MenuItem::RADIO) { |
| menu_model->AddSeparator(ui::NORMAL_SEPARATOR); |
| last_type = MenuItem::SEPARATOR; |
| } |
| |
| int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++; |
| if (menu_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) |
| return; |
| extension_item_map_[menu_id] = item->id(); |
| base::string16 title = item->TitleWithReplacement(selection_text, |
| kMaxExtensionItemTitleLength); |
| if (item->type() == MenuItem::NORMAL) { |
| MenuItem::List children = |
| GetRelevantExtensionItems(item->children(), can_cross_incognito); |
| if (children.empty()) { |
| menu_model->AddItem(menu_id, title); |
| } else { |
| ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_); |
| extension_menu_models_.push_back(submenu); |
| menu_model->AddSubMenu(menu_id, title, submenu); |
| RecursivelyAppendExtensionItems(children, can_cross_incognito, |
| selection_text, submenu, index); |
| } |
| } else if (item->type() == MenuItem::CHECKBOX) { |
| menu_model->AddCheckItem(menu_id, title); |
| } else if (item->type() == MenuItem::RADIO) { |
| if (i != items.begin() && |
| last_type != MenuItem::RADIO) { |
| radio_group_id++; |
| |
| // Auto-append a separator if needed. |
| menu_model->AddSeparator(ui::NORMAL_SEPARATOR); |
| } |
| |
| menu_model->AddRadioItem(menu_id, title, radio_group_id); |
| } else if (item->type() == MenuItem::SEPARATOR) { |
| menu_model->AddSeparator(ui::NORMAL_SEPARATOR); |
| } |
| last_type = item->type(); |
| } |
| } |
| |
| MenuItem* ContextMenuMatcher::GetExtensionMenuItem(int id) const { |
| MenuManager* manager = MenuManager::Get(profile_); |
| std::map<int, MenuItem::Id>::const_iterator i = |
| extension_item_map_.find(id); |
| if (i != extension_item_map_.end()) { |
| MenuItem* item = manager->GetItemById(i->second); |
| if (item) |
| return item; |
| } |
| return NULL; |
| } |
| |
| void ContextMenuMatcher::SetExtensionIcon(const std::string& extension_id) { |
| MenuManager* menu_manager = MenuManager::Get(profile_); |
| |
| int index = menu_model_->GetItemCount() - 1; |
| DCHECK_GE(index, 0); |
| |
| const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id); |
| DCHECK(icon.width() == gfx::kFaviconSize); |
| DCHECK(icon.height() == gfx::kFaviconSize); |
| |
| menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon)); |
| } |
| |
| } // namespace extensions |